How to Build an Exchange: Sub-Millisecond Response Times and 24/7 Uptime in the Cloud

2026-08-0133 min read

Most systems are designed so that the average request is fast. An exchange is designed so that the worst request is fast, because participants build trading models around your latency distribution and a surprise one-second pause costs them real money. Frank Yu, Director of Engineering at Coinbase working on exchange platforms, uses that constraint to argue for an architecture that looks backwards to most backend engineers: no database in the hot path, one thread doing all the business logic, a Raft cluster deliberately packed into a single availability zone, and a JVM that never allocates. His thesis is that these are not exotic low-latency tricks but consequences of one goal — simplicity — and that the same simplicity is what buys availability, testability, and fast deployment.

Yu previously served as Principal Engineer and then Director of Software Engineering at FairX, leading the design of what became the Coinbase Derivatives Exchange after the acquisition. This is a 49-minute QCon San Francisco talk; InfoQ dates the recording April 23, 2026. He opens with a caveat worth carrying through these notes: "This talk represents our current thinking on the matter. Don't hold me to any of this stuff, it may change as soon as we find out new things." Everything below is what he presented; clearly marked additions supply background an intermediate engineer needs.

What You Will Learn

  • What an exchange actually does, and why its non-functional requirements (correctness, fairness, availability, auditability, consistency) are unusually strict.
  • How price-time priority works, traced through a concrete order book, including who gets price improvement in a trade.
  • Why concurrency destroys determinism, and why determinism is the property that the entire architecture is built to protect.
  • How a single-threaded matching engine makes throughput and latency the same metric, and what that unification buys you.
  • How Raft consensus replaces a database in the hot path, and why Coinbase runs five nodes rather than three.
  • Why deterministic replay lets you replicate the small input log instead of the large output stream, and how that produces read replicas, edge access, and disaster recovery almost for free.
  • Why the talk argues against spreading a hot-path cluster across availability zones.
  • Concrete techniques for sub-millisecond tails: fixed-offset binary encoding, allocation-free JVM code, CPU pinning and busy spinning, bounded work, and rate limiting.
  • How to deploy code changes to a deterministic system without breaking replay, and how to measure headroom on a CPU that is pegged by design.

What an Exchange Is, and Why the Bar Is So High

An exchange is a place where you submit an order to buy or sell something; it publishes the price you want and tells you when your order is filled. Yu's framing is that this makes it financial infrastructure: everyone participating in markets comes to the exchange for up-to-date prices and to print trades, and they treat it as a trusted third party.

The risk is asymmetric in a way that shapes every design decision. Yu describes the potential losses from a failure as multiple orders of magnitude larger than the revenue from any transaction — he floats a ratio on the order of 0.00001% — and says he has seen this play out in his career. Because the industry focuses so heavily on reliability, participants effectively outsource their own reliability to the exchange, reasoning that if the exchange is down they are finished anyway. The consequence is that the exchange is not just keeping its own state correct, it is keeping state correct on behalf of every client.

That produces five requirements the rest of the talk keeps returning to. Correctness is table stakes. Fairness means giving every participant equal opportunity to get performance; if you advantage some participants, the others find trading prohibitively expensive and leave, which destroys the market. Availability matters because the failure mode is direct financial harm: you buy an asset, it starts falling, and you cannot sell to cut your losses because the exchange is down. Auditability is a regulatory requirement — regulators want to know the exact state of the entire market at an arbitrary microsecond, years in the past, and Yu says they have served exactly those requests, so the system must be able to reconstruct and replay state tick by tick, and anything sent and confirmed must be persistent. Consistency is the subtle one: infrastructure should not surprise you, so the team works to keep P99 latency as flat as P50 and as flat as the average. Participants literally build models around the exchange's variance and delays, and a random one-second pause generally means a bad trade and a material loss.

Added context: P50 and P99 are the median and 99th-percentile response times. In most web systems a P99 several times the median is normal and acceptable. Here it is a product defect, which is why so much of the talk is about eliminating sources of variance rather than reducing average cost.

