Zachary Lili

Queueing discipline, buffers, and the slow path

June 2026·Networking·11 min read

Bufferbloat is one of those problems that hides in plain sight. A link tests fine on a throughput measurement and feels terrible in interactive use, because the buffer in front of it has filled with packets that are now waiting their turn.

What the buffer is for

A queue exists to absorb bursts. Without it, a sudden arrival of packets faster than the egress rate would simply be dropped, and dropping is expensive because it forces retransmission. The trouble is that the same buffer that absorbs a microsecond-scale burst will happily absorb a second's worth of backlog if the arrival rate stays above the drain rate.

Once the backlog exists, every packet behind it waits. For bulk transfer this is invisible. For a voice call, a keystroke, or a TLS handshake, it is the entire experience.

Why fq helps

A fair queue splits the single buffer into per-flow queues and services them round-robin. A bulk download no longer sits in front of an interactive flow; they interleave. The bulk transfer still gets its throughput, because it has many packets in flight and is able to fill whatever share it is given, but it stops monopolising the buffer.

On Linux this is a two-line change:

sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr

The congestion control choice and the queueing discipline are separate decisions, but they interact. BBR estimates bandwidth and round-trip time directly and paces its sends accordingly, so it tends to keep queues short on its own. Running BBR without a fair queue leaves an older problem in place, though, so the two usually travel together.

Verifying it took effect

Setting the sysctl is not the same as the change being live. Check the running value, then confirm the discipline is actually attached to the interface:

sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
ss -ti | grep -m1 bbr

The first line shows what new connections will use. The second confirms an existing connection negotiated it. If the interface had an explicit qdisc set elsewhere, the default from sysctl may not be the one in use.

Making it stick

Anything set with sysctl -w is gone after a reboot. Persist it in a drop-in file under /etc/sysctl.d/, and make sure the module is loaded at boot if the kernel ships it as a module rather than building it in.

  • Write the settings to /etc/sysctl.d/99-tuning.conf.
  • Add the module name to /etc/modules-load.d/ if needed.
  • Reboot or reload, then re-check both values above.

The change is small and disproportionate: a link that felt unusable under load becomes merely busy.