Network tuning must optimize meaningful workload and user wait times simultaneously, rather than simply inflating a single speed test number. First, define the objective: effective throughput for large files, P99 latency for short requests, connection establishment rate, or packets per second (PPS) for small packets; different objectives may require different batching, queuing, and concurrency strategies.
Metric
Required Definition
Throughput
Application payload, IP bytes, or link rate; unidirectional or bidirectional
Latency
Idle/load state, connection reuse, P50/P95/P99, and error rate
PPS
Packet length, Layer 2 overhead, protocol, whether forwarding/encryption is involved
CPU
Per-core and queue distribution, soft interrupts, user-space vs. kernel-space
Resources
Concurrent connections, buffers, queues, memory pressure, and drop counts
First, Confirm Which Type of Constraint Is the Bottleneck
Bandwidth underutilization may be caused by single-flow window limits, congestion, application supply, CPU, encryption, storage, rate limiting, or receive processing. A small cwnd does not automatically prove that the socket buffer is small; if the sending application is not continuously supplying data, expanding the window will not magically generate load.
Observation
Next Step for Verification
Conclusion Not to Draw Prematurely
Single flow is slow, multiple flows are fast
Single-flow window, CPU/queue distribution, rate limiting, and path hashing
Multiple flows have fixed all performance issues
Send queue has unsent data for a long time
Receive window, congestion permission, pacing, and application backpressure
Simply increasing the send buffer is sufficient
RTT spikes significantly under load
Queues, wireless contention, CPU scheduling, and receive processing
It is definitely bufferbloat on a router
One core is fully utilized, but overall average is low
RSS/IRQ/Application threads and NUMA
CPU is not the bottleneck
Receiver reports ZeroWindow
Application read speed and buffer release rate
Switching the sender's congestion algorithm will solve it
BDP: Estimating Required In-Flight Allowance
Bandwidth-Delay Product BDP = Rate × RTT represents the amount of in-flight data required to cover one round-trip wait time when sending continuously at that rate. It is a starting point for capacity planning, not a directive to pre-allocate memory for every socket.
Target Rate and RTT
BDP (Decimal)
Approx. Binary Capacity
1 Gbit/s, 30 ms
3.75 MB
3.58 MiB
1 Gbit/s, 150 ms
18.75 MB
17.88 MiB
10 Gbit/s, 200 ms
250 MB
238.42 MiB
After unifying cwnd, receive window, and in-flight data into bytes, you can estimate the rate upper bound for a fixed window. For example, with a usable window of 1 MiB and an RTT of 100 ms, even without packet loss, the theoretical upper bound is only about 83.9 Mbit/s; slow start, pacing, and application supply will further affect the result. ss often displays cwnd in segments, which must be combined with MSS and cannot be directly compared to byte-based buffers.
If you use an RTT that already includes severe queuing to expand buffers, you may mistake queuing for a "pipeline that must continue to be filled." You should combine baseline/minimum RTT with actual contention conditions, rather than always multiplying the maximum RTT by bandwidth.
When Is More In-Flight Data Just Adding to the Queue?
At a default of 1 Gbit/s and 30 ms, 3.75 MB exactly covers the baseline BDP. Continuing to increase the in-flight budget lengthens the golden queue but does not raise the throughput upper bound; reducing it below the BDP causes the queue to disappear and the window to start limiting throughput.
BDP=CRTT0,R≤min(C,W/RTT0),dq=max(0,W−BDP)/C
Preparing the visual
When Is More In-Flight Data Just Adding to the Queue?
After reaching the baseline BDP, additional sustained in-flight data forms a queue in this single-bottleneck model and cannot exceed the egress rate. Using the queue-inflated RTT to expand the budget again chases the wait time you created.
{"id":"bdp-window-queue","title":"When Is More In-Flight Data Just Adding to the Queue?","summary":"After reaching the baseline BDP, additional sustained in-flight data forms a queue in this single-bottleneck model and cannot exceed the egress rate. Using the queue-inflated RTT to expand the budget again chases the wait time you created.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"rate\" data-i18n=\"rate\"></label><output for=\"rate\" id=\"rate-value\"></output><input id=\"rate\" type=\"range\" min=\"100\" max=\"1000\" value=\"1000\" step=\"100\"></div><div class=\"control\"><label for=\"rtt\" data-i18n=\"rtt\"></label><output for=\"rtt\" id=\"rtt-value\"></output><input id=\"rtt\" type=\"range\" min=\"10\" max=\"200\" value=\"30\" step=\"10\"></div><div class=\"control\"><label for=\"flight\" data-i18n=\"flight\"></label><output for=\"flight\" id=\"flight-value\"></output><input id=\"flight\" type=\"range\" min=\"0.25\" max=\"30\" value=\"3.75\" step=\"0.25\"></div><div id=\"pipe\" class=\"budget\"></div><div class=\"metrics\"><div><span data-i18n=\"bdp\"></span><strong id=\"bdp\"></strong></div><div><span data-i18n=\"throughput\"></span><strong id=\"throughput\"></strong></div><div><span data-i18n=\"queue\"></span><strong id=\"queue\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.metrics strong{font-size:18px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const rate=+$('#rate').value,rtt=+$('#rtt').value,f=+$('#flight').value,bdp=rate*rtt/8000,q=Math.max(0,f-bdp),speed=Math.min(rate,f*8000/rtt);for(const id of ['rate','rtt','flight'])set(id+'-value',$('#'+id).value);band('pipe',[{name:'base pipe',value:Math.min(f,bdp)},{name:'queue',value:q}],Math.max(f,bdp));set('bdp',bdp.toFixed(2));set('throughput',speed.toFixed(1));set('queue',(q*8000/rate).toFixed(1));set('equation','BDP = '+rate+'×'+rtt+'/8000 = '+bdp.toFixed(2)+' MB\\nqueue = max(0,'+f+' − '+bdp.toFixed(2)+') = '+q.toFixed(2)+' MB');$('#queue').dataset.value=q*8000/rate;$('#throughput').dataset.value=speed;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"bdp":"Baseline BDP (MB)","flight":"In-Flight Budget (MB)","heading":"When Is More In-Flight Data Just Adding to the Queue?","intro":"Change the bottleneck rate, baseline RTT, and in-flight budget to observe unfilled, just-filled, and over-queued states.","queue":"Additional Queuing Delay (ms)","rate":"Bottleneck Rate (Mbit/s)","reset":"Reset","rtt":"Baseline RTT (ms)","scope":"Steady-state fluid model, single flow, fixed rate, no packet loss/ACK compression/contention, full utilization of allowance. MB is 10^6 B; RTT does not include new queueing generated here. Not a TCP congestion algorithm simulation.","scopeLabel":"Model Scope and Conventions","throughput":"Ideal Throughput Upper Bound (Mbit/s)"}}
Linux Buffers: Upper Limits, Actual Allocation, and Advertised Windows Differ
The three values in tcp_rmem and tcp_wmem describe memory policies for min/default/max; receive auto-tuning is also affected by conditions such as tcp_moderate_rcvbuf. Explicitly setting SO_RCVBUF/SO_SNDBUF in the application changes the corresponding auto-tuning behavior; you cannot assume the kernel still grows strictly according to automatic limits.
# Read current settings only, first confirming the actual values on the current machine and namespace.sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem \
net.ipv4.tcp_moderate_rcvbuf net.core.rmem_max net.core.wmem_maxss -tinm
Kernel default values may vary by version and memory size, and distributions may override them. You cannot judge all machines by the statement "Linux default receive max is about 6 MB." Linux 7.2 documentation clearly distinguishes between auto-tuning limits and manual socket options; the auto-maximum of tcp_wmem does not override net.core.wmem_max. Linux IP Sysctl
Linux's internal accounting for SO_RCVBUF/SO_SNDBUF parameters includes extra space; getsockopt often returns doubled values; socket memory usage, data payload, and TCP advertised window should not be treated as the same number. socket(7)
Changing the receive limit to 64 MiB does not mean every connection immediately consumes 64 MiB; however, if 10,000 active connections average 4 MiB actual usage, this memory class alone accounts for approximately 39.1 GiB, not counting other buffers and metadata. Raising the limit should simultaneously evaluate concurrency, memory pressure, and whether the original limit was truly reached.
Queue Management: Only Meaningful When Controlled to the Bottleneck
Queues absorb short bursts but also increase wait times. A 4 MB backlog before a 100 Mbit/s egress link adds approximately 320 ms of serialization wait; no matter how much the queue is expanded, the egress will not become 1 Gbit/s as a result. When sustained input exceeds output, congestion feedback, shaping, or application backpressure is required.
CoDel observes the residence time of packets in the queue and determines whether the minimum residence time remains above the target for a sustained interval, thereby identifying persistent queues; it does not drop any packet immediately just because it exceeds the default 5 ms threshold. Signals can be drops, or ECN marking where applicable. CoDel
FQ-CoDel distributes flows into queues and schedules them, working with CoDel to control persistent waits in each queue. It can improve coexistence between interactive flows and bulk flows, but hash classification, varying flow counts, and implementation conditions mean that "absolute fairness and never starving" is not a universal guarantee. FQ-CoDel
CAKE combines shaping, queue management, flow/host isolation, and DiffServ mechanisms. It is not simply a renamed fq_codel, nor is it necessarily optimal for all interfaces. Shaping rates, link-layer overhead, and actual bottlenecks must match; downstream traffic also requires the ability to implement control at the appropriate ingress/forwarding path. tc-cake
Scenario
Why Changing qdisc on the Local Machine May Be Ineffective
1 Gbit/s NIC connected to an uncontrollable 100 Mbit/s device
The local machine sends too fast, leaving deep queues downstream
Multiple external hosts sharing the egress
The local machine cannot see all competing traffic
Actual uplink is only 100 Mbit/s, but shaping is set to 950 Mbit/s
The bottleneck has not been moved to a controllable position
Wireless capacity changes rapidly
Fixed shaping values may not match instantaneous capacity
First, observe the existing structure with tc -s qdisc show dev eth0, then decide where to schedule or shape. Replacing the root qdisc may replace existing business policies; record and understand the existing configuration before operating; tuning articles should not directly apply a replace command to all machines.
CUBIC, BBR, and Pacing: Algorithm Names Are Not Performance Conclusions
CUBIC adjusts send permissions based on congestion events and window growth functions; RFC 9438 describes CUBIC, not BBR. BBR builds a path model based on delivery rate, RTT, etc., and controls pacing/inflight; different generations have different behaviors regarding packet loss, ECN, and probing. CUBIC RFC 9438, BBR Official Project
BBR's startup and bandwidth probing can create queues, and it does not guarantee superiority over CUBIC at all times. You must report the exact kernel/patch version, competing flows, RTT, packet loss sources, and measurement objectives. A locally named bbr module does not automatically mean the latest generation from the Google repository is deployed.
sysctl net.ipv4.tcp_available_congestion_control \
net.ipv4.tcp_congestion_controltc -s qdisc show dev eth0
Global default congestion algorithms mainly affect subsequent connections of that type; applications may also select algorithms per socket; changing sysctl once does not prove that all existing connections have switched. Send pacing cooperates with qdisc/kernel implementations; fq, fq_codel, and cake are not the same mechanism, and you cannot assume they have identical capabilities just because their names contain 'fq'. tc-fq
Small Packets: Calculate PPS First, Then Look at CPU Distribution
For ideal 10 Gbit/s Ethernet line-rate minimum frames, calculated with 64 B frames (including FCS), 8 B preamble/SFD, and 12 B inter-frame gap, each frame takes 84 B of time, resulting in approximately 14.88 Mpps. This places a completely different load on the CPU compared to "10 Gbit/s large file downloads."
fromfractionsimportFractionasFsmall_pps=F(10_000_000_000,(64+8+12)*8)large_pps=F(10_000_000_000,(1518+8+12)*8)assert14_880_000<small_pps<14_890_000assert812_000<large_pps<813_000assert18<small_pps/large_pps<19assertF(10_000*4*1024**2,1024**3)==F(625,16)print('Ideal minimum frame 14.88 Mpps, 1518 B frame 0.813 Mpps, average buffer ~39.06 GiB')
This calculation ignores other rate definition differences such as higher-layer physical encoding, and does not imply that the current host can achieve this PPS. At high packet rates, you should not assume that each packet triggers an independent interrupt; NAPI, interrupt coalescing, and batching will change CPU costs.
Mechanism
Primary Function
Trade-offs
TSO
Hardware performs TCP segmentation
Reduces software per-segment work, constrained by hardware and packet conditions
GSO
Defers segmentation in the software stack
Does not move all work to the NIC
GRO
Merges suitable packets on the receive path
Changes upper-layer processing granularity and packet capture observation
RSS
Hardware hashes flows to receive queues
Single flows usually map to one queue to avoid out-of-order delivery
RPS/RFS/XPS
Software CPU/queue selection and locality optimization
May increase cross-core passing; more cores are not always better
Ring / Interrupt Coalescing
Buffers bursts, reduces interrupt frequency
Deeper buffers or more coalescing can increase wait times
Viewing load by CPU, IRQ, queue, and NUMA location is usually more meaningful than simply maximizing all rings. Offload issues should also be checked against packet capture locations and driver statistics; disabling offloads changes CPU and throughput, and must be treated as a controlled variable under explicit assumptions. Linux Scaling, Segmentation Offloads
What Evidence Should Common Parameters Correspond To?
Parameter or Mechanism
Reasonable Investigation Entry Point
What It Cannot Replace
somaxconn / listen backlog
Accept queue backlog, overflow, and application accept speed
Increasing actual business processing capacity
tcp_max_syn_backlog
Pressure on the incomplete handshake queue and related counters
Confusing it with the completed queue
TCP_NOTSENT_LOWAT
Too much unsent data, application writable notifications, and backpressure
Precisely limiting in-flight bytes online
TCP Fast Open
Applications supporting TFO, handshake costs, and replay semantics
Expecting all applications to work by simply setting tcp_fastopen=3
tcp_tw_reuse
Real source port/connection reuse needs and protocol conditions
Assuming fewer TIME_WAITs are always better
Linux 7.2's tcp_tw_reuse distinguishes between 0 (disabled), 1 (globally enabled), and 2 (loopback only); you cannot simply conclude "set to 1, safe for clients." First, check connection reuse, destination distribution, source port constraints, and business lifecycle; deleting large numbers of TIME_WAITs is not an optimization goal. Linux IP Sysctl Parameters
Let Tests Answer Specific Questions
The following commands are for environments with authorized test services; the iperf3 server must be prepared by both parties in advance.
# Client sends to server, single flow.iperf3 -c perf.example -t 30 -P 1 -J# Reverse: server sends to client, not simultaneous bidirectional testing.iperf3 -c perf.example -t 30 -P 1 -R -J# Controlled multi-flow; do not mix results with single-flow data into one number.iperf3 -c perf.example -t 30 -P 4 -J
-R only reverses the direction; simultaneous bidirectional is a different test condition. iperf3 measures the throughput of the tool and endpoints under those conditions, not "bare network bandwidth" detached from CPU, kernel, and sockets. UDP test send target rates, receiver goodput, losses, and out-of-order packets should also be reported separately. iperf3 Official Manual
A single control test should record at least the test version, direction, number of flows, duration, packet length or block size, CPU on both ends, throughput, RTT/tail latency under load, and error rate. To avoid misleading results from external load changes, you can alternate running baseline and candidate configurations. If throughput increases by 5%, but P99 doubles or the error rate rises, the result should be evaluated against the original objective, rather than just reporting the fastest single instance.
Specific evidence collection is covered in tcpdump and Wireshark, and phase positioning is covered in Network Troubleshooting Methodology. Each change should correspond to an observed limitation and verify its improvement and cost under the same workload.
Connect measurements to queueing models
When latency rises sharply near the throughput limit, use queueing measurement and simulation to fix the request definition, concurrency model, and observation window. Then use Little’s law to check mean in-flight work, throughput, and mean response time. A relation between means does not determine p99; distinguish slower service, longer waits, and changes in the load generator.