Kafka의 메시지 처리에서 가장 중요한 요소는 “Offset을 어떻게 관리하느냐”다.
Offset은 단순한 숫자 같지만, 실제로는 중복 처리, 유실 방지, 재처리, 장애 복구, Rebalance 안정성을 좌우하는 핵심 개념이다.
Consumer의 모든 동작은 Offset을 기준으로 이루어진다.
이 글에서는 Offset의 의미부터 Commit 방식, Auto/Manual commit 차이, Commit 지연 시 실제 발생하는 문제, Rebalance와의 관계, 그리고 At-most-once / At-least-once / Exactly-once 동작 방식까지 체계적으로 정리한다.
1. Offset이란 무엇인가?
Offset은 Partition 내 메시지의 고유 번호이다.
메시지가 Partition에 append될 때마다 Offset은 0부터 시작해 1씩 증가한다.
- Partition 0: 0, 1, 2, 3 …
- Partition 1: 0, 1, 2, 3 …
- Partition마다 독립적인 Offset 시퀀스가 존재
Kafka는 메시지를 삭제하더라도 Offset 번호를 재사용하지 않는다.
즉, Offset은 파일의 라인 번호 같은 개념이다.
왜 중요한가?
Consumer는 Offset을 기준으로 “어디까지 읽었는지”를 판단한다.
즉, Offset 관리는 곧 메시지 처리 상태 관리다.

그림으로 다시보자.
- Producer는 Partition의 맨 끝에 새로운 메시지를 append하며, 메시지는 0부터 연속된 offset 번호를 갖는다.
- Consumer A와 Consumer B는 같은 Partition을 읽지만, 각자 다른 위치(offset)를 기준으로 읽고 있다.
- Kafka는 Consumer가 읽은 offset을 기반으로 “어디까지 처리했는지”를 관리하게 되고, 이는 메시지 처리 상태 추적의 핵심이 된다.
2. Consumer는 Offset을 어떻게 추적하는가?
Consumer는 두 가지 상태를 관리한다.
(1) 현재 처리한 Offset (processed offset)
Consumer가 실제로 데이터를 읽고 처리 완료한 위치
(2) Commit된 Offset (committed offset)
Kafka에 “여기까지 읽었음”이라고 공식적으로 저장한 위치
Kafka는 이 정보를 __consumer_offsets라는 내부 토픽에 저장한다.
즉, 처리한 offset과 Commit된 offset은 같을 수도 있고 다를 수도 있다.
Consumer가 재시작하면 “Commit된 Offset”부터 다시 읽는다.
어떻게 이런 상황들이 발생할까 ?
- Consumer는 메시지를 처리(process)한 시점과 Kafka에 commit 기록하는 시점이 서로 다르다.
- 처리 도중 장애가 나면 processed offset은 증가했지만 commit은 못 해서 둘이 달라진다.
- 반대로 auto commit이 먼저 일어나면 처리 전에 commit이 저장되어 메시지 유실이 발생할 수도 있다.
- 따라서 Consumer 재시작 시에는 항상 committed offset 기준으로 다시 읽기 때문에 두 값의 차이가 발생한다.

