Skip to content

perf(worker): cache the public summary per colo and read D1 one query fewer - #50

Merged
cgaravitoq merged 2 commits into
mainfrom
cgaravitoq/summary-cache
Oct 8, 2026
Merged

cgaravitoq merged 2 commits into
mainfrom
cgaravitoq/summary-cache

Conversation

@cgaravitoq

Copy link
Copy Markdown
Owner

What

The summary route advertised Cache-Control: public, s-maxage=300, but a Worker on a Custom Domain runs in front of the edge cache, so nothing was ever cached. Measured on 2026-10-08:

  • About 6,000 summary requests a day, nearly all from the portfolio's server render.
  • 54% of them ended in 499, because that caller gives up at 500 ms from BOM, ICN, NRT, HKG, LAX and SEA.
  • About 4,500 SELECT ... GROUP BY executions a day in wrangler d1 insights.
  • Each summary made three sequential round trips to D1 in WEUR.

Change

  • summarizeUsage reads the user and the timezone of its most recently seen machine in one LEFT JOIN, keeping the same last_seen, machine_id tie-break and the UTC default. A summary now costs two round trips instead of three.
  • cachedSummary in src/server/summary-cache.ts keeps a summary in the Cache API per colo for the five minutes the route already advertises. It keys by the lowercased login and the range, and never caches a login without an account. The route uses it, and the landing page can call it instead of summarizeUsage.
  • The portfolio gets its own per-colo stale-while-revalidate cache in a separate PR (cgaravitoq/portfolio-monorepo), which removes most of the traffic. This PR makes the remaining misses and the public widget cheap.

Verification

  • New specs: summary-cache.spec.ts covers a repeated summary served without touching D1, one key per lowercased login and range at max-age=300, and no entry for an unknown login. A usage.spec.ts case asserts two prepare calls per summary.
  • The app and catch-all specs stub caches.open with an in-memory cache through src/test/memory-cache.ts.
  • format, lint:slop, check-types, test, test:dependency-policy, test:package, audit:production, build and wrangler deploy --dry-run all pass locally.

…query

summarizeUsage made three sequential round trips to D1, which lives in WEUR:
the user, the timezone of its most recently seen machine, then the GROUP BY.
The first two only depend on the login, so a LEFT JOIN answers both and keeps
the same last_seen and machine_id tie-break and the UTC default.
…utes

The summary route answered with `Cache-Control: public, s-maxage=300`, but a
Worker on a Custom Domain runs in front of the edge cache, so every request
read D1: about 4,500 summary queries a day on 2026-10-08, ~6,000 requests
with 54% aborted by a caller timing out at 500 ms.

cachedSummary keeps a summary in the Cache API per colo for the five minutes
the route already advertises, keyed by the lowercased login and the range, and
never caches a login without an account. The landing page can call it too
instead of summarizeUsage.
@cgaravitoq
cgaravitoq merged commit d8ebd83 into main Oct 8, 2026
4 checks passed
@cgaravitoq
cgaravitoq deleted the cgaravitoq/summary-cache branch October 8, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant