Skip to content

perf: cache /v1/genres/popular with 15-minute TTL#964

Merged
dylanjeffers merged 1 commit into
mainfrom
perf/cache-genres-popular
Jun 18, 2026
Merged

perf: cache /v1/genres/popular with 15-minute TTL#964
dylanjeffers merged 1 commit into
mainfrom
perf/cache-genres-popular

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

What

GET /v1/genres/popular ran a GROUP BY genre scan over the tracks table — plus an in-process normalize/merge/sort — on every request, with no caching. This adds a 15-minute in-memory TTL cache.

How

  • New genresPopularCache (otter.Cache[string, []PopularGenre], 15m TTL) on ApiServer, built and wired in NewApiServer exactly like the existing relatedUsersCache / qualifiedPlaylistsCache.
  • Extracted getPopularGenres() which does the DB query + genre normalization/merge/sort, caches the result, and returns it. Keyed by (limit, offset, startTime bucket), where the bucket is startTime truncated to the 15m TTL (startTime is a rolling window, so bucketing keeps the key stable within a window; the DB query still uses the exact startTime on a miss).
  • min_count is applied per request after the cache lookup, so one cached slice serves every threshold. The cached slice is treated as immutable — the handler builds a fresh filtered slice rather than aliasing it.
  • Normalization preserved: the NormalizeGenre variant-merge + re-sort logic is unchanged, just moved behind the cache.

Tests

  • TestGetPopularGenresCache — DB-free unit test pinning the cache-hit path and the (limit, offset, bucket) key contract (a miss reaches the nil pool and panics, proving the key actually differs).
  • TestGenresPopularExcludesAccessAuthoritiesTracks now calls genresPopularCache.Clear() after its DB mutation so the re-read reflects the change instead of the cached count.
  • go build ./..., go vet ./api/..., and the new unit test pass locally. DB-backed tests run in CI.

🤖 Generated with Claude Code

The endpoint ran a GROUP BY genre scan over the tracks table plus an
in-process normalize/merge/sort on every request. Cache the normalized
result slice in an otter TTL cache (matching the existing relatedUsersCache
/ qualifiedPlaylistsCache pattern), keyed by (limit, offset, startTime
bucket) where the bucket is startTime truncated to the 15m TTL.

min_count is applied per request after the cache lookup so a single cached
slice serves every threshold, and the cached slice is treated as immutable
(the handler builds a fresh filtered slice rather than aliasing it).

Adds a DB-free unit test for the cache key/bucketing contract, and clears
the cache in the access-authorities test so its post-mutation re-read isn't
served a stale cached count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dylanjeffers dylanjeffers merged commit a67b8dd into main Jun 18, 2026
4 checks passed
@dylanjeffers dylanjeffers deleted the perf/cache-genres-popular branch June 18, 2026 18:23
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