PATTERN Cited by 1 source
Compatibility-mode client transition¶
Problem¶
A stateful datastore ships a new major version. The operations team wants to upgrade the cluster (security, performance, new features). But a cluster-version upgrade usually requires a client-library upgrade — the old client may not speak the new server's wire protocol or response shape. For a large application (in Zalando's case, 443k LOC / 846 files of Scala), the client upgrade is a multi-engineer-month rewrite.
If the client upgrade must complete before the cluster upgrade, the application team is the critical path for a security / SLA / feature decision the operations team wants to make now. That's an unsustainable coupling.
Solution¶
Use a compatibility primitive shipped by the datastore — almost always a header on the client request — that instructs the new cluster to preserve old response semantics. The old client now works against the new cluster. Then:
- Upgrade the cluster first, keeping the old client running with the compat header set.
- Gradually migrate application call-sites to the new client, one at a time, with both clients live in the application process.
- Drop the old client once no call-site references it.
The primitive decouples the two upgrades: the cluster upgrade no longer blocks on the application rewrite.
Canonical instance¶
sources/2023-11-19-zalando-migrating-from-elasticsearch-7-to-8 — Zalando's Origami application used Elasticsearch compat mode to run the 7.17 HLRC against fresh ES 8.x clusters during the cluster Blue/Green migration, then separately migrated Origami's 443k LOC / 846 files from HLRC to the Elasticsearch Java API Client. Without the compat mode, the cluster upgrade would have been blocked on the application rewrite; the cluster was already multi-cluster Blue/Green migrated while the Origami client migration continued as a parallel track.
Shape of the primitive¶
Versioning primitives that enable this pattern:
| Provider | Primitive |
|---|---|
| Elasticsearch | Accept: application/vnd.elasticsearch+json;compatible-with=7 header |
| Stripe | Stripe-Version: 2023-10-16 header |
| GitHub REST | Accept: application/vnd.github.v3+json header |
| Kubernetes | apiVersion: field + per-group / per-version API |
| Protobuf | Field numbers + proto3 wire compat |
Each works the same way: the server knows the client's version expectation and serves accordingly. The server's versioning discipline is what makes the pattern viable. Without server-side support for multiple concurrent protocol versions, the pattern is unavailable.
Trade-offs¶
- Code velocity gain is large — the application team no longer blocks the cluster upgrade.
- Technical debt accumulates — the old client + compat header is now a known-to-be-retired codepath. Gradual migration needs to actually happen.
- Testing surface grows — the application now runs against the new cluster in two modes (old-client-compat and new-client-native). Both must pass. See patterns/multi-version-testcontainers-parity-suite.
- Some new features are not usable in compat mode — e.g. ES 8's approximate kNN has no HLRC DSL, so the migration off HLRC to the Java API Client remains the prerequisite for new-feature adoption.
Related¶
- concepts/elasticsearch-compatibility-mode — the specific Elasticsearch primitive.
- concepts/backward-compatibility — the broader discipline the primitive instantiates.
- patterns/multi-version-testcontainers-parity-suite — the testing discipline that catches compat-mode regressions before cutover.
Gaps¶
- No Zalando-disclosed duration of the Origami HLRC→Java-client migration.
- No disclosure of what fraction of Origami was migrated at cutover vs still on compat mode.