System Design ·

How FoundationDB Works Internally: Ordered Key-Value Storage, Serializable Transactions, and the Simulation Testing Framework That Proves Correctness

A deep-dive into FoundationDB internals for senior engineers. Covers the five-role cluster architecture, the optimistic concurrency control protocol driven by the Sequencer, the log-structured storage engine, multi-Paxos coordination, the deterministic simulation testing framework that found hundreds of bugs before release, the layer concept for higher-level data models, and production considerations.

How FoundationDB Works Internally: Ordered Key-Value Storage, Serializable Transactions, and the Simulation Testing Framework That Proves Correctness

Most distributed databases claim ACID transactions. Few of them can prove it. FoundationDB is the exception: it ships a deterministic simulation framework that replays every failure scenario in a single process, found hundreds of correctness bugs before a single production deployment, and is the storage substrate underneath Apple’s CloudKit, Snowflake’s metadata tier, and several other hyperscale systems. Understanding how it achieves serializable transactions across a distributed cluster without sacrificing throughput is worth your time.

The Core Abstraction: Ordered Key-Value Store

FoundationDB presents one deceptively simple API: an ordered key-value store with fully serializable, multi-key ACID transactions. Keys and values are arbitrary byte strings. Key ordering is lexicographic. There are no secondary indexes, no schema, no native document or relational layer. That is not a limitation; it is the point. By narrowing the surface area to a well-understood primitive, FoundationDB can provide a correctness guarantee that higher-level systems (the “layer” concept, covered later) can rely on without re-implementing consensus.

The ordered key space is the foundation for range reads (getRange), which is how layers implement indexes, sorted sets, and table scans efficiently without scatter-gather.

import { Database, open } from "foundationdb";

// Open a FoundationDB database connection
const db: Database = open("fdb.cluster");

// Write a key-value pair inside a transaction
await db.doTransaction(async (tx) => {
  tx.set(
    Buffer.from("user/alice/email"),
    Buffer.from("alice@example.com")
  );
  tx.set(
    Buffer.from("user/alice/plan"),
    Buffer.from("pro")
  );
});

// Read a range of keys sharing a prefix
await db.doTransaction(async (tx) => {
  const entries = await tx
    .getRange(
      Buffer.from("user/alice/"),
      Buffer.from("user/alice/\xff")
    )
    .toArray();

  for (const [key, value] of entries) {
    console.log(key.toString(), "->", value.toString());
  }
});

Every operation happens inside a transaction. There are no single-key shortcuts that bypass the transaction machinery. This uniformity is what allows FoundationDB to reason about consistency globally rather than per-operation.

The Five-Role Cluster Architecture

FoundationDB separates concerns across five distinct process roles. Unlike systems where each node is symmetric, FoundationDB assigns each role a narrow responsibility. This separation is what makes the simulation framework tractable.

Coordinator. A small set of coordinators stores the cluster configuration (the “cluster file”) and is the first contact point for any client. Coordinators do not participate in the transaction path; they exist solely to allow clients and new processes to discover the rest of the cluster. They use a Paxos variant to agree on cluster state.

ClusterController. One elected ClusterController monitors the health of all other processes and recruits replacements when processes fail. It is the orchestration layer, not the data path. The ClusterController also manages the distributed transaction log and storage server recruitment.

Sequencer. The Sequencer is the central source of logical time in the cluster. It assigns two version numbers that define the entire transaction protocol:

  • A read version, issued when a transaction begins, which identifies the consistent snapshot the transaction reads from.
  • A commit version, issued when a transaction is ready to commit, which establishes the transaction’s position in the global serialization order.

There is exactly one active Sequencer at a time. If it fails, the ClusterController elects a new one and the current transaction logs are sealed. The Sequencer is a single logical process but its work is lightweight: it only issues monotonically increasing version numbers and does not touch data.

Transaction Log (TLog). Transaction logs are the durability layer. Before a commit is acknowledged to the client, the write-set must be persisted to a quorum of TLog processes. TLogs form a replicated write-ahead log; they hold mutations in memory (flushed to disk) until Storage Servers have durably applied them, at which point TLogs can discard those entries. The number of TLog replicas is configurable (default: two in production, three recommended).

Storage Server. Storage Servers hold the actual key-value data and serve reads. They consume the TLog stream asynchronously, applying mutations to their local B-tree variant. Because reads go to Storage Servers and writes go to TLogs, reads and writes are decoupled on the critical path.

