Ledger Architecture
A ledger that works for 1,000 transactions per day and a ledger that works for 1 million transactions per second are built differently. The double-entry model tells you what to store. Ledger architecture tells you how to store it at scale while keeping balance queries fast and consistency guarantees intact.
The Balance Computation Problem#
Deriving balance as SELECT SUM(amount) FROM ledger WHERE account_id = X is correct but slow when the ledger has billions of rows. An account active for 10 years has millions of entries. Full scan on every balance check is unacceptable.
Solution: materialized balance with event sourcing. Maintain a balances table with the current balance per account. On every ledger write, update the balance atomically in the same transaction. The ledger is the source of truth; the balance table is a cache. If the balance table corrupts, you recompute from the ledger.
Checkpointing: periodically create a balance snapshot as of a specific ledger sequence number. Balance as of now = snapshot balance + sum of entries since snapshot. This bounds the recomputation cost.
Sharding the Ledger#
Shard by account ID. All ledger entries for account X go to the same shard, which keeps per-account queries fast. Cross-account transfers (Alice to Bob on different shards) require a distributed transaction: write both entries atomically across shards.
For high-throughput systems, use a multi-currency or multi-asset account structure where each asset type is a separate account. This distributes write load: a user holding BTC, ETH, and USD has three accounts on potentially three shards.
Sequence Numbers and Ordering#
Each ledger entry needs a monotonically increasing sequence number per account for ordering. Use a database sequence or a distributed ID generator that embeds a timestamp. Sequence numbers let you reconstruct history deterministically and detect missing entries (a gap in sequence numbers indicates a lost write).
At Salesforce#
The billing ledger at Salesforce partitioned entries by org ID. Each org’s billing history lived on one shard, making per-org balance queries single-shard. Cross-org adjustments (rare, for enterprise agreements spanning multiple orgs) went through a separate reconciliation service that wrote compensating entries on each shard. Annual audit queries against 5 years of billing history ran in under 2 seconds because the snapshot + delta pattern limited the scan to entries since the last monthly snapshot.
What I’m Learning#
Ledger architecture is a balance between write throughput (favor append-only, sharded ledger) and read performance (favor materialized balances, checkpoints). Getting both requires treating the ledger as immutable truth and the balance table as a derived cache.
What’s the largest ledger you’ve worked with, and how did you handle the balance query performance problem?