Index Files in Kafka: Offset Index and Time Index Explained

Index Files in Kafka: Offset Index and Time Index Explained

How Kafka finds one message among billions in a fraction of a millisecond: what the offset index and the time index store, how they are written, searched, rebuilt after a crash, and operated safely in production.

Imagine a gigantic library that never stops growing. Every minute, thousands of new pages are glued onto the end of one enormous scroll, and the scroll is so long that it is cut into big rolls stored on different shelves. Now a visitor walks in and says, “Please give me everything starting from page number 8,400,000.” If the librarian had to unroll every roll from the very beginning and count pages, the visitor would be waiting for hours. Instead, the librarian keeps a small card on each roll with a handful of bookmarks: “page 8,398,112 starts this many inches from the edge.” One glance at the card, one short walk, and the visitor has the right page.

Apache Kafka has exactly this problem, and it solves it with exactly this kind of bookmark card. The cards are called index files. Kafka keeps two kinds for every piece of every log: an offset index that answers “where is message number X?” and a time index that answers “where are the messages from time T?”. In this tutorial we will open these files, look at every byte that matters, follow a read request from the network all the way to the disk, and learn how to run brokers so that these small files never become a big problem.

1Why Kafka Needs Index Files

Before we meet the files themselves, we need to feel the problem they were invented to solve.

Kafka in one paragraph

Kafka stores messages, which it calls records, in named streams called topics. To spread the work across many machines, each topic is cut into pieces called partitions. A partition is an append-only log: new records are only ever added at the end, and old records are never edited in place. Each record in a partition gets a whole number called its offset, starting at 0 and growing by one for every record. Offset 41 is the record right before offset 42, always, inside that partition.

Simple Analogy

A partition is like a very long train. Every coach has a number painted on it: 0, 1, 2, 3, and so on. New coaches are always attached at the back. Nobody ever swaps a coach in the middle. The number on a coach is its offset, and it never changes for the life of that coach.

The problem: a huge log and a picky reader

A consumer, which is a program that reads records, does not always want to start at the beginning. Very often it says, “I already processed everything up to offset 8,399,999, so please start from 8,400,000.” A single partition can hold hundreds of gigabytes. Reading from the start and counting records until we reach offset 8,400,000 would take a very long time and would waste disk and network. Kafka must jump straight to the right spot, and it must do that for thousands of consumers at once, all day long.

There is a second, different question. Sometimes a person does not know an offset at all. They only know that “the bug started at 2:15 in the afternoon” and they want to replay everything from that moment. Offsets are just counters, so they say nothing about the clock. Kafka needs a second helper that translates a time into an offset.

The two helpers

Helper 1

Offset Index

Maps an offset to a byte position inside a log file. Answers: where does message number X live?

Helper 2

Time Index

Maps a timestamp to an offset. Answers: which message was the first one at or after time T?

Words you will see again and again

Term 1

Record

One message: an optional key, a value, a timestamp and headers. Records are written in groups called batches.

Term 2

Offset

The permanent sequence number of a record inside one partition.

Term 3

Segment

One file-sized slice of a partition’s log. A partition is a chain of segments.

Term 4

Broker

A Kafka server. It stores partitions on its disks and serves producers and consumers.

Term 5

Leader and Follower

For each partition, one broker is the leader that serves clients. Other brokers are followers that copy the leader.

Term 6

Page Cache

Memory the operating system uses to keep recently used file data, so repeated reads avoid the disk.

8 bytes
size of one offset index entry
12 bytes
size of one time index entry
4 KiB
default byte gap between index entries
i
Remember This

Index files never store your message data. They store only tiny signposts: numbers that tell Kafka where to start looking. That is why they can be so small and so fast.

2Anatomy of a Partition Directory

Index files do not live alone. They are one member of a small family of files that together describe a segment.

Partitions become directories, logs become segments

On a broker’s disk, every partition is a directory. The directory name is the topic name, a dash, and the partition number, so the third partition of a topic called payments is stored in a directory named payments-2. Inside that directory you will not find one giant file. You will find many segments, and each segment is a small group of files that share the same name prefix.

The prefix is the base offset of the segment: the offset of the first record that the segment can hold. It is written as a twenty-digit number padded with zeros, so a segment starting at offset 368,769 has a name that begins with the digits 00000000000000368769. Because the name is the base offset, Kafka can sort the segments simply by name, and it can tell which offsets each segment covers without opening a single file.

flowchart TD
  T["Topic: payments"] --> P0["Partition directory: payments-2"]
  P0 --> S1["Segment with base offset 0"]
  P0 --> S2["Segment with base offset 368769"]
  P0 --> S3["Active segment with base offset 737912"]
  S2 --> L2["log file: the records"]
  S2 --> I2["index file: offset to position"]
  S2 --> TI2["timeindex file: time to offset"]
  S2 --> TX2["txnindex file: aborted transactions"]
  P0 --> CK["checkpoint and epoch files"]
        
FIG 1 — A partition directory is a chain of segments, each with its own family of files

The family of files

FileWhat it holdsNotes
.logThe actual record batchesThe only file that contains your data
.indexOffset index: relative offset to byte position8 bytes per entry, memory mapped
.timeindexTime index: timestamp to relative offset12 bytes per entry, memory mapped
.txnindexAborted transactions that touch this segmentUsed by consumers that read only committed data
.snapshotSnapshot of producer stateSupports idempotent and transactional producers
leader-epoch-checkpointWhich leader era started at which offsetOne per partition directory, used to truncate safely
partition.metadataTopic identifier of the partitionOne per partition directory

In this tutorial we focus on the two files with the words index and timeindex in their names. The others matter because they show that Kafka uses the same idea, “keep a small side file next to the big data file”, for several different questions.

Why segments exist at all

Why not keep one endless log file per partition? There are three strong reasons. First, deleting old data would be painful: to remove the oldest hour you would have to rewrite a giant file, while with segments Kafka simply deletes whole old files. Second, indexes would grow without limit; with segments each index stays small and bounded. Third, after a crash, Kafka can check and repair segments one by one instead of scanning one monstrous file.

Simple Analogy

Think of a school diary split into monthly booklets. To throw away last year’s notes you just recycle twelve booklets. Each booklet has its own little contents page at the front, so you never need a contents page for the whole year in one giant list.

Active segment versus closed segments

Only the newest segment, called the active segment, accepts writes. All older segments are closed: their log and index files never change again until they are deleted. This simple rule makes reading old data very safe and very cache friendly, because closed files are read-only and their pages in memory are never dirtied by new writes.

i
Good To Know

Index files are found by name, not by content. The .index and .timeindex files of a segment always share the exact twenty-digit prefix with the .log file of the same segment.

3The Offset Index in Depth

The offset index is a tiny sorted table that turns “which offset?” into “which byte?”.

What one entry looks like

Every entry in the offset index is exactly eight bytes. The first four bytes hold a relative offset. The last four bytes hold a physical position, which is the number of bytes from the start of the .log file where a batch of records begins. The pair says: “Around this offset, start reading at this byte.”

Why relative? Instead of storing the full offset, which could be a big number needing eight bytes, Kafka stores how far the offset is from the segment’s base offset. If the segment starts at 368,769 and the entry is for offset 368,800, Kafka stores 31. The full offset is rebuilt by adding the base offset from the file name. This halves the size of the number, and it explains two hidden limits: a single segment cannot cover more than about two billion offsets, and a segment file cannot be larger than about two gibibytes, because both numbers must fit in four bytes.

