Naoto Exchange
A trading exchange I built alone: it takes orders, matches buyers with sellers and keeps every balance up to date, pairing an order in under a microsecond.
Personal R&D, Jul 2025-present
C++20, DPDK, SSE2/SSE4, epoll (ET), lock-free SPSC, etcd
Source on GitHub- to match an order (P99)
- 600 ns
- to acknowledge it at the gateway (P99)
- 1.1 µs
- memory allocations per order
- 0
Processing time inside the services, network excluded, measured on a 6-core laptop (Ryzen 5 4600H) with 40,000 orders a second sent to it; that's the load during the measurement, not a capacity limit. For scale, one round trip to another machine in the same datacenter usually takes tens of microseconds.
I started Naoto to find out how an exchange stays fast without ever getting a balance wrong. An exchange pairs buyers with sellers and records who now owns what, and speed matters because whoever gets matched first gets the price.
It's three kinds of service. Clients connect to an order gateway, which checks that they can afford what they're asking for. Each asset has its own matching engine, which pairs buy and sell orders. An account service owns every balance. Services can join and leave while the system is running.
Why it's fast
Most of what an order sets off doesn't need to happen while the order waits. Some of it happens earlier: memory is set aside at startup, and a client's balance is fetched from the account service once, at login, instead of on every order. Asking another machine anything costs a network round trip, which dwarfs everything else an order does.
The rest runs on other threads that the order never waits for. The few steps left on its path are built around the hardware: no memory allocated per order, lock-free handoffs between threads, and an order book that checks sixteen slots in one SIMD instruction.
Why it stays correct
The gateway decides whether a client can afford an order using a copy of their balance that's always slightly behind, so it reserves the funds the moment it accepts. Every trade is published once, with a sequence number, and each service builds its own copy of the balances from that stream. A copy that falls behind catches up exactly, with nothing skipped or applied twice.
Where it stands
The path from order to match to balance update works end to end, with latency measured at every stage. Still in progress: recovering trade reports lost on the network, and retiring old connections more safely.
Writing on Naoto Exchange
9 posts, in the order they're meant to be read.
- 1Moving work off the hot pathBefore any code, every step an order takes was designed from what the system needs and what the hardware does, to move as much work as possible off the hot path.Inside one service, 8 min read
- 2One writer for every piece of stateMost locks exist because two things can write the same data, so Naoto makes sure they never do, whether they're separate services or two threads.Across services, 3 min read
- 3One stream of trade reports, many copies of every balanceThe matching engine says what happened once, on a multicast stream, and every service that needs a balance builds its own copy from it.Across services, 3 min read
- 4Checking risk against a balance that's always behindThe risk check never sees a client's real-time balance, only a slightly stale copy, and it has to be correct anyway.Between two threads, 4 min read
- 5Triple-buffering client state without ever blocking a readerA risk check can't wait on whatever thread is updating a client's balance, so the balance lives in three buffers behind one atomic index.Between two threads, 6 min read
- 6Catching up a stale snapshot without blockingInstead of waiting on another service for a client's balance, the gateway replays the updates it has already seen onto whatever snapshot comes back.Across services, 3 min read
- 7When a descriptor changes mid-sendA file descriptor is just a reusable integer, so a thread sending on one while another replaces it can send an order to the wrong socket.Between two threads, 3 min read
- 8One order book, two data structuresAn order book needs exact-price lookups and best-price order at the same time, and no single structure is good at both, so it keeps two.Down to the bytes, 3 min read
- 9Designing a cache-friendly, SIMD-accelerated key-value storePicking, and then hand-building, the data structure the order book actually lives in.Down to the bytes, 10 min read