Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Eventuous/eventuous/llms.txt

Use this file to discover all available pages before exploring further.

A checkpoint records the last successfully processed position in an event stream. When a subscription restarts — whether after a planned shutdown or a crash — it loads its checkpoint and resumes reading from that position rather than from the very beginning. Without checkpoints, every restart would replay the entire event history, making long-running projections impractical.

The Checkpoint type

A checkpoint is a lightweight value type:
public record struct Checkpoint(string Id, ulong? Position) {
    public static Checkpoint Empty(string id) => new(id, null);

    public readonly bool IsEmpty => Position == null;
}
MemberDescription
IdMatches SubscriptionOptions.SubscriptionId; used as the document/row key in the store
PositionLast committed global or stream position; null means “start from the beginning”
IsEmptytrue when Position is null
Checkpoint.Empty(id)Creates an initial, position-less checkpoint

ICheckpointStore

Any persistent backend can be used as a checkpoint store by implementing two methods:
public interface ICheckpointStore {
    ValueTask<Checkpoint> GetLastCheckpoint(
        string checkpointId,
        CancellationToken cancellationToken);

    ValueTask<Checkpoint> StoreCheckpoint(
        Checkpoint checkpoint,
        bool       force,
        CancellationToken cancellationToken);
}
  • GetLastCheckpoint — called once at startup; returns Checkpoint.Empty when no record exists yet.
  • StoreCheckpoint — called by CheckpointCommitHandler; the force flag bypasses any batching and writes immediately (used at shutdown or on errors).

NoOpCheckpointStore

For ephemeral subscriptions that do not need to persist progress — such as integration tests or transient catch-up reads — use NoOpCheckpointStore:
// Always starts from the beginning (or from a fixed offset)
public class NoOpCheckpointStore(ulong? start = null) : ICheckpointStore
It holds the last stored position in memory only — there is no durable backing store, so the position is lost when the process exits. On the next startup the subscription will start from start (the constructor argument) or from the beginning if start is null. It also fires an optional CheckpointStored event that is useful in tests for asserting progress.
var store = new NoOpCheckpointStore();
store.CheckpointStored += (sender, checkpoint) =>
    Console.WriteLine($"Position: {checkpoint.Position}");

MongoCheckpointStore

The MongoDB implementation stores one document per subscription in a dedicated collection:
public class MongoCheckpointStore(
    IMongoDatabase database,
    MongoCheckpointStoreOptions options,
    ILoggerFactory loggerFactory) : ICheckpointStore
It can also be constructed with just the database and a logger factory, using default options:
new MongoCheckpointStore(database, loggerFactory);

MongoCheckpointStoreOptions

public record MongoCheckpointStoreOptions {
    // MongoDB collection that holds checkpoint documents. Default: "checkpoint"
    public string CollectionName  { get; init; } = "checkpoint";

    // Batch up to this many store calls before flushing. Default: 1
    public int    BatchSize       { get; init; } = 1;

    // Flush at least every N seconds. Default: 5
    public int    BatchIntervalSec { get; init; } = 5;
}
Increasing BatchSize and BatchIntervalSec reduces write pressure on MongoDB at the cost of replaying more events after an unclean shutdown.

How checkpoints are committed

Checkpoints are not written after every single event. EventSubscriptionWithCheckpoint<T> uses a CheckpointCommitHandler to buffer commit positions and flush them in the background:
Event 1 ──► Ack ──► CheckpointCommitHandler ──► (buffering)
Event 2 ──► Ack ──►         │
Event 3 ──► Ack ──►         │
                   ─────────┘
                   batch full or delay elapsed


                   ICheckpointStore.StoreCheckpoint
The CheckpointCommitHandler uses CommitPositionSequence — a sorted set — to track in-flight positions. It will only advance the committed position to the highest consecutive sequence number without gaps. If events arrive out of order (as they do with PartitioningFilter), the handler waits until the gap is filled before writing a new checkpoint.
Tuning optionLocationDefault
Batch sizeSubscriptionWithCheckpointOptions.CheckpointCommitBatchSize100
Commit delaySubscriptionWithCheckpointOptions.CheckpointCommitDelayMs5 000 ms

Registering a checkpoint store

Subscription-scoped store

The most common approach — attach the store to a single subscription using .UseCheckpointStore<T>() on the builder:
services.AddSubscription<AllStreamSubscription, AllStreamSubscriptionOptions>(
    "BookingsProjections",
    builder => builder
        .UseCheckpointStore<MongoCheckpointStore>()
        .AddEventHandler<BookingStateProjection>()
);
The store is registered as a keyed singleton under the SubscriptionId, so different subscriptions can use different stores independently.

Application-wide store

To share a single store instance across all subscriptions, register it globally:
services.AddCheckpointStore<MongoCheckpointStore>();

In-memory store (testing)

For tests or transient subscriptions, skip persistence entirely:
services.AddSubscription<AllStreamSubscription, AllStreamSubscriptionOptions>(
    "TestSubscription",
    builder => builder
        .UseCheckpointStore(new NoOpCheckpointStore())
        .AddEventHandler<MyTestHandler>()
);
When EventuousDiagnostics is enabled (the default in development), Eventuous automatically wraps the checkpoint store in a MeasuredCheckpointStore that records OpenTelemetry metrics for checkpoint load and store operations.

Build docs developers (and LLMs) love