티스토리 뷰
운영 중인 카프카 토픽의 처리량이 증가하면 파티션을 증설하고, 그에 맞춰 컨슈머도 1:1로 함께 늘리는 경우가 많습니다. 이때 컨슈머의 auto.offset.reset 값을 어떻게 설정하느냐에 따라 메시지 유실 여부가 달라지는데, 이를 이번 포스팅에서 정리해보려고 합니다.
1. 파티션과 컨슈머의 관계, 그리고 auto.offset.reset 옵션
[ 파티션과 컨슈머의 일반적인 관계 (1:1 균형 상태) ]
기본적으로 컨슈머는 파티션과 1대1 관계를 유지해야 한다. 다음의 그림은 파티션 3개와 컨슈머 3대가 1:1로 매핑된 안정적인 운영 상태로, 컨슈머 그룹이 각 파티션을 균등하게 나눠 담당하므로 처리량이 잘 분산되고, 모든 파티션은 이미 자신만의 커밋된 오프셋을 가지고 있다.

만약 컨슈머의 수가 파티션의 수보다 많다면, 복수 개의 컨슈머가 하나의 파티션을 나누어 처리해야 하는 상황이 된다. 따라서 남는 컨슈머는 유휴 (idle) 상태이므로 불필요하게 CPU와 메모리를 낭비만 하는 상황에 해당한다.

그렇다면 파티션의 수가 컨슈머의 수보다 많다면 어떻게 될까? 이때는 일부 컨슈머가 여러 파티션을 담당하므로 처리 병목을 유발하여 부하가 불균등해진다.

이러한 이유로 카프카는 파티션과 컨슈머의 수를 1대1로 유지할 것을 권장하는 것이다.
[ auto.offset.reset 옵션 ]
auto.offset.reset 옵션은 초기 오프셋이 없거나, 휴효하지 않은 경우 데이터를 어디서부터 읽을 것인가를 결정하는 컨슈머 클라이언트 측 설정이다. 이 설정은 특정한 토픽에 대해 컨슈머 그룹 기준으로, 파티션에 커밋된 오프셋이 존재하지 않을 때만 적용된다.
| 값 | 동작 |
| earliest | 가장 처음 오프셋부터 |
| latest(default) | 가장 마지막 오프셋부터 |
| none | 예외 발생시킴 |
특정한 토픽에 대해 새로운 컨슈머 그룹이 파티션에 연결되면, 커밋된 오프셋이 존재하지 않으므로 auto.offset.reset 옵션에 따라 데이터를 읽는 시점이 결정된다. 만약 이미 쌓인 데이터가 많은 파티션에 컨슈머 그룹을 연결할 때, "earliest" 옵션으로 연결한다면 어떻게 될까? 이전에 쌓였던 모든 데이터를 읽어 처리하려고 시도할 것이고, 데이터가 너무 많을 경우 최신의 데이터를 처리하지 못하고 과거의 데이터만 처리하느라 오랜 시간을 쏟을 수 있어 주의가 필요하다. 다행히 기본값은 latest이기 때문에, 초기 컨슈머들을 연결하는 시점에 굳이 earliest로 설정해주는 상황만 아니라면 크게 문제되지는 않을 것이다.
이미 운영 중인 컨슈머에 기능을 수정하여 배포하는 상황이라면 어떨까? 이미 파티션 별로 커밋된 오프셋이 존재하므로, 해당 설정값이 적용되는 것이 아니라 마지막 커밋된 오프셋부터 데이터를 읽을 것이다.
하지만 문제는 발행되는 메시지의 수가 늘어나면서, 파티션에 데이터가 지나치게 빠르게 쌓이는 상황이 생기면서부터 시작된다.
2. 파티션 증설 시 컨슈머의 auto.offset.reset 설정
[ 파티션 증설 직후 (불균형 + 새 파티션 발생) ]
특정한 파티션에 데이터가 쌓이는 속도가 컨슈머의 처리 속도보다 빠른 상황이 존재할 수 있다. 그러면 파티션에 존재하는 데이터 처리가 점점 밀리게 되는데, 이를 랙(Lag)이라고 부른다.

이러한 경우에는 기본적으로 컨슈머의 처리 속도를 늘려야 하지만, 컨슈머의 처리 속도를 극대화했는데도 불구하고 랙이 계속 쌓여가는 상황이라면 파티션 증설을 고려해야 하는데, 이때 우리는 auto.offset.reset 옵션을 주의깊게 살펴봐야 한다. (물론 카프카 컨슈머의 배치 처리 활용도 고려할 수도 있다.) 먼저 파티션을 증설한 상황을 그림으로 표현하면 다음과 같다.

