Lock-free SPSC ring buffers for tick ingestion
A back-pressure-free producer/consumer pattern that survived a 27k tick/sec burst without dropping a frame.
The Hook
The tick ingester started dropping frames at peak load. Single-producer, single-consumer bounded channels in Tokio were doing fine at 6k tick/sec — and then 27k arrived during the close-of-day burst and the producer started parking on send().await.
The Context
The standard advice is “increase the channel capacity”. That hides the problem until the next burst. The real lever is to remove the lock entirely from the hot path. A lock-free SPSC ring buffer is the right primitive for a single-producer-single-consumer queue: atomic head/tail indices, no kernel mediation, predictable latency.
The Code
use crossbeam::queue::ArrayQueue;
use std::sync::Arc;
pub fn make_ring<T>(capacity: usize) -> (Arc<ArrayQueue<T>>, Arc<ArrayQueue<T>>) {
let q = Arc::new(ArrayQueue::new(capacity));
(q.clone(), q)
}
The producer thread calls push; the consumer thread calls pop. No locking, no syscalls in the fast path.
The Takeaways
- Lock-free is not free — it shifts the cost to atomic ordering, which is a real CPU expense at MHz rates.
- Capacity sizing matters: too small and you back-pressure; too large and you hide a real producer/consumer imbalance.
- Always benchmark with a realistic burst pattern. The mean rate is the lie that hides the tail.