Kafka 기초 (4) - Consumer Groups and Offsets
consumer group으로 partition을 나눠 읽는 것을 터미널 두 개로 확인하고, kafka-consumer-groups.sh로 lag를 읽는 법과 offset reset, consumer를 늘려도 처리량이 안 느는 함정을 다룹니다.
Kafka 기초 시리즈의 4편입니다. 2편의 kafka container와 events topic을 이어서 씁니다. 전체 목차는 0편에 있습니다.
Group으로 읽기
지금까지 consumer는 group 없이 띄웠습니다. 이번에는 group 이름을 붙입니다.
1
2
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic events --group loader
겉보기는 같지만 달라진 것이 하나 있습니다. group을 붙인 consumer는 어디까지 읽었는지(offset)를 broker에 group 이름으로 저장합니다. Ctrl+C로 껐다가 다시 켜면, --from-beginning 없이도 지난번 멈춘 지점부터 이어 읽습니다. 애플리케이션에서 consumer는 사실상 항상 group과 함께 씁니다.
Group 상태 확인: lag 읽는 법
group의 진행 상황을 보는 도구가 kafka-consumer-groups.sh입니다. consumer를 하나 띄워 둔 채 실행합니다.
1
2
docker exec kafka /opt/kafka/bin/kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 --describe --group loader
1
2
3
4
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID ...
loader events 0 4 4 0 console-consumer-...
loader events 1 2 2 0 console-consumer-...
loader events 2 5 5 0 console-consumer-...
세 열이 핵심입니다.
- LOG-END-OFFSET: partition에 쌓인 마지막 offset. 생산이 만든 값입니다
- CURRENT-OFFSET: 이 group이 읽고 커밋한 offset. 소비가 만든 값입니다
- LAG: 둘의 차이. 소비가 생산을 얼마나 못 따라가고 있는지입니다
LAG 0은 다 따라잡은 상태입니다. producer로 이벤트를 몇 개 보내고 consumer를 꺼 둔 채 다시 describe 하면 LAG가 쌓이는 것이 보입니다. 운영에서 Kafka 모니터링의 첫 번째 지표가 바로 이 lag입니다 — lag가 계속 자라면 소비 쪽이 밀리고 있다는 뜻이고, retention을 넘길 때까지 방치하면 읽지 못한 이벤트가 삭제되는 사고가 됩니다.
실습: partition 나눠 맡기
1편의 규칙 “같은 group의 consumer들은 partition을 나눠 맡는다”를 눈으로 확인합니다. 같은 group 이름으로 consumer 두 개를 띄웁니다.
1
2
3
# 터미널 1과 터미널 2에서 같은 명령 실행
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic events --group loader
두 번째 consumer가 붙는 순간 첫 번째 consumer의 화면에 잠깐 멈칫하는 순간이 지나가는데, 이것이 rebalance입니다. group의 구성원이 바뀔 때마다 partition 담당을 다시 나누는 과정입니다. 결과를 describe로 확인합니다.
1
2
3
4
GROUP TOPIC PARTITION ... LAG CONSUMER-ID
loader events 0 0 console-consumer-7f3a... ← consumer A
loader events 1 0 console-consumer-7f3a... ← consumer A
loader events 2 0 console-consumer-b81c... ← consumer B
partition 3개를 두 consumer가 2:1로 나눠 맡았습니다. 이제 3편의 key 붙은 producer로 user-1, user-2 이벤트를 보내면, 같은 key의 이벤트는 정해진 한쪽 터미널에만 나타납니다. partition이 담당별로 갈렸기 때문입니다.
터미널 하나를 Ctrl+C로 죽여 봅니다. 잠시 후 남은 consumer가 세 partition을 전부 이어받아 다시 모든 이벤트를 받기 시작합니다. 장애 시 자동 인계가 group의 두 번째 역할입니다.
함정: consumer를 늘렸는데 처리량이 그대로
같은 group에 consumer를 partition 수보다 많이 붙이면 어떻게 되는지도 확인해 둘 가치가 있습니다. partition 3개인 events topic에 consumer 4개를 붙이면, describe 출력에서 네 번째 consumer는 어느 partition에도 나타나지 않습니다. partition 수를 넘는 consumer는 유휴 상태로 대기만 합니다.
병렬성의 상한은 consumer 수가 아니라 partition 수입니다. “처리가 밀려서 consumer를 늘렸는데 그대로다”의 원인이 대부분 이것이고, 해결은 topic의 partition 수 자체를 늘리는 것입니다 — 그리고 그 결정에는 3편의 key 재배치 비용이 따라옵니다. 처리량 계획이 partition 설계에서 시작하는 이유입니다.
Offset Reset: 다시 읽기
이미 읽은 이벤트를 처음부터 다시 처리해야 할 때(적재 로직 버그로 재적재 등)는 group의 offset을 되감습니다. group의 consumer를 전부 끈 상태에서 실행해야 합니다.
1
2
3
docker exec kafka /opt/kafka/bin/kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 --group loader \
--reset-offsets --to-earliest --topic events --execute
--to-earliest: 남아 있는 가장 오래된 offset으로 되감기.--to-latest(끝으로 건너뛰기),--to-datetime(특정 시각부터)도 있습니다--execute: 실제 적용. 빼고 실행하면 적용 없이 결과 미리보기만 출력합니다
reset 후 consumer를 다시 켜면 과거 이벤트가 전부 다시 들어옵니다. 같은 이벤트를 두 번 처리해도 결과가 안 깨지도록(멱등) 소비 쪽을 설계해야 하는 이유가 여기서 나옵니다 — 이 이야기는 6편에서 정리합니다.
kafka-consumer-groups.sh 정리
| 옵션 | 역할 |
|---|---|
--list | group 목록 |
--describe --group 이름 | partition별 offset, lag, 담당 consumer 확인 |
--reset-offsets --to-earliest --execute | offset 되감기 (group 정지 상태에서) |
--delete --group 이름 | group 삭제 (저장된 offset 제거) |
다음 편에서 console 도구로 하던 일을 Python 코드로 옮깁니다. 오늘 본 group.id와 offset 설정이 client 설정값으로 그대로 나타납니다.