The old engine was a JVM service that had grown for six years. It was correct, well-tested, and comprehensively understood by four people. It was also spending 40% of its p99 budget inside garbage collection pauses that we could shape but never eliminate.
We did not rewrite it because Rust is fashionable. We rewrote it because the tail latency distribution had a shape that no amount of tuning would change, and because the shape of that distribution is the product. A trader does not experience your median. They experience the fill they did not get.
The first thing we got wrong was assuming the rewrite was a performance project. It was a data-structure project. The order book is the entire system, and the JVM implementation had accreted three different indices onto the same price level because each one solved a real problem at the time. Collapsing those into a single intrusive structure did more for latency than the language change did.
The second thing we got wrong was the migration plan. We intended to shadow the new engine against production for four weeks. It took five months. Every week we found another behaviour that was not in the specification because it had never been written down — self-trade prevention edge cases, the exact ordering of cancel-replace under contention, what happens to a stop order when the trigger and the limit cross in the same tick.
Shadowing is the only reason this shipped without an incident. We replayed every production order against both engines and diffed the resulting book state, byte for byte, for five months. The final divergence count was eleven, all of them cases where the old engine was wrong and nobody had noticed.
Today p50 order-to-ack is 0.9 ms and p99 is 3.1 ms, down from 4.2 ms and 51 ms respectively. We publish the benchmark methodology and the raw numbers quarterly, because a latency claim you cannot reproduce is marketing, not engineering.