An order book that survives kill -9.
C# order matching with a write-ahead log, group commit and live trades.
Crash test result
0 lost of 43,808 acknowledged orders
- Test
python/crash_test.py- Method
- 10 rounds of
kill -9while 32 clients place orders, then restart and re-send every acknowledged order - Mode
- Durability = Group
Same guarantee, 55× the throughput
One fsync covers every order in a batch, and each client is acknowledged only after it.
| Path | Orders/s | p50 | p99 | p99.9 | Survives |
|---|---|---|---|---|---|
| Matching engine alone | 476,367 | 0.3 µs | 2.2 µs | 6.0 µs | n/a |
| End to end, no fsync | 148,800 | 0.32 ms | 2.9 ms | 20.8 ms | process crash |
| End to end, group commit | 20,318 | 3.0 ms | 6.0 ms | 30.3 ms | power loss |
| End to end, fsync per order | 372 | 189 ms | 210 ms | 210 ms | power loss |
End-to-end latency is mostly queueing: with 64 orders in flight, p50 is about 64 ÷ throughput (Little’s law). The engine’s worst call in a million, 153 ms, was a GC pause. “Power loss” follows from the fsync design; the crash test kills the process, not the power.
One thread owns the book
No locks in matching, and the log order is exactly the execution order.
Queue
Requests arrive over a bounded channel; callers wait when it is full.
Append to the log
Each record is
[length][CRC32][payload].Match
Price-time priority, integer ticks, O(1) cancel lookup.
Fsync once per batch
Up to 256 queued orders share one fsync of about 3 ms. Under light load a batch is one order.
Acknowledge the batch
Trades go to the WebSocket feed and SQLite.
Replay
On start the log is replayed; a torn last record fails its checksum and is cut.
Idempotent retries
The same clientOrderId returns the original result, even after a restart.
SQL bars
Minute OHLC and VWAP with window functions over 1M trades. Last hour: 16.3 ms with the index, 82.2 ms without. A full day: 1.1×.
28 tests
Matching, recovery in every durability mode, torn logs, HTTP across a restart.
Reproduce it
The numbers above come from these commands. One laptop; the crash test starts its own server.
# 28 tests
dotnet test
# latency and SQL tables
dotnet run -c Release --project bench/Tickflow.Bench -- results
# kill -9 test, 10 rounds
cd python && uv venv && uv pip install aiohttp pandas matplotlib
.venv/bin/python crash_test.py --rounds 10