retention과 compaction — 메시지가 언제, 어떻게 사라지는가

Kafka는 메시지를 읽어도 지우지 않는다(장 01·02). 그럼 디스크는 계속 찰 수밖에 없나? 아니다 — topic마다 "언제, 무엇을 지울지" 정책(retention/compaction)을 둬서 오래된 데이터를 정리한다. 이 글은 그 두 가지 규칙과 실제 설정 방법을 다룬다.

주의할 점부터: "compaction"은 압축(zip)이 아니다. 같은 key의 옛 메시지를 버리는 것이다. 이 용어 혼동이 운영 착오를 자주 만든다.

메시지는 영원히 남지 않는다 — 왜 retention이 필요한가

Kafka topic은 로그라 메시지가 계속 쌓인다. 하지만 디스크는 유한하다. 그래서 topic마다 정리 정책을 둔다. 두 가지:

  • delete — 오래된 것을 시간이나 크기 기준으로 버린다.
  • compact — 같은 key의 옛 값을 버리고 최신값만 남긴다.

이 둘은 "언제 지울지"의 차이가 아니라 "무엇을 지울지"의 차이다.

segment — 로그를 이루는 파일 단위

partition 하나는 여러 segment로 구성된다 (log.md). 각 segment는 세 파일로 이루어진다:

파일 역할
.log 실제 메시지 데이터(순차 append)
.index offset → 파일 내 바이트 위치 매핑(빠른 임의 접근)
.timeindex timestamp → offset 매핑(시간 기반 조회·retention)

segment는 log.segment.bytes(기본 1 GiB)에 도달하거나 log.segment.ms(시간)가 지나면 새 segment로 넘어간다(rolling). active segment(마지막, 쓰기 진행 중)는 삭제 대상이 아니다 — rolling되어 읽기 전용이 된 segment만 삭제 후보.

segment가 왜 중요한가

"consumer가 offset 5000을 읽고 싶다"고 할 때, partition 전체를 처음부터 훑으면 느리다. .index 파일이 offset 5000이 어느 바이트 위치에 있는지 알려주어 O(1)에 찾는다. retention도 segment 단위로 일어나서 — 파일 중간을 수정할 필요 없이 segment 전체를 삭제(append-only 유지).

delete 정책 — 오래된 segment를 통째로 버린다

cleanup.policy=delete(기본)는 오래된 segment를 통째로 버린다. 두 기준:

  • 시간 기반: segment 내 가장 큰 timestampretention.ms(기본 7일 = 604800000ms)를 넘으면 그 segment 삭제.
  • 크기 기반: partition 크기가 retention.bytes(기본 -1 = 비활성) 한계를 넘으면 가장 오래된 segment부터 삭제.

둘 다 켜면 어떤 쪽이든 임계에 닿으면 삭제(먼저 닿는 쪽).

retention 설정 예

topic 생성 시 retention을 1시간으로 설정:

# Kafka 4.3
bin/kafka-topics.sh --create --topic short-lived \
  --partitions 3 --replication-factor 1 \
  --config retention.ms=3600000 \
  --bootstrap-server localhost:9092

topic별로 동적 변경:

bin/kafka-configs.sh --alter --topic short-lived \
  --add-config retention.ms=7200000,retention.bytes=1073741824 \
  --bootstrap-server localhost:9092

현재 설정 확인:

bin/kafka-configs.sh --describe --topic short-lived --bootstrap-server localhost:9092

retention 시나리오 — 시간순 타임라인

시각 0:    partition 생성, segment-0 활성화
시각 1h:   segment-0이 1GiB 도달 → segment-1로 rolling
시각 7d:   segment-0의 max timestamp가 7일(retention.ms) 초과 → segment-0 삭제
시각 8d:   segment-1 삭제...

이 흐름이 "7일 보관, segment 단위 삭제"가 실제로 어떻게 일어나는지 보여준다.

compact 정책 — 같은 key의 최신값만 남긴다

cleanup.policy=compact는 key 기반이다. 같은 key의 메시지가 여러 개 있으면 가장 최근 것만 남기고 옛 것은 버린다 (design.md — Log Compaction).

예: topic에 user1의 프로필이 v1, v2, v3로 업데이트되며 들어온다. compaction 후엔 user1v3만 남는다. 과거(v1, v2)는 버려진다. 이게 "사용자 최신 상태만 유지"가 필요한 topic에 유용한 이유다.

compaction ≠ 압축

"compaction = 압축(zip)"이라고 생각하면 안 된다. 압축은 용량을 줄이지만 데이터는 그대로고, compaction은 옛 데이터 자체를 버린다. 용량은 줄지만 원리가 다르다.

tombstone — "이 key를 삭제하라" 표식

value가 null인 메시지를 tombstone이라 한다. compaction에서 "이 key를 더 이상 유지하지 마라"는 삭제 표식. 일정 기간 후 tombstone 자체도 제거된다.

tombstone 사용 예:

# user1을 삭제하려면 value=null로 produce
bin/kafka-console-producer.sh --topic user-profiles \
  --property "parse.key=true" --property "key.separator=:" \
  --bootstrap-server localhost:9092