Simple Analogy

Suppose a street has house numbers from 3,687 to 3,999 in one block. On the block’s signboard you would not write the full number every time. You would write “house 1, house 2, house 3” counting from the start of the block. The full number is easy to rebuild when you know where the block begins.

Sparse, not dense

An obvious idea would be to write one index entry for every single record. That is called a dense index. Kafka deliberately does not do that. It writes an entry only after roughly a certain number of bytes have been appended since the last entry. The gap is controlled by a setting whose default is 4,096 bytes. This is called a sparse index.

Let us do the arithmetic for a full one gibibyte segment. With the default gap, the index holds at most 262,144 entries, and at eight bytes each that is about two mebibytes. Now imagine records that are only 100 bytes each. A one gibibyte segment then holds roughly ten million records, and a dense index would need about eighty mebibytes for that one segment. The sparse index is around forty times smaller, small enough that the indexes of thousands of segments can live comfortably in memory.

2 MiB
sparse index for a full 1 GiB segment
80 MiB
dense index for the same segment with 100 byte records
about 40x
memory saved by staying sparse

The price of being sparse

A sparse index cannot land exactly on the record you want. It lands on the closest signpost at or before it, and then Kafka reads forward through the log file until it reaches the exact batch. Because signposts are at most about four kibibytes apart, that forward walk is short: a few thousand bytes that are already in the page cache. A tiny bit of extra reading is a very good trade for a much smaller index.

Sorted and append-only

Entries are added only at the end, and each new entry has a larger offset and a larger position than the one before. The file is therefore always sorted, and a sorted table can be searched with binary search, the “guess the middle and halve the range” method. Finding one entry among a quarter of a million takes only about eighteen steps.

Preallocated files

When Kafka creates a new segment, it creates the index files at their full maximum size at once. The maximum is controlled by a setting whose default is ten mebibytes. The file is filled with zeros, and Kafka tracks how many entries are valid. When the segment is closed, Kafka trims the index down to its real size. Preallocation avoids the cost of growing the file repeatedly and lets the operating system map it into memory once.

!
Common Surprise

If you list the files of a live broker, the index files of the active segment look like ten mebibytes even when the segment has only a few records. This is normal preallocation, not a leak. Closed segments show their true, much smaller size.

4The Time Index in Depth

The time index lets Kafka work with clocks even though offsets are only counters.

What one entry looks like

Every time index entry is twelve bytes. The first eight bytes are a timestamp in milliseconds. The last four bytes are a relative offset, counted from the segment’s base offset just like in the offset index. The entry means: “By the time we reached this offset, the biggest timestamp we had seen in this segment was this one.”

Notice the careful wording: the biggest timestamp seen so far, not the timestamp of that single record. This matters because record timestamps can arrive out of order. One producer machine may have a clock that runs a bit slow, and another may run fast. By always storing the largest timestamp seen so far, and by writing an entry only when that value grows, Kafka keeps the time index sorted. A sorted table again means binary search works.

Simple Analogy

Picture a diary where, every few pages, you write in the corner “latest date so far: 14 March.” Even if you sometimes wrote an older note late, that corner date never goes backwards. To find March 10 you flip to a corner that says March 10 or earlier, and then read forward a little.

Two kinds of timestamps

Each topic decides where its record timestamps come from. This choice changes how trustworthy the time index is.

SettingWho sets the timeStrengthWeakness
CreateTimeThe producer, when it creates the recordReflects when the event really happenedDepends on client clocks, so can be wrong or out of order
LogAppendTimeThe broker, when it appends the batchAlways increasing and consistentShows arrival time, not event time

Why the time index was invented

Before Kafka 0.10.1, there was no time index. Kafka decided when to delete old data by looking at the last modified time of the segment file on disk. That sounds reasonable until you remember that files get copied. When a partition is moved to a new broker, or a follower rebuilds its copy, the new files get fresh modified times. Old data suddenly looked young, so it stayed on disk far longer than intended. A proposal known as KIP-33 added the time index and per-record timestamps so that retention and searching could use the real data times instead of file times.

What the time index is used for

Use 1

Seek by time

Consumers and tools ask for the first offset at or after a timestamp, to replay from “two hours ago”.

Use 2

Time-based retention

The broker deletes a segment only when its largest timestamp is older than the retention limit.

Use 3

Time-based rolling

A segment can be closed after a set amount of time, measured against the data it holds.

Use 4

Tooling and audits

Offset reset commands and offset listing tools translate human dates into offsets using this index.

!
Watch Out

A single record with a timestamp far in the future can raise a segment’s largest timestamp so high that retention refuses to delete that segment for a very long time. Later we will see how to guard against this.

5How Index Entries Get Written

Every append to the log makes a small decision: is it time to drop another bookmark?

The write path, step by step

1

A batch arrives at the leader

A producer sends a batch of records. The leader broker validates it, assigns the next offsets in order, and checks or overwrites timestamps depending on the topic’s timestamp type.

2

The batch is appended to the active segment’s log file

The bytes go to the end of the .log file through the operating system, which keeps them in the page cache and writes them to disk in the background.

3

The segment updates its running facts

It remembers the biggest timestamp seen so far, and which offset carried it, so the time index can use them later.

4

The gap check

Kafka compares the number of bytes appended since the last index entry with the index interval setting. If the gap is not yet reached, nothing else happens.

5

The bookmark is written

If the gap is reached, one entry goes into the offset index and one entry goes into the time index, and the byte counter starts again from zero.

flowchart TD
  A["Batch arrives at the leader"] --> B["Assign offsets and validate timestamps"]
  B --> C["Append bytes to the active log file"]
  C --> D["Update largest timestamp seen so far"]
  D --> E{"Bytes since last entry at least the index interval?"}
  E -- "No" --> F["Done. No index entry this time"]
  E -- "Yes" --> G["Write offset index entry"]
  G --> H["Write time index entry if the timestamp grew"]
  H --> I["Reset the byte counter"]
  I --> J{"Segment full or too old?"}
  F --> J
  J -- "Yes" --> K["Roll to a new active segment"]
  J -- "No" --> L["Wait for the next batch"]
        
FIG 2 — The append path and the decision to write an index entry

Bytes, not records

The gap is measured in bytes, not in number of records. If your records are large, say one kibibyte each, an entry appears roughly every four records. If your records are tiny, an entry might appear only every few dozen records. In both cases the distance the broker has to scan after a lookup is about the same, and that is exactly the point of measuring in bytes.

Batches are the unit of appending

Kafka appends whole batches, and index entries are decided after a batch is written. So an index entry always points to a batch boundary, never to the middle of a batch. When a consumer asks for an offset in the middle of a batch, the broker returns the whole batch and the consumer library quietly skips the earlier records it did not need.

Followers build their own indexes

This is an important and often misunderstood point. Followers do not copy the index files from the leader. They fetch the record batches, append them to their own log, and run the very same gap check to build their own index files. The offsets inside the batches are already assigned by the leader, so every replica ends up with identical records at identical offsets, while the exact index entries may differ slightly because the batch boundaries seen by followers can be different.

When a segment is closed

A segment is rolled, meaning closed and replaced by a fresh active segment, when any of these happens: the next batch would push the log past the segment size limit; the segment has been open longer than the segment time limit; the offset index or time index is full; or the relative offset would no longer fit in four bytes. Rolling is a normal, cheap event and we will follow it in detail in chapter 8.

i
Design Insight