Start With the Functional Spec: Price, Time, Priority

Yu inverts the usual talk order and starts with functionality, because the first thing he does when building an exchange — the last four or five times — is open an IDE and build a black-box harness of user API actions and user stories that specify the behaviour he wants. The harness has three parts: the input to the system, the state the system tracks, and the output it broadcasts. Holding those three separate is what later makes the whole architecture possible.

The functional requirements compress into three words: price, time, priority. Yu notes that many exchanges use this rule and that pages of functional spec collapse into it. Two terminology notes first: incoming instructions are buy and sell orders, and what the public sees are bids (resting buy interest) and asks (resting sell interest). Many venues say "offer" rather than "ask"; Yu prefers ask because it is three characters and symmetrical with bid.

Walk through his example. The market opens empty — nobody wants to buy, nobody wants to sell. Mark M submits a buy order for one BTC at 100. The API call is handled, the order becomes a resting bid, and the exchange broadcasts to everyone that somebody — not identified as Mark — is willing to buy BTC at 100. Anyone who wants to sell now knows they can get 100, for one unit. Mark, who wants to make money buying low and selling high at scale, then submits a sell order at 101. He will not match with himself, so it rests as the ask. Now the market shows 101 to buy and 100 to sell: the top of book, the best price on each side. Mark then adds depth, offering to buy two more at 99 and to sell two more at 102 — worse prices for size, because if you want to trade a lot with him he wants a better deal.

The interesting case is Alice, who submits a buy order for two BTC at 100. Whose order fills first, Mark's or Alice's? Alice arrived later than Mark's first order, so she queues behind it, but her price is better than Mark's second order at 99, so she slots ahead of that. Price first, then arrival time as the tie-breaker. Each side is therefore kept sorted best-first, and Yu's rule of thumb is that "higher bids are better in general" — a buyer offering more is ahead of a buyer offering less, and symmetrically a seller asking less is ahead of one asking more. Still nothing has traded, because no buy and sell prices overlap.

Then Bob submits a sell order for two BTC at 99. That crosses the book — his sell price overlaps resting bids. Matching starts from the best price and, within a price, from the earliest order. Mark's original one-unit bid at 100 was both the best price and the first, so it fills. Bob's second unit goes to Alice's order rather than Mark's 99 bid, because Alice's price is better; her order is updated to one unit remaining and the display is corrected. The whole rule exists to incentivise participants to quote better prices.

What price does Bob get? He is willing to sell at 99 but he trades at 100. Yu's explanation is that the participant arriving second generally receives the price improvement, so that it is safe for Bob to submit an aggressive price: he does not have to worry that the market moves and catches him at a worse price. The exchange broadcasts the print: two BTC traded at 100. Bob is happy because he beat his limit, and Mark and Alice are happy because they got filled. Yu's point in walking through this is that even this small script involves a lot of interacting state, and a real test suite of such scripts is complicated — which is exactly why the core must stay simple.

Determinism, and Why the Core Is a Single Thread

Scaling, in Yu's definition, means two things: handling many orders per second, and being able to change behaviour quickly. He does not want to wait three months between deployments. Both push toward keeping the core as simple as possible.

The obvious throughput answer is parallelism: assign different CPUs to handle different orders and let them find matches concurrently. Yu allows that this could work, then names the cost — "good luck finding out what happened given the state of the market when you went back 5 years." Concurrency is where you lose determinism. If the same sequence of inputs must produce the same outputs every time, the simplest way to guarantee it is to handle inputs strictly in order, in one thread: one program running on one CPU core, reordering nothing.

Added context on why this matters more here than elsewhere: determinism is what makes the auditability requirement tractable, because you can reconstruct any historical state by replaying inputs rather than storing every intermediate state. It is also what makes several later capabilities — replay debugging, replicating the request stream, off-leader snapshots — possible at all. Note that determinism constrains more than threading: anything nondeterministic in the hot path (wall clock reads, random numbers, iteration over unordered collections) would break replay in the same way. Yu does not enumerate these, but they follow from the property he is protecting.