# 입력: user1:  (value 없음 = null = tombstone)

dirty ratio — compaction 트리거

min.cleanable.dirty.ratio(기본 0.5) — 한 segment에서 옛 key 값이 일정 비율(dirty) 이상 쌓여야 compact 대상이 됨. 너무 낮으면 빈번 compact(CPU 비용), 너무 높으면 용량 비효율.

key가 없으면 compact는 무의미

key가 null인 메시지는 compact 대상이 아니다 → delete와 혼합(compact,delete) 운영이 권장된다.

delete vs compact — 무엇을 지울 것인가

정책 무엇이 남나 전형 용도 설정
delete 기간 내 전체 이력 이벤트 로그, 원시 데이터 cleanup.policy=delete, retention.ms=604800000
compact 각 key 최신값 상태 스냅샷, config, 사용자 프로필 cleanup.policy=compact, min.cleanable.dirty.ratio=0.5
compact,delete key 최신 + 기간 제한 상태 + tombstone 정리 cleanup.policy=compact,delete

Kafka 4.3 실습 — 직접 확인하기

사전: KRaft 단일 노드.

retention 관찰

# Kafka 4.3 — 짧은 retention으로 topic 생성 (1분)
bin/kafka-topics.sh --create --topic ret-demo \
  --partitions 1 --replication-factor 1 \
  --config retention.ms=60000 \
  --bootstrap-server localhost:9092

# 메시지 produce 후 1분 대기 → describe로 segment 확인
bin/kafka-topics.sh --describe --topic ret-demo --bootstrap-server localhost:9092

compaction (key 기반)

# cleanup.policy=compact 로 topic 생성
bin/kafka-topics.sh --create --topic comp-demo \
  --partitions 1 --replication-factor 1 \
  --config cleanup.policy=compact \
  --bootstrap-server localhost:9092

# 같은 key에 여러 값 produce
bin/kafka-console-producer.sh --topic comp-demo \
  --property "parse.key=true" --property "key.separator=:" \
  --bootstrap-server localhost:9092
# user1:v1 / user1:v2 / user1:v3 입력

확인할 것: compact 실행 후 user1의 최신값(v3)만 남는지(즉시 관찰은 어려움 — dirty ratio·시간 조건 충족 시 실행).

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"compaction = 압축(zip)" 아님. key별 옛값 제거
"retention은 시간 OR 크기 중 하나만" 둘 다 켜면 먼저 닿는 쪽 삭제
"key 없이 produce해도 compact된다" key 없으면 compact 의미 없음
"메시지 단위로 삭제된다" segment 단위로 통째 삭제
"active segment도 retention 대상" 아님. rolling 후 후보
"tombstone은 영구 남는다" 일정 기간 후 제거됨

더 깊이

  • __consumer_offsets는 왜 compact인가: consumer offset은 (group,topic,partition)을 key로 저장. 같은 key의 옛 offset은 필요 없음 → compact로 최신만 유지(장 04).
  • tiered storage (3.x+): 로컬 디스크 외 원격(S3)에 오래된 segment 보관 → 비용 절감.
  • log.segment.ms: 시간 기반 segment rolling. 기본값은 7일(미검증). 짧게 두면 segment 수가 많아져 파일 핸들 증가.

요약 — 이 글의 결론

  • segment: partition을 이루는 파일 단위(.log + .index + .timeindex). active segment만 쓰기.
  • delete: 시간(retention.ms, 기본 7일) 또는 크기(retention.bytes) — 먼저 도달한 쪽. segment 통째 삭제.
  • compact: key별 최신값만 유지. tombstone(null)으로 key 삭제. key 없으면 무의미. (압축 아님!)
  • compact,delete: 상태 유지 + 기간 제한 결합.
  • 설정: topic별로 동적 변경(kafka-configs.sh --alter --add-config).
  • delete vs compact는 "무엇을" 지울지의 차이 — 시간/크기(delete) vs key별 옛값(compact).

생각해 볼 문제

  1. delete와 compact의 차이를 "무엇을 지우는가" 관점에서 설명하라.
  2. retention 시간과 크기를 둘 다 켜면 어떻게 동작하는가?
  3. segment를 통째로 삭제하는 이유(append-only)를 설명하라.
  4. compaction에서 tombstone(value=null)의 역할은? 어떻게 쓰는가?
  5. key 없이 produce하는데 cleanup.policy=compact인 topic에서 무슨 일이 벌어지는가?
  6. __consumer_offsets가 compact 정책을 쓰는 이유는?
  7. retention.ms를 topic별로 동적 변경하는 명령은?

참고

'Tech Artifacts > Kafka' 카테고리의 다른 글

Kafka - 09. schema registry  (1) 2026.07.09
Kafka - 08. delivery semantics  (0) 2026.07.09
Kafka - 06. replication  (0) 2026.07.09
Kafka - 05. consumer group  (0) 2026.07.09
Kafka - 04. consumer  (0) 2026.07.09