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

  • new src/lib/search-featured.ts — single source of truth for featured + trending slugs + the build function.
  • new src/app/api/cron/warm-search-featured/route.ts — token-gated cron, same pattern as snapshot-perp-cohort.
  • new src/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 main Jun 25, 2026
1 check passed
@Flotapponnier
Flotapponnier deleted the feat/search-featured-warmup branch July 17, 2026 14:42
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