Stock Exchange System Design — Trade-offs & Architectural Decisions
There is no single correct architecture for an electronic exchange — every major decision in Flow Exchange's design is workload-dependent, trading latency against durability, fairness against throughput, or operational simplicity against scale. This page walks through 23 concrete decisions, the genuine alternatives for each, the baseline we chose for the assumptions stated in Complete Design, and the specific signal that would make us choose differently.
Matching Concurrency: Single Writer vs Shared Multithreaded Book
Every order book mutation — insert, match, cancel — must appear to happen atomically and in a single agreed order, or price-time priority stops being a meaningful guarantee. The question is whether to enforce that with one thread per book or with locks across many threads.
| Option | Favors | Costs |
|---|---|---|
| Single writer per book (one thread/event loop owns all mutations) | Deterministic ordering comes for free; no lock contention inside the hot path; replay of the same command stream reproduces the same book and trades exactly | Caps a single book's throughput to one core's processing rate; a very hot symbol cannot be sped up by adding threads to that same book |
| Shared book with fine-grained locking (e.g. per-price-level locks) | In theory lets multiple cores touch the same book concurrently | Two threads racing to match the same resting order need a serialization point anyway, so the lock just moves the bottleneck without removing it; introduces lock-ordering bugs, priority inversion and non-deterministic replay — the match outcome can depend on scheduler timing, which breaks the invariant that replaying a command stream yields the same trades |
| Lock-free/optimistic concurrent structures | Avoids blocking on contended locks | Correctness under concurrent price-time matching (two incoming orders racing for the same resting liquidity) is extremely difficult to prove; still needs a logical commit point, which in practice becomes a serialization step in disguise |
The underlying reason single-writer wins for a single book is explained at the algorithm level in Architecture Deep Dive → Matching Engine Execution Model: threads are still free to handle networking, risk checks, journaling and market-data fan-out concurrently — only the book mutation itself is serialized.
Order Book Storage: In-Memory vs Database-Backed Matching
The live order book is read and mutated on every single incoming command. Whether that structure lives in process memory or behind a database round-trip determines whether the matching engine can meet a microsecond-to-low-millisecond latency budget at all.
| Option | Favors | Costs |
|---|---|---|
| In-memory order book (ordered structures + hash map, per Architecture Deep Dive) | Matching happens at memory-access speed, not network/disk round-trip speed; the data shape (price-sorted levels, FIFO queues) matches the access pattern exactly | State lives only in the matching process unless paired with a durable journal — see Decision 07; a crash without a journal loses the book entirely |
| Database-backed book (every order row read/written through a relational or KV store) | Gets durability and crash-safety 'for free' from the database's own guarantees; familiar operational tooling | Every match now costs one or more round-trips on the hot path; matching logic, which needs to walk price levels and FIFO queues repeatedly within a single command, does not map onto per-row database operations without severe latency penalty; the database quickly becomes the bottleneck under sustained order flow |
This is not an argument against databases generally — post-trade capture, reporting, and compliance stores are database-backed by design (see Architecture Deep Dive → Post-Trade Integration). It is specifically about the structure that absorbs every match on the hot path.
Matching Priority: Price-Time vs Pro-Rata vs Auction-Based Allocation
When multiple resting orders are eligible to fill against an incoming order, the allocation rule decides who gets filled first — and different venues legitimately choose different rules for different instrument types.
| Rule | Favors | Costs |
|---|---|---|
| Price-time priority | Simple to reason about and explain; rewards being first at a price level (as shown in Test A: S1 fills before S2 at the same $101.00 level); predictable for market makers who arrive early | Does not reward size — a large resting order has no advantage over a small one at the same price besides arriving first |
| Pro-rata allocation | Common in some derivatives markets; allocates fills proportionally to resting order size at a price level, which can reward market makers who post large size | Harder to explain and audit ('why did I only get 23% of my order filled') than FIFO; can incentivize quote stuffing with oversized resting orders to claim a larger pro-rata share |
| Auction-based (uncrossing at a single clearing price) | Suited to opening/closing moments where many orders arrive before a conceptual 'go' — produces a single fair clearing price and reduces the advantage of sub-millisecond speed at that moment | Not continuous — introduces a wait and a distinct matching mode (see Architecture Deep Dive → Market Orders, IOC/FOK & Stops); needs its own indicative-price and uncrossing logic, which is materially different code from continuous matching |
Price-Level Representation: Balanced Tree/Ordered Map vs Array/Tick-Indexed
Price levels need to be kept sorted so the engine can always find the best bid and best ask instantly. The data structure choice trades generality against raw lookup speed.
| Structure | Favors | Costs |
|---|---|---|
| Balanced tree / ordered map (e.g. red-black tree, skip list, ordered map type) | Handles any price range without pre-allocating; insert/lookup is O(log P) where P is the number of distinct occupied price levels; straightforward to reason about and implement correctly | O(log P) is slower than O(1) array indexing at the same P, and tree-node allocation can add GC/allocator pressure in managed runtimes |
| Array / tick-indexed structure (index = price in ticks, bounded range) | O(1) best-bid/ask and level lookup when the tick range is bounded and known ahead of time; excellent cache locality for latency-sensitive engines | Needs a bounded, pre-sized price range (a bounded number of ticks around a reference price); a runaway price move or very wide price collar can blow past the allocated range unless handled explicitly; less flexible for instruments with unusual or very wide price ranges |
Both structures are explored with pseudocode and complexity detail in Architecture Deep Dive → Order Book Data Structures; this decision is about which family to pick for a given instrument's behavior, not the implementation itself.
Engine Partitioning: Single Engine vs Per-Symbol Partitions
Because matching is single-writer per book (Decision 01), scaling across many instruments means scaling across independent engine instances rather than scaling one engine's internal concurrency.
| Option | Favors | Costs |
|---|---|---|
| Single engine process handling all instruments | Simplest to operate and reason about at small scale; one process to deploy, monitor and fail over | All instruments compete for the same single-writer loop's CPU time; a burst on one hot instrument (Test-vector style sweeps, high cancel rate) steals processing time from every other instrument sharing that process |
| Per-symbol (or per-symbol-group) partitions, each independently single-writer | Isolates one instrument's load from another; horizontal scale-out by adding partitions as instrument count grows; a crash or slowdown in one partition does not stall unrelated instruments | More processes/instances to deploy, route to, monitor and fail over; needs a routing layer that reliably sends every command for a given instrument to its current owner (see Decision 20 and Architecture Deep Dive → Partitioning & Hot Instruments) |
Command Ordering: Central Global Sequencer vs Per-Partition Sequencing
Determinism requires every accepted command for a given book to be assigned one unambiguous position in a single serial order. The question is whether that order is assigned by one global sequencer across the whole exchange or by a sequencer scoped to each partition.
| Option | Favors | Costs |
|---|---|---|
| Single global sequencer for the entire exchange | One authoritative sequence number space is conceptually simple to audit end-to-end | Becomes a single serialization point and potential bottleneck across every instrument, even though price-time priority is only promised per book (Section 2); concentrates failure-domain risk — a single global sequencer outage affects every instrument, not just one |
| Per-partition (per-instrument-group) sequencer | Matches the actual correctness requirement exactly — ordering only needs to be deterministic within a book, not across books (Section 2's explicit non-promise of a universal total order); each partition's sequencing can scale and fail independently | No single number tells you 'the Nth event across the whole exchange'; cross-instrument causality claims require correlating timestamps/sequence numbers across partitions deliberately, not assuming a shared counter |
Persistence: Synchronous WAL vs Group Commit vs Asynchronous Journal
Every accepted command must be durable before it is acknowledged as committed — but exactly when 'durable' happens relative to 'acknowledged' is a direct trade between the durability guarantee and acknowledgment latency.
| Strategy | Favors | Costs |
|---|---|---|
| Synchronous write-ahead log, fsync before ack | Strongest possible durability guarantee — nothing is acknowledged that is not already safely on durable storage | Every single command pays the full disk-flush latency individually; under high command rates this serializes the hot path on storage I/O |
| Group commit (batch several pending commands, fsync once, ack the whole batch) | Amortizes flush cost across many commands, recovering most of the throughput lost to per-command fsync while still giving every acknowledged command a durable position | Adds a small, bounded wait for the batch window, which slightly increases the latency of the earliest command in a batch; batch-size/time tuning is itself a trade-off |
| Fully asynchronous journal (ack before durable write lands) | Lowest possible acknowledgment latency | A crash between acknowledgment and durable write produces a committed-looking order the journal never actually recorded — violates the durable-and-replayable invariant (Section 4) and risks silent data loss that a client cannot distinguish from a dropped acknowledgment |
The exact commit-point definition and a concrete crash-between-commit-and-ack walkthrough are covered in Architecture Deep Dive → Journal, Commit & Replay and Failure & Scale Scenarios' crash incidents.
Replication: Active-Passive vs Active-Active for the Same Book
A book needs a standby in case its current owner fails — but letting two nodes both believe they own the same book at the same time is a correctness violation, not a performance optimization.
| Topology | Favors | Costs |
|---|---|---|
| Active-passive with leader fencing (single writer, epoch-fenced standby) | Exactly one authoritative matching process per book per epoch, by construction; standby catches up from the journal and takes over cleanly on failover, as shown in Architecture Deep Dive → Replication & Leader Fencing | A failover has a brief unavailability window while the standby catches up and is promoted; throughput ceiling is still the single active node's capacity |
| Active-active (both nodes able to accept and match independently against the same book) | In principle, more aggregate throughput and no failover gap | Two nodes matching the same book independently can produce two different, conflicting trade histories for the same resting liquidity — this directly violates the single-active-leader invariant (Section 4) and the no-dual-primaries rule (Section 2); reconciling or discarding one side's trades after the fact is not an acceptable correctness model for executed trades |
Recovery: Journal-Only Replay vs Snapshot + Incremental Replay
After a crash or planned restart, the engine must rebuild an exact in-memory book from durable state. How far back it has to replay from determines recovery time.
| Strategy | Favors | Costs |
|---|---|---|
| Journal-only replay (replay every event from the beginning of time) | Simplest possible recovery model — one source of truth, no separate snapshot artifact to manage or validate | Recovery time grows unbounded with total journal length; a long-lived, high-volume instrument eventually takes an unacceptably long time to recover, directly hurting recovery-time objectives |
| Periodic snapshot + replay of the incremental tail (snapshot at sequence N, replay N+1 onward) | Bounds recovery time to roughly 'snapshot load time + events since last snapshot', independent of total history length; snapshot interval is a tunable dial | Needs snapshot generation to not stall or corrupt the live book, snapshot checksums/versioning (Architecture Deep Dive → Journal, Commit & Replay), and a policy for a corrupt or missing snapshot falling back to an older one plus more replay |
Protocol: FIX vs Custom Binary Order Entry
Order entry needs a wire format that both sides agree on, that handles retransmission and gaps, and that is fast enough for the latency budget. FIX and custom binary protocols sit at different points on that trade-off.
| Protocol | Favors | Costs |
|---|---|---|
| FIX (or FIX-derived tag-value/session semantics) | Widely understood session/sequence/resend concepts; broker-side integration teams are frequently already familiar with the message model; easier onboarding for new members | Tag-value text encoding carries more parsing and bandwidth overhead than a tightly packed binary format, which matters more as message rates climb |
| Custom binary order-entry protocol | Minimal parsing and framing overhead, favorable for the tightest latency budgets | Requires every participant to implement and test a proprietary format; no existing tooling/libraries to lean on; versioning and schema evolution must be built and governed in-house, as must session/resend semantics that FIX already standardizes |
Message framing, versioning, checksums, heartbeats and session reset mechanics for both families are covered in Architecture Deep Dive → Network Protocols & Session Management; this is not every venue standardizing on the same stack — real exchanges genuinely differ here.
Data Transport: TCP vs UDP Multicast
TCP and UDP multicast solve different problems and are not universal substitutes for each other — the right choice depends on whether the data is a reliable one-to-one session or a lossy-tolerant one-to-many broadcast.
| Use case | Transport | Why |
|---|---|---|
| Order entry (new/cancel/replace, execution reports) | TCP | Needs reliable, ordered, one-to-one delivery with retransmission and backpressure built into the transport; losing or reordering an order command silently is unacceptable |
| Market-data distribution (book deltas, trade prints) to many subscribers | UDP multicast where the network supports it | One publisher, many simultaneous subscribers; multicast avoids the publisher having to send N individual copies over N TCP connections; the protocol is explicitly designed to tolerate loss because sequence numbers plus snapshot/gap-recovery (Decision 12, Architecture Deep Dive → Market Data Architecture) handle it at the application layer instead |
Market Data: Push Deltas vs Polling vs Snapshot-Only
Subscribers need to see the book and trade prints change in near real time without putting read load anywhere near the matching hot path.
| Model | Favors | Costs |
|---|---|---|
| Push deltas (snapshot + incremental sequence-numbered updates) | Lowest latency from book change to subscriber visibility; bandwidth scales with actual change rate, not subscriber polling rate; gap detection via sequence numbers enables precise resync | Subscribers must implement gap detection and snapshot-resync logic correctly, or they silently drift from the true book |
| Polling (subscriber periodically requests current state) | Simple subscriber implementation; no persistent connection or sequence-tracking logic needed | Either wastes requests when nothing changed or misses intermediate book states between polls; poll frequency vs load is a constant tuning fight, and poll traffic itself can become a load problem at scale |
| Snapshot-only (no incremental deltas, full snapshot on every update or interval) | Simplest possible protocol — no sequence numbers, no gap logic | Bandwidth cost scales with full book size times update frequency instead of change size, which is wasteful for a deep, active book; higher latency per update for large books |
Hot-Path Messaging: Direct Engine Pipeline vs General-Purpose Message Broker
Within the matching hot path — gateway to risk to sequencer to engine to journal — every extra hop costs latency and, more importantly, every extra layer of indirection risks reordering the very commands whose order must be deterministic. This is the one decision where the industry default ('just put a message broker in the middle') deserves explicit scrutiny rather than being adopted unquestioned.
| Option | Favors | Costs |
|---|---|---|
| Direct engine pipeline (purpose-built in-process/inter-process queue dedicated to one partition's command flow, tuned specifically for ordering and latency) | No general-purpose broker overhead (topic/partition metadata lookups, consumer group coordination, serialization layers designed for broad reuse rather than this one hot path); ordering guarantee is a direct property of the pipeline's own design rather than something layered on top of a general tool | Purpose-built code to build and maintain; fewer off-the-shelf operational tools than a mainstream broker |
| General-purpose message broker (e.g. a Kafka-style log) placed directly in the matching hot path | Mature tooling, monitoring and client libraries; team may already operate it for other asynchronous workloads | A general broker is built for durable multi-consumer fan-out and replay, not minimal-latency single-partition ordering — the throughput/latency profile and reordering-under-retry semantics of a general broker were not designed around the sequencing-fairness guarantee the matching engine needs; depending on configuration, consumer rebalances, partition reassignment or at-least-once redelivery semantics can reorder or duplicate messages relative to the exact order a sequencer must preserve, which is precisely the invariant this engine cannot compromise on |
This directly reflects Section 2's explicit constraint: no 'Kafka in the matching hot path' as an unquestioned default. The question to ask before adopting a general broker on this specific path is whether its ordering and latency model was built for exactly this job, not whether it is popular or already running elsewhere in the stack.
Risk Validation: Synchronous Pre-Trade Checks vs Deferred Checks
Pre-trade risk checks (order size, notional, price collar, symbol permission) add latency on the path before a command is even sequenced. The question is whether that check must block sequencing or can be relaxed.
| Approach | Favors | Costs |
|---|---|---|
| Synchronous pre-trade checks (block before sequencing/matching) | Guarantees an order that fails a hard risk rule (bad tick, excessive size, halted symbol) never reaches the book at all — the strongest possible protection against a rule violation ever becoming a committed order | Adds the risk-check latency to every single command's path to acceptance; risk-state freshness (credit, position limits) must be kept current enough to check against without itself becoming a bottleneck |
| Deferred checks (accept into the pipeline, validate after sequencing, reject/unwind if a check fails) | Removes risk-check latency from the critical-acceptance path | An order can briefly exist inside the sequenced/matching pipeline before a check catches it, which risks allowing a rule violation to actually match before it's caught — unwinding a matched trade after the fact is not a clean operation (Section 2: a completed cancel cannot undo an earlier committed fill, and the same logic applies to unwinding executions) |
Cancel/Replace: In-Place Mutation vs Cancel/New With Priority Consequences
Amending a resting order's price or size can either preserve its existing place in the FIFO queue or push it to the back, as if it were a fresh order — and that choice changes the fairness guarantee for every other resting order at that price.
| Approach | Favors | Costs |
|---|---|---|
| In-place mutation preserving time priority (allowed for qualifying amendments, typically a size decrease only) | A participant lowering their remaining size does not need to lose their place in line — reasonable, since reducing size can't disadvantage orders behind it in the queue | Requires a precisely defined rule for exactly which amendment types qualify (e.g. decrease-only); any price change or size increase cannot safely preserve priority without disadvantaging orders already resting behind it |
| Cancel/new semantics (replace cancels the old order and inserts a new one at the back of its new price level's queue) | Simple, uniform rule: any amendment that could affect other orders' relative position forfeits the original queue position, which is fair to orders that were already resting | A participant who wants to adjust price slightly loses queue priority entirely, which can be a meaningful cost for a market maker constantly refreshing quotes near the touch |
The precise state machine, versioning and idempotency handling for replace are covered in Architecture Deep Dive → Cancel/Replace Semantics; this decision is about the priority policy, not the mechanics of applying it.
Order Types: Simple First vs Advanced/Hidden/Stop/Auction Rules
Market and limit orders with Day/IOC/FOK time-in-force cover the large majority of educational and practical matching scenarios. Iceberg, stop, and auction order types each add meaningfully different logic, not just extra fields.
| Tier | Includes | Why it's a separate tier |
|---|---|---|
| Core (build and prove correct first) | Limit, market; Day, IOC, FOK time-in-force | These exercise continuous price-time matching, partial fills, and all-or-nothing feasibility directly, and every test vector in Section 4 is built on exactly this tier |
| Advanced, additive | Iceberg/hidden orders, stop and stop-limit orders, opening/closing/volatility auctions | Iceberg changes what 'visible depth' even means and interacts with priority (Architecture Deep Dive explains the priority implications); stops require a separate trigger-and-release model, not resting-book matching, until triggered; auctions are a distinct matching mode (uncrossing at a clearing price), not a variant of continuous matching |
Fairness vs Latency: Admission and Sequencing Policy Tuning
Once commands reach the sequencer, the policy for how it admits and orders them can be tuned for raw speed or for treating similarly-timed arrivals fairly — and pushing too far toward either extreme has a real cost.
| Policy lean | Favors | Costs |
|---|---|---|
| Latency-optimized (process and sequence as fast as physically possible, minimal batching/smoothing) | Lowest possible time from gateway receipt to sequenced position, which matters directly to latency-sensitive participants | Can make outcomes sensitive to tiny, effectively-arbitrary timing differences between near-simultaneous arrivals — two orders a few microseconds apart may be ordered in ways no participant can meaningfully anticipate or verify, which strains the credibility of the fairness story even though the ordering itself is still deterministic once fixed |
| Fairness-oriented (bounded intake window or published, defensible tie-break rule for very close arrivals) | Easier to explain and defend as fair across all participants regardless of connection speed; reduces the incentive to spend disproportionate effort purely on shaving microseconds off network path | Adds a small, deliberate delay or batching window to the admission path, which raises the latency floor for everyone, including participants who did nothing to deserve the wait |
Whatever policy is chosen, the sequencer remains the sole ordering authority — see Decision 21 and Architecture Deep Dive → Command Sequencing & Fairness for how retries, reordering and gateway failover are handled without ever falling back to wall-clock comparisons.
Availability vs Safety: Fail-Closed/Halt vs Accepting Uncertain Orders
When a dependency the engine needs for correctness becomes unavailable or uncertain — risk state is stale, the journal can't confirm a write, a partition's ownership is ambiguous — the system must choose between stopping and continuing on uncertain information.
| Posture | Favors | Costs |
|---|---|---|
| Fail closed / halt the affected instrument or path | Never matches an order under conditions that cannot be verified as correct; keeps the invariant that a book never diverges from its true, auditable state | Trading visibly stops for affected participants, which has a real cost (missed opportunity, participant frustration) and must be communicated clearly |
| Continue accepting/matching under uncertainty (e.g. proceed with possibly-stale risk data, or accept orders during a durability-uncertain window) | Keeps the venue 'available' in a narrow uptime sense | Risks an unrecoverable correctness violation — e.g. a trade executed against a limit that should have been rejected, or a durability gap that produces an order the journal never actually has a record of; such violations cannot be cleanly reversed once a trade has executed (Section 2) |
Scale Strategy: Faster Single-Book Engine vs More Symbol Partitions
When overall exchange throughput needs to grow, there are two distinct levers: make each single-writer engine process commands faster, or add more independently-owned partitions. They solve different bottlenecks.
| Lever | Solves | Does not solve |
|---|---|---|
| Faster single-book engine (better data structures, memory layout, reduced allocation, CPU/NUMA tuning — see Architecture Deep Dive → Latency Engineering) | A single hot instrument that is CPU-bound on its own processing, independent of how many other instruments exist | Total exchange-wide throughput across thousands of instruments — one faster core still has a ceiling, and most instruments are not individually CPU-bound in the first place |
| More symbol partitions (horizontal scale-out across independent books) | Aggregate exchange-wide throughput as instrument count and overall order volume grow | A single pathologically hot instrument's own per-core ceiling — adding partitions for other symbols does nothing for one symbol that is itself CPU-bound |
Geographic Topology: One Authoritative Matching Region vs Distributed Gateways
Matching for a given book must have exactly one authoritative location at any moment, but the gateways that accept orders from participants can be geographically distributed to reduce access latency.
| Option | Favors | Costs |
|---|---|---|
| Single authoritative matching region, local gateways only | Simplest to reason about; no cross-region replication complexity for the book itself | Participants far from the matching region see higher round-trip latency with no local mitigation |
| One authoritative matching region plus geographically distributed gateways that forward into it | Reduces local network hop latency for the last mile to the nearest gateway, without multiplying matching authorities | Gateway fleet and session management become geographically distributed, needing careful failover and consistent routing to the single authoritative region; the cross-region hop from gateway to matching region is still there for the actual order-to-ack round trip |
| Fully distributed/multi-region active matching for the same book | Would, in principle, reduce latency for every region symmetrically | Reintroduces the same correctness problem as active-active replication (Decision 08) at a geographic scale — two regions both matching the same book is two primaries, which this design explicitly prohibits; this is a different instrument-ownership topology, not a loophole around single-leader matching |
Trade Publication: Synchronous Outbound Reporting vs Committed Event-Driven Fan-Out
Once a match commits, execution reports must reach both counterparties and trade prints must reach the market-data pipeline and post-trade integration. Whether that happens synchronously inline with the match or via a committed event handed off for independent fan-out changes both latency and failure isolation.
| Model | Favors | Costs |
|---|---|---|
| Synchronous outbound reporting (engine blocks on delivering reports/events before considering the command fully done) | Conceptually simple — one linear sequence from match to delivery | A slow or unavailable downstream consumer (a sluggish execution-report gateway, a backed-up market-data publisher) stalls the matching engine itself, which is exactly the kind of non-critical-path leak Section 2 and this module's architecture repeatedly warn against |
| Committed event-driven fan-out (engine durably commits the trade/execution event; delivery to reports, market data and post-trade is a decoupled, independently-retriable consumer process) | Matching never blocks on a downstream consumer's health; each consumer (execution-report gateway, market-data publisher, post-trade capture) can retry, catch up or recover independently without affecting the others or the engine | Requires the commit itself to be the durability point — the event must be committed before any consumer sees it — and consumers must handle at-least-once delivery with idempotent processing keyed on unique trade/execution IDs |
Operational Complexity: Custom Low-Latency Stack vs Conventional Managed Infrastructure
Every latency-engineering technique discussed in Architecture Deep Dive — kernel bypass, CPU pinning, NUMA-aware allocation, busy-polling — trades operational simplicity for tail-latency improvement. Most of the exchange does not need to pay that cost.
| Component class | Favors | Costs |
|---|---|---|
| Custom low-latency stack (tuned kernel/network settings, CPU affinity, specialized allocators, bespoke pipeline code) | Meaningfully reduces p99/p99.9 latency and jitter for the specific hot-path components that need it — sequencer, matching engine, journal commit | Specialized operational knowledge to run and debug; harder to hire for and onboard new engineers into; conventional cloud/container tooling (autoscaling, generic monitoring dashboards) often does not apply cleanly to pinned, tuned processes |
| Conventional managed infrastructure (standard container orchestration, managed databases/queues, off-the-shelf monitoring) | Familiar operational tooling, faster onboarding, strong ecosystem support, easier to staff | Not tuned for the tightest latency requirements of the matching hot path specifically — scheduling jitter, network virtualization overhead and generic resource isolation all work against sub-millisecond consistency |
Decision Record: All 23 Trade-offs at a Glance
No row in this table is a universal truth — each baseline choice is correct for the assumptions stated across this module (correctness and deterministic replay prioritized over raw single-book throughput; per-instrument isolation preferred over a single shared engine; fail-closed over uncertain availability). Change the assumptions, and some of these choices change with them.
| # | Decision | Baseline choice | Rationale | Reconsider when |
|---|---|---|---|---|
| 01 | Matching concurrency | Single writer per book | Deterministic ordering and replay without lock contention | A single instrument is sustained-CPU-bound, not merely bursty |
| 02 | Order book storage | In-memory, journal-backed | Matching needs memory-speed access; durability comes from the journal, not sync DB writes | A product requirement removes the need for low-latency continuous matching entirely |
| 03 | Matching priority | Price-time priority (continuous) | Simple, auditable, and what every canonical example demonstrates | A market-structure goal explicitly rewards size (pro-rata) or requires a scheduled clearing moment (auction) |
| 04 | Price-level representation | Balanced tree/ordered map per instrument | No per-instrument tuning needed; handles any price range safely | A specific hot instrument has a small, stable, provably-bounded tick range |
| 05 | Engine partitioning | Per-symbol partitions | Isolation is a fairness/correctness property, not just performance | Operational overhead of many small partitions outweighs isolation for genuinely cold symbols (group them) |
| 06 | Command ordering | Per-partition sequencer | Matches the actual per-book ordering requirement; avoids a global bottleneck | A new requirement demands cross-instrument causal ordering (rare, out of current scope) |
| 07 | Persistence | Group commit | Keeps the durable-before-ack contract while amortizing fsync cost | Measured throughput at very low command rates makes per-command sync acceptable, or batch latency exceeds budget |
| 08 | Replication | Active-passive with leader fencing | Exactly one active matcher per book per epoch — a correctness requirement, not a preference | Never — active-active on one book is unsafe by definition |
| 09 | Recovery | Snapshot + incremental replay | Bounds recovery time independent of total history length | Measured recovery time in a drill exceeds the stated recovery-time objective (tune snapshot interval) |
| 10 | Protocol | FIX-style order entry | Broad member onboarding and familiarity prioritized over micro-latency encoding gains | A specific member's measured latency requirement cannot be met by tag-value parsing overhead |
| 11 | Data transport | TCP for order entry, UDP multicast for market data | Each transport matches the reliability/fan-out shape of its traffic | Network path cannot support multicast reliably (fall back to TCP feed for those subscribers) |
| 12 | Market data | Snapshot + incremental push deltas | Matches the required snapshot-plus-contiguous-deltas reconstruction guarantee | A consumer class cannot implement gap detection correctly (fix at the client-library layer) |
| 13 | Hot-path messaging | Direct, purpose-built pipeline | General-broker ordering/latency semantics were not built for single-writer sequencing | Off the hot path (post-match fan-out) — see Decision 22 — where a general broker is the right fit |
| 14 | Risk validation | Synchronous pre-trade checks | A hard rule violation must never become a committed order; executions can't be cleanly unwound | Only for checks whose failure mode is safe to detect and reconcile after the fact (non-blocking audits) |
| 15 | Cancel/replace | Cancel/new, with time-priority kept only for same-price size decreases | One simple, auditable fairness rule instead of a continuum of partial-priority exceptions | A clearly-scoped, explicitly published market-making incentive program needs a different stated rule |
| 16 | Order types | Core types first (limit/market, Day/IOC/FOK), advanced types layered in | Core tier is what every required test vector validates; advanced types need their own correctness review | Never as a silent default — advanced types ship only with their own explicit design and tests |
| 17 | Fairness vs latency | Latency-optimized sequencer, published deterministic tie-break | The sequencer's deterministic order is itself the fairness guarantee | A demonstrated, measurable network-path advantage repeatedly determines outcomes |
| 18 | Availability vs safety | Fail closed on correctness-critical dependencies | An unrecoverable correctness violation is worse than a visible, communicated halt | Only for genuinely non-critical, reversible paths (e.g. a delayed, non-authoritative feed tier) |
| 19 | Scale strategy | Horizontal partitioning as primary lever; single-engine speed for identified hot symbols | The two levers solve different bottlenecks and are complementary | Per-partition CPU/queue metrics show which lever the current bottleneck actually needs |
| 20 | Geographic topology | One authoritative matching region, distributed gateways | Matching authority must stay singular; gateways can still reduce access latency | Never for matching authority itself — a genuine multi-venue product is a different system, out of scope |
| 21 | Timestamp authority | Sequencer sequence number | Deterministic replay requires it; wall-clock is recorded but never decides order | Not reconsiderable within this design's determinism requirement |
| 22 | Trade publication | Committed event, then decoupled fan-out | Matching must never block on a downstream consumer's health | Delivery lag to consumers exceeds the stated reporting SLA (scale consumers, don't move delivery onto the hot path) |
| 23 | Operational complexity | Custom low-latency stack only for sequencer/engine/journal; conventional infra elsewhere | Latency engineering cost should track where the latency guarantee actually matters | Measurement shows a specific component is genuinely on the critical path for the stated budget |
- Almost every decision here is reversible given a changed assumption — the two that are not (active-active replication on one book, and wall-clock as the matching order authority) are excluded by correctness invariants, not by current workload.
- The recurring pattern is: keep the matching hot path narrow, deterministic and single-writer per book, and push everything that can tolerate asynchrony (reporting, market-data fan-out, post-trade, analytics) off that path into decoupled, independently-scalable consumers.
- A 'Kafka in the hot path' default is specifically rejected for sequencer-to-engine-to-journal (Decision 13), while a general-purpose broker is specifically recommended for post-commit fan-out (Decision 22) — the same technology, two different fits, decided by where the ordering guarantee has to live.
- For incident-driven reconsideration of these baselines, see Failure & Scale Scenarios; for the algorithms and pseudocode behind each decision, see Architecture Deep Dive; for interview framing of how to present these trade-offs under time pressure, see Interview Perspective.