Skip to content

Stop Envoy reusing upstream connections to actors#549

Open
Chuang Wang (chuangw6) wants to merge 1 commit into
mainfrom
bench/envoy-no-connection-reuse
Open

Stop Envoy reusing upstream connections to actors#549
Chuang Wang (chuangw6) wants to merge 1 commit into
mainfrom
bench/envoy-no-connection-reuse

Conversation

@chuangw6

@chuangw6 Chuang Wang (chuangw6) commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

Envoy pools HTTP/1.1 connections by destination address. That is safe when an address means one stable server, and for actors it does not. A worker pod keeps its IP for its whole lifetime, but the actor sandbox behind port 80 is torn down on every Suspend and a different actor takes the slot. A pooled connection therefore belongs to an actor that may already be gone, and reusing it races the far end's close.

Symptom

The request goes into a dead socket and Envoy synthesizes a 503. At 50 actors churning across 50 workers, ~42% of pings failed, with Envoy reporting 450 upstream_cx_destroy_remote_with_active_rq.

Solution

Set max_requests_per_connection to 1 on the dynamic forward proxy cluster. Failures and the reset counter both went to 0.

Notes

The handshake this costs is sub-millisecond in-cluster, against pings that take ~22ms. It removes the race rather than recovering from it after the fact, which is why this is preferred over a route-level retry policy.

Carrying HttpProtocolOptions on a dynamic_forward_proxy cluster also requires allow_insecure_cluster_options. Envoy guards that because a DFP cluster forwards to whatever host the request names, so a TLS upstream must validate against it. This cluster has no transport socket at all and the authority is an IP literal, so there is nothing to validate. Without the flag Envoy NACKs every CDS push and drops the cluster, which presents as "all actor traffic 503s" rather than as a config error, so there is a test pinning it.

Independent of the benchmarking stack (#542 / #543 / #545); this only touches cmd/atenet/internal/router.

Envoy pools HTTP/1.1 connections by destination address, which is safe when
an address means one stable server. For actors it does not. A worker pod
keeps its IP for its whole lifetime, but the actor sandbox behind port 80 is
torn down on every Suspend and a different actor takes the slot. A pooled
connection therefore belongs to an actor that may already be gone, and
reusing it races the far end's close: the request goes into a dead socket
and Envoy synthesizes a 503.

At 50 actors churning across 50 workers this was ~42% of pings failing, with
Envoy reporting 450 upstream_cx_destroy_remote_with_active_rq. Setting
max_requests_per_connection to 1 took it to 0% and 0.

The handshake this costs is sub-millisecond in-cluster against pings that
take ~22ms, and it removes the race rather than recovering from it after the
fact.

Carrying HttpProtocolOptions on a dynamic_forward_proxy cluster also needs
allow_insecure_cluster_options. Envoy guards that because a DFP cluster
forwards to whatever host the request names, so a TLS upstream must validate
against it; this cluster has no transport socket at all and the authority is
an IP literal. Without the flag Envoy NACKs every CDS push and drops the
cluster, which looks like "all actor traffic 503s" rather than a config
error, so there is a test pinning it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant