perf: Upstream transports keep only 2 idle connections per host
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 80/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- go
- Área
- backend, networking
Línea de trabajo
Lee core/tlsbridge/upstream.go y core/listener/forwardproxy/server.go en los ajustes de transporte indicados en el issue. Añade en ambos lugares la configuración incluida del pool de conexiones y luego ejecuta varias veces el experimento proporcionado TestUpstreamPoolReuse desde core/tlsbridge/. Se considera terminado cuando se abre aproximadamente una conexión upstream por worker, en lugar de varias veces esa cantidad.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
What happens
The proxy keeps only 2 idle upstream connections per host. When more than two requests to one host overlap, each extra request opens a new TCP+TLS connection rather than reusing one, and that connection is closed afterwards instead of being kept for the next request.
This hurts most on a gateway that speaks only HTTP/1.1, where each connection carries one request at a time. LiteLLM is one: it offers no h2 in ALPN. Each extra handshake costs about 40-90 ms on the laptop's VPN-reached gateways.
Reproduction
The bridge's own upstream client (tlsbridge.NewUpstreamClient), callers running at the same time, 10 sequential requests each, against one h1-only TLS origin. We counted the TCP connections the origin accepted, over three runs:
| Concurrent callers | Ideal | Today | With MaxIdleConnsPerHost: 100 |
|---|---|---|---|
| 4 | 4 | 5 / 16 / 19 | 5 / 5 / 5 |
| 10 | 10 | 36 / 39 / 45 | 18 / 19 / 23 |
| 20 | 20 | 66 / 76 / 79 | 46 / 49 / 49 |
Through the forward proxy end to end (10 bridged clients × 10 requests), the origin accepted 69 connections.
Test source (drop into core/tlsbridge/)
package tlsbridge
import (
"crypto/tls"
"io"
"net"
"net/http"
"net/http/httptest"
"sync"
"sync/atomic"
"testing"
)
func TestUpstreamPoolReuse(t *testing.T) {
var conns atomic.Int32
origin := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "ok") }))
origin.Config.ConnState = func(_ net.Conn, s http.ConnState) {
if s == http.StateNew {
conns.Add(1)
}
}
origin.TLS = &tls.Config{NextProtos: []string{"http/1.1"}} // h1-only, like LiteLLM
origin.StartTLS()
defer origin.Close()
const workers = 10
c, _ := NewUpstreamClient(nil, true)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
if resp, err := c.Get(origin.URL); err == nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
}
}()
}
wg.Wait()
t.Logf("%d callers x 10 requests: %d upstream connections (ideal %d)", workers, conns.Load(), workers)
}
Why
Both transports set MaxIdleConns: 100 (the total across all hosts) but not MaxIdleConnsPerHost, so Go's DefaultMaxIdleConnsPerHost of 2 applies:
core/tlsbridge/upstream.go:31: every decrypted (bridged) request goes through this transport.core/listener/forwardproxy/server.go:212: plain-HTTP forward-proxy requests.
Possible fix
Add MaxIdleConnsPerHost: 100 next to MaxIdleConns in both transports. Two lines.
Even with the fix, concurrent callers still open more connections than the ideal; Go dials a new connection whenever none is idle at that moment. A MaxConnsPerHost cap would bound that too, but it makes extra requests wait in a queue, so it is a separate decision.
Acceptance
The test above opens about workers connections, not several times that, across repeated runs.
Related, not verified
proxy.log on a laptop shows 65 fast EOF failures (128-221 ms) on LiteLLM POSTs. That is the usual sign of reusing a connection the server had just closed. Go retries such a request only when it can replay the body, and the forward proxy never sets r.GetBody after buffering the body (setBody, server.go:1306). We did not reproduce this, so it is noted here rather than filed.
Found by an experiment-driven audit of main at 43cf5539; every number above was re-measured on that commit.
Assisted-By: Claude (Anthropic AI) [email protected]
- Lenguaje dominante
- Go
- Estrellas
- 21
- Forks
- 43
- Merge medio
- 14 h 25 min
- PR fusionados (30 d)
- 245
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de rossoctl/cortex
-
aiac enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
UI shows costs for all sessions even when drilled into a sessionPosiblemente ocupada @esnible la tomó hace 15 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
rossoctl/cortex#1116 · 2 comentarios · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
bug laptop
Dificultad 4/5 3-5 días Aptitud para principiantes 42/100
Los mantenedores suelen responder en 1 día
-
bug: Copied listener and parser code has drifted (hop-by-hop response headers, SSE BOM/CR handling)Abiertobug
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 1 día
-
bug: Ring and ledger count errors and coverage differently, cost reads slow down over time, and cost comments are stalePosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertobug
Dificultad 4/5 3-5 días Aptitud para principiantes 49/100
Los mantenedores suelen responder en 1 día
Todos los issues de rossoctl/cortex
Issues similares
-
Type: Bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
projectdiscovery/notify#569 ·
-
internal
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
Los mantenedores suelen responder en 1 día
-
Results from sources can contain invalid host namesPosiblemente ocupada @Mzack9999 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
projectdiscovery/subfinder#1983 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
bug triaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 80/100
radius-project/radius#13566 · 2 comentarios ·
Los mantenedores suelen responder en 1 día