오프셋과 커밋 관리
오프셋과 커밋 관리
카프카에서 오프셋(offset) 은 로그 안에서 컨슈머가 현재 읽고 있는 위치를 가리키는 번호예요. 컨슈머는 fetch 요청을 보낼 때마다 자신의 오프셋을 로그에 지정하고, 그 위치부터 시작하는 로그 덩어리를 돌려받아요. 그래서 컨슈머가 데이터가 저장된 위치를 얼마나 잘 관리하느냐가 곧 소비의 정확성과 직결돼요.
출처: https://kafka.apache.org/43/design/design/#consumer-position
본문
카프카의 파티션은 완전히 정렬된 로그예요. 각 파티션은 각 consumer group 안에서 어떤 순간에는 정확히 하나의 컨슈머가 소비해요. 그러니까 컨슈머 그룹이 파티션을 멤버들에게 어떻게 배정하는가가 정렬(ordering) 과 확장성(scalability) 을 동시에 주는 강력한 조합이 되는 거예요.
컨슈머는 자신이 읽은 위치를 오프셋으로 추적해요. 오프셋을 저장하는 일을 커밋(commit) 이라고 하는데, 카프카는 컨슈머가 커밋한 오프셋을 브로커에 기록해 둬요. 만약 컨슈머가 죽었다가 다시 살아나거나, 같은 그룹의 다른 컨슈머가 이어서 처리하게 되면 이 커밋된 오프셋부터 다시 읽기 시작해요. 그래서 데이터를 정확히 이어서 처리할 수 있어요.
커밋 시점을 어떻게 잡느냐에 따라 전달 의미론(delivery semantics)이 달라져요. 예를 들어 컨슈머가 배치(batch)를 처리한 후에 오프셋을 커밋하면, 만약 실패한다면 이미 처리된 메시지를 다시 읽게 되어 at-least-once 가 돼요. 반대로 배치를 처리하기 전에 오프셋을 커밋하면 실패 시 그 배치를 잃게 되어 at-most-once 가 되고요. 기본 자동 커밋(enable.auto.commit)을 켜두면 카프카가 주기적으로 오프셋을 커밋해주지만, 정확성을 원하면 enable.auto.commit=false 로 끄고 어플리케이션이 직접 커밋 타이밍을 제어하는 게 좋아요.
커밋된 오프셋을 직접 다루는 방법도 있어요. KafkaConsumer.committed 로 카프카에 저장된 커밋 오프셋을 조회하고, KafkaConsumer.seek 로 현재 위치를 특정 오프셋으로 되감거나 앞으로 이동시킬 수 있어요. 예를 들어 트랜잭션 메시지 복사기처럼 좀 더 세밀한 제어가 필요한 어플리케이션은 이 방법을 사용하면 돼요. 이렇게 오프셋을 마음대로 되감을 수 있기 때문에, 실패한 처리 구간을 처음부터 다시 재처리하는 재처리(reprocessing)도 가능해져요.
오프셋이 저장되는 구조가 최근에는 바뀌었어요. 예전에는 consumer group 오프셋을 카프카 내부 토픽 __consumer_offsets 에 저장했는데, 지금은 그룹 코디네이터가 메타데이터와 함께 관리해요. 어쨌든 핵심은 오프셋을 카프카가 안전하게 보관해 준다는 점이고, 그것 덕분에 컨슈머가 장애가 나도 정확한 위치부터 이어갈 수 있다는 거예요.
더 알아보기
- 정확히 한 번 처리를 위해 오프셋 커밋을 조율하는 방법은 [메시지 전달 의미론] 문서에서 봐요.
- 컨슈머가 파티션을 읽는 동작 방식은 [프로듀서와 컨슈머] 문서를 참고해요.
- 컨슈머의 커밋 관련 설정 항목은
consumer-config문서를 확인해요.