본문 바로가기
KAFKA/개념 정리

Kafka의 메시지 복제를 이해하는 세 가지 키워드 — LEO, HW, acks

by Machine-Geon 2026. 4. 15.
반응형

Kafka의 메시지 복제를 이해하는 세 가지 키워드 — LEO, HW, acks

Kafka를 운영하다 보면 "메시지 유실", "복제 지연", "컨슈머 가시성" 같은 이슈를 마주하게 된다. 이 문제들을 제대로 이해하려면 LEO, High Watermark, 그리고 acks의 관계를 알아야 한다.


LEO (Log End Offset)

LEO는 각 레플리카에서 다음에 쓸 오프셋 위치다. 즉, 마지막으로 기록된 메시지의 오프셋 + 1이다.

파티션에 오프셋 0, 1, 2까지 메시지가 있다면 → LEO = 3

LEO는 레플리카마다 다를 수 있다. 리더는 프로듀서로부터 직접 메시지를 받으니 LEO가 가장 앞서고, 팔로워는 리더를 fetch해서 따라가므로 약간 뒤처지는 게 정상이다.


ISR (In-Sync Replicas)과 LEO의 관계

팔로워가 ISR에 유지되는 기준은 replica.lag.time.max.ms(기본 30초) 내에 리더의 LEO를 따라잡았는지 여부다.

정확히는 "현재 리더의 LEO와 항상 동일해야 한다"가 아니다. 팔로워가 fetch를 보내면, 리더는 그 시점의 LEO를 기록해둔다. 팔로워가 그 LEO까지 복제를 완료하면 lag 타이머가 리셋된다.

1. 팔로워가 fetch 요청
2. 리더는 그 시점의 LEO를 기록 (예: LEO=100)
3. 팔로워가 100까지 복제 완료 → 타이머 리셋
4. 그 사이 리더 LEO가 105로 올라가도 상관없음
5. 다음 fetch에서 또 따라잡으면 → 다시 타이머 리셋

이 시간 내에 주기적으로 따라잡기를 반복하고 있으면 ISR로 유지된다. 초과하면 ISR에서 제거된다.

참고: 과거 Kafka 0.9 이전에는 replica.lag.max.messages(메시지 개수 기준)도 있었지만, 프로듀서 burst 시 불필요한 ISR 축출이 반복되는 문제로 제거되었다.


High Watermark (HW)

HW는 ISR 내 모든 레플리카의 LEO 중 최솟값이다. 즉 ISR 전체가 복제를 완료한 지점이다.

오프셋:  0  1  2  3  4  5
리더:    ✓  ✓  ✓  ✓  ✓  ✓   LEO=6
팔로워A: ✓  ✓  ✓  ✓  ✓  ✓   LEO=6
팔로워B: ✓  ✓  ✓  ✓  ✗  ✗   LEO=4
                      ↑
                    HW=4

HW는 언제 갱신되는가

팔로워가 fetch 요청을 보내면, 리더는 그 팔로워의 LEO를 알게 된다. 이 시점에 ISR 전체의 LEO 최솟값을 다시 계산해서 HW를 갱신하고, 다음 fetch 응답에 갱신된 HW를 실어서 팔로워들에게 전파한다.

HW의 역할

1. 컨슈머 가시성 경계

컨슈머는 HW 이전까지만 읽을 수 있다. 이 제한이 없다면 어떤 일이 벌어지는지 보자.

1. 리더 LEO=6, 팔로워 LEO=4, HW=4
2. 컨슈머가 오프셋 5까지 읽고 커밋 완료
3. 리더 사망 → 팔로워가 새 리더로 승격
4. 새 리더는 오프셋 4까지만 보유
→ 컨슈머가 처리한 오프셋 4, 5의 메시지가 클러스터에서 영원히 사라진다

HW까지만 읽게 제한하면, 컨슈머가 읽은 모든 메시지는 ISR 전체가 보유하므로 리더 장애 시에도 새 리더에 반드시 존재한다.

2. 로그 복구 기준

브로커가 재시작하면 HW 이후의 데이터를 잘라내고(log truncation) 리더로부터 다시 복제한다. HW 이후는 ISR 전체가 합의하지 않은 데이터이므로 일관성을 위해 버린다.


