Skip to content
About
Main content

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 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 . The structure is read → compute → attempt → on failure, loop. It is the same shape as a lock-free loop and has the same failure mode: , 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 T, naive retry gives one winner per round, so all n need about 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, 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 system a read-only transaction allocates no transaction ID, takes no , writes no , and forces no log. Its commit path is empty. In Spanner (Google's distributed database) it needs neither locks nor , 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 isolation, shown by Fekete, O'Neil and O'Neil in 2004. So "read-only" does not automatically mean "serializable". Postgres's handles it by identifying : 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.