highload.html: kernel TLS, splice() heuristics, grace delay by traffic

A section on ssl_ktls: what it needs, how 3proxy reports it, and that
it pays with splice() for TLS connections carrying large transfers.
The splice() section gets the :MIN,STREAK,SMALL tuning and says TLS
services splice where the kernel does TLS. Grace delay is recommended
by the clients' speed for steady streams only: for web traffic it
saved nothing on any of the links measured, nor with the second CPU
the HTTP proxy needed on the 5 Mbit/s one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-14 19:14:46 +03:00
parent f834e0134b
commit 1a193eb82d

View File

@ -670,7 +670,17 @@ least 16K of it is left to read, a stream (CONNECT, SOCKS, tcppm) once it carrie
large reads, going back to copying when they turn small. -s1 always splices what the large reads, going back to copying when they turn small. -s1 always splices what the
server sends to the client (downloads), -s2 what the client sends to the server server sends to the client (downloads), -s2 what the client sends to the server
(uploads), -s3 both; -s5, -s6 and -s7 (the same as -s) do it for those directions (uploads), -s3 both; -s5, -s6 and -s7 (the same as -s) do it for those directions
only where it pays. -s0 turns it off. In builds without it (CMake option 3PROXY_USE_SPLICE=OFF, only where it pays. -s0 turns it off. Where it pays can be tuned after the mode,
as grace delay is, with :MIN,STREAK,SMALL:
<pre>
proxy -s:16384,2,4096
tcppm -s6:65536,3,8192 8080 192.168.1.1 80
</pre>
A direction is spliced after STREAK reads in a row of MIN bytes or more (SMALL
from a TLS socket, which hands out a record at a time), and copied again after
more than STREAK splices in a row of less than SMALL; a body of known length is
spliced if at least MIN of it is left to read. The first line is the default.
A larger MIN or STREAK splices only longer transfers. In builds without it (CMake option 3PROXY_USE_SPLICE=OFF,
or -DWITHSPLICE removed from the Makefile) -s is accepted and ignored. or -DWITHSPLICE removed from the Makefile) -s is accepted and ignored.
<p>splice() pays off when most of the traffic is large transfers. Measured in <p>splice() pays off when most of the traffic is large transfers. Measured in
the <a href="https://github.com/3proxy/3proxy/blob/devel/bench/RESULTS.md">benchmarks</a> the <a href="https://github.com/3proxy/3proxy/blob/devel/bench/RESULTS.md">benchmarks</a>
@ -706,6 +716,7 @@ kernel's network work included):
<tr><td>no limit, no delay</td><td>8% less (4%)</td><td>9% less (6%)</td><td>12% less (7%)</td></tr> <tr><td>no limit, no delay</td><td>8% less (4%)</td><td>9% less (6%)</td><td>12% less (7%)</td></tr>
<tr><td>100 Mbit/s, 2 ms, 0.1% loss</td><td>7% less (+1%)</td><td>1% less (+3%)</td><td>6% less (+1%)</td></tr> <tr><td>100 Mbit/s, 2 ms, 0.1% loss</td><td>7% less (+1%)</td><td>1% less (+3%)</td><td>6% less (+1%)</td></tr>
<tr><td>20 Mbit/s, 5 ms, 0.5% loss</td><td>+1% (+3%)</td><td>12% less (5%)</td><td>+7% (+8%)</td></tr> <tr><td>20 Mbit/s, 5 ms, 0.5% loss</td><td>+1% (+3%)</td><td>12% less (5%)</td><td>+7% (+8%)</td></tr>
<tr><td>10 Mbit/s, 10 ms, 0.5% loss (two CPUs)</td><td>–</td><td>–</td><td>9% less (2%)</td></tr>
<tr><td>5 Mbit/s, 25 ms, 1% loss</td><td>8% less (0%)</td><td>10% less (9%)</td><td>6% less (2%)</td></tr> <tr><td>5 Mbit/s, 25 ms, 1% loss</td><td>8% less (0%)</td><td>10% less (9%)</td><td>6% less (2%)</td></tr>
</table> </table>
<p><b>Conclusion:</b> for web traffic -s, the default, and -s0 show no <p><b>Conclusion:</b> for web traffic -s, the default, and -s0 show no
@ -720,8 +731,9 @@ CPU limits a connection.
buffer memory. splice() is not used where 3proxy has to see the data: on a connection while buffer memory. splice() is not used where 3proxy has to see the data: on a connection while
data filters apply to it (an HTTP body is spliced only once the filters are data filters apply to it (an HTTP body is spliced only once the filters are
done with it: they saw it whole, or it is larger than filtermaxsize, which done with it: they saw it whole, or it is larger than filtermaxsize, which
they skip), and not at all by a service that ends or begins TLS (ssl_serv, they skip), and by a service that ends or begins TLS (ssl_serv, ssl_cli) only for
ssl_cli). connections the kernel does TLS for both ways (see <a href="#ktls">Kernel
TLS</a>).
<p>TCP_NODELAY and splice() do not conflict and can be combined on high-speed connections. <p>TCP_NODELAY and splice() do not conflict and can be combined on high-speed connections.
<h4>Add Grace Delay to Reduce System Calls</h4> <h4>Add Grace Delay to Reduce System Calls</h4>
@ -762,25 +774,65 @@ against 3proxy without grace delay:
<tr><td rowspan="3">100 Mbit/s, 2 ms, 0.1% loss</td><td>-g8000,2,2</td><td rowspan="3">the same (100 Mbit/s each)</td><td rowspan="3">the same: reads are large, waits do not apply</td><td rowspan="3">the same (73%)</td></tr> <tr><td rowspan="3">100 Mbit/s, 2 ms, 0.1% loss</td><td>-g8000,2,2</td><td rowspan="3">the same (100 Mbit/s each)</td><td rowspan="3">the same: reads are large, waits do not apply</td><td rowspan="3">the same (73%)</td></tr>
<tr><td>-g6000,3,2</td></tr> <tr><td>-g6000,3,2</td></tr>
<tr><td>-g8000,2,3</td></tr> <tr><td>-g8000,2,3</td></tr>
<tr><td rowspan="3">20 Mbit/s, 5 ms, 0.5% loss</td><td>-g8000,2,2</td><td>3% less</td><td>41% less</td><td>88% instead of 97%</td></tr> <tr><td rowspan="3">20 Mbit/s, 5 ms, 0.5% loss</td><td>-g8000,2,2</td><td>3% less</td><td>41% less</td><td rowspan="3">about the same: 88-96% with and without, across two sessions</td></tr>
<tr><td>-g6000,3,2</td><td>3% less</td><td>35% less</td><td>92%</td></tr> <tr><td>-g6000,3,2</td><td>3% less</td><td>35% less</td></tr>
<tr><td>-g8000,2,3</td><td>7% less</td><td>46% less</td><td>88%</td></tr> <tr><td>-g8000,2,3</td><td>7% less</td><td>46% less</td></tr>
<tr><td rowspan="3">5 Mbit/s, 25 ms, 1% loss</td><td>-g8000,2,2</td><td>the same (held by the loss)</td><td>21-23% more</td><td rowspan="3">CPU-bound with or without</td></tr> <tr><td rowspan="3">5 Mbit/s, 25 ms, 1% loss</td><td>-g8000,2,2</td><td>the same (held by the loss)</td><td>21-23% more</td><td>117-124% against 109-111% (of 200%: two CPUs)</td></tr>
<tr><td>-g8000,2,10</td><td>5-8% less</td><td>21-29% less</td></tr> <tr><td>-g8000,2,10</td><td>5-8% less</td><td>21-29% less</td><td>about the same: 112-117%, within the runs' spread (two CPUs)</td></tr>
<tr><td>-g8000,2,20</td><td>12-17% less</td><td>31-32% less</td></tr> <tr><td>-g8000,2,20</td><td>12-17% less</td><td>31-32% less</td><td>about the same: 113% (two CPUs)</td></tr>
</table> </table>
Grace delay pays where clients receive data steadily at a few tens of Mbit/s, On the 5 Mbit/s link one CPU was not enough for the HTTP proxy at 3000
in small pieces, with DELAY matched to the speed: about the time TRAFF bytes exchanges a second with any setting, so its column is from a run with a second
take to arrive. At 20 Mbit/s -g8000,2,2 takes about 40% off the CPU per CPU for 3proxy, where it kept up with every one. The settings ranked the same
gigabyte for 3% of the throughput; at 5 Mbit/s 2 ms is too short and costs there: 10 ms took 27-34% off the CPU
CPU, while 10 ms takes a quarter off for 5-8%. On fast links it does nothing. per gigabyte of the streams for 3-8% of their throughput, 20 ms 33-37% for
In these tests splice() and copying cost about the same once clients' links 10-14%, 2 ms added 12-19%.
are emulated; with grace delay on such streams splice() saves a little more. Many short exchanges gain <p>Grace delay pays only where most of the traffic is steady streams to and
little either way. Nor does grace delay help once 3proxy's CPU is exhausted: from clients: large downloads and uploads, video. For web traffic, many short
the waits then come on top of the queueing, and it degrades slightly, with a requests and answers, it reduced the CPU on none of these links (the mix
little more CPU per exchange and longer delays. Choose DELAY so that the cap is above the speed column above, and -g8000,2,5 through CONNECT at 10 Mbit/s, 10 ms, 0.5% loss): such connections rarely give NUM small reads in a row, so the
of a single client connection you want to serve in full, and TRAFF below the waits seldom apply. There leave it off, whatever the clients' speed.
read size you expect from a fast one. Grace delay does not help many small <p>For streams, set DELAY to the clients' speed: about the time TRAFF bytes
requests and answers: they are too short to give NUM small reads in a row, and take to arrive. A shorter wait gathers nothing and costs CPU; a longer one
the waits rarely apply. <pre>logdump 1 1</pre> is useful to see how grace costs throughput. As a starting point for TRAFF 8000:
delays work. <table border="1" cellpadding="3">
<tr><th>a client connection's speed</th><th>web traffic</th><th>steady streams</th></tr>
<tr><td>100 Mbit/s and more</td><td>none</td><td>none: reads are large, the wait does not apply</td></tr>
<tr><td>about 20 Mbit/s</td><td>none</td><td>-g8000,2,2: about 40% less CPU for 3% of the throughput</td></tr>
<tr><td>about 5 Mbit/s</td><td>none</td><td>-g8000,2,10: 21-34% less CPU for 3-8% of the throughput</td></tr>
</table>
Choose DELAY so that its cap (above) stays over the speed of a single
connection you want to serve in full. Nor does grace delay help once 3proxy's
CPU is exhausted: the waits come on top of the queueing, and it degrades
slightly, with a little more CPU per exchange and longer delays. With grace
delay on streams splice() saves a little more than copying. <pre>logdump 1 1</pre> is useful to see how grace delays work.
<h4><a name="ktls">Kernel TLS</a></h4>
A service that ends or begins TLS (ssl_serv, ssl_cli, MITM) encrypts and
decrypts in 3proxy and does not splice(). With OpenSSL 3.0 or later built with
kernel TLS (enable-ktls), and the tls module loaded on Linux, the kernel can do
the encryption instead:
<pre>
ssl_ktls
ssl_server_cert /path/to/cert.pem
ssl_server_key /path/to/key.pem
ssl_serv
tcppm 8443 192.168.1.1 80
</pre>
ssl_ktls applies to the services configured after it, ssl_noktls (the
default) turns it off. Whether the kernel took sending and receiving depends on
the library, the kernel and the negotiated cipher (AES-GCM and ChaCha20-Poly1305
are the usual ones); 3proxy logs it on the first TLS connection of a service:
<pre>kernel TLS: sending in the kernel, receiving in the kernel</pre>
Receiving with TLS 1.3 needs a later OpenSSL than 3.0. Distributions differ in
whether their OpenSSL is built with it (RHEL and AlmaLinux are, Ubuntu 24.04 is
not). wolfSSL builds do not support it and ignore the command.
<p>A connection the kernel does TLS for both ways is spliced as a plain one
is (with -s, the default; not where data filters apply). Measured on the Intel
machine above, ending or beginning TLS with ssl_ktls, 3proxy moved bulk data
(1 MB answers, streams) 6-17% faster at 5-34% less CPU per gigabyte, while 1K
requests, which are not spliced, were 0-11% slower: the kernel does the same
encryption, with a layer more for every record. Without splice() kernel TLS
does not pay at all, as for nginx, which was 0-9% slower with it. Turn it on
where TLS connections carry large transfers.