Module O-4·30 min read

Blue-green, canary, feature flags, expand/contract migrations — and why the danger on a multi-terabyte table is lock queueing, not the rewrite.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module O-4 — Deploying and Migrating Without Downtime

What this module covers: The decoupling that makes everything else safe — deploy is not release — and feature flags as the mechanism. Rolling, blue-green, canary and shadow deployments with their real trade-offs, and the constraint every one of them imposes and everyone forgets: two versions run simultaneously, so every change must be compatible with the version before it. Expand/contract as the only schema-change pattern that is reversible at each step, worked through a column rename. Then the centrepiece, which is Postgres-specific and contradicts the common belief: a multi-terabyte migration does not take an outage because it rewrote the table — it takes an outage because an ACCESS EXCLUSIVE request queues behind a long-running read and every subsequent query queues behind it, so a two-millisecond DDL statement blocks every reader for the duration of somebody's report. The one setting that prevents most of these outages. Which operations are metadata-only and which rewrite. CREATE INDEX CONCURRENTLY and its sharp edges. View-based migration tooling, and why the well-known MySQL shadow-table tools are the wrong reach on Postgres. And backfills that do not take the database down.


Deploy Is Not Release

The single most useful reframing in this module:

  • Deploy — the new code is running in production.
  • Release — users are exposed to the new behaviour.

Coupling them means every deploy is a product event: it must happen when the team is available, when traffic is low, when marketing is ready, and any problem requires a rollback of everything in that build.

Decoupling them, with feature flags, means you deploy the code dark at 10 a.m., verify it is healthy, and turn it on at 2 p.m. with everyone watching — and turning it off is a configuration change measured in seconds rather than a deploy measured in minutes. That single property is what makes the rest of this module's risk manageable, because your fastest rollback stops being a redeploy.

Analogy: a stage production. Deploying is building the set, hanging the lights and running the technical rehearsal — all of it done in the theatre, with the curtain down, while the audience is elsewhere. Releasing is raising the curtain. Doing both in one motion means the first time anyone discovers the lighting rig is wrong is with four hundred people watching, and the only remedy is to stop the show. A theatre that separates them can also drop the curtain on one scene without cancelling the evening, which is what a kill switch is.


Four Strategies, and the Constraint All of Them Share

Rollback speedCostRisk detectionNeeds schema compatible with both versions
Rollingminutes (redeploy)noneafter full rolloutyes
Blue-greenseconds (switch back)2× capacityafter switch, all at onceyes — they share a database
Canaryseconds (shift traffic)smallduring rollout, on a sliceyes
Shadow / darkn/a — no user trafficduplicate computebefore any user is affectedyes, and side effects must be safe

Rolling replaces instances gradually and is the default almost everywhere. Cheap, simple, and slow to roll back because it is another rollout.

Blue-green runs two complete environments and switches traffic at the load balancer. Rollback is a switch back, which is the fastest available. The costs: double capacity during the switch, and — the point that matters — it does not solve database migrations, because both environments share one database. The schema must work with both versions regardless, so blue-green buys you fast code rollback and nothing at all on the data side.

Canary sends a small traffic fraction to the new version and compares metrics before ramping. It is the best risk detection available because problems are found on 1% of users instead of 100%. Two real traps: at low traffic, 1% for ten minutes is not enough events to be statistically meaningful, so you conclude "no difference" from noise; and a user whose requests bounce between versions can see inconsistent behaviour, which needs either session affinity or genuine version compatibility.

Shadow (dark) traffic duplicates real requests to the new version and discards the responses — excellent for validating performance against production traffic shape, and dangerous unless the path is read-only or the side effects are genuinely idempotent, because otherwise you have just double-charged every customer (Module P-16).

The N−1 compatibility rule

Every strategy above has one moment in common: both versions are running at the same time. During a rolling deploy that lasts minutes; during a canary it can last hours; during a blue-green switch, requests in flight span both.

So the rule that governs all schema and contract changes:

Version N must be compatible with version N−1, in both directions, for everything they share.

Which means:

  • API responses: additive only. Removing or renaming a field breaks the old client still running.
  • API requests: the new version must accept the old shape, because old instances are still generating it.
  • Message and event formats: tolerant readers, additive changes, and a schema registry that rejects incompatible changes (Module P-23).
  • Cache entries: a changed value shape needs a versioned key, or the new code reads an old-format entry and crashes (Module P-13).
  • Session and cookie data: the same problem, with a longer tail.
  • The database schema: both versions must be able to read and write it, which is the whole of expand/contract below.

Almost every "the deploy broke production for four minutes and then healed" incident is a violation of this rule.


Feature Flags, and the Debt They Are

Four kinds, with different lifetimes:

KindLifetimeExample
Release toggledays — delete itnew checkout flow
ExperimentweeksA/B on ranking
Operational togglepermanentkill switch for recommendations (Module A-7)
Permission / entitlementpermanententerprise-only feature

Release toggles are debt with a due date. Each one doubles the number of code paths, and n flags give 2ⁿ combinations that nobody tests. Track flag age, alert on flags older than a threshold, and treat removing them as part of the work rather than a follow-up nobody schedules. A codebase with two hundred permanent release toggles has branching that no one can reason about, which is a worse position than shipping without flags.

And the flag system must not be a hard dependency (Module A-7): cache values locally with a generous TTL and compile in defaults, so an unreachable flag service means "last known values," never "no features."


Schema Migrations: Expand/Contract Is the Only Reversible Pattern

The pattern, four phases, each independently deployable and reversible except the last:

text

That last line is Module A-3's pivot transaction, arriving in a new setting: everything up to the contract can be rolled back, and after it cannot, so the contract step happens only when you are certain — usually days or weeks later, not in the same release.

Worked: renaming a column

The tempting one-liner destroys production:

sql

Every running instance of the previous version references email, and there is no moment when both names exist. The correct sequence is six steps across several deploys:

text

Tedious, and every step is safe with both versions running. Note that steps 2, 4 and 5 are separate deploys deliberately: collapsing 4 and 5 means a rollback to step 4's code finds rows written only to the new column.

Two more common cases

Adding a NOT NULL column — do not add the constraint directly, because validating it requires a full table scan under a strong lock:

sql

The NOT VALID then VALIDATE split is the technique to remember: it separates the lock from the scan, so neither is held long and strong at the same time.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.