# How do you evolve a configuration schema while old and new binaries read and write the same config?

> A practical guide to evolving shared configuration safely in mixed-version fleets, covering additive changes, defaults, downgrade support, and rollout order.

Canonical URL: https://www.devobs.io/articles/qa-ge50-config-schema-version-compatibility-mixed-fleet/
By: Lena Fischer
Published: 2023-03-10T04:10:55.368Z
Updated: 2026-09-06T10:18:15.722Z
Section: Architecture

Treat shared config as a compatibility contract, not just a file format. If old and new binaries will both read or rewrite the same document, the safe path is to keep schema changes additive, make presence-versus-default explicit, preserve unknown fields on round-trip, and delay any new write format until every possible writer can tolerate or preserve it. If rollback matters, design downgrade behavior before release.

## What contract should you define before changing the schema?

Write down four rules first: who reads the config, who can write it back, whether humans depend on comments or ordering, and whether a partial rollout can be rolled back after new binaries have already rewritten the file.

Also separate parse compatibility from behavior compatibility. A document can still parse while changing meaning because a default changed, a unit changed from seconds to milliseconds, or an unset value now means something different. [JSON Schema defines structure, constraints, and related processing for JSON data](https://json-schema.org/draft/2020-12/json-schema-core.html), but it does not define your rollout semantics or downgrade policy.

The key operational boundary is the write path. During a mixed-version rollout, keep the canonical config in the old writable shape until every binary that might rewrite that config has been upgraded. A preflight compatibility check is useful, but it is not enough if an older writer can still publish or normalize the file afterward.

## How should the format evolve without breaking mixed versions?

Prefer additive changes and stable identifiers. In protobuf, field identity is tied to its number, and the [proto3 language guide](https://protobuf.dev/programming-guides/proto3/) says field numbers cannot be changed once a message type is in use and should never be reused. Even if your on-disk config is JSON or YAML, the lesson holds: do not quickly recycle names, meanings, or enum values.

Be especially careful with defaults. The same [proto3 guide](https://protobuf.dev/programming-guides/proto3/) recommends `optional` for maximum compatibility and explains that explicit presence distinguishes an unset field from one set to a default value. That is exactly the distinction configuration systems lose when they collapse absence into `false`, `0`, or empty string. If behavior changes based on whether an operator set a value intentionally, model that state directly.

YAML adds another risk: it is a full data serialization language, not just pretty key-value text. The [YAML 1.2.2 specification describes multiple information models and includes comments, anchors, aliases, styles, and mapping key order in its presentation and serialization model](https://yaml.org/spec/1.2.2/). A parser/emitter that reloads and rewrites the whole file may preserve data values while still dropping comments or normalizing structure in ways operators dislike. If humans author the file, separate machine-owned state from human-owned config unless your tooling can round-trip safely.

## What rollout pattern is safe when versions coexist?

Use read-old/write-old first, then read-old-and-new/write-new only after all writers are upgraded.

A concrete example: rename `timeout_ms` to `request_timeout_ms`.

- Release N: readers accept both keys; writers emit only `timeout_ms`.
- After every writer is upgraded: writers emit both keys and reject mismatched values.
- After the downgrade window closes: writers emit only `request_timeout_ms`; readers may still accept the old key for one release.

This is the same core idea Kubernetes uses for versioned custom resources: [CustomResourceDefinitions can serve multiple versions during migration](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). When representations differ, Kubernetes supports explicit conversion; when schemas do not change, the default `None` conversion strategy can serve different versions by changing `apiVersion`. The same Kubernetes documentation also says, ["It is perfectly safe for some clients to use the old version while others use the new version"](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/).

## What should you test before shipping?

Test compatibility as a matrix, not a unit test.

Checklist:

- Old binary reads new config without crashing.
- Old writer does not drop unknown fields you need to keep.
- New binary preserves absence versus explicit zero or false.
- New config can be rolled back safely, or rollback is explicitly unsupported.
- Round-trip tests compare old→new→old and new→old→new on golden files.
- Policy checks block unsafe changes such as renamed required fields or changed units.

**Should you add a schema version field?**  
Yes, when it selects a converter or blocks an unsafe downgrade. Do not rely on it alone; real compatibility still depends on reader and writer behavior.

**Should multiple services write the same canonical config during rollout?**  
Prefer one canonical writer or a separate migration tool. The more writers you have, the harder it is to enforce the upgrade boundary that keeps new state from being erased.

If you are planning a schema change this week, start by listing every reader and every writer, then decide which version must remain the writable shape until the rollout and rollback windows both close.

Reviewed: 2026-09-06

## Source references

- <https://json-schema.org/draft/2020-12/json-schema-core.html>
- <https://protobuf.dev/programming-guides/proto3/>
- <https://yaml.org/spec/1.2.2/>
- <https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/>
