프로그래밍/kafka

Kafka Transaction - Lag 1 Gap issue

seungdols 2026. 6. 11. 14:35

Kafka Transaction Commit Marker로 인한 Phantom Lag 현상

들어가며

Kafka 기반의 이벤트 드리븐 시스템을 운영하다 보면, 모니터링 대시보드에서 consumer lag이 0이 되지 않는 현상을 마주할 때가 있습니다. 메시지를 모두 처리했는데도 lag이 1씩 남아있는 이 현상을 Phantom Lag이라고 부릅니다.

이 글에서는 실제 서비스에서 Phantom Lag을 만나게 된 과정, 원인 분석, 그리고 해결 방안을 공유합니다.

시스템 구성

CDC(Change Data Capture) 기반으로 검색 엔진 인덱싱을 수행하는 스트림 처리 시스템입니다.

[MongoDB CDC] → [Indexer] → (delete-request 토픽) → [Deleter] → [Search Engine]
  • Indexer: CDC 이벤트를 consume하여 검색 엔진에 인덱싱하고, 삭제 대상은 delete 토픽으로 produce
  • Deleter: delete 토픽을 consume하여 검색 엔진에서 문서 삭제

Indexer는 Kafka Transaction을 사용합니다. CDC 대량 유입 시 consume은 완료했지만 delete produce 전에 offset이 commit되어 메시지가 유실되는 문제가 있었기 때문입니다. Transaction을 통해 consume-produce atomicity를 보장하여 이 문제를 해결했습니다.

# Indexer - Spring Cloud Stream 설정
spring:
  cloud:
    stream:
      kafka:
        binder:
          brokers: ${KAFKA_BROKERS}
          required-acks: all
          transaction:
            transactionIdPrefix: "${spring.application.name}-${HOSTNAME:${random.uuid}}-tx-"
            producer:
              configuration:
                linger.ms: 100
                retry.backoff.ms: 2000

문제 발견

어느 날 모니터링에서 Deleter consumer group의 lag이 지속적으로 9(파티션 수와 동일)로 유지되는 것을 발견했습니다. 모든 메시지를 정상 처리하고 있는데도 lag이 줄어들지 않았습니다.

Partition 0: current_offset=100, end_offset=101, lag=1
Partition 1: current_offset=200, end_offset=201, lag=1
...
Partition 8: current_offset=150, end_offset=151, lag=1
Total Lag: 9

첫 번째 시도: Deleter의 Transaction 제거

"Deleter에 불필요하게 설정된 Kafka Transaction이 원인이 아닐까?"라는 가설로 Deleter의 transaction 설정을 제거했습니다.

결과는 변화 없음. Phantom Lag은 여전히 9로 유지되었습니다.

원인 분석

Transaction Commit Marker란?

Kafka의 Transactional Producer는 메시지를 produce할 때 실제 메시지 외에 commit marker라는 제어 레코드를 파티션에 기록합니다. 이 marker는 "여기까지의 메시지가 commit되었음"을 나타내는 메타데이터입니다.

Partition 내부 구조:
[msg@offset_0] [commit_marker@offset_1] [msg@offset_2] [commit_marker@offset_3] ...

Consumer는 이 commit marker를 건너뛰고 실제 메시지만 처리합니다. 하지만 end offset은 commit marker를 포함하여 계산됩니다.

Phantom Lag 발생 메커니즘

  1. Indexer(transactional producer)가 delete 토픽에 메시지 produce + commit
  2. 파티션에 [메시지][commit_marker] 기록
  3. Deleter가 메시지를 consume하고 offset commit
  4. 하지만 end offset은 commit marker 이후를 가리킴
  5. end_offset - committed_offset = 1Phantom Lag
[msg] [commit_marker]  ← 더 이상 새 메시지 없음
  ^                ^
  consumer offset  end offset
  lag = 1

왜 Deleter의 Transaction 제거로 해결이 안 되는가

Phantom Lag의 원인은 consumer 측이 아닌 producer 측입니다. Deleter에서 아무리 설정을 변경해도, Indexer가 transactional producer로 해당 토픽에 produce하는 한 commit marker는 계속 기록됩니다.

왜 다른 서비스에서는 문제가 안 되는가

같은 transaction 구조를 사용하는 다른 서비스에서는 Phantom Lag이 관측되지 않았습니다. 차이점은 트래픽 패턴이었습니다.

  • 고트래픽 토픽: 메시지가 지속적으로 유입되므로 commit marker 이후에 바로 새 메시지가 쌓임 → consumer가 계속 처리 → lag이 순간적으로만 1이 되고 즉시 해소
  • 저트래픽 토픽(delete): 메시지가 간헐적 → commit marker가 파티션의 마지막 레코드로 장시간 유지 → Phantom Lag이 지속

Burrow 모니터링의 오탐 문제

Burrow를 통해 consumer lag을 모니터링하는 경우, Phantom Lag으로 인해 추가적인 문제가 발생할 수 있습니다.

Burrow는 sliding window 기반으로 consumer 상태를 판단합니다:

  • Lag이 존재하는데 committed offset이 변하지 않으면 → STALLED 또는 WARN 판정
  • Partition 하나라도 WARN이면 → consumer group 전체가 OK가 아닌 상태

