A toy order matching engine for a prediction market built in Rust.
Screen.Recording.2026-06-24.at.6.33.20.AM.mp4
Start Kafka:
docker-compose up -d
Start the matching engine worker:
cargo run --bin worker
Start one or more API servers:
PORT=3000 cargo run --bin server
PORT=3001 cargo run --bin server
Place an order
POST /orders
{"side": "Buy", "price": 50, "qty": 10}
Get the orderbook
GET /orderbook
WebSocket feed
ws://localhost:3000/ws
Connects and receives fill events in real time.
All API servers publish incoming orders to a single Kafka topic (orders, 1 partition). A single worker process consumes from this topic under consumer group engine. Kafka guarantees only one consumer in a group reads each message from a partition, so no order is ever processed twice regardless of how many API servers are running.
The worker is the only process that runs the matching engine and holds the orderbook in memory. API servers have no matching logic , They just publish to Kafka and return an order ID.
BTreeMap<Reverse<u64>, VecDeque<Order>> for bids and BTreeMap<u64, VecDeque<Order>> for asks.
BTreeMap keeps price levels sorted automatically (No need to sort separately )bids use Reverse<u64> as the key so the highest bid is always at the front, asks use u64 so the lowest ask is always at the front. This means finding the best price is O(1) and no manual sorting is needed.
VecDeque<Order> at each price level maintains time priority , new orders are pushed to the back, matches pop from the front. It shifts the point to next data instead of shifting data .
The single worker is the bottleneck. It processes orders sequentially from one Kafka partition. At high throughput it will fall behind. Everything downstream fills, snapshots, WebSocket broadcasts will lag.
The orderbook snapshot approach also gets expensive under load. Publishing a full snapshot after every single order means serializing the entire book on every message. With a large book and high order rate this becomes significant overhead.
Replace the orderbook snapshot topic with a Redis-backed read model. The worker writes only fills and resting order updates to Redis. API servers read from Redis for GET /orderbook. This removes the snapshot overhead from the hot path entirely and decouples the read model from the matching engine.
After that scale the worker horizontally by sharding orders by market symbol across multiple Kafka partitions, one worker per partition, each with its own in-memory book for eg -> kafka-key will be btc-usdc