Writing an index entry costs almost nothing: eight bytes appended at the end of a memory-mapped file. Kafka gets fast lookups by doing a tiny amount of extra work on the write path, exactly when it already has the numbers in hand.

6Finding a Message by Offset

Now we follow a real read request from the moment it arrives to the moment bytes leave the machine.

The scenario

A consumer sends a fetch request to the leader of a partition: “Give me records starting at offset 8,400,000.” The broker must find the right segment, the right byte inside the segment, and then send the data. Three small lookups do all the work, and each one is cheap.

sequenceDiagram
  participant C as Consumer
  participant B as Broker
  participant M as Segment map
  participant I as Offset index
  participant L as Log file
  C->>B: Fetch from offset 8400000
  B->>M: Which segment has the biggest base offset at or below 8400000
  M-->>B: Segment with base offset 8391200
  B->>I: Find last entry at or below relative offset 8800
  I-->>B: Entry for relative offset 8790 at position 5316608
  B->>L: Scan forward from position 5316608 to the right batch
  L-->>B: Batch found at position 5318912
  B-->>C: Send bytes from that position, up to the limits
        
FIG 3 — Three small lookups turn an offset into a file position

Lookup 1: choose the segment

The broker keeps the segments of each partition in a sorted in-memory map whose keys are base offsets. It asks the map for the entry with the largest key that is not bigger than the requested offset. Because the map is sorted, this takes a handful of comparisons. If the answer is the segment with base offset 8,391,200, we now know that the record must be inside that segment, or the offset is beyond the end of the log.

Lookup 2: binary search in the offset index

The broker subtracts the base offset from the requested offset to get a relative offset, here 8,800. It then binary searches the segment’s offset index for the last entry whose relative offset is less than or equal to 8,800. That entry, say for relative offset 8,790, gives a byte position. Since the index is memory mapped, this search touches only a few memory pages and usually never reaches the disk.

Lookup 3: a short scan in the log file

From that position the broker reads batch headers one after another until it finds the batch that contains offset 8,400,000. It only reads headers, not whole payloads, so the scan is tiny. If the requested offset is smaller than the first index entry, the scan simply starts at position zero.

Simple Analogy

To find a scene in a long movie, you first pick the right disc (the segment map), then jump the slider to the nearest chapter mark (the offset index), and finally let the film play a few seconds until the exact scene appears (the short scan).

Sending the bytes

Once the position is known, the broker does not load the records into its own program memory. It asks the operating system to copy bytes from the file’s page cache directly to the network connection. This technique is called zero-copy, and it is one reason Kafka can serve enormous read rates on modest hardware. The broker also respects limits: the maximum bytes the consumer asked for, and the boundary of what is safe to expose, which is the high watermark, or the last stable offset for consumers that read only committed transactions.

i
Why It Is Fast

The total cost of a lookup is a sorted-map search, a binary search of about eighteen steps over memory that is probably already cached, and a scan of at most a few kibibytes. Nothing in this path grows with the size of the partition.

When the offset does not exist

If the requested offset is lower than the oldest record still kept, or higher than the newest, the broker answers with an “offset out of range” error. The consumer then applies its reset policy: jump to the earliest offset, jump to the latest, or fail loudly. The index files are what make this check quick, because the first and last offsets are known from the segment names and the log end.

“The index never finds the record. It only gets Kafka close enough that finding the record is trivial.”

7Finding a Message by Timestamp

Time lookups reuse the offset index, so the two files work as a relay team.

The question being asked

A consumer program, an admin tool, or a stream processing job calls a lookup that says, “Return the earliest offset whose timestamp is greater than or equal to this time.” The answer is an offset. From that offset, everything continues exactly like chapter 6. This lookup is exposed to clients as a method that turns timestamps into offsets, and to operators through tools that list offsets by time and reset consumer groups to a date.

flowchart TD
  Q["Request: first offset at or after time T"] --> S1["Walk segments in order and pick the first one whose largest timestamp is at least T"]
  S1 --> S2["Binary search that segment's time index for the last entry with timestamp at most T"]
  S2 --> S3["Take the offset stored in that entry as a starting hint"]
  S3 --> S4["Use the offset index to turn that offset into a byte position"]
  S4 --> S5["Scan the log forward until a record has timestamp at least T"]
  S5 --> R["Return that record's offset"]
        
FIG 4 — A time lookup uses the time index first and the offset index second

Step by step

1

Skip whole segments

Each closed segment knows its largest timestamp. Any segment whose largest timestamp is older than the requested time cannot contain the answer, so it is skipped without reading anything.

2

Binary search the time index

Inside the chosen segment, Kafka finds the last time index entry whose timestamp is not later than the requested time. That entry gives an offset that is safely before, or at, the wanted record.

3

Translate the offset into a position

The offset index converts that offset into a byte position, the same way as in a normal fetch.

4

Scan forward for the first match

Kafka reads forward and returns the first record whose timestamp is at least the requested time. Because timestamps can be slightly out of order, this final scan is what guarantees the correct answer.

Why a scan is still needed

The time index stores the largest timestamp seen so far, which is a summary, not a record-by-record truth. Suppose a record with a late timestamp appears early in the segment. The index entry after it will show that big timestamp, even though many later records have smaller ones. The lookup therefore uses the index only to land safely before the answer, and lets the final scan pick the exact first record. The result is correct even for messy clocks.

Edge cases you should know

Case 1

Time is in the future

If no record is that new, the lookup returns nothing for that partition. Client libraries show this as an empty result, and callers usually seek to the end.

Case 2

Time is before all data

The answer is the earliest offset still stored. Deleted data cannot be found again.

Case 3

Old message format

Records written by very old producers have no timestamp, so time lookups on them are not meaningful.

Case 4

Skewed client clocks

With CreateTime, a wrong clock gives a wrong answer for “what happened at 2 pm”. This is a data quality problem, not an index problem.

!
Common Misconception

Time lookups are not “exact to the millisecond” queries. They return the first record at or after a time. If you need every record of a one minute window, look up the start offset and the end offset, and read the range between them.

8Segment Lifecycle: Roll, Retain, Delete

Index files are born, grow, are frozen, and finally are removed together with their segment.

flowchart LR
  A["Created and preallocated"] --> B["Active: entries appended"]
  B --> C["Roll trigger fires"]
  C --> D["Trim indexes and flush"]
  D --> E["Closed: read only"]
  E --> F["Retention says too old"]
  F --> G["Renamed with a deleted suffix"]
  G --> H["Files removed after a delay"]
        
FIG 5 — The life of one segment and its index files

Birth

When a new active segment starts, Kafka creates its log file and preallocates both index files at their maximum size. The name of the segment is the offset of the first record it will receive. From this moment the segment grows only by appending.

Roll triggers

TriggerControlled byTypical default
Log file would exceed the size limitSegment size setting1 GiB
Segment is older than the time limitSegment time setting, with optional random jitter7 days
Offset index or time index is fullIndex size setting10 MiB
Relative offset would overflow four bytesBuilt-in limitAbout 2 billion offsets from base

The time limit deserves a comment. On a topic with little traffic, a segment might never reach one gibibyte in a week, and without a time trigger the data in it could never be deleted, because only closed segments can be deleted. The optional jitter spreads rolls of many partitions over time so they do not all happen at the same second.

Closing: trim, flush, freeze

At a roll, Kafka trims the index files from their preallocated size down to the actual number of valid entries and flushes them to disk. It also writes one final time index entry that records the segment’s largest timestamp, so that the closed segment carries an accurate “newest data” label for retention. A new active segment then takes over.

