Stop Reaching for io_uring. The Kernel Is Already Smarter Than You

If you’ve been building high-throughput Linux services or storage engines lately, you’ve probably scrolled past articles about io_uring and felt a sudden wave of technical dread. Are you using the right tool? Are you leaving performance on the table? (And let’s be honest, every time it scrolled past, you read it as ‘io_uring without Radiohead’—and honestly, what’s the point of async I/O without Thom Yorke?)

The tech industry hype machine tells us that async is king. If you want speed, you use the shiniest, newest asynchronous I/O interface. But here is the dirty secret nobody mentions: a state-of-the-art async API is often the completely wrong tool for the job.

Benchmarks are the ultimate trap. They tell you what is fast in a vacuum, not what is safe in a storm.

Look at the recent debates around Turso and SQLite. Developers are realizing that if you’re doing contiguous reads, the old, boring preadv syscall absolutely obliterates io_uring in many cases. Why? Because when you use io_uring with O_DIRECT, you’re trying to outsmart the kernel’s page cache and readahead mechanisms. You’re fighting the very system designed to make your life easier.

We’ve been conditioned to think that bypassing the kernel makes us hardcore. But if your database doesn’t own the complete bare-metal box it’s running on, raw syscall overhead is the wrong metric to optimize for.

You don’t get a medal for bypassing the kernel. You just get a slower database and a misplaced sense of superiority.

The anxiety of making a low-level I/O choice is real because it silently determines your database’s performance. But we need to stop treating io_uring like a magic wand. In embedded database contexts, predictability, multi-process safety, and kernel buffering matter far more than shaving off a few microseconds of syscall overhead.

If you’re building storage engines, you need to know when an async API adds complexity without payoff. You need to recognize when the kernel’s facilities are already doing the heavy lifting.

Stop optimizing for the benchmark. Start matching your I/O primitive to your actual access pattern and hardware.

Performance isn’t about using the newest tool; it’s about knowing when the old tools are already doing the heavy lifting.

FAQ

Q: If io_uring is slower, why was it created?

A: io_uring is brilliant for massive, high-concurrency, non-contiguous I/O workloads where context switch overhead is the bottleneck. The problem isn't the tool; it's the misapplication of using a sledgehammer for a scalpel's job when doing simple contiguous reads.

Q: Should I rip out io_uring from my database right now?

A: Not necessarily. But if you're using O_DIRECT and io_uring for contiguous reads, you should benchmark preadv. You might find the kernel's existing readahead and page cache already beats your async implementation.

Q: Are benchmarks completely useless then?

A: For embedded databases sharing hardware, yes. A benchmark assumes you own the hardware. In reality, predictability, multi-process safety, and kernel buffering beat raw throughput every single time.

📎 Source: View Source