ADR-0015: TigerBeetle for Double-Entry Ledgers¶
Status: Accepted Date: 2026-02-15
Context¶
Applications handling financial transactions (payments, wallets, credits, revenue tracking) need ACID-compliant double-entry accounting. Traditional approaches use PostgreSQL tables with application-level transaction logic, which is error-prone and slow at scale.
TigerBeetle is a purpose-built financial transactions database designed for exactly this: double-entry bookkeeping with ACID guarantees, sub-millisecond latency, and built-in balance constraints.
Decision¶
Use TigerBeetle as the ledger engine for applications that declare ledger and transaction DSL constructs. The DSL provides a declarative interface; the runtime connects to TigerBeetle via its client library (optional dependency).
DSL Syntax¶
ledger CustomerWallet "Customer Wallet":
account_code: 1001
ledger_id: 1
account_type: asset
currency: GBP
flags: debits_must_not_exceed_credits
sync_to: Customer.balance_cache
transaction RecordPayment "Record Payment":
execution: async
priority: high
transfer revenue:
debit: CustomerWallet
credit: Revenue
amount: payment.amount
code: 1
flags: linked
idempotency_key: payment.id
Account types: asset, liability, equity, revenue, expense
Runtime¶
- TigerBeetle client is an optional dependency (
pip install dazzle-dsl[tigerbeetle]) - Lazy import via
pra/tigerbeetle_client.py— no import cost if not used sync_tofield enables one-way cache sync from TigerBeetle balance to a PostgreSQL entity field
Consequences¶
Positive¶
- Sub-millisecond financial transaction processing
- Built-in double-entry invariants (debits = credits)
- Balance constraints enforced at the database level (
debits_must_not_exceed_credits) - Idempotency keys prevent duplicate transactions
Negative¶
- Additional infrastructure dependency for financial apps
- TigerBeetle requires its own deployment and monitoring
- Limited query capabilities compared to PostgreSQL (designed for writes, not ad-hoc queries)
Neutral¶
- Apps without
ledgerconstructs never touch TigerBeetle - Balance caches in PostgreSQL provide queryable read path
Alternatives Considered¶
1. PostgreSQL-Only Double-Entry¶
Implement ledgers as PostgreSQL tables with triggers and constraints.
Rejected: Application-level double-entry is error-prone. PostgreSQL lacks built-in balance constraint enforcement. Performance degrades under high transaction volumes.
2. Stripe/Payment Provider Only¶
Delegate all financial logic to payment providers.
Rejected: Not all ledger use cases are payments (credits, loyalty points, internal transfers). Provider lock-in. Latency for internal operations.