Changelog Topics in Kafka
How Kafka keeps a durable, replayable backup of an application's local state as a compacted log of updates, so that a crashed or moved stream processing task can rebuild exactly what it knew, and what happens at every step of that rebuild.
Imagine a shopkeeper who keeps a small notebook on the counter with today’s stock: twelve apples, five loaves, three jars of jam. The notebook is fast to check, so customers are served quickly. But the notebook lives on the counter, and if a fire destroys the shop, the stock levels vanish with it. A wise shopkeeper therefore also phones every change to a head office, which writes each one in a permanent ledger: “apples down by one”, “loaves up by ten”. If the shop burns down, a new shopkeeper can read the ledger from the top, replay each line, and end up with the same notebook, page for page.
A stream processing application has exactly this need. It keeps counts, totals, and lookup tables in fast local storage so it can answer in microseconds, yet it must survive crashes and machine moves without forgetting anything. Kafka’s answer is the changelog topic: a special topic that records every change to the local state, in order, forever compacted down to the latest facts. This tutorial explains what changelog topics are, how they are written and read, how they stay small, how they rebuild state after failure, and how to operate them safely.
1The Problem: State That Lives on One Machine
Before meeting the solution, feel the problem that makes it necessary.
Stateless versus stateful processing
Some stream processing is stateless: each record is handled entirely on its own. Converting a temperature from Celsius to Fahrenheit, or dropping records that fail a filter, needs no memory of past records. If the application crashes and restarts, it simply carries on with the next record, and nothing is lost.
Much more useful processing is stateful. Counting page views per user, keeping the latest balance per account, joining an order with the customer’s profile, or computing an average over the last five minutes all require the application to remember something about the past. That memory is called state, and it has to be stored somewhere.
A cashier who only scans items and reads out prices needs no memory. A cashier who keeps a running total for each customer in the queue needs a notepad. The notepad is the state, and losing it mid-shift means every customer’s total is gone.
Why local storage
Reaching across the network to a remote database for every single record would be slow, and it would make the processing speed depend on that database. So stream processing libraries keep state next to the code, in an embedded store on the local disk, or in memory. A lookup then takes microseconds. This design is fast and scales well, because each application instance owns the state for only its share of the data.
The danger of local storage
Local state has a fatal weakness: it lives and dies with the machine. If the machine fails, the disk may be lost. If the cluster manager moves the work to another machine, the state stays behind. If the application is scaled out, some state must move to the new instance. Without a plan, any of these events means the counts restart at zero or the lookup table is empty, and the results downstream are silently wrong.
Machine failure
A dead disk takes the state with it, and nothing on another machine knows what the totals were.
Rescheduling
Container platforms move workloads between hosts all the time, and a fresh container starts with an empty disk.
Scaling out or in
Adding an instance means some partitions, and their state, must migrate. Removing one means its state must be picked up by others.
Rolling upgrades
Restarting instances one by one repeatedly interrupts state ownership, and each restart must not lose anything.
Some people think that because the input data is safe in Kafka, the results can always be recomputed. That is true in principle, but replaying weeks of input to rebuild a counter can take hours or days. A changelog makes recovery a matter of minutes, and often seconds.
2What Is a Changelog?
A changelog is not a copy of the state. It is the story of how the state came to be.
State as the sum of its history
Consider a table that holds the number of visits per user. At any moment, the table shows only the current numbers: Asha has 7, Bilal has 3. But there is another way to describe the same table: as a list of events that produced it. “Asha goes to 1, Bilal goes to 1, Asha goes to 2, Asha goes to 3, Bilal goes to 2”, and so on. If you replay that list from the start, you arrive at the current table. The list is the changelog.
flowchart LR
A["Event: Asha visits"] --> T1["Table: Asha 1"]
T1 --> C1["Changelog record: Asha = 1"]
B["Event: Bilal visits"] --> T2["Table: Asha 1, Bilal 1"]
T2 --> C2["Changelog record: Bilal = 1"]
D["Event: Asha visits"] --> T3["Table: Asha 2, Bilal 1"]
T3 --> C3["Changelog record: Asha = 2"]
C1 --> LOG["Changelog topic: Asha=1, Bilal=1, Asha=2"]
C2 --> LOG
C3 --> LOG
The stream-table duality
This idea is often called the duality of streams and tables. A stream of changes can be turned into a table by keeping the latest value for each key. A table can be turned back into a stream of changes by emitting a record every time a row changes. They are two views of the same information, and each can be rebuilt from the other. A changelog topic is simply the stream view of a state store, stored durably in Kafka.
A bank statement lists every transaction, and the balance at the bottom is the sum of them all. Given the statement you can compute the balance, and given a series of balances you can work out the transactions. The statement is the changelog, and the balance is the table.
What each record contains
A changelog record carries the key of the row that changed and the new value for that key. It does not say “add one”. It says “the value for Asha is now 2”. This choice is important, because it makes each record self-contained. To find the current value of a key, you only need the latest record for it, and older records for the same key are redundant. That property is what makes compaction, explained later, possible.
Changelog records store the resulting value, not the operation that produced it. Replaying the same records twice, or replaying only the latest record per key, leads to the same final table. That is what makes the changelog safe to replay after any failure.
3Core Vocabulary You Need
Each term below is defined once here and then used consistently for the rest of the tutorial.
State store
A local key-value store that a stream processing task uses to remember information between records.
Changelog topic
An internal Kafka topic that records every update to a state store, one record per update.
Log compaction
A background clean-up that keeps only the latest record for each key, so the log stays as small as the live data.
Tombstone
A record with a key and an empty value, meaning “this key is deleted”. Compaction eventually removes the key entirely.
Task
The unit of work in a stream application, responsible for one partition of the input and its own state stores.
Restoration
Rebuilding a state store by reading its changelog topic from the start, or from a saved position, up to the end.
Checkpoint file
A small file on the local disk recording how far the local store has caught up with its changelog, to shorten restoration.
Standby replica
A passive copy of a task’s state kept warm on another instance by continuously reading the changelog.
Warm-up replica
A temporary copy created while a task is being moved, so the destination catches up before taking over.
Application ID
The unique name of a stream application. It prefixes the names of all its internal topics and identifies its consumer group.
Windowed store
A state store whose keys also include a time window, used for counts or aggregates over periods such as five minutes.
Internal topic
A topic created and managed by the stream library itself, such as changelog and repartition topics, and not meant for other applications.
The word changelog is also used in other places. Data capture tools describe the stream of changes from a database as a changelog, and a compacted topic that holds the latest value per key is often described the same way. This tutorial focuses on the state store changelog in stream processing, and later chapters show how the same idea appears elsewhere in Kafka.
4The Big Picture: A Fast Store Backed by a Durable Log
The design combines the speed of local storage with the safety of a replicated log.
Two copies, two jobs
Every stateful task keeps its state in two places. The local state store is the working copy: it answers reads and writes in microseconds and lives on the instance’s disk or in memory. The changelog topic is the durable backup: it lives in the Kafka cluster, replicated across brokers, and survives the loss of any instance. Each time the task updates the local store, it also appends the update to the changelog.
A chef keeps a whiteboard with today’s ingredient counts, and updates it constantly. Every time she changes the board, an assistant writes the change in a bound book locked in the office. The board is fast to read, and the book is safe from spills and thieves. If the board is wiped, the book restores it.
flowchart TD
subgraph Instance["Application instance"]
IN["Input record"] --> PR["Processing logic"]
PR --> ST["Local state store: fast reads and writes"]
ST --> CL["Changelog writer"]
end
CL --> TOP["Changelog topic in Kafka: replicated, compacted"]
TOP --> RES["After failure: another instance restores the store from the topic"]
RES --> ST2["Rebuilt local store on new instance"]
Why this arrangement is smart
Reads never touch the network
The common operation, looking up the current value for a key, is served from local storage. There is no remote call in the hot path.
Writes are cheap appends
Writing to the changelog is a sequential append to a Kafka log, the operation Kafka is best at. It adds little cost per update.
Durability comes from replication
The changelog is an ordinary replicated topic, so the backup enjoys the same protection as any other Kafka data.
Recovery is just reading a topic
No special backup system, snapshot service, or external database is needed. Any instance can rebuild the state by consuming the changelog.
State moves with the work
When a task is reassigned to another instance, that instance rebuilds the state from the topic. Ownership can move freely between machines.
5Architecture and Components
Several parts of the stream library and of Kafka cooperate to keep the local store and the changelog in step.
flowchart LR
subgraph Inst["Stream application instance"]
TASK["Stream task for partition 2"]
STORE["State store: embedded database on disk or in memory"]
WRAP["Logging layer around the store"]
COL["Record collector: producer"]
RC["Restore consumer"]
CHK["Checkpoint file"]
end
subgraph Cluster["Kafka cluster"]
CLT["Changelog topic, partition 2"]
OUT["Output topics"]
OFF["Offsets topic"]
end
TASK --> WRAP
WRAP --> STORE
WRAP --> COL
COL --> CLT
COL --> OUT
COL --> OFF
CLT --> RC
RC --> STORE
RC --> CHK
The roles, one by one
Stream task
Processes one partition of the input and owns the state for the keys in that partition, giving it one changelog partition per store.
State store
The embedded key-value database. By default it uses a disk-based engine, and an in-memory version is also available.
Logging layer
A wrapper around the store. Whenever the task puts or deletes a key, the wrapper also forwards the change to the changelog.
Record collector
The producer of the task. It sends changelog records alongside the task’s output records, using the same producer.
Restore consumer
A dedicated consumer that reads changelog partitions during restoration and feeds the records into the local store.
Checkpoint file
Records the last changelog offset that is known to be reflected in the local store, letting a restart skip most of the replay.
Treating changelog topics as ordinary topics that other applications may read or write. They are internal, the format may include library-specific details, and writing to them from outside can corrupt the state that the application rebuilds from them.
6Anatomy of a Changelog Topic
Every changelog topic follows the same conventions for its name, partitions, and settings.
The name
A changelog topic is created automatically by the stream library, and its name follows a fixed pattern: the application ID, then the name of the state store, then the word changelog, all joined by hyphens. For an application called page-views-app with a store named views-per-user, the topic becomes page-views-app-views-per-user-changelog. Because the application ID is part of the name, two applications never share changelog topics by accident, unless someone gives them the same ID.
The partitions
The changelog has exactly as many partitions as the input the store is built from. If a task processes partition 2 of the input, its store’s changelog records go to partition 2 of the changelog. This alignment is deliberate. It means one task owns one input partition and one changelog partition per store, and moving a task means moving a matched set of partitions. The stream library also checks these counts at startup, and refuses to run if the changelog has a different number of partitions than expected.
flowchart LR
subgraph In["Input topic: 3 partitions"]
I0["Partition 0"]
I1["Partition 1"]
I2["Partition 2"]
end
subgraph Tasks["Three tasks"]
T0["Task 0"]
T1["Task 1"]
T2["Task 2"]
end
subgraph CL["Changelog topic: 3 partitions"]
C0["Partition 0"]
C1["Partition 1"]
C2["Partition 2"]
end
I0 --> T0
I1 --> T1
I2 --> T2
T0 --> C0
T1 --> C1
T2 --> C2
The settings
| Setting | Typical value for a changelog | Reason |
|---|---|---|
| Cleanup policy | Compact | Keep only the latest record for each key |
| Cleanup policy for windowed stores | Compact and delete | Old windows must eventually disappear, so time-based deletion is added |
| Replication factor | Three in production | The changelog is the only durable copy, and needs full protection |
| Partitions | Same as the input partitions | Keeps tasks and their state aligned |
| Retention time | Not used for plain stores, window retention plus a margin for windowed stores | Prevents unbounded growth of window state |
| Segment size and age | Moderate | Compaction only works on closed segments, so smaller segments compact sooner |
The stream library creates changelog topics with sensible defaults when the application first starts. The application needs permission to create topics, or an administrator must create them in advance with the exact expected names and partition counts.
What a record looks like
The key of each changelog record is the serialized key of the store row. The value is the serialized value, which in many store types is stored together with a timestamp so that time-aware operations remain correct after restoration. The timestamp of the changelog record itself is normally taken from the input record that caused the update, so the changelog remains a faithful timeline of the input. A record with an empty value is a tombstone, which signals a deletion.
7The Write Path: From an Update to a Changelog Record
Follow one input record as it changes the state and leaves a footprint in the changelog.
Input record arrives
The task reads a record, for example a page view event for user Asha, from its input partition.
Read the current value
The processing logic looks up Asha in the local store and finds the count 6.
Compute and put the new value
The logic adds one and puts the new count, 7, into the store under the key Asha.
Logging layer forwards the change
The wrapper around the store takes the put and hands the key and new value to the record collector.
Producer appends to the changelog
The record collector sends the record to the matching partition of the changelog topic, where the partition leader appends it.
Commit ties it all together
At the next commit, the library flushes the store and the producer, and commits the input offsets, so the state, the output, and the progress agree.
sequenceDiagram
participant In as Input partition
participant T as Task
participant S as Local store
participant P as Record collector (producer)
participant C as Changelog partition
In->>T: Page view for Asha
T->>S: Get Asha
S-->>T: 6
T->>S: Put Asha = 7
S->>P: Forward change (Asha, 7)
P->>C: Append record (Asha, 7)
C-->>P: Acknowledged after replication
T->>T: Commit interval reached
T->>S: Flush store
T->>P: Flush producer, commit input offsets
Batching and the commit interval
The library does not wait for a changelog acknowledgement after every single update. Records are batched by the producer and sent in the background, and acknowledgements are awaited when the task commits. The commit interval therefore controls how much work is in flight at any time. A longer interval allows bigger batches and higher throughput, and a shorter interval gives smaller batches and tighter durability.
Caching before the changelog
Stateful operations such as aggregations often place a small in-memory cache in front of the store. If the same key is updated many times between commits, only the last value needs to reach the store and the changelog. This reduces write volume considerably for hot keys, and it also reduces the number of output records emitted downstream. The cache is flushed at each commit, so the changelog always catches up at commit boundaries.
The store is updated first and the changelog second, both in the same task thread. If the process crashes between the two, the local store may be ahead of the changelog. The library handles this by never trusting local state after an unclean shutdown in the strictest processing mode, as later chapters explain.
8Log Compaction: Keeping Only the Latest
Without compaction, a changelog would grow forever. With it, the log stays roughly as large as the live state.
The growth problem
If Asha’s counter is updated a million times, the changelog holds a million records for the key Asha. Yet only the last one matters for restoring the state. Keeping all million would waste disk space and make restoration slow, since a restoring instance would have to read every one of them. A plain time-based retention would not help either, because deleting old records could delete the only record for a key that never changes.
What compaction does
Log compaction is a broker background process that scans closed log segments and, for each key, keeps only the most recent record, removing older ones. The log’s offsets are preserved for the records that remain, so consumers still see a consistent order, but many offsets simply no longer exist. The end result is a log that holds, at minimum, the latest value for every key that has not been deleted.
flowchart TD
subgraph Before["Before compaction"]
B1["offset 0: Asha = 1"]
B2["offset 1: Bilal = 1"]
B3["offset 2: Asha = 2"]
B4["offset 3: Chen = 1"]
B5["offset 4: Asha = 3"]
B6["offset 5: Bilal = 2"]
end
Before --> CMP["Log cleaner keeps latest record per key"]
CMP --> After
subgraph After["After compaction"]
A1["offset 3: Chen = 1"]
A2["offset 4: Asha = 3"]
A3["offset 5: Bilal = 2"]
end
Think of a phone book where every time someone changes their number, a new line is added. Over the years the book gets huge. A clean-up removes each old line for anyone who has a newer line, leaving one current number per person. The book is smaller, and it answers every question the long version could.
What compaction guarantees and what it does not
Guaranteed
- The latest record for every key is never removed by compaction
- Order of remaining records is preserved
- Consumers reading from the start see at least the final value of each key
- Log size trends toward the size of the live data plus a small tail
Not guaranteed
- Immediate removal of old records, since compaction runs in the background
- Compaction of the currently active segment, which is left alone
- That a reader sees every intermediate value, since some are removed
- A predictable moment when a given record disappears
The tuning knobs
| Setting | What it controls | Effect of raising it |
|---|---|---|
| Minimum cleanable dirty ratio | How much of the log must be superseded before cleaning starts | Less frequent cleaning, more wasted space and slower restores |
| Segment age or size | When the active segment is closed and becomes eligible | Longer wait before recent records are compacted |
| Minimum compaction lag | How long a record must sit before it can be compacted | Keeps recent history available longer for readers |
| Delete retention time | How long tombstones are kept after compaction | Longer window for slow readers to notice deletions |
A restoring instance must read every record still in the changelog. A well-compacted log holds roughly one record per live key, so restoration reads about as many records as there are keys. A poorly compacted log can hold many times more, and restoration time grows to match.
9Tombstones: Recording Deletions
State is not only created and updated. Sometimes keys must disappear, and the changelog has to say so.
What a tombstone is
When the application deletes a key from the store, the logging layer appends a record with that key and an empty value to the changelog. This is a tombstone. It tells any reader replaying the changelog that the key is gone. During restoration, the library sees the tombstone and removes the key from the rebuilt store, exactly as the original deletion did.
The life of a tombstone
Key is deleted
The application removes the key, and a tombstone is appended to the changelog after the key’s last real value.
Compaction removes old values
The cleaner discards earlier records for that key, leaving only the tombstone.
Tombstone lingers for a while
The tombstone stays for a configured period, by default about a day, so slow readers can learn of the deletion.
Tombstone is removed
After that period, the tombstone itself is deleted, and the key no longer appears anywhere in the log.
A reader that is offline for longer than the tombstone retention period may miss the deletion entirely. It could then keep a stale copy of a key that no longer exists in the log. State restoration inside a healthy application always reads from a consistent starting point, but external tools that cache data from a compacted topic must consider this window.
Deletions and unbounded growth
A store whose keys are never deleted grows without limit, and so does its changelog. An application that tracks one entry per user session, for example, accumulates entries for every session ever seen unless something removes them. Good designs remove entries when they are no longer needed, by emitting tombstones or by using windows that expire on their own. Without this discipline, the changelog and restoration time grow steadily for months until they become a problem.
Session tracking
Remove a session entry when the session closes or a timeout passes, and the changelog gets a tombstone, so restoration never rebuilds finished sessions.
Deduplication tables
Keep recent identifiers only. Expire old ones with windows, so the table and its changelog stay small.
Customer lookup tables
Deletions in the source system arrive as tombstones in the input topic. They propagate to the store and to its changelog, and compaction eventually removes the data entirely.
10Windowed Stores and Retention
Time-based state has a natural expiry, and the changelog must know about it.
Windows add time to the key
A windowed aggregation, such as “page views per user per five minutes”, stores one row for each user and each window. The store’s key is really the pair of user and window start time. The changelog records these composite keys, so every window of every user is a separate key with its own latest value.
Why compaction alone is not enough
Windows end. After a window closes and its allowed lateness passes, its row will never change again, and nobody needs it after the retention period. But compaction only removes superseded records, not finished ones. If the changelog used compaction alone, closed windows would stay forever, and the log would grow with every new window.
flowchart TD
A["Window row updated as events arrive"] --> B["Window closes, late arrivals still allowed for a grace period"]
B --> C["Window becomes final"]
C --> D{"Older than window retention plus margin?"}
D -->|"No"| E["Row stays in store and changelog"]
D -->|"Yes"| F["Local store drops the row"]
D -->|"Yes"| G["Changelog segments older than the retention are deleted by the broker"]
Compact and delete together
For windowed stores, the changelog uses both cleanup policies at once: compaction to remove superseded values, and time-based deletion to remove old windows. The retention time is set to the window retention of the store plus an extra margin, by default about a day, so that the changelog never deletes data the local store might still legitimately need. This is a careful balance: too short, and a restore could lose windows that still matter, and too long, and the log holds pointless history.
| Store kind | Cleanup policy on changelog | Growth behavior |
|---|---|---|
| Plain key-value store | Compact | Bounded by the number of live keys |
| Windowed store | Compact and delete | Bounded by keys times windows within retention |
| Session-windowed store | Compact and delete | Bounded by active and recent sessions within retention |
The number of rows in a windowed store is roughly the number of distinct keys multiplied by the number of windows kept. Halving the retention roughly halves both the local store and the changelog, and also halves restoration time.
11Restoration: Rebuilding State After a Failure
This is the moment the changelog earns its keep. Follow it from the failure to the first processed record.
When restoration happens
Instance crashes and restarts
The local disk may survive, in which case only a small tail must be replayed, or it may be gone, in which case the whole store is rebuilt.
Task moves to another instance
After a rebalance, the new owner of a task needs the state for its partitions and starts from whatever local copy it has.
New instance joins
Some tasks migrate to the newcomer, which begins with empty local state for them.
Corrupted local state
If the library detects that local state cannot be trusted, it discards it and rebuilds from the changelog.
The restoration steps
Task is assigned
After a rebalance, the instance receives a task and finds that its store is missing or behind.
Find the starting point
The library checks the checkpoint file for the last known changelog offset. If none exists, it starts at the beginning of the changelog partition.
Find the end point
It asks the broker for the current end offset of the changelog partition. This is the target the restore must reach.
Read and apply records
The restore consumer reads records in batches and writes each key and value into the local store, removing keys for tombstones. It writes directly, without producing anything back to the changelog.
Reach the end and switch
When the restore position reaches the recorded end offset, the store matches the changelog. The task moves to the running state and begins processing input.
sequenceDiagram
participant App as Instance
participant CP as Checkpoint file
participant B as Broker
participant S as Local store
App->>CP: Read last restored offset
CP-->>App: Offset 4,200,000 or none
App->>B: Get end offset of changelog partition
B-->>App: End offset 4,350,000
loop Until end offset reached
App->>B: Fetch next batch from changelog
B-->>App: Records
App->>S: Write keys and values, delete for tombstones
end
App->>CP: Save new checkpoint
Note over App: Task starts processing input
While a task’s state is being restored, that task does not process input. For large stores this pause can last minutes or longer, during which the partition’s consumer lag grows. This is the price of correctness, since processing with incomplete state would produce wrong results.
12Checkpoints and Shortcuts
Reading a whole changelog on every restart would be wasteful. Checkpoints let a restart resume where it left off.
What the checkpoint file records
When an instance shuts down cleanly, it writes a small checkpoint file into the state directory. For each store, the file records the changelog offset up to which the local data is known to be complete. On the next start, if the local store is still on disk and the checkpoint is present, the restoration begins at that offset rather than at the start of the log. Only the records written after the checkpoint need to be replayed, which is often a tiny fraction of the total.
A reader who puts a bookmark in a long novel does not start again from chapter one after a break. The bookmark is the checkpoint, and it tells them exactly which page to continue from.
flowchart TD
A["Instance starts task"] --> B{"Local store and checkpoint present?"}
B -->|"Yes"| C["Restore from checkpoint offset to end of changelog"]
B -->|"No local store"| D["Restore from the beginning of the changelog"]
B -->|"Store present, checkpoint missing or untrusted"| E["Discard local store, restore from the beginning"]
C --> F["Small replay, fast start"]
D --> G["Full replay, slowest start"]
E --> G
Why the state directory matters
The location of the local state is set by a configuration value for the state directory. If that directory is on storage that survives restarts, such as a persistent volume, restarts are fast. If it is on temporary storage that disappears with the container, every restart becomes a full restoration. On container platforms, this single decision often decides whether deployments feel instant or take a long time.
| State directory location | Restart behavior | Trade-off |
|---|---|---|
| Persistent volume that follows the instance | Checkpoint found, short replay | Needs stable volume management and identity per instance |
| Local disk of a fixed host | Fast if the same host is reused | Fast only while the workload stays on that host |
| Temporary container storage | Full restore every time | Simplest to operate, slowest to recover |
The checkpoint and local disk are only optimizations. If they are missing or untrusted, the library falls back to the changelog, which is always complete. Correctness never depends on the local disk, only speed does.
Fast bulk loading
When restoring a large amount of data into the embedded database, the library switches the store into a mode optimized for bulk writes, for example by postponing background compaction inside the database until the restore ends. This makes restoration considerably faster than normal writes, since the data arrives in order and needs no reads to check for existing values. After the restore finishes, the store returns to its normal mode.
13Standby Replicas and Warm-Up Replicas
Restoration takes time. Standby replicas remove most of that time by keeping a second copy always ready.
The idea
A standby replica is a copy of a task’s state stores that lives on a different instance, and keeps itself up to date by continuously reading the changelog. It does not process input or produce output. It only maintains the store. If the instance holding the active task fails, the group moves the task to the instance that holds its standby, which already has almost all the data. The remaining restoration is a few seconds of catch-up instead of minutes.
A relief pilot sits in the cockpit jump seat, watching every instrument. If the captain becomes unwell, the relief pilot takes over immediately, with full knowledge of the flight so far. Compare that with calling a pilot from home, who must first be briefed on everything.
flowchart LR
subgraph A["Instance A"]
AT["Active task 0: processes input, writes changelog"]
end
subgraph B["Instance B"]
SB["Standby task 0: reads changelog, keeps copy warm"]
end
AT -->|"appends updates"| CL["Changelog partition 0"]
CL -->|"continuous reads"| SB
AT -.->|"instance A fails"| SB
SB -->|"promoted to active with almost no restore"| NEW["Active task 0 on instance B"]
The cost and the benefit
Benefits
- Failover in seconds rather than minutes for large stores
- Smaller processing pause and less lag buildup after a failure
- Smoother rolling upgrades, since tasks can move to warm copies
- Less load on the changelog during a crisis
Costs
- Extra disk space for each replica of each store
- Extra network and consumer load to keep replicas current
- More instances or capacity needed to host the copies
- No effect on data safety, which the changelog already provides
Warm-up replicas during moves
A similar mechanism helps when a task must move, for example onto a newly added instance. Rather than stopping the task and restoring from scratch on the destination, the library creates a temporary warm-up replica on the destination, lets it catch up in the background while the old owner keeps processing, and only then completes the move. A periodic check, called a probing rebalance, decides when the destination has caught up close enough to the end of the changelog. The gap it must close is governed by an acceptable recovery lag setting, and the number of simultaneous warm-ups is limited so that the changelog and the network are not flooded.
New instance joins
The group decides some tasks should move to it, but its local state is empty.
Warm-up begins
The new instance restores the state of those tasks in the background. The old owners keep running.
Probing rebalance checks progress
At regular intervals, the group asks whether each warm-up is within the acceptable lag of the changelog end.
Hand over
When the copy is caught up, the task moves. The new owner needs almost no restoration, and processing barely pauses.
Applications with strict recovery time targets typically set at least one standby replica. Applications that can tolerate pauses, or whose state is tiny, often leave it at zero to save resources.
14Exactly-Once Processing and Changelogs
State updates must follow the same all-or-nothing rules as output records, otherwise recovery would double count.
The problem without atomicity
Suppose a task updates a counter, writes the new value to the changelog, and crashes before committing its input offsets. On restart, it reads the same input record again and increments the counter a second time. The state now reflects the record twice. In a counting application, this is a permanent error, because nothing will ever subtract the extra one.
Changelog writes inside the transaction
In exactly-once mode, the changelog writes, the output records, and the input offset commits are all part of a single transaction. If the transaction commits, the state change, the output, and the progress become visible together. If it aborts, the changelog records are hidden by an abort marker, the offsets do not move, and the input is read again. Restoration reads the changelog in committed-only mode, so aborted updates never reach a rebuilt store.
sequenceDiagram
participant T as Task
participant S as Local store
participant P as Transactional producer
participant CL as Changelog partition
participant O as Output partition
T->>P: Begin transaction
T->>S: Update counter to 7
S->>P: Changelog record (Asha, 7)
P->>CL: Append, flagged transactional
T->>P: Output record for downstream
P->>O: Append, flagged transactional
T->>P: Add input offsets, commit
P->>CL: Commit marker
P->>O: Commit marker
Note over CL,O: State, output, and progress become visible together
Why local state cannot always be trusted after a crash
In exactly-once mode, the local store may contain updates from a transaction that never committed, because the store was written before the commit. If the instance crashes, its local disk holds state that includes those uncommitted changes, and the changelog does not. To stay correct, the library takes a cautious approach: after an unclean shutdown, it treats the local store as untrustworthy, discards it, and restores from the committed changelog. A clean shutdown writes a checkpoint and the local copy can be reused. This is why crashes cost more in exactly-once mode than in at-least-once mode, and why standby replicas are valuable there.
| Processing mode | State update and output | After an unclean crash |
|---|---|---|
| At least once | Written independently, may repeat on replay | Local state may be reused, but replayed input may update it twice |
| Exactly once | Atomic with offsets in one transaction | Local state discarded, rebuilt from committed changelog, replay is safe |
Correct counting after a crash is worth a longer restoration. But the longer restoration means recovery time is driven by store size, so keep stores lean, keep the changelog well compacted, and use standby replicas where recovery time matters.
15Shortcuts: Source Topics and Global Tables
Sometimes the changelog would only copy data that already exists in another topic. The library can skip the duplication.
Tables built straight from a topic
Suppose a table is defined directly on a topic that already holds the latest value per key, for example a compacted customer profile topic. The store’s changelog would contain exactly the same data as the source topic, doubling storage and write cost for no benefit. With a suitable optimization setting, the library skips the separate changelog and, when it needs to restore the store, reads the source topic itself.
flowchart TD
A["Table defined directly on a compacted source topic"] --> B{"Source topic reuse optimization enabled?"}
B -->|"No"| C["Separate changelog topic created, data duplicated"]
B -->|"Yes"| D["No separate changelog, source topic serves as the changelog"]
D --> E["Restoration reads the source topic"]
C --> F["Restoration reads the changelog topic"]
This shortcut is only safe when the table is a direct, unmodified view of its source topic. Any transformation before the store breaks the equivalence, and the library then needs its own changelog. The source topic should also be compacted, or restoration would replay a very long history.
Global tables
A global table is fully replicated to every instance, so every instance can look up any key. It has no changelog of its own. Each instance restores it directly from the source topic, reading all partitions, and then keeps it current by continuing to read them. The trade-off is that every instance must hold the entire table, which is fine for small reference data and costly for large data.
| Kind of table | Data on each instance | Backed by | Best for |
|---|---|---|---|
| Partitioned table with changelog | Only the partitions the instance owns | Changelog topic per store | Large state, aggregations, joins on co-partitioned data |
| Partitioned table on source topic | Only the owned partitions | Source topic itself, with the optimization | Large lookup tables that are direct views of a topic |
| Global table | All partitions of the source | Source topic itself | Small reference data needed everywhere |
16The Same Idea Elsewhere in Kafka
Once you recognize the pattern of a compacted log of state changes, you will see it in several other places.
Consumer offsets
The internal offsets topic is a compacted log where the key is a group, topic, and partition, and the value is the committed position. It is the changelog of every consumer group’s progress.
Transaction state
The transaction log is a compacted topic holding the latest state of each transactional identity, rebuilt by the coordinator when leadership moves.
Metadata quorum
In the built-in metadata quorum, cluster decisions form a log with snapshots. Different mechanics, same idea: state is the result of replaying a durable log.
Connector storage topics
Integration frameworks store connector configurations, offsets, and statuses in compacted topics, keyed by connector, so a restarted worker rebuilds them.
Database change capture
Tools that capture row changes from databases write them into topics keyed by the primary key. Compacted, such a topic is the latest image of the table.
Application-owned compacted topics
Teams create compacted topics for reference data, such as product catalogs or feature flags, which any service can load fully at startup.
Why the pattern keeps recurring
Log-based state has attractive properties for a distributed system: writes are simple appends, replication is already solved by Kafka, recovery is reading from the start, and any number of readers can build their own view. The changelog topics of stream applications are simply the same design applied to each application’s private state.
Bootstrapping a service from a compacted topic
A service reads a compacted topic from the beginning at startup to load a complete in-memory table, then keeps reading to stay current. This is a hand-made version of what state restoration does inside a stream application.
Materialized views for other systems
Sink connectors read a compacted topic and keep a search index or a database table in sync, treating each record as an upsert or a delete when a tombstone appears.
17Configuration and Defaults
A short list of settings controls how changelogs are created, written, and restored.
| Setting | Role | Guidance |
|---|---|---|
application.id | Names the application and prefixes internal topics | Unique per application, stable across versions |
replication.factor | Replication of internal topics such as changelogs | Three in production. Recent versions can follow the broker default |
num.standby.replicas | Number of warm copies per task | Zero by default. Raise for faster failover |
state.dir | Where local state lives | Use durable storage if restart speed matters |
commit.interval.ms | How often state, output, and offsets are committed | Long by default at-least-once, short in exactly-once mode |
processing.guarantee | At-least-once or exactly-once | Exactly-once for correct counting across failures |
acceptable.recovery.lag | How close a warm copy must be to count as caught up | Lower means stricter, higher means earlier handover |
max.warmup.replicas | Limit on simultaneous warm-ups | Keep low to avoid flooding the changelog and network |
probing.rebalance.interval.ms | How often warm-up progress is checked | Default of several minutes suits most workloads |
topology.optimization | Enables shortcuts such as reusing source topics | Enable deliberately and test topology changes |
| Restore consumer prefix settings | Tunes the consumer used for restoration | Raise fetch sizes to speed up large restores |
| Internal topic override prefix | Sets topic-level settings for internal topics | Use for segment size or compaction tuning |
Per-store control
The store definition lets a developer give the store a stable name, override changelog settings for that store, or turn logging off entirely. Turning logging off removes the changelog and with it the fault tolerance of that store: after a failure the store starts empty. This is acceptable only for caches that can be rebuilt cheaply from other sources, or for stores whose loss is truly harmless.
Disabling the changelog for a store that holds important state means any crash, move, or rolling restart silently resets it. Reserve this for state you can recreate quickly and safely, and document the decision so nobody enables it by accident in a copy of the code.
Broker-side settings that matter
Minimum in-sync replicas
With three replicas and a minimum of two in sync, an acknowledged changelog write survives a broker failure. With weaker settings, an acknowledged update could vanish with a failed broker.
Unclean leader election
Keep it disabled. Otherwise an out-of-date replica could become leader of a changelog partition and silently discard recent updates.
Log cleaner capacity
Brokers need enough cleaner threads and memory to compact busy changelog partitions. If the cleaner falls behind, logs grow and restoration slows.
18Naming and Topology Evolution
The most damaging changelog problems come from innocent-looking code changes, not from crashes.
How names are generated
If a developer does not name a store or an operation explicitly, the library generates a name from the operation type and a running number, based on the position of the operation in the topology. A changelog name might therefore contain a number such as 0000000005. The number depends on how many operations came before it in the definition of the topology.
The trap
Now imagine a developer adds a new filter early in the topology. Every later operation shifts by one position, and the generated names change. On the next deployment, the application looks for changelog topics with the new names, finds none, and creates empty ones. The old changelogs, holding all the accumulated state, are now orphaned. The application starts with empty state, and every count starts again from zero.
flowchart TD
A["Version 1: filter, then aggregate"] --> B["Aggregate store auto-named with number 3, changelog with number 3"]
C["Version 2: new map added at the start"] --> D["Aggregate store now auto-named with number 4"]
D --> E["Application looks for changelog number 4, finds none"]
E --> F["Creates a new empty changelog, ignores the old one"]
F --> G["State appears reset, counts restart from zero"]
Problem
A production application relies on automatically generated names for its stateful stores and repartitioning steps.
Why It’s Harmful
Any change to the topology can silently rename internal topics. State is lost or split, and repartition topics can be abandoned with data still in them. The failure is quiet, and the symptoms appear only as wrong numbers.
Correct Approach
Give every stateful operation an explicit, stable name for its store, its grouping, and its repartition step. Treat those names as part of the public contract of the application, and never rename them without a migration plan.
Other changes that need care
| Change | Effect on the changelog | Safe approach |
|---|---|---|
| Rename a store | New changelog name, old one orphaned | Migrate with a new application version and a rebuild, or keep the old name |
| Change key or value format | Old records cannot be read by new code | Use a compatible format, or reset and rebuild from source topics |
| Change the number of input partitions | Partition counts no longer match | Create a new application with new topics and migrate |
| Add a new stateful operation | New empty changelog, no history | Accept the rebuild time, or seed the state deliberately |
| Change the application ID | Whole new set of internal topics and group | Plan as a full migration, replay input from the start |
Print the description of the topology for the old and new versions and compare them. A difference in internal topic names is a red flag that state will not carry over, and it is far better to find it in a review than in production.
19Sizing and Capacity Planning
Changelog size is predictable once you know which numbers drive it.
The main drivers
After compaction, a plain changelog holds roughly one record per live key. Its size is therefore about the number of live keys multiplied by the average row size. On top of that sits an uncompacted tail: records written since the last cleaning, which grows with the update rate and the time between cleanings. Replication multiplies the total on disk across the cluster. Windowed stores add another factor: the number of windows kept.
| Scenario | Live keys | Average row | Approximate compacted size | Meaning for restoration |
|---|---|---|---|---|
| Small lookup table | 100,000 | 200 bytes | About 20 megabytes | Restores in seconds |
| Per-user counters | 50 million | 100 bytes | About 5 gigabytes | Restores in minutes, spread across tasks |
| Windowed counts, 24 hourly windows | 10 million keys times 24 | 120 bytes | About 29 gigabytes | Noticeable restore time, standbys advised |
| Unbounded session table with no expiry | Grows every day | 500 bytes | Grows without limit | Restore time grows month by month |
Spreading across tasks
The total state is divided among tasks, one per input partition. A store of five gigabytes across twenty partitions gives each task about 250 megabytes to restore, and tasks restore in parallel on different instances. More partitions therefore mean smaller pieces and faster parallel recovery, up to the point where per-partition overhead outweighs the benefit. When planning partition counts for a stateful application, think about recovery time as well as processing throughput.
Divide the per-task state size by the effective restore throughput to get a rough restore time. If a task holds 250 megabytes and the restore reads about 50 megabytes a second, the restore takes about five seconds. If the same task holds 25 gigabytes, the same throughput means about eight minutes. Measure your own throughput, since it depends on disk, network, and the embedded database.
What to watch as it grows
Live key count
A steadily rising key count with no plateau means entries are never removed. Investigate expiry before disks fill.
Ratio of log size to live data
If the changelog is many times larger than the store, compaction is not keeping up, and both disk use and restoration time suffer.
Disk headroom on brokers and instances
Local stores and changelog partitions both need room. Running low on either leads to slowdowns or failures that are harder to fix under pressure.
20A Worked Example With Real Numbers
Follow one small application through updates, compaction, a crash, and a restore.
The setting
A counting application tracks page views per user. Its input has three partitions, so it runs three tasks, each with one store and a matching changelog partition. Focus on task 1 and its changelog partition. The store uses the key user name and the value view count.
Phase one: updates
| Input event | Store after update | Changelog record appended | Changelog offset |
|---|---|---|---|
| Asha views | Asha 1 | Asha = 1 | 0 |
| Bilal views | Asha 1, Bilal 1 | Bilal = 1 | 1 |
| Asha views | Asha 2, Bilal 1 | Asha = 2 | 2 |
| Chen views | Asha 2, Bilal 1, Chen 1 | Chen = 1 | 3 |
| Asha views | Asha 3, Bilal 1, Chen 1 | Asha = 3 | 4 |
| Bilal account deleted | Asha 3, Chen 1 | Bilal = tombstone | 5 |
Phase two: compaction
The broker later compacts the closed segments of this partition. Offsets 0 and 2 are superseded by offset 4 for Asha, and offset 1 is superseded by the tombstone at offset 5 for Bilal. After compaction, the partition holds offset 3 for Chen, offset 4 for Asha, and offset 5, the tombstone for Bilal. Six records became three. Later, once the tombstone retention passes, offset 5 also disappears, leaving only the two live keys.
Phase three: the crash and restore
The instance running task 1 dies and its disk is lost. A different instance is assigned task 1. It has no checkpoint, so it restores from the start of the compacted changelog. It reads Chen = 1, applies it, reads Asha = 3, applies it, and reads the tombstone for Bilal, which removes Bilal from the store, though Bilal was never there in this fresh store. It reaches the end offset, and the store now shows Asha 3 and Chen 1, exactly matching the state before the crash.
sequenceDiagram
participant Old as Old instance
participant CL as Changelog partition 1
participant New as New instance
Old->>CL: Asha=1, Bilal=1, Asha=2, Chen=1, Asha=3, Bilal=tombstone
Note over CL: Compaction keeps Chen=1, Asha=3, Bilal=tombstone
Note over Old: Crash, disk lost
New->>CL: Read from start
CL-->>New: Chen=1
CL-->>New: Asha=3
CL-->>New: Bilal=tombstone
Note over New: Store shows Asha 3 and Chen 1, matches the old state
New->>New: Resume processing input
What if compaction had not run yet?
The result would be identical, only slower. The new instance would read all six records, applying Asha = 1, then 2, then 3, and so on, and it would end in the same final state. Compaction changes how much work restoration takes, never what state it produces. This is the property that makes the changelog design robust: compaction can run late, run early, or run partially, and correctness is unaffected.
The changelog stores results, not operations. Because of that, replaying any superset of the latest records for each key gives the correct state. Compaction is purely an efficiency measure.
21Performance and Restore-Time Trade-offs
Every choice around changelogs trades speed in normal operation against speed in recovery.
Costs in normal operation
Extra producer traffic
Each state update becomes a record sent to Kafka. Caching before the store softens this for hot keys.
Replication load on brokers
With three replicas, each changelog write is stored three times, adding disk and network work to the cluster.
Compaction work
The log cleaner reads and rewrites segments in the background, using brokers’ disk bandwidth and memory.
Standby replicas
Each standby reads the whole changelog stream and maintains a full copy, doubling the storage and read load for that task.
Levers to shorten recovery
Keep state small
Expire entries, use windows with modest retention, and avoid storing large values when a reference would do. Every byte in the store is a byte to restore.
Compact aggressively
Lower the dirty ratio and use smaller segments so the changelog stays close to the size of live data.
Use durable local storage
A persistent state directory turns most restarts into short replays from a checkpoint.
Add standby replicas
Warm copies turn failover into a brief catch-up rather than a full restore.
Spread state across more partitions
More partitions give smaller per-task state and more parallel restoration.
Tune the restore consumer
Larger fetch sizes and batch settings for the restore consumer raise restore throughput on large stores.
flowchart LR
A["Large store, no standby, temporary disk"] --> B["Full restore after each failure"]
C["Large store, persistent disk"] --> D["Short replay from checkpoint after clean restart"]
E["Large store, standby replica"] --> F["Seconds of catch-up after failure"]
G["Small store, compacted changelog"] --> H["Restore in seconds even from scratch"]
| Design choice | Steady-state cost | Recovery time |
|---|---|---|
| No standby, temporary disk | Lowest | Longest, full restore |
| No standby, persistent disk | Low, needs volume management | Short after clean restarts, long after crashes in exactly-once mode |
| One standby replica | About double storage and read load for state | Seconds |
| Two standby replicas | About triple | Seconds, tolerates two failures with fast recovery |
The changelog already makes state safe. Standby replicas and fast disks do not make data safer. They make recovery faster. Choose them based on your recovery time targets, not on a fear of data loss.
22Security and Privacy
Changelog topics contain the complete state of your application, including anything sensitive inside it.
Access control
A changelog topic contains every key and value stored in the state store, which may include personal data, account balances, or business secrets. Anyone who can read the topic can read the entire state. Access should therefore be limited to the application’s own identity. A convenient approach is to grant permissions by a prefix of the application ID, so that the application can create, read, write, and describe all internal topics whose names begin with its ID, and nobody else can.
Prefixed permissions
Grant the application identity access to topics starting with its application ID. Deny everything else by default.
Restrict human access
Engineers should not read changelog topics casually. Treat them like production databases.
Encrypt in transit and at rest
Use TLS between applications and brokers, and encrypt disks on brokers and on instances holding local state.
Protect the state directory
The local store is a full copy of the data. Set tight file permissions and secure storage volumes.
Pre-create topics in locked-down clusters
If applications may not create topics, administrators create the changelogs with the exact names, partition counts, and settings the application expects.
Guard against outside writers
Only the owning application should write to its changelogs. A rogue producer could inject records that corrupt restored state.
Deletion and the right to erasure
Privacy rules often require that data about a person can be deleted on request. In a changelog, deletion means emitting a tombstone for the key, and waiting for compaction and the tombstone retention period to remove the data physically. Until then, older values may still be present in the log. Data may also linger in the local stores of standby replicas, in aborted transactional records, in backups, and in downstream copies. A privacy-aware design documents this timeline, sets compaction and retention to meet the required deadlines, and, for very sensitive fields, encrypts values with per-person keys that can be destroyed to make the data unreadable at once.
Writing a tombstone does not immediately remove the earlier values from disk. Plan for the delay, verify it in tests, and involve your privacy or compliance team when defining the time limits.
23Monitoring, Logging, and Metrics
A healthy changelog pipeline is quiet. These signals show when something drifts.
| Signal | What it tells you | Healthy pattern |
|---|---|---|
| Application state | Running, rebalancing, or in error | Mostly running, brief rebalancing during changes |
| Restoration progress and duration | How long tasks spend rebuilding state | Short and rare, longer only after large changes |
| Restore consumer lag | Distance to the end of the changelog during a restore | Falls steadily to zero |
| Standby replica lag | How far warm copies trail the changelog | Small and stable |
| Changelog size versus store size | Effectiveness of compaction | Log is a small multiple of live data |
| Log cleaner activity on brokers | Whether compaction keeps up | Steady, no growing backlog of dirty segments |
| Producer errors on changelog writes | Trouble writing state updates | None |
| Task migration count | How often state moves between instances | Rare, tied to deployments or failures |
| Store disk usage on instances | Growth of local state | Plateaus with the live key count |
Hooks and tools
Restore listener
The library lets an application register a listener that is told when a restore starts, when each batch is restored, and when it ends. It is ideal for logging and for metrics on restore duration.
Store lag information
Applications can ask for the lag of their local stores compared with the changelog, which is useful for routing queries to the freshest copy.
Topic and consumer group tools
Describe the changelog topic, its partitions, and its settings, and check the lag of consumers reading it.
Log dump tool
Reads the changelog segment files directly, showing keys, values, timestamps, and tombstones, useful for investigating suspicious state.
Application reset tool
Resets an application’s input positions and deletes its internal topics when a full rebuild is intended. It must be used deliberately.
Record the start, duration, and record count of every restoration. Over months, a slow upward trend in restore time is an early warning of state growth, long before it becomes an outage.
flowchart TD
A["Alert: input lag rising on one partition"] --> B{"Is the task restoring?"}
B -->|"Yes"| C{"Is restore consumer lag falling?"}
C -->|"Yes"| D["Wait, and consider standbys or smaller state"]
C -->|"No"| E["Check changelog broker health, network, and restore consumer settings"]
B -->|"No"| F{"Is the store disk full or slow?"}
F -->|"Yes"| G["Free space, tune the embedded database, or add capacity"]
F -->|"No"| H["Look at processing logic and downstream backpressure"]
24Failure Scenarios and Recovery
Walk through what happens for each common failure, and what you should do.
| Failure | Immediate effect | How it resolves |
|---|---|---|
| Application instance crashes | Its tasks are reassigned | Standbys are promoted, or new owners restore from the changelog |
| Instance disk lost | Local state gone | Full restore from the changelog on whichever instance takes over |
| Changelog partition leader fails | Brief pause for writes and restores of that partition | New leader elected among in-sync replicas, no acknowledged data lost |
| Broker outage below minimum in-sync replicas | Changelog writes rejected | Tasks stall or fail until brokers return, no wrong state is written |
| Changelog replication factor of one, broker lost | Data may be permanently lost | State must be rebuilt by replaying input, possibly for a long time |
| Changelog topic deleted by mistake | Restoration cannot find its data | Recreate and rebuild from source topics after resetting the application |
| Changelog retention shortened by an administrator | Records removed that were still needed | State restored with gaps, rebuild from input to correct |
Rolling upgrade of the application
Instances restart one at a time. With a persistent state directory and clean shutdowns, each restarts with a short replay. With temporary storage, each restart triggers a restore, and standby replicas or warm-up replicas hide most of the delay.
Changelog offset out of range
If the changelog was truncated or recreated so that the checkpointed offset no longer exists, the library detects this, discards the local store, and restores from the beginning. It is safe but slow.
Restore that never seems to finish
Usually the store is far larger than expected, the changelog is barely compacted, or the restore consumer is throttled. Check the log size, compaction backlog, and fetch settings before anything else.
Full rebuild from input
When the changelog is lost or incompatible, the last resort is to reset the application and replay the input topics from the start. It is the slowest path, so it is worth protecting the changelog well to avoid it.
flowchart TD
A["Task needs its state"] --> B{"Local store trusted and checkpoint valid?"}
B -->|"Yes"| C["Replay only the tail of the changelog"]
B -->|"No"| D{"Changelog intact and readable?"}
D -->|"Yes"| E["Full restore from changelog"]
D -->|"No"| F["Reset application, replay input topics to rebuild state"]
C --> G["Resume processing"]
E --> G
F --> G
Most of the safety of a stateful application rests on the changelog being intact and well replicated. Guard it with sound replication settings, restricted access, and administrative discipline, so that the fast paths are always available.
25Design Patterns and Anti-patterns
Some designs around changelogs repeatedly work well. Others repeatedly fail, usually at three in the morning during a rebalance.
Patterns worth copying
Name every store explicitly
Give each state store, and each stateful operator, a deliberate and stable name. The changelog name is derived from it, so a stable name means the stored history survives every future edit of the topology.
Keep values small and keys well spread
Store compact summaries such as counters, totals and latest snapshots rather than whole event histories. Small values mean small changelogs, short restores, and light compaction work.
Standby replicas for latency-sensitive services
When a service must recover in seconds, keep one or more standby copies. They continuously replay the changelog, so a failover starts from almost-current state instead of an empty disk.
Persistent disks for state directories
Place the local state directory on storage that survives a restart of the application instance. A returning instance then only replays the tail of the changelog, guided by its checkpoint.
Treat the changelog as owned infrastructure
Restrict who can write or delete these topics, alert on their health, and back their configuration with the same review process as any production database.
Anti-patterns to avoid
Problem
A team lets the changelog topics be created with a single replica, or with a minimum in-sync setting of one, because it is quicker in a test environment, and the same settings reach production.
Why It’s Harmful
The changelog is the only durable copy of the state. Losing one broker loses that copy, and the only recovery is a slow full replay of the input topics, if the inputs even still exist.
Correct Approach
Use three replicas and a minimum of two in-sync replicas for every changelog in production, and verify the created topics rather than assuming the defaults applied.
Problem
A team stores an ever-growing list inside a single key, for example every order a customer has ever placed, and rewrites the whole list on each update.
Why It’s Harmful
Every update writes the full, growing value to the changelog. Storage, network use and restore time all grow with the size of the list, and one hot key can dominate a partition.
Correct Approach
Use a composite key such as customer plus order, so each record stays small and independent, or keep only an aggregate and send detailed history to a separate topic.
Problem
A team turns logging off for a store to save disk and network, and then relies on it for data that the business cannot recreate.
Why It’s Harmful
Without the changelog the store has no backup. Any machine loss, redeployment to a new disk, or rebalance to another instance starts that store empty, and results silently become wrong.
Correct Approach
Disable logging only for stores that are cheap to rebuild from a source topic or are truly disposable caches. Document the decision beside the store definition.
Problem
An operator edits the retention or cleanup policy of a changelog by hand to reclaim space, changing a compacted topic to a time-based deletion topic.
Why It’s Harmful
Time-based deletion removes old but still-current values, for example a customer who has not changed for a month. The next restore silently misses that state.
Correct Approach
Leave the cleanup policy alone. If space is the concern, reduce the number of keys, shrink the values, or expire keys deliberately by writing tombstones.
26Best Practices and Common Mistakes
Most changelog trouble comes from a small set of repeated mistakes. Knowing them ahead of time is cheaper than discovering them in an incident.
A working checklist
Common mistakes and their fixes
| Mistake | What it causes | Fix |
|---|---|---|
| Relying on automatic store names | Renamed changelog after adding an operator; state appears lost | Name stores and operators explicitly |
| Ignoring restore time in capacity plans | Long outages after a failure, in spite of good steady-state speed | Measure restore rate and plan standbys |
| Deleting local state directories casually | Full restores for every task on the next start | Keep directories on persistent storage |
| Changing partition counts of input topics | Keys move to different tasks; old state no longer lines up | Create a new application and rebuild |
| Skipping tombstones for expired keys | Store and changelog grow without bound | Delete keys deliberately when they end |
| Treating an internal topic as a public API | Other teams depend on a format that may change | Publish a proper output topic instead |
The most damaging errors are the ones that raise no alarm. Wrong retention, missing replicas and renamed stores all keep the application running. They only reveal themselves when a failure arrives and the promised recovery does not.
flowchart LR
A["Design: keys, names, value size"] --> B["Configure: replicas, standbys"]
B --> C["Provision: disks, brokers"]
C --> D["Secure: ACLs, erasure plan"]
D --> E["Observe: lag, restore, disk"]
E --> F["Rehearse: kill and time recovery"]
F --> A
The loop matters because none of these steps is a one-time task. Data volumes grow, topologies change, and infrastructure is replaced. Each change can quietly undo an earlier assumption, so the cycle of design, configuration, observation and rehearsal keeps repeating for as long as the application lives.
27Real-World and Industry Examples
The pattern of a fast local store backed by a compacted log appears wherever a stream of events must be turned into current facts.
Retail: live inventory
A retailer receives sale, return and delivery events from many stores. A stateful application keeps the current stock count per product and store. The changelog lets any instance take over that count instantly if a machine fails, while the count itself is served in microseconds during checkout.
Banking: fraud detection windows
A bank counts card transactions per customer over the last ten minutes. Windowed stores hold those counts, and windowed changelogs with time-based retention keep only the recent history. A failure of one instance does not reset any customer’s count, so a burst of suspicious activity is never forgotten.
Ride hailing: driver location and status
A platform keeps the latest position and availability of every driver. Each new update replaces the older one, which is the ideal shape for compaction. The changelog stays small even though billions of updates flow through it.
Streaming media: viewing sessions
A media service groups viewing events into sessions and computes watch time per user. Session stores and their changelogs hold the open sessions. When a session closes, tombstones and retention rules let the storage shrink back down.
Logistics: shipment state machines
A carrier tracks each parcel through a series of states. The store keeps the current state per parcel, and the changelog guarantees that a rebalance during a peak day never loses a parcel’s position in its journey.
What the examples share
In every case there is a stream of small updates, a need for very fast lookups of the latest answer, and a requirement that a machine failure must not change that answer. The combination of local storage for speed and a compacted log for durability fits each of these needs, which is why the same design appears across industries.
28Frequently Asked Questions
Short, direct answers to the questions that come up most often when people first meet changelog topics.
Usually not. The stream processing library creates them automatically when the application starts, provided its identity has permission to create topics. Some locked-down clusters require an administrator to create them ahead of time using the exact expected names.
Because it stores current state, not history. A value written a year ago is still the current value if nothing changed it. Deleting by age would erase valid state, so compaction keeps the latest record per key indefinitely.
Equivalent, not identical. The changelog holds the history of changes, of which compaction keeps the newest per key. Replaying it produces the store’s contents, but the store itself is a separate, indexed local structure.
Technically yes, but it is discouraged. The internal format and naming are implementation details that may change. If others need the data, publish a purpose-built output topic.
By default, yes, except stores for global tables and stores where logging was explicitly disabled. Some tables also reuse their source topic as the changelog when a specific optimization is enabled.
The application loses its backup. While it runs, the local stores still work, but the next restart or rebalance to a new instance finds nothing to restore. Rebuilding needs an application reset and a replay of the input topics.
It depends on the size of the changelog and the restore speed. Divide the data size by the sustained read and write rate of the instance. Measuring in a test is far more reliable than estimating.
Processing on a task would otherwise use incomplete state and produce wrong results. The library waits until the store has caught up with the changelog before resuming that task.
Extra disk, network and processing for the copy of the store it maintains. In return, failover starts from nearly current state, cutting recovery from minutes or hours to seconds.
They can be used for reading stale data through interactive queries when enabled. They are not the primary, so their data may lag slightly behind.
Not safely for a running stateful application. Keys would map to different partitions, so existing state would sit in the wrong place. The safe route is a new application identity and a rebuild from the source.
Write a tombstone for that key so the store and the changelog drop it. Remember that the tombstone itself is retained for a period before compaction removes it, and that copies in other topics need their own handling.
Compaction runs in the background, not instantly. Until it catches up, old versions of keys remain in the log. Tuning the cleaning thresholds trades storage against background work.
Yes. Changelog writes join the transaction, so an aborted attempt leaves no visible changes. After an unclean crash, the local store may contain uncommitted changes, so the library discards it and restores from the changelog.
A standby is a long-lived backup copy kept ready for failover. A warm-up replica is a temporary copy built on a new or different instance so that a task can move there without a long stall, after which it becomes the active copy.
Yes, in effect. The changelog names are built from the application identity, so a new identity looks for new, empty topics. Keep the identity stable for the life of the state.
Check that it exists with the expected partition count and replication, that its consumer lag from the application is near zero during normal running, and that its size stays proportional to the number of keys.
It holds whatever the store holds, so it needs the same protection as any topic carrying that data: access control, encryption in transit, encryption at rest where required, and a plan for erasure.
Reading a log to answer a lookup means scanning it, which is slow. The local store provides an index for instant reads. The changelog is the durable record, and the store is the fast view of it.
Run the application with realistic data, stop an instance abruptly, delete its local directory, and time how long until processing resumes and outputs match a known correct result. Repeat with a broker failure.
29Summary and Key Takeaways
A stateful stream application needs memory, and memory kept on one machine dies with that machine. Kafka’s answer is the changelog: every change to a local store is also written to a compacted, replicated topic, so the store is a fast view of a durable log. Compaction keeps the latest value per key and tombstones record deletions. Windowed stores add time-based retention. When a task moves or a machine returns, restoration replays the changelog, shortened by checkpoints and by standby and warm-up replicas. Exactly-once processing folds changelog writes into transactions, and stable names, sound replication, restricted access and careful monitoring keep the whole arrangement trustworthy over years of change.
Key Takeaways
- The changelog is the backup of the state — every store update is also written to a topic, so a lost machine never means lost state.
- Compaction fits the job — keeping the newest record per key bounds the size by the number of keys, not the number of updates, and tombstones record deletions.
- Restoration is the price of failure — replay time is proportional to data size, so checkpoints, persistent disks and standby replicas exist to shrink it.
- Exactly once includes state — changelog writes join the transaction, and an unclean crash means wiping the local store and restoring from committed data.
- Names and partitions are contracts — changing an application identity, a store name, or the input partition count breaks the link between state and its history.
- Configure it like a database — three replicas, two in-sync, restricted access, erasure plans, and alerts on lag, size and restore progress.
- Prove it by rehearsal — kill instances and brokers on purpose, measure recovery, and keep the numbers in your runbook.