hub: the Gitea client keeps its connections (MaxIdleConnsPerHost was 2)
gates / gates (push) Successful in 28s

Third and last leg, found the same way as the second — by not accepting that the numbers matched the
arithmetic when they did not. After the fan-out and the side-by-side resolve the page was ~11.9s mean
where ~3s was predicted.

Cause: the client used http.DefaultTransport, whose MaxIdleConnsPerHost is 2. Above that Go opens a
connection per request and discards it after, so under a 16-way fan-out almost every call paid a
fresh TCP setup AND a fresh authentication. Authentication is the expensive half: unauthenticated
/api/v1/version answers in ~0.03s while an authenticated package call takes ~0.24s against the same
Gitea instance.

Transport sized to the fan-out: MaxIdleConnsPerHost 16, MaxConnsPerHost 16 as a ceiling so a large
package list can never stampede Gitea harder than the fan-out needs, IdleConnTimeout 90s.
This commit is contained in:
2026-08-08 17:55:18 +02:00
parent de0110b8da
commit e348c4ef6e
2 changed files with 22 additions and 2 deletions
+7 -1
View File
@@ -1,4 +1,4 @@
## v0.100.1 — the Configuration page took 26 seconds, and it was never hashing anything (2026-08-08)
## v0.100.2 — the Configuration page took 26 seconds, and it was never hashing anything (2026-08-08)
**Measured, not guessed:** `GET /configuration`**HTTP 200 in 26.2 s**.
@@ -37,6 +37,12 @@ and that search is the slowest single call here: measured in-cluster at **1.1
**The cap stays at 20** and now bounds the fan-out too, not just the rendered list.
**And the client no longer uses `http.DefaultTransport`**, whose `MaxIdleConnsPerHost` is **2**.
Above that it opens a connection per request and discards it, so under a 16-way fan-out nearly every
call paid a fresh TCP setup *and* a fresh authentication — and the authentication is the expensive
half: an unauthenticated `/api/v1/version` answers in **~0.03 s** where an authenticated package call
takes **~0.24 s** against the same Gitea. The transport is now sized to the fan-out (16).
**MEASURED HONESTLY, because the first fix did less than the arithmetic predicted and that mattered.**
Concurrency alone took 26.2 s to ~1118 s, not the ~2 s expected. Chasing the gap is what found the
package SEARCH — `/api/v1/packages/admin?type=generic&q=…` — which no amount of per-version fan-out
+15 -1
View File
@@ -26,13 +26,27 @@ type Client struct {
}
// New builds a client. baseURL/owner/user/token come from the hub's registry config.
//
// ⚠ IT DOES NOT USE http.DefaultTransport, and that is the point (v0.100.2). The Configuration page
// resolves up to 40 package-metadata calls against this one host, now concurrently — and the default
// transport allows **MaxIdleConnsPerHost: 2**. Above that it opens a connection per request and
// throws it away afterwards, so almost every call paid a fresh TCP setup AND a fresh
// authentication, which is the expensive part: an unauthenticated `/api/v1/version` answers in
// ~0.03 s while an authenticated package call takes ~0.24 s against the same Gitea.
//
// Sizing it to the fan-out (16 = 8 in flight × the 2 dropdowns resolved side by side) lets the
// connections be reused for the whole page instead of churned.
func New(baseURL, owner, user, token string) *Client {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.MaxIdleConnsPerHost = 16
tr.MaxConnsPerHost = 16 // a ceiling too: never stampede Gitea harder than the fan-out needs
tr.IdleConnTimeout = 90 * time.Second
return &Client{
baseURL: strings.TrimRight(baseURL, "/"),
owner: owner,
user: user,
token: token,
http: &http.Client{Timeout: 10 * time.Second},
http: &http.Client{Timeout: 10 * time.Second, Transport: tr},
}
}