Logo wiki

Kafka Connect는 왜 변화가 없으면 offset을 기록하지 않을까

July 9, 2025
4 min read
Table of Contents
index

찾아보게 된 이유

Quote

2022년 여름의 어느 날, 자고 일어났더니 메인 DB의 CPU 사용률이 80%가 넘었다는 알림이 슬랙에 남아 있었습니다.
알람이 울린 시점에는 지표 상 트래픽이 튀지 않았습니다.

connector가 마지막으로 읽은 binlog 위치를 상시로 기록하는 것이 아니라 connector가 추적하는 테이블에 변경사항이 있을 때만 기록할 수도 있겠다는 생각이 들었습니다.

heartbeat.interval.ms 옵션을 제공하면 옵션으로 제공된 주기마다 debezium이 connector의 상태를 기록하는 것을 알 수 있었습니다.

위 문장을 기술 블로그에서 읽고, DB CPU 부하를 유발하는 컴포넌트가 왜 이전에 읽었던 지점을 사전에 기록하지 않는지 궁금해져, 관련 내용을 조사하고 기술 블로그 내용을 간략히 정리했습니다.

DB 변경 로그를 읽는 컴포넌트

"데이터가 변경되었음"이라는 메세지를 여러 시스템에 전파하기 위해, 데이터 변경 관련된 소스(ex. MySQL의 binlog)를 추적합니다.
이 역할을 수행하는 컴포넌트 중 하나인 Debezium connector는 binlog의 변경 이벤트를 순차적으로 읽어들이며, 변경이 발생한 지점(즉, binlog 상의 위치)을 offset으로 기록합니다.
이 offset은 connector가 재시작될 때 어디서부터 binlog를 다시 읽어야 하는지를 결정하는 기준점으로 사용됩니다.

DB 부하를 유발한 오래된 offset

MySQL의 binlog는 모든 테이블의 변경 사항이 기록되는 파일입니다.
이와 더불어, offset이 데이터 변경이 발생한 위치를 나타내는 binlog 내 특정 지점이라는 점을 종합해 보면, 데이터 변경이 드문 테이블을 추적할 경우에는 offset이 오래된 값일 가능성이 높다는 것을 알 수 있습니다.
따라서 connector가 재시작하면 이 오래된 offset 이후의 변경 사항들을 다시 읽게 되며, 이 과정에서 DB CPU에 부하가 발생할 수 있습니다.

데이터 변경이 발생하지 않아도, 읽은 지점을 기록하게 하는 heartbeat 옵션

기술 블로그에서는 변경이 잦지 않은 테이블이 가질 수 있는 오래된 offset 값이 DB 부하의 원인이라는 점에 주목했습니다.
이에 변경이 없더라도 connector가 주기적으로 로그 파일의 현재 위치를 읽고 offset을 기록하도록 heartbeat 옵션을 적용했고, 이를 통해 해결할 수 있었습니다.

왜 기본적으로 읽은 위치를 바로 offset으로 기록하지 않을까?

글을 읽으며 궁금했던 점은, binlog를 읽어 데이터 변경을 감지하므로 새로운 로그가 추가될 때마다 해당 위치를 읽게 되는데, 왜 offset을 항상 기록하지 않고 오직 Kafka에 메시지를 보낼 때만 기록하는지, 그로 인해 중복 읽기가 발생하는 현상이 왜 생기는가 하는 점이었습니다.

그 이유는 At least once 처리 보장을 위해서입니다.
만약 메시지가 정상적으로 처리되지 않았는데 offset이 미리 기록된다면, 장애 발생 시 메시지가 유실될 수 있습니다.
즉, binlog를 읽었지만 전송되지 않은 위치가 offset으로 저장한다면, 재시작 시 그 이후 위치부터 읽기 때문에 아직 Kafka에 발행되지 않은 메시지가 사라지는 문제가 발생합니다.

따라서 offset은 단순히 connector가 읽은 위치가 아니라, 읽은 후에 메시지가 외부 시스템에 정상적으로 전송된 위치를 의미합니다. 그래서 변경이 발생해 메시지가 전송된 경우에만 offset을 기록합니다.
물론 offset을 미리 기록하는 방식을 사용할 수도 있지만, 이 경우 상태 머신이나 롤백 같은 추가적인 복잡한 동기화 기법이 필요해집니다.

이러한 복잡성을 줄이고 처리 흐름을 단순화하기 위해, "Kafka로 실제 전송된 메시지가 없으면 offset도 저장하지 않는다"라는 원칙을 적용한 것입니다.

그렇다면 heartbeat 옵션을 켜면, 전송하지 않은 위치도 offset으로 기록되어 설계 원칙을 깨는 것이 아닌가?

위 사실을 알게 된 후, heartbeat 옵션을 켜면 offset이 실제 데이터가 전송되지 않은 위치도 기록되는 것처럼 보여, 단순한 흐름을 유지하고자 하는 설계 철학을 깨뜨리는 것은 아닌가 하는 궁금증이 생겼습니다.

하지만 여기에는 기발한 트릭이 사용되었습니다.
heartbeat 옵션은 실제로 필요하지 않은 데이터를 별도의 토픽(heartbeat 토픽)에 주기적으로 전송합니다.
이는 오래된 offset으로 인한 재처리 부하를 방지하기 위한 트릭으로, Kafka Connect의 “전송된 메시지만 offset으로 기록한다”는 설계 철학을 지키면서도, 변경이 없는 구간에서도 offset을 주기적으로 갱신할 수 있게 해줍니다.

느낀 점

Kafka source connector도 단순한 패러다임을 유지하고자 하였기에, 옵션을 통해 복잡한 설계 대신 보다 효과적인 방식을 제공할 수 있다는 점이 인상적이었습니다.
“결정되지 않은 사항이 많은 아키텍처가 좋은 설계”라는 문장을 본 적이 있는데, 개발할 때도 복잡한 로직보다는 단순한 흐름을 유지하면서 예상되는 문제에 대해 옵션으로 대응하는 방식이 더욱 유연하고 실용적일 것이라 생각합니다.

또한, 해당 기술 블로그에서 장애를 찾아가는 과정과 회고를 상세히 기록한 점이 매우 인상 깊었습니다.

참고 링크