Kafka 토픽에서 같은 키의 가장 최신 값만 남기고 과거 값을 정리하는 방식. cleanup.policy=compact로 켠다.
기본 방식(delete)과의 차이 — 지우는 기준
-
cleanup.policy=delete — 기준은 나이·크기
- retention.ms(기본 7일)를 넘긴 세그먼트 파일을 통째로 삭제
-
cleanup.policy=compact — 기준은 키
- 같은 키의 옛 값만 골라 제거. 나이와 무관
동작
컴팩션 전: (u1,"서울") (u2,"부산") (u1,"대전") (u3,"광주") (u1,"제주")
↓ 같은 키의 옛 값 제거
컴팩션 후: (u2,"부산") (u3,"광주") (u1,"제주")
중요한 성질이 하나 있다 — 오프셋 번호는 그대로 유지된다. 즉 컴팩션 후 오프셋이 촘촘하지 않고 군데군데 비는데, 컨슈머는 원래 "다음 오프셋으로"가 아니라 "다음에 존재하는 레코드로" 읽으므로 문제가 없다.
무엇에 쓰나 — 토픽의 성격이 바뀐다
컴팩션 토픽은 "무슨 일이 있었나(이벤트 기록)"가 아니라 "지금 어떤 상태인가(스냅샷)" 를 표현한다. 처음부터 끝까지 다 읽으면 모든 키의 최신 상태가 복원되기 때문이다.
- 서비스가 뜰 때 캐시를 채우는 용도(캐시 워밍)
- 설정·기준정보 배포
- Kafka 자신도 쓴다 —
__consumer_offsets가 컴팩션 토픽이다. 필요한 건 "이 그룹의 현재 오프셋"이지 그 변경 이력이 아니니까
두 가지 전제
① 키가 없으면 컴팩션할 수 없다. 무엇의 최신인지 판단할 근거가 없다. 컴팩션 토픽에 보낼 메시지는 반드시 키를 가져야 한다.
② 삭제는 툼스톤으로 표현한다. 키는 있고 값이 null 인 메시지를 툼스톤(tombstone, 묘비) 이라 부른다. 컴팩션이 이걸 만나면 그 키의 모든 값을 지운다.
producer.send(new ProducerRecord<>("users", "u1", null)); // u1 삭제 표시
툼스톤 자체도 영원히 남지 않는다. 컨슈머들이 "이 키가 지워졌다"를 볼 시간을 준 뒤 delete.retention.ms 가 지나면 정리된다. 개인정보 삭제 요구(GDPR 등)가 이 방식으로 처리된다.
둘을 함께 쓰기
- cleanup.policy=compact,delete
- 먼저 키별로 컴팩션하고, 그다음 리텐션으로 오래된 것을 버린다
면접 함정
- ❌ "컴팩션을 켜면 중복 메시지가 사라진다" → 같은 키의 옛 값을 정리하는 것이지 중복 제거가 아니다. 중복은 여전히 컨슈머 멱등성으로 푼다.
- ❌ "컴팩션 토픽은 재처리에 좋다" → 반대다. 중간 이력이 사라지므로 이벤트 재생에는 쓸 수 없다. 재생이 목적이면
delete+ 충분한 리텐션이다.