Skip to content

perf(cache): probe-based cache admission and an off-request board warmer - #83

Merged
kvaps merged 2 commits into
mainfrom
perf/warm-board-cache
Aug 9, 2026
Merged

perf(cache): probe-based cache admission and an off-request board warmer#83
kvaps merged 2 commits into
mainfrom
perf/warm-board-cache

Conversation

@kvaps

@kvaps kvaps commented Aug 9, 2026

Copy link
Copy Markdown
Member

Problem

Opening aeman after any break longer than ten minutes hung on a 20-30s progress bar. Measured on the production-size board (~1400 cards): a full load is ~15 sequential GraphQL pages at ~2s each — sequential because GitHub's cursor pagination cannot be parallelized — so ~28s wall-clock. Two designs made that load far more frequent than it had to be:

  • The shared board cache is (correctly) gated per login, but the only way a login could prove access was a full token-scoped board load. Every engineer coming back from a break personally re-paid ~28s to be admitted into a cache their teammates kept warm.
  • Overnight, with every laptop asleep, no watch connection survived to keep the cache alive — the first open of the morning always started cold. During the day, the opposite: every open tab kicked a stale revalidation from its 30s ping ticker, a near-continuous full reload.

Change

Probe-based admission. Proving that a token can read the board costs one project-id GraphQL query (~0.5s), not a full load. A login reaching a warm cache is admitted by that probe; an aging proof refreshes the same way in the background while the read is served fresh. The full load remains the authorization path wherever the probe is unavailable or fails, and a probe that positively reports access gone drops the proof. A parity test runs identical GitHub fixtures (not-found, partial errors, rate-limit, 401/403, insufficient scopes, 502) through both the probe and the full load and pins their verdicts together — the probe is exactly as strict as the load it stands in for.

Off-request warmer. One server-side loop per board refreshes the cache on the cadence of the fresh window while the board has watchers, and on a slower idle cadence for 16h past the last read — carrying the cache through the night, so the morning's first open is served instantly. It replaces the per-tab ping revalidation entirely. The warmer's identity rotates to the most recent successful loader and is bound to that user's web session: it stops on logout or session expiry, never runs on MCP-request tokens (nothing to bind their lifetime to), tolerates transient upstream failures by skipping a tick, and stops with a log line on persistent failure or access loss. Warm refreshes never vouch for anyone's access.

Mutations benefit directly: with the warmer keeping the cache inside the fresh window, reads and mutations land on the cached board instead of blocking on a reload.

Review

The change went through three independent adversarial reviews (concurrency/lifecycle, cache-authorization security, perf/operations). The security review verified empirically that the probe admits no one the full load would reject, and found no bypass of the per-login cache gate. All blockers and majors they raised are addressed in the second commit; the notable ones: mutations re-paying full loads (fresh window now matches the warmer cadence), the warmer dying overnight on a single 502 (bounded retries), a captured OAuth token outliving its owner's session (session-bound, rotating source), and an unauthorized caller being able to extend the warm window (reads stamp it only when actually served).

Numbers

On the ~1400-card board: first open after a break drops from ~28s to milliseconds (probe ~0.5s when a re-proof is needed); the only remaining cold load is the very first request after a server restart. Steady-state GitHub traffic drops from roughly one full reload per active tab per minute to one per board per 3 minutes (8 minutes when nobody is watching).

Known trade-offs

  • External GitHub edits (made outside aeman) surface within one warmer tick (~3 min worst case, was ~30-60s via per-tab revalidations) for passive viewers; active users' own reads still trigger revalidation as before.
  • A user whose token can read the project but not some linked private issues gets the shared cache's fuller view (titles instead of "(untitled)" placeholders). Not new — the pre-probe flow had the same property one full load later — but worth stating: the gate proves "can read this board", not "sees exactly what their own token would fetch".
  • The post-deploy cold start is not addressed here; a follow-up could warm the pinned board at boot from a persisted session token.

