Skip to content

perf(auth): memoize getAuthToken and refreshToken row read (CLI-13V)#828

Merged
BYK merged 3 commits intomainfrom
fix/n-plus-one-auth-query
Apr 23, 2026
Merged

perf(auth): memoize getAuthToken and refreshToken row read (CLI-13V)#828
BYK merged 3 commits intomainfrom
fix/n-plus-one-auth-query

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Apr 23, 2026

Summary

Fixes CLI-13V — N+1 Query detector flagged sentry.issue.view on every run, culprit getAuthToken.

Every outgoing API request hit the SQLite auth row three times:

  1. tryCacheHitauthHeaders(getAuthToken()) — cache-key build
  2. fetchWithRetryrefreshToken() — its own raw SELECT * FROM auth WHERE id = 1
  3. cacheResponse(..., authHeaders(getAuthToken())) — on cache miss

Each getAuthToken() was wrapped by withDbSpan plus the traced-statement proxy, producing paired db.operation/db spans. With 12 offender spans per pattern repetition, the performance detector flagged it.

Repeating pattern in the trace before this fix:

http.client  - GET /api/0/organizations/.../shortids/EVA-B7V/
  db.operation - getAuthToken
    db         - SELECT * FROM auth WHERE id = 1
  cache.get    - ...

Approach

Mirror the existing cachedFingerprint memo-and-reset pattern (src/lib/db/auth.ts:290+):

  • Add cachedAuthToken memo for getAuthToken().
  • Add cachedAuthRow memo for refreshToken()'s full-row read.
  • Both use { value: T | undefined } | undefined wrapper objects to distinguish "not cached" from "cached as undefined" (logged-out) — avoids re-querying on every call in the logged-out case.
  • Invalidation hooks (resetAuthTokenCache, resetAuthRowCache) wired into both mutation points (setAuthToken, clearAuth) alongside the existing resetIdentityFingerprintCache().

Safe under OAuth token rotation and 401 force-refresh because both paths route writes through setAuthToken, which now invalidates all three auth caches.

Net Impact

  • Per API request: 3 auth-row SQL reads → 1 (on first call), 0 on subsequent calls within the command.
  • Per command: getAuthToken span appears at most once, not per API request.

Testing

  • test/lib/db/auth.test.ts — new getAuthToken memoization suite (5 tests: cache hit, logged-out caching, setAuthToken invalidation, clearAuth invalidation, env-var contract) and refreshToken row-read memoization suite (2 tests).
  • test/helpers.tsuseTestConfigDir now invalidates both caches in beforeEach/afterEach so module-scoped memos can't leak across test files.
  • test/lib/db/auth.property.test.tsresetAuthCaches() helper called at the top of each property iteration (env-var mutations bypass the internal invalidation hooks).
  • test/lib/db/model-based.test.ts — env-var commands and test setup invalidate the caches; this actually fixed one previously-flaky assertion (clearAuth also clears pagination cursors).

All 56 auth-related tests pass. Full suite has the same number of pre-existing flaky failures as baseline (minus one this PR fixed).

Verification

bun test test/lib/db/auth.test.ts test/lib/db/auth.property.test.ts test/lib/db/model-based.test.ts

Manual trace verification: run bun run dev -- issue view <id> with SENTRY_DSN set and confirm the getAuthToken span appears at most once per command in the resulting trace.

Eliminates the N+1 'SELECT * FROM auth WHERE id = 1' pattern flagged on
every API-driven command (issue view, trace view, etc.). Each outgoing
request previously hit the auth row three times: two getAuthToken() calls
for cache-key building and one raw SELECT inside refreshToken().

Mirrors the existing cachedFingerprint memo-and-reset pattern: both new
caches use wrapper-object sentinels to distinguish 'not cached' from
'cached as undefined' (logged-out), and are invalidated at both auth
mutation points (setAuthToken, clearAuth) alongside the fingerprint.

Also centralizes auth-cache reset in useTestConfigDir to stop module-scoped
memos leaking across test files.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-828/

Built to branch gh-pages at 2026-04-23 11:21 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

Codecov Results 📊

138 passed | Total: 138 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

✅ Patch coverage is 100.00%. Project has 1944 uncovered lines.
✅ Project coverage is 95.28%. Comparing base (base) to head (head).

Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    95.27%    95.28%    +0.01%
==========================================
  Files          284       284         —
  Lines        41152     41183       +31
  Branches         0         0         —
==========================================
+ Hits         39206     39239       +33
- Misses        1946      1944        -2
- Partials         0         0         —

Generated by Codecov Action

BYK added 2 commits April 23, 2026 11:08
Tighten JSDoc and inline comments added in the previous commit to match the
terse style used elsewhere in auth.ts (cf. cachedFingerprint). No behavior
change.
Add resetIdentityFingerprintCache() alongside the two new cache resets so
the helper invalidates all three module-scoped auth caches, matching the
existing lore documentation.
@BYK BYK merged commit 49b8985 into main Apr 23, 2026
26 checks passed
@BYK BYK deleted the fix/n-plus-one-auth-query branch April 23, 2026 11:27
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