In this lesson, you will learn how to evolve your application’s state schema without losing the ability to resume from existing checkpoints.
When you deploy a streaming application, the engine serializes your state—like counters or windowed aggregates—into a binary format within your checkpoints. As your business logic matures, you may need to add a new field to a state object or change a data type. If the serialization format changes, your job will fail to restore from the old checkpoint because the binary data no longer maps to your updated Java or Scala classes.
Managing State Evolution
To handle schema evolution, you must ensure your state objects remain compatible with their previous versions. The primary approach involves using evolution-aware serializers, such as those provided by Avro or Protobuf.
When you modify a class, follow these best practices:
- Don't rename fields: Use the same field names so the deserializer can map them correctly.
- Add, don't delete: If you need a new field, add it as an optional value. Defaulting to
nullor a specific default value prevents the job from crashing when it encounters old state that lacks that field. - Use Schema Evolution: If you use Avro, you can define a schema evolution policy where the system automatically handles the mapping between the "reader" schema (your new code) and the "writer" schema (the old checkpoint data).
For example, if you are tracking a user's total spend:
// Original state class
public class UserState {
public double totalSpend;
}
// Evolved state class (Compatible)
public class UserState {
public double totalSpend;
public double lastTransactionAmount; // Added field with default 0.0
}
By ensuring your serialization framework (like Flink's TypeSerializer) can handle these structural changes, you avoid the "incompatible checkpoint" error. If your changes are too drastic to reconcile—such as changing a field type from Integer to String—you must clear your state and start from a fresh checkpoint, which resets your streaming history.
Key Takeaway: To evolve state successfully, prioritize additive changes and use schema-aware serialization to ensure that updated code can still interpret binary data stored in older checkpoints.