Retention uses the time index

A background task on the broker regularly checks closed segments. With time-based retention, a segment is eligible for deletion when its largest timestamp is older than the retention period. It is not the file modified time, and it is not the smallest timestamp. This is a deliberately safe choice: a segment is removed only when even its newest record is old enough. With size-based retention, the oldest closed segments are removed until the partition fits under the limit. Deletion can also happen when the log start offset moves forward, for example after an administrator deletes records up to a point.

Simple Analogy

A supermarket throws away a whole crate of yoghurt only when the newest cup in the crate has expired. One old cup is not enough reason, because fresher cups are still sitting in the same crate.

Deletion is two-phase

Kafka first renames the segment’s files with a special suffix that marks them as deleted, and removes them from its in-memory segment map right away, so readers no longer see them. A scheduled task deletes the renamed files after a short delay, sixty seconds by default. The delay avoids blocking request threads on file system work and gives in-flight readers time to finish.

i
Good To Know

Compacted topics follow a different path. The log cleaner rewrites old segments into new ones, and new index files are built for those new segments. The offsets stay the same, but gaps appear where records were removed. The relative offsets in the index handle gaps without any trouble.

9Memory Mapping, the Page Cache and the Operating System

The speed of index files comes from the operating system, and so do their biggest operational limits.

What memory mapping means

Kafka opens each index file and asks the operating system to map it into the broker’s address space. After that, reading or writing the index looks like reading or writing an ordinary array in memory. The operating system quietly loads the needed pieces of the file into the page cache, and writes changed pieces back to disk later. Kafka does not need to copy data between kernel and program buffers, and the index behaves like fast memory that happens to be backed by a file.

Simple Analogy

Instead of borrowing a book each time you need one fact, you pin a photocopy of the contents page on your wall. Looking at the wall is instant, and the library still owns the original.

Warm and cold parts of an index

Most lookups ask for recent data, because most consumers are close to the end of the log. Kafka’s index search takes advantage of this. It runs the binary search in two zones: a small “warm” zone at the end of the index, which holds the newest entries and is almost always in memory, and the larger “cold” zone before it. Searching the warm zone first keeps the pages that recent readers need hot in the page cache, and stops occasional old reads from scattering random accesses across the whole file.

Memory is off the Java heap

Index pages live in the operating system’s page cache, not in the Java heap that the garbage collector manages. This is the reason Kafka administrators are told to give brokers a modest heap and leave the rest of the machine’s memory to the operating system. More free memory means more of the logs and indexes stay cached.

The hidden limit: memory map count

Every memory-mapped file uses one mapping area, and Linux limits how many mappings a process may hold. The setting is called the maximum map count, and a common default is 65,530. Every segment has two mapped index files, so a broker with many partitions and many segments can run out. When that happens, the broker fails with an out-of-memory error whose message says a map failed, even though plenty of memory is free. This confuses many teams the first time they see it.

2
memory mappings per segment (offset and time index)
65,530
common default map count limit per process
32,765
segments a broker can hold before hitting that limit

A quick calculation shows how easy it is. A broker hosting 4,000 partition replicas with 10 segments each has 40,000 segments and 80,000 mappings, which is already above the default limit. The remedy is twofold: raise the operating system limit to a comfortably higher number, and avoid creating needlessly tiny segments.

!
Production Warning

Raise the map count limit before you scale up partition counts, not after the first outage. Also remember that other things, including some libraries, use mappings too, so leave generous headroom above your calculated need.

File handles are a separate limit

Log files need open file handles, and brokers with many segments need a high limit on open files per process. Index files, once mapped, do not need their own long-lived handle, which is why the map count is the limit that usually bites for them, while the open file limit bites for the log files. Both should be raised on any serious broker.

Where you must not put these files

Log directories should be on local disks or on well-behaved block storage with a standard local file system. Network file systems can behave poorly with memory mapping and with the file rename and delete patterns that Kafka uses, and are a well-known source of strange failures.

10Crash Recovery and Index Rebuilding

Index files are a cache of facts that already exist in the log, so they can always be thrown away and rebuilt.

The key idea: the log is the truth

Everything in the index files can be recomputed by reading the log file. This single fact shapes all of Kafka’s recovery design. If an index is damaged, missing or suspicious, Kafka does not try to repair it byte by byte. It deletes it and builds a new one from the log.

Clean shutdown versus crash

When a broker stops cleanly, it flushes everything, trims indexes, writes a marker that says “clean shutdown”, and stores checkpoint files that remember up to which offset data is known to be safely on disk, called the recovery point. On the next start, the marker tells the broker that it can load segments quickly without deep checks.

After a crash or power loss, there is no marker. Kafka must assume that the newest segments may have half-written data. It takes each partition’s recovery point and examines the segments at and after it: it reads through the log, drops any incomplete or corrupted bytes at the end, and rebuilds the offset and time indexes for those segments from what remains.

flowchart TD
  A["Broker starts"] --> B{"Clean shutdown marker present?"}
  B -- "Yes" --> C["Load segments and run light index checks"]
  B -- "No" --> D["Read recovery point for each partition"]
  D --> E["Scan unflushed segments and cut off bad bytes"]
  E --> F["Rebuild offset index and time index from the log"]
  C --> G{"Index passes sanity check?"}
  G -- "Yes" --> H["Partition goes online"]
  G -- "No" --> F
  F --> H
        
FIG 6 — How a broker decides whether to trust or rebuild its index files

What the sanity checks look for

Check 1

File size shape

An index file must contain a whole number of entries. A size that is not a multiple of the entry size means corruption.

Check 2

Ordering

Entries must be strictly increasing. A new entry that is not larger than the last one is rejected as invalid.

Check 3

Range

The last offset in an index must not be smaller than the segment’s base offset.

Check 4

Consistency with the log

Positions must not point beyond the end of the log file.

Cost of rebuilding and how to control it

Rebuilding indexes means reading log data, so the time grows with the number and size of segments that need recovery. A broker that crashed with a large amount of unflushed data can take many minutes to come back. Kafka reduces this by recovering many partitions in parallel, using a configurable number of recovery threads per log directory. Setting this higher, up to the number of disks and cores that the machine can really use, is a common tuning step for brokers with many partitions.

i
Operational Trick

If you must fix one corrupt index by hand, stop the broker first, remove only the damaged index files of that one segment, and start again. Kafka will rebuild them. Never delete index files while the broker is running.

Why Kafka does not fsync every write

Kafka by default leaves flushing to the operating system and relies on replication for durability. Your data is safe because other brokers hold copies, not because one disk was told to flush after every message. The index files follow the same philosophy: cheap to write, safe to lose, and always rebuildable. This is a rare case where “throw it away and rebuild” is the correct and fast design.

11Replication, Truncation, Compaction and Sibling Indexes

The offset and time indexes have to stay correct while the log around them is copied, cut and rewritten.

Truncation after leader changes

When leadership moves, a new leader may have less data than an old leader had. A follower that had extra records must cut its log back to match the new leader. Kafka uses leader epochs, numbered eras of leadership, stored in the epoch checkpoint file, to find the exact offset where two replicas stop agreeing. The follower then truncates its log to that offset.

Truncation must cut the indexes as well. Kafka removes offset index entries at or beyond the truncation point, and removes time index entries that point to removed offsets. If the truncation point lies inside a segment, that segment’s indexes are shortened. If it lies before a whole segment, that segment is deleted entirely. After this, indexes and log agree again, and appending continues from the truncation point.