Testing

  • TestBoardCacheProbeAdmitsSecondUser, TestBoardCacheProbeDeniedFallsThrough — probe admits exactly one cheap check, denial falls through to the caller's own load.
  • TestCachedAuthz — the full state matrix including the new re-proof flag.
  • TestBoardCacheWarmerFollowsWatchers — refreshes on its own, outlives watchers within the idle window, stops when both conditions lapse.
  • TestCheckBoardAccessMatchesFullLoad — 10-fixture probe/load parity table.
  • Shared test fakes made goroutine-safe (the async title resolve and the warmer hit them from background goroutines); everything runs and passes under -race.
  • Verified live against the production-size board: cold prime once, then instant reads across warmer ticks.

kvaps added 2 commits August 9, 2026 22:40
Opening aeman after any >10-minute break hung on a 20-30s progress bar.
Two causes stacked:

- The shared board cache is (correctly) gated per login, but the only
  way a login could prove access was a full token-scoped board load —
  a dozen sequential GraphQL pages on a large board. Every engineer
  coming back from a break personally re-paid that load to be admitted
  into a cache their teammates kept warm.
- Overnight, with every laptop asleep, no watch connection survived to
  keep the cache alive, so the first open of the morning always started
  from a dead cache. Meanwhile, during the day, every open tab kicked a
  stale revalidation from its 30s ping ticker — a near-continuous full
  reload of a multi-page board.

Now a login reaching a warm cache proves access with a single
project-id probe (~0.5s) instead of the full load, and one server-side
warmer loop per board refreshes the cache every few minutes while the
board has watchers or was read within the idle window — carrying it
through the night — replacing the per-tab ping revalidation entirely.
The warmer runs on the token of the request that started it, stops on
the first failure (a revoked token must not keep polling), and never
vouches for anyone's access.

Net effect on a ~1400-card board: first open after a break drops from
~28s to under a second, and steady-state GitHub traffic goes down.

The shared test fakes also become goroutine-safe: the async title
resolve and the warmer hit them from background goroutines, which the
race detector rightly flagged.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Three independent reviews (concurrency/lifecycle, security, perf/ops)
of the previous commit converged on the same root problems; this
commit resolves every blocker and major, and the minors.

Mutations no longer re-pay the full board load. boardFreshFor now
matches the warmer's watched cadence (3 minutes): the warmer is what
keeps the cache inside the fresh window, so both reads and mutations
land on cacheFresh instead of blocking on a multi-page reload — the
previous 30s window left every mutation outside it most of the time
(review blocker). A mutation that queues behind the warmer's reload
now also rides the freshly installed board, proving its own access by
probe, instead of loading again.

An aging access proof no longer forces a full reload. cached() reports
'serve fresh, but re-prove' and the proof refreshes via a background
probe (deduplicated per login) — previously the 60s..10min proof-age
window pushed every read into a full background reload, which negated
most of the intended GitHub-traffic win (review major). A probe that
positively reports access gone drops the proof; the boardStaleMax
ceiling still bounds a revoked token's ride exactly as before.

The warmer no longer outlives its owner or dies of a single 502.
Its identity rotates to the most recent successful loader, is bound to
that request's web session, and stops when the session ends (logout or
TTL) — a captured OAuth token no longer polls GitHub after its owner
left (security major). MCP-request tokens never power the warmer: they
have no session to bind their lifetime to. Transient refresh failures
now skip a tick (with a Sync broadcast, matching revalidate's
contract) instead of killing the loop — one hiccup at 02:00 used to
silently guarantee the next morning's 30s cold open (perf major);
persistent failure or access-gone still stops it, with a log line.
Watcher-less boards refresh on a slower idle cadence to spare the
captured token.

Also per review: lastRead is stamped only on successful serves, so an
unauthorized caller cannot extend the warm window; the warming flag is
cleared in the same critical section as the exit decision, closing a
lost-wakeup race; markAuthed sweeps expired proofs before the
empty-login return so the warmer's installs keep the map clean; warm
loads are bounded by a timeout; and the probe now has a parity test
running identical GitHub fixtures through CheckBoardAccess and the
full load, pinning the two verdicts together — that lockstep is the
security argument for admitting users by probe.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
@kvaps
kvaps marked this pull request as ready for review August 9, 2026 23:08
@kvaps
kvaps merged commit 2a0a688 into main Aug 9, 2026
2 checks passed
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