Payments Ledger
A ledger for a wallet or bank: idempotent transfer APIs, double-entry journal, balances with concurrency control, a transfer state machine around external rails, unknown outcomes, and reconciliation.
Brief, questions and numbers
- Users hold balances and transfer money to each other and to external banks; every naira must be accounted for.
| question | answer we assume |
|---|---|
| consistency? | strong for balances: no overdraft, no double spend |
| scale? | 5M transfers/day, peaks at salary days |
| external rails? | yes, with timeouts and callbacks |
| audit? | every change traceable forever |
| latency? | p99 under 500 ms for internal transfers |
transfers = 5M/day ≈ 60/s average, ~600/s peak on salary days entries = 2-4 per transfer → ~20M rows/day → 7B rows/year: partition by month hot rows = settlement and fee accounts receive a credit on every transfer: hot spots storage = 7B × 150 B ≈ 1 TB/year plus indexes
v1, the break, and v2
v1. A balances table updated in place by each transfer (UPDATE balance = balance - x), with an HTTP call to the bank inside the same database transaction.
The break. Retries double-charge without idempotency; updates without history cannot be audited; the bank call holds locks for seconds; and every transfer updates the same fee and settlement rows, serialising all traffic.
v2. An append-only double-entry journal with idempotency keys, balances maintained transactionally with versions, a state machine with rail calls outside transactions and an outbox, sharded sub-accounts for hot internal accounts (summed for reporting), monthly partitions, and reconciliation.