06 · Log Segments, Indexes & Cleanup in Practice¶
Level 1 introduced segments and showed one. This lesson watches segments roll in real time as data is written, then watches time-based retention genuinely delete them — including a real, two-step deletion process this course's own test run surfaced along the way.
Worked example: real segment rolling at a byte boundary¶
$ bin/kafka-topics.sh --create --topic segment-demo --bootstrap-server localhost:9092 \
--config segment.bytes=1048576 --config retention.ms=5000 --config segment.ms=2000
$ bin/kafka-producer-perf-test.sh --topic segment-demo --num-records 20000 --record-size 200 ...
$ ls -la .../segment-demo-0/*.log
1048128 00000000000000000000.log
1048128 00000000000000004992.log
1048128 00000000000000009984.log
1048128 00000000000000014976.log
6749 00000000000000019968.log <- active segment, still growing
(Real output from this course's lab broker.) With segment.bytes=1048576 (1 MB), the broker rolled a new
segment exactly at that size, four times in a row, producing base offsets 0, 4992, 9984, 14976,
19968 — each roughly 4,992 records apart, consistent with 200-byte records plus overhead fitting almost
exactly 4,992 times into 1 MB. The final, smaller segment is the active one, still being appended to.
Worked example: real retention deletion, including a genuine two-step process¶
$ # (broker log, after retention.ms=5000 elapsed and the check interval ran)
INFO [UnifiedLog partition=segment-demo-0] Deleting segment LogSegment(baseOffset=0, ...)
due to log retention time 5000ms breach based on the largest record timestamp in the segment
INFO [UnifiedLog partition=segment-demo-0] Deleting segment LogSegment(baseOffset=4992, ...) due to ...
INFO [UnifiedLog partition=segment-demo-0] Deleting segment LogSegment(baseOffset=9984, ...) due to ...
INFO [UnifiedLog partition=segment-demo-0] Deleting segment LogSegment(baseOffset=14976, ...) due to ...
INFO [UnifiedLog partition=segment-demo-0] Deleting segment LogSegment(baseOffset=19968, ...) due to ...
INFO [UnifiedLog partition=segment-demo-0] Incremented log start offset to 20000 due to segment deletion
$ ls -la .../segment-demo-0/
1048128 00000000000000000000.log.deleted
1048128 00000000000000004992.log.deleted
1048128 00000000000000009984.log.deleted
1048128 00000000000000014976.log.deleted
6749 00000000000000019968.log.deleted
0 00000000000000020000.log
$ bin/kafka-get-offsets.sh --bootstrap-server localhost:9092 --topic segment-demo --time -2
segment-demo:0:20000
$ bin/kafka-get-offsets.sh --bootstrap-server localhost:9092 --topic segment-demo --time -1
segment-demo:0:20000
(Real output — this topic genuinely had all 20,000 records retained for only 5 seconds, by design, to make
this lesson's point observable quickly.) Every segment got deleted, including the previously-active one —
once every record in the log, even the newest, is older than retention.ms, there's nothing left to keep.
Notice the files weren't simply removed: they were renamed to .log.deleted first, and a brand-new, empty
active segment (00000000000000020000.log) was created. Both the earliest and latest offset now report
20000 — the log is genuinely, completely empty of the old data, even though the renamed .deleted files
were still sitting on disk at the moment this was checked.
How It Actually Works¶
The periodic log-cleanup thread runs every log.retention.check.interval.ms (the default is a relatively
relaxed 5 minutes — this lesson lowered it to 2 seconds specifically to make the demonstration observable
in a reasonable time, and a short retention.ms without also lowering the check interval would mean data
technically eligible for deletion sits around for up to 5 minutes longer than you might expect, a real and
common point of confusion). Each closed or active segment's eligibility is judged by its largest record
timestamp compared against retention.ms — once every segment in a partition breaches that threshold, the
log's start offset advances past all of it.
Deletion itself is two-phase, exactly as observed above: a segment marked for deletion is first renamed
with a .deleted suffix (removing it from the broker's active, in-memory view of the log immediately, so reads
and writes no longer see it) and only physically unlinked from disk after log.segment.delete.delay.ms
(default 60 seconds) passes. This delay exists to protect against a narrow race: a reader that had already
opened a file handle to a segment just before it was logically removed shouldn't have the underlying file
yanked out from under it mid-read; the renamed .deleted file sticks around briefly so any in-flight read
can finish safely before the bytes are actually reclaimed.
Common mistakes¶
- Setting an aggressively short
retention.msand expecting near-instant cleanup, without checkinglog.retention.check.interval.ms. As this lesson shows, the check interval's default (5 minutes) can make a 5-second retention setting behave like a 5-minute one in practice unless the interval is also tuned. - Seeing
.log.deletedfiles during a disk-space investigation and assuming something is stuck or broken. They're expected, temporary artifacts of the two-phase deletion process — they'll disappear on their own afterlog.segment.delete.delay.ms, and lowering that value (with caution) is the right lever if disk pressure from this delay window is a genuine operational concern. - Assuming the active segment is exempt from retention deletion. As this lesson's real log output shows directly, if the data in it is old enough, it gets rolled and deleted along with everything else — there's no special protection for "the segment currently being written to" beyond it naturally containing the newest data, which is usually (but not always, as shown here) younger than the retention threshold.
Exercise¶
Create a topic with a short retention.ms and a short log.retention.check.interval.ms (you'll need to set
the latter at the broker config level, as this lesson did, since it isn't a per-topic setting), produce enough
data to roll at least two segments, and watch the broker log for the real deletion messages. Confirm the
.log.deleted rename step yourself with ls, and check again after log.segment.delete.delay.ms passes to
confirm the files are finally gone.