ISR (In-Sync Replicas)

ISR은 Leader와 동기화된 Replica 집합으로, Kafka의 데이터 일관성과 가용성을 결정합니다.

ISR 개념

정의

┌─────────────────────────────────────────────────────────────────┐
│                    ISR (In-Sync Replicas)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  모든 Replica (AR: Assigned Replicas)                            │
│  ┌───────────────────────────────────────────────────────┐      │
│  │                                                       │      │
│  │   ISR (In-Sync Replicas)                              │      │
│  │   ┌───────────────────────────────────────────┐      │      │
│  │   │                                           │      │      │
│  │   │  Leader    Follower    Follower           │      │      │
│  │   │  (Broker1) (Broker2)   (Broker3)          │      │      │
│  │   │     ✓         ✓           ✓               │      │      │
│  │   │                                           │      │      │
│  │   └───────────────────────────────────────────┘      │      │
│  │                                    │                  │      │
│  │   OSR (Out-of-Sync Replicas)       │                  │      │
│  │   ┌─────────────────────────┐      │                  │      │
│  │   │  Follower (Broker4)     │←─────┘                  │      │
│  │   │     ✗ (lag 초과)        │  동기화 지연            │      │
│  │   └─────────────────────────┘                         │      │
│  │                                                       │      │
│  └───────────────────────────────────────────────────────┘      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

ISR 멤버십 조건

Replica가 ISR에 포함되려면:

1. replica.lag.time.max.ms 내에 Leader에 fetch 요청
   기본값: 30000ms (30초)

2. Leader의 log-end-offset을 따라잡음
   (최신 메시지까지 복제 완료)

ISR 동작

ISR 축소 (Shrink)

정상 상태:
ISR = [Broker1(L), Broker2, Broker3]

Broker3 지연 발생 (replica.lag.time.max.ms 초과):
┌─────────────────────────────────────────┐
│ Broker1 (Leader)                        │
│ Log-end-offset: 1000                    │
│                                         │
│ Broker2: offset 1000 ✓ (In-sync)        │
│ Broker3: offset 800  ✗ (Lagging)        │
└─────────────────────────────────────────┘

ISR 축소:
ISR = [Broker1(L), Broker2]
OSR = [Broker3]

ISR 확장 (Expand)

Broker3이 따라잡음:
┌─────────────────────────────────────────┐
│ Broker1 (Leader)                        │
│ Log-end-offset: 1200                    │
│                                         │
│ Broker2: offset 1200 ✓                  │
│ Broker3: offset 1200 ✓ (Caught up!)     │
└─────────────────────────────────────────┘

ISR 확장:
ISR = [Broker1(L), Broker2, Broker3]
OSR = []

ISR 관련 설정

Broker 설정

# Replica lag 임계값 (ISR 제외 기준)
replica.lag.time.max.ms = 30000
 
# 최소 ISR 수 (acks=all 시 적용)
min.insync.replicas = 2

Topic 설정

# Topic별 min.insync.replicas 설정
kafka-configs.sh --alter \
    --entity-type topics \
    --entity-name my-topic \
    --bootstrap-server localhost:9092 \
    --add-config min.insync.replicas=2

ISR과 Producer acks

acks=all의 동작

acks=all (또는 acks=-1):

Producer → Leader → 모든 ISR에 복제 → ACK

┌──────────┐     ┌──────────┐     ┌──────────┐
│ Producer │────>│  Leader  │────>│ ISR 복제 │
│          │     │          │     │          │
│          │<────│          │<────│ 완료     │
│   ACK    │     │          │     │          │
└──────────┘     └──────────┘     └──────────┘

min.insync.replicas 효과

RF = 3, min.insync.replicas = 2, acks = all

ISR = 3 (정상):
├── 쓰기 성공 ✓
└── 2개 이상 ISR에 복제됨

ISR = 2:
├── 쓰기 성공 ✓
└── 2개 ISR에 복제됨

ISR = 1:
├── 쓰기 실패 ✗
└── NotEnoughReplicasException

예외 처리

try {
    producer.send(record).get();
} catch (ExecutionException e) {
    if (e.getCause() instanceof NotEnoughReplicasException) {
        // ISR 부족
        log.error("Not enough ISR replicas");
        // 재시도 또는 대체 처리
    }
}

ISR 모니터링

JMX 메트릭

파티션별 ISR 크기:
kafka.cluster:type=Partition,name=InSyncReplicasCount,
    topic={topic},partition={partition}

Under-replicated 파티션:
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions

ISR 변화율:
kafka.server:type=ReplicaManager,name=IsrShrinksPerSec
kafka.server:type=ReplicaManager,name=IsrExpandsPerSec

명령행 도구

# 토픽 상세 정보 (ISR 포함)
kafka-topics.sh --describe \
    --topic my-topic \
    --bootstrap-server localhost:9092
 
# 출력:
# Topic: my-topic  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2
 
# Under-replicated 파티션 확인
kafka-topics.sh --describe \
    --bootstrap-server localhost:9092 \
    --under-replicated-partitions

알림 설정 (Prometheus)

groups:
  - name: kafka-isr-alerts
    rules:
      - alert: KafkaUnderReplicatedPartitions
        expr: kafka_server_ReplicaManager_UnderReplicatedPartitions > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Under-replicated partitions detected"
 
      - alert: KafkaISRShrinking
        expr: rate(kafka_server_ReplicaManager_IsrShrinksPerSec[5m]) > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "ISR is shrinking"

ISR 문제 해결

Under-replicated 파티션

# 1. 원인 파악
kafka-topics.sh --describe \
    --topic my-topic \
    --bootstrap-server localhost:9092
 
# Replicas: 1,2,3  Isr: 1,2
# → Broker 3이 ISR에서 빠짐
 
# 2. Broker 상태 확인
kafka-broker-api-versions.sh \
    --bootstrap-server broker3:9092
 
# 3. Broker 로그 확인
grep -i "replica" /var/log/kafka/server.log

일반적인 원인

원인증상해결
네트워크 지연간헐적 ISR 변화네트워크 확인
디스크 병목지속적 lag디스크 성능 개선
Broker 과부하느린 복제부하 분산
GC pause일시적 lagJVM 튜닝

replica.lag.time.max.ms 튜닝

# 너무 짧으면: ISR 빈번한 변화
# 너무 길면: 장애 감지 지연
 
# 기본값 (30초)
replica.lag.time.max.ms = 30000
 
# 네트워크 지연이 있는 환경
replica.lag.time.max.ms = 60000
 
# 빠른 장애 감지 필요
replica.lag.time.max.ms = 10000

High Watermark

개념

High Watermark: 모든 ISR에 복제된 마지막 오프셋

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  Leader Log:    [0][1][2][3][4][5][6][7][8][9]                 │
│  Follower1 Log: [0][1][2][3][4][5][6][7]                       │
│  Follower2 Log: [0][1][2][3][4][5][6]                          │
│                                      ↑                          │
│                              High Watermark = 6                 │
│                                                                 │
│  Consumer는 HW까지만 읽을 수 있음                                │
│  (커밋된 메시지만 읽음)                                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Consumer와 HW

// Consumer는 자동으로 HW까지만 읽음
// 별도 설정 불필요
 
ConsumerRecords<String, String> records = consumer.poll(...);
// HW 이후 메시지는 포함되지 않음

관련 문서