Market data at scale
Why market data is the exchange's hardest scaling problem (not matching), and the two tools that tame it: tiered feeds and conflation.
Every match in 1.3 emitted public updates. Multiply by 10 million users and see what happens.
Run the numbers. The core produces perhaps 500k book updates/second at peak. Matching handles that in one process. But market data must deliver those updates to every subscriber: 500k updates × 200k concurrent subscribers is 1011 messages per second of naive fan-out. The bottleneck of an exchange at scale is not deciding trades — it's telling everyone about them.
Tool 1 — tiered feeds. Different consumers need radically different fidelity, so we sell different products:
| Tier | Content | Volume | Consumer |
|---|---|---|---|
| L1 | Best bid/ask + last trade | Tiny | Retail apps, tickers — the vast majority of users |
| L2 | Aggregated depth, top 10–20 price levels | Moderate | Active traders, charting tools |
| L3 | Every individual order event, add/modify/cancel | Full firehose | Market makers, HFT — a few hundred firms |
| Delayed | Any of the above, 15 minutes late | — | Free tiers, websites |
Tool 2 — conflation. A phone on hotel Wi-Fi cannot drink from even the L1 firehose during a volatile minute. Rather than queue unboundedly (memory death) or disconnect the client (availability death), the feed server conflates: it keeps only the latest state per symbol, and when the slow client is ready, sends current truth — skipping the intermediate churn the client no longer cares about.
flowchart TB
ME["Matching engine"] --> MDP["Publisher: builds L1 / L2 / L3 streams"]
MDP --> F1["Feed servers — L3 firehose"]
MDP --> F2["Feed servers — L1/L2 fan-out tier"]
MDP --> DLY["Delay buffer — 15 min"]
F1 --> MM["Hundreds of firms — every message, gap-detected"]
F2 --> FAST["Fast consumers — every update"]
F2 --> SLOW["Slow consumers — conflated: latest state per symbol"]
DLY --> FREE["Millions of free-tier users via CDN-style edge caches"]
Conflation works because L1/L2 data is state, not history — a client showing prices needs the current best bid, not every bid that briefly existed. L3 consumers, who genuinely need history, get sequence-numbered streams with gap detection instead (the mechanics arrive in lesson 2.3). Note the asymmetry with private data: your own fills are never conflated. Public state can be summarized; personal facts cannot.
- Fan-out, not matching, is the scale bottleneck: messages × subscribers.
- Tier the product: L1/L2/L3/delayed serve different needs at different volumes.
- Conflate public state for slow consumers; never conflate private facts.