Borrow double-entry: a ledger that checks itself
Why it matters Most data loss is silent. A row that never got written leaves no trace, so nobody knows to look for it. Some fields solved this centuries ago by making every record carry its own check.
Three ways to notice something missing
Double-entry bookkeeping. Every transaction is written twice: a debit in one account and an equal credit in another. Selling a chair adds to cash and takes from stock. If one half is lost, the books no longer balance, and the trial balance shows it.
A balanced chemical equation. Atoms are never created or destroyed in a reaction, so the left and right sides must have the same atoms. An equation that does not balance has a mistake in it, even before you know which.
Cashing up a till. At the end of a shift, the float plus the day's takings must equal what is in the drawer. A missing receipt shows up as a difference, even though nobody saw it go.
Compare before you code
- What is conserved in each one?
- Where does "new" value come from in each (the money for a sale, the chemist's ingredients, the float)? Is that source recorded too?
- Write the shared idea in one sentence without mentioning money, atoms or tills.
The target
A loyalty programme stores its points as rows in an append-only store. Users earn points, redeem them, transfer them to each other, and lose them when they expire. The store is fed by a queue that, very occasionally, loses a single row. Today nobody can tell when that happens.
op = {"id": "t00017", "kind": "transfer", "user": "u004", "to": "u031", "points": 120}
rows_for(op) # -> the rows to store, each (op_id, account, delta, ...)
audit(rows) # -> ids of operations that lost a rowWrite rows_for(op) and audit(rows). op has "id", "kind" (earn, redeem, transfer or expire), "user", "points" (more than 0) and, for a transfer, "to" (another user). Each row is a tuple whose first three fields are (op_id, account, delta); you may add more fields. A user's balance is the sum of deltas on their account: earn adds, redeem and expire take away, transfer moves points from user to to. Any account that is not a user must be named "system:...". audit(rows) gets every stored row, in any order, and returns the ids of operations that lost exactly one row. Trap to beat: the test drops one row from 60 random operations of every kind, and a clean store must report nothing.
Spotted a problem in this lesson? Report it · Ask about this lesson
This exercise opens soon
Finish the lesson before it and this one unlocks. The whole course is free, no account needed, and you can read this lesson right now.
This browser is blocking cookies for learn.floati.life, so lessons you finished here cannot open in order. Allow cookies for this site and reload.