왜 리텐션이 운영에서 문제가 되는가
Kafka 브로커의 디스크는 생각보다 빨리 찬다. 토픽마다 파티션이 있고, 파티션은 세그먼트 파일로 나뉘어 쌓인다. 기본 리텐션은 7일이지만, 트래픽이 몰리는 토픽 하나가 예상보다 3배 더 쌓이면 브로커 전체 디스크가 위험해진다. 디스크가 가득 차면 브로커는 로그를 쓸 수 없어 파티션 리더가 죽고, 클러스터 전체 가용성에 연쇄 장애가 온다. 리텐션은 단순한 청소 설정이 아니라 클러스터 안정성의 1차 방어선이다.
delete와 compact, 두 가지 정책
Kafka의 cleanup.policy는 두 축으로 나뉜다. delete는 시간(retention.ms)이나 크기(retention.bytes) 기준으로 오래된 세그먼트를 통째로 지운다. compact는 같은 key의 최신 값만 남기고 이전 값을 정리한다. 둘은 배타적이지 않아 compact,delete로 함께 쓸 수도 있다.
| 구분 | delete | compact |
|---|---|---|
| 기준 | 시간 / 크기 | key별 최신값 |
| 용도 | 이벤트 로그, 메트릭 | 상태 스냅샷, CDC, 설정 |
| 보존 단위 | 세그먼트 | 레코드(key 단위) |
| tombstone | 없음 | null 값으로 삭제 표현 |
이벤트 로그: delete 정책 튜닝
이벤트성 토픽은 시간과 크기를 함께 걸어 상한을 만든다. 시간만 걸면 트래픽 폭증 시 디스크가 터지므로, retention.bytes로 파티션당 물리 상한을 반드시 둔다.
# 토픽 단위로 시간 3일 + 파티션당 20GB 상한
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --entity-type topics --entity-name access-log \
--add-config retention.ms=259200000,retention.bytes=21474836480,segment.bytes=1073741824
# 현재 설정 확인
kafka-configs.sh --bootstrap-server localhost:9092 \
--describe --entity-type topics --entity-name access-log
segment.ms나 segment.bytes도 중요하다. 세그먼트는 닫혀야(roll) 삭제 대상이 되기 때문에, 세그먼트가 너무 크면 리텐션 시간이 지나도 삭제가 지연된다. 활성(active) 세그먼트는 절대 지워지지 않는다는 점을 기억해야 한다.
상태 토픽: compact 정책과 tombstone
KTable 백킹 토픽이나 CDC, 사용자 설정처럼 "현재 상태"가 중요한 데이터는 compact를 쓴다. 삭제는 값을 null(tombstone)로 보내 표현한다.
from confluent_kafka import Producer
p = Producer({"bootstrap.servers": "localhost:9092"})
# key별 최신 상태만 유지됨
p.produce("user-profile", key="u-1001", value='{"tier":"gold"}')
# tombstone: 이 key를 삭제하겠다는 신호
p.produce("user-profile", key="u-1001", value=None)
p.flush()
주의할 점은 tombstone도 즉시 사라지지 않는다는 것이다. delete.retention.ms(기본 24시간) 동안 유지되어야, 오프셋을 늦게 따라오는 컨슈머가 삭제 신호를 놓치지 않는다. 이 값을 너무 짧게 잡으면 컨슈머가 삭제를 못 보고 상태를 영원히 들고 있게 된다.
컴팩션이 실제로 도는 조건
컴팩션은 자동으로 즉시 실행되지 않는다. min.cleanable.dirty.ratio(기본 0.5)만큼 "더러운" 로그가 쌓여야 클리너 스레드가 동작한다. 즉 절반이 중복/삭제 대상이 되기 전엔 정리가 미뤄진다. 최신값이 빨리 반영되길 원하면 이 비율을 낮추고, 대신 클리너 부하가 늘어난다.
# 컴팩션을 더 자주, tombstone은 1시간만 보존
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --entity-type topics --entity-name user-profile \
--add-config cleanup.policy=compact,min.cleanable.dirty.ratio=0.1,delete.retention.ms=3600000,min.compaction.lag.ms=60000
min.compaction.lag.ms는 레코드가 최소 이 시간 동안은 컴팩션되지 않도록 보장한다. CDC 파이프라인에서 최근 변경 이력을 잠깐이라도 읽어야 할 때 유용하다.
운영 시 자주 밟는 지점
- 브로커 기본값 의존 금지:
log.retention.hours같은 브로커 전역값에만 기대면 토픽별 특성을 못 살린다. 중요한 토픽은 토픽 단위로 명시한다. - 클리너 스레드 부족: 컴팩트 토픽이 많으면
log.cleaner.threads가 부족해 정리가 밀린다. 디스크가 안 줄면 클리너 지연을 먼저 의심한다. - compact 토픽에 key 없이 produce: key가 null이면 컴팩션 대상이 안 되어 무한정 쌓인다. 프로듀서 단에서 key를 강제한다.
- 세그먼트 롤 지연: 트래픽이 적은 토픽은 세그먼트가 안 닫혀 삭제가 지연된다.
segment.ms로 시간 기반 롤을 걸어둔다.
정리
리텐션과 컴팩션은 "데이터를 얼마나, 어떤 기준으로 남길지"를 토픽 성격에 맞춰 설계하는 일이다. 이벤트 로그는 시간·크기 상한으로 디스크를 지키고, 상태 토픽은 compact와 tombstone으로 최신값을 유지한다. 자동 정리가 도는 조건(세그먼트 롤, dirty ratio, 클리너 스레드)을 이해하고 있어야, 디스크가 안 줄어드는 상황에서 원인을 빠르게 짚을 수 있다.