Skip to content

perf: client-side athlete index for instant search#188

Merged
rootulp merged 6 commits into
mainfrom
worktree-groovy-petting-kahn
May 17, 2026
Merged

perf: client-side athlete index for instant search#188
rootulp merged 6 commits into
mainfrom
worktree-groovy-petting-kahn

Conversation

@rootulp
Copy link
Copy Markdown
Owner

@rootulp rootulp commented May 17, 2026

Summary

  • Move homepage athlete search from a server /api/search round-trip per keystroke to a client-side index served as a static asset.
  • Browser fetches /athlete-index.tsv.gz once per session (CDN-cached with stale-while-revalidate=604800), decompresses with DecompressionStream, then runs the same prefix / rotated-token / substring search locally — ~56µs per query on the full 808K-athlete index.
  • API stays as the fallback while the index is in flight (typically the first 1–2 s of a fresh visit on broadband) and for browsers without DecompressionStream.
  • New Vercel Analytics events search_latency (source: "client" | "api"), search_index_load, and search_index_load_error let us verify the win in production.

Test plan

  • Open the homepage, focus search, type a name. First keystroke or two may hit /api/search (visible in Network); subsequent keystrokes show no further network requests.
  • Hard refresh — index served from browser HTTP cache, every keystroke is local from the start.
  • DevTools → throttle to "Slow 3G" → hard refresh → type immediately. /api/search fallback populates results while the index downloads in the background.
  • Cmd-K command palette on a non-home page (e.g. /races) still works.
  • cd app && npm run build succeeds with the new copy step in the build chain.
  • cd app && npm run test — 35/35 passing, npx tsc --noEmit clean, npm run lint clean.

🤖 Generated with Claude Code

rootulp added 6 commits May 16, 2026 21:24
Adds a copy step that mirrors data/athlete-index.tsv.gz into app/public/
on dev and build, plus a Cache-Control header allowing returning visitors
to instantly reuse the cached index while the browser revalidates in the
background. Sets up the static-asset path that client-side search will
consume in the following commits.
Splits the prefix / rotated-token / substring search algorithm out of
search-index.ts (which depends on fs/zlib) into a Node-free search-core
module. Server-side search-index.ts re-exports the pure pieces and
keeps the disk loader; client-side code added in the next commit will
import directly from search-core.

Existing search-index tests still pass against the re-exported API.
Adds parseIndexTsv (pure, unit-tested) and loadSearchIndex (browser-only,
memoized): fetches /athlete-index.tsv.gz, decompresses with
DecompressionStream, parses into IndexEntry[], and builds the sorted
search keys. searchAthletesInClientIndex wraps the shared core algorithm
over the in-memory data. Not wired into the UI yet.
useAthleteSearch now consults a module-level memoized index loader. Once
the index resolves, every keystroke runs locally (binary search over the
sorted name array + rotated-token keys) with no network roundtrip. While
the index is in flight, the hook falls back to /api/search unchanged.
GlobalSearchBar triggers the fetch on mount; CommandPalette triggers it
on open (renamed prefetchSearch → prefetchSearchIndex).

Emits search_latency, search_index_load, and search_index_load_error
analytics events so we can verify the win in Vercel Analytics.
Runs 10 representative queries against the full ~800K-entry index. Not
a CI gate — informational, run with \`vitest bench\`. Establishes a
baseline number so future algorithm changes can be compared.
@rootulp rootulp self-assigned this May 17, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tritimes Building Building Preview, Comment May 17, 2026 5:02am

@rootulp rootulp merged commit 140542a into main May 17, 2026
5 of 6 checks passed
@rootulp rootulp deleted the worktree-groovy-petting-kahn branch May 17, 2026 05:02
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