Concepts
Event store
Most databases keep the answer. They store how things are right now, and every update overwrites what came before. When the business later asks how it got there, that history is gone.
An event store keeps the history instead. Every change is recorded as an immutable event, in order, and current state is derived from it. A purpose-built event store also validates every new event against that history before accepting it, so business rules hold at the moment of the write.
One row, overwritten
Each change replaces the last value. The row says where the student is today, not how they got there.
| ID | STATUS | COURSES | CITY |
|---|---|---|---|
| s-1 | registered | 0 | Amsterdam |
An ordered history, appended
Each change is an immutable event, written after the last one and never written over.
This page explains what an event store is, what it guarantees, how event store databases differ from the databases and platforms teams often use instead, and what it takes to run one at billions of events.
What is an event store?
An event store is a database that keeps every change in your application as an immutable, ordered event, retrieves one entity’s history efficiently even at billions of events, and validates every new event against that history before accepting it.
Any database can accumulate records, and none of them becomes an event store by doing so. The name is earned by the two operations that must hold under production load. Retrieval has to isolate one entity’s ordered history from billions of records during reads at latencies a live application can tolerate, and every append must be validated against the existing history at write time.
Each change goes after the last one. Recorded events never change.
An index picks out only the events about the entity at hand.
The store rejects an append when the history it was decided on has changed.
What is an event?
An event is a record of something significant that happened in your system, written in past tense, for example RegistrationConfirmed or PaymentRejected. These are facts and are never changed afterwards. What happened, happened. When circumstances change, a new fact is appended after the old one rather than written over it. Each event carries its type, its position in the store, when it was recorded, the identifiers or tags that link it to the entities it concerns, and its payload.
Named for the business fact, not the technical change. Handlers and queries select on it.
How is an event store different from a database?
A traditional database stores the answer, while an event store stores everything that produced said answer. Where a relational database holds an entity’s current status and nothing else, an event store holds every change that produced that status, in sequence, permanently. Applications built on event sourcing derive their current state from those events instead of keeping it in mutable rows.
An event store guarantees consistency at the moment of append, immutability once an event is written, and strict ordering on read. The first is crucial because every new event is checked against the history already recorded, so conflicting facts are rejected before they enter the record rather than untangled after they corrupt it.
History also gives auditors and regulators a complete account of what happened and in which order, without a separate audit log that can drift away from the data it describes.
Keeping history pays off twice. A live application can act on it while a request is still in flight. And when the business asks a question nobody anticipated at design time, the answer is read from the events instead of being wished back into existence.
Can’t I use any database as an event store?
You can. An event store is a log with an index in front of it, and any database can hold a log, one table with a row per event and a position column. Teams have run event-sourced systems on PostgreSQL, SQLite, MongoDB and DynamoDB, and at a small scale it works.
The work is in what you add on top, so that both operations still hold with several writers, several application instances and a few billion events. The two boards below show those pieces for PostgreSQL and for SQLite, the two most common do-it-yourself choices. A piece marked “you build it” is code you write, test and operate. A piece marked “not available” is a limit you design around.
The honest summary is that PostgreSQL gives you a correct event store once you have built ordering, subscriptions, snapshots, retention and failover around it, and SQLite gives you ordering for free because it has one writer, then stops at one machine for the same reason. Axon Server is that list built once, as one system, with the routing that keeps it fast.
Is it slow to event source millions of events?
It would be if systems actually did that on every request, and the reason they do not is a two-layer mechanism. Consider an entity whose history spans five thousand events. With snapshots taken every hundred events, the first four thousand nine hundred are compressed into a single stored state. When a request arrives, the application loads that snapshot, applies only the few dozen events recorded since, and responds fast, without touching the thousands of events underneath.
The second layer is aggregate caching, which removes the event store from the hot path on reads entirely for most cases. When the same instance of an application is always responsible for the same entity, that entity’s state is often still in memory from the last request, so the application applies its decision directly from cache and sends only the resulting append to the store. The consistency check in the event store makes this safe, because it verifies that the application’s view of history is current before accepting the event.
Apply every event since the entity was created.
With a policy of one snapshot per 100 events, events 1 to 4,900 are folded into one stored state.
Same entity, same instance, state still in memory from the last request. The store only verifies the append.
Underneath is the operative word, because those events are compressed out of the hot path, not thrown away. If a bug corrupted the snapshots, the full history still exists to source from scratch and rebuild them correctly, and if a new analytical question arrives, the complete record is there to replay. Snapshots buy speed without spending history.
What does Axon Server add as an event store?
Axon Server brings the pieces teams otherwise build around a general-purpose database into one system.
01
Consistency on every append, per aggregate or across entities with dynamic consistency boundaries.
02
Fast per-entity reads out of billions of events, through dedicated indexes and snapshots.
03
High availability through Raft-based replication groups and dedicated node roles.
04
Histories that grow for years, with tiered storage moving older segments off primary disks.
The broker does not know which instance holds the order’s state.
The routing key sends every command for o-7 to the same instance, as long as the set of instances is stable.
instance 1
instance 2
instance 3
How does Axon Server store events?
Axon Server is built around an append-only log. Its storage is organized in layers, each with one job.
First, events are appended to segment files. Each context has its own event store on disk. New events are written to the end of the current segment, 250 MB by default, and closed segments are never changed in place. Every event receives a position in one global sequence.
Second, indexes find one entity’s history. In aggregate-based contexts, a global index locates an aggregate’s latest event, and per-segment pointers lead back to the earlier ones, skipping the segments in between. Contexts built for dynamic consistency boundaries (DCB) use a different index, one built to answer criteria over tags and event types.
Third, snapshots live in their own store. A snapshot holds the computed state of an entity at a known position, so loading it means reading the snapshot and only the events recorded after it, in one operation. The application decides when to take a snapshot. Since Axon Server 2026.1, DCB contexts source snapshot and events together as well.
Fourth, older data can move to other tiers. On each node, tiered storage moves closed segments to other storage based on time or size, so recent segments stay on fast disks and older ones on cheaper storage, while the full history stays readable. With secondary nodes in the cluster, primary nodes can drop the oldest data entirely.
New events go to the end of the current segment. Closed segments are never changed in place.
segment 1CLOSED
segment 2CLOSED
segment 3CLOSED
segment 4OPEN
In aggregate-based contexts, a global index points to the latest event and per-segment pointers lead back, skipping segments in between. DCB contexts use a different index.
Computed state at a known position, kept in its own store. The application decides when to take one.
Closed segments move off primary disks by age or size.
PRIMARY · FAST DISK
OTHER TIER · ARCHIVE
The index returns only student s-1’s events, folded into current state.
A cursor reads the whole log in order to build a view nobody planned for.
FOLDED FROM 0 EVENTS
Reading follows the two paths the figure shows. Sourcing reads one entity’s events, bounded and fast, to make a decision. Streaming reads the global log from a tracking token onward, which is how event processors keep projections up to date and how a replay rebuilds a view from the very first event.
How does an event store keep appends consistent?
Every append to Axon Server can carry a condition. Before an event reaches the store, the application decides what has to be true for that event to be valid and attaches that requirement to the append. The store evaluates the condition against the current history at the moment the append arrives, rather than trusting whatever the application assumed was true when it issued the request. If the condition fails, nothing is written, and the application re-reads and decides again. No cleanup job, no deduplication logic downstream, and no idempotency key bolted on.
In aggregate-based contexts, the condition is the aggregate’s sequence number, so two writers that both read sequence 41 cannot both append 42. In DCB contexts, it is an append condition made of event criteria over tags and types plus the consistency marker returned when the events were read. The append fails if any event matching the criteria arrived after that marker.
The second form lets one rule span several entities, such as a course’s capacity and a student’s subscription limit, without sagas and without fixing aggregate boundaries before the first event is written.
Two writers that read sequence 41 cannot both append 42.
Criteria over tags and types, plus the marker from the read.
WRITER AREAD
loaded order o-7 at sequence 41
WRITER BREAD
loaded order o-7 at sequence 41
How does Axon Server stay available?
In a cluster, Axon Server nodes form replication groups that use the Raft protocol. Each group elects a leader, and an append is confirmed once a majority of the group’s primary nodes has stored it, plus any active backup nodes the group is configured to wait for, so losing one primary node out of three costs neither data nor availability. Nodes can also take other roles. Active and passive backup nodes keep copies of the event store for recovery, secondary nodes keep the full history so primary nodes only need to hold recent segments, and messaging-only nodes route messages without storing events. Clients reconnect to another node automatically.
axonserver-1LEADER
writes event 7
axonserver-2
waiting
axonserver-3
waiting
Why one system
for events and messages
Because in a live event-sourced system, where a message is handled determines how fast the history can be used. Applications do not exchange only events, since a command such as “change my address” is the intent to do something, and the event is the proof that it happened. That intent has to reach the right place, because aggregate caching only works when requests for the same entity consistently land on the same application instance, where that entity’s state is already warm in memory. Route the command elsewhere and the cache is cold, the application sources from scratch, and the speed the architecture promised evaporates.
Splitting storage and routing across two systems means keeping two views of ownership and order aligned with each other, which is yet another distributed-systems problem acquired for no benefit. Combining them means the knowledge exists once, which is why Axon Server embodies both jobs in a single integrated system.
The same history also feeds analysis. Axoniq Insights lets data and business teams query event history directly, so a new question does not have to become a new projection.
How do event stores compare?
Many systems can hold events. Fewer are designed around the operations that define an event store. The table compares Axon Server with other purpose-built event stores, event sourcing libraries, general-purpose databases used as event stores, and two systems that are often mistaken for one. It reflects each product’s public documentation as of November 2026.
| System | Built for event sourcing | Optimistic append | DCB append conditions | Per-entity reads | Global order | Catch-up subscriptions | Snapshots | Clustering and HA | Event transformation | Command and query routing | License and hosting |
|---|---|---|---|---|---|---|---|---|---|---|---|
| PURPOSE-BUILT EVENT STORES | |||||||||||
| Axon ServerAxoniq | Yes | Yes | Yes1 | Yes | Yes | Yes | Yes | Yes2 | Yes2 | Yes | Free for non-production, paid self-hosted plans, managed cloud |
| KurrentDBformerly EventStoreDB | Yes | Yes | Partial3 | Yes | Yes | Yes | No, pattern | Yes4 | Partial5 | No | Source-available Kurrent License, Kurrent Cloud |
| EventSourcingDBthe native web | Yes | Yes | Yes | Yes | Yes | Yes | No, pattern | No6 | No | No | Closed source, free up to 25,000 events |
| UmaDBDCB-native store | Yes | Yes | Yes | Yes, by query | Yes | Yes | n/a, undocumented | n/a, undocumented | n/a, undocumented | No | MIT or Apache 2.0, self-hosted |
| EVENT SOURCING LIBRARIES | |||||||||||
| Marten.NET library on Postgres | Yes | Yes | Yes | Yes | Partial7 | Yes | Yes | Partial, via Postgres | Yes, masking | No | MIT, runs on any Postgres |
| Akka PersistenceJVM library | Yes | Partial8 | No | Yes | No | Yes | Yes | Yes, Akka Cluster | Partial, adapters | Partial8 | BSL 1.1, Akka platform |
| GENERAL-PURPOSE DATABASES USED AS AN EVENT STORE | |||||||||||
| PostgreSQLappend-only table | No | DIY9 | DIY | Yes, index | DIY9 | DIY | DIY | Partial9 | DIY | No | PostgreSQL License, many managed offerings |
| MongoDBevent documents | No | DIY | DIY | Yes, index | No | Partial10 | DIY | Yes, replica sets | DIY | No | SSPL, MongoDB Atlas |
| Amazon DynamoDBevent items | No | Yes, conditions | Partial11 | Yes | No | Partial11 | DIY | Yes, managed | DIY | No | Proprietary, AWS only |
| Azure Cosmos DBevent documents | No | Yes, ETag | Partial12 | Yes | No | Partial12 | DIY | Yes, managed | DIY | No | Proprietary, Azure only |
| OFTEN MISTAKEN FOR AN EVENT STORE | |||||||||||
| Apache Kafkastreaming platform | No | No13 | No | No13 | No, per partition | Yes | No | Yes | No13 | No | Apache 2.0, Confluent Cloud, Amazon MSK |
| Temporalworkflow persistence | No14 | n/a | n/a | n/a | n/a | n/a | n/a | Yes | n/a | n/a | MIT, Temporal Cloud |
Scroll sideways to see every column.
- Axon Server. DCB contexts append with an append condition: event criteria over tags and types plus a consistency marker. Fully supported since 2025.2, and existing contexts can be converted since 2026.0. DCB is a licensed capability, available from the Team plan upward.
- Axon Server. Clustering and replication groups depend on the license. Persistent Event Transformation replaces or deletes events in aggregate-based contexts; since 2026.1, transformed contexts can be converted to DCB as well.
- KurrentDB. Multi-stream appends check an expected version per stream. Since 26.1 an append can also carry expected versions for streams it does not write to. That covers some cross-entity rules, but conditions are stream-based, not tag or query based.
- KurrentDB. Clustering requires an Enterprise license from version 26.2, per Kurrent’s licensing announcement.
- KurrentDB. Redaction is a separate tool run against a running server, once per node: licensed through 26.1, free from 26.2. Streams can be deleted or truncated and scavenged.
- EventSourcingDB. The documentation states high availability is planned, not yet available.
- Marten. The global sequence is a Postgres sequence, which can have gaps. The async daemon tracks a high water mark to handle them.
- Akka Persistence. Consistency comes from a single writer per entity through Cluster Sharding, not an expected version from the client. Sharding routes commands to entities, but there is no command or query bus.
- PostgreSQL. Expected versions usually mean a unique (stream, version) constraint. Sequences are not transactional, so positions can have gaps and become visible out of commit order. Automatic failover needs external tooling or a managed service.
- MongoDB. Change streams give live subscriptions, but resuming depends on oplog retention, so catching up from the first event is up to you.
- DynamoDB. Transactions with condition checks across up to 100 items can emulate some multi-entity rules. Streams keep records for 24 hours.
- Cosmos DB. Transactional batches are atomic within one logical partition key, and the change feed is ordered per partition key.
- Kafka. A producer cannot append on the condition of an expected offset, reading one entity means scanning a partition, and records cannot be rewritten in place.
- Temporal. A durable workflow engine. Its event history is an internal log per workflow execution, stored in Cassandra, MySQL or PostgreSQL and capped at 51,200 events. It is not a store for application domain events.
Get Started with Axoniq
Run your event store on Axon Server
Validated appends and fast per-entity retrieval are the operations a general-purpose database makes you build. Axon Server brings storage, consistency, replication and routing into one system, so your team spends its time on the business problem.