Phantom Lag은 구조적으로 0이 될 수 없으므로, Burrow가 정상 상태를 장애로 오판하여 불필요한 알림이 발생할 수 있습니다.

아래와 같은 모니터링 코드가 있다면:

@Scheduled(cron = "0 30 * * * *")
public void kafkaCheck() {
    webClient.get()
        .uri("/consumer/{group}/status", consumerGroup)
        .retrieve()
        .bodyToMono(ConsumerGroupStatus.class)
        .subscribe(status -> {
            // Phantom Lag으로 인해 항상 "OK"가 아닌 상태가 될 수 있음
            if (!"OK".equals(status.getStatus())) {
                sendAlert("consumer group status error");
            }
            // totalLag이 파티션 수(9) 이하여도 알림 발생
            if (status.getTotalLag() > warningTotalLag) {
                sendAlert("consumer group total lag warning");
            }
        });
}

Phantom Lag 때문에 정상 상태에서도 지속적인 알림이 발생하게 됩니다.

해결 방안 검토

1. Indexer Transaction 제거 → 불가

Transaction은 CDC 대량 유입 시 메시지 유실 방지를 위해 필수입니다. Consume-produce atomicity 없이는 delete 메시지가 누락되는 문제가 재발합니다.

2. Delete output만 Non-Transactional Binder로 분리 → 불가

Spring Cloud Stream에서 별도 binder를 정의하여 특정 binding만 transaction scope에서 제외할 수 있습니다:

spring:
  cloud:
    stream:
      binders:
        kafka-tx:
          type: kafka
          environment:
            spring.cloud.stream.kafka.binder:
              transaction:
                transactionIdPrefix: "..."
        kafka-no-tx:
          type: kafka
          environment:
            spring.cloud.stream.kafka.binder:
              brokers: ${KAFKA_BROKERS}
      bindings:
        cdc-in-0:
          binder: kafka-tx
        delete-out-0:
          binder: kafka-no-tx  # transaction 없음

하지만 이렇게 분리하면 해당 binding이 transaction scope에서 빠지므로, consume-produce atomicity가 깨져 메시지 유실 문제가 재발합니다.

3. Idempotent Producer로 대체 → 불가

enable.idempotence=true는 producer 재시도 시 중복 방지 기능이며, consume-produce atomicity를 보장하지 않습니다. Transaction 사용 시 이미 자동 활성화되어 있기도 합니다.

설정 역할 Commit Marker 생성
enable.idempotence=true producer 재시도 시 중복 방지 X
transactionIdPrefix 설정 consume-produce atomicity O

4. 모니터링 Threshold 조정 → 현실적 해결

현재 Kafka 버전(3.x)에서는 Phantom Lag을 제거할 수 없으므로, 모니터링 수준에서 대응합니다:

  • warningTotalLag: 파티션 수 초과로 설정 (예: > 파티션 수)
  • warningCurrentLag: 1 초과로 설정 (예: > 1)
  • Burrow status가 WARN이고 totalLag ≤ 파티션 수일 때는 알림 제외

KAFKA-17600: 근본적 해결

ref. KAFKA-10683

Apache Kafka 프로젝트에서 이 문제를 인지하고 KAFKA-17600으로 수정했습니다.

수정 내용

Consumer lag 계산 시 transaction commit marker를 offset에서 제외하도록 변경되었습니다. 이를 통해 실제 메시지 기준으로만 lag이 계산되어 Phantom Lag이 발생하지 않습니다.

적용 버전

Apache Kafka 4.0부터 포함됩니다.

Spring Boot 3.2.x 기준 내장 Kafka Client는 3.6.x이므로, 이 fix를 적용하려면 Kafka 브로커와 클라이언트 모두 4.0 이상으로 업그레이드해야 합니다.

하지만, 현재 상황에서 버전 업그레이드는 당장 어려워서, 알림쪽 수정 예정

정리

구분 내용
현상 Consumer lag이 파티션당 1씩 영구 유지
원인 Transactional producer의 commit marker가 offset을 차지
오해하기 쉬운 점 Consumer 측 설정 문제가 아님, Producer 측 구조적 문제
단기 대응 모니터링 threshold 조정
근본 해결 Kafka 4.0 업그레이드 (KAFKA-17600)

요약

  1. Phantom Lag은 consumer가 아닌 producer 관점에서 봐야 한다: Consumer의 설정을 아무리 바꿔도 producer가 남긴 commit marker는 사라지지 않습니다.

  2. 트래픽 패턴이 문제의 가시성을 결정한다: 같은 구조라도 고트래픽 토픽에서는 보이지 않고, 저트래픽 토픽에서만 드러납니다.

  3. 모니터링 도구의 한계를 이해해야 한다: Burrow 같은 도구는 offset 숫자 기반으로 판단하므로, transaction marker를 구분하지 못합니다.

  4. Trade-off를 인식하자: Transaction은 메시지 유실을 방지하지만, Phantom Lag이라는 부작용이 있습니다. 시스템 요구사항에 따라 적절한 선택이 필요합니다.

참고

반응형

'프로그래밍 > kafka' 카테고리의 다른 글

confluent-kafka clients 다운로드가 안 될때  (0) 2023.09.13