Simple Analogy

If two friends compare their diaries and find that one wrote three extra pages that never really happened, the friend tears out those pages and also crosses out the matching lines in the contents list at the front. Otherwise the contents page would point to pages that no longer exist.

Compaction and the offset map

For compacted topics, Kafka keeps the latest record per key. A background log cleaner reads old segments, builds an in-memory map of the newest offset per key, and writes new, smaller segments that contain only surviving records. This offset map is a different structure from the offset index. It is a temporary hash table used during cleaning, while the offset index is a permanent sorted file used for reading. Students often confuse the two, so keep them apart in your mind.

Each cleaned segment gets fresh index files built while it is written. Because the original offsets are preserved, the offsets in the cleaned segment have gaps. Since index entries are only signposts and lookups pick the nearest entry at or before the target, gaps do not break anything.

Records that are not data

Transactions add control records, which are markers that say a transaction committed or aborted. These consume offsets but are hidden from consumers, which is one more reason offsets in a partition are not always a smooth run of visible records. The lookup logic already handles this because it works with batch positions rather than counting visible records.

The other side files

Sibling 1

Transaction index

Lists aborted transactions with their producer identity and offset range, so a consumer that reads only committed data can skip aborted records.

Sibling 2

Producer snapshot

A saved picture of the latest sequence number seen from each producer, so idempotent producers can be recognised after a restart without rescanning the whole log.

Sibling 3

Leader epoch checkpoint

The map from leadership era to starting offset that makes safe truncation possible.

i
Pattern To Notice

Kafka repeatedly uses the same trick: keep the big log as the single source of truth, and keep small, rebuildable side files that make specific questions cheap. The offset index and time index are the two most important examples of that pattern.

12Index Files and Tiered Storage

When old segments move to cheap remote storage, their indexes have to travel with them.

The idea of tiered storage

Keeping months of data on expensive local disks is costly. Tiered storage, introduced through KIP-405, lets a broker keep only recent segments on local disks, the local tier, and copy closed segments to cheaper object storage, the remote tier. Consumers still read old data through the same Kafka interface, and the broker fetches what it needs from the remote tier when required.

Simple Analogy

A shop keeps this week’s goods on the front shelves and moves older stock to a big warehouse across town. Customers ask at the same counter. But the shopkeeper needs a catalogue of what is in the warehouse and where, or every old request would turn into a long search.

What travels with a segment

When a segment is copied to the remote tier, Kafka uploads more than the log file. The offset index, the time index, the transaction index, the producer snapshot and the leader epoch information are uploaded too. Without the offset index, the broker would have to download and scan an entire remote segment to find one offset. With it, the broker can fetch just the small index, compute the byte position, and request only the needed byte range from remote storage.

A cache for remote indexes

Downloading an index for every remote read would be slow, so brokers keep a configurable local cache of index files for remote segments. Frequent readers of old data hit this cache. Sizing it well is part of running tiered storage, because a cache that is too small forces repeated downloads and higher latency for historical reads.

Advantages

  • Local disks hold only hot data, so brokers can be smaller and cheaper
  • Retention of months or years becomes affordable
  • Adding capacity no longer means moving huge amounts of data between brokers
  • The same offset and time lookup logic works for old data

Disadvantages / Trade-offs

  • Reads of old data have higher latency than local reads
  • Index caches and remote fetch settings need tuning
  • Object storage costs and request limits become part of your design
  • More moving parts to monitor and to secure
i
Why This Matters For Learning

Tiered storage is a strong proof that indexes are the real reason Kafka’s log design scales. The data can live far away, but as long as the small signposts are close, lookups stay cheap.

13Configuration Reference and Trade-offs

Only a few settings influence index files. Knowing what each one trades away is the real skill.

The settings that matter

SettingScopeDefaultWhat it controls
segment.bytesTopic, with a broker-wide default1 GiBMaximum size of a log segment before rolling
segment.msTopic, with a broker-wide default7 daysMaximum age of a segment before rolling
segment.jitter.msTopic, with a broker-wide default0Random delay added to the time-based roll
segment.index.bytesTopic, with a broker-wide default10 MiBMaximum size of each index file of a segment
index.interval.bytesTopic, with a broker-wide default4096How many bytes of log pass between index entries
message.timestamp.typeTopic, with a broker-wide defaultCreateTimeWho assigns record timestamps
retention.ms and retention.bytesTopic, with a broker-wide default7 days and unlimitedWhen closed segments become eligible for deletion
file.delete.delay.msBroker and topic60 secondsDelay between marking and removing files
num.recovery.threads.per.data.dirBroker1 (raised in recent versions)Parallelism of startup recovery and index rebuilds

Reading the trade-offs

Knob 1

Smaller index interval

More entries, larger index, shorter scans after each lookup. Usually not worth it, because the scan is already tiny.

Knob 2

Larger index interval

Smaller index but longer scans, and more cache pressure during reads. Also rarely worth changing.

Knob 3

Smaller segments

Faster and finer-grained deletion, but many more files, more mappings, more handles and slower recovery.

Knob 4

Larger segments

Fewer files and less overhead, but coarser retention, since a huge segment is deleted only when its newest record is old.

How to choose segment size

A useful rule of thumb is to size segments so that each partition holds a healthy number of them, not just one or two, and not thousands. For a busy topic with a retention of a few days, the default one gibibyte is usually fine. For a low-volume topic, the time limit matters more than the size limit, because the size limit may never be reached. A topic that receives a few megabytes per day and keeps data for thirty days will hold roughly a week of data in each segment, and retention will lag behind by up to a week, since the newest record in a segment decides when it can go.

!
Common Mistake

Setting the segment size very small to make retention “more precise” often backfires. Precision improves a little, while the number of files, mappings and recovery work multiplies. If you need finer retention, prefer a moderate time-based roll instead.

Choosing the timestamp type

Choose CreateTime when event time matters and you trust your producers’ clocks. Choose LogAppendTime when you want a clean, always-increasing arrival clock, for example on topics fed by many unreliable devices. Also consider limiting how far a record’s timestamp may differ from the broker’s clock. Newer Kafka versions offer separate limits for timestamps in the past and in the future, and rejecting extreme values protects retention and time lookups from bad data.

14Security and Data Governance

Index files hold no message payloads, but they still live next to data that must be protected.

What an index file can and cannot reveal

The offset index holds offsets and byte positions. The time index holds timestamps and offsets. Neither holds keys, values or headers. Even so, they reveal patterns: how fast data arrives, at what hours, and how large batches are. Treat them as sensitive operational metadata, especially in regulated industries, where activity patterns can be confidential.

Protect the directory, not just the topic

Kafka access control lists protect topics at the network and API level. They do nothing for someone who can read the broker’s disk. Anyone who can open the log directory can read the actual records from the log files, so the whole log directory needs tight operating system permissions, and the broker should run as a dedicated user with no unnecessary access.

Control 1

Disk encryption

Use volume or file system encryption so that lost or recycled disks do not expose data.

Control 2

File permissions

Restrict log directories to the broker service account and to a small set of administrators.

Control 3

Restricted tooling

Log inspection tools can print record contents. Limit who may run them on production machines.

Control 4

Backup discipline

Copies of log directories are copies of your data. Apply the same retention and access rules to backups.

Deletion and the right to be forgotten

Index files do not change what happens when data must be removed. Data disappears when its segment is deleted by retention, or when compaction removes older values for a key after a tombstone. Because deletion happens per segment, the newest record in a segment controls when the whole segment goes. Teams with legal deletion deadlines should set retention and segment roll times with that delay in mind, and should test that data is really gone from disk, not only from consumers’ view.

