Kafka log compaction vs time-based retention for changelog topics: an interactive simulator
A log that only grows is a complete history but an ever larger one. Time-based retention deletes old messages, which is right for events but wrong for state: a key that has not changed recently vanishes. Log compaction keeps the newest message for every key instead, so the topic stays about the size of the current state and a new consumer can rebuild that state by reading it from the start.
The playground follows a topic of user profiles for 60 days: every user written once on day 0, then 2,000 updates a day, mostly to a few active users, and a few account deletions. Pick a day and see what a brand new consumer could rebuild from each topic.
Day 10: 1,000 users were written on day 0, then 2,000 profile updates and 3 account deletions a day; 970 users exist now. Keeping everything holds 20,987 messages, growing every day, and a new consumer must read them all to rebuild 970 profiles. Deleting after 7 days keeps the log at 13,979 messages, but 87 users who have not changed for a week are gone from it: a new consumer rebuilding from this topic silently loses them. Compaction keeps only the newest message per user, plus today’s not yet compacted: 2,973 messages, and every one of the 970 profiles rebuilds correctly. Deleted users disappear once their tombstones have been cleaned.
retention.ms = 7 days- Messages kept
- 13,979
- Users rebuilt
- 883 of 970
- Users missing
- 87
retention.ms = -1- Messages kept
- 20,987
- Users rebuilt
- 970 of 970
- Users missing
- 0
cleanup.policy = compact- Messages kept
- 2,973
- Users rebuilt
- 970 of 970
- Users missing
- 0
What each policy keeps
- Keeping everything works for rebuilding but grows without bound: by day 60 the topic holds 119,619 messages for 820 users, and every new consumer must read all of them.
- Deleting after 7 days keeps the size flat, about 14,000 messages, but it drops users who have not changed for a week. From day 7 on, a new consumer silently misses between 58 and 106 of them, depending on the day. Time retention suits events; it is the wrong tool for state.
- Compaction keeps one message per live user plus the not yet compacted active segment: about 2,800 to 3,000 messages, and every profile rebuilds. Deleting a user takes a tombstone, a message with the key and no value, which compaction keeps for a while so consumers see the delete, then removes along with the user.
Configuring a compacted topic
# a changelog topic: the newest value per key, forever
kafka-topics --create --topic user-profiles \
--config cleanup.policy=compact \
--config min.cleanable.dirty.ratio=0.5 \ # clean once half the log is superseded
--config segment.ms=86400000 \ # roll the active segment daily; it is never compacted
--config delete.retention.ms=86400000 # keep tombstones a day after cleaning
# producing: the key is what compaction keeps one of
key=user:812 value={"name":"Ada","plan":"pro"}
key=user:812 value={"name":"Ada","plan":"team"} # supersedes the line above
key=user:977 value=null # tombstone: delete user 977Compaction runs in the background and never touches the active segment, so a compacted topic still holds some superseded values; consumers must apply messages in order and let later ones win. A consumer that is offline for longer than delete.retention.ms can miss a tombstone and keep a deleted key, so that setting should exceed the longest expected outage.
In a system design interview
Compacted topics come up wherever a stream carries state: “Each service publishes its entity changes to a compacted topic keyed by entity id. A new service, or a rebuilt cache or search index, bootstraps by reading the topic from the beginning and then keeps consuming. Event history we need for audit goes to a separate topic with time-based retention and to cold storage.” Kafka itself stores consumer offsets this way, and Kafka Streams backs its state stores with compacted changelog topics. Expect the follow-ups: how deletes work, how long a rebuild takes, and what the topic looks like before compaction catches up.
Read the guide to Kafka internals or change data capture, see Kafka partitions and consumer groups and event sourcing, or watch a strong candidate design a message queue.
Questions people ask
What is Kafka log compaction?
A cleanup policy that keeps at least the newest message for every key in a topic and discards older messages with the same key. The topic stays roughly the size of the current state instead of the full history, and can be replayed to rebuild that state.
What is the difference between log compaction and retention?
Time or size retention deletes whole old segments regardless of key, which suits event streams. Compaction deletes only messages superseded by a newer one with the same key, which suits changelogs where the latest value per key is what matters.
How do you delete a key from a compacted topic?
Produce a tombstone: a message with the key and a null value. Compaction removes earlier values for that key, keeps the tombstone for delete.retention.ms so consumers can see the delete, and then removes the tombstone too.
Why does a compacted topic still contain duplicates?
Compaction runs in the background, only once enough of the log is superseded, and never touches the active segment being written. Consumers therefore see some older values before newer ones and must apply messages in order, letting the latest win.
What are compacted topics used for?
Changelogs of entities, such as user profiles or product catalogues, that other services bootstrap from; Kafka’s own consumer offsets; and the changelog topics backing Kafka Streams state stores, which let a restarted instance restore its state.
Can I combine compaction and time retention?
Yes: cleanup.policy=compact,delete compacts by key and also deletes segments older than the retention period, which bounds history for keys that stop being updated, at the cost of losing them from the topic.
What happens if a consumer misses a tombstone?
A consumer offline for longer than delete.retention.ms may never see the tombstone, because both the old value and the tombstone have been removed, and keep the deleted key in its local state. Set the tombstone retention longer than the longest expected consumer outage.