Skip to main content

Kafka Streams Disaster Recovery: State Stores and Changelogs

· 9 min read
OSO Engineering
The team behind OSO Kafka Backup

Kafka Streams disaster recovery means recovering state stores, not just topics. Every Streams task keeps its aggregation, join, or window state on local disk — Kafka's replication never touches that disk. The only durable copy of that state Kafka manages is the changelog topic behind it, and that is the thing your recovery plan actually needs to protect.

This post explains why a Streams app needs a different recovery plan than a plain consumer, compares the three ways to get its state back, and works out what state-store size does to your recovery time.

Key takeaway

A Streams app's real recovery unit is input topics, changelog topics, and its application.id consumer offsets together. Backing up only the visible input and output topics leaves every in-flight aggregation unrecoverable.

Why Kafka Streams needs its own DR plan

A Kafka Streams task keeps its working state — the running total in an aggregation, the joined record in a stream-table join, the contents of a window — in a local state store, RocksDB by default, with an in-memory option available too. That store lives on the disk of whichever instance is running the task. Kafka's replication mechanism has no role in protecting it.

What does protect it is the changelog topic. Every write to a state store also gets written to a compacted Kafka topic, so the store's contents exist twice: once on local disk, once as a Kafka topic that replicates like any other. Back up that changelog topic, and you can rebuild the store. Skip it, and the state only ever existed on one disk.

Streams applications also generate repartition topics — intermediate topics created when an operation needs data re-keyed before a downstream aggregation or join. Repartition topics don't need backup coverage. They regenerate automatically from the input topics the next time the application reprocesses them.

Topic typeWhat it holdsBack it up?Why
Input topicsSource records the app consumesYesThe origin of everything downstream
Output topicsProcessed results the app producesYesConsumers depend on this data existing
Changelog topicsState-store contents (aggregations, joins, windows)YesThe only durable copy of local state
Repartition topicsIntermediate re-keyed recordsNoRegenerates automatically from input on reprocess

Three ways to recover Streams state

Every Streams DR plan reduces to one of three strategies, and they trade recovery time against consistency in different ways.

Rebuild from input. Restore only the input and output topics, delete the local state directory, and let the application reprocess from the beginning with the Kafka Streams reset tool:

kafka-streams-application-reset \
--bootstrap-servers target-kafka:9092 \
--application-id order-processor \
--input-topics orders,inventory,customers \
--to-earliest

This is the simplest strategy and needs no changelog backup at all. It's also the slowest: recovery time scales with total input volume, not with how long the outage lasted. An aggregation running for months takes months' worth of reprocessing to rebuild, unless the input topic's retention has already trimmed that history.

Restore from changelog. Back up the changelog topics alongside input and output, and restore all of them together. On restart, the application reloads state directly from the changelog instead of reprocessing anything:

source:
bootstrap_servers:
- kafka:9092
topics:
include:
- orders
- customer-totals
- "order-processor-*-changelog"
exclude:
- "order-processor-*-repartition"

storage:
backend: s3
bucket: kafka-backups
prefix: streams/order-processor

backup:
compression: zstd
source_cluster_id: "production"

This is fast and exact, because the restored data is the state, not the inputs it was computed from. The trade-off is changelog volume: a long-running aggregation over a large key space produces a changelog that can be as large as the state store itself, and backing it up costs storage and restore time proportional to that size. /examples/kafka-streams has the complete backup and restore YAML for this pattern, including the Kubernetes CronJob and CRD forms.

Standby replicas. Set num.standby.replicas above zero, and Kafka Streams keeps a warm duplicate of each task's state running on a different instance at all times:

props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1);

When the active instance fails, Streams promotes the standby immediately — no restore step, no reprocessing. This is the closest thing to zero-downtime recovery Streams offers, but it only covers instance or broker-level failure. A standby reads from the same source topics as the task it backs up, so losing the whole cluster or the region takes the standby down with everything else. Standby replicas are a complement to backup, not a substitute for it.

StrategyRecovery timeConsistencyCovers region loss?Operational cost
Rebuild from inputSlow — scales with input volumeEventually consistentYes, if input is backed upLow — no changelog backup needed
Restore from changelogFast — scales with changelog sizeExactYesMedium — changelog backup storage
Standby replicasNear-instantExactNoMedium — extra running capacity

