Ordering + Sequencing¶
How the system guarantees message ordering and how consumers detect and recover from disruptions.
Epochs¶
An epoch is an incarnation counter that increments when the adapter reconnects to a venue or resets its internal state. Epochs appear in the common message header as a u32 field.
When the epoch changes for a given (venue, msg_type, inst_id) triple:
- All prior sequence numbers for that triple are invalidated
- The consumer MUST discard all cached ordering state for that triple
- The
RESETflag is set on the first message of the new epoch
Epochs never decrease. A new epoch means the adapter's view of the venue stream was interrupted — the consumer cannot assume continuity across the boundary.
Sequence numbers¶
Every message carries a seq field (u64, monotonically increasing) in its common header. Sequence numbers are scoped to an ordering domain — they are only comparable within the same domain.
Ordering domains¶
The ordering domain is (venue, msg_type, inst_id).
Within a single domain, seq is monotonically increasing and gap-free (unless a GAP or DROP flag is set). Across different domains, sequence numbers are independent and MUST NOT be compared.
| Scope | Example |
|---|---|
| Same domain | Two L3 messages for inst_id=42 on Binance — seq values are comparable |
| Different msg_type | An L1 and an L3 for the same inst_id — seq values are independent |
| Different inst_id | Two L3 messages for different instruments — seq values are independent |
| Different venue | Any two messages from different venues — seq values are independent |
For Status messages, inst_id is 0 — the domain is (venue, STATUS, 0).
Gap detection¶
A gap occurs when the consumer observes a discontinuity in seq within a single ordering domain, or when the GAP flag is set on an incoming message.
Detection methods:
- Seq discontinuity: If the consumer tracks
last_seqper domain and receives a message whereseq > last_seq + 1, a gap has occurred. - GAP flag: The adapter sets the
GAPflag when it detects an upstream discontinuity from the venue. - Ring overflow: The ring consumer detects that the producer has lapped it (
has_gap()returns true). This is a transport-level gap that affects all domains on that ring.
On any gap, the consumer MUST mark the affected book as INVALID and initiate recovery.
Reset semantics¶
A reset occurs when the epoch changes. The adapter sets the RESET flag on the first message of the new epoch.
On reset:
- Discard all
seqtracking state for the affected(venue, msg_type, inst_id)domain - Mark the affected book as INVALID
- Begin tracking
seqfrom the new message's value - Initiate snapshot recovery
A reset is strictly more severe than a gap — it invalidates the entire ordering history, not just a range of missed messages.
Recovery procedure¶
After a gap or reset, the consumer recovers by requesting a fresh snapshot:
- Mark the book as INVALID — do not use it for trading decisions
- Request a snapshot via the Control Plane
- Continue draining messages from the ring, buffering deltas for the affected instrument
- When the SnapshotRef arrives:
- Read the snapshot payload from the snapshot SHM region
- Discard any buffered deltas where
seq <= snap_seq - Apply the snapshot to initialize the book
- Apply remaining buffered deltas in
seqorder - The book is now VALID
- Resume normal processing
The adapter handles all venue-specific reconciliation (e.g., Binance update ID matching) internally. The consumer operates entirely in the adapter's seq / snap_seq space.
Do not skip recovery
Processing deltas over a gap produces a silently incorrect book. There is no way to detect the error after the fact — the only safe action on gap detection is to stop, invalidate, and resync.