#01 1. Where epoll Breaks Down on Modern NVMe and 100G NICs
For years, high-concurrency network servers on Linux have been built around epoll (Nginx, Redis, Envoy and Netty all are). But as 100GbE / 400GbE NICs and PCIe 5.0 NVMe SSDs became commonplace, hardware throughput climbed into the tens of millions of IOPS per second.
Under that kind of load, epoll's structural weaknesses are magnified without limit:
- Constant context switching: serving one million requests takes at least two million syscalls, and the CPU burns enormous amounts of time saving and restoring registers;
- Multi-core scaling and thundering-herd contention: threads sharing an epoll fd contend on the
epmutexlock; - Redundant data copies: packets are DMA'd from the NIC into the kernel socket buffer, then copied again from kernel space into user-space application memory, badly thrashing the CPU's L3 cache.
#02 2. io_uring Primitives: The SQ / CQ Dual-Ring Topology and Its Lock-Free Design
io_uring, introduced by Jens Axboe, upended Linux's I/O philosophy. It uses mmap to map kernel and user space onto the same block of contiguous physical memory, and builds two ring buffers on top:
- Submission Queue (SQ): user space writes the structures describing the I/O operations it wants performed (SQEs);
- Completion Queue (CQ): once the kernel has finished the hardware DMA, it pushes result structures (CQEs) onto the CQ ring.
The two sides coordinate through memory barriers and atomic head/tail pointers, with no mutex anywhere.
I tested IORING_REGISTER_BUFFERS on Linux 6.6 and single-connection latency dropped straight from 28ÎĽs to 3.2ÎĽs. Genuinely useful for production.