fivenines
11/39

Guided Problem

PFX Build 08: Fan Out Market Data Safely

Time
20m
Level
foundation
Artifacts
not specified
Progress0%
Lesson 1.8

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.

The authoritative log emits ordered facts. Market data is the largest audience for those facts, but not every audience buys the same product or reads at the same speed.

The obvious shortcut is to have the matching engine publish directly to every subscriber and retain every update in an unbounded per-client queue. Yet consumer pressure never blocks matching; public state may be conflated for a slow audience, while private order and fill facts remain complete and replayable.

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:

TierContentVolumeConsumer
L1Best bid/ask + last tradeTinyRetail apps, tickers — the vast majority of users
L2Aggregated depth, top 10–20 price levelsModerateActive traders, charting tools
L3Every individual order event, add/modify/cancelFull firehoseMarket makers, HFT — a few hundred firms
DelayedAny of the above, 15 minutes lateFree 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"]
    
Fan-out as a tree. Each layer multiplies reach; conflation at the leaves converts "can't keep up" from a failure into a quality-of-service level. Slow consumers get correct-but-sparser truth, never a stale queue or a dropped session.

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.

A retail phone stops reading for 30 seconds. Preserving every intermediate quote can exhaust memory, while dropping an account fill would erase a fact. The correct degradation sends the newest public state and retains the complete private sequence.

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.

Next step

See what actually stuck.

Take the practice scenarios now.