Skip to content
Course contents

Your Application Is Two Versions At Once

For the length of a deploy, old code and new code run side by side against the same data. Everything about schema change follows from that one fact.

Lesson 1 of 48 min read

By the end of this lesson you can

  • Explain why a rolling upgrade requires compatibility in both directions
  • Distinguish backward from forward compatibility without confusing them
  • Identify which direction a proposed change breaks

You deploy. For the next few minutes, some servers run v1 and some run v2. Requests hit both. They share one database.

That is not an edge case to be tidied away — it is the normal condition of every system that deploys without downtime. And it means your data format has to satisfy two different programs at the same time.

The idea

Backward compatible: new code can read data written by old code. Forward compatible: old code can read data written by new code.

Backward is the easy one — the new version knows what the old one looked like. Forward is harder, because the old version must tolerate something that did not exist when it was written.

The names are worth getting straight, because half the confusion in schema discussions is two people using them in opposite directions. The trick that works: the adjective describes what the new code can cope with. Backward compatible code reaches backward in time to read old data. Forward compatible data can be read by code from the past, reaching forward.

Why both directions, at once

During a rolling deploy, both happen simultaneously:

v2 server writes a record  ──→  v1 server reads it     needs forward compat
v1 server writes a record  ──→  v2 server reads it     needs backward compat

Neither is optional, because you do not control which instance handles which request. If either direction fails, some fraction of traffic hits a version that cannot parse what it was given, and the failure rate is proportional to how far through the deploy you are — which is why these incidents look so strange in graphs, ramping up and then resolving as the rollout completes.

The direction each change breaks

Once you have the two directions, evaluating a change becomes mechanical.

Adding an optional field — safe both ways. New readers apply a default when it is absent; old readers ignore what they do not recognise.

Adding a required field — breaks forward compatibility. Old writers never sent it, so the new reader meets data with a hole and no default to fill it. This is the single most common broken deploy in the industry.

Removing an optional field — safe both ways, for the mirror-image reasons.

Removing a required field — breaks backward compatibility. The old reader demands a field the new writer has stopped sending.

Renaming a field — breaks both directions, unless the encoding identifies fields by something other than their name. Which is a large hint about what the next two lessons are for.

Where this bites in practice

  • A mobile app. You cannot force an upgrade. Some users run a version from 2023, and your server must still speak to it. Forward compatibility is not a deploy-window concern here, it is permanent.
  • A message queue. Producers and consumers deploy independently, and messages sit in the queue across the boundary. A message written by v2 may be consumed by v1 an hour later.
  • A public API. Your clients are other companies. They upgrade when they feel like it, which may be never.
  • An event log or data lake. Records are kept deliberately forever, and the whole point is that future code will analyse them.

Check yourself

You add a required `currency` field to an order record and deploy. Some orders fail to load. Which compatibility broke, and on which instances?

What to take away

Two versions of your code are always running, and durable data outlives every version that ever touched it. Evaluate a change by asking what breaks in each direction — and when one direction breaks, split the deploy rather than hoping the window is short.

Finished this one?

skip for now