MongoEventLog
The EventLog of one bounded context on MongoDB, a first-class log backend (documentation/designs/03-log-and-snapshot.md §2). It needs a replica set, for its majority reads and writes.
The log takes one collection per aggregate type, wow_log__<context>__<aggregate>; a context-level collection holds one counter document per partition, so partitions, their sequences, leases and checkpoints stay per context. A counter also holds its partition's lineage, parent (null) and epoch (0), which lineage reads back, and the partition's open blocks.
Appends. An append is one insertOne acknowledged by a majority, at a sequence this node allocated from a block it reserved on the partition's counter (MongoSequencer); no transaction. The unique keys settle the rest: a taken version is a conflict, a taken request a duplicate, a taken position a block another node sealed (the append takes a new sequence). Every sequence the tail passes is final: an entry, or a placeholder. A sequence allocated and not written gets a placeholder at once (a conflict, a refusal) or as soon as it can (an unknown outcome, written over by the stream if it landed); a block's unused remainder is closed by one range placeholder; and the block of a node that stopped is sealed with a placeholder at each of its sequences left, in each collection it may write, so a late write of that node meets a taken position. Placeholders have no aggregate, version or request: the reads by aggregate and the version and request keys never see them. An aggregate's next stream takes a sequence after its previous one, which the log remembers from its reads and appends, so a partition keeps each aggregate's streams in version order across owners.
Aggregate collections. An aggregate type's collection and its indexes are created the first time this log touches the type, or up front by createCollection; a new aggregate type needs no restart. Every aggregate collection is recorded in a registry collection (REGISTRY) before its first entry is written, and tail reads the context's collections from it.
Tail. tail reads a partition in one aggregation: the partition's counter first, then, through $unionWith, each aggregate collection's first entries and placeholders after the last sequence; it merges them by sequence and emits the entries of the run from the last sequence that is final, stepping over placeholders. It never steps over a missing sequence: an append of some node may still land there. Missing for STALL_GRACE, the sequence is resolved through the counter's open blocks: this node's own remainder is closed, its own unfilled sequence filled, another node's block sealed; entries past it while it waits may also be in a collection this log does not know yet, so it reads the registry again.
Read concern. Every read, the tail, head, the version slot reads (readEntries, positionOf) and the request lookups included, reads what a majority of the replica set has committed (readConcern: majority). With the default (local), a tail could deliver an entry the primary committed and lost in a failover before it replicated, which is then rolled back: its subscriptions would have acted on a fact that never was. The cost is latency: an entry reaches the tail once a majority has it, about one replication round trip after its commit (appends already wait for it, w: majority). The backend so needs a replica set whose majority can commit: an arbiter that makes a majority unreachable stalls the tail.
Request ids. The request key is (tenant_id, request_id) within each aggregate collection: a request id reused by two aggregate types commits twice (makes the reuse the caller's error). The kernel's pre-check, the findByRequestId taking the aggregate type, reads that type's collection alone.
A stream's facts are fields of its document; the document has no aggregate name, the collection is the aggregate type. The transport is a field of its own, transport, binary data holding the bytes the runtime encoded, stored and given back unchanged, which only tail and readEntries read: read and findByRequestId leave it out of their projection. Each event's payload is a BSON document whose decimals are Decimal128, exact (ExactBson); an append whose payload holds a number MongoDB cannot store exactly is refused with an IllegalArgumentException.
Polling. A tail polls every pollInterval (50 ms by default) while it finds entries; each poll that finds none doubles the wait, up to maxPollInterval (1 s). A commit this log makes wakes the partition's tails at once, and a poll it woke that finds the partition caught up keeps the backoff, so a commit costs one read of the tail; an entry another node appends is read within maxPollInterval. A change stream could wake the tails of every node; it is not used, the backoff keeping an idle cluster cheap without a cursor per partition and node.
Calls run on dispatcher, virtual threads by default.
Constructors
Properties
Functions
Appends streams, of one partition or of several, with one ordered insertMany per aggregate collection (group commit): each stream takes its own partition's sequence, and gets the outcome append would give it. A stream the store refuses alone fails alone, with its aggregate's later streams; a sequence no stream took gets its placeholder; a stream whose position another node sealed is appended again alone. A group whose write is unknown is unknown for each stream it held.
Creates the indexes of aggregateName's log collection and registers it when missing, once per log; refuses a collection recorded in another format version (MongoFormat), and an aggregate whose collection name another aggregate has.
Creates the partition counters when missing; refuses a different partition count, and a log recorded in another format version (MongoFormat). The aggregate collections are created on first use, or by createCollection.
The highest sequence of a stream in partition, in one aggregation over the collections this log knows; a placeholder is no stream, and a reader that reads the tail up to the head waits for a stream there. The counter is the blocks reserved, not the streams written. An append in flight may still land below it: one concurrent with this read, which a subscription starting from the head may count as committed before it.
The lineage of partition from its counter document: its parent and epoch, read as PartitionLineage.ROOT when they are null or missing (a counter written before them).