One entry takes thousands of read-modify-write attempts per second: how do a server-side edit, declare-and-retry, and an expiring claim each degrade?
answer
- each remedy spends a different currency
- wasted attempts versus waiting
- attempts-per-success is the metric
- hold time is the throughput ceiling
- the deadline must outlast the work
basics
~10 sThey spend different currencies. A server-side in-place update stays one round trip and never conflicts. Declare-and-retry turns most attempts into wasted work. An expiring claim serialises callers, so hold time becomes the throughput ceiling.
solid answer
~50 sAll three are correct at low contention, so the interesting question is what each does when the entry is hot. A **server-side in-place update** never conflicts — the change happens where the value is, one round trip, and the cost shows up as queueing at the entry rather than as failed attempts. **Declare-and-retry** — a version token, or a declared read set that abandons a group — is the one that falls apart: as contention rises, most attempts are refused, each having spent a read and a write, and some callers lose repeatedly while others get through. An **expiring claim** converts the race into a queue: throughput is roughly one holder per hold time, waiting callers burn their own deadlines, and a claim shorter than the work it guards gives you two holders who each believe they are alone. Choose by what degrades acceptably, not by what is theoretically correct.
go deeper
Recall that all three approaches are correct when nobody else is touching the entry, and that the differences only appear when many callers want the same entry at the same time.
Explain the mechanism of each cost: a refused attempt has already spent two round trips, and a claim lets only one caller work at a time, so its hold time sets the ceiling on updates per second.
Show that you have watched this happen: name the numbers you would look at — attempts per success, server time per operation, throughput against caller count — and say which remedy you would switch to and on what signal.
Ask whether the entry should be the coordination point at all. Decide what accuracy the value really needs, whether the update must be synchronous, and whether a tier with no isolation level and no rollback should own this invariant.
## Why contention is the whole question At one attempt a second, all three remedies work and nothing distinguishes them. The interview question is what happens at ten thousand. Each remedy has a different currency: one spends server time, one spends wasted round trips, one spends waiting. Knowing which currency you are spending is the difference between a design that degrades and one that falls over. | remedy | what a losing caller does | throughput as contention rises | where it stops existing | |---|---|---|---| | server-side in-place update | nothing — there is no loser | flat, bounded by server time at the entry | where the server cannot interpret the value | | declare-and-retry | re-reads and attempts again | falls; most attempts produce nothing | nowhere, but the cost becomes unacceptable | | expiring claim | waits, then gives up | roughly one holder per hold time | where the work outlasts the deadline | ## Server-side in-place update The change is expressed as something the server can do itself, so nothing crosses the network between reading and writing and there is no gap to lose. Its behaviour under contention is the best of the three: - Every attempt succeeds; there is no retry path to tune and no failure mode to explain. - The cost appears as **queueing at the entry** — under one-operation-at-a-time execution every caller waits behind the operation in progress; under per-entry locking they contend for that entry's lock while other entries proceed. Either way the operations are short, so the queue drains. - Its limits are real: the server must interpret the value, and the change must be one the server knows how to make. A decision that depends on what was read — "increase this only if the other field is below a threshold" — is not expressible as an in-place update, and needs a short submitted program instead, which is a different leaf's subject. ## Declare-and-retry This is the remedy that people reach for first and that contention punishes hardest. The caller either reads a version token with the value and presents it on write, or names the entries it read before submitting a group that is abandoned if one of them changed. - Each lost attempt has already spent a round trip to read, work to compute, and a round trip to be refused. **Under heavy contention most of the traffic to that entry is attempts that will not land.** - The system is not deadlocked and never reports an error — callers are busy and making no progress, which looks like ordinary load. - The distribution is unfair: a caller with a slow compute step between its read and its write loses to faster callers repeatedly, so the tail latency is far worse than the average suggests. - Adding retries makes it worse, not better: more attempts against the same entry means more refusals. The practical rules that follow are: **bound the attempts**, decide explicitly what happens at the bound, and instrument attempts-per-success — that ratio is the health metric for this remedy, and it is the number that tells you when to switch. ## The expiring claim A claim converts a race into a queue. One caller creates a marker entry only if none exists, does the work, and releases it; everyone else waits. - Throughput is bounded by **one holder per hold time**, so if the protected cycle takes five milliseconds, the entry supports roughly two hundred updates a second no matter how many callers there are. - The cost is wasted waiting rather than wasted attempts. That is the right trade when the protected work is expensive — a large document to parse, a call to another system inside the cycle — because losing that work repeatedly is worse than queueing for it. - The deadline is the trap. It exists so that a caller that dies does not block the entry forever, which means a claim whose deadline is shorter than the work it guards expires while the holder is still working, a second caller takes it, and both proceed believing they are alone. That is the original race with more moving parts and a false sense of safety. - Waiting callers are consuming their own request timeouts, so a claim held under load pushes latency into the caller's callers. ## Reading the signal in production The three remedies fail in ways that look alike from the outside — requests get slower — and quite different in the numbers: 1. If **server time per operation stays flat** while caller-side latency climbs, the time is being spent in retries or in waiting, not in the store. 2. If **attempts-per-success** rises above one, declare-and-retry is the thing degrading. 3. If callers are idle-waiting and throughput sits at a ceiling that does not move when you add callers, the claim's hold time is the ceiling. ## When none of the three is acceptable Sometimes the entry should not be the coordination point at all. Ask whether the value must be exact, whether the update must be synchronous, and whether this tier — which offers no isolation level, no rollback and no cross-entry guarantee — should own the invariant. "None of these, and here is where the invariant moves" beats picking the least bad of three.
- How do you tell that retries are the problem rather than the store being slow?Compare the two sides of the wire. If server time per operation is flat and the operation rate at that entry is high while successful updates per second are low, the store is healthy and the traffic is attempts that do not land. The decisive number is attempts per successful update: at one it is fine, at five most of your capacity is being spent producing nothing.
- Does raising the retry limit ever help a contended entry?Rarely, and it usually hurts. Every extra attempt adds traffic to the entry everyone else is contending for, so higher limits raise the refusal rate for all callers while improving one caller's odds slightly. Raise it only where contention is a short burst that will clear; where it is the steady state, change the shape of the write instead.
- What does a long submitted program do to the other callers on a hot entry?It holds the store for its whole duration. Under one-operation-at-a-time execution every other caller waits behind it — head-of-line blocking — and even where worker threads lock individual entries, anything it touches is unavailable for that time. The rule is that a program sent to the store must be short and bounded; the moment it contains an unbounded loop it has become an availability risk for everyone.
saying these in an interview costs you the question
- Says optimistic retry is always cheaper because it takes no lock
- Expects a higher retry limit to raise throughput on a hot entry
- Runs a retry loop with no bound and no fallback
- Gives a claim a deadline shorter than the work it guards
- Calls repeated refusals a store problem rather than a design one
- Puts an unbounded program on the store to avoid the retries