이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Kafka lag가 증가할 때 처리량 문제, poison message, downstream 장애를 어떻게 구분하는가?
- consumer group, partition, committed offset, end offset 중 무엇을 먼저 확인해야 하는가?
- Spring Kafka consumer에서 DB commit과 offset commit 경계가 장애 대응에 어떤 영향을 주는가?
- lag 복구가 끝났다고 판단하려면 target 데이터와 품질 rule을 어떻게 확인해야 하는가?
개요
Kafka lag는 원인이 아니라 증상이다.
lag가 늘어나는 이유는 producer traffic 증가, consumer 중단, partition skew, poison message, downstream DB 지연, offset commit 실패처럼 다양하다.
따라서 lag 대응은 “consumer를 늘린다”가 아니라 어느 partition에서 왜 처리가 멈췄는지 확인하는 절차로 시작한다.
기본 용어
- end offset: broker에 쌓인 최신 message 위치.
- committed offset: consumer group이 처리 완료로 기록한 위치.
- lag: end offset과 committed offset의 차이.
- partition lag: 특정 partition의 lag.
- consumer lag: consumer group 전체 lag.
전체 lag만 보면 한 partition이 멈췄는지, 모든 consumer가 느린지 구분할 수 없다.
첫 10분
Kafka lag 알림을 받으면 다음 순서로 본다.
- 어떤 topic과 consumer group인가?
- lag가 전체 partition에서 증가하는가, 특정 partition만 증가하는가?
- consumer instance가 살아 있는가?
- error log와 DLQ 또는 DLT가 증가하는가?
- downstream DB/API latency가 증가했는가?
- 최근 배포, schema 변경, traffic spike가 있었는가?
- target mart, search, feature freshness에 사용자 영향이 있는가?
consumer를 재시작하기 전에 이 정보를 모아야 같은 message에서 다시 멈추는 일을 줄일 수 있다.
상태 확인
kafka-consumer-groups \
--bootstrap-server kafka-1:9092 \
--describe \
--group order-mart-consumer중점적으로 볼 항목은 다음과 같다.
- topic.
- partition.
- current offset.
- log end offset.
- lag.
- consumer id.
- host.
특정 partition만 lag가 크면 partition key skew나 poison message 가능성이 높다.
모든 partition lag가 함께 늘면 consumer 처리량, downstream 병목, 전체 traffic spike를 먼저 본다.
원인 분류
| 신호 | 가능성 | 먼저 할 일 |
|---|---|---|
| 특정 partition만 lag 증가 | hot key, poison message | 해당 partition log, key 분포 확인 |
| 모든 partition lag 증가 | 처리량 부족, downstream 지연 | consumer 처리 시간, DB latency 확인 |
| lag는 늘고 error log 많음 | parsing/schema 오류 | DLT, schema version 확인 |
| consumer가 자주 rebalance | session timeout, 긴 처리 시간 | poll interval, 처리 시간 확인 |
| DLT 급증 | 영구 실패 message | DLT payload와 error class 분류 |
| DB CPU 증가와 lag 동시 증가 | target DB 병목 | write query, lock, batch size 확인 |
lag는 Kafka만의 문제가 아닐 수 있다.
consumer가 쓰는 DB가 느려져도 offset commit은 늦어진다.
Spring Kafka 경계
@KafkaListener(topics = "order-paid", groupId = "order-mart-consumer")
public void listen(OrderPaidEvent event, Acknowledgment ack) {
orderMartHandler.handle(event);
ack.acknowledge();
}이 코드는 handle이 끝난 뒤 offset을 commit한다.
handle 안에서 DB transaction이 실패하면 ack가 호출되지 않아 같은 message를 다시 받을 수 있다.
그래서 handler는 idempotent해야 한다.
CREATE TABLE processed_events (
consumer_name text NOT NULL,
event_id text NOT NULL,
processed_at timestamptz NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);offset commit은 처리 이력 table을 대체하지 않는다.
offset은 Kafka consumer group 위치이고, 처리 이력은 business side effect의 중복 방지 장치다.
Poison Message 대응
poison message는 같은 record에서 consumer가 반복 실패해 partition 진행을 막는 상황이다.
대응 순서:
- 실패 payload와 error class를 확인한다.
- transient failure인지 permanent failure인지 분류한다.
- permanent failure라면 DLT로 격리하고 partition 진행을 재개한다.
- consumer 코드 또는 schema mapping을 수정한다.
- DLT message를 재처리할 command와 검증 쿼리를 남긴다.
무한 retry는 lag를 키우고 뒤의 정상 message까지 막는다.
처리량 부족 대응
처리량이 부족할 때 가능한 조치는 다음과 같다.
- consumer instance 수를 partition 수 이하에서 늘린다.
- DB write를 batch 처리하되 transaction 크기를 제한한다.
- 느린 외부 API 호출을 consumer path에서 제거한다.
- hot key가 있다면 partition key 설계를 재검토한다.
- producer traffic spike라면 upstream throttle이나 backpressure를 협의한다.
partition 수보다 consumer 수가 많아도 한 group 안에서 병렬성은 늘지 않는다.
완료 검증
lag가 0에 가까워졌다고 바로 종료하지 않는다.
다음 확인이 필요하다.
SELECT max(event_time) AS newest_event,
now() - max(event_time) AS freshness_lag
FROM mart_order_events;SELECT status, count(*)
FROM quality_rule_runs
WHERE dataset_name = 'mart_order_events'
AND checked_at >= now() - interval '1 hour'
GROUP BY status;consumer가 따라잡았더라도 target 데이터가 틀렸으면 장애는 끝나지 않았다.
운영 지표
consumer_group_lag: group 전체 lag.max_partition_lag: partition별 최대 lag.records_per_second: consumer 처리량.processing_latency_ms: message 처리 시간.dlt_count: DLT로 보낸 message 수.rebalance_count: consumer group rebalance 횟수.target_write_latency_ms: target DB write latency.
lag dashboard에는 partition별 분포가 반드시 있어야 한다.
위험 신호!
- lag 전체 합계만 보고 partition별 lag를 보지 않는다.
- consumer 재시작으로 poison message를 반복 처리한다.
- ack를 DB commit 전에 수행한다.
- DLT로 보낸 message의 재처리 runbook이 없다.
- lag가 사라졌다는 이유만으로 mart 품질 검증 없이 장애를 닫는다.
확인 질문
확인 질문
- 특정 partition만 lag가 클 때 의심해야 할 원인은 무엇인가?
- offset commit과 processed event table은 왜 서로 다른 역할인가?
- poison message를 무한 retry하면 downstream에 어떤 문제가 생기는가?
- lag가 0이어도 target 품질 rule을 확인해야 하는 이유는 무엇인가?
참고 문서
- Apache Kafka Documentation
- Spring for Apache Kafka Reference
- PostgreSQL Documentation
- Martin Kleppmann, Designing Data-Intensive Applications