i
Governance Tip

Write down the longest time a record can survive on disk, which is the retention period plus the segment roll time plus the delete delay, and compare it to your compliance promise.

15Monitoring, Logging and Troubleshooting

Index problems are rare, but when they appear they are confusing. Knowing the symptoms saves hours.

What to watch on a healthy broker

Signal 1

Segment count per partition

A sudden rise means segments are rolling too often. It predicts memory mapping and file handle pressure.

Signal 2

Memory mappings of the process

Compare the real number of mappings with the system limit. Alert when usage passes about seventy percent.

Signal 3

Open file handles

Log files need handles. A steady climb without a matching increase in partitions is a warning.

Signal 4

Log loading and recovery progress

During startup, brokers report how many logs and segments remain to be recovered. Watch this to judge restart time.

Signal 5

Log start and end offsets

Unexpected jumps in the start offset show retention or deletion activity. Stuck end offsets show a stalled writer.

Signal 6

Disk and page cache health

Slow historical reads with a cold page cache often point to too little free memory for indexes and logs.

Messages you may see in the broker logs

Message meaningLikely causeWhat to do
Found a corrupted index file, rebuildingCrash during writes, or disk troubleUsually harmless. Let recovery finish and check the disk if it repeats
Index size is not a multiple of the entry sizeTruncated or damaged index fileStop the broker, remove that segment’s index files, restart
Attempt to append an offset that is not larger than the last oneOut-of-order write or corrupted stateInvestigate the partition. Consider replacing the replica from the leader
Map failed, out of memoryMemory map count limit reachedRaise the system limit and reduce the number of segments
Too many open filesFile handle limit too lowRaise the limit and review segment counts
Recovering unflushed segmentBroker restarted after an unclean stopWait. Consider more recovery threads for faster restarts

Inspecting index files safely

Kafka ships a log dump tool that can open segment files and print their contents, including the offset index and the time index, and it has options to verify that an index is consistent with its log. Use it on a copy of the files, or on a stopped broker’s data, and remember that it can print record contents unless you ask it not to.

1

Identify the partition and segment

From the error message or the consumer symptom, find the topic, partition and the segment’s base offset.

2

Check the box first

Look at disk errors, free memory, memory map usage and open files. Most index trouble is really machine trouble.

3

Verify the index against the log

Run the dump tool in its index verification mode on a copy of the segment to see if entries match the log.

4

Rebuild or replace

If the index is bad, stop the broker, remove only that segment’s index files, and restart. If the log itself is bad, rebuild the replica from a healthy leader.

i
Troubleshooting Habit

Slow “seek to time” calls are not always an index issue. Check first whether the requested time falls in an old segment that is no longer in the page cache, since the first touch must load the index pages from disk.

16Design Patterns and Anti-Patterns

Good habits keep index files invisible. Bad habits make them the cause of a midnight incident.

Patterns that work

Replay from a timestamp after an incident

When a bug corrupts downstream data, look up the offsets for the time just before the bug in every partition, reset the consumer group to those offsets, and reprocess. This turns a scary recovery into a routine operation, and it depends entirely on the time index.

Start new consumers from a chosen time

New services often need “the last 24 hours” instead of the whole history. Seeking by timestamp gives them a clean starting point without guessing offsets.

Set retention with the segment roll time in mind

Because deletion is per segment and driven by the newest record, plan both values together. This keeps disk use and compliance promises predictable.

Use broker-assigned timestamps for untrusted producers

For topics fed by phones, devices or partner systems, LogAppendTime protects retention and time lookups from wrong clocks, while the event time can travel inside the payload for applications that need it.

Plan the operating system limits before growth

Compute the expected number of segments per broker, multiply by two for the memory mappings, and configure limits with a generous margin before the partition count grows.

Anti-patterns to avoid

ANTI-PATTERN-01 Avoid
Problem

An operator deletes .index or .timeindex files on a running broker to “clean up” or to “fix” a slow topic.

Why It’s Harmful

The broker has these files mapped in memory and is using them. Removing them under its feet can cause errors, and forcing a rebuild during peak time consumes disk bandwidth and can delay partitions.

Correct Approach

Leave index files alone. If one is truly damaged, stop the broker cleanly, remove only that segment’s index files, and let the restart rebuild them.

ANTI-PATTERN-02 Avoid
Problem

Tiny segments are configured for many topics, for example a new segment every few minutes, to force quick deletion.

Why It’s Harmful

Every segment costs two memory mappings and a file handle. Thousands of partitions with hundreds of segments each can exhaust system limits, slow down restarts, and add pressure to the file system.

Correct Approach

Keep segments at a sensible size, use a moderate time-based roll for slow topics, and accept that retention works at segment granularity.

ANTI-PATTERN-03 Avoid
Problem

Trusting client-supplied timestamps blindly, including values far in the future or the distant past.

Why It’s Harmful

A future timestamp raises a segment’s largest timestamp, so the segment can survive long past the retention period. Very old timestamps can make new data look expired or roll segments at odd moments. Time lookups return misleading answers.

Correct Approach

Validate producer clocks, use limits on timestamp difference, or use broker-assigned time for untrusted sources.

ANTI-PATTERN-04 Avoid
Problem

Tuning the index interval without measurement, because “a smaller number must be faster”.

Why It’s Harmful

The scan after each lookup is already only a few kibibytes. Halving it barely changes latency but doubles index size and memory use.

Correct Approach

Keep the default unless a measured, repeatable problem proves that a change helps.

ANTI-PATTERN-05 Avoid
Problem

Placing log directories on a network file system to “share” storage between brokers.

Why It’s Harmful

Memory mapping, renames and deletions can behave unpredictably over network file systems, and the latency spoils Kafka’s design. Failures are hard to reproduce and diagnose.

Correct Approach

Use local disks or block storage attached to each broker, and let replication provide redundancy.

17Real-World and Industry Examples

Offset and time lookups quietly power some of the most common operations in Kafka-based companies.

Consumer group reset by date

Operations teams use the consumer group tooling that ships with Kafka to move a group back to a chosen date and time. Behind the scenes, the tool asks each partition leader for the offset that matches the time, which is the time index at work. Banks, retailers and streaming platforms use this to reprocess after a faulty deployment.

Stream processing engines starting from a time

Popular engines such as Apache Flink and Apache Spark Structured Streaming can start a Kafka source from a timestamp instead of an offset. They rely on the same timestamp-to-offset lookup, so that a new job can begin exactly where a business day began.

Cross-cluster replication and disaster recovery

Tools like MirrorMaker 2 copy data between clusters, and the offsets in the two clusters are usually different. Timestamps are a natural common language between them, so failover procedures often translate a consumer position by time and then use the time index on the target cluster.

Change data capture pipelines

Systems that stream database changes, for example with Debezium, need fast recovery when a downstream sink fails. Engineers seek back a known number of minutes and reapply changes, trusting the index to place them accurately.

Managed Kafka services at scale

Cloud providers that run Kafka for thousands of customers, such as Confluent Cloud and Amazon MSK, tune segment sizes, operating system limits and recovery threads across huge fleets. The most common reason for surprise restarts in such fleets is the same one you learned in chapter 9: too many segments for the memory map limit.

Audit and compliance investigations

When investigators need “all events between two moments”, teams look up the start and end offsets by time and read only that range, instead of scanning days of data.

