Payments & ledgers
Double-entry ledger - the balance is not a column
Teaches why money is stored as immutable movements and read as a derived sum, and what it costs to never update a total.
What this board gets wrong on purpose
The tension
The board spends every paragraph arguing that a balance must never be stored, and then stores one. The snapshot exists because a merchant settlement account takes 200,000 entries a day and no screen is going to SUM 365 million rows - but a snapshot is a cached sum, and a cached sum is a balance column with better manners. It can be written by a projector that read a partially-committed batch; it can be built from a replica that was behind; and when it is wrong, the read path returns a number that is confidently, silently, off. The trial-balance job is the only thing that would notice, it runs hourly, and it proves a GLOBAL invariant - debits equal credits across the whole ledger - which is exactly the check that a per-account snapshot error passes. So the system can be internally consistent and still show one customer the wrong balance for an hour, and the design has no per-account proof that runs often enough to catch it.
1 · Requirements
A ledger is the system of record for money that has moved. It is not a balance service with history bolted on; it is a history from which balances happen to be derivable. Every requirement below is downstream of that one inversion.
Ask these before designing
- Is this the system of record, or a reporting copy? If a regulator or an auditor will read it, entries are immutable and retention is a decade. If it is a convenience projection of somebody else’s ledger, almost nothing here is needed.
- Can an account go negative? A customer wallet must not. A revenue account must. A clearing account is expected to be negative for milliseconds. "No negative balances" is not a ledger rule, it is a per-account-type rule, and getting that wrong blocks legitimate postings at 3 a.m.
- One currency or many? Multi-currency does not mean a currency column. It means an account has exactly one currency, a transaction balances per currency, and any cross-currency movement is two transactions joined by an FX position account that somebody owns.
- How late can a posting be? If a card scheme can send a transaction dated four days ago, the ledger needs both an effective date and a recorded date, and every report has to say which one it used.
- Who is allowed to post? If the answer is "any service", the invariant is enforced in fifteen codebases. This board has exactly one writer for that reason.
Functional
- Post a transaction of two or more entries whose amounts sum to zero, atomically.
- Reject any transaction that does not balance, before anything is written.
- Read an account balance, and read it as of any past instant.
- List an account’s entries in stable order, paginated.
- Reverse a transaction by posting its mirror image, linked to the original.
- Prove, on demand, that the whole ledger balances.
Non-functional
- Entries are immutable. No UPDATE, no DELETE, no exceptions, enforced by revoked grants rather than by code review. The ledger role has INSERT and SELECT on entries and nothing else.
- Durability beats latency, every time. Synchronous commit before acknowledgement. A ledger that loses an acknowledged write is not a ledger.
- p99 under 150 ms to post a transaction, under 40 ms to read a balance.
- 99.99% on reads, 99.95% on writes. Reads serve screens; writes can be queued and retried by the caller with an idempotency key.
- The global invariant - sum of all entries = 0 - is checked hourly and alerts loudly. It is the one assertion that cannot be argued with.
Explicitly out of scope
Chart-of-accounts design, tax treatment, period close and journal approval workflow, FX rate sourcing, the payment rails themselves. The ledger records what happened; deciding what should happen is somebody else’s job.
The rest of this board
Available on Tier Slate
This page publishes the question. The answer — 5 more written pages, an 8-step narrated walkthrough and a 5-table schema — is the board itself, and it opens in Tierslate.
- 5written pages
- 8walkthrough steps
- 5tables27 columns
5 pages behind this one
- Capacity estimation
- Storage estimation
- Availability
- How it works
- Deployment plan
More boards
Systems where losing a write is losing money.
- Fraud detection - a decision that must fit in 120 milliseconds
- Payment gateway - what a timeout means when the card may already be charged
- Matching engine - price-time priority, and why the clock cannot decide who was first
- Wallet transfers - the state machine, the hold that expires, and the reversal that cannot