백엔드 데이터 인프라 107편. Kafka Log Compaction — cleanup.policy=compact 의 메커니즘. 키별 최신 값만 유지·tombstone 으로 삭제·dirty ratio·자주 쓰는 자리 (config·state·CDC·Kafka Streams state store) 까지 풀어쓴 학습 노트. Part 5-6 고급 인프라 마무리.
이 글은 백엔드 데이터 인프라 시리즈 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% 넘으면 compactionmin.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,compacthybrid — 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 에서 자세한 설계와 운영을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 102편 — Kafka 다중 데이터센터 (Stretched · Local + Mirror)
- 103편 — Kafka Geo-Replication (MirrorMaker 2)
- 104편 — Kafka Hardware · OS (CPU·메모리·디스크·튜닝)
- 105편 — Kafka KRaft (Zookeeper 의 후속 · Quorum 운영)
- 106편 — Kafka Tiered Storage (S3 · 무한 Retention)
다음 글: