feat(plugin): split swr out of cache-hit + per-route serve/age metrics; v0.33.0 - #69
feat(plugin): split swr out of cache-hit + per-route serve/age metrics; v0.33.0#69harper-joseph wants to merge 2 commits into
Conversation
…s; v0.33.0
TTL-tuning telemetry, so raising or lowering a route's renderInterval can be decided
from a dashboard instead of a one-off investigation. Request-path counters only
(recordAnalytics in-memory buffers) — deliberately NOT the backlog snapshot, whose
timer is disabled on the kohls deployment because its native count walk stalls
worker 0 (harper-pro#664).
- bot_serve cacheStatus now distinguishes 'hit' (expiresAt still ahead — the page's
own renderInterval is being met) from 'swr' (past expiresAt, served from the
stale-while-revalidate window while the re-render is late/in flight). Both are
cache serves; the serve itself is unchanged, as is the source dimension (origin
offload reads the same). Folding both into 'hit' made the headline hit rate
unreadable as a freshness signal: at one measured point 71.9% "hit" quietly
included ~13% of the corpus being served past expiry. The x-harper-cache debug
header reports the same split.
- New route_serve (route, cacheStatus, deviceType) and route_page_age (same dims,
ms since render, cache-served only). recordAnalytics has exactly three dimension
slots (path/method/type) and bot_serve's are all taken, hence separate metrics
rather than a fourth dimension. Route label = matched route path ('/',
'/catalog/', '/product/prd-'), else route class, else 'unrouted' — tiny, stable
cardinality. Per route these answer: is the cadence delivered (swr/stale share),
is the corpus covered (miss share), and what age are bots actually served
(route_page_age vs that route's own renderInterval).
- The hit/swr boundary is extracted as cacheServeStatus(expiresAtMs, swrTtl, now),
the single source of truth for cache servability, and its edges are pinned in
tests: the expiry instant itself is already swr, the swr edge is exclusive,
NaN never serves, swrTtl 0 disables the window.
Cost per request: two boolean counter bumps, plus two numeric samples on cache
serves. No storage touch, no await, nothing on the response path.
Dashboards keying bot_serve on cacheStatus='hit' will see hit split into
hit + swr after this release — sum them for the old cache-served rate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request bumps the package version to 0.33.0 and introduces per-route analytics tracking for the prerender plugin. Specifically, it adds route_serve and route_page_age metrics to allow independent tuning of each route's render interval. It also refactors the cache freshness logic by introducing a cacheServeStatus helper function to distinguish between 'hit' and 'swr' (stale-while-revalidate) cache statuses. Comprehensive unit tests have been added to verify these new metrics and helper logic. There are no review comments to address, and I have no additional feedback to provide.
…he admin's three freshness copies onto it Self-review follow-up. The hit/swr boundary was defined in bot_request.js while PrerenderAdmin.js still carried three hand-copied `!isNaN(expiresAtMs) && expiresAtMs + swrTtl > now` computations — one of them commented "same freshness rule the serving path applies, so this cannot disagree", which was aspirational, not structural. Now it is structural: one exported function in util/pageFreshness.js (a util home, so resources don't import from a handler module), and every consumer — serve path, explain, pages view — calls it. When per-route swrTtl lands, it lands everywhere at once. renderNow's `lastCached >= since` check deliberately stays separate: that is "was this re-rendered after my request started", not swr servability. No behavior change; 392/392 tests (cacheServeStatus edges now pinned against the util module). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Self-review pass for efficiency + maintainability, one commit added: Efficiency (verified, no change needed): the serve path gained two numeric comparisons ( Maintainability (fixed): 392/392 tests, lint + format clean. |
Why
Phase 1 of TTL-decision telemetry: make "should this route's
renderIntervalmove up or down" answerable from recorded metrics instead of a one-off investigation. Two gaps found while tuning the kohls TTLs:hit.cacheStatus: 'hit'covers everything insideexpiresAt + swrTtl, so the headline hit rate can't distinguish "TTL is being met" from "we're serving out of the stale window because the re-render is late". At one measured point, 71.9% "hit" quietly included ~13% of the corpus being served past expiry.bot_serve/page_ageaggregate across all of them, so a per-route TTL decision has no data.What
bot_servecacheStatussplitshit→hit|swr.hit=expiresAtstill ahead (the renderInterval is being met);swr= pastexpiresAt, inside the stale-while-revalidate window. The serve itself is unchanged, and thesourcedimension is untouched (origin-offload reads identically). Thex-harper-cachedebug header reports the same split.route_serve(route, cacheStatus, deviceType) — per-route delivery: swr/stale share says whether that route's cadence is being delivered; miss share says whether its corpus is covered.route_page_age(route, cacheStatus, deviceType; ms since render, cache-served only) — served age against that route's own interval: the direct "should this TTL move" number.cacheServeStatus(expiresAtMs, swrTtl, now), with its edges pinned in tests (expiry instant is alreadyswr; window edge exclusive;NaNnever serves;swrTtl: 0disables the window).Why separate metrics instead of a 4th dimension
recordAnalyticshas exactly three dimension slots (path/method/type— verified in harper-prodist/core/resources/analytics/write.js), andbot_serveuses all three. Route label cardinality is tiny and stable: the matched route's path, else the route class, else'unrouted'.Why the request path, not the backlog snapshot
The kohls deployment just disabled the 15-min backlog snapshot timer (kohls-pr#52) because its
getRecordCountwalk issues a multi-second synchronous native iteration that stalls worker 0 and bot serving (harper-pro#664). These metrics ride the request path instead: two boolean counter bumps per request, two numeric samples on cache serves, buffered in-memory byrecordAnalytics— no storage touch, no await, nothing on the response path, and nothing that runs when no bots are being served.Breaking-ish
Dashboards keying
bot_serveoncacheStatus='hit'will see it split intohit+swrafter this release — sum them to recover the old cache-served rate. Dimension order is unchanged everywhere.Tests
392/392 pass (7 in botServe.test.js, covering dimension order for all four metrics, the route-label fallback chain, swr propagation, the age guards, and the
cacheServeStatusedges). Lint + format clean.Follow-ups (not this PR)
swrTtlalongsiderenderInterval(the global value can't fit 1h/6h/48h intervals at once).admin/views/overview.js).