Why concurrency is a different kind of problem
Why does ten times more throughput create a hundred times more conflict, and how should we think about correctness when many users act at once?
The failure mode changes
Think about the topics that come before concurrency in a database: how bytes sit on a disk, how rows are laid out, how data is encoded, how indexes find rows. All of them answer one question: "what does it cost to represent bytes and reach them?" You can read all of them as if the database had exactly one user. The way they go wrong is that the system is slow or expensive.
Concurrency control is the first topic where the number of actors is the subject, and the first where the failure mode is wrong, silently, and only sometimes. Two things change in a basic way.
Change 1: we judge a schedule, not a state
For storage questions you can point at bytes and ask whether they were laid out well. Here the thing being judged is a schedule: an ordering of operations taken from several transactions. A system is correct if every schedule it allows is equivalent to some serial schedule (one transaction at a time).
You cannot look at a database state and tell whether its concurrency control is correct. The bug lives in a history, not in a state. That is why this topic resists the debugging habits that everything below it taught you: there is often nothing wrong to see in the data afterwards. Two lost updates produce a perfectly normal-looking balance. It is just the wrong normal-looking balance.
Change 2: the cost is combinatorial, not physical
A lock acquisition is one CAS on a hash bucket, about 100 ns. A commit is one log write. Nothing here is expensive in the way a disk seek or a network shuffle is expensive. What is expensive is conflict. Conflict is a property of the workload and of how many transactions you insist on running at the same time. You cannot buy your way out with faster hardware, and that is new.
The arithmetic that governs this mechanism
This calculation is worth doing once, because everything later is built on it.
Step 1: how many transactions are in flight? Little's Law says that to sustain an arrival rate λ when each transaction stays in the system for W seconds, you must have
- L
- — average number of items inside the system (the concurrency)
- λ
- — arrival rate (items per second)
- W
- — average time an item stays in the system (residence time, including waiting)
transactions alive at the same moment. This is not a decision. It is an identity. Here it is a correctness constraint, not only a capacity one: the more transactions overlap, the more chances for them to disagree.
Step 2: how many conflicts? Suppose each of the L live transactions touches k items out of an effective key space of N items, and assume every item is equally likely.
- Two given transactions each have k items, so the chance that they share at least one item is about k²/N (k × k pairs of items, each pair equal with probability 1/N, when this number is small).
- There are about L²/2 pairs of live transactions.
So the expected number of conflicts at any moment is about
- live transactions (Little's Law: arrival rate × duration)
- items each transaction touches
- the effective key space (skew shrinks it)
- transactions per second and how long each holds its items
Data table
| Throughput λ (transactions per second) | series | Conflicts at any moment |
|---|---|---|
| 1K | N = 10⁶ items | 0.001 |
| 1.09K | N = 10⁶ items | 0.001 |
| 1.19K | N = 10⁶ items | 0.002 |
| 1.3K | N = 10⁶ items | 0.002 |
| 1.41K | N = 10⁶ items | 0.002 |
| 1.54K | N = 10⁶ items | 0.003 |
| 1.68K | N = 10⁶ items | 0.004 |
| 1.83K | N = 10⁶ items | 0.004 |
| 2K | N = 10⁶ items | 0.005 |
| 2.18K | N = 10⁶ items | 0.006 |
| 2.37K | N = 10⁶ items | 0.007 |
| 2.59K | N = 10⁶ items | 0.008 |
| 2.82K | N = 10⁶ items | 0.01 |
| 3.07K | N = 10⁶ items | 0.012 |
| 3.35K | N = 10⁶ items | 0.014 |
| 3.65K | N = 10⁶ items | 0.017 |
| 3.98K | N = 10⁶ items | 0.02 |
| 4.34K | N = 10⁶ items | 0.024 |
| 4.73K | N = 10⁶ items | 0.028 |
| 5.16K | N = 10⁶ items | 0.033 |
| 5.62K | N = 10⁶ items | 0.04 |
| 6.13K | N = 10⁶ items | 0.047 |
| 6.68K | N = 10⁶ items | 0.056 |
| 7.29K | N = 10⁶ items | 0.066 |
| 7.94K | N = 10⁶ items | 0.079 |
| 8.66K | N = 10⁶ items | 0.094 |
| 9.44K | N = 10⁶ items | 0.111 |
| 10.3K | N = 10⁶ items | 0.132 |
| 11.2K | N = 10⁶ items | 0.157 |
| 12.2K | N = 10⁶ items | 0.187 |
| 13.3K | N = 10⁶ items | 0.222 |
| 14.5K | N = 10⁶ items | 0.264 |
| 15.8K | N = 10⁶ items | 0.314 |
| 17.3K | N = 10⁶ items | 0.373 |
| 18.8K | N = 10⁶ items | 0.444 |
| 20.5K | N = 10⁶ items | 0.527 |
| 22.4K | N = 10⁶ items | 0.626 |
| 24.4K | N = 10⁶ items | 0.745 |
| 26.6K | N = 10⁶ items | 0.885 |
| 29K | N = 10⁶ items | 1.05 |
| 31.6K | N = 10⁶ items | 1.25 |
| 34.5K | N = 10⁶ items | 1.49 |
| 37.6K | N = 10⁶ items | 1.77 |
| 41K | N = 10⁶ items | 2.1 |
| 44.7K | N = 10⁶ items | 2.49 |
| 48.7K | N = 10⁶ items | 2.96 |
| 53.1K | N = 10⁶ items | 3.52 |
| 57.9K | N = 10⁶ items | 4.19 |
| 63.1K | N = 10⁶ items | 4.98 |
| 68.8K | N = 10⁶ items | 5.91 |
| 75K | N = 10⁶ items | 7.03 |
| 81.8K | N = 10⁶ items | 8.35 |
| 89.1K | N = 10⁶ items | 9.93 |
| 97.2K | N = 10⁶ items | 11.8 |
| 106K | N = 10⁶ items | 14 |
| 115K | N = 10⁶ items | 16.7 |
| 126K | N = 10⁶ items | 19.8 |
| 137K | N = 10⁶ items | 23.5 |
| 150K | N = 10⁶ items | 28 |
| 163K | N = 10⁶ items | 33.3 |
| 178K | N = 10⁶ items | 39.5 |
| 194K | N = 10⁶ items | 47 |
| 211K | N = 10⁶ items | 55.8 |
| 230K | N = 10⁶ items | 66.4 |
| 251K | N = 10⁶ items | 78.9 |
| 274K | N = 10⁶ items | 93.7 |
| 299K | N = 10⁶ items | 111 |
| 325K | N = 10⁶ items | 132 |
| 355K | N = 10⁶ items | 157 |
| 387K | N = 10⁶ items | 187 |
| 422K | N = 10⁶ items | 222 |
| 460K | N = 10⁶ items | 264 |
| 501K | N = 10⁶ items | 314 |
| 546K | N = 10⁶ items | 373 |
| 596K | N = 10⁶ items | 444 |
| 649K | N = 10⁶ items | 527 |
| 708K | N = 10⁶ items | 626 |
| 772K | N = 10⁶ items | 745 |
| 841K | N = 10⁶ items | 885 |
| 917K | N = 10⁶ items | 1,050 |
| 1M | N = 10⁶ items | 1,250 |
| 1K | effective N = 10⁴ (skewed keys) | 0.125 |
| 1.09K | effective N = 10⁴ (skewed keys) | 0.149 |
| 1.19K | effective N = 10⁴ (skewed keys) | 0.177 |
| 1.3K | effective N = 10⁴ (skewed keys) | 0.21 |
| 1.41K | effective N = 10⁴ (skewed keys) | 0.249 |
| 1.54K | effective N = 10⁴ (skewed keys) | 0.296 |
| 1.68K | effective N = 10⁴ (skewed keys) | 0.352 |
| 1.83K | effective N = 10⁴ (skewed keys) | 0.419 |
| 2K | effective N = 10⁴ (skewed keys) | 0.498 |
| 2.18K | effective N = 10⁴ (skewed keys) | 0.591 |
| 2.37K | effective N = 10⁴ (skewed keys) | 0.703 |
| 2.59K | effective N = 10⁴ (skewed keys) | 0.835 |
| 2.82K | effective N = 10⁴ (skewed keys) | 0.993 |
| 3.07K | effective N = 10⁴ (skewed keys) | 1.18 |
| 3.35K | effective N = 10⁴ (skewed keys) | 1.4 |
| 3.65K | effective N = 10⁴ (skewed keys) | 1.67 |
| 3.98K | effective N = 10⁴ (skewed keys) | 1.98 |
| 4.34K | effective N = 10⁴ (skewed keys) | 2.35 |
| 4.73K | effective N = 10⁴ (skewed keys) | 2.8 |
| 5.16K | effective N = 10⁴ (skewed keys) | 3.33 |
| 5.62K | effective N = 10⁴ (skewed keys) | 3.95 |
| 6.13K | effective N = 10⁴ (skewed keys) | 4.7 |
| 6.68K | effective N = 10⁴ (skewed keys) | 5.58 |
| 7.29K | effective N = 10⁴ (skewed keys) | 6.64 |
| 7.94K | effective N = 10⁴ (skewed keys) | 7.89 |
| 8.66K | effective N = 10⁴ (skewed keys) | 9.37 |
| 9.44K | effective N = 10⁴ (skewed keys) | 11.1 |
| 10.3K | effective N = 10⁴ (skewed keys) | 13.2 |
| 11.2K | effective N = 10⁴ (skewed keys) | 15.7 |
| 12.2K | effective N = 10⁴ (skewed keys) | 18.7 |
| 13.3K | effective N = 10⁴ (skewed keys) | 22.2 |
| 14.5K | effective N = 10⁴ (skewed keys) | 26.4 |
| 15.8K | effective N = 10⁴ (skewed keys) | 31.4 |
| 17.3K | effective N = 10⁴ (skewed keys) | 37.3 |
| 18.8K | effective N = 10⁴ (skewed keys) | 44.4 |
| 20.5K | effective N = 10⁴ (skewed keys) | 52.7 |
| 22.4K | effective N = 10⁴ (skewed keys) | 62.6 |
| 24.4K | effective N = 10⁴ (skewed keys) | 74.5 |
| 26.6K | effective N = 10⁴ (skewed keys) | 88.5 |
| 29K | effective N = 10⁴ (skewed keys) | 105 |
| 31.6K | effective N = 10⁴ (skewed keys) | 125 |
| 34.5K | effective N = 10⁴ (skewed keys) | 149 |
| 37.6K | effective N = 10⁴ (skewed keys) | 177 |
| 41K | effective N = 10⁴ (skewed keys) | 210 |
| 44.7K | effective N = 10⁴ (skewed keys) | 249 |
| 48.7K | effective N = 10⁴ (skewed keys) | 296 |
| 53.1K | effective N = 10⁴ (skewed keys) | 352 |
| 57.9K | effective N = 10⁴ (skewed keys) | 419 |
| 63.1K | effective N = 10⁴ (skewed keys) | 498 |
| 68.8K | effective N = 10⁴ (skewed keys) | 591 |
| 75K | effective N = 10⁴ (skewed keys) | 703 |
| 81.8K | effective N = 10⁴ (skewed keys) | 835 |
| 89.1K | effective N = 10⁴ (skewed keys) | 993 |
| 97.2K | effective N = 10⁴ (skewed keys) | 1,180 |
| 106K | effective N = 10⁴ (skewed keys) | 1,400 |
| 115K | effective N = 10⁴ (skewed keys) | 1,670 |
| 126K | effective N = 10⁴ (skewed keys) | 1,980 |
| 137K | effective N = 10⁴ (skewed keys) | 2,350 |
| 150K | effective N = 10⁴ (skewed keys) | 2,800 |
| 163K | effective N = 10⁴ (skewed keys) | 3,330 |
| 178K | effective N = 10⁴ (skewed keys) | 3,950 |
| 194K | effective N = 10⁴ (skewed keys) | 4,700 |
| 211K | effective N = 10⁴ (skewed keys) | 5,580 |
| 230K | effective N = 10⁴ (skewed keys) | 6,640 |
| 251K | effective N = 10⁴ (skewed keys) | 7,890 |
| 274K | effective N = 10⁴ (skewed keys) | 9,370 |
| 299K | effective N = 10⁴ (skewed keys) | 11.1K |
| 325K | effective N = 10⁴ (skewed keys) | 13.2K |
| 355K | effective N = 10⁴ (skewed keys) | 15.7K |
| 387K | effective N = 10⁴ (skewed keys) | 18.7K |
| 422K | effective N = 10⁴ (skewed keys) | 22.2K |
| 460K | effective N = 10⁴ (skewed keys) | 26.4K |
| 501K | effective N = 10⁴ (skewed keys) | 31.4K |
| 546K | effective N = 10⁴ (skewed keys) | 37.3K |
| 596K | effective N = 10⁴ (skewed keys) | 44.4K |
| 649K | effective N = 10⁴ (skewed keys) | 52.7K |
| 708K | effective N = 10⁴ (skewed keys) | 62.6K |
| 772K | effective N = 10⁴ (skewed keys) | 74.5K |
| 841K | effective N = 10⁴ (skewed keys) | 88.5K |
| 917K | effective N = 10⁴ (skewed keys) | 105K |
| 1M | effective N = 10⁴ (skewed keys) | 125K |
Useful work rises in a straight line. Conflict rises with the square. The two lines cross, and that crossing is why the throughput curve of every transactional system turns over (it goes up, peaks and then falls) instead of flattening.
This is exactly what Tomas saw. Going from 800 to 8,000 orders per second multiplied the number of transactions in flight by ten and the number of colliding pairs by about a hundred.
The same square appears elsewhere
This quadratic is not new. In scalability theory (the Universal Scalability Law) there is a "coherency" term that grows as N(N−1): the cost of every worker needing to agree with every other worker. You may know it from false sharing (several CPU cores fighting for one cache line) and from incast (many servers answering one server at the same time). For transactions the pairwise term is literal, not an analogy: its coefficient is the probability that two concurrent actors touch the same item.
The assumption nobody can check: uniform access
The formula needs N, and this is the assumption the whole topic makes and cannot verify. The arithmetic above assumes every item is equally likely to be touched. Real access patterns are skewed (statisticians say Zipfian: a few keys get most of the traffic). So the effective N is not the number of rows you store. It is smaller, often by two or three orders of magnitude (100 to 1,000 times).
Independence is assumed everywhere and true almost nowhere. Who pays? The hot key, the popular account, the one partition that every writer touches on the hour. For Kestrel: 50,000 seats exist, but if 90% of buyers want 200 floor seats, the effective N for most buyers is closer to 200 than to 50,000.