Reading — step 1 of 5
Read
Transactions: BEGIN / COMMIT / ROLLBACK
Move money between two accounts and you must update two keys. If the process dies between the writes, the books are silently wrong forever. A transaction groups operations into an all-or-nothing unit — the A (atomicity) in ACID:
BEGIN
PUT alice 90
PUT bob 110
COMMIT -- both apply, or neither
The write-buffer idiom
The simplest correct implementation never touches the store until commit. All writes go into a per-transaction buffer; COMMIT applies the buffer; ROLLBACK throws it away:
Because nothing touched the store early, rollback is free — there is no undo log to walk. And because the buffer is a dict, writes to the same key collapse: the transaction's last write wins, and n= counts distinct keys, not operations.
Read-your-own-writes
A GET inside a transaction consults the buffer first, then falls through to the store. The three-way distinction is where implementations go wrong:
- key in buffer with a value → return it (yes, even though it is uncommitted);
- key in buffer as the DEL sentinel → return
<nil>— do not fall through to the store; - key absent from the buffer entirely → read the committed store.
Encoding "deleted" as None and testing truthiness conflates the last two cases, so a buffered delete leaks the old committed value. Use a dedicated sentinel and test membership with in.
Reads outside any transaction never see buffered writes — until COMMIT, the rest of the world observes nothing at all.
What real stores do
Redis MULTI/EXEC queues commands and runs the batch atomically — but with no isolation story between concurrent clients. etcd builds atomic updates out of compare-and-swap on revisions. Postgres and FoundationDB offer full ACID with real isolation levels. Our exercise is single-threaded, so isolation can wait — MVCC, next lesson, adds it.
Your exercise
Implement direct PUT/DELETE/GET plus BEGIN, COMMIT, ROLLBACK. The grader-caught mistakes, from the real tests: (1) the buffered delete — with a committed, BEGIN, DELETE a, GET a must print <nil>, and after ROLLBACK a plain GET a prints the original value again (the visible test ends on exactly this sequence, printing 100); (2) exact error tokens — a second BEGIN inside a transaction prints ERR already in transaction; COMMIT or ROLLBACK with no open transaction prints ERR no transaction; (3) exact success tokens — BEGIN prints OK, committing two buffered keys prints COMMIT n=2 (a lone buffered DELETE commits as COMMIT n=1), and rollback prints ROLLBACK; (4) COMMIT and ROLLBACK must actually close the transaction, or the visible test's second BEGIN would wrongly answer ERR already in transaction.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…