Dev.to · 7 min read

TigerBeetle: What Actually Happens When You Need a Database That Can't Lose Money

TigerBeetle: What Actually Happens When You Need a Database That Can't Lose Money

TigerBeetle: What Actually Happens When You Need a Database That Can't Lose Money You use PostgreSQL. You use MySQL. You've probably heard of Redis, Cassandra, maybe even CockroachDB. But there's a database that financial infrastructure companies quietly rely on for the hot path of money movement—and almost no application developers know it exists. TigerBeetle. It's not a general-purpose database. It doesn't do JOINs. It doesn't have a query planner. It doesn't support arbitrary schemas. What it does is one thing: financial transactions at a million transfers per second on a single core, with durability guarantees that survive disk corruption, network partitions, and power loss. Here's what actually happens under the hood. The Problem: OLTP Isn't OLGP Most developers think of databases as "places to store data." But there's a fundamental split: OLGP (Online General Purpose) — PostgreSQL, MySQL, SQLite. Flexible schemas, ad-hoc queries, ACID transactions, indexes for everything. Great for "what's this user's order history?" OLTP (Online Transaction Processing) — TigerBeetle. Rigid schema (double-entry bookkeeping), no ad-hoc queries, deterministic execution, hardware-aware storage. Great for "move $42.37 from account A to account B, right now, and never lose it." The world is becoming more transactional. India's UPI processed 16.9 billion payments in January 2025 alone. Serverless APIs bill by the millisecond. Smart meters trade energy by the kilowatt-hour every 15 minutes. General-purpose databases weren't built for this write-heavy, high-contention reality. TigerBeetle was. Double-Entry Isn't Just Accounting—It's a Data Model Every transfer in TigerBeetle has: debit_account_id — where value comes from credit_account_id — where value goes to amount — unsigned 128-bit integer (no floating point, ever) ledger — which asset type (USD, BTC, kilowatt-hours, loyalty points) code — business event type (refund, payout, fee, etc.) user_data_128 — your idempotency key (critical for exactly-once) That's it. No NULLs. No variable-length strings. Every transfer is exactly 128 bytes, cache-line aligned. The entire schema is baked into the binary. Why double-entry? Because credits == debits is an invariant the database can enforce without application logic. If the sum ever diverges, something is corrupted. This isn't just accounting hygiene—it's a correctness check that runs at wire speed. Single-Threaded by Design (And Faster for It) "Wait, single-threaded? In 2026?" Yes. And here's why. Financial workloads are contention-heavy. A few accounts (the "hot" ones) get hammered—think a payment processor's settlement account, or a popular creator's payout wallet. Transfers between hot accounts must serialize. No amount of sharding or parallelism fixes this; Amdahl's Law sees to that. TigerBeetle leans into it: one thread, one event loop, zero locks, zero mutexes, zero GC pauses (it's written in Zig, no runtime). The commit function that executes a batch of transfers is a tight CPU loop: for (transfers) |transfer| { debit = accounts[transfer.debit_account_id]; credit = accounts[transfer.credit_account_id]; if (debit.balance < transfer.amount) { result = .insufficient_funds; continue; } debit.balance -= transfer.amount; credit.balance += transfer.amount; result = .success; } Two hash-map lookups, a bounds check, two integer additions. That's the "speed of light" for this problem. TigerBeetle gets within striking distance by eliminating everything else: no network round-trips to application code, no lock acquisition, no page cache indirection. The Prefetch Trick: IO Parallelism Without Thread Parallelism Here's the clever part. The commit function doesn't read from disk. It only touches in-memory state. Before commit runs, a separate prefetch phase analyzes the batch, predicts which accounts will be touched, and issues parallel async IO to load them into memory. While commit executes sequentially on one core, the storage subsystem is doing parallel reads across all available IO queues (io_uring on Linux). When commit starts, the data is already hot. Zero stall cycles. Consensus: Viewstamped Replication, Not Raft TigerBeetle replicates across 6 replicas using a Viewstamped Replication derivative (VSR)—the same lineage as Raft, but with a critical difference: the log is the source of truth, not the state machine. Each replica stores a single file: the "data file." It contains: A hash-chained Write-Ahead Log (WAL) of "prepares" (batches of ~8,000 transfers) A forest of LSM trees for the derived state (account balances, transfer history) The primary accepts requests, orders them, creates prepares, appends to WAL, replicates to backups. Quorum (4 of 6) = committed. Replicas apply prepares in sequence number order. Deterministic state machine = identical state everywhere. But here's what makes it unusual: the data file uses copy-on-write blocks with checksums at every level. Blocks are 0.5 MiB. Each block references children by (offset, checksum). The superblock (root) is atomically flushed to form checkpoints. If a replica crashes, it reads the last checkpoint, replays the WAL suffix, and arrives at the exact same state. Determinism guarantees it. Durability That Survives Silent Corruption This is where TigerBeetle diverges from almost every other database. Most databases trust fsync(). If fsync() returns success, the data is "safe." But fsync() can lie—silent disk corruption, controller bugs, helical corruption (where a write lands on the wrong track). Jepsen found TigerBeetle survives all of this. How? Logical AND physical determinism. Every block has a checksum. Every block references children by checksum. The hash chain from superblock to leaf mathematically proves the data wasn't corrupted. If a replica reads a block and the checksum mismatches, it doesn't panic—it fetches the same block from another replica (which has the identical hash chain) and repairs itself transparently. The system doesn't distinguish between "local disk" and "remote replica" for checksummed data. It's all just "sources of blocks with verified checksums." This is why TigerBeetle can guarantee durability even when storage lies. The Architecture Around It: Gateways, Not Direct Access You don't connect your frontend to TigerBeetle. The architecture looks like: [Mobile App] → [API Gateway] → [TigerBeetle Cluster] ↓ [PostgreSQL (metadata)] The gateway: Authenticates/authorizes Generates idempotency keys (128-bit, client-persisted) Batches individual transfers into large prepares Maps string enums ("refund", "USD") to TigerBeetle's integer codes Caches ledger/account metadata (which never changes—append-only) TigerBeetle only sees integer IDs. It never does string parsing, never talks to untrusted clients, never does auth. It just processes transfers. What You Don't Get (And Why That's the Point) Feature TigerBeetle SQL ❌ JOINs ❌ Secondary indexes (arbitrary) ❌ Arbitrary schemas ❌ Interactive transactions ❌ Ad-hoc analytics ❌ Horizontal sharding ❌ (6 replicas = HA, not scale-out) If you need to query "all transfers for user X in March," you do that in PostgreSQL. TigerBeetle stores the transfer, PostgreSQL stores the metadata linking transfer IDs to user IDs. This separation is intentional. The hot path (recording money movement) stays fast and simple. The control plane (who owns what, what's the currency name) stays flexible. When You Actually Need This Payment processors (millions of txns/sec) Marketplace payouts (high contention on creator wallets) Ledger services (immutable audit trail required) Real-time billing (per-second serverless metering) Loyalty/points systems (high volume, low value, strict balances) When you don't need it: User profiles, product catalogs, content management Anything with ad-hoc query patterns Low-write, read-heavy workloads Prototyping (operational complexity of 6-replica cluster) The Mental Model Shift Stop thinking "database = general purpose store." Start thinking: Hot path = specialized OLTP database. Cold path = general purpose OLGP database. TigerBeetle doesn't replace PostgreSQL. It sits beside it, handling the one thing PostgreSQL struggles with: high-contention, write-heavy, durability-critical financial transfers at scale. And it does that one thing so well that a single core handles a million transfers per second—while your PostgreSQL cluster chokes on lock contention for the same workload. TigerBeetle is open source (Apache 2.0), written in Zig, and production-ready. The architecture docs are unusually readable for a systems project. The single-page docs get you running in minutes.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News