Skip to content
Tech News
← Back to articles

We Replaced MMAP with Io_uring in Our Rust Query Engine. It Got Slower

read original get Samsung 990 PRO NVMe SSD → more articles
Why This Matters

A Conviva engineering post-mortem shows that swapping mmap for io_uring in a DataFusion/Arrow Rust query engine actually hurt performance, a useful counterweight to the current enthusiasm for io_uring as a drop-in I/O upgrade. The details matter for anyone building analytics engines on local NVMe, where page-cache behavior under heavy concurrency—not raw disk throughput—turns out to be the real bottleneck.

Key Takeaways
Worth a Look

Samsung 990 PRO NVMe SSD — The article's whole story hinges on reading multi-gigabyte Arrow IPC files off fast local NVMe, and the 990 PRO is a go-to PCIe 4.0 drive for exactly that kind of throughput-hungry workload. Great for anyone building or benchmarking a query engine at home and wanting local storage that won't be the bottleneck.

See Samsung 990 PRO NVMe SSD on Amazon → Affiliate link — we may earn a commission on purchases, at no extra cost to you. Product picked by AI based on this article; it is not a tested recommendation.

In the beginning, there was mmap. It was convenient: it let us lazily read huge numbers of Arrow IPC files from disk without managing memory ourselves. It fit our file format perfectly — Arrow IPC’s layout is designed for zero-copy random access, and mmap gives you exactly that.

Then we deployed to production, ran real concurrent query loads, and mmap became a real problem.

Our Workload

At Conviva, we analyze trillions of events a day to pinpoint and diagnose end user experience. At the core of our architecture is an event and pattern analysis engine built on DataFusion, Arrow, Rust, Rayon, and Tokio. Raw events get transformed, encoded in a proprietary mostly-numeric format, and stored in the cloud. We copy them to local NVMe and read large (~3–5 GB) Arrow IPC files. We chose Arrow IPC for simplicity and speed — its memory and disk layouts are identical, so decode cost is minimal, and mmap gives us zero-copy reads natively supported by arrow-rust. A typical query touches 6 columns across 8 batch files (one batch per file), ~1.6 GB per batch, ~13 GB total per day of data.

The Test Setup

Hardware: 192-core box, ~750 GB RAM. Two disk configs during the investigation: 2× NVMe LVM-striped (~5.5 GB/s fio ceiling) and 32× NVMe RAID-0 (~21 GB/s fio ceiling). Kernel 5.15 during investigation, 6.x in production.

The Production Symptom

At lighter loads, mmap worked well — fast, serving queries from raw events in seconds. The trouble started under heavier concurrency. Some latency increase under load is expected — more queries competing for the same CPU. But we saw p95s and p99s spike well beyond what linear scaling would predict, with rows scanned per core dropping sharply even after accounting for concurrency:

OS page cache shrank — each pod consumed more memory as private allocations, less as shared cache

A huge number of page faults

... continue reading