Log compaction — keep the last value per key

A compacted topic retains only the last value per key, with tombstones (null values) for explicit deletes — turning the log into a state store that Streams k-tables, Connect CDC sinks, and the __consumer_offsets topic itself rely on.

Previously

Time-based retention drops old records. Compaction is the OTHER retention policy — keep the latest value per key forever, and turn the log into a state store the rest of the ecosystem reads from.

Scene 05a

Log compaction — keep the last value per key

  1. Watch
  2. Try it
  3. Predict
  4. Capture
Producersend(key, value)key, valuePARTITION 0users1 partitioncompact-retentionConsumerreads current state (0 keys)
What to watch for

A topic called "users" with one partition. The producer keeps writing the same keys (alice, bob, carol) with new values. Watch the compactor pass at the end — same key, only the last value survives.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

LogCleaner.compact
the dedup-by-key pass that rewrites a dirty segment
def compact(segment):
# pass 1: scan, remember newest offset per key
latest_offset_for_key = {}
for record in segment:
latest_offset_for_key[record.key] = record.offset
# pass 2: emit only the survivor of each key
cleaned = []
for record in segment:
if shouldRetain(record, latest_offset_for_key):
cleaned.append(record)
swapSegment(segment, cleaned)
Cleaner.shouldRetain
per-record decision: am I the survivor for this key?
def shouldRetain(record, latest_offset_for_key):
latest = latest_offset_for_key[record.key]
# survivor = the record at the newest offset
if record.offset != latest:
return False
# tombstones (null value) linger one extra round so
# downstream consumers observe the delete, then go
if record.value is None:
return within(delete.retention.ms, record.timestamp)
return True
Cleaner.dropTombstone
second pass: finally remove the tombstone (and the key)
def dropTombstone(record, now):
if record.value is not None:
return False
# delete.retention.ms gives slow consumers a chance
# to observe the null and propagate the delete
if now - record.timestamp < delete.retention.ms:
return False
return True # key vanishes from the log

Where this sits in Build Kafka

Scene 05a of 13, in the Write side act — Partitioning, replication, and durability knobs.. Compact-retention turns the log into a state store; tombstones propagate deletes.

Up next. Leader epoch — when a leader fails over, how do followers know which records to keep and which to truncate? Pre-KIP-101 used the High Water Mark, and that silently lost acked writes.

All 13 scenes in Build Kafka · Every curriculum

Built with Arqly
Every scene in Build Kafka builds on the one before it.All 13 Build Kafka scenes