The architecture separates the “what version is this transaction” question (Sequencer), the “is this commit durable” question (TLog quorum), and the “where is this key” question (Storage Server). Each component can be scaled, failed, and replaced independently.

Optimistic Concurrency Control: The Transaction Lifecycle

FoundationDB uses optimistic concurrency control (OCC). Transactions never hold locks during execution; conflicts are detected at commit time.

Step 1: Obtain a read version. When a transaction opens, the client library requests a read version from the Sequencer. The Sequencer returns the current committed version number. The client uses this as the “as-of” timestamp for all reads in the transaction.

Step 2: Execute reads and writes locally. Reads go to the appropriate Storage Server for the key’s shard. The Storage Server returns the value at exactly the read version (older versions are kept via multi-version concurrency control). Writes are buffered locally in the client library. No locks are held anywhere.

Step 3: Commit. At commit time, the client sends a commit request containing:

  • The read version.
  • The read-set (the set of key ranges the transaction observed).
  • The write-set (the mutations to apply).

The Sequencer assigns a commit version (strictly greater than the read version). It then checks for conflicts: if any key in the read-set was written by another committed transaction between the read version and the commit version, the transaction aborts with a conflict error. The client library automatically retries on conflict.

If there are no conflicts, the mutations are sent to the TLog quorum for durability. Once a quorum of TLogs acknowledges, the commit is confirmed to the client. Storage Servers consume the mutations asynchronously.

// FoundationDB automatically retries on conflict inside doTransaction
await db.doTransaction(async (tx) => {
  // Read — establishes a read version and read-set entry
  const current = await tx.get(Buffer.from("account/balance/alice"));
  const balance = current ? parseInt(current.toString(), 10) : 0;

  if (balance < 100) {
    throw new Error("Insufficient funds");
  }

  // Writes are buffered; the read-set covers "account/balance/alice"
  tx.set(
    Buffer.from("account/balance/alice"),
    Buffer.from(String(balance - 100))
  );
  tx.set(
    Buffer.from("account/balance/bob"),
    Buffer.from(String((await tx.get(Buffer.from("account/balance/bob"))
      ? parseInt((await tx.get(Buffer.from("account/balance/bob")))!.toString(), 10)
      : 0) + 100))
  );
  // On commit, Sequencer assigns commit version and checks conflicts
  // TLog quorum confirms durability before doTransaction resolves
});

The serialization guarantee follows from the Sequencer’s version ordering: every committed transaction has a unique commit version, and conflict detection ensures that any two transactions that observe each other’s read-sets must serialize in version order. This is strict serializability, not snapshot isolation.

Storage Engine: The B-Tree Behind Storage Servers

Each Storage Server uses a log-structured variant of a B-tree, specifically a copy-on-write (CoW) B-tree with an in-memory mutation buffer. This design avoids random writes for incoming mutations: new mutations are applied in memory and written sequentially to an append-only log. Periodic compaction merges the in-memory buffer into the on-disk B-tree pages.

Multi-version concurrency is maintained by keeping old B-tree page versions alive until the oldest active transaction’s read version advances past them. Storage Servers track the minimum read version across all open client transactions (communicated by the Sequencer) and discard page versions older than that watermark. This is how range reads at a historical read version work without blocking writes.

Each Storage Server is responsible for a contiguous shard of the key space. The cluster dynamically splits and rebalances shards based on data size and load, coordinated by the ClusterController.

Multi-Paxos for Coordination

The Coordinators run a multi-Paxos protocol (specifically, FoundationDB uses a protocol called “LivenessManager” internally, but it is Paxos in structure) to agree on which process is the active ClusterController and to persist a small amount of cluster metadata. This is not the data path. Data commits use the TLog quorum, not Paxos.

The separation is important: Paxos is slow (it requires two round trips to persist a single value) and is appropriate for infrequently-changing coordination data. TLog replication is faster (it requires one write to a quorum) and is appropriate for the high-throughput commit path.

The Simulation Testing Framework

This is FoundationDB’s most distinctive engineering investment and the reason its correctness claims are credible.

The simulation framework runs the entire FoundationDB codebase, including all network I/O and disk I/O, inside a single deterministic simulation process. No real sockets are opened. No real files are written. Every source of non-determinism (thread scheduling, network latency, packet loss, disk fault injection, clock skew) is replaced with a controlled, reproducible simulation.

The framework can run months of simulated cluster time in minutes of wall-clock time. It injects faults systematically: random process crashes, correlated machine failures, network partitions, Byzantine disk failures that corrupt pages, clock jumps, and message reordering. After each simulated sequence of faults, the framework verifies that the cluster’s behavior satisfies the invariants of serializable transactions: no lost commits, no phantom reads, no dirty reads, no write skew.

