THIS EXPLANATION
THE ROOM
CDA·193 Computing, Data & AI 6 MIN · 8 STATIONS

Running both and comparing

A Socratic walk-through of running both and comparing — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

How do you prove a replacement behaves like the original when nobody knows exactly what the original does?

Testing normally starts from a statement of what ought to happen. You write the expected result, you run the code, you compare. That works because someone knows the answer in advance.

Now take the case where nobody does. The system being replaced has run for fifteen years; its documented behaviour is a subset of its real behaviour, and some of what it does was never decided by anyone. You cannot write the expected results, because the expected results are precisely what you are trying to discover. So what evidence is even available?

b

Reasoning it through

REASONING #

There is one source of truth still in the room: the old system itself, running, with real requests arriving at it. If it cannot be described, it can still be observed. And that suggests a move — stop trying to state the specification and start sampling it.

So: take each real request, send it to both systems, return the old system's answer to the user, and record the new system's answer for comparison. Nothing the new one produces is used. That single property is what makes this safe enough to run on all traffic at once: a divergence is an observation, not an incident. GitHub's Scientist library packaged exactly this shape in 2016, and the pattern is variously called a parallel run, shadow traffic, or differential testing.

Two requirements decide whether the experiment means anything.

The first is side effects. The new path must not send the email, charge the card, post the ledger entry, or consume the next invoice number. If it does, you have not built a test — you have built a duplicate transaction, and you will discover this from a customer. So the shadow path must run against a sandbox, write to a shadow store, or have its outbound effects stubbed, and that plumbing is usually more work than the comparison itself.

The second is inputs. Both must see the same request and the same state. If the new system reads its own replicated copy of the data, replication lag alone produces divergences that mean nothing about the logic. Time is the sharpest version: two systems reading their own clocks a few hundred milliseconds apart will disagree about anything straddling a boundary.

Now the comparison, which is where the naive version of this idea quietly fails. Equality is the wrong test. Most differences are benign: timestamps, generated identifiers, the ordering of a collection, float formatting, whitespace, a field the new system populates and the old one leaves empty. So you must normalise before comparing — and notice what a normalisation rule really is. It is a claim that some difference does not matter. That growing list of claims is one of the most valuable artefacts the migration produces, and also the easiest place to hide a real bug, which is why the rules deserve review and why raw pairs should be sampled and kept rather than only their verdicts.

Then the harder problem, and it is a genuinely uncomfortable one. The old system is the reference, so its bugs are the standard. A divergence tells you the two differ; it does not tell you which is right. Every difference has to be triaged into three buckets — the new one is wrong, the old one is wrong, or neither matters — and only the first is a defect. The middle case is a decision, not a finding: someone with domain knowledge must decide whether to reproduce the old behaviour faithfully or to record an accepted, dated difference that says "we are deliberately changing this". Undocumented, it will resurface as a bug report in six months.

Finally, the limitation no engineering removes. Production traffic covers what actually happens, weighted by how often it happens. Year-end processing, a refund of a refund, the one customer with the odd configuration — these may not occur during the whole comparison window. A parallel run bounds risk in proportion to frequency rather than importance, which is backwards for the rare-and-catastrophic cases. The complements are replaying historical data, especially period-end batches, and targeted tests for the rare paths you already know about; neither covers the ones you do not.

c

The analogy

THE ANALOGY #
THE FIGURE

Think of a trainee flying alongside the captain, hands on a second set of controls that are disconnected. Every leg, the trainee decides what to do, and afterwards you compare their inputs with the captain's. No passenger is at risk, and after a hundred flights you know a great deal about whether the trainee flies like the captain.

WHERE IT BREAKS DOWN

A trainee can be put in a simulator and given an engine fire on purpose — whereas a parallel run only ever sees the weather that happens, so the emergency you most want the replacement to handle correctly is precisely the one the comparison is least likely to contain.

d

Clarifying the model

THE MODEL #

Three refinements.

First, this is evidence, not proof. What a clean parallel run establishes is that on the distribution of traffic observed, over the window observed, after the normalisations declared, the two systems agreed. Each of those clauses is a real limit, and stating them is what makes the result honest rather than reassuring.

Second, the exit criterion should not be zero differences. It should be a difference rate that is fully explained — every remaining class understood, categorised, and either accepted with a decision or fixed — held stable across a window long enough to include the business cycle, which usually means a month-end and often a year-end. A team chasing zero tends to widen the normalisation rules until the number reaches zero, which is the same as switching the test off.

