2026-07-03
A 10Gbps NIC receiving 64-byte packets at line rate sees ~14.88 million packets per second. If each packet triggered an interrupt, your CPU would spend all its time in the interrupt handler — every interrupt costs 1-5μs of context save/restore, cache pollution, and pipeline flush. At 14.88M IRQs/sec, that's more than a full core's worth of overhead just entering and leaving handlers, before you've processed a single byte.
Interrupt coalescing is the NIC's answer: batch multiple packet completions into one interrupt. Two knobs control it:
Whichever threshold hits first triggers the interrupt. The NIC's descriptor ring keeps filling; the driver processes the whole batch in one softirq run.
The tradeoff is stark. Coalescing amortizes interrupt cost across many packets (great for throughput) but adds latency (bad for RPC, HFT, gaming). A rx-usecs=100 setting means every packet waits up to 100μs before the CPU even knows it arrived — an eternity for a database that expected 10μs round-trips.
Real-world example: Netflix's FreeBSD CDN servers famously tuned coalescing per workload. For bulk video streaming they set rx-usecs=125 — throughput matters, latency doesn't. For their control-plane API servers they set rx-usecs=0 with rx-frames=1 — every packet interrupts immediately. Same NIC, same driver, opposite settings. Check yours with ethtool -c eth0; tune with ethtool -C eth0 rx-usecs 50 rx-frames 32.
Adaptive coalescing (enabled with ethtool -C eth0 adaptive-rx on) lets the driver adjust thresholds based on measured packet rate. Under low load it drops to rx-usecs=10 for latency; under 10Gbps sustained load it raises to rx-usecs=100 for throughput. Most modern Intel/Mellanox drivers ship this on by default.
Rule of thumb: interrupt cost is ~2μs. If your packet processing takes 500ns per packet, coalescing 10 packets amortizes the 2μs down to 200ns per packet — the interrupt overhead drops from 80% to 29% of total CPU time. Diminishing returns kick in around batch=32; beyond that you're paying latency without meaningful throughput gain.
Beware: coalescing interacts badly with NAPI polling. Once the softirq drains the ring, the NIC re-arms interrupts — but if the coalescing timer is 100μs, the next single packet still waits 100μs. That's why latency-sensitive tuning needs both rx-usecs=0 and busy-polling (SO_BUSY_POLL) to skip the interrupt path entirely.