Single-threading has a second consequence Yu clearly likes: scalability becomes directly tied to performance. If the thread is faster, you handle more orders per second. He calls this the best unification of KPIs, because it frees you to optimise the hot path and immediately do more business, and it makes the decision about what to do next to scale the business obvious. There is no separate throughput lever to pull.

And because the whole thing fits on one core, it fits on one box, and boxes have many cores. If it fits on one box, you can run copies of it — which is where availability comes from.

Raft Instead of a Database in the Hot Path

You still need durability. One option is writing to disk, with replicas or RAID to survive disk failure. Yu says that works but is slow, and offers consensus as another way to hit the same durability SLA. With a fast consensus implementation, you accept orders as they arrive, get them replicated, and let business logic process concurrently with the durability process.

He contrasts this with the standard shape. A web server receives an API call, does its work, sends state to Postgres over the network, Postgres computes, writes its write-ahead log, and acknowledges. All of that is blocking: your process waits, Postgres works, the round trip completes, and only then do you respond to the client. It is slow and, more importantly for an exchange, full of jitter. With Raft there is no database and no Postgres in the hot path.

Added context: Raft is a consensus algorithm in which a leader replicates an append-only log of entries to followers. An entry is committed once a majority — a quorum — has acknowledged it, and if the leader dies a follower is elected to take over. Here the replicated log is the stream of incoming orders, not the resulting state.

Consider the failure modes. Without replication, the matching engine runs very fast because it waits for nothing, but if the hardware dies you lose data, people complain, and you are not happy. With Raft, before processing a request you make sure most of the cluster has it — two of three, or three of five. When the hardware dies you promote a replica and keep processing. Clients may see a 500 for a request or two, but, in Yu's words, "you will not acknowledge anything that you've forgotten." Failing a request is acceptable; losing an acknowledged one is not.

His cluster-sizing advice is emphatic: in the cloud, run five, not three. With three nodes you can only lose one machine before you lose replication; with five you can lose two before you are scared and three before you are broken. He says they have been very happy to have run five.

Rolling Deployments and Why 24/7 Is Non-Negotiable

The replicated cluster unlocks something beyond fault tolerance: zero-downtime rolling deployments. Yu's observation is that from the cluster's point of view, taking a node down looks identical whether the cause is a machine failure or a software release. That unifies resiliency with the deployment process and, in his phrase, "makes the unexpected normal" — the failover path is exercised every time you ship. Operationally you shut down a follower, bring it back on the new version, repeat, and restart the leader last, giving a blue-green style rollout with effectively no exchange downtime.

The business effect is large. Yu says traditional exchanges update their core technology maybe once a quarter; his team ships weekly or more often. He also flags a constraint that surprises engineers from product backgrounds: you cannot feature-flag an exchange feature. Giving one user behaviour that makes things better while withholding it from everyone else violates fairness, so every deploy is big bang. What rolling deployment buys is not gradual exposure but the ability to stream changes out as soon as you have confidence, without taking an outage.

Why does 24/7 matter so much? In crypto, things go wild at any time, and the worst outcome is that Bitcoin drops over the weekend and you cannot sell because the market is closed, because, as Yu puts it, "the world does not stop turning at 4 p.m. Eastern." Extended downtime also creates financial discontinuities: users who cannot submit orders fall back on side channels to manage their risk, which adds complication and risk for everyone. Yu is blunt that maintenance windows are an anachronism in backend computing — plenty of websites with far less engineering investment run continuously — and that closing is pleasant for engineers because you can shut everything down, verify versions and replication, and start back up. "You've effectively pushed the cost of your own engineering savings to the customer."

Architecture And Data Flow

The Coinbase International Exchange runs in a region in Tokyo, with the matching engine as a five-node Raft cluster. In front of it sit API gateways that arbitrate access. Requests arrive as messages or REST calls; the gateway is stateless with respect to business logic, though it does perform work like rate limiting to keep things stable and correct. Yu's description of the goal is memorable: "dumb, fat, fast pipes into our matching engine."

