Skip to content

Frequently Asked Questions

Answers here are kept consistent with the status table, which is the page to trust when any two disagree.

A distributed data backend that serves streams (pub/sub), work queues (consumer groups), and a key-value cache, all as readings of one replicated append-only log, reached over QUIC. The design optimizes for predictable tail latency, high fanout, and strict slow-consumer isolation. The overview is the ten-minute version.

Not yet: it’s pre-1.0, so APIs can still change between releases, and it hasn’t had an external security review. Quite a lot works: multi-broker clusters, durable replicated streams, quorum acknowledgement, failover, consumer groups, the log-backed cache, and OIDC auth with RBAC. It is tested hard, including fault-injection suites. Releases are tagged (the newest is v0.6.1) and publish container images, but there is no second implementation of anything, and the faults it is proven against are the ones a single machine can produce. It’s ready for evaluation, prototyping and benchmarking.

Different centre of gravity. Kafka is a durable log first: everything is persisted, consumers pull, latency is a throughput trade-off, and the ecosystem is enormous. Felix is latency-and-fanout first: streams can be ephemeral (no disk on the hot path), each subscriber is isolated, and the same log also serves cache and queue semantics so you run one system instead of three.

Use Kafka when you need long retention, stream processing, or its connector ecosystem. Felix keeps durable logs and replays by offset, but what a client can read is bounded by one machine’s disk: sealed segments can be copied to a directory before retention deletes them, and nothing reads them back yet. It is built for live distribution, not for being your system of record.

Felix does speak part of Kafka’s wire protocol, so the two are not an either-or at the client: see the next question.

Producers, yes. Consumers, if they assign their own partitions. With FELIX_KAFKA_LISTEN set, every broker serves the Kafka protocol and each durable stream is a topic named <namespace>.<stream>, with one partition per shard and Felix’s own offsets. A Kafka producer can write to it with any compression codec and any acks, and an idempotent producer’s re-sends are recognised even after a leader failover. A consumer that calls assign() and keeps its own offsets can read it. This is tested with kcat (librdkafka).

What does not work is anything built on consumer groups or transactions: subscribe() with a group.id, committed offsets, transactional.id, and so Kafka Connect, Kafka Streams, ksqlDB, Debezium and MirrorMaker. Those are refused with an error that says why, rather than left hanging. Record keys and headers are not stored. The whole picture, with use cases and troubleshooting, is on Kafka compatibility.

Redis is a data-structure server with basic pub/sub bolted on. Felix is a log with a cache reading. If you need sorted sets, Lua, or transactions, that’s Redis. If you need high-fanout delivery with per-subscriber isolation, a cache whose changes you can watch (with offsets, so reconnects are gapless), and durable counters, and you’d rather not operate a broker and a cache separately, that’s what Felix is for.

Mostly for one property: streams multiplex over a connection without head-of-line blocking, so a retransmission for one subscription never stalls another. Beyond that: TLS 1.3 is part of the protocol (no unencrypted mode to misconfigure), handshakes are one round trip, and flow control exists per stream as well as per connection, which is where Felix’s backpressure story starts. The trade-off is real but small: some networks block UDP, and TCP has better debugging tooling. Details in QUIC Transport.

At every level, and always bounded. QUIC has flow-control windows per connection and per stream. The publish queue is bounded, and its overflow is a visible error rather than unbounded buffering. Each subscription has a bounded queue whose overflow policy (drop-new by default) is the isolation mechanism: a slow subscriber loses its own events instead of slowing anyone else. See Publish/Subscribe for the full story and the policy trade-off.

Per stream. An ephemeral stream lives in memory: lowest latency, lost on restart, right for data whose old values are worthless. A durable stream (durable: true, and the broker must run with FELIX_DURABLE_STORAGE_DIR) writes every record to a segmented, CRC-checked, crash-safe log, and subscribers can replay from any retained offset. By default a publish to a durable stream is acknowledged after the write, under the fsync policy (with the default periodic fsync the bytes are in the log file, so they survive a broker crash). FELIX_ACK_ON_COMMIT=0 acknowledges when it is queued instead, which is faster but can lose an acknowledged record, and a Quorum stream always waits (see ack_on_commit). A stream marked durable on a broker without a storage dir is rejected, not silently downgraded.

Retention is available and off by default. Set a stream’s retention, or FELIX_DURABLE_RETENTION_BYTES / FELIX_DURABLE_RETENTION_SECONDS for every stream that sets none, or a log grows until the disk ends. See Durable Storage.

