Savepoints and state schema evolution
Why must new code read old state, and why is the state schema a public interface?
Deploying new code against existing state requires the new program to read the old state. This is the practical dominant operational problem of streaming.
A savepoint is a deliberately taken, format-stable, self-contained checkpoint that you own, rather than one that the retention policy owns and may delete. You take one before an upgrade. To restore a savepoint into modified code, three things must line up:
- Stable operator identity. The engine matches saved state to operators by an ID. Auto-generated IDs change when the topology changes, so you must assign explicit IDs (often called UIDs).
- Compatible state serializers. The code that writes and reads the state bytes must stay compatible. Adding a field with a default value is fine. Changing a field's type, or removing a key field, is not.
- A topology shape that tolerates the change. Removed state must be possible to drop, and added state must be able to start empty without corrupting the meaning of results.
Now notice what this list is. It is compatibility modes over a schema registry (a service that stores each schema version and checks backward and forward compatibility), relocated into the runtime.
The escape hatches and their prices
| Escape | How | Price |
|---|---|---|
| Parallel run and cutover | start a new job from an earlier source offset, run both, then switch | double compute during the replay window; the source must still retain that window; sinks must be idempotent or partitioned so the overlap is harmless |
| Offline state migration | a program that reads the savepoint and rewrites it | a bespoke program against an internal format |
| Accept state loss, restart empty | begin with no state | for a 30-day window, 30 days of degraded output |