The key invariant is that the input log plus the live state completely determine the output. A request from Alice to buy two Bitcoin futures contracts arrives at the gateway over TCP, is converted into a compact internal representation, and is forwarded to the matching engine. If Alice's order trades against two other participants, the output is, in Yu's words, a mouthful: her order was accepted, she traded, Bob traded, she traded again, Charlie traded. All of it is pushed back asynchronously over open connections — clients do not poll for their trades — and the entire system is asynchronous message passing.

Notice the size asymmetry: the single request that caused all this is much smaller than the pile of events it produces. That asymmetry drives the next decision. Replicating the output events cross-region for long-term storage or edge access is expensive — "that's how they get you with the cross-VPC stuff." Because the system is deterministic, you do not have to. Replicate the request stream to downstream systems, have each rerun the matching logic locally, and deliver the resulting mouthful over IPC on the same box at no network cost. This gives direct control over network egress spend, and it means the thing being transported is the request stream rather than the output.

Yu generalises with an analogy any backend engineer will recognise. Imagine the input is a SQL statement and the output is the write-ahead log. One UPDATE ... WHERE can produce thousands or tens of thousands of WAL entries, so a single request becomes a spike in your change data capture stream. If your system is deterministic, replicate the annoying query instead and generate the change data locally.

flowchart TD
    Clients["Trading clients
messages or REST"] --> GW subgraph Region["Primary region (Tokyo)"] GW["API gateways
stateless, rate limiting"] subgraph Cluster["Matching engine — Raft cluster of 5, tight placement"] Leader["Leader
single-threaded core logic"] Followers["4 followers
quorum + snapshots"] end Replica["Local core-logic replicas
beside each gateway"] end Remote["Remote region
analytics, back office, DR"] GW -->|"compact input message"| Leader Leader <-->|"Raft replication"| Followers Followers -.->|"append-only log to EBS, S3, Kafka
off the hot path"| Durable[("Durable storage")] Leader -->|"input log stream"| Replica Replica -->|"reruns logic, serves reads
from memory over IPC"| GW GW -->|"async events: acks, fills"| Clients Leader -->|"input log stream, not output"| Remote

Running copies of the matching logic local to the gateways serves a second purpose: queries no longer perturb the writer. These replicas are strongly consistent and stream updates, so they run slightly latent but not far behind, and they answer order queries out of memory in microseconds without touching disk. Sending those reads to the leader instead would double the latency with a round trip. The same mechanism extends outward: non-latency-sensitive analytics and back-office queries can be served from another region. Yu points out that the usual way to protect a database from read load is a cache such as Redis, and that this design does not need an arbitrary cache — you rerun the core logic and serve directly from it. Added context on the wording: Yu calls these replicas strongly consistent because they apply exactly the same input log in exactly the same order, so they never show a state the leader did not pass through. They are not linearizable — a read may reflect a bounded lag behind the leader — so treat them as read replicas that are never inconsistent, only slightly behind.

Disaster recovery comes out of the same construction. If the primary side is wiped out, you cut the replication link, promote the remote core logic, and you are recovered roughly as fast as you can get approval to do it. Yu notes the promotion can be automated but that people want to be sure before triggering a DR event.

Do Not Spread the Hot Path Across Availability Zones

The scalability of the whole system is tied to the performance of the hot path, so Yu defines it precisely: an order entering the gateway, going to the cluster of core logic, and coming back out. Nothing else counts. Do whatever you like elsewhere, but that path must be quick. His first rule is the blunt one — "a simple way to make stuff fast is just get rid of stuff," so do not do things that are not mission critical — and his second is to avoid blocking the hot path, minimising any reason for the processor to sit waiting.

This produces his most contrarian recommendation. The Raft nodes must be physically close, because a commit requires acknowledgement from other nodes. "Don't bother putting those three clusters on different AZs." A cross-AZ round trip costs 3 to 4 milliseconds, which for this system "is literally an outage every message." His preference is stated as a trade: "I'd rather have an outage every few trillion messages than an outage every message." Use tight cluster placement, accept that a full region outage is handled by DR, and remember you are already running five nodes with substantial redundancy inside the region.

He adds a second, social justification. The participants who care about latency are colocated with you anyway, so anything that takes you out takes them out too. When that happens you pick up your customers and perform the DR exercise hand in hand, having rehearsed it with them beforehand — and if they will not do the exercises with you, they do not really care. He closes the section with a warning aimed at a common pattern: think hard before spreading your Kafka, or equivalent, all over the world in front of a highly contingent transactional system.

Added context and caveat: this advice is specific to a system whose correctness model tolerates losing a region and recovering deliberately, and whose latency budget is under a millisecond. Most systems are better served by multi-AZ redundancy. What generalises is the reasoning — quantify what a cross-AZ round trip costs relative to your latency budget, and be explicit about which failure you are actually buying protection against.

Making the Data Cheap to Handle

Transactional cores attract junk. Yu notes that you often want to support customers with unusual, very long or very short names, but the transactional system does not need that information. Avoid arbitrarily nested and arbitrary-length data, "because what happens is at the worst time someone's going to give you a blob of bytes and you'll wonder what the heck happened."

CPUs, by contrast, are excellent at walking sequentially through bytes. The internal message representation uses Simple Binary Encoding — fields at fixed byte offsets, one after another. Six bytes in is the message type, so the process can decide whether it even cares about the message almost immediately; the next eight bytes are the instrument ID, then the price, then the quantity. Contrast that with deeply nested Protobuf, where you must remarshal and "build up this whole palace of Legos" before you can even tell what kind of message you are holding. Yu's minimum ask is that at least your message types use a simple offset-based layout. A useful side effect is that many services can share the same log without interfering with each other.

You can fit a lot into 64 bits. Rather than UUIDs, use Snowflake IDs: globally unique, sortable by timestamp, and 64 bits wide. Added context: a Snowflake ID packs a timestamp, a machine identifier, and a per-machine sequence number into a single integer, so it is generated without coordination while remaining roughly time-ordered — and a 64-bit integer is dramatically cheaper to copy, compare and index than a 128-bit UUID, especially as a string.

Fixed-width fields laid out contiguously also give you, on the JVM, "a facsimile of structs." The team stores SBE structures in an off-heap map.

Fighting the JVM and the Operating System

Yu anticipates the obvious objection: the JVM? Java is slow, garbage collectors pause for milliseconds, and a concurrent collector lowers throughput — surely you have lost already. His answer is a single sentence: "Your garbage collector won't run if you never call new."

That means provisioning everything ahead of time. You should not allocate memory on a per-order basis; keep buffers of data, and hold hot state in off-heap structures so you are not subject to what he calls spooky action at a distance from the collector. He is careful to bound the rule: garbage collection is great, and test code, web code and analytics code can be totally fluffy — the discipline applies to the hot path only. And the cost is not just collection pauses. Allocation itself can be computationally intensive, and while you are allocating and page faulting, every order queued behind you is stalled. If you are running single-threaded, excise the extra news.

Next, the operating system, which Yu describes as "like the government, get out of my body." Left alone, the OS scheduler will preempt your core at any moment to run something that has nothing to do with your business. The remedy has three parts: pin the thread to a specific CPU core, move interrupts off that core, and let the thread busy spin — a while loop that processes new work if there is any and otherwise keeps spinning. Do not give up your compute if you care about responsiveness in a single-threaded system.

Just as you avoid arbitrary-length data, avoid arbitrary-length compute. Yu says to be very suspicious of for loops that iterate over unbounded structures, "because that is how you get pathological P99.999s." The patterns come from databases: prefer indexed access over scanning a table, so use hash maps rather than looping over everything, and choose structures that cannot randomly explode. When you genuinely have a large operation, break it up — the database equivalent is pagination, and here it means continuations. Do a chunk of work, send results out, handle other requests, then continue.

Finally, control what reaches the core at all. It is tempting to put everything in the core and let it handle messages from everyone, but you end up with a massive component that takes years to decompose. Rate limit. Do not give users an incentive to send you economically irrelevant transactions, because removing that incentive is good for everybody. "The best optimization for a transaction is one that doesn't need to happen."

What They See in Production

Yu reports that this architecture, running in the cloud, spikes to six-figure transactions per second with no issues and no pages, keeping P99 response times to customers under a millisecond. His comment on the cloud is worth quoting for anyone who assumes low latency requires on-premise hardware: if your boxes are really close to each other, the cloud can do things pretty quickly these days, and "there's nothing we did, that's the bleeding edge catching up to what's on-premise." These are the speaker's reported production figures for one exchange, not an industry benchmark.

The capability he is most enthusiastic about is replay debugging. Because everything fits on one thread, the entire working memory fits on one machine — which means it fits on his laptop. When something looks strange in the market, he downloads the request log, replays it, and runs the production logic in a debugger, right down to inspecting individual registers. That is invaluable both for deciding whether an anomaly can wait for the next release or needs an immediate fix, and for chasing pathological performance — why did this buffer get really big? — since the same replay can be run through a profiler.

Replay also enables production-scale experimentation. You can stream the request log to a second stack and perturb it: run experiments on real production data, pre-test rolling deployments and configuration changes, and validate complicated cloud topology changes under live production load. Yu calls doing topology changes in the cloud terrifying to anybody, and says the ability to rehearse them against streaming production traffic has been a superpower for the team.

Everything, he argues in closing, is chasing the engineering ideal of simplicity. A simple thing is easy to make stable, because there are fewer variables and you can just run five of them. It is fast, because you control what the operating system does and you have already simplified the data going in and out. And because it is fast and easy to test, you can deploy changes quickly. His parting claim: remove enough boxes from your architecture diagram and there are probably easy 10x opportunities sitting in your system right now.

Details From the Q&A

The audience questions fill in the parts the architecture diagram glosses over.

Only the input is persisted; the state lives in memory. Asked what bounds guarantee the state fits in RAM, Yu confirmed the output is not stored and the input is written append-only. The bid and ask state has to fit in memory, but with fixed-length records — an active order is about 250 bytes — you fit a great many active orders in a process, and RAM is far cheaper than it used to be. The persistence SLA is that three of five machines have received the message; downstream, replication writes to EBS, eventually S3, and eventually Kafka for durable on-disk storage, none of which is in the hot path. Achieving cheap append-only durability involves "creative fsync orderings."

Snapshots are an optimisation, and they run off the leader. Asked how you restore the state that existed just before a given input in order to replay from there, Yu answered that in principle you do not need snapshots at all — you could replay from zero — but that is not realistic. You take a snapshot as often as your replay needs demand, hourly or every ten minutes. Crucially, because the system is deterministic, the snapshot, which scans the entire memory space, does not have to run on the write leader; it can run on a follower, out of band, without affecting throughput or jitter. You write the memory image to a binary blob in S3, tag it with the log position it represents, then load it and replay from the next entry.

Decouple code deployment from behaviour change. Asked how rolling deployments avoid a state where some nodes behave differently from others, Yu gave a rule that is easy to state and easy to get wrong: deploy the code, and the new code must do exactly the same thing as the old code so determinism holds. Then send a request into the input log that enables the new behaviour. Because the switch is itself a log entry, every replica flips at the same logical point, and any future replay reproduces the change at exactly the right position in history.

Scale vertically, and optimise for clock speed rather than core count. Yu confirmed with a one-word "yes" that they go big on machines. The questioner — not Yu — observed that this is somewhat an antipattern relative to how Google approached scaling, and that CPUs and memory are now cheap. Yu's substantive answer was about clock speed: that, not core count, is what matters, so a small number of hot machines suffices; maybe 16 or 32 cores so the OS has room to do other things, with one hot CPU doing the hot work. You also get Moore's Law improvements essentially for free as you upgrade CPUs. If you somehow need beyond millions of transactions per second, sharding is possible, but you can get quite high in the cloud with no sharding at all and keep a simple system that fits in the palm of your hand.

Yes, the hot CPU is pegged all the time — literal busy spinning. Only the core logic thread does this; everything else does what normal CPUs do, which is sit idle. That raises an excellent operational question the questioner drew from similar systems such as Redpanda: if the CPU always reads as 100%, how do you know how much headroom you have left? Yu's answer is that replay gives you a permanent load test. A replica replaying ten minutes behind runs as fast as it possibly can, with no network in the way, so you always know your red line. "I can confidently tell you 300k, that's my red line," and code optimisations move that number.

Trade-offs And Limitations

  • The single thread is a hard ceiling. Throughput is bounded by what one core can do, which is why clock speed matters more than core count and why Yu names sharding as the escape hatch beyond millions of transactions per second. He is clear you can go a long way before needing it, but the ceiling is real and it cannot be raised by adding machines.
  • Determinism constrains how you write code, permanently. Every optimisation in this architecture — replay, off-leader snapshots, input-stream replication, read replicas, DR — depends on identical inputs producing identical outputs. That is why a deployment may not change behaviour and why a feature toggle has to travel through the log. As noted earlier, the same constraint extends to wall clocks, randomness and unordered iteration inside the hot path.
  • Single-AZ placement trades regional resilience for latency. Yu accepts this explicitly and covers it with DR plus a rehearsed, human-approved failover rather than automatic multi-AZ redundancy. Recovery time therefore includes the time it takes to get approval. This is defensible for a venue whose clients are themselves colocated; it is a poor default for a general service.
  • State is memory-resident and therefore bounded by RAM. The argument that 250-byte orders make this comfortable holds only while records stay fixed-length and the product does not accumulate open-ended per-order data.
  • You cannot feature-flag. Fairness forbids giving some participants better behaviour, so every release is big bang. Confidence has to come from replay testing and shadow stacks instead of gradual exposure.
  • A pegged CPU destroys the usual utilisation signal. Busy spinning means standard CPU metrics tell you nothing about headroom, and the team substitutes replay throughput as the capacity measure. Any organisation copying the busy-spin pattern needs a comparable substitute before it can do capacity planning.
  • Allocation-free JVM code has an engineering cost. Preallocated buffers and off-heap structures are harder to write, read and refactor than idiomatic Java, and Yu confines the discipline to the hot path precisely because it is expensive. Fixed-offset binary encoding carries a related cost around schema evolution that the talk does not discuss.
  • Simplicity here is bought with strictness. The system is simple in structure because it refuses inputs — rate limits, bounded loops, fixed-length data, no chatty clients. That refusal is a product decision as much as a technical one, and not every domain can make it.

Practical Takeaways

  1. Write the black-box behavioural harness first, framed as input, state, and output. Yu does this every time he builds an exchange, and that separation is what later makes the input log sufficient to reconstruct everything else.
  2. Look for the three-word rule that collapses your spec. "Price, time, priority" replaces pages of requirements; most domains have an equivalent compression, and finding it is what keeps the core small.
  3. Decide explicitly whether you need determinism before you reach for concurrency. If audit, replay, or reproducible debugging matter, ordered single-threaded execution buys them, and it makes latency and throughput the same metric to optimise.
  4. Consider consensus rather than a database for hot-path durability, so business logic proceeds concurrently with replication instead of blocking on a network round trip. Run five nodes rather than three so you can lose two.
  5. Replicate inputs, not outputs, wherever downstream consumers can rerun your logic. It shrinks cross-region egress, and it turns replicas into read servers that never disturb the writer. The SQL-versus-WAL analogy is the test: if one request produces thousands of downstream events, you are shipping the wrong thing.
  6. Measure what a cross-AZ round trip costs relative to your latency budget before spreading a latency-critical cluster for redundancy. Milliseconds are free in most systems and fatal in this one.
  7. Use fixed-offset binary encoding for hot-path messages, at minimum for the message type, so a process can reject irrelevant messages without parsing. Use 64-bit sortable IDs rather than UUIDs.
  8. Eliminate allocation and scheduling jitter in the hot path only — preallocate buffers, keep hot state off-heap, pin the thread to a core, move interrupts away, and busy spin. Leave the rest of the codebase idiomatic.
  9. Bound your compute as carefully as your data. Replace scans with indexed lookups, and split large operations into continuations that yield between chunks so one request cannot stall the queue behind it.
  10. Rate limit, and remove incentives to send you worthless transactions. The cheapest request is the one that never arrives.
  11. Ship code and behaviour changes separately. Deploy behaviour-identical code, then flip the switch by writing a request into the input log.
  12. Turn your replay capability into tooling: laptop-scale debugging of production incidents, a continuously running load test that reveals your red line, and a shadow stack for rehearsing deployments, configuration changes and topology migrations under live traffic.

Key Terms

  • Exchange — A venue where participants submit buy and sell orders; it publishes prices and reports fills, acting as a trusted third party for the market.
  • Matching engine — The core component that maintains the order book and pairs incoming orders against resting ones according to the matching rules.
  • Order book — The set of resting buy interest (bids) and sell interest (asks) at each price level.
  • Bid / ask — Public representations of resting buy and sell interest. Ask is also commonly called offer.
  • Top of book — The best price on each side: the highest bid and the lowest ask.
  • Resting order — An unfilled order sitting in the book, available for incoming orders to trade against.
  • Limit price — The worst price at which an order's owner is willing to trade; a resting order never trades beyond it.
  • Fill — The execution of an order, in whole or in part, against an order on the opposite side.
  • Price, time, priority — The matching rule: better prices fill first, and among equal prices the earliest order fills first.
  • Cross — When an incoming order's price overlaps resting orders on the other side, producing a trade.
  • Price improvement — Receiving a better price than your limit; in the talk's model, the participant arriving second into a cross generally gets it.
  • Determinism — The property that identical inputs in identical order produce identical outputs, enabling replay, off-leader snapshots, and input-stream replication.
  • Raft — A consensus algorithm that replicates an append-only log from a leader to followers, committing an entry once a quorum acknowledges it and electing a new leader on failure.
  • Quorum — The majority of cluster nodes that must acknowledge an entry before it is considered durable.
  • Hot path — Here, the route from an order entering the gateway, through the core logic cluster, and back out; the only path whose latency is optimised.
  • Simple Binary Encoding (SBE) — A message format placing fixed-width fields at fixed byte offsets so a CPU can read them sequentially without parsing or object construction.
  • Snowflake ID — A 64-bit identifier combining timestamp, machine ID and sequence number, giving uncoordinated generation with rough time ordering.
  • Busy spin — Keeping a thread in a tight loop rather than yielding to the scheduler, so it never has to be rescheduled onto a core.
  • CPU pinning — Binding a thread to a specific core, usually with interrupts moved elsewhere, to eliminate scheduler-induced jitter.
  • Continuation — Splitting a long operation into chunks that yield between them, so no single unit of work blocks the queue.
  • Snapshot — A periodic dump of in-memory state tagged with its log position, used to bound replay time; an optimisation, not a correctness requirement.
  • Replay debugging — Downloading the production input log and re-executing production logic locally under a debugger or profiler.

The through line is that every capability in this talk falls out of one decision. Handling inputs strictly in order on one thread makes the system deterministic; determinism makes the small input log a complete description of the system, and once that is true, the log becomes your durability mechanism, your replication mechanism, your read replicas, your disaster recovery, your debugger, your load test, and your feature-flag channel. Yu's closing advice — look for the boxes you can delete from your architecture diagram — is an invitation to find the property in your own system that, if you protected it properly, would let several separate subsystems collapse into one.


Reference: Frank Yu, How to Build an Exchange: Sub Millisecond Response Times and 24/7 Uptimes in the Cloud, QCon San Francisco, published by InfoQ.