Skip to content

search dialog: cron-warmed KV blob for Live Leaders + Trending (perf) - #707

Merged
Flotapponnier merged 1 commit into
mainfrom
feat/search-featured-warmup
Jun 25, 2026
Merged

search dialog: cron-warmed KV blob for Live Leaders + Trending (perf)#707
Flotapponnier merged 1 commit into
mainfrom
feat/search-featured-warmup

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Most durable fix for the slow Live Leaders skeleton in the search dialog.

Before

  • Dialog open → fetch /api/citable (full ~30-bench index, ~50 KB)
  • Server-side: assemble all benchmarks, materialize KV reads + Prom queries
  • Visible 1-2 s skeleton state on every cold edge cache

After

  • Vercel cron/api/cron/warm-search-featured runs every minute → calls buildFeaturedLeaders() → writes a single ~2 KB JSON blob to Upstash KV (ocb:cohort:search-featured:v1)
  • Public endpoint/api/search/featured → 1 KV GET (~5-10 ms) → returns slim payload
  • SearchProvider prefetches the endpoint at mount + on trigger hover/focus (idempotent, de-duped via ref)
  • Dialog consumes pre-resolved featured / trending arrays from context. Drops the per-mount /api/citable fetch entirely.

Edge cache s-maxage=60, stale-while-revalidate=300 → most requests served by Vercel edge with zero origin hit.

Graceful degradation

  • No CRON_SECRET / no Upstash creds → cron returns {configured: false}, endpoint falls through to live build, dialog still works.
  • Upstash unreachable → endpoint falls through to live build (same UX as old /api/citable path).
  • Worker dies → endpoint serves last-good KV until 24h safety TTL.

Files

  • newsrc/lib/search-featured.ts — single source of truth for featured + trending slugs + the build function.
  • newsrc/app/api/cron/warm-search-featured/route.ts — token-gated cron, same pattern as snapshot-perp-cohort.
  • newsrc/app/api/search/featured/route.ts — KV-first reader with live fallback.
  • src/components/search/search-provider.tsx — adds featured + prefetchFeatured to context, mount-time prefetch.
  • src/components/search/search-trigger.tsx — onMouseEnter + onFocus prefetch.
  • src/components/search/search-dialog.tsx — drops /api/citable fetch, consumes context blob.
  • vercel.json — adds the cron schedule.

Test plan

  • After deploy + first cron tick: curl https://openchainbench.com/api/search/featured | jq '.source, .featured | length, .trending | length'"kv", 6, 6 (or whichever subset has live leaders).
  • Hover the header search trigger before clicking → Network tab shows the /api/search/featured prefetch fire on hover.
  • Open Cmd+K → Live Leaders cards render immediately with leader name + value (no visible skeleton).
  • Disable JS for one tab → endpoint still 200s with the latest blob.
  • Set CRON_SECRET to wrong value temporarily → cron returns 401, KV blob ages out gracefully, endpoint live-fallbacks.

Fixes the slow Live Leaders skeleton: dialog used to fetch /api/citable
on every open (~50 KB, full benchmark assembly server-side, frequently
visible 1-2 s skeleton). Now a Vercel cron pre-builds a 12-card subset
every minute, persists it as a single Upstash KV blob, and the dialog
opens with data already prefetched at page load.
Architecture:
- src/lib/search-featured.ts: builds the slim blob (featured + trending
cards: slug, title, category, unit, value, leader). Single source of
truth for the dialog's hardcoded slug lists.
- src/app/api/cron/warm-search-featured/route.ts: every minute, calls
buildFeaturedLeaders + writeCohortSnapshot('search-featured'). Same
CRON_SECRET gate + soft-no-op-on-missing-creds pattern as the existing
cohort cron jobs.
- src/app/api/search/featured/route.ts: KV-first reader with live-build
fallback (cold-start safety). Aggressive edge cache (s-maxage=60,
swr=300) — most requests served by Vercel edge with zero origin hit.
- SearchProvider: prefetches /api/search/featured at mount (idempotent)
and exposes featured + prefetchFeatured via context. Re-fetch hook on
trigger hover/focus.
- SearchTrigger: onMouseEnter + onFocus call prefetchFeatured. Dialog
almost always opens with the blob in memory; the brief 'skeleton'
state is now invisible to the user.
- SearchDialog: consumes featured/trending arrays from context (zipped
against the search index), drops the per-mount /api/citable fetch.
Search-results rows still get leader logos via the same warmed blob
when the bench appears in featured/trending; otherwise fall back to
the kind-icon (no extra API calls).
- vercel.json: cron schedule * * * * * for warm-search-featured.
Graceful degradation:
- No CRON_SECRET / no Upstash creds → cron 200s with configured:false,
endpoint falls through to live build path, dialog still works.
- Upstash unreachable → endpoint falls through to live build, same UX
as the old /api/citable path (slow but functional).
- Worker dies → endpoint serves last-good KV until 24h safety TTL
expires, then falls through to live build.
Payload size: ~50 KB (/api/citable, full index) → ~2 KB
(/api/search/featured, 12 cards).
TTFB: ~1-2 s server-side assembly → ~5-10 ms KV GET (edge-hit: ~0 ms).
@Flotapponnier
Flotapponnier merged commit e3c234d into mainJun 25, 2026
1 check passed
@Flotapponnier
Flotapponnier deleted the feat/search-featured-warmup branch July 17, 2026 14:42
Sign up for freeto 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

@Flotapponnier