Event Data Storage
Event storage is the backbone of event-sourced architectures, audit trails and CQRS systems: every state change becomes an immutable, append-only document.
Introduction
Event storage is the backbone of event-sourced architectures, audit trails and CQRS systems: every state change becomes an immutable, append-only document. MongoDB's append-friendly write path, time-series collections and powerful aggregations make it a strong default for event stores up to billions of events.
Compared to dedicated event stores (Kafka, EventStoreDB), MongoDB gives you flexible projections, ad-hoc replays, and rich queries out of the box — at the cost of slightly less throughput at extreme scale.
Understanding the topic
Designing an event store on MongoDB:
- Append-only collection — no updates, no deletes;
$setis forbidden by convention and validators. - Monotonic event id via
{ aggregateId, version }— unique compound index ensures ordering. - Event envelope —
{ id, aggregateId, type, version, ts, actor, payload }. - Projections — materialized read models built via change streams or scheduled
$merge. - Change streams — push-based feed of new events to downstream consumers.
- Snapshots — periodic state snapshots in a side collection to bound replay cost.
Informative example
Append + project pattern with change streams:
// Append (idempotent with optimistic lock on version)await db.events.insertOne({_id: ulid(),aggregateId, type: "OrderPaid", version: nextVersion,ts: new Date(), actor, payload: { amount, currency }});// Unique index that enforces orderingdb.events.createIndex({ aggregateId: 1, version: 1 }, { unique: true });// Project — change stream rebuilds the orders_read modeldb.events.watch([{ $match: { "fullDocument.type": { $in: ["OrderPaid", "OrderShipped"] } } }]).on("change", c => projectIntoReadModel(c.fullDocument));
Real-world use
Many ledger, audit and "what changed?" systems run on MongoDB event stores. Change streams + materialized views give you CQRS without operating Kafka.
Best practices
- Mark the events collection append-only with a
$jsonSchemavalidator that forbids updates outside the writer service. - Snapshot aggregates every N events so replays stay cheap.
- Use change streams (resumable from a token) so projectors survive restarts.
- Store actor + correlation id on every event — the audit story is the killer feature.