acks 설정에 따른 동작 차이

acks=1

리더 혼자 기록하면 바로 프로듀서에게 ack를 보낸다. 팔로워 복제를 기다리지 않는다.

프로듀서 → 리더 기록 → ack 즉시 반환 (LEO=6)
                        ... 팔로워 복제 진행 중 (HW는 아직 4)
                        ... 팔로워 따라잡음 (HW → 6)
                        → 컨슈머 읽기 가능
  • ack와 컨슈머 가시성 사이에 시간차가 발생한다.
  • 리더 LEO와 HW 사이에 갭이 생기고, 이 구간이 리더 장애 시 유실 가능한 구간이다.
  • min.insync.replicas는 무시된다. 이 설정은 acks=all에서만 동작한다.

acks=all

ISR 전체가 복제를 완료해야 프로듀서에게 ack를 보낸다.

프로듀서 → 리더 기록 → ISR 전체 복제 완료 → HW 갱신 → ack 반환
                                                      → 컨슈머 즉시 읽기 가능
  • ack = ISR 복제 완료 = HW 갱신이 사실상 동시에 일어난다.
  • 프로듀서가 ack를 받은 메시지는 이미 HW 이하이므로, 리더가 죽어도 절대 잘리지 않는다.
  • ack를 못 받은 메시지(HW 이후)가 잘려나가면, 프로듀서는 타임아웃/에러로 인지하고 재전송한다.
  • 따라서 "프로듀서가 성공으로 인지한 메시지"가 유실되는 경우는 없다.

acks=all + min.insync.replicas

min.insync.replicas는 쓰기 시점에 ISR 수가 이 값 이상이어야 프로듀싱을 허용하는 설정이다. 읽기에는 영향을 주지 않는다.

예: acks=all, min.insync.replicas=2, 복제본 3개, 브로커 2대 다운 시

  • 리더가 살아있어도 ISR이 1이므로 → 프로듀싱 실패 (NotEnoughReplicasException)
  • 컨슈밍은 정상 (리더만 살아있으면 됨)

정리

acks=1 acks=all

ack 조건 리더 기록 즉시 ISR 전체 복제 완료
ack 시점의 HW 아직 안 올라감 같이 올라감
컨슈머 가시성 ack 이후 지연 있음 ack와 동시에 소비 가능
리더 장애 시 유실 LEO-HW 갭만큼 가능 ack 받은 메시지는 유실 없음
min.insync.replicas 무시됨 적용됨 (쓰기만)
레이턴시 낮음 상대적으로 높음

---

ISR/HW 메커니즘의 한계 — Last Replica Standing

위 메커니즘에는 구조적 약점이 하나 있다. ISR에서 빠진 레플리카를 더 이상 추적하지 않는다는 점이다.

다음 시나리오를 보자. `replication.factor=3`, `min.insync.replicas=2`인 파티션이다.

T0: ISR=[0,1,2], HW=100
T1: 브로커 0 네트워크 단절 → ISR=[1,2], HW 계속 전진 → HW=200
T2: 브로커 1도 장애 → ISR=[2] (리더만 남음, 프로듀싱 차단)
T3: 브로커 2(리더)가 unclean shutdown → 디스크 일부 유실
T4: 브로커 0, 1 복구 → 하지만 ISR이 아니므로 리더 선출 불가
T5: 브로커 2 재시작 → 리더로 복귀, 유실된 데이터 기준으로 복제 시작
→ 글로벌 데이터 로스

브로커 0은 T0 시점의 HW(100)까지 온전한 데이터를 갖고 있고, 브로커 1은 T1 시점의 HW(200)까지 갖고 있다. 하지만 기존 메커니즘에서는 ISR에서 빠진 순간 "리더 후보 자격 없음"으로 분류되어, 데이터가 더 온전한 레플리카가 있어도 활용할 수 없다.

Kafka 4.0에서 도입된 ELR(Eligible Leader Replicas, KIP-966)이 이 문제를 해결한다. KRaft 컨트롤러가 ISR에서 빠졌지만 HW까지의 데이터를 보유한 레플리카를 별도로 추적하여, ISR이 전멸해도 안전한 리더 선출이 가능해진다. ELR의 동작 방식과 설정은 다음 글에서 다룬다.Kafka

반응형