Repository navigation
atunnel, atenet-router and the egress gateway limit kernel send queues behind slow readers - #3320
Chuang Wang (chuangw6) wants to merge 3 commits into
Conversation
47a2b2d to
14102f5
Compare
14102f5 to
ac3a2d4
Compare
bf1a3df to
2070fc9
Compare
| tcp linuxsys.TCPBufferOptions | ||
| } | ||
|
|
||
| func (l tunedListener) Accept() (net.Conn, error) { |
There was a problem hiding this comment.
Use point as the receiver: l* tunedListener, this copies the struct on every method invocation, which can be surprising.
There was a problem hiding this comment.
done.
| // tunedListener sets its TCP options on every connection it accepts. A | ||
| // connection the options can't be set on is closed, and the next one | ||
| // accepted. | ||
| type tunedListener struct { |
There was a problem hiding this comment.
listenerWithOptions
There was a problem hiding this comment.
done
| fs.Float64Var(&cfg.ParkedRequest.RetryJitter, "parked-request-retry-jitter", ingress.DefaultParkedRequestRetryJitter, "Random fraction in [0, 1) added to each retry delay to de-synchronize parked requests") | ||
| fs.IntVar(&cfg.ExtProcMaxRequests, "extproc-max-requests", 0, "Circuit-breaker max_requests for Envoy's ext_proc cluster; 0 (the default) derives it as twice --parked-request-max (minimum 1024). Explicit values must be >= --parked-request-max: every parked request holds one slot for its full wait, and the excess is fast-path headroom") | ||
|
|
||
| bindSocketOptionsFlags(fs, &cfg.TCP) |
There was a problem hiding this comment.
for consistency in naming, this should be called bindTCPSocketOptionsFlags
There was a problem hiding this comment.
done.
Lior Lieberman (LiorLieberman)
left a comment
There was a problem hiding this comment.
Nice!!
| # the kernel. See docs/network-socket-memory.md. | ||
| upstream_bind_config: | ||
| socket_options: | ||
| - { description: TCP_NOTSENT_LOWAT, level: 6, name: 25, int_value: 16384, state: STATE_PREBIND } |
There was a problem hiding this comment.
should we make the format nicer here??
There was a problem hiding this comment.
done.
| # TCP_NOTSENT_LOWAT on the sockets from atunnel, as on the forward | ||
| # proxy clusters below. See docs/network-socket-memory.md. | ||
| socket_options: | ||
| - { description: TCP_NOTSENT_LOWAT, level: 6, name: 25, int_value: 16384, state: STATE_PREBIND } |
There was a problem hiding this comment.
There was a problem hiding this comment.
what is that for?
| xdsSrv.SetTraceRootSamplingPercent(traceRootSamplingPercent) | ||
|
|
||
| xdsSrv.SetRouteTimeout(s.cfg.RouteTimeout) | ||
| xdsSrv.SetTCPBufferOptions(s.cfg.TCP) |
There was a problem hiding this comment.
is this the only place we should do it? in atenet-egress.yaml looks like you add it in a few different places.
There was a problem hiding this comment.
Yes for the router. SetTCPSocketOptions hands the options to the xDS server, which puts them on both TLS listeners (https, connect-tls) and both actor clusters (actor_connect, actor_original_dst). The gateway's config is a static manifest, so each listener and cluster carries its own block there.
| `TCP_NOTSENT_LOWAT` only on kernels that copy it: GKE's COS 6.12 does, some | ||
| other kernels don't. Internal listeners and clusters have no kernel sockets |
There was a problem hiding this comment.
Nit - are there kernels that dont copy it? accepted sockets inherit NOTSENT_LOWAT on every kernel afaict but not sure.
I think same applies in socketoptions.go and tunnel.go (where you talk about tunedListener).
If there is a kernel that does not support lets just say what it is
Also dont need to mention GKE imo
There was a problem hiding this comment.
You're right, plain TCP always inherits it. I thought otherwise because an earlier CI run failed: on the Ubuntu runner, the accepted socket didn't have the listener's value.
The actual cause was MPTCP: Go listens with MPTCP by default when the kernel enables it, and MPTCP doesn't pass the option on (repro: https://github.com/chuangw6/substrate/actions/runs/40614204349). Envoy listens with plain TCP, so this only affected atunnel, which now sets it on each connection.
A slow reader lets a socket's send queue grow to tcp_wmem's maximum, 4 MiB on GKE nodes. That memory is charged to atunnel's cgroup but is not in its heap. With TCP_NOTSENT_LOWAT at 16 KiB, a write blocks once 16 KiB wait unsent, and the backlog stays upstream under flow control. In the atunnel benchmark this cut socket memory behind a stalled actor from 753 to 62 KiB per connection, and behind a stalled router-side client from 737 to 201 KiB, with throughput within run-to-run noise. Add --atunnel-tcp-notsent-lowat, default 16384, set on every TCP connection atunnel holds: each connection its ingress listeners and egress listeners accept, and its dials to actors and to the gateway. Accepted connections get the option themselves: Go listens with MPTCP where the kernel enables it, and an MPTCP listener does not pass TCP_NOTSENT_LOWAT to the TCP connections it accepts. -1 leaves the kernel default. --atunnel-tcp-send-buffer and --atunnel-tcp-receive-buffer set fixed buffer sizes, off by default (-1): they cap a connection at the buffer size per round trip. Signed-off-by: Chuang Wang <chuangw@google.com>
A client that stops reading lets each of its sockets queue up to tcp_wmem's maximum, 4 MiB on GKE nodes, charged to the Envoy container but missing from Envoy's statistics. With TCP_NOTSENT_LOWAT at 16 KiB, the backlog stays in Envoy's buffers and upstream. In the Envoy benchmark this cut socket memory behind a stalled client from 981 to 57 KiB per connection, with throughput within run-to-run noise. Add --router-tcp-notsent-lowat, default 16384, set as an Envoy socket option on the TLS listeners and on the clusters that dial atunnel. -1 leaves the kernel default. --router-tcp-send-buffer and --router-tcp-receive-buffer set fixed buffer sizes, off by default (-1): they cap a connection at the buffer size per round trip. Signed-off-by: Chuang Wang <chuangw@google.com>
An upload to a destination that reads slowly can put the tunnel's 1 MiB HTTP/2 stream window into the send queue of the gateway's socket to the destination, charged to the gateway's container. Set TCP_NOTSENT_LOWAT to 16 KiB on the egress listener and on the four dynamic forward proxy clusters, so the backlog stays behind Envoy's buffer limit and the tunnel's flow control. In the Envoy benchmark a stalled tunnel alone on its connection dropped from 1420 to 48 KiB of socket memory, with throughput within run-to-run noise. Fixed buffer sizes are left out: a fixed SO_RCVBUF cut a tunnel's throughput by 90% at 10 ms round trip. Signed-off-by: Chuang Wang <chuangw@google.com>
2070fc9 to
e21ec6a
Compare
Part of #3133; design and data in
docs/network-socket-memory.md(#3319).atunnel, atenet-router and the egress gateway set
TCP_NOTSENT_LOWATto 16 KiB on their TCP sockets. A write then blocks once 16 KiB is waiting to be sent, so data behind a slow reader stays upstream instead of piling up in the proxy's kernel send queue, which grows to 4 MiB per socket on GKE nodes and counts toward the container's memory limit. One commit per component:--atunnel-tcp-notsent-lowat(default 16384),--atunnel-tcp-send-bufferand--atunnel-tcp-receive-buffer(default 0), on every connection it accepts or dials.--router-tcp-notsent-lowat(default 16384),--router-tcp-send-bufferand--router-tcp-receive-buffer(default 0), as Envoy socket options on the TLS listeners and the clusters that dial atunnel.TCP_NOTSENT_LOWAT16 KiB on theegresslistener and the four forward proxy clusters, inatenet-egress.yaml. A test checks they keep it.0 leaves the kernel default. Fixed buffer sizes stay off: they cut throughput by 85 to 98% with a few milliseconds of round trip.
Kernel socket memory per stalled connection, 100 connections, median of 3:
Throughput stayed within run-to-run noise.
atunnel sets the option on each accepted connection, because Go listens with MPTCP where the kernel enables it, and an MPTCP listener doesn't pass the option to the TCP connections it accepts. Envoy listens with plain TCP, so the router's client sockets and the gateway's sockets from atunnel inherit it from the listening socket.