The io_uring SQPOLL Thread: Submitting I/O Without a Syscall At All

2026-07-10

Regular io_uring already cuts syscall overhead by batching submissions: you fill the submission queue (SQ) in shared memory, then call io_uring_enter() once to tell the kernel to consume them. But that single syscall still costs ~500ns and blows out your L1 with kernel code. SQPOLL mode eliminates it entirely — a dedicated kernel thread polls the SQ ring and dispatches I/O as soon as entries appear.

You enable it by setting IORING_SETUP_SQPOLL in io_uring_setup(). The kernel spawns a thread (visible as iou-sqp-<pid>) that spins on the SQ tail pointer. Your userspace code just writes an SQE, advances the tail with a release store, and moves on — no syscall, no context switch, no ring transition. The submission-to-dispatch latency drops from microseconds to tens of nanoseconds.

The catch: a spinning kernel thread burns a whole core. To avoid this, SQPOLL parks itself after sq_thread_idle milliseconds of inactivity (default 1ms, tunable). When parked, the IORING_SQ_NEED_WAKEUP flag appears in the SQ ring's flags field. Your submission fast-path must check it:

Real-world example: ScyllaDB's Seastar framework uses SQPOLL for its storage engine on dedicated I/O cores. On an NVMe workload doing 1M IOPS per core, the syscall overhead of vanilla io_uring (~500ns × 1M = 500ms/sec = 50% CPU) disappears entirely. They pin the SQPOLL thread with IORING_SETUP_SQ_AFF to a sibling hyperthread so the polling thread shares the L2 with the submitting thread — SQE writes stay hot in cache.

Rule of thumb: SQPOLL wins when your submission rate is high enough that the polling core is genuinely earning its keep. Break-even is roughly 1 submission per microsecond — below that, a busy core burning 100% to save 500ns per call is a bad trade. Above that, and especially above 100k IOPS/core, SQPOLL becomes essentially free per-op.

Since kernel 5.13, unprivileged users can enable SQPOLL (it used to require CAP_SYS_NICE). And since 5.11, multiple io_uring instances can share one SQPOLL thread via IORING_SETUP_ATTACH_WQ, so a 16-core server doesn't need 16 pollers.

See it in action: Check out IO Programming Tutorial - Part III: io_uring by praisethemoon to see this theory applied.
Key Takeaway: SQPOLL replaces the io_uring_enter() syscall with a kernel thread polling shared memory, dropping submission latency to tens of nanoseconds — worth the burned core only above ~1M ops/sec.

All newsletters