Microseconds
typical index search cost when pages are cached
18 steps
binary search over a quarter million entries
Constant
lookup cost, however large the partition grows

18A Worked Example from Write to Read

Let us follow one small, imaginary partition from its first record to a consumer’s fetch, using round numbers so every step is easy to check.

The setting

Imagine a topic called orders with a partition that has just started a new segment. The segment’s base offset is 1,000,000, so its files begin with that number padded to twenty digits. Producers send batches of ten records, and each record is about five hundred bytes, so a batch is roughly five thousand bytes. The index interval is the default, 4,096 bytes. All numbers in this example are illustrative, chosen to show the mechanism and not to match a real system exactly.

Simple Analogy

Picture a delivery van filling up with parcels. Every time about a crate’s worth of new parcels is loaded, the driver puts a sticky note on the wall of the van that says “parcel number so-and-so is roughly this far from the back door.” After a few hours the wall has a neat row of notes, and finding any parcel is quick.

Writing the first few batches

1

First batch arrives

Ten records get offsets 1,000,000 to 1,000,009. About five thousand bytes are appended to the log. That is more than the 4,096 byte gap, so the broker writes the first index entries. The offset index stores a relative offset near the start of the segment together with position zero, and the time index stores the biggest timestamp seen so far.

2

Second batch arrives

Offsets 1,000,010 to 1,000,019 are appended, and the log is now around ten thousand bytes long. Again more than four kibibytes have passed since the last entry, so a second offset index entry is written, this time pointing to a position near five thousand.

3

A batch with an older timestamp arrives

A producer with a slow clock sends a batch whose timestamps are a little older than those already seen. The offset index still gets its normal entry, but the time index does not grow a new entry, because the largest timestamp so far did not increase. The time index stays sorted.

4

Many more batches later

After twenty thousand batches, the segment holds two hundred thousand records and about one gibibyte of data. The segment is full and gets rolled. Its offset index holds roughly one entry per batch, about twenty thousand entries or one hundred sixty thousand bytes, which is tiny compared with the data.

Reading it back

Now a consumer asks for offset 1,150,004. The broker looks at the sorted map of segments and picks the segment with base offset 1,000,000, since that is the largest base offset not above the request. It subtracts to get relative offset 150,004 and binary searches the offset index. The search lands on the entry for the batch that starts at relative offset 150,000, which points to some byte position, say around seventy-five million. The broker reads the header of that batch, sees that it covers offsets 1,150,000 to 1,150,009, and knows that offset 1,150,004 is inside it. It sends that batch, and the consumer library drops the four records it already has.

flowchart TD
  R["Consumer asks for offset 1150004"] --> A["Pick segment with base offset 1000000"]
  A --> B["Relative offset is 150004"]
  B --> C["Binary search the offset index"]
  C --> D["Entry for relative offset 150000 gives a byte position"]
  D --> E["Read batch header at that position"]
  E --> F["Batch covers 1150000 to 1150009"]
  F --> G["Send the batch and the consumer skips the first four records"]
        
FIG 7 — The worked example, from request to bytes on the wire

Now a time question

An engineer asks, “What is the first offset at or after 10:30 in the morning?” The broker skips older segments by checking their largest timestamps, and settles on our segment. The binary search in the time index finds the last entry that is not later than 10:30 and reports an offset, perhaps 1,073,000. The offset index turns that into a position. The broker reads forward until it sees the first record whose timestamp is at least 10:30, and returns its offset. The engineer resets a consumer group to exactly that offset and the replay begins.

i
What To Notice

At no point did Kafka read the whole segment. It touched a sorted map, two small sorted files, and one short stretch of the log. That is the entire trick, repeated for every fetch on every partition of every broker.

19Capacity Planning and Performance Tuning

Index files are small, but small things multiplied by thousands of segments deserve a plan.

How big are all the indexes together?

For every four kibibytes of log data, the broker can write at most one offset index entry of eight bytes and one time index entry of twelve bytes. That is a maximum of twenty bytes of index for every 4,096 bytes of log, a bit under half of one percent. If a broker holds twelve tebibytes of log data, its indexes will take up to roughly sixty gibibytes in the worst case. In practice it is usually less, because large batches produce fewer entries than the maximum, and the time index only gets an entry when the largest timestamp grows.

Under 0.5%
maximum index size compared with log size at default settings
About 60 GiB
worst-case indexes for 12 TiB of log data
2 per segment
memory mappings that each closed segment needs

A capacity worksheet in plain words

1

Estimate the data kept per broker

Multiply the incoming bytes per day by the retention days and by the replication factor, then divide by the number of brokers. This gives the local log size each broker will carry.

2

Estimate the number of segments

Divide the local log size by the segment size, then add one active segment for every partition replica on the broker. For low-volume topics, remember that the time limit can create many small segments too.

3

Convert segments to mappings

Multiply the segment count by two. Compare that number with the memory map limit of the operating system and keep the result well below it, with a comfortable margin for growth and for other users of memory maps.

4

Check file handles

Each segment’s log file needs a handle, plus handles for sockets and other files. Confirm that the open file limit has plenty of room above the segment count.

5

Plan the memory for caching

Leave enough free memory for the page cache so that the indexes and recent log data of active partitions stay in memory. A broker with a huge heap and little free memory serves reads slowly, even though nothing looks broken.

A short example with numbers

Suppose a broker carries twelve tebibytes of local data on 3,000 partition replicas. With one gibibyte segments, that is about twelve thousand closed segments plus three thousand active ones, so about fifteen thousand segments and thirty thousand memory mappings. That is under half of a limit of 65,530, which is comfortable. Now imagine someone lowers the segment size to 128 mebibytes to make deletion “more precise”. The count becomes roughly a hundred thousand segments and two hundred thousand mappings, far above the limit, and the broker fails. One innocent-looking setting decided the outcome.

SituationSegment sizeApproximate segmentsApproximate mappingsVerdict with a 65,530 limit
Default segments1 GiB15,00030,000Safe, with headroom
Medium segments512 MiB27,00054,000Risky, little margin
Small segments128 MiB99,000198,000Fails without raising the limit

Tuning tips that actually help

Tip 1

Raise limits first

Increase the memory map count and open file limits as part of the standard broker image, not as an emergency fix.

Tip 2

Use a fast local file system

Kafka documentation favors well-tested Linux file systems such as XFS and ext4 on local disks. Avoid exotic setups.

Tip 3

Add recovery threads

Raise the recovery thread count for brokers with many partitions so restarts after crashes finish sooner.

Tip 4

Prefer clean shutdowns

Use controlled shutdown for maintenance. A clean stop avoids index rebuilding and shortens the next start.

Tip 5

Do not over-partition

Each partition adds at least one active segment and its indexes. Choose partition counts from real throughput needs.

Tip 6

Measure before you tune

Compare latency percentiles, page cache hit behavior and disk activity before and after any change.

!
Do Not Skip

Capacity plans age quickly. Every new topic, every longer retention and every extra partition adds segments. Recalculate the mapping and handle numbers whenever any of these change.

20Misconceptions, Comparisons and How the Design Evolved

Clearing away wrong pictures is as valuable as learning right ones.

Six wrong ideas about Kafka index files

Myth 1

The index stores messages

It stores only numbers: offsets, positions and timestamps. Messages live in the log file.

Myth 2

The index lets me look up by key

Kafka has no lookup by key. The indexes work on offsets and timestamps only.

Myth 3

Every record has an index entry

