백엔드 면접 용어 사전
메시지 큐consumer lag · 컨슈머 lag

컨슈머 랙

쌓인 마지막 오프셋에서 그룹이 커밋한 오프셋을 뺀 값. 소비가 생산을 못 따라가는 정도를 나타내는 운영 1순위 지표.

아직 읽지 않고 남아 있는 메시지 수. Kafka 운영에서 가장 먼저 보는 지표다.

정의는 뺄셈 하나

랙 = LEO(Log End Offset) − 그룹이 커밋한 오프셋

  • LEO — 파티션에 지금까지 쌓인 마지막 오프셋 (생산자가 밀어 넣은 최신 지점)
  • 커밋 오프셋 — 컨슈머 그룹이 "여기까지 처리했다"고 기록한 지점

예: LEO 10,000 / 커밋 9,500 → 랙 500

파티션마다 따로 계산된다. 그룹 전체 랙은 파티션별 랙의 합이다.

kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --describe --group order-consumer
# TOPIC   PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG
# orders  0          9500            10000           500
# orders  1          8200            8210            10

랙이 말해 주는 것은 "느리다"가 아니다

랙은 절대 속도가 아니라 생산 속도와 소비 속도의 관계다. 초당 10만 건을 처리해도 생산이 12만이면 랙은 계속 는다. 그래서 어디에 쌓이는지가 원인을 가른다.

랙 패턴원인대응
특정 파티션에만 쏠림키 분포 치우침(핫 파티션)키 설계 변경
모든 파티션에서 고르게 증가컨슈머 부족·처리 로직이 느림스케일아웃(파티션 수가 상한)·처리 최적화
배포·리밸런스 직후 급증 후 회복정상대응 불필요
갑자기 0오프셋 리셋 또는 리텐션으로 데이터가 날아감오히려 경보 대상

왜 순간값으로 알림을 걸면 안 되나

표의 세 번째 줄 때문이다. 배포할 때마다 컨슈머가 재시작되고, 그 몇 초 동안 쌓인 메시지가 랙으로 잡혔다가 곧 따라잡힌다. 순간값에 임계값을 걸면 배포마다 알림이 울린다.

LinkedIn이 만든 Burrow 같은 도구가 임계값 대신 시간에 따른 추세로 판단하는 이유가 이것이다 — 랙이 줄고 있으면 정상, 계속 늘고 있으면 경보. 임계값을 사람이 토픽마다 정할 필요도 없어진다.

건수보다 시간이 유용하다

"500건 밀림"은 서비스 영향을 알려 주지 않는다. 건당 처리 시간이 1ms면 0.5초 지연이고 100ms면 50초 지연이다. 그래서 랙을 시간으로 환산해 보는 게 SLA와 직접 연결된다.

면접 함정

  • "랙 = 지연 시간" → 건수다.
  • "컨슈머를 늘리면 랙이 준다" → 파티션 수가 상한이라, 파티션보다 많은 컨슈머는 그냥 논다.

애플리케이션에서 노출하기

// Spring Kafka + Micrometer: 컨슈머 메트릭이 자동으로 등록된다
// kafka.consumer.fetch.manager.records.lag  (파티션별 랙)
// kafka.consumer.fetch.manager.records.lag.max
@Bean
MeterBinder kafkaMetrics(ConsumerFactory<?, ?> cf) {
    return new KafkaClientMetrics(cf.createConsumer());
}

CLI로 보는 건 사람이 확인할 때고, 알림은 이렇게 수집한 메트릭에 건다.

알림 조건을 어떻게 잡나

✗ lag > 1000 배포마다 울린다 ✓ lag > 1000 AND 5분간 감소 없음 따라잡는 중이면 안 울린다 ✓ lag_seconds > SLA 건수가 아니라 시간으로 환산

두 번째가 Burrow가 하는 일을 조건식으로 옮긴 것이고, 세 번째가 서비스 영향과 가장 직접 연결된다. 랙 500건이 0.5초인지 50초인지는 건당 처리 시간에 달렸다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 메시지 큐·비동기 — Kafka/RabbitMQ·EOS·순서·outbox