When a test finds a violation, the deterministic simulation records the exact random seed that produced it. The bug is 100% reproducible by replaying the same seed. This is the property that makes the framework genuinely useful rather than just impressive: you can debug the failure without any flakiness.

The FoundationDB team documented that the simulation framework found hundreds of correctness bugs before the first production deployment. The framework ran continuously during development. Every code change was required to pass a large test suite before merging.

The practical implication: the serializable isolation guarantee in FoundationDB is not a claim about a protocol that is believed to be correct. It is a guarantee about a codebase that has been tested against thousands of failure scenarios under adversarial conditions, with every failure fully reproducible.

The Layer Concept

FoundationDB deliberately provides no schema, no document model, no relational tables, and no secondary indexes at the core layer. Higher-level data models are implemented as “layers” on top of the raw key-value API.

The Record Layer, open-sourced by Apple, is the most sophisticated layer. It implements a typed record store with secondary indexes, covering indexes, nested records, and a query planner. The key encoding convention maps record types and field values to lexicographically ordered byte sequences, so range scans over the ordered key space serve as index scans.

// Layers use structured key encoding to map higher-level concepts
// onto FoundationDB's ordered key space. A simple example:

const USERS_PREFIX = Buffer.from("\x01users\x00");

function userEmailKey(userId: string): Buffer {
  // Encoding: prefix + userId + separator
  return Buffer.concat([
    USERS_PREFIX,
    Buffer.from(userId),
    Buffer.from("\x00"),
    Buffer.from("email"),
  ]);
}

function usersByEmailIndexKey(email: string, userId: string): Buffer {
  const INDEX_PREFIX = Buffer.from("\x01idx/email\x00");
  return Buffer.concat([
    INDEX_PREFIX,
    Buffer.from(email),
    Buffer.from("\x00"),
    Buffer.from(userId),
  ]);
}

await db.doTransaction(async (tx) => {
  const userId = "alice-uuid-1234";
  const email = "alice@example.com";

  // Write primary record
  tx.set(userEmailKey(userId), Buffer.from(email));

  // Write index entry — both updates are atomic in the same transaction
  tx.set(usersByEmailIndexKey(email, userId), Buffer.from(userId));
});

Because all index updates happen inside a single FoundationDB transaction, the layer always maintains index consistency. There is no eventual consistency between the primary record and its indexes. This is the payoff of the serializable ACID guarantee at the lowest layer.

Production Considerations

Transaction size limits. FoundationDB enforces a 10 MB write-set limit per transaction and a 5-second transaction timeout. These are deliberate constraints that keep the commit path fast and the Sequencer’s version tracking bounded. Long-running transactions that scan large ranges will hit the timeout and must be restructured as ranged operations with vended read versions or segmented scans.

Conflict rates and read-set discipline. OCC conflict rates climb when many transactions write overlapping keys at high throughput. The primary lever is narrowing read-sets: use getRange with tight boundaries rather than scanning broad prefixes, and avoid reading keys that do not actually influence the write decision. FoundationDB exposes addReadConflictRange and addWriteConflictRange explicitly so layers can tune conflict surfaces without reading data just to register a conflict key.

Storage Server hardware. Storage Servers are the read-path bottleneck. SSDs are effectively mandatory in production. The log-structured write path tolerates slower sequential write throughput, but the B-tree read path requires low-latency random reads. Dedicated NVMe per Storage Server process is the standard recommendation.

TLog durability vs. latency tradeoff. The default TLog replication factor is two (commit requires two TLog acknowledgments). In deployments where cross-datacenter latency is acceptable, replication factor three across three availability zones eliminates any single-node failure risk. Increasing the replication factor linearly increases commit latency.

Monitoring the commit path. The most useful signal is commit latency percentiles broken down by shard. High p99 on a specific shard typically indicates a hot key range. The remedy is either key distribution redesign (avoid sequential keys that funnel all writes to the same Storage Server shard) or explicit manual sharding hints to the ClusterController.

Client library behavior. The FoundationDB client library handles retries, read-version vending, and conflict back-off automatically. The most common operational mistake is wrapping the doTransaction call in an outer retry loop that also catches conflict errors, which defeats the back-off logic and creates thundering-herd behavior under load.

Tradeoffs: FoundationDB vs. Alternatives

