Compress embedded web assets with correct HTTP negotiation #82
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem and evidence
Cold containers JavaScript was 324,670 raw bytes / 117,684 gzip-equivalent bytes but was served with identity encoding. Host detail was 344,432 / 123,055 bytes. Across static assets, raw size was 2,291,200 bytes; JavaScript was 1,744,641 raw / 533,182 gzip bytes. Load1 was 25–28 during the browser capture. Current embedded responses move asset.data into a Vec and set content type/cache control, with no content-encoding negotiation or Vary header (crates/server/src/lib.rs:1002-1020).
Network evidence is in browser-cold-results.json and browser-cold-timeline.json.
Recommendation
Precompress embedded text assets at build time, negotiate accepted encodings, and implement correct Vary and ETag behavior. Serve shared immutable response bytes; avoid retaining a second full raw copy if the identity strategy permits it.
Payoff, effort and risk
Expected gain: reduce the containers response by 206,986 bytes (63.8%). Replacing rather than duplicating raw text storage could save about 1.39 MB in the release payload; measure the net binary size.
Quick win vs deeper change: build-time compression is estimated at ≤½ day; robust content negotiation, validators and cache tests are the deeper part, estimated at 1–2 days.
Risk: low to medium; incorrect Vary or ETag handling can serve the wrong representation or break conditional requests.
Priority: Now (≤1 day for the transfer win).
2026-10-04 owner decisions: Matches the one-binary/static-web product and no-Node-at-runtime decision. Generate compressed assets during the existing build. Related: #69 (keep the production web bundle in the shipped binary).
Report: perf-audit report
Source: perf-audit 2026-10-04
Starting web-perf-now on branch
web-perf-now, base head069059d31fcd91366e76072e6be4871c01e5cde6. I am reading the three issue scopes and existing perf evidence, then will implement the precompressed asset serving, cold-route JS reductions, and normalized container search cache in this worktree.#82 progress (branch web-perf-now, base
069059d)The static build now emits gzip and Brotli variants plus per-representation SHA-256 ETags. The final browser run measured the /containers JavaScript payload at 98,959 Content-Length bytes across 30 responses (26 encoded), versus the 324,670-byte uncompressed baseline: 225,711 fewer payload bytes (69.5%). The route-wide gzip-equivalent JavaScript check is 111,793 bytes. The full browser gate still fails after its route captures on /api/v1/containers 503/401 responses under load; all cold-route measurements were emitted.
#82 finished
Branch: web-perf-now
Base:
069059d31fHead:
52fe3ae1daBuilt 109 text assets into 102 gzip and 102 Brotli variants. Precompression reported 2,315,378 source bytes, 609,837 gzip-or-identity bytes, and 518,533 Brotli-or-identity bytes.
Before, cold /containers JavaScript was 324,670 raw bytes / 117,684 gzip-equivalent bytes and served with identity. After, the browser measured 98,959 Content-Length payload bytes across 30 JS responses (26 encoded), a 225,711-byte / 69.5% reduction from the raw baseline; route JavaScript is 111,793 gzip-equivalent bytes. Host detail moved from 344,432 raw / 123,055 gzip-equivalent to 102,762 transfer bytes / 116,431 gzip-equivalent. All cold-route measurements are in /mnt/hdd/infraternal/artifacts/verify/web-perf-now-browser-final/browser.
The required fast gate exited 0. Verbatim output:
The separate browser gate recorded all cold-route budget metrics, then failed its page-clean assertion:
FAIL browser: Playwright against release server (324.222s). It reported /api/v1/containers 503/401 console errors while VM load was above the 8-CPU target. I did not weaken that assertion.Correction: I retried
scripts/verify browseragainst committed head52fe3ae1daafter the other active browser job ended. The retry again emitted the passing cold-route budgets and transfer metrics, then reportedFAIL browser: Playwright against release server (318.534s)at the light/desktop page-clean assertion, with /api/v1/containers 503/401 console errors. The cause is not established; the previous note attributing it to VM load alone was too strong. The requiredfastgate remains green (verbatim output in the finish comment above).Starting fix-static-check (#82) on branch
browser-green, base heada10c3e43a86a2cc2d6a6fca86afc225c906ff390.I’ll trace
integration_http_checks, reproduce the failure against/mnt/hdd/infraternal/gate-target/browser-green2/release/infraternal, fix the product or check based on the failing assertion, then run the requestedfasttier. I will not runmerge.Finding from the isolated
integration_http_checkstrace: it returns 1 at the root-page assertiongrep -F 'data-sveltekit-preload-data="hover"' "$index_file". The supplied release server is ready and the health/OpenAPI checks pass.select_content_codingchooses Brotli whenAccept-Encodingis absent, so the plaincurlused for/writes compressed bytes toindex.html; the assertion then cannot find the HTML marker. I’m changing the absent-header default to identity and adding coverage for that request case.The expanded rerun reached only the new compressed-representation assertion before returning 1. The first HTML-linked asset,
/_app/immutable/entry/start.zjOP-vs_.js, has no.brrepresentation because precompression intentionally omits a variant when it would not reduce bytes. Other linked assets do have.brfiles. I’m changing the check to select an HTML-linked JS/CSS file whose generated.brvariant exists, preserving the production behavior and making the assertion match the build rule.Finished fix-static-check (#82) on
browser-green.Head SHA:
92a04fb3913940a25f924e4c32dd8921f342c0baThe failing assertion was the root-page
grepfordata-sveltekit-preload-data="hover". With noAccept-Encoding,select_content_codingselected Brotli, so plain curl wrote compressed bytes and the HTML assertion failed. MissingAccept-Encodingnow selects identity; explicit Brotli and gzip negotiation is preserved. The server unit test covers unnegotiated HTML bytes and immutable asset headers across identity, Brotli, and gzip. The HTTP check compares identity bodies, requires the SPA fallback to match/, and verifiesContent-Encoding,Vary, cache policy and content type for identity, Brotli and gzip.Verification:
/mnt/hdd/infraternal/gate-target/browser-green2/release/infraternal:PASS isolated release server HTTP and static behaviorcargo test: 1 passed, 367 filtered out (15 suites, 0.00s)INFRATERNAL_VERIFY_RUN_ID=fix-static-check scripts/verify faststage output (verbatim):An earlier full workspace test attempt hit an unrelated
provider-bollardtest once; it passed in isolation and all subsequent completefastruns passed. No merge gate was run.Started fix-deploy-node on branch fix-deploy-node at base head
851d502909. Scope: make scripts/build-web work with the repository's existing JS runtime, inspect deploy/build paths, and run the requested build-only and fast gates. No production deploy will be run.Finding:
deploy/o2/build.shpins Bun 1.4.2 and puts it on PATH, then callsscripts/build-web; that script's final precompression step invokednode, which the disposable Ubuntu build image never installs. I switched that invocation to Bun and documented the shared pinned runtime indocs/DEPLOY-MONITOR01.md. Bun's currentnode:zlibreference marks the module fully supported and exposes bothgzipSyncandbrotliCompressSync: https://bun.sh/reference/node/zlibPath check:
scripts/deploy-o2still exists and uses the samedeploy/o2/build.sh;scripts/verify fastprepares the bundle throughscripts/build-web;scripts/previewalso shares it. No tracked CI/workflow files were present.Local output evidence:
scripts/build-webgenerated 109.gz, 109.br, and 348 ETag metadata files, including_metadata/index.html.identity.etag. A byte check found identity assets and Brotli variants identical to Node output. Bun's gzip bytes differ from Node for 47 variants (and their 47 gzip ETags); the generated ETags match the actual Bun bytes. A decode/hash check verified all 109 gzip and 109 Brotli variants reproduce their source bytes and have matching ETags.Finished fix-deploy-node. Branch:
fix-deploy-node; base:851d50290998f1328b7e5557f9084a5d7d0dff07; head:5af6c2797a7a955baf06eb5884ba4c8dbac0edb8. Commit:fix(deploy): use Bun for web precompression.The web build now invokes
scripts/precompress-web.mjswith the pinned Bun runtime already installed bydeploy/o2/build.sh.docs/DEPLOY-MONITOR01.mdrecords that shared build choice.scripts/deploy-o2is still present and uses this same builder;scripts/verify fastandscripts/previewcall the sharedscripts/build-web. No tracked CI/workflow files were present.rtk scripts/build-webpassed locally.scripts/deploy-monitor01 --build-only --ref HEADpassed for x86_64 and aarch64; both builds emitted 109 gzip and 109 Brotli variants and completed bundle checksums and local OCI image validation. The packaged image HTTP smoke checked identity, Brotli, and gzip bodies against each other and verified each response ETag against its embedded bytes:Fast gate exited 0. Its stage output:
Build-only output:
The fast gate's Bun audit step reported one low
cookie@0.6.0advisory as report-only; it did not fail the gate. No production deployment was run.