Linux I/O / updated Jun 20, 2026
io_uring From First Principles
A Linux systems mini-series starter on io_uring: submission queues, completion queues, batched I/O, WAL group commit, and storage-engine block reads.
Question
What does io_uring actually give a storage engine beyond the vague promise of “async file I/O”?
Core Idea
Traditional blocking I/O has a simple shape:
call read()
kernel does work
thread waits
read() returns
That is easy to program, but it ties an operation to a waiting thread.
io_uring changes the interface into two shared queues:
submission queue userspace -> kernel
completion queue kernel -> userspace
Userspace writes requests into the submission queue. The kernel processes them and writes results into the completion queue.
The basic loop:
prepare SQE
submit SQE
kernel performs operation
read CQE
handle result
SQE means submission queue entry. CQE means completion queue entry.
Why It Matters
A storage engine may want to:
- append WAL records
- fsync batched writes
- read SSTable blocks
- write compaction output
- prefetch blocks for scans
- avoid parking one thread per slow operation
The useful io_uring shape is not just async. It is batched submission and batched completion.
Instead of:
read block
wait
read block
wait
read block
wait
the engine can move toward:
submit many block reads
do other work
collect completed reads
WAL Group Commit
The WAL is one of the most tempting places to use io_uring.
A simple write path wants:
encode record
append to WAL
possibly fsync
update memtable
acknowledge write
If every write waits for its own fsync, latency gets expensive. A better engine can batch commits:
collect many writes
write them together
fsync once
acknowledge the group
With io_uring, a future version could submit linked work:
write WAL bytes
fsync WAL file
complete batch
That should come after the blocking WAL is correct and benchmarked.
SSTable Reads
An SSTable point lookup often does:
check Bloom filter
search block index
read one data block
decode entries
Most reads should eventually hit a block cache. But misses, scans, and compaction inputs can benefit from overlapping block reads.
Possible uses:
- concurrent point lookups
- range-scan prefetch
- compaction input streams
- block-cache miss handling
Working Rule
Do not add io_uring because it sounds modern.
Add it only when the engine has a clear batch, prefetch, compaction, or group-commit workflow that benefits from submitting work first and handling completions later.
Mental model:
io_uring is a shared queue interface for describing I/O work and collecting results.