Streams and caches are split into shards. The control plane assigns each shard a leader (and replicas) by rendezvous hashing, and brokers follow its assignment feed. One leader accepts a shard’s writes. A broker that receives a request for a shard it doesn’t lead forwards it or redirects the client. Leaders ship log records to followers. This is deliberately not per-shard Raft (docs/replication-design.md records why leases plus log shipping were chosen), and a lost leader is replaced only by a replica that provably holds the log. A Quorum stream’s publishes wait for a majority before acknowledging.

A broker that joins takes shards from any broker leading more than its share, and a drained broker hands off everything it leads before it is removed. Both go through a staged handoff so a shard is never served by a broker that has not seen its log (see Adding, draining and removing brokers).

Not built: follower reads (every read goes to the leader).

Not implemented, and not planned as a delivery guarantee. Felix offers at-most-once (plain subscriptions) and at-least-once (durable streams and consumer groups). Deduplication has to live in the application anyway, because only it knows what makes two records “the same”. Put it there, keyed on something the record carries.

Measured numbers live in one place, Benchmarks, with methodology. The shape of it: single-message publish-and-ack round trips are low hundreds of microseconds on loopback, cache operations similar, and batched throughput runs trade per-message latency for rate. Always benchmark release builds (--release). Debug builds are 10–100x slower and tell you nothing.

For latency under load, FELIX_EVENT_BATCH_MAX_DELAY_US: the longest an event waits for its batch to fill once events arrive faster than the broker drains them. An idle subscriber’s events are sent without waiting. For throughput, FELIX_EVENT_BATCH_MAX_EVENTS and its byte sibling. For memory, the flow-control windows (FELIX_*_RECV_WINDOW), since window × connections bounds in-flight data. The environment variable reference has the full list with defaults. Change things off a measurement.

Yes to both. See Docker Compose and Kubernetes. Each release publishes images to ghcr.io/getfelix/felix-broker:<version> and ghcr.io/getfelix/felix-controlplane:<version> (ghcr.io/gabloe for 0.6.0-preview and earlier). The Helm chart is not published, so install it from deploy/helm/felix in the repository. The broker ships what an orchestrator expects: /live and /ready that answer different questions, and a bounded drain on SIGTERM (graceful shutdown).

Prometheus metrics on the metrics endpoint (/metrics), structured logs via RUST_LOG, and optional OTLP tracing. Which metrics answer which operational questions is the whole point of the observability page.

Every QUIC connection uses TLS 1.3; the control-plane API is plain HTTP until you give it a certificate. The control plane does OIDC token exchange and issues tenant-scoped EdDSA tokens. The broker enforces RBAC, with delegation rules that prevent privilege escalation. Broker-to-broker traffic uses mTLS, bound to the node id, when certificates are configured. Not built: encryption at rest, end-to-end payload encryption, audit logging. The security page states each plainly.

Can I grant stream access by IdP group instead of per-user?

Section titled “Can I grant stream access by IdP group instead of per-user?”

Yes. Configure groups_claim for the tenant issuer, then bind RBAC roles to group:<issuer>#<name> subjects. During token exchange, Felix maps incoming group claims to those subjects, scoped by the token’s issuer, and evaluates role permissions:

  • grouping: g, group:https://login.example.com#g1, role:reader, tenant-a
  • policy: p, role:reader, tenant-a, stream:tenant-a/payments/*, stream.subscribe

Will there be clients for other languages?

Section titled “Will there be clients for other languages?”

Rust, Python and TypeScript ship today, the latter two as bindings over the Rust client rather than reimplementations. Both pass every required scenario in the client conformance catalogue, and CI is gated on it. Go and C# are not started; felix-capi, the C ABI they will bind to, covers connect, publish and subscribe so far, all asynchronous through callbacks. The wire protocol is language-neutral and documented precisely for this reason (see Wire Protocol), and a conformance runner exists to check an implementation against it.

Why won’t the broker start? / Why is latency high? / Connection issues?

Section titled “Why won’t the broker start? / Why is latency high? / Connection issues?”

The troubleshooting guide covers these with commands. The three most common answers: you’re running a debug build (use --release), a firewall is dropping UDP on the broker port, or FELIX_EVENT_BATCH_MAX_DELAY_US is set high and your load keeps the subscriber busy enough that batches wait for it.

Fork, branch, make the change with tests, run task lint and task test, open a PR. The contributing guide has the details, and How Felix Works is the fastest way to build a mental model of the codebase.