Repository navigation
perf: faster doc open — compressed/immutable assets, editor preload, blob ETags, doc-load metrics - #32
Merged
Conversation
…ad, blob ETags, doc-load histograms
trevex
approved these changes
Oct 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opening a document on the dogfood (Hetzner) deployment is slow. This PR does not find the root cause. It removes avoidable load time, and adds the metrics that will show where the rest of the time goes.
Not Hetzner Object Storage. Dogfood runs with the default
blob.backend: postgres. The Hetzner bucket only holds CNPG's WAL archive and nightly backups, which are not on the read path. A cache in front of S3 would not help.Changes
/assetshave content-hashed names. They are now brotli-compressed and served aspublic, max-age=31536000, immutable. The 749 KB editor chunk goes over the wire as 241 KB.index.htmland client-side routes areno-cache, so a deploy is picked up on the next load, not after the browser's heuristic cache expires. The serving code moves intostatic_files.rs, and compression applies there only, not to the API.DocPagestarts downloading the editor chunk on mount, in parallel withGET /api/docs/{id}. Before, the download only started after that request returned.import()is cached, so the preload andlazyshare one fetch.GET /api/blobs/{id}now sends the stored sha256 as a strongETag. A matchingIf-None-Matchgets a 304 without the bytes being read. The check runs after the ACL check.max-agestays at 60 s, so an access revoke still takes effect within a minute.knot_collab_initial_sync_secondscovers the time from the socket upgrade until the document state is queued to the client.knot_room_hydrate_secondscovers loading a cold room. The HTTP histogram stops at the 101 upgrade, so neither was measured before.Bugs found on the way
knot_http_request_duration_seconds_bucketnever existed, so the dashboard, thedocs/SLO.mdqueries and thePrometheusRulelatency alert had nothing to read.set_bucketsfixes this; the bucket edges include each SLO threshold.ServeDir::not_found_serviceforces a 404 status on its fallback, so/docs/<id>returned 404 withindex.htmlas the body. It rendered, but inflated the 4xx rate. It now usesfallback, which returns 200. A missing/assets/*chunk is now a plain 404 instead ofindex.htmlserved as JavaScript.Tests
static_files_integration.rs(new) covers:no-cacheon the shell and on client-side routes;blobs_integration.rscovers:metrics_integration.rscovers:The exporter now runs on its own runtime. Before, it lived on the runtime of whichever test installed it first, and died when that test finished.
DocPage.preload.test.tsx(new) checks that the editor module is requested while the metadata request is still pending.Each new behaviour test failed before its change was made. The Rust workspace suite, the web suite, clippy (
-D warnings, all features), rustfmt, tsc and eslint pass locally. The Playwright e2e suite could not run locally (no Docker), so it runs here in CI first.Also checked end to end against a release build with a real SPA build:
curlshowscontent-encoding: brand the expectedcache-controlvalues./metricsshows# TYPE … histogramwith_bucketseries for the new metrics.After deploy
This shows where document-open time goes on dogfood:
Not in this PR
/p/<token>/blobs/…) don't get the ETag handling yet.🤖 Generated with Claude Code