Section 7: Two Maximums Walk Into a Transaction - Deep Dive: Bid Acceptance & the Proxy Engine
Executive Summary: Everything the prompt called non-negotiable is decided inside one transaction about two milliseconds long, opened here statement by statement: idempotency lookup, row lock, checks, proxy engine, ledger insert, row update, outbox row, commit, cache write, verdict. The engine is a pure function with four cases, and all three moments of auction #7731 run through it against an eBay-style increment table, including the lookup-order rule that puts Thursday's price at $9,550. Then the defence: why a row lock is enough at 200 bids a second, why FOR UPDATE beats a version check under contention, and why a replayed request returns the verdict it earned the first time, even after the price has moved on.
The interviewer taps the Bid Service box from Section 6. "Open it. Elena's Thursday bid, every statement, in order."
Two maximums are about to meet: Marcus's $12,000, secret on the auction row since Tuesday, and Elena's $9,500, arriving now. This transaction is the only place they are ever compared.
The Ten Steps
BEGIN;
-- 1. idempotency: a replay returns its stored verdict, never reaching the lock
SELECT body_hash, verdict_json FROM bid_attempts
WHERE bidder_id = $b AND auction_id = $a AND key_hash = $k;
-- hit, same body_hash -> return verdict_json; hit, different body_hash -> 422 KEY_BODY_MISMATCH
-- 2. one auction, one serialisation point; 50 ms lock wait, one retry
SELECT status, end_time, seller_id, current_price, high_bidder_id, high_max_amount, next_seq, owner_node
FROM auctions WHERE auction_id = $a FOR UPDATE;
-- 3. checks, in order: owner_node IS NULL else ROLLBACK, forward to the owner, same key (Section 8)
-- status = 'OPEN' AND $accepted_at < end_time else AUCTION_ENDED
-- $b <> seller_id else SELLER_OWN_ITEM
-- $max >= current_price + increment(current_price) (first bid: >= start_price) else BELOW_MINIMUM_BID
-- $b = high_bidder_id ? require $max > high_max_amount, skip step 4
-- 4. (new_high, new_high_max, new_price, outcome) = resolve(...) -- pure, below
-- 5. the ledger; seq is assigned under the lock
INSERT INTO bids (auction_id, seq, bid_id, bidder_id, max_amount, accepted_at, price_after, outcome)
VALUES ($a, next_seq, $bid_id, $b, $max, $accepted_at, new_price, outcome);
-- 6. the row holds the answer
UPDATE auctions SET current_price = new_price, high_bidder_id = new_high,
high_max_amount = new_high_max, bid_count = bid_count + 1,
next_seq = next_seq + 1, version = version + 1 WHERE auction_id = $a;
-- 7. event and verdict ride the same commit
INSERT INTO outbox (event, auction_id, seq, payload) VALUES ('bid.accepted', $a, next_seq, ...);
INSERT INTO bid_attempts (bidder_id, auction_id, key_hash, body_hash, verdict_json, created_at)
VALUES ($b, $a, $k, $body_hash, ..., now());
COMMIT; -- 8. returns after the second-AZ replica acknowledges
-- 9. write-through the hot-state cache: price, high bidder id, count, version; never the max;
-- best-effort, never blocks the verdict
-- 10. return the verdict
Step 2 is the sentence the module rests on: one auction is one serialisation point. Every bid on #7731 passes through this lock one at a time, and seq is handed out inside it, which is why two bids can share a millisecond but never a sequence number. Step 3 opens with a fence. If the row carries an owner_node, the auction has been promoted to Section 8's hot-item mode: the transaction rolls back and the bid service forwards the request, same idempotency key, to that owner; if the owner is unreachable, the answer is a 503 with Retry-After: 0. The row itself refuses the normal path once promoted, so a node that has not yet heard about the promotion cannot accept a bid behind the owner's back. The rest of step 3 stamps nothing: accepted_at was taken on ingress, before any queueing, and is only compared here. Theo's 19:00:00.050 bid dies on the end-time check with AUCTION_ENDED, in the owner's memory by then since #7731 was promoted at 18:58, and the attempt row is still committed, so Section 10's "prove I bid" challenge has evidence.
The last check is the one candidates miss. If Marcus, already leading, raises his own maximum on Friday, nobody has outbid anybody: the engine is skipped, high_max_amount moves, and the price stays at $9,550. Lifting the price when the leader raises their own ceiling is bidding against yourself, in front of 50,000 watchers.
Steps 5 to 7 are one fact or no fact, and the outbox row is the one interviewers probe. Publish bid.accepted after commit, and a crash in between means Dana never learns she was outbid on Tuesday. Publish before commit, and a rollback means she learns it for a bid that never existed. A late notification is recoverable; a false one is not. So the event is a row in the same transaction, a relay publishes it after commit at least once, and consumers deduplicate on (auction_id, seq).
Quick check before moving on:
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson