백엔드 데이터 인프라 107편 — Kafka Log Compaction (Key 별 최신만 유지)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 107편. Kafka Log Compaction — cleanup.policy=compact 의 메커니즘. 키별 최신 값만 유지·tombstone 으로 삭제·dirty ratio·자주 쓰는 자리 (config·state·CDC·Kafka Streams state store) 까지 풀어쓴 학습 노트. Part 5-6 고급 인프라 마무리.

📚 백엔드 데이터 인프라 · 107편 — Kafka Log Compaction (Key 별 최신만 유지)

이 글은 백엔드 데이터 인프라 시리즈 130편 중 107편이에요. Part 5-6 고급 인프라의 마지막 글. 106편 까지 KRaft·Tiered Storage 를 잡았다면, 이번 107편은 Log Compaction시간 기반 retention 의 대안. CDC(Change Data Capture, DB 변경을 이벤트로 흘려보내기)·event sourcing·state 같은 자리의 핵심.

Log Compaction이 어렵게 느껴지는 이유

대부분 사람이 시간 기반 retention (예: 7일 후 삭제) 만 알아요. Compaction 은 모델 자체가 다릅니다.

첫째, "왜 시간이 아닌 key 기반?"최신 값만 중요한 데이터 가 많아서. config·user profile·CDC 가 다 그런 자리예요.

둘째, Tombstone 이라는 새 개념이 들어옵니다. delete = value=null 메시지 publish. 이게 실제 키 삭제 신호예요.

셋째, Dirty Ratio 와 Compaction 타이밍. 언제 compaction 이 일어나나 가 곧 튜닝 영역.

이 글에서 compaction 의 의의·메커니즘·자주 쓰는 자리·운영까지 짚어봐요.

핵심 모델 — Key 별 최신 값

전통 retention (delete)

시간 1: key=user42 → "name=Alice"
시간 2: key=user42 → "name=Alice, age=30"
시간 3: key=user42 → "name=Alice, age=30, city=Seoul"
시간 4: key=user99 → "name=Bob"

7일 후 → 모두 삭제 (retention.ms)

Compaction

시간 1: key=user42 → "name=Alice"
시간 2: key=user42 → "name=Alice, age=30"
시간 3: key=user42 → "name=Alice, age=30, city=Seoul"   ← user42 의 latest
시간 4: key=user99 → "name=Bob"                          ← user99 의 latest

Compaction 후:
  key=user42 → "name=Alice, age=30, city=Seoul"   (이전 2개 삭제)
  key=user99 → "name=Bob"

각 key 의 최신 값만 유지. 시간 무관, 영구 보존.

결과 — 무한 retention with bounded size

사용자 100만 명
1만 번 update each (총 100억 메시지)

전통 retention 7일:
  10GB × N (보존 기간 따라)

Compaction:
  최종 100만 명 × N KB = ~수 GB (영구)

데이터 크기 = key 수 × 메시지 크기. 시간과 무관해요.

Tombstone — 삭제 신호

publish: key=user42, value=null   ← Tombstone

value=null 메시지 = "이 키 삭제" 신호. Compaction 시 해당 key 의 모든 메시지 + tombstone 자체 가 같이 사라집니다.

Delete Retention

delete.retention.ms=86400000          # 1일 (기본)

Tombstone 자체는 delete.retention.ms 동안 유지 돼요 — consumer 가 삭제 신호 받을 시간. 그 뒤로 완전 제거.

여기서 시험 함정 한 가지 — Tombstone 제거가 너무 빠르면 consumer 가 삭제 신호를 놓칩니다. 늦은 consumer 의 처리 시간 보장 이 필요해요. 기본 1일이면 일반적으로 충분.

설정

Topic 활성

$ kafka-topics.sh --create --topic user-profiles \
    --partitions 3 --replication-factor 3 \
    --config cleanup.policy=compact

Cleanup Policy 옵션

cleanup.policy=delete                # 기본, 시간/크기 retention
cleanup.policy=compact               # compaction only
cleanup.policy=delete,compact         # 둘 다 (compaction 후 오래된 것도 delete)

delete,compact = 최신 값 유지 + 너무 오래된 것은 시간 기반 삭제 (hybrid).