Only about one entry appears per few kibibytes of log. The index is sparse on purpose.

Myth 4

The time index is exact

It gives a starting hint and a final scan finds the answer. It also inherits any error in producer clocks.

Myth 5

Index files are backups of the log

They are a helper and contain less information than the log. They cannot rebuild the data.

Myth 6

Data disappears at the exact retention time

Data disappears when its whole segment is deleted, which can be later than the retention time.

Kafka’s sparse index compared with a database index

People who know databases often ask how this compares with a database index. The ideas are related, but the goals are different. A database index must support arbitrary lookups and constant updates in the middle of the data. Kafka’s log is written only at the end, so its indexes can be much simpler.

QuestionTypical database indexKafka offset and time index
What does it search by?Any chosen column or keyOnly offset or timestamp
How dense is it?Usually one entry per rowOne entry per few kibibytes of log
How is it updated?Insertions, deletions and reshaping in the middleAppend only at the end, never edited
What structure is it?Trees or hash tables with many levelsA flat, sorted array searched by binary search
If it is lost?Rebuilt from the table, sometimes slowly and with locksRebuilt from the log at broker startup

How the design grew over time

The offset index has been part of Kafka from its early years, because reading from an arbitrary offset was always a core promise. The other pieces arrived as needs appeared. The approximate order is shown below, and exact version numbers should be checked against the official release notes for your installation.

1

Early releases: offset index only

Kafka could find a record by offset. Retention decisions used the last modified time of files.

2

Release 0.10.0: timestamps in records

Records gained a timestamp field, opening the door to time-based features.

3

Release 0.10.1: the time index

The time index arrived, enabling seek by timestamp and retention based on record times instead of file times.

4

Release 0.11: transactions and idempotence

The transaction index and producer snapshots joined the family of side files, along with leader epochs that made truncation safer.

5

Recent releases: tiered storage

Remote storage support moved from early access to production readiness over the 3.x series, with indexes uploaded and cached alongside remote segments.

Three habits of engineers who never fear index files

The first habit is to treat the log as the single source of truth and the indexes as disposable helpers. When an index looks wrong, they do not panic or hand-edit bytes. They stop the broker cleanly, remove the damaged index files of one segment, and let Kafka rebuild them from the log during startup. This habit turns a scary situation into a routine, well-understood maintenance step that takes a few minutes and risks no data.

The second habit is to watch the operating system, not just Kafka. Most production surprises connected to indexes come from limits outside Kafka itself: the memory map count, the open file limit, the free memory left for the page cache, and the speed of the disks. Engineers who add these numbers to their dashboards and alerts find problems days before their users do, because segment counts grow slowly and predictably as topics, partitions and retention settings change.

The third habit is to keep timestamps honest. Because retention, time-based rolling and seek by time all depend on record timestamps, one careless producer can quietly distort all three. Teams that validate client clocks, limit how far a timestamp may differ from the broker’s clock, or choose broker-assigned time for untrusted sources enjoy predictable disk usage and trustworthy replays. This is a small amount of discipline that pays back every time an incident needs a clean replay from a known moment.

“A good index is small enough to forget about and reliable enough to trust.”

What to remember for real-world work

When something is slow or strange, follow the questions in order. Is the request asking for an offset or for a time? Is the target segment on a local disk or in the remote tier? Are the index pages in memory? Is the machine near its limits for mappings, handles or disk? Most puzzles about “index problems” end up being answered by one of these four questions, and none of them requires touching the index files themselves.

i
Study Tip

To make this knowledge stick, draw one partition on paper: three segments, each with a log, an offset index and a time index. Then trace by hand one offset lookup and one timestamp lookup. If you can do that without looking, you understand Kafka’s storage read path.

21Frequently Asked Questions

Short, direct answers to the questions that come up most often.

Q1Why is the offset index sparse instead of having one entry per record?

A dense index would be many times larger and would not fit comfortably in memory for big partitions. A sparse index is tiny, and the short forward scan after each lookup costs almost nothing, so the trade is very favorable.

Q2Are index files replicated between brokers?

No. Followers replicate record batches and build their own index files while appending. Since the log is the source of truth, each replica can always produce a correct index by itself.

Q3What happens if I delete an index file while the broker is stopped?

The broker rebuilds it from the log at startup. This can take time on large segments, but no data is lost, because the index holds no information that the log does not already have.

Q4Why does the active segment’s index file look so big?

Kafka preallocates index files to their maximum size when a segment is created and trims them when the segment is closed. The size you see is reserved space, not stored entries.

Q5Does the time index store the timestamp of every record?

No. It stores the largest timestamp seen so far at the moment an entry is written, and only when that value grows. This keeps it sorted, small and searchable even when producer clocks disagree.

Q6Why did my old data not get deleted after the retention period?

Common reasons are that the segment is still the active one, that a record in the segment has a far-future timestamp, or that the segment’s newest record is still younger than the retention period. Deletion works on whole closed segments, judged by their largest timestamp.

Q7What is the difference between the offset map used in compaction and the offset index?

The offset index is a permanent sorted file that helps readers find a position in a segment. The offset map is a temporary in-memory table the log cleaner builds to know the latest offset for each key while compacting. They serve different jobs.

Q8Can a segment be larger than two gibibytes?

No. Positions in the offset index are stored in four bytes, which limits a segment file to about two gibibytes. This is why the segment size setting has an upper bound.

Q9Why can too many segments crash a broker even when memory is free?

Each segment’s two index files use memory mappings, and the operating system limits how many mappings a process can hold. When the limit is reached, mapping fails and the broker reports an out-of-memory error unrelated to free memory.

Q10How does Kafka find the record for a given timestamp?

It picks the first segment whose largest timestamp reaches the requested time, searches that segment’s time index for the last entry not later than the time, converts that offset to a byte position with the offset index, and scans forward to the first record that matches.

Q11Should I change the index interval to speed up consumers?

Almost never. The default already keeps the post-lookup scan very short. Look first at page cache size, disk speed, fetch sizes and network settings.

Q12Do consumers ever read index files directly?

No. Consumers only talk to brokers through the Kafka protocol. The broker uses its index files internally to answer fetch requests and offset-by-time requests.

22Summary and Key Takeaways

Every Kafka partition is a long, ever-growing log cut into segments, and every segment carries a small family of files that make the log searchable. The offset index turns an offset into a byte position. The time index turns a timestamp into an offset. Both are sparse, sorted, memory mapped and always rebuildable from the log, which makes them cheap to write, quick to read and safe to lose. Once you see the log as the single source of truth and the indexes as rebuildable signposts, most of Kafka’s storage behavior, from retention to recovery to tiered storage, becomes easy to reason about.

Key Takeaways

  • Two indexes, two questions — the offset index finds a byte position for an offset, and the time index finds an offset for a timestamp.
  • Sparse by design — one entry per roughly four kibibytes of log keeps indexes tiny, and a short forward scan finishes the job.
  • Small, fixed entries — eight bytes for the offset index and twelve for the time index, both using offsets relative to the segment’s base offset.
  • Time index stores the largest timestamp so far — this keeps it sorted and makes retention and time lookups safe even with messy clocks.
  • Everything is rebuildable — the log is the truth, so damaged indexes are deleted and rebuilt at startup, and followers build their own indexes.
  • The operating system is your partner and your limit — memory mapping gives speed, while the memory map count and open file limits decide how many segments a broker can safely hold.
  • Retention works per closed segment — a segment is deleted only when its newest record is old enough, so plan segment size, roll time and timestamp trust together.