그림으로 다시보자.
- Consumer는 offset 2까지만 commit했지만, poll()로 3~11까지 읽어 처리 중이다.
- commit되지 않은 3~10 구간은 리밸런스나 재시작 시 다시 읽혀 중복 처리가 발생할 수 있다.
- 오른쪽의 offset 10은 현재 처리 중인 이벤트이며, commit 지점과 processed 지점의 차이가 중복 발생 영역임을 보여준다.
3. Commit 구조 – Auto Commit vs Manual Commit
Commit은 “어디까지 처리했는지 Kafka에게 저장하는 일”이다.
Commit 방식은 크게 두 가지다.
A. Auto Commit
기본 설정: enable.auto.commit=true
Kafka Consumer 라이브러리가 일정 주기마다 자동 커밋한다.
동작 방식:
- poll()으로 데이터를 읽는다
- 처리 여부와 상관없이 주기마다 offset commit
장점:
단점:
- 메시지가 실제로 처리되지 않았는데도 commit될 위험
- 장애 시 메시지가 유실될 가능성
- 실무에서 거의 사용하지 않음
Auto commit은 편하지만 정확한 처리가 필요한 시스템에서는 위험하다.
B. Manual Commit
enable.auto.commit=false
개발자가 직접 commit 시점을 제어한다.
종류는 두 가지:
- Sync commit (commitSync)
- Async commit (commitAsync)
commitSync
Kafka에 commit 요청을 보낸 뒤, commit이 성공할 때까지 기다린다.
장점:
단점:
- 속도가 느릴 수 있음
- 대량 스트림 처리에서는 성능 저하 가능
commitAsync
Kafka에게 commit 요청만 보내고 결과를 기다리지 않는다.
장점:
단점:
- commit이 실제로 실패했을 때 복구가 어려움
- 순서 보장이 어렵기도 함
실무에서는 다음과 같이 섞어서 사용한다.
- 일반 메시지 처리: commitAsync
- 종료 직전 / Rebalance 직전: commitSync
- 종료직전과 Rebalance 직전을 어떻게알까 ?
- 애플리케이션 종료 타이밍은 JVM의 Shutdown Hook(try-with-resources, SIGTERM 등)을 등록하여 감지하고 종료 직전 commitSync()를 호출
- 리밸런스 직전 타이밍은 Kafka가 제공하는 ConsumerRebalanceListener의 onPartitionsRevoked() 콜백에서 감지됨
- 즉, 종료는 OS/JVM 신호로, 리밸런스는 Kafka 내부 프로토콜 이벤트로 각각 자동 감지됨
Rebalance란 무엇인가?
→ Rebalance는 Consumer Group 내부에서 Partition 할당이 변경되는 작업을 뜻한다.
그럼 언제 Rebalance가 발생할까?
- Consumer Group에 새로운 Consumer가 추가됨 → 파티션 재분배
- Consumer 하나가 죽음 → 남은 Consumer에게 파티션 재배분
- Consumer가 너무 오래 poll() 안 함 → 그룹에서 제외됨
- 구독한 Topic의 Partition 개수가 증가함
4. Offset commit 실패·지연 시 실제로 발생하는 문제
Offset commit이 중요하다는 건 이 문제를 보면 바로 이해된다.
A. Commit이 너무 빠르면 → 메시지 유실
예:
- offset=10까지 commit
- 메시지 11~20 읽음
- 아직 처리 중인데 장애 발생
Consumer 재시작하면 offset=10 다음인 11부터 읽어야 한다.
하지만 11~20은 이미 처리 중이었지만 commit되지 않았기 때문에 다시 읽게 된다.
반대로, commit을 처리가 끝나기 전 미리 해버리면?
- offset=20까지 commit
- 21~30 읽는 중 장애 발생
- 재시작 시 offset=21부터 읽음
하지만 21~30 처리 도중 실패했는데 commit은 이미 20까지 되어 있으므로
그 전에 메시지를 잃을 위험이 있다.
B. Commit이 너무 느리면 → 메시지 중복 처리
Commit이 지연되는 경우:
- offset=20까지 commit
- 21~30 읽고 처리 완료
- commitSync 지연 발생
- 장애 발생
다시 시작하면 offset=20 다음인 21부터 다시 읽는다 → 중복 처리
Kafka에서는 특별한 작업 없이도 중복 처리가 쉽게 발생한다.
그래서 idempotent(멱등성) 구현이 매우 중요하다.
5. Rebalance와 Offset의 관계
Consumer Group은 동적으로 consumer 개수가 변화할 수 있고, Partition은 Rebalance 과정에서 재할당된다.
Rebalance가 발생하면
- Consumer는 poll 종료
- 모든 Consumer가 현재 처리한 메시지를 정리
- 각 Consumer는 마지막 상태를 commit
- GroupCoordinator가 파티션 재할당
즉, Rebalance의 안정성은 commit 시점에 달려 있다.
Rebalance 전에 commit하지 않은 메시지는 다른 Consumer에게 넘어가면서 중복 처리될 가능성이 있다.
이 때문에 실무에서는 Rebalance Listener를 활용해 Rebalance 시작 시 commitSync를 수행하는 패턴을 많이 사용한다.
6. At-most-once / At-least-once / Exactly-once 메시지 처리 모델
Kafka는 Offset commit 방식에 따라 메시지 처리 모델이 달라진다.
A. At-most-once (최대 한 번 처리 → 유실 가능성 있음)
동작:
- 메시지 읽기 전 commit
- 처리
- 실패해도 이미 commit 되어 있음
특징:
- 빠르지만 신뢰성이 낮음
- 유실 가능성 존재
- 실무에서 잘 사용하지 않음
B. At-least-once (최소 한 번 처리 → 중복 가능성)
동작:
- 메시지 처리
- 처리 후 commit
장점:
단점:
- 중복 처리 발생 가능
- 대부분 Kafka Consumer 기본 모델
오늘날 기업들의 90% 이상 Kafka 아키텍처가 이 모델을 사용한다.
C. Exactly-once (정확히 한 번 처리)
Kafka Streams나 Transactional Producer에서 제공하는 모델이다.
Producer + Consumer 조합에서 Exactly-once 구현하려면:
- Transactional Producer
- idempotent 메시지 처리
- atomic commit
이 구조는 까다롭고 운영 난이도도 있다.
그래서 “일반 Consumer → DB insert” 같은 시나리오에서는
대부분 At-least-once로 처리하고
비즈니스 레이어에서 중복 제거(idempotence) 처리를 한다.
7. 정리
Offset은 단순한 번호가 아니라 Kafka 메시지 처리의 기준점이다.
Offset을 commit하는 방식에 따라 Kafka는 다음처럼 동작한다.
- Auto commit → 편하지만 메시지 유실 위험
- Manual commit → 안정적이지만 개발 비용 증가
- commitSync / commitAsync 각각 장단점 존재
- commit 타이밍 잘못 잡으면 중복 또는 유실 발생
- Rebalance 안정성은 commit 시점에 좌우
- Kafka의 기본 모델은 "At-least-once"
- Exactly-once는 Streams나 Transactional Producer에서만
Offset을 정확히 이해하면 “Kafka 메시지가 왜 중복되었는지, 왜 유실되었는지”를 99% 원인 분석할 수 있게 된다.
참고문헌
https://sjh9708.tistory.com/269
https://shubhamagtech.home.blog/2019/07/31/kafka-offset-management/