Third, a misconception worth correcting: a parallel run is not a rehearsal for cutover. It exercises the comparison, not the switch. Whether traffic can actually be moved — and moved back — is a separate question answered by routing a small share of live traffic to the new system and watching real users, which is a different risk with a different failure mode.

e

A picture of it

THE PICTURE #
Running both and comparing
Running both and comparing Start at the centre and read each branch as one bucket a divergence can fall into; sorting every difference into exactly one of them is the daily work of a parallel run. Only the second branch is a defect in the replacement. The first is handled by normalisation rules, which should be reviewed rather than accumulated. The third is a decision for someone with domain knowledge, recorded and dated. The fourth is the one to check first: those differences say nothing about the logic at all -- they mean the experiment was not set up fairly, and until they are gone every other count is inflated. {"generator":"[email protected]","source":"../Socrates/.diagram-cache/_src/running-both-and-comparing.md","sourceIndex":1,"sourceLine":4,"sourceHash":"2a806679a7d68d75102a84f4dc34c3edb1f726a428164d8d88fec4a8f413cdff","diagramType":"mindmap","layoutVariant":"source","repairedDuplicateIds":[{"original":"mermaid-2a806679a7d68d75-0-node_1","replacement":"mermaid-2a806679a7d68d75-0-node_1--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_2","replacement":"mermaid-2a806679a7d68d75-0-node_2--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_3","replacement":"mermaid-2a806679a7d68d75-0-node_3--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_4","replacement":"mermaid-2a806679a7d68d75-0-node_4--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_5","replacement":"mermaid-2a806679a7d68d75-0-node_5--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_6","replacement":"mermaid-2a806679a7d68d75-0-node_6--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_7","replacement":"mermaid-2a806679a7d68d75-0-node_7--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_8","replacement":"mermaid-2a806679a7d68d75-0-node_8--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_9","replacement":"mermaid-2a806679a7d68d75-0-node_9--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_10","replacement":"mermaid-2a806679a7d68d75-0-node_10--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_11","replacement":"mermaid-2a806679a7d68d75-0-node_11--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_12","replacement":"mermaid-2a806679a7d68d75-0-node_12--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_13","replacement":"mermaid-2a806679a7d68d75-0-node_13--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-node_14","replacement":"mermaid-2a806679a7d68d75-0-node_14--duplicate-2"},{"original":"mermaid-2a806679a7d68d75-0-gradient","replacement":"mermaid-2a806679a7d68d75-0-gradient--duplicate-2"}],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":1032,"height":590},"qa":{"passed":true,"findings":[]}} Why the two outputsdiffer Benign noise Timestamps Generated ids Result ordering Rounding New system wrong Missing edge case Misread rule Old system wrong Long standing bug Accepted difference Not comparable Stale replica read Side effect leaked

How to readStart at the centre and read each branch as one bucket a divergence can fall into; sorting every difference into exactly one of them is the daily work of a parallel run. Only the second branch is a defect in the replacement. The first is handled by normalisation rules, which should be reviewed rather than accumulated. The third is a decision for someone with domain knowledge, recorded and dated. The fourth is the one to check first: those differences say nothing about the logic at all — they mean the experiment was not set up fairly, and until they are gone every other count is inflated.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

When the specification is unavailable, the running system is the only oracle you have, and comparing against it converts an unanswerable question into a sampling problem. What makes that work is not the comparison but the discipline around it: no side effects on the shadow path, identical inputs, normalisation rules treated as reviewable claims, and every divergence triaged rather than counted — and what it can never give you is confidence about the rare cases that did not happen to arrive while you were watching.

g

Where to go next

ONWARD #
  • Traffic replay from logs, and reproducing the state a request originally saw.
  • Canary releases, which answer what a parallel run cannot: can traffic actually be moved.
  • Property-based testing, for the rare paths production traffic never exercises.
h

Key terms

TERMS #
TermWhat it means
Parallel runoperating old and new on the same inputs at once, using only the old system's output.
Shadow trafficproduction requests copied to a system whose responses are discarded.
Differential testingcomparing two implementations on the same inputs, treating disagreement as the signal.
Normalisation rulea declared claim that some class of difference is irrelevant to the comparison.
Oraclewhatever supplies the correct answer a test compares against; here, the legacy system itself.
Accepted differencea divergence deliberately kept, recorded with the reason and date it was agreed.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4