Compaction 관련 설정

# 압축 가능 비율 (dirty 부분이 50% 넘으면 압축)
min.cleanable.dirty.ratio=0.5         # 기본 50%

# 압축 전 최소 보존 시간 (recent message 는 압축 안 함)
min.compaction.lag.ms=0               # 기본 0

# 압축 트리거 최대 대기 (강제 압축)
max.compaction.lag.ms=Long.MAX_VALUE  # 기본 무한대

# Tombstone 보존
delete.retention.ms=86400000          # 1일

# Segment 크기 (작은 게 compaction 효율 ↑)
segment.bytes=104857600                # 100MB (compacted topic 권장)

동작 — Log Cleaner Thread

Broker 의 Log Cleaner Thread (별도 백그라운드 thread)
  ↓ 주기적으로 활성
각 partition 의 segment 들 검사
  ↓ dirty ratio 계산
dirty ratio >= min.cleanable.dirty.ratio?
  ↓ Yes
old segments + new segments → compact → 새 segment 로 교체

Dirty vs Clean

Log:
  Clean section: 이미 compaction 된 부분 (각 key 최신 값만)
  Dirty section: 압축 안 된 새 메시지들

dirty ratio = dirty_size / (clean_size + dirty_size)

min.cleanable.dirty.ratio=0.5 = dirty 가 50% 넘으면 compaction 트리거.

자주 쓰는 자리

1. __consumer_offsets — Kafka 내부

Consumer offset 저장. 각 (group, topic, partition) 의 최신 offset 만 의미가 있어요. Kafka 가 자동으로 compacted topic 사용.

2. Kafka Streams State Store

KTable(Kafka Streams 의 키별 최신 상태 테이블)·state store 가 내부적으로 compacted topic 사용. 상태가 곧 key 별 최신 값이니까요.

3. CDC (Change Data Capture)

Debezium(대표 CDC 도구) 같은 것이 DB row 변경 → Kafka topic 으로 흘려보내요. 같은 row 는 최신 상태만 의미 있음. Compacted topic 자리.

4. Configuration·Feature Flags

key=feature-X → value="enabled"
key=feature-X → value="disabled"
key=feature-X → value="enabled"

각 feature 의 최신 설정만 의미. 영구 보존 + key 별 최신.

5. User Profile·Entity State

User·Product·Order 같은 엔티티의 최신 상태. 도메인 이벤트 sourcing 의 snapshot 으로 활용해요.

예제 — 운영 패턴

Pattern 1: Pure Compacted (영구)

cleanup.policy=compact
retention.ms=-1                       # 무제한 (compaction 만 의지)

User profile·config 처럼 영구 보존이 필요한 자리.

Pattern 2: Delete + Compact (Hybrid)

cleanup.policy=delete,compact
retention.ms=2592000000               # 30일
delete.retention.ms=86400000          # tombstone 1일

CDC 같은 환경 — 최신 값 + 30일 이상 오래된 키는 삭제.

Pattern 3: 빠른 Compaction

cleanup.policy=compact
min.cleanable.dirty.ratio=0.1         # 10% 만 넘어도 압축
segment.bytes=10485760                # 10MB (작은 segment)

매우 자주 update 되는 환경. Compaction 도 자주.

Consumer 측 영향

Read 동작

Consumer 는 compaction 영향 없음 — 같은 API 그대로.

차이:

  • 이미 compact 된 partition 에서 read = 각 key 의 최신 값 만 받음
  • 중간 update 이력 X

Replay 사용

Compacted topic 을 처음부터 (earliest) consumer 가 read 하면 모든 key 의 최신 상태 가 한 번에 들어와요. 상태 재구축 패턴.

Kafka Streams 의 KTable 이 이 패턴 활용:

KTable<String, User> users = builder.table("user-profiles");   // compacted

한계·실무 함정

1. 빈번한 update 없으면 비효율

각 key 가 update 거의 없음 = compaction 효과 0. 시간 기반 retention 이 더 적합.

2. Key 가 없는 메시지

Compacted topic 은 key 필수. key=null 메시지 는 compaction 안 됨 (또는 거부).

3. Large value