Most production deployments combine two of these: standby replicas for the common case of a single instance dying, and changelog or input backup for the case where the whole cluster is gone. The Kafka Streams example and the Streams section of cross-cutting concerns both document the topic-filter patterns that keep changelog backups scoped correctly.

What this does to your RTO math

State-store rebuild time is a function of data volume, not infrastructure provisioning time. A fresh cluster can be ready in minutes. A state store holding weeks of aggregated history can still take minutes to hours to reload or rebuild, depending on which strategy you picked and how much data that involves.

That changes what "recovered" means for a Streams app. The RTO/RPO planning guide defines the recovery-time clock as stopping when the service is actually ready, not when a process starts — for a Streams app, that means the clock runs until the state store has caught up, not until the container is running.

Standby replicas push the RTO for single-instance failure close to zero. They don't change the RTO for full-cluster or region loss, which still depends on how fast you can restore and rebuild from backup. Test both numbers separately, because they answer different failure scenarios.

Testing has to include the state, not just the topics. The DR testing playbook covers the general drill structure; for a Streams app, add two items specific to stateful processing: query the restored state store directly to confirm its contents match expectations, and time an actual standby promotion under load rather than assuming the failover is as fast as the configuration implies.

Consumer groups and application.id during recovery

A Kafka Streams application's consumer group is named after its application.id. Confirm which group you're recovering before you touch offsets:

kafka-consumer-groups \
--bootstrap-server target-kafka:9092 \
--group order-processor \
--describe

Which recovery strategy you chose decides what happens to that group next. A changelog restore needs the group's offsets repositioned to match the restored data — the offset management guide and the consumer offsets backup post cover that mechanic in depth. A rebuild-from-input strategy instead uses the reset tool shown above to force the group back to the earliest offset, so reprocessing starts clean.

PITR and Streams state don't mix cleanly

Point-in-time recovery restores topics to a specific timestamp, and it's tempting to apply the same window to a changelog topic. Don't. A windowed changelog restore can leave the state store out of sync with input and output topics that weren't restored to the same cut — the aggregation the changelog represents no longer matches the data it was computed from.

The safer pattern: restore input and output topics to the PITR window, skip the changelog, and let the application rebuild state fresh from that point. It costs more time than a changelog restore, but it guarantees the state matches the data. The restore and PITR guide covers the general timestamp-filtering mechanism this borrows from.

FAQ

Frequently asked questions

Does Kafka replication protect Kafka Streams state stores?

No. State stores live on local disk on whichever instance runs the task, and Kafka replication has no role there. Only the changelog topic backing a store is a Kafka topic, so it is the only part of Streams state that replication and backup can actually reach.

What's the fastest way to recover a Kafka Streams application after a failure?

Standby replicas, configured with num.standby.replicas, keep a warm duplicate task running and promote it almost immediately on failure. That only covers instance-level failure, though — a full cluster or region loss takes the standby down too, since it reads from the same source topics.

Do I need to back up Kafka Streams repartition topics?

No. Repartition topics are intermediate and regenerate automatically from input data when the application reprocesses. Changelog topics are the ones that hold state and need backup coverage.

How long does it take to rebuild Kafka Streams state after a restore?

It depends on changelog or input volume, not on how fast the infrastructure comes back online. Large aggregations can take minutes to hours to reload or reprocess, so factor state-store rebuild time into your RTO separately from cluster restart time.

Can I restore Kafka Streams changelog topics to a specific point in time?

Not safely on their own. A time-windowed changelog restore can desynchronize from input and output topics that were not restored to the same window. The safer pattern is to restore input and output topics to the PITR window and let the application rebuild state fresh from there.

Conclusion

A Kafka Streams application's state store is the part of the system Kafka's own replication doesn't protect. The changelog topic behind it is what makes that state recoverable at all, and the strategy you pick — rebuild from input, restore from changelog, or standby replicas — sets whether recovery takes minutes or hours. Pick based on the RTO the failure actually demands, and test the choice before an incident forces it.

Ready to Back Up Streams State, Not Just Topics?

OSO Kafka Backup captures changelog topics alongside input and output topics in one config, so Streams state stores recover with the rest of the pipeline. See the Kafka Streams example or get started.