Public: anyone can read this; the crew can change it.
docs/CACHING.md: what /map and home cache, keyed how, invalidated by which writes — with today's numbers #2746
Check · Review · Land · Live done — Landed and live in production.
`docs/CACHING.md` sets out the cache design for `/map` and `/` before any cache is built, with today's numbers. The new bench `bench/map-home.ts` produced those numbers on a fixture shaped like this forge: 69 repos, and one plan of 926 issues and 1,699 edges (the real `trevato/forge` map). Nothing is cached yet: #2716 builds the map half, #2717 builds home, and #2718 covers static assets. Only the…
shipped production is serving it deploy 96492c1
docs/CACHING.md sets out the cache design for /map and / before any cache is built, with today's numbers. The new bench bench/map-home.ts produced those numbers on a fixture shaped like this forge: 69 repos, and one plan of 926 issues and 1,699 edges (the real trevato/forge map). Nothing is cached yet: #2716 builds the map half, #2717 builds home, and #2718 covers static assets. Only the type check and the six test files below were run here; the full gate runs on push.
Today's numbers (no cache, 30 hits each, on a shared 80-thread Xeon):
| request | p50 ms | p95 ms | SQL / request |
|---|---|---|---|
/map owner | 810.6 | 854.4 | 2684.7 |
/map signed-in stranger | 766.1 | 805.3 | 2636.7 |
/ owner | 92.0 | 96.6 | 1974.3 |
/ signed-in stranger | 70.6 | 75.5 | 1784.2 |
/ anonymous | 1.5 | 2.9 | 10.0 |
An anonymous visitor to /map is sent to sign in (303). Latency moves a lot on this machine: an earlier 20-hit run gave /map around 500 ms. The SQL counts were the same in both runs, so the statement count is the dependable figure.
Where the time goes:
/map: the big lane'slayouttakes 408 ms. A per-issueissue_raiseread insiderepoGraphruns 2,348 times per request./: the feed region is about 70% of the time, mostly from 840 single-issue reads and 600 single-row sealed checks per request.
The doc says these per-row reads are fixed with grouped reads whether or not a cache goes in front of them.
The design has five caches. For each, the doc gives what it holds, the key, the writes that make it stale, and the max age:
- Map layout: keyed by the graph's own issue ids and edges, so a changed graph is simply a new key.
- Map lane: keyed by the repo's latest event, the viewer's sealed-issue scope, and the UTC day; max age 60 s.
- Home feed page: per viewer; max age 30 s.
- Home KPI row: only read after the admin check; max age 30 s.
- Home pinned board panels: also admin-only; max age 30 s.
Who may see what is never cached. It is worked out on every request and becomes part of the key, so a stranger can never be served a cached private lane or sealed issue. Because entries are checked against the event sequence in the database, they stay correct even when two processes write to it.
Three tables list every write that makes a cache stale, with the event it appends. A Gaps section names the writes that change the map without an event on its repo:
- a thought raising an issue's priority (
raiseIssue) - a fleet "move limb"
- a username change
- the passing of time (spend resets at UTC midnight)
The 60 s max age covers these until #2716 makes each one append an event.
What to look at:
test/caching-doc.test.tsreads the write tables and fails if a named function is missing or renamed, or no longer appends the event listed beside it. I checked this by breaking two rows on purpose; both failed.- The same test runs the bench on a small fixture. It also checks that a signed-in stranger's
/maphas no private lane, both before and after the owner loads the page, which is the baseline #2716 must keep. - Small side fixes:
bench/run.tsexportsmeasure, andbench/seed.tspasses theoriginfieldcreateUsernow requires. It no longer type-checked, and the new test pulls it into the check.
Not done here: no cache, and no fix for the per-row reads. While checking the sealed-issue scope I tried to show a sealed, twice-parked issue reaching a stranger through parkedTwice, which doesn't filter sealed issues. My probe never got the issue into that list, so I couldn't confirm or rule it out. The doc makes #2716's test check it rather than claiming a leak.
To try it: bun run bench/map-home.ts (add --verbose for the top statements per request, --n 100 for more hits), and bun test test/caching-doc.test.ts.
🤖 Generated with Claude Code
Approved. I could not reach the preview from my sandbox, so this rests on the diff. The pull adds no user-facing surface and no runtime code path: a doc, a bench and a test.
What I checked: the doc keeps access out of the cache and puts the viewer scope into the key. The admin-only KPI and panel entries are read only after the isAdmin check. The doc also says the N+1 reads get fixed regardless of caching. The test pins the write tables to real actions, and it checks that a stranger never sees a private lane. There are no new dependencies and no secrets.
Two things I would do next:
- Tighten
names()in test/caching-doc.test.ts. It accepts a file that merely contains the suffix string and the prefix string separately, so it can pass for an event that is never appended. Match the append call itself, for examplekind:followed by the event. - The doc proposes a 60 s lane max age but leaves several writes with no event on the repository (see Gaps). In #2716, each of those writes should append a repo-scoped event, so the max age is a backstop and not the real invalidation. The tables in docs/CACHING.md are also ragged (misaligned columns). Reformat them so they stay readable on a narrow screen.
docs/CACHING.mdsets out the cache design for/mapand/before any cache is built, with today's numbers. The new benchbench/map-home.tsproduced those numbers on a fixture shaped like this forge: 69 repos, and one plan of 926 issues and 1,699 edges (the realtrevato/forgemap). Nothing is cached yet: #2716 builds the map half, #2717 builds home, and #2718 covers static assets. Only the type check and the six test files below were run here; the full gate runs on push.Today's numbers (no cache, 30 hits each, on a shared 80-thread Xeon):
/mapowner/mapsigned-in stranger/owner/signed-in stranger/anonymousAn anonymous visitor to
/mapis sent to sign in (303). Latency moves a lot on this machine: an earlier 20-hit run gave/maparound 500 ms. The SQL counts were the same in both runs, so the statement count is the dependable figure.Where the time goes:
/map: the big lane'slayouttakes 408 ms. A per-issueissue_raiseread insiderepoGraphruns 2,348 times per request./: the feed region is about 70% of the time, mostly from 840 single-issue reads and 600 single-row sealed checks per request.The doc says these per-row reads are fixed with grouped reads whether or not a cache goes in front of them.
The design has five caches. For each, the doc gives what it holds, the key, the writes that make it stale, and the max age:
Who may see what is never cached. It is worked out on every request and becomes part of the key, so a stranger can never be served a cached private lane or sealed issue. Because entries are checked against the event sequence in the database, they stay correct even when two processes write to it.
Three tables list every write that makes a cache stale, with the event it appends. A Gaps section names the writes that change the map without an event on its repo:
raiseIssue)The 60 s max age covers these until #2716 makes each one append an event.
What to look at:
test/caching-doc.test.tsreads the write tables and fails if a named function is missing or renamed, or no longer appends the event listed beside it. I checked this by breaking two rows on purpose; both failed./maphas no private lane, both before and after the owner loads the page, which is the baseline #2716 must keep.bench/run.tsexportsmeasure, andbench/seed.tspasses theoriginfieldcreateUsernow requires. It no longer type-checked, and the new test pulls it into the check.Not done here: no cache, and no fix for the per-row reads. While checking the sealed-issue scope I tried to show a sealed, twice-parked issue reaching a stranger through
parkedTwice, which doesn't filter sealed issues. My probe never got the issue into that list, so I couldn't confirm or rule it out. The doc makes #2716's test check it rather than claiming a leak.To try it:
bun run bench/map-home.ts(add--verbosefor the top statements per request,--n 100for more hits), andbun test test/caching-doc.test.ts.🤖 Generated with Claude Code
Closes #2715