각 key 마다 큰 value 가 들어가면 key 수 폭증 시 데이터도 큼. Compaction 효과는 있지만 memory·디스크 부담 을 모니터링해야 해요.

4. Log Cleaner Thread 성능

Compaction 자체가 CPU·디스크 I/O 부담. log.cleaner.threads·log.cleaner.io.max.bytes.per.second 같은 튜닝 포인트 가 있어요.

5. Tombstone 미사용

key 삭제 의도 인데 tombstone 안 보내면 영원히 보존. 명시적으로 tombstone 을 publish 해야 해요.

6. Min compaction lag 너무 큼

min.compaction.lag.ms너무 크면 compaction 이 안 일어나서 디스크가 폭증해요.

7. Tiered Storage 와의 호환

Kafka 3.6+ 의 compaction + tiered storage 결합은 일부 제약 이 있어요. 최신 문서 확인.

모니터링

JMX(Java Management eXtensions, JVM 운영 지표 노출 표준):

  • UncleanablePartitionsCount — compaction 불가능 partition 수 (0이어야)
  • MaxDirtyPercent — 가장 dirty 한 partition 의 %
  • time-since-last-run-ms — 마지막 compaction 후 시간

UncleanablePartitionsCount > 0 = 운영 사고 (log cleaner 가 처리 못 함).

Spring Boot 통합

spring:
  kafka:
    admin:
      properties:
        cleanup.policy: compact      # 기본값 설정 X (개별 topic 별)
@Bean
public NewTopic userProfilesTopic() {
    return TopicBuilder.name("user-profiles")
            .partitions(3)
            .replicas(3)
            .config("cleanup.policy", "compact")
            .config("min.cleanable.dirty.ratio", "0.5")
            .config("delete.retention.ms", "86400000")
            .build();
}

Part 5-6 고급 인프라 마무리

3편 (105~107):

  • 105 KRaft — Zookeeper 의 후속, Quorum 운영
  • 106 Tiered Storage — local + remote, 비용 절감
  • 107 Log Compaction — key 별 최신 유지

Kafka 의 최신 + 고급 기능 이에요. 입문 단계에서는 기본만 알고 실무 도입 시 깊이 학습.

시험 직전 한 번 더 — Log Compaction 함정 압축 노트

  • Log Compaction = key 별 최신 값만 유지, 시간 무관
  • 결과 = 데이터 크기 = key 수 (시간 무관, 영구 보존 가능)
  • Tombstone = value=null 메시지 = key 삭제 신호
  • delete.retention.ms (기본 1일) = tombstone 자체 보존
  • cleanup.policy = delete (시간) / compact (key) / delete,compact (hybrid)
  • min.cleanable.dirty.ratio=0.5 = dirty 50% 넘으면 compaction
  • min.compaction.lag.ms = 최근 메시지 보호
  • max.compaction.lag.ms = 강제 compaction
  • Segment 크기 작게 (segment.bytes=10~100MB) = compaction 효율
  • Log Cleaner Thread = 백그라운드 compaction
  • Dirty = 압축 안 된 새 메시지 / Clean = 이미 압축된 부분
  • 자주 쓰는 자리 = __consumer_offsets (내부)·Kafka Streams state·CDC·Configuration·User Profile
  • Pattern 1 = Pure compacted (영구) — config·user profile
  • Pattern 2 = delete,compact hybrid — CDC
  • Pattern 3 = 빠른 compaction — 매우 자주 update
  • Consumer = compaction 투명, 같은 API
  • earliest 부터 read = key 별 최신 상태 한 번에 (상태 재구축)
  • 함정 — 빈번한 update 없으면 비효율
  • 함정 — Key 필수 (key=null 안 됨)
  • 함정 — Log Cleaner Thread CPU·I/O 부담
  • 함정 — Tombstone 미사용 → 영원 보존
  • 함정 — min.compaction.lag.ms 너무 큼 → 디스크 폭증
  • 함정 — Tiered Storage 와 일부 제약
  • 모니터링 = UncleanablePartitionsCount·MaxDirtyPercent·time-since-last-run-ms
  • Part 5-6 고급 인프라 3편 = KRaft·Tiered Storage·Log Compaction

공식 문서: Kafka Log Compaction 에서 자세한 설계와 운영을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!