Guarantees

How Felix keeps your data

What an acknowledgement promises when a broker fails, and how those promises are tested. Each part links to the full write-up in the docs.

Three brokers and three shards. Each broker leads one shard and follows the other two, so every shard has a copy on each broker.shard 0shard 1shard 2broker 1broker 2broker 3leaderfollowerfollowerfollowerleaderfollowerfollowerfollowerleader
Clustering

Shards, replicas and failover

A stream is split into shards. The control plane gives each shard a leader broker and followers, and moves shards between brokers while they keep serving. What an acknowledgement promises is set per stream.

stream ordersshards 3replication_factor 3consistency Quorumdurable true
A three-broker Felix cluster running one stream with three shards, each replicated to all three brokers.Each broker leads one shard and follows the other two. A write for shard 0 goes to its leader on broker 1, which ships it to the followers on brokers 2 and 3; the client is acknowledged once two of the three hold it. Broker 1 then fails. Shards 1 and 2 keep serving, and the control plane promotes broker 2's copy of shard 0, which already holds the acknowledged record. The client follows the new leader and its next write is held by brokers 2 and 3. Broker 1 comes back as a follower and catches up, and placement moves shard 0 back to it.SHARD 0SHARD 1SHARD 2CLIENTBROKER 1UPDOWNBROKER 2UPBROKER 3UPLEADERFOLLOWEROFFLINEFOLLOWERFOLLOWERFOLLOWERLEADERFENCEDLEADERFOLLOWERFOLLOWERFOLLOWERLEADER1 of 32 of 3 · ack3 of 31 of 32 of 3 · ack3 of 3CONTROL PLANEplaces shards on brokerspromotes shard 0 on broker 2broker 1 rejoinsmoves shard 0 to broker 1
One stream, three shards, three brokers. Every shard has a copy on each broker, and each broker leads one of them.

What an acknowledgement promises

Leader

The shard's leader answers alone.

Acknowledged
On a durable stream, when the leader has written the record to its log. On an in-memory stream, when it has queued it.
Leader lost
Records it had not yet shipped are lost. With FELIX_ACK_ON_COMMIT=0, so is a record still queued when it crashes or its lease runs out. Replication lag bounds that window and is exported as a metric.
Failover
Roughly 13 to 26 s after a crash with the defaults. A broker stopped gracefully hands its shards to followers first.

Closing the loss window would take a majority round on every write, which is what Quorum does.

Quorum

A majority of the replicas answers.

Acknowledged
When a majority of the replica set holds it, the leader counted: 2 of 3, 3 of 5.
Leader lost
Nothing acknowledged is lost. Only a replica holding every acknowledged record can be promoted.
Failover
About 5 s on a durable stream, on its followers' word, once the generation_start, majority_ack and lease_free_reads fleet features are finalized. A new cluster finalizes them when its first broker registers; an upgraded one waits for the operator. Otherwise on the lease.

With no majority reachable, a publish fails after 5 s with quorum_timeout. The record may still survive, so the outcome is unknown, not refused.

Durability, per stream

  • In memory, the default: never on disk and not replicated. Delivery is at most once, and records go with their leader.
  • durable: true: a checksummed segment log, replicated to the shard's followers and replayable from any retained offset. Delivery is at least once.
  • The broker's fsync mode says when a write is on disk: on_commit before the ack, periodic (the default) within 250 ms, none when the OS gets to it. By default an acknowledged record is already in the log file, so it survives a broker crash; under periodic a machine crash can lose the last interval.
  • With replication_factor: 1, the default, a durable shard has no follower and waits for its broker to return.

Ordering

  • Per shard, so per routing key. A record with no key goes to shard 0.
  • A durable stream with one shard is totally ordered.
  • Nothing is ordered across streams, and on an in-memory stream two concurrent publishers can be seen in either order.

Delivery

  • Each event on a durable stream carries its offset. Subscribe from earliest, latest or an offset.
  • Under the default overflow policy, a slow subscriber is dropped from, never waited on. On a durable stream its subscription ends at the first drop and names the offset to resume from.
  • Under overload, with the defaults, a broker sheds fire-and-forget publishes. An acknowledged publish it has no room for is refused with a retryable overloaded error: at once on an in-memory stream, or on a durable stream (acked on commit, the default) after waiting up to 2 seconds for room (FELIX_PUBLISH_QUEUE_WAIT_MS). With pub_ingress_wait, fire-and-forget publishers wait instead.
  • Idempotent producers make a re-sent publish land once. Exactly-once delivery is not offered.

Partial The control plane decides every failover, so while it is down a dead leader is not replaced. Read the full contract

How Felix is tested

Tested against crashes, partitions and power loss

The replication claims above rest on evidence anyone can rerun: models that search every interleaving within their bounds, a checker that drives a real cluster through faults, and storage tests that rebuild the disk a power cut could leave after each flush.

campaign nightlyfaults 20 minstreams Quorumseed new each night
  1. 1kill broker 2
  2. 2freeze broker 3
  3. 3partition broker 1
  4. 4one-way link broker 3
  5. 5clock 20× broker 2
  6. 6power loss every broker
  7. 7crash mid-move control plane

Faults in progress. Nothing is judged until every fault has healed.

Final read. Every stream is read back from its leader and compared with each acknowledgement.

0

acknowledged records lost or reorderedin the final read, taken once the last fault healed

  • No acknowledged write is missing from the final read.
  • A write acknowledged before another was sent sits earlier.
  • No value sits at two offsets.
  • Every read agrees with the final log where they overlap.
  • A write refused outright never appears.
  • Subscribers get offsets in order, with every record or a visible gap.
An illustration of the nightly history campaign, not a recorded run. Each night runs 20 minutes of single faults on three brokers and 20 minutes of compound ones, such as the power loss and the control-plane crash, on four. Each uses a new seed, which a failing night prints so it can be replayed.

TLA+ models

Replication, promotion and shard moves are specified in TLA+. TLC checks every interleaving within small bounds, and random walks sample behaviours hundreds of steps long. The model found a way to lose an acknowledged Quorum record that the design's prose had missed, and the broker was fixed.

every code changewalks nightly

History checker

Clients write to Quorum streams and a Quorum cache on a real cluster while faults strike: kill, freeze, partition, one-way links, clock faults, failed fsyncs, shard moves, power loss of every broker and a control-plane crash mid-move. Every operation is recorded and checked against eleven rules.

every pull request20 min nightly

Power-loss sweeps

The storage engine runs a workload while a test layer records what each flush made durable, then rebuilds the disk a reboot could find, with unsynced writes dropped, torn or zeroed. Every record acknowledged as durable must come back, with no gap before it.

every pull request110 seeds nightly

Recovery rules

  • A torn tail is repaired: a record a crash left unfinished is cut away.
  • Corruption anywhere else refuses to start, naming the segment and byte position.
  • Indexes are rebuilt from their segments, never trusted.

Client conformance

A catalogue of scenarios every client must pass, with the Rust client as the reference. CI checks the Python and TypeScript clients against it and drives the C interface against the same fixture. A cluster suite checks that a client can't tell one broker from three.

every code change

Fuzzed decoders

The decoders for wire frames, Kafka requests and record batches, and the on-disk records, indexes and recovery state are fuzzed. A short run gates each pull request, and a nightly campaign keeps building its corpus.

every pull requestnightly

How Felix is tested, in full