Retry semantics and read-only transactions
Why mark reads as read-only?
Write conflicts and retry semantics
A retry is correct only if the operation is idempotent or the retry re-derives its effect from the current state. That splits into two cases:
- Blind writes and appends retry safely. Their read set is empty, so nothing they read can have been invalidated.
- Read-modify-write must re-read inside the retry loop. Retrying with the stale values that you read the first time is exactly a lost update. The structure is
read → compute → attempt → on failure, loop. It is the same shape as a lock-free CAS loop and has the same failure mode: livelock, where everyone re-reads and everyone fails again.
def add_to_wallet(wallet_id, amount):
for attempt in range(MAX_ATTEMPTS):
version, balance = read_wallet(wallet_id) # re-read every time
ok = write_if_version_matches(wallet_id, version, balance + amount)
if ok:
return "done"
sleep(random_between(0, backoff(attempt))) # jitter, see below
return "gave up"The contention arithmetic settles arguments. With n writers fighting over one pointer and a commit latency T, naive retry gives one winner per round, so all n need about n²T/2 to finish. With n = 10 and T = 50 ms: 10² × 0.05 / 2 = 2.5 seconds of pure coordination to land ten commits. During those seconds the system is fully busy and produces almost nothing. So backoff with jitter (wait a random time that grows with each failed attempt) is not optional. And since the failure is contention rather than lack of capacity, the random waits must be decorrelated, or you rebuild the synchronized-retry stampede: every client waits the same time, then they all come back together.
The architectural point matters more than the tuning: retryability is a property of where the transaction's logic lives, not of the database. To retry a read-modify-write, something must be able to re-execute the logic. If the logic runs in an interactive client session mixed with user decisions ("show the price, wait for the user to click"), nothing can re-run it. If it is a one-shot statement or a stored procedure, the server can retry it invisibly. This is the same property that made deterministic execution viable in Multiversion and optimistic schemes, and here it returns as an operational concern. It is why "just retry on serialization failure" is sound advice for some architectures and impossible for others.
Finally, retries should be budgeted as a fraction of all attempts, not counted per request, or an outage multiplies the load on the service that is already failing.
Read-only transactions
This is the highest-leverage item in the article.
In an MVCC system a read-only transaction allocates no transaction ID, takes no locks, writes no commit record, and forces no log. Its commit path is empty. In Spanner (Google's distributed database) it needs neither locks nor two-phase commit, only a timestamp and a short wait for replicas to catch up. Declaring READ ONLY removes a transaction from three separate cost structures at once, for the price of one keyword.
BEGIN TRANSACTION READ ONLY;
SELECT count(*) FROM seats WHERE event_id = 42 AND status = 'free';
COMMIT;But it is cheap, not free, and the reason is counterintuitive. A read-only transaction can take part in a serialization cycle, through an incoming write-then-read dependency and an outgoing read-then-write dependency. This is the read-only anomaly under snapshot isolation, shown by Fekete, O'Neil and O'Neil in 2004. So "read-only" does not automatically mean "serializable". Postgres's SSI handles it by identifying safe snapshots: moments at which a read-only transaction provably cannot be part of a dangerous structure, so it needs no tracking at all. The option DEFERRABLE makes the transaction wait until such a snapshot exists.
The general shape: the optimization is not "reads are free". It is "find the timestamp at which this read is provably safe, then it is free". That is the same move as the commutativity escape of Multiversion and optimistic schemes: restructure the work so that coordination is unnecessary, rather than making coordination cheaper.