PropertyFoundationDBCockroachDBTiDBSpanneretcd
Isolation levelStrict serializableSerializableSnapshot isolation (default)External consistency (strict serial.)Linearizable (single-key)
Data modelOrdered key-valueRelational (PostgreSQL wire)Relational (MySQL wire)Relational + semi-structuredKey-value
Transaction scopeMulti-key, full clusterMulti-row, cross-shardMulti-row, cross-shardMulti-row, multi-regionSingle-key or limited multi-key
Write latencyLow (TLog quorum, no Paxos per commit)Medium (Raft per range)Medium (Raft + TiKV)High (TrueTime, cross-region)Low (single Raft group)
Read scalabilityHigh (Storage Servers scale independently)Medium (limited by Raft leader)High (TiFlash for analytics)High (read-only replicas)Low (single Raft group)
Schema / query layerNone (layers required)Full SQLFull SQLFull SQL + JDBCNone
Testing rigorDeterministic simulation (industry-leading)Jepsen testedJepsen testedInternal chaosJepsen tested
Operational complexityHigh (five roles, cluster config)Medium (single binary)High (TiKV + PD + TiDB)Very high (managed on GCP)Low (three-node Raft)
Best fitPlatform substrate, metadata stores, layersOLTP replacing PostgreSQLHTAP, MySQL migrationMulti-region global consistencySmall critical config/lock store

FoundationDB occupies a narrow but important niche: it is the right choice when you are building a data platform or storage layer that needs provably correct serializable transactions and you are willing to implement a domain model on top of the raw key-value API. For teams that need SQL out of the box, CockroachDB or TiDB are more immediately productive. For teams that need to store a few hundred megabytes of strongly-consistent configuration, etcd is simpler operationally. FoundationDB earns its complexity when the correctness guarantee is itself the product, and when the simulation-tested foundation is the thing your customers are buying.

The simulation framework is the real lesson. FoundationDB’s correctness comes from an engineering culture that said “we need a way to make distributed system bugs reproducible and exhaustive” before writing the database code. That investment compounded over years. The database is the output; the simulation framework is the method.

More in System Design

How Amazon Aurora Works Internally: The Log-Is-the-Database Architecture, Quorum Writes, and Storage-Compute Separation Behind Cloud-Native SQL
System Design ·

How Amazon Aurora Works Internally: The Log-Is-the-Database Architecture, Quorum Writes, and Storage-Compute Separation Behind Cloud-Native SQL

A deep dive into Aurora's storage-compute separation, the log-is-the-database design that pushes redo log processing to storage nodes, quorum-based replication across 6 copies in 3 AZs, protection group architecture, fast cloning, Serverless v2 scaling mechanics, and honest tradeoffs versus RDS, self-managed PostgreSQL, CockroachDB, and Cloud Spanner.

How CockroachDB Works Internally: Distributed SQL, Raft Consensus Per Range, and the Architecture Behind Serializable Transactions at Global Scale
System Design ·

How CockroachDB Works Internally: Distributed SQL, Raft Consensus Per Range, and the Architecture Behind Serializable Transactions at Global Scale

A deep dive into CockroachDB's internals: the 512MB range-based data model with automatic splitting, per-range Raft replication, MVCC timestamp ordering with hybrid logical clocks, DistSQL physical planning, serializable transactions with timestamp refreshes, closed timestamps for follower reads, online schema changes using multi-version schema descriptors, and production considerations for hotspots and write amplification.

How Container Runtimes Work Internally: Namespaces, Cgroups, and the OCI Stack From Docker Run to Process Isolation
System Design ·

How Container Runtimes Work Internally: Namespaces, Cgroups, and the OCI Stack From Docker Run to Process Isolation

Containers are not virtual machines and they are not magic. They are a thin composition of Linux kernel primitives: namespaces, cgroups, and a layered filesystem. This article traces exactly what happens between docker run and a running process, covering the OCI spec, containerd, runc, overlay filesystems, and container networking at the kernel level.

How YugabyteDB Works Internally: DocDB Storage, Tablet Splitting, and the Dual API Architecture That Scales PostgreSQL Horizontally
System Design ·

How YugabyteDB Works Internally: DocDB Storage, Tablet Splitting, and the Dual API Architecture That Scales PostgreSQL Horizontally

A deep dive into YugabyteDB internals covering the DocDB storage engine built on RocksDB LSM trees, per-tablet Raft consensus, YSQL and YCQL dual query layers, distributed MVCC with hybrid logical clocks, automatic tablet splitting and rebalancing, xCluster multi-region replication, and production considerations for hotspots, connection pooling, and schema design.