Design Billing Ledger
Immutable financial billing ledger with double-entry security constraints and payment checks.
1. System Requirements
- Create billing profiles and balance statements.
- Record billing debits, credits, and invoices dynamically.
- Reconcile account transactions for audits.
- Immutability of transaction entries (strictly append-only).
- Strict transaction safety (ACID transaction isolation).
2. Scale & Resource Estimates
Throughput: 50M active users, 1 Million transactions processed/day = ~11.5 transactions/sec.
Storage & RAM: Append-only database logs: ~10 GB/year, highly compressed.
3. System API Endpoints
POST /api/v1/ledger/entry
Request: { debit_account: "acc-user-41", credit_account: "acc-stripe-fees", amount: 1250 }
Response: { entry_id: "ledg-881", status: "posted" }
4. Database Schema Design
Table: journal_entries (PostgreSQL - Append-Only Ledger)
- entry_id (BIGINT - Primary Key)
- transaction_id (UUID - Index)
- account_id (UUID - Foreign Key)
- type (ENUM: debit, credit)
- amount (BIGINT)
- description (TEXT)
- created_at (TIMESTAMP)
5. Interactive High-Level Architecture
6. Scalability Deep Dive & Bottlenecks
Enforces double-entry bookkeeping principles: every financial transaction must post a debit to one account and a credit to another, with their sum strictly equaling zero. The ledger database is configured as append-only, and updates or deletions on past ledger lines are blocked by schema constraints. Revisions are executed exclusively using compensating transactions.
7. Production Scaling Strategies
Append-Only Storage: Ledger entries are immutable — no UPDATE or DELETE allowed. Corrections are made via compensating entries (reverse the original, post the corrected amount). This creates a complete, tamper-proof audit trail for regulatory compliance.
Sharding by Account: Shard the ledger database by account_id (hash mod 128). Each shard handles ~1M accounts. Within a shard, entries are ordered by timestamp for efficient balance computation. Cross-account transfers use two-phase commit or saga pattern.
Balance Materialization: Computing balance from all historical entries is expensive (O(N) entries). Maintain a materialized balance table updated on each new entry. Use database triggers or application-level logic to keep balances consistent with the journal.
Reconciliation Batch Jobs: Nightly Spark job recomputes all account balances from raw journal entries and compares against materialized balances. Any discrepancy triggers an alert. This catches bugs, race conditions, and potential fraud.
Event Sourcing Integration: Every ledger write publishes an event to Kafka (LedgerEntryCreated). Downstream consumers: (1) balance cache updater, (2) analytics pipeline, (3) regulatory reporting exporter. Each consumer processes events independently.
Multi-Currency Support: Store amounts in the smallest currency unit (cents for USD, satoshi for BTC) as integers — avoid floating-point. FX conversion uses a rate table snapshotted at transaction time. Ledger entries always store both original and converted amounts.
8. Runnable Production Starter Code
Scaffold