What is Celeriant
Celeriant is an append-only event store for the write side of CQRS. It is the source of truth: services append events, and read models are projected from the log. It is not a query database and it is not a message broker.
The problem it solves
Event sourcing rests on one question: can I append this event only if nobody changed the aggregate under me?
Postgres can answer it with a version check and a conditional insert, and it holds up at low concurrency. Measured on identical hardware, Marten on Postgres peaks at 42,721 writes a second at 500 connections, then degrades as connections climb and collapses to 901 at 12,000. Every commit pays a synchronous WAL flush, every connection is an OS process, and the stream version row is a lock. See Coming from Postgres.
Kafka plays a different game. Each service keeps its own Postgres, writes state plus an outbox row, and CDC publishes the outbox to topics for other services. Throughput solved, and inside one service the conditional write still works, because Postgres does it.
The gap is between services. A service validates its next event against state assembled from other services' topics, and those topics lag. Two services clear validation against stale views at the same moment, both append, and an invariant is gone. Kafka cannot say "append only if the aggregate is still at version N"; that request has been open since 2015.
So teams bolt on sagas, DLQs and idempotency keys at every consumer. Scaffolding around a missing primitive, and it fails in production, not in tests.
Celeriant makes the conditional write a first-class operation. Services still deploy independently; writes go to one place.
What you get
- Optimistic concurrency. Append only if the aggregate is still at the version you read. One write can cover several aggregates atomically, provided they route to the same shard. See Optimistic concurrency and Consistency boundaries.
- Idempotent retries. Each writer tags its writes with a client id and a client sequence number, so a retried write is rejected instead of appended twice. See Idempotent retries.
- Strict per-aggregate ordering. Reads by version range, filterable by event type. See Reads and ordering.
- Server-side schema validation for JSON Schema, Avro, or Protobuf. An event whose type has a registered schema is validated before it enters the log. See Schema validation.
- Two-node durability. The leader
fdatasyncs every write through Direct I/O, then replicates it to the follower, which does the same, before the ack. If the follower is down, the leader replicates to S3 instead, so an acknowledged write always lives on two storage systems. See Durability and safety. - A watch API, so read models follow the log in real time. See Watch and subscribe.
- Memory bounded by a budget, not by cardinality. Caches take a configured share of RAM; a cold aggregate is found by a bloom-filtered reverse scan of the log, not an in-memory index entry per aggregate.
- Clients for Rust and .NET, plus a CLI. See Clients.
Where it fits
Celeriant is the write side. You append events to it and project them into a read store (Postgres, a search index, a cache) that answers queries. If your system already separates writes from reads, Celeriant slots in where the write log belongs. If it does not, read When not to use it first; CQRS is a real tradeoff, not a free upgrade.