파티션 4개, 컨슈머 3대로 불균형이 발생한 상태이며, 이때 두 가지 변화가 동시에 일어난다. 새 파티션은 어디서부터 메시지를 읽을지 결정해야 하는데, 이 결정 방식에 따라 메시지가 유실되거나 정상적으로 소비되거나 갈리게 된다.
- 컨슈머 그룹이 리밸런싱(Rebalancing)을 수행: 한 컨슈머가 일시적으로 파티션 2개를 담당함
- 새 파티션 (Partition 3)에는 커밋된 오프셋이 없음: auto.offset.reset 값에 따라 데이터를 읽을 범위가 결정됨
문제는 파티션 3는 새롭게 추가되어 아직 커밋된 오프셋이 없다는 것이다. 따라서 auto.offset.reset 값에 따라 데이터를 읽을 범위가 결정되는데, 기본 설정값이 latest인 것이 문제다. 결론부터 얘기하면, 파티션 증설 이전에는 먼저 auto.offset.reset 값을 earliest로 수정 및 배포한 후에 파티션을 증설하는 것이 안전하다. 그렇지 않으면 메시지 유실이 발생할 수 있다.
[ 파티션 증설 시 auto.offset.reset 값에 따른 동작 차이 ]
auto.offset.reset = latest로 파티션 증설한 경우
파티션이 증설되면 컨슈머 그룹은 리밸런싱을 수행하고, 그 결과로 새 파티션을 컨슈머에게 할당한다. 그런데 이 리밸런싱이 완료되기 전에 프로듀서가 새 파티션으로 메시지를 보낸다면 어떻게 될까? latest로 설정된 컨슈머는 “리밸런싱이 끝난 시점의 가장 마지막 메시지”부터 읽는다. 즉, 그 사이에 새 파티션으로 들어온 메시지들은 컨슈머가 읽기 시작하는 오프셋보다 앞쪽에 위치하게 되어 그대로 유실된다. 아래 그림에서 보이듯, msg1과 msg2는 리밸런싱 도중 도착한 메시지지만 컨슈머가 가장 마지막 메시지부터 읽기 시작하기 때문에 영구적으로 유실되고, 컨슈머가 할당된 시점 이후에 도착한 msg3부터 정상 소비된다.

auto.offset.reset = earliest로 파티션 증설한 경우
auto.offset.reset = earliest일 때에는 모든 메시지가 정상 소비된다. 컨슈머는 새 파티션의 0번 오프셋부터 읽기 시작할 것이고, 새 파티션은 증설 직후에는 비어있거나, 증설 이후에 들어온 메시지만 존재하기 때문에 사실상 “처음부터” = “증설 이후 들어온 첫 메시지부터” 가 된다. 따라서 메시지 유실도, 과거 데이터의 중복 소비도 발생하지 않는다. 아래 그림에서 보이듯 msg1, msg2, msg3 모두 빠짐없이 정상적으로 소비된다.

파티션 증설 시에 earliest로 컨슈머를 설정하면, 토픽 전체를 처음부터 다시 읽지 않을까? 하는 염려가 생길 수 있다. 하지만 위에서 설명하였듯 auto.offset.reset 설정은 아직 커밋된 오프셋이 없는 경우에 적용이 된다. 이미 운영 중인 컨슈머 그룹은 기존 파티션에 대해 커밋된 오프셋을 가지고 있기 때문에, auto.offset.reset 값과 무관하게 마지막 커밋 위치부터 이어서 읽는다. earliest로 설정해도 적용되는 대상은 오직 커밋 오프셋이 없는 새 파티션뿐이다.
하지만 앞서 설명하였듯 새롭게 특정 토픽에 대해 컨슈머 그룹을 기존 파티션을 연결할 때부터 latest로 설정한다면, 토픽 전체를 처음부터 읽게 되므로 주의해야 한다. 이를 케이스별로 정리하면 다음과 같다.
| 케이스 | 커밋 오프셋 | 적용 |
| 기존 파티션 | 있음 | 마지막 커밋 위치부터 읽음, auto.offset.reset 무시 |
| 증설로 추가된 새 파티션 | 없음 | auto.offset.reset 적용 |
| 새 group.id로 첫 기동 | 없음 | auto.offset.reset 적용 (토픽 처음부터 읽힐 수 있음 ⚠️) |
| retention으로 오프셋이 만료된 경우 | 무효 | auto.offset.reset 적용 |
[ Step 3. 컨슈머 증설 후 (다시 1:1 균형) ]
기존에 운영되는 컨슈머들을 auto.offset.reset=earliest로 적용하고, 파티션을 늘렸다면 이제 컨슈머의 수를 증설하여 파티션과 컨슈머의 수를 다시 1대1 균형 맞춰줘야 한다. 새 파티션 수에 맞춰 컨슈머 1대를 추가하면 또 한 번의 리밸런싱이 일어나고, 다시 1:1 매핑이 복원된다. 이 시점부터는 새 파티션도 컨슈머가 커밋한 오프셋을 가지게 되어 운영이 안정화된다.

'Server' 카테고리의 다른 글
| [Server] 프로메테우스와 푸시 게이트웨이의 한계(Prometheus and limits of Push Gateway) (1) | 2026.05.12 |
|---|---|
| [AI] AI Harness(하네스) 구축을 위한 shim 아키텍처 with Busy Box pattern and PATH 하이재킹 (1) | 2026.05.05 |
| [Server] 실용적인 아키텍처 패턴을 찾아서(Practical Architecture Pattern) (11) | 2025.11.25 |
| [Server] 운영 환경을 위한 실용적인 로그 레벨(Practical Log Level) (0) | 2025.11.11 |
| [Server] MCP 서버 프로토콜, SSE에서 Streamable HTTP 방식으로의 대변경 (11) | 2025.08.12 |