Bytes and compatibility
Apps change, and their data changes with them.
Encoding and decoding
In memory, objects link to each other by pointers. A pointer is an address inside one process, so it means nothing in a file or on another machine. Encoding (serializing, marshaling) turns data into a self-contained run of bytes. Decoding (parsing, deserializing) turns the bytes back into objects. "Serializable" means something else in transactions, so this course says "encoding".
Rolling upgrades
A rolling upgrade puts the new version on a few servers, checks them, then moves on, with no downtime. App users update whenever they like, or never. So old and new code run at the same time. Data mixes too: a relational table has one schema at a time, but a schemaless store can hold old and new shapes together.
Backward vs forward compatibility
Backward: new code can read what old code wrote. Forward: old code can read what new code wrote. Backward is usually easy, since new code knows the old format. Forward is harder, since old code must cope with things added later. Old client, new server: the request needs backward, the response needs forward. New client, old server: the other way round.
Keeping unknown fields
New code saves a record with a new field. Old code reads it, changes one thing, and writes it back. If old code decoded into a class that drops unknown fields, the new field is gone. Old code should carry unknown fields through untouched.
Language-specific formats
Java's built-in serialization and Python's pickle save objects with almost no code. But other languages struggle to read the data. Decoding can build any class, so crafted input can run an attacker's code. Versioning is an afterthought. Use them only for very short-lived data.
Remember
- Encoding: objects to bytes. Decoding: bytes to objects.
- Old and new code overlap during every upgrade.
- Backward = new reads old. Forward = old reads new.
- Old code must keep fields it does not understand.
- Built-in language serializers are for short-lived data only.