hub v0.100.0 — the Configuration page took 26 seconds, and it was never hashing anything
gates / gates (push) Successful in 21s
gates / gates (push) Successful in 21s
MEASURED, NOT GUESSED: GET /configuration -> HTTP 200 in 26.2s. The reasonable guess was that it hashes the artifacts on page load. It does not, and the code already said so: Gitea stores each package file's sha256 and gitea.FileSHA256 reads it as metadata — "a cheap metadata call, the artifact bytes are never downloaded". The cost was never CPU. IT WAS LATENCY x COUNT. artifactChoices made ONE SERIAL round-trip per version, for two packages, capped at 20 each: 2 x (1 version list + 20 sha lookups) = 42 sequential requests at ~0.6s each out through the public ingress. 42 x 0.6 = 26s, which is what the clock said. 1. The sha lookups now run CONCURRENTLY, bounded at 8 in flight. Order preserved by writing into a slot rather than appending — the dropdown is newest-first, and a scrambled sha would show the operator a hash belonging to a DIFFERENT artifact. A failed lookup still drops that version only. 2. The client talks to Gitea IN-CLUSTER (http://gitea.gitea-system.svc.cluster.local:3000, overridable via GITEA_API_URL). Measured from the hub pod: 0.11s against 0.26-1.16s, because the public path adds DNS, the ingress hop and a TLS handshake to each of the 42. Plain HTTP is safe ONLY because it never leaves the cluster network — the registry token rides the Authorization header, so this must not point at a public host without TLS. Unreachable -> the existing graceful degradation to manual text entry, unchanged. DELIBERATELY NOT DONE: caching the sha in the hub's own database. That was the other half of the proposal and it is the wrong shape. Gitea already IS the store; a copy in hub_settings would be a second source of truth that can drift from the registry it describes — and the operator reads exactly this value to confirm what they are about to vouch, so a stale one would be a confident wrong answer. The same reasoning golden_currency_gate.py already records for the vouched version. With the fan-out, a cold load needs no cache to be fast. The cap stays at 20 and now bounds the FAN-OUT too, not just the rendered list. Tests pin order (and that each sha belongs to its own version), per-version failure isolation, and THE CONCURRENCY ITSELF — a wall-clock assertion plus an in-flight counter, so a fast run cannot be luck, and an upper bound so a large package list cannot stampede Gitea. Red-proof: reverting to the serial loop takes 861ms where the concurrent one takes 150ms, and the test fails naming the 26-second page. go build / go vet / go test ./... green (18 packages), run separately from this commit.
This commit is contained in:
@@ -1,3 +1,42 @@
|
||||
## v0.100.0 — 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**.
|
||||
|
||||
**The reasonable guess was that it hashed the artifacts on load. It does not, and the code already
|
||||
said so** — Gitea stores each package file's sha256 and `gitea.FileSHA256` reads it as metadata
|
||||
(*"a cheap metadata call — the artifact bytes are never downloaded"*). The cost was never CPU.
|
||||
|
||||
**It was latency × count.** `artifactChoices` made **one serial HTTP round-trip per version**, for
|
||||
two packages, capped at 20 each: `2 × (1 version list + 20 sha lookups)` = **42 sequential
|
||||
requests**, each ~0.6 s out through the public ingress. 42 × 0.6 ≈ 26 s, which is what the clock said.
|
||||
|
||||
**Two changes, both to the count-and-latency, neither introducing new state:**
|
||||
|
||||
1. **The sha lookups now run concurrently**, bounded at 8 in flight. Order is preserved by writing
|
||||
into a slot rather than appending — the dropdown is newest-first and a scrambled sha would show
|
||||
the operator a hash belonging to a different artifact. A failed lookup still drops **that**
|
||||
version only.
|
||||
2. **The client talks to Gitea in-cluster** (`http://gitea.gitea-system.svc.cluster.local:3000`,
|
||||
overridable with `GITEA_API_URL`) instead of the public name. Measured from the hub pod: **0.11 s
|
||||
against 0.26–1.16 s**, because the public path adds DNS, the ingress hop and a TLS handshake to
|
||||
every one of the 42. Plain HTTP is safe **only** because it never leaves the cluster network; the
|
||||
registry token rides the Authorization header, so this must not be pointed at a public host
|
||||
without TLS.
|
||||
|
||||
**Deliberately NOT done: caching the sha in the hub's own database.** That was the other half of the
|
||||
proposal and it is the wrong shape here. Gitea already is the store; a copy in `hub_settings` would
|
||||
be a **second source of truth that can drift from the registry it describes** — and the operator
|
||||
reads exactly this value to confirm what they are about to vouch, so a stale one would be a
|
||||
confident wrong answer. The same reasoning `golden_currency_gate.py` already records for the vouched
|
||||
version. With the fan-out, a cold load needs no cache to be fast.
|
||||
|
||||
**The cap stays at 20** and now bounds the fan-out too, not just the rendered list.
|
||||
|
||||
Tests assert the three things that had to survive: order (and that each sha belongs to its own
|
||||
version), per-version failure isolation, and **the concurrency itself** — a wall-clock assertion plus
|
||||
an in-flight counter, so a fast run cannot be luck. Red-proof: reverting to the serial loop takes
|
||||
861 ms where the concurrent one takes 150 ms, and the test fails naming the 26-second page.
|
||||
|
||||
## v0.99.0 — the hub can finally see whether the operator can get in (2026-08-08, R-260 / G-1)
|
||||
|
||||
**`oobDegraded` tested five things and the sixth never arrived.** The agent has emitted
|
||||
|
||||
Reference in New Issue
Block a user