ContentsThe library

Doing Two Things at Once

The Update That Vanished

Adding a counter looks like one action and is three. Two workers doing it at the same time can both read the same value, and one of the two additions disappears with nothing broken.

Here is the shortest possible version of the whole subject. A page view counter

is stored somewhere. Two requests arrive at the same instant. Each one adds one.

Afterwards the counter has gone up by one.

Nothing crashed. No error was logged. No component was unavailable. The code that

did it is three lines long and obviously correct when you read it.

One operation, three steps

Adding one to a stored number is never one action. It is fetch the current

value, compute a new value, store the new value. Those are three separate things

with gaps between them, and the worker can be interrupted in either gap.

FIG 1Where the gap is, and what can arrive in it
There is no faulty step in this diagram. Each worker did precisely what it was told, in order, with no interruption to its own logic. The defect is in the arrangement rather than in either worker, which is why reading one worker's code carefully will never reveal it.
FIG 2Two workers incrementing the same counter, step by step
stepworker one's held valueworker two's held valuethe stored counterincrements requested so farwhat happened
100410Starting state. Nobody has read anything yet.
2410411Worker one reads. It now holds 41, and the stored value is still 41.
34141412Worker two reads, in the gap. It holds 41 as well, which is true at this instant and will not be true for long.
44241422Worker one writes its result. The stored counter is correct right now.
54242422Worker two writes its result, computed from a value that is now stale. Two requests, one increment.
5 steps
Follow the third column. It is correct at every single step, including the last one, in the sense that it always holds a value somebody computed honestly. What is lost is not a value but an event, and no record of the loss exists anywhere.

Why you will not find it in testing

The window between the read and the write is small. For an in-memory counter it

may be a few tens of nanoseconds; for a counter in a database behind a network

call it may be a few milliseconds. Two workers have to arrive inside the same

window for anything to go wrong.

FIG 3Roughly how often two workers will collide
the expected number of colliding pairs in the period
how many times the operation runs in the period
the width of the dangerous window, from the read to the write
the length of the period the operations are spread over
the number of pairs of operations that could possibly collide
the chance that any one pair lands close enough together
The first factor grows with the square of the traffic and the second does not shrink, which is why a race that was invisible at a hundred operations an hour becomes a daily event at ten thousand. Nothing about the code changed; only the first factor did.
FIG 4Expected collisions in an hour, against how wide the window is
0.0030.0060.0090.00120.000.0125.0250.0375.0500.0window width, in microseconds
a thousand operations an hourten thousand operations an hour
Both lines pass through the origin, which is the reassuring part, and both are straight, which is the part that gets people. There is no threshold below which a race is safe. There is only a rate at which it is currently too rare to have been noticed yet.

A test suite runs the operation a few hundred times, usually in sequence, and

passes. The system then runs it ten million times a day with real concurrency,

and the counter drifts by a few tenths of a percent a week. By the time anybody

investigates, the evidence is a number that is slightly wrong and no log line

anywhere that says why.

The same shape, wearing different clothes

Counters are the teaching example because they are easy to draw. The pattern is

much broader, and it covers anything that checks a condition and then acts on

what it saw.

FIG 5Four everyday operations with the same gap in them
how often it appears, onhow bad the failure is, how hard it is to noticehow easily the database
add one to a counter4234
create the record if it 3443
spend from a balance if 3324
claim the next job from 2442
The marked cell is the one that funds the incident reviews. Two workers both check that no account exists for an address, both find none, and both create one, after which every later operation has to decide which of the two duplicates is the real account.

In each of these, two workers look at the world, both conclude that an action is

safe, and both act. The check was true when it was made. It stopped being true

before the action, and nothing told anybody.

The machine has its own ideas

One more complication, because it surprises people who have understood

everything above. Even without any interleaving of your steps, a worker can read

a value that another worker has already changed.

Each processor keeps its own copy of recently used memory. A write made on one

processor becomes visible to another only when the machine arranges for it to be,

and both the compiler and the processor are permitted to reorder operations that

appear independent. Two assignments made in one order can become visible to

another worker in the opposite order.

This is why every serious language now publishes a memory model, which is a

precise statement of when one worker's write is guaranteed to be seen by another.

The practical consequence is simple enough to remember: unless you have used

something the language promises will synchronise, you have no promise at all

about what another worker can see.

What to hold on to

An operation that reads and then writes has a gap in it, and a second worker

inside that gap makes the result wrong without breaking anything. The failure is

rare in proportion to how narrow the gap is, which means it survives testing and

arrives with traffic. The same shape appears wherever a check is followed by an

action. And separately from all of that, a worker is not guaranteed to see

another worker's writes at all unless something was done to make it so.

Recap

  • Almost every operation that looks atomic is really a read, then a change, then a write, and the gap between the read and the write is the window in which another worker can ruin the result.
  • These failures are rare by construction, because the window is microseconds wide, which means they survive testing, appear under load, and cannot be reproduced on demand.
  • The pattern is not limited to counters. Anything that checks a condition and then acts on it has the same gap, which is where duplicate records and negative balances come from.

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

NextThe Part Only One May Enter →

The rest of this course

  1. 01The Update That Vanishedyou are here
  2. 02A Door That Admits Oneopening only
  3. 03The Bill Arrives as Latencyopening only
  4. 04Everybody Holding, Nobody Movingopening only
  5. 05Try, Check, Try Againopening only
  6. 06The Promise Is Weaker Than You Thinkopening only
  7. 07The Ceiling You Cannot Buyopening only
  8. 08The Problem You Decide Not to Haveopening only

Read alongside