Context
An endpoint decrements stock: read the row, check there is enough, write the new value. Every test passes, because every test sends one request.
Send fifty at once and the invariant breaks — you sell what you do not have. This is the bug that never reproduces locally and always reproduces on launch day.
Build the naive version first and prove it is broken. Only then fix it, and be able to say what your fix costs under contention.
Requirements
What must be true — the how is yours.
-
R1
Stock never goes below zero, whatever the concurrency.
rehearsed by D1
-
R2
A request that cannot be served is refused cleanly, not served wrongly and not left hanging.
rehearsed by D2
-
R3
Two concurrent operations touching the same rows cannot deadlock the service into failing every request.
rehearsed by D3
Technical guidance
- ▸Write the broken version first; a fix you cannot demonstrate is a guess.
- ▸Read-modify-write across two statements is where the update is lost.
- ▸Whatever you choose — a lock, an isolation level, an atomic write — it has a failure mode under contention. Know which one you bought.
Definition of done
All of them, or it isn't shipped.
-
○
Stock goes negative, or more units are sold than existed — and you have the number.
— not recorded yet
D1
-
○
Exactly ten succeed, the rest are refused with a deliberate status, and stock lands at zero.
— not recorded yet
D2
-
○
You can show whether the database aborts one with a deadlock error, and your service turns that into a retry or a clean refusal rather than a 500.
— not recorded yet
D3
-
○
The load script that breaks the naive version ships with the repo and runs from a clean clone.
— not recorded yet
nothing recorded yet — running the drills fills these in
spec v1.0 · drafted with AI, human-reviewed