Skip to content

Conversation

@Akhileshait
Copy link
Contributor

@Akhileshait Akhileshait commented Oct 29, 2025

What does this PR do?

This PR adds caching for key lookups in the Go API to reduce database queries when using updateKey, updateCredits, and setRoles endpoints.

Previously, these handlers were querying the database directly every time they needed to fetch key information. Now they use a new LiveKeyByID cache with a Stale-While-Revalidate (SWR) pattern that:

  • Returns fresh data immediately from cache (10-second freshness window)
  • Revalidates stale data in the background (10-minute staleness window)
  • Properly invalidates cache when keys are updated

This reduces database load and improves API response times for repeated key operations.

Fixes #4150

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Unit Tests

Run the existing unit tests for the modified handlers:

cd go
make test

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

@changeset-bot
Copy link

changeset-bot bot commented Oct 29, 2025

⚠️ No Changeset found

Latest commit: a991c68

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Oct 29, 2025

@Akhileshait is attempting to deploy a commit to the Unkey Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

📝 Walkthrough

Walkthrough

Adds a LiveKeyByID cache to the caches service and wires a new LiveKeyCache into multiple v2 key handlers and their tests; handlers now use SWR-backed lookups for live-key-by-id and invalidate LiveKeyCache (and KeyCache where applicable) after operations.

Changes

Cohort / File(s) Summary
Cache Service Initialization
go/internal/services/caches/caches.go
Add LiveKeyByID cache.Cache[string, db.FindLiveKeyByIDRow] to Caches; create liveKeyByID with Fresh:10s, Stale:10m, MaxSize:1_000_000; adjust some existing cache sizes and expose via tracing wrapper.
Route Registration
go/apps/api/routes/register.go
Wire LiveKeyCache: svc.Caches.LiveKeyByID into v2 keys route handlers during registration.
Handlers — SWR lookup + invalidation
go/apps/api/routes/v2_keys_*/handler.go
(reroll_key, delete_key, get_key, set_roles, set_permissions, add_permissions, add_roles, remove_permissions, remove_roles, update_key, update_credits)
Add public field LiveKeyCache cache.Cache[string, db.FindLiveKeyByIDRow]; replace direct FindLiveKeyByID DB calls with h.LiveKeyCache.SWR(ctx, keyId, loader, caches.DefaultFindFirstOp) and preserve not-found/error semantics; remove/evict LiveKeyCache.Remove(key.ID) after success; many also continue to evict KeyCache.
v2_keys_reroll_key — extra cache
go/apps/api/routes/v2_keys_reroll_key/handler.go
Add KeyCache and LiveKeyCache fields; use SWR for live-key lookup; invalidate KeyCache (by hash) and LiveKeyCache (by id) after success.
Tests — Handler wiring updates
go/apps/api/routes/**/_test.go (many files across v2_keys_*)
Update test Handler composite literals to provide LiveKeyCache: h.Caches.LiveKeyByID (and KeyCache where applicable) across updated tests to match new Handler fields.

Sequence Diagram(s)

sequenceDiagram
    rect rgb(250,250,255)
    participant Client
    participant Handler
    participant LiveKeyCache
    participant DB
    participant Tx as Transaction
    end

    Client->>Handler: request (keyId, action)
    Handler->>LiveKeyCache: SWR(ctx, keyId, loader, DefaultFindFirstOp)
    alt Cache hit
        LiveKeyCache-->>Handler: cached key
    else Cache miss / stale
        LiveKeyCache->>DB: FindLiveKeyByID(keyId)
        DB-->>LiveKeyCache: key / not-found / error
        LiveKeyCache-->>Handler: key / result
    end

    Handler->>Tx: perform DB changes
    alt Success
        Tx-->>Handler: commit
        Handler->>LiveKeyCache: Remove(keyId)
        Handler->>Handler: invalidate KeyCache by key.Hash (if present)
        Handler-->>Client: success response
    else Failure
        Tx-->>Handler: rollback
        Handler-->>Client: error response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to SWR loader semantics and returning/caching of not-found vs error across handlers.
  • Verify correct eviction keys and ordering (pre/post-transaction comments in some handlers).
  • Confirm all tests and route registration updated to compile with new LiveKeyCache field.

Possibly related PRs

Suggested labels

Bug, Core Team

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes adding caching for key endpoints with ID lookup, directly matching the main changeset objective.
Description check ✅ Passed Description comprehensively covers objectives, testing instructions, and checklist completion following the template structure.
Linked Issues check ✅ Passed All coding requirements from issue #4150 are met: LiveKeyByID cache created in caches.go, integrated into all v2_keys handlers via SWR pattern, wired in route registration and tests.
Out of Scope Changes check ✅ Passed All changes directly support LiveKeyByID cache implementation. Minor adjustments to cache configurations (keyAuthToApiRow/apiToKeyAuthRow) relate to cache optimization and are within scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5e915c and a991c68.

📒 Files selected for processing (1)
  • go/apps/api/routes/register.go (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.743Z
Learning: PR #4190 for unkeyed/unkey is focused solely on database schema and query changes for identity-based credits. It adds IdentityCredits and KeyCredits fields to structs and queries, but does not implement the priority enforcement logic in the usagelimiter. The logic implementation is intentionally deferred to a later PR in the stack.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/workflows/runbook-freshness-check.yaml:157-173
Timestamp: 2025-08-08T14:59:52.283Z
Learning: Repo unkeyed/unkey: When a CI/workflow fix is deferred, imeyer prefers a thorough GitHub issue be opened with sections (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References) and assigned to imeyer, including backlinks to the originating PR and comment.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: For repo unkeyed/unkey and PR review workflows: When imeyer comments "issue" on a thread, automatically create a thorough GitHub issue (sections: Summary, Impact, Where, Repro/Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and the specific comment, and assign the issue to imeyer.
🧬 Code graph analysis (1)
go/apps/api/routes/register.go (8)
go/internal/services/caches/caches.go (1)
  • Caches (21-53)
go/apps/api/routes/v2_keys_add_permissions/handler.go (1)
  • Handler (29-36)
go/apps/api/routes/v2_keys_get_key/handler.go (1)
  • Handler (30-37)
go/apps/api/routes/v2_keys_update_credits/handler.go (1)
  • Handler (29-37)
go/apps/api/routes/v2_keys_set_roles/handler.go (1)
  • Handler (29-36)
go/apps/api/routes/v2_keys_reroll_key/handler.go (1)
  • Handler (34-42)
go/apps/api/routes/v2_keys_update_key/handler.go (1)
  • Handler (34-42)
go/apps/api/routes/v2_apis_delete_api/handler.go (1)
  • Handler (30-37)
🔇 Additional comments (1)
go/apps/api/routes/register.go (1)

422-429: LGTM: Consistent LiveKeyCache wiring across handlers.

The LiveKeyCache dependency is correctly wired to all modified v2/keys handlers. The dependency injection pattern is consistent and aligns with the existing KeyCache wiring.

Also applies to: 436-442, 454-454, 463-469, 493-493, 502-507, 515-520, 528-533, 541-546, 554-559, 567-572


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 29, 2025

Thank you for following the naming conventions for pull request titles! 🙏

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
go/apps/api/routes/v2_keys_update_key/200_test.go (1)

211-218: Add missing LiveKeyCache field for consistency.

The Handler initialization in TestKeyUpdateCreditsInvalidatesCache is missing the LiveKeyCache field that's present in the other two tests in this file (lines 35 and 139). This inconsistency could cause issues if the handler expects this field to be set.

Apply this diff to add the missing field:

 route := &handler.Handler{
 	Logger:       h.Logger,
 	DB:           h.DB,
 	Keys:         h.Keys,
 	Auditlogs:    h.Auditlogs,
 	KeyCache:     h.Caches.VerificationKeyByHash,
+	LiveKeyCache: h.Caches.LiveKeyByID,
 	UsageLimiter: h.UsageLimiter,
 }
go/apps/api/routes/v2_keys_update_key/403_test.go (1)

37-44: Critical: Missing LiveKeyCache initialization will cause nil pointer dereference.

The handler initialization is missing the LiveKeyCache field, but the handler code now requires it (see handler.go line 68 where h.LiveKeyCache.SWR is called). This will cause a panic when the test runs.

Apply this diff to fix:

 route := &handler.Handler{
 	DB:           h.DB,
 	Keys:         h.Keys,
 	Logger:       h.Logger,
 	Auditlogs:    h.Auditlogs,
 	KeyCache:     h.Caches.VerificationKeyByHash,
+	LiveKeyCache: h.Caches.LiveKeyByID,
 	UsageLimiter: h.UsageLimiter,
 }
🧹 Nitpick comments (3)
go/internal/services/caches/caches.go (1)

108-118: Right TTLs; consider sizing and memory footprint

Fresh/stale windows align with SWR goals. MaxSize=1,000,000 with a large value type (db.FindLiveKeyByIDRow) can be memory‑intensive; expose this via config/env and add a rough size budget alert to avoid OOM under churn.

go/apps/api/routes/v2_keys_update_credits/handler.go (2)

280-282: Post‑update: write‑through updated row to cache

Eviction is good; also Set the fresh row to minimize stale windows and reduce immediate re‑loads.

  h.KeyCache.Remove(ctx, key.Hash)
- h.LiveKeyCache.Remove(ctx, key.ID)
+ // Replace with fresh value to avoid stale re-population and shorten warmup.
+ h.LiveKeyCache.Set(ctx, key.ID, key)

65-75: Avoid negative caching on write paths

Caching 404s (WriteNull) can cause transient "not found" responses for keys created shortly before an update. Prefer Noop for not‑found to skip negative caching here.

Apply this diff:

-		if db.IsNotFound(err) {
-			return cache.WriteNull
-		}
+		if db.IsNotFound(err) {
+			// Skip negative caching on write path to avoid transient 404s post-create.
+			return cache.Noop
+		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce0505d and 02adfcd.

📒 Files selected for processing (21)
  • go/apps/api/routes/register.go (3 hunks)
  • go/apps/api/routes/v2_keys_set_roles/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/handler.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_credits/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/handler.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/200_test.go (2 hunks)
  • go/apps/api/routes/v2_keys_update_key/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/403_test.go (2 hunks)
  • go/apps/api/routes/v2_keys_update_key/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/handler.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/three_state_test.go (1 hunks)
  • go/internal/services/caches/caches.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (21)
go/apps/api/routes/v2_keys_update_key/handler.go (3)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
  • FindLiveKeyByIDRow (105-141)
go/pkg/cache/interface.go (3)
  • Op (51-51)
  • WriteValue (57-57)
  • WriteNull (60-60)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/apps/api/routes/v2_keys_update_key/three_state_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_key/404_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_credits/404_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_credits/403_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_set_roles/handler.go (4)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
  • FindLiveKeyByIDRow (105-141)
go/pkg/cache/interface.go (3)
  • Op (51-51)
  • WriteValue (57-57)
  • WriteNull (60-60)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/apps/api/routes/v2_keys_set_roles/403_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_set_roles/401_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_key/401_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_credits/400_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_credits/200_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_set_roles/200_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_key/200_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/internal/services/caches/caches.go (5)
go/pkg/cache/interface.go (1)
  • Cache (7-32)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
  • FindLiveKeyByIDRow (105-141)
go/apps/gw/services/caches/caches.go (3)
  • New (74-142)
  • Config (38-43)
  • Caches (20-35)
go/pkg/cache/cache.go (2)
  • New (55-99)
  • Config (33-50)
go/pkg/cache/middleware/tracing.go (1)
  • WithTracing (16-18)
go/apps/api/routes/v2_keys_set_roles/404_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_set_roles/400_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_credits/401_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_key/400_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/register.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_credits/handler.go (3)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
  • FindLiveKeyByIDRow (105-141)
go/pkg/cache/interface.go (3)
  • Op (51-51)
  • WriteValue (57-57)
  • WriteNull (60-60)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/apps/api/routes/v2_keys_update_key/403_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
🔇 Additional comments (30)
go/internal/services/caches/caches.go (2)

28-30: LGTM: new LiveKeyByID cache handle exposed

Field addition is clear and typed correctly for SWR lookups by key ID.


124-124: LGTM: tracing middleware applied

Wiring through middleware.WithTracing mirrors other caches.

go/apps/api/routes/v2_keys_update_credits/handler.go (1)

35-35: LGTM: LiveKeyCache dependency added

Keeps handler testable and consistent with other routes.

go/apps/api/routes/v2_keys_update_credits/404_test.go (1)

24-25: LGTM: tests wire LiveKeyCache

Ensures handler has the new dependency in not‑found scenario.

go/apps/api/routes/v2_keys_set_roles/200_test.go (1)

24-30: LGTM: LiveKeyCache wired into handler

Consistent with service changes; tests remain readable.

go/apps/api/routes/v2_keys_update_credits/400_test.go (1)

25-26: LGTM: LiveKeyCache in bad‑request tests

Keeps test setup aligned with handler signature.

go/apps/api/routes/v2_keys_update_key/three_state_test.go (1)

36-36: LGTM! Proper cache dependency wiring.

The LiveKeyCache field is correctly initialized with the LiveKeyByID cache from the test harness, consistent with the new caching pattern introduced in this PR.

go/apps/api/routes/v2_keys_update_credits/403_test.go (1)

26-26: LGTM! Cache dependency properly initialized.

The LiveKeyCache field is correctly wired to h.Caches.LiveKeyByID, maintaining consistency with other test files in this PR.

go/apps/api/routes/v2_keys_update_credits/200_test.go (1)

31-31: LGTM! Proper cache wiring for credits test.

The LiveKeyCache is correctly initialized, and the test at lines 172-218 properly validates cache invalidation after credit updates.

go/apps/api/routes/v2_keys_update_key/200_test.go (2)

35-35: LGTM! Cache dependency properly initialized.

The LiveKeyCache field is correctly wired for this test.


139-139: LGTM! Consistent cache initialization.

The LiveKeyCache field is properly set up for this test case.

go/apps/api/routes/v2_keys_set_roles/404_test.go (1)

31-31: LGTM! Cache dependency correctly initialized.

The LiveKeyCache field is properly wired to h.Caches.LiveKeyByID, maintaining consistency with the caching pattern in this PR.

go/apps/api/routes/v2_keys_update_key/404_test.go (1)

27-27: LGTM! Proper cache wiring.

The LiveKeyCache field is correctly initialized for the not-found test scenarios.

go/apps/api/routes/v2_keys_update_key/400_test.go (1)

28-28: LGTM! Cache dependency properly set.

The LiveKeyCache is correctly initialized for the invalid refill config test.

go/apps/api/routes/v2_keys_set_roles/403_test.go (1)

24-24: LGTM! Cache dependency correctly wired.

The LiveKeyCache field is properly initialized, completing the Handler setup for authorization error tests.

go/apps/api/routes/v2_keys_set_roles/401_test.go (1)

17-23: LGTM!

The test setup correctly initializes the new LiveKeyCache field, ensuring the handler can use the SWR-based live key lookup pattern introduced in this PR.

go/apps/api/routes/v2_keys_update_credits/401_test.go (1)

19-27: LGTM!

The handler initialization correctly includes the LiveKeyCache field, allowing the test to exercise the new caching behavior.

go/apps/api/routes/v2_keys_update_key/403_test.go (2)

90-98: LGTM!

The handler correctly initializes LiveKeyCache, ensuring the test can use the new SWR-based key lookup.


137-145: LGTM!

The handler is properly configured with the LiveKeyCache field.

go/apps/api/routes/v2_keys_update_key/401_test.go (1)

20-28: LGTM!

The test setup correctly includes the LiveKeyCache field for the handler.

go/apps/api/routes/v2_keys_set_roles/400_test.go (1)

17-24: LGTM!

The handler is correctly initialized with the new LiveKeyCache field.

go/apps/api/routes/register.go (3)

420-431: LGTM!

The v2KeysUpdateKey handler is correctly wired with the new LiveKeyCache, enabling SWR-based key lookups to reduce database load.


458-469: LGTM!

The v2KeysUpdateCredits handler is correctly configured with the LiveKeyCache.


472-482: LGTM!

The v2KeysSetRoles handler is properly configured with the LiveKeyCache field, completing the caching implementation for all three key endpoints.

go/apps/api/routes/v2_keys_set_roles/handler.go (3)

28-35: LGTM!

The LiveKeyCache field is properly added to the handler, enabling SWR-based caching for live key lookups.


62-84: LGTM!

The SWR cache pattern is correctly implemented:

  • Caches successful lookups to reduce DB load.
  • Caches not-found results to prevent repeated lookups for non-existent keys.
  • Doesn't cache transient errors, allowing retry on next request.
  • Preserves existing error handling semantics.

278-279: LGTM!

Cache invalidation is correctly implemented:

  • Both KeyCache (by hash) and LiveKeyCache (by ID) are invalidated after successful role updates.
  • Proper placement after transaction success ensures cache consistency.
go/apps/api/routes/v2_keys_update_key/handler.go (3)

33-41: LGTM!

The LiveKeyCache field is correctly added to enable SWR-based key lookups.


68-94: LGTM!

The SWR cache pattern is correctly implemented with appropriate error handling:

  • Successful lookups and not-found results are cached.
  • Transient errors are not cached.
  • Existing error semantics are preserved.

640-649: LGTM!

Cache invalidation is correctly implemented:

  • Both KeyCache and LiveKeyCache are invalidated after successful key updates.
  • UsageLimiter is additionally invalidated when credits are updated.
  • Proper placement ensures cache consistency.

Copy link
Member

@Flo4604 Flo4604 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are not passing, and there are other endpoints that should also use this cache.
12handlers in total that use FindLiveKeyByID

And also please use the caches.DefaultFindFirstOp instead of having to re-use the same cache operation function.

@Akhileshait Akhileshait requested a review from Flo4604 October 30, 2025 07:12
@Flo4604
Copy link
Member

Flo4604 commented Oct 30, 2025

@Akhileshait some tests are still not passing, you can run make test locally to run the test suite on your machine

@Akhileshait
Copy link
Contributor Author

@Flo4604 Fixed the issue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0495a75 and 1ccbf61.

📒 Files selected for processing (5)
  • go/apps/api/routes/v2_keys_delete_key/handler.go (4 hunks)
  • go/apps/api/routes/v2_keys_get_key/412_test.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/200_test.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/403_test.go (3 hunks)
  • go/pkg/zen/validation/validator.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • go/apps/api/routes/v2_keys_update_key/403_test.go
  • go/apps/api/routes/v2_keys_update_key/200_test.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/runbook-freshness-check.yaml:157-173
Timestamp: 2025-08-08T14:59:52.283Z
Learning: Repo unkeyed/unkey: When a CI/workflow fix is deferred, imeyer prefers a thorough GitHub issue be opened with sections (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References) and assigned to imeyer, including backlinks to the originating PR and comment.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: For repo unkeyed/unkey and PR review workflows: When imeyer comments "issue" on a thread, automatically create a thorough GitHub issue (sections: Summary, Impact, Where, Repro/Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and the specific comment, and assign the issue to imeyer.
📚 Learning: 2025-08-27T11:02:17.155Z
Learnt from: chronark
PR: unkeyed/unkey#3860
File: go/pkg/zen/middleware_metrics_test.go:297-313
Timestamp: 2025-08-27T11:02:17.155Z
Learning: The redact function in go/pkg/zen/middleware_metrics.go is not expected to be called concurrently, so concurrency tests are unnecessary for this function.

Applied to files:

  • go/pkg/zen/validation/validator.go
🧬 Code graph analysis (1)
go/apps/api/routes/v2_keys_delete_key/handler.go (3)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
  • FindLiveKeyByIDRow (105-141)
go/internal/services/caches/op.go (1)
  • DefaultFindFirstOp (9-22)
🔇 Additional comments (4)
go/apps/api/routes/v2_keys_get_key/412_test.go (1)

19-25: LGTM! Cache dependency properly wired into all test cases.

All three Handler initializations consistently include the new LiveKeyCache field, correctly sourced from h.Caches.LiveKeyByID. The tests maintain their focus on verifying precondition error scenarios while properly accommodating the new cache dependency.

Also applies to: 90-97, 142-149

go/pkg/zen/validation/validator.go (3)

6-6: LGTM: Import is necessary.

The sync import is correctly added to support the mutex field.


27-29: Mutex field is properly documented, but appears unrelated to PR objectives.

The field and documentation are correct. However, this concurrency fix for the OpenAPI validator seems unrelated to the PR's stated goal of adding caching for key lookup endpoints. Consider whether this should be in a separate PR for better traceability.


66-70: Rewrite review comment with corrected version information and verified findings.

The review comment references libopenapi-validator v0.9.0, which does not exist—the latest version is v0.8.0 (released Oct 22, 2025), and your codebase uses v0.4.6. Additionally, I found no public documentation of a race condition in ValidateHttpRequest across the library's changelog or issues.

More notably, two other validator implementations in your codebase (go/apps/gw/server/validation/ and go/apps/agent/pkg/openapi/) call the same ValidateHttpRequest method on the same library version without mutexes. This inconsistency suggests either the race condition is specific to this context, overly cautious, or outdated.

The defer pattern suggestion is still valid for safety:

 	// Lock to protect concurrent access to the validator
 	// This works around race conditions in libopenapi-validator v0.9.0 and earlier
 	v.mu.Lock()
+	defer v.mu.Unlock()
 	valid, errors := v.validator.ValidateHttpRequest(r)
-	v.mu.Unlock()

Verify with the team: Is the mutex addressing a known production issue, or can it be removed given that other validators don't use it?

@Akhileshait
Copy link
Contributor Author

Hi @Flo4604 Tests are passing now please review this

@Flo4604
Copy link
Member

Flo4604 commented Nov 1, 2025

@Akhileshait we will review this next week

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
go/apps/api/routes/v2_keys_delete_key/200_test.go (1)

24-31: Handler initialization looks correct.

The LiveKeyCache is properly wired to the handler alongside existing fields. However, consider verifying cache invalidation after the delete operations in your test assertions. Since this PR introduces caching for key lookups, the tests should confirm that both KeyCache and LiveKeyCache are properly invalidated when keys are deleted (soft or hard delete).

For example, after the deletion operations, you could add assertions like:

// After soft delete
_, found := route.LiveKeyCache.Get(softDeleteKeyID)
require.False(t, found, "LiveKeyCache should not contain deleted key")

// After hard delete  
_, found = route.LiveKeyCache.Get(hardDeleteKeyID)
require.False(t, found, "LiveKeyCache should not contain permanently deleted key")
go/apps/api/routes/v2_keys_set_roles/200_test.go (1)

23-30: LGTM! Handler initialization correctly includes the new cache.

The LiveKeyCache field is properly initialized with h.Caches.LiveKeyByID, consistent with the other cache dependencies.

As an optional enhancement, consider adding assertions to verify the cache is being used and invalidated correctly, since this PR specifically introduces caching to reduce database load. For example:

  • Mock or instrument the cache to verify lookups occur before DB queries
  • Verify cache invalidation happens after role updates
  • Check that subsequent operations use cached data (within the freshness window)

This would provide stronger confidence that the caching feature works as intended, though behavioral correctness is already well-covered by the existing assertions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41adb66 and 8dc1bf5.

📒 Files selected for processing (51)
  • go/apps/api/routes/v2_keys_add_permissions/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_permissions/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_permissions/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_permissions/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_get_key/200_test.go (2 hunks)
  • go/apps/api/routes/v2_keys_get_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_get_key/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_get_key/412_test.go (3 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_reroll_key/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_reroll_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_reroll_key/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_reroll_key/handler.go (4 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/200_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/403_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/handler.go (4 hunks)
  • go/apps/api/routes/v2_keys_update_key/200_test.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/400_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/401_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/403_test.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/404_test.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/three_state_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (35)
  • go/apps/api/routes/v2_keys_remove_permissions/400_test.go
  • go/apps/api/routes/v2_keys_update_credits/200_test.go
  • go/apps/api/routes/v2_keys_delete_key/403_test.go
  • go/apps/api/routes/v2_keys_update_key/404_test.go
  • go/apps/api/routes/v2_keys_add_roles/200_test.go
  • go/apps/api/routes/v2_keys_update_key/200_test.go
  • go/apps/api/routes/v2_keys_remove_permissions/200_test.go
  • go/apps/api/routes/v2_keys_set_permissions/200_test.go
  • go/apps/api/routes/v2_keys_remove_roles/200_test.go
  • go/apps/api/routes/v2_keys_set_permissions/403_test.go
  • go/apps/api/routes/v2_keys_update_key/three_state_test.go
  • go/apps/api/routes/v2_keys_add_permissions/200_test.go
  • go/apps/api/routes/v2_keys_update_credits/handler.go
  • go/apps/api/routes/v2_keys_update_key/400_test.go
  • go/apps/api/routes/v2_keys_update_key/401_test.go
  • go/apps/api/routes/v2_keys_reroll_key/401_test.go
  • go/apps/api/routes/v2_keys_remove_roles/401_test.go
  • go/apps/api/routes/v2_keys_add_permissions/400_test.go
  • go/apps/api/routes/v2_keys_remove_roles/400_test.go
  • go/apps/api/routes/v2_keys_add_permissions/404_test.go
  • go/apps/api/routes/v2_keys_set_roles/404_test.go
  • go/apps/api/routes/v2_keys_set_permissions/400_test.go
  • go/apps/api/routes/v2_keys_remove_permissions/403_test.go
  • go/apps/api/routes/v2_keys_set_roles/403_test.go
  • go/apps/api/routes/v2_keys_add_roles/400_test.go
  • go/apps/api/routes/v2_keys_add_roles/403_test.go
  • go/apps/api/routes/v2_keys_reroll_key/403_test.go
  • go/apps/api/routes/v2_keys_add_roles/404_test.go
  • go/apps/api/routes/v2_keys_set_permissions/401_test.go
  • go/apps/api/routes/v2_keys_get_key/200_test.go
  • go/apps/api/routes/v2_keys_update_credits/400_test.go
  • go/apps/api/routes/v2_keys_remove_roles/404_test.go
  • go/apps/api/routes/v2_keys_add_permissions/403_test.go
  • go/apps/api/routes/v2_keys_delete_key/404_test.go
  • go/apps/api/routes/v2_keys_update_credits/401_test.go
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.710Z
Learning: PR #4190 for unkeyed/unkey is focused solely on database schema and query changes for identity-based credits. It adds IdentityCredits and KeyCredits fields to structs and queries, but does not implement the priority enforcement logic in the usagelimiter. The logic implementation is intentionally deferred to a later PR in the stack.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/workflows/runbook-freshness-check.yaml:157-173
Timestamp: 2025-08-08T14:59:52.283Z
Learning: Repo unkeyed/unkey: When a CI/workflow fix is deferred, imeyer prefers a thorough GitHub issue be opened with sections (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References) and assigned to imeyer, including backlinks to the originating PR and comment.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: For repo unkeyed/unkey and PR review workflows: When imeyer comments "issue" on a thread, automatically create a thorough GitHub issue (sections: Summary, Impact, Where, Repro/Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and the specific comment, and assign the issue to imeyer.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
📚 Learning: 2025-10-30T15:10:52.710Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.710Z
Learning: PR #4190 for unkeyed/unkey is focused solely on database schema and query changes for identity-based credits. It adds IdentityCredits and KeyCredits fields to structs and queries, but does not implement the priority enforcement logic in the usagelimiter. The logic implementation is intentionally deferred to a later PR in the stack.

Applied to files:

  • go/apps/api/routes/v2_keys_update_credits/403_test.go
  • go/apps/api/routes/v2_keys_add_permissions/401_test.go
📚 Learning: 2025-08-14T16:25:48.167Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3785
File: go/apps/api/routes/v2_keys_reroll_key/401_test.go:52-61
Timestamp: 2025-08-14T16:25:48.167Z
Learning: User Flo4604 requested creation of a GitHub issue to track converting all test files to use table-driven test patterns as a broader codebase improvement, following the suggestion made during review of go/apps/api/routes/v2_keys_reroll_key/401_test.go.

Applied to files:

  • go/apps/api/routes/v2_keys_update_key/403_test.go
  • go/apps/api/routes/v2_keys_remove_permissions/401_test.go
  • go/apps/api/routes/v2_keys_set_permissions/404_test.go
  • go/apps/api/routes/v2_keys_add_permissions/401_test.go
  • go/apps/api/routes/v2_keys_add_roles/401_test.go
  • go/apps/api/routes/v2_keys_get_key/404_test.go
📚 Learning: 2025-08-08T15:09:01.312Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3753
File: go/apps/api/openapi/config.yaml:9-10
Timestamp: 2025-08-08T15:09:01.312Z
Learning: Repo unkeyed/unkey: For go/apps/api/openapi, oapi-codegen is used and does not support OpenAPI 3.1 union types like [T, "null"]; an overlay step is required to downconvert to 3.0-style nullable before code generation.

Applied to files:

  • go/apps/api/routes/v2_keys_reroll_key/handler.go
🧬 Code graph analysis (4)
go/apps/api/routes/v2_keys_update_credits/403_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_update_key/403_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
go/apps/api/routes/v2_keys_reroll_key/handler.go (3)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
  • FindLiveKeyByIDRow (105-141)
go/internal/services/caches/op.go (1)
  • DefaultFindFirstOp (9-22)
go/apps/api/routes/v2_keys_set_roles/200_test.go (1)
go/internal/services/caches/caches.go (1)
  • Caches (15-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
🔇 Additional comments (18)
go/apps/api/routes/v2_keys_update_credits/403_test.go (1)

26-26: LGTM! Cache wiring is correct.

The addition of LiveKeyCache correctly wires the new LiveKeyByID cache into the handler for testing. This enables the handler to use SWR-backed key lookups and follows the consistent pattern across the PR.

go/apps/api/routes/v2_keys_remove_roles/403_test.go (1)

23-30: LGTM! LiveKeyCache correctly wired into handler initialization.

The addition of LiveKeyCache: h.Caches.LiveKeyByID follows the consistent pattern established by the existing KeyCache field and aligns with the PR's objective to integrate caching for key lookups across handlers.

go/apps/api/routes/v2_keys_set_permissions/404_test.go (1)

23-30: LGTM! Cache dependency correctly wired.

The addition of LiveKeyCache to the handler initialization properly integrates the new LiveKeyByID cache. The test continues to validate 404 scenarios correctly, and the minimal change aligns with the PR's objective to add caching infrastructure for key lookups.

go/apps/api/routes/v2_keys_add_permissions/401_test.go (1)

24-29: LGTM! Cache dependency correctly wired.

The addition of LiveKeyCache: h.Caches.LiveKeyByID properly wires the new cache dependency into the handler initialization. This is consistent with the PR's objective to add caching for key lookups and follows the same pattern applied across other test files.

go/apps/api/routes/v2_keys_add_roles/401_test.go (1)

18-25: LGTM! Handler initialization correctly wired with LiveKeyCache.

The addition of LiveKeyCache: h.Caches.LiveKeyByID properly wires the new cache into the test Handler, consistent with the PR's objective to enable key lookups by ID across all route handlers.

go/apps/api/routes/v2_keys_delete_key/401_test.go (1)

22-29: Handler initialization looks correct.

The LiveKeyCache is properly wired alongside existing fields. All required dependencies are present and correctly initialized for the unauthorized access test scenarios.

go/apps/api/routes/v2_keys_update_key/403_test.go (2)

37-45: LGTM: LiveKeyCache wiring is correct.

The addition of LiveKeyCache: h.Caches.LiveKeyByID correctly wires the new cache dependency into the handler for testing, consistent with the handler's updated structure.


138-146: Cache behavior verification confirmed.

Cache invalidation is properly tested in 200_test.go (lines 272-281), which verifies that after a successful key update, the cache is invalidated by retrieving the key again and confirming the updated value is reflected. This is the correct location for such tests since cache invalidation only occurs in success paths. The 403_test.go tests authorization failures, which never reach the cache removal code, so it's appropriate that they don't test cache behavior.

go/apps/api/routes/v2_keys_reroll_key/handler.go (4)

14-14: LGTM: Cache imports added correctly.

The imports for the caching packages are properly added to support the new caching functionality.

Also applies to: 18-18


40-41: LGTM: Cache fields added to Handler.

The KeyCache and LiveKeyCache fields are correctly typed and align with the caching strategy described in the PR objectives. KeyCache is used for verification key lookups by hash, while LiveKeyCache is used for live key lookups by ID.


70-72: LGTM: SWR caching pattern correctly implemented.

The SWR (Stale-While-Revalidate) pattern is correctly implemented with:

  • Cache key based on req.KeyId
  • Loader function performing the database query
  • DefaultFindFirstOp determining cache behavior based on query result

This reduces database load for repeated key lookups while maintaining consistency.


371-372: LGTM: Cache invalidation properly implemented.

Cache invalidation correctly targets the old key after the successful reroll operation:

  • KeyCache.Remove(ctx, key.Hash): Ensures verification endpoints see the expired key
  • LiveKeyCache.Remove(ctx, key.ID): Ensures lookup endpoints see the updated key state

The invalidation order and timing are appropriate. Note that KeyCache is invalidated even though this handler doesn't read from it, which is correct because other handlers (e.g., verification endpoints) may have cached the key by hash.

go/apps/api/routes/v2_keys_reroll_key/200_test.go (1)

24-30: LGTM: Test handler initialization correctly updated.

The Handler initialization properly wires the cache instances from the test harness:

  • KeyCacheh.Caches.VerificationKeyByHash
  • LiveKeyCacheh.Caches.LiveKeyByID

This ensures the handler can function with the new caching functionality during tests.

go/apps/api/routes/v2_keys_remove_permissions/404_test.go (1)

26-33: LGTM! Correct wiring of the LiveKeyCache dependency.

The Handler initialization properly adds the new LiveKeyCache field alongside existing dependencies, using h.Caches.LiveKeyByID from the test harness. This correctly supports the PR's caching objective for key lookups.

go/apps/api/routes/v2_keys_remove_permissions/401_test.go (1)

18-25: LGTM! Consistent dependency injection.

The Handler initialization correctly adds the LiveKeyCache field, matching the pattern used across other test files in this PR. The authentication error tests remain unchanged while properly supporting the new cache dependency.

go/apps/api/routes/v2_keys_get_key/404_test.go (1)

20-27: LGTM! Cache dependency correctly wired.

The LiveKeyCache field is properly initialized from the test harness, enabling the handler to use the new SWR-backed live key cache.

go/apps/api/routes/v2_keys_get_key/412_test.go (1)

19-25: LGTM! Cache dependency consistently wired across all test cases.

All three Handler initializations correctly add LiveKeyCache from the test harness, maintaining consistency across the precondition error test scenarios.

Also applies to: 90-97, 142-149

go/apps/api/routes/v2_keys_get_key/403_test.go (1)

25-32: LGTM! Cache dependency correctly wired.

The LiveKeyCache initialization is consistent with the broader pattern across v2_keys_* route tests and properly enables cache usage in the handler.

@Flo4604
Copy link
Member

Flo4604 commented Nov 10, 2025

Hello @Akhileshait some merge conflicts popped up, could you please resolve them

Thank you

@Akhileshait
Copy link
Contributor Author

Hi @Flo4604 Fixed the merge conflicts

Copy link
Member

@Flo4604 Flo4604 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handlers are all missing their NULL handling
see go/apps/api/routes/v2_apis_get_api/handler.go for an example on how we handle it normally.

Comment on lines +61 to +63
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
return db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
}, caches.DefaultFindFirstOp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have to look at the _ which is the hit parameter.

Hit can be NULL which means a key doesn't exist

Comment on lines +62 to +64
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
return db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
}, caches.DefaultFindFirstOp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment on lines +64 to +66
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
return db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
}, caches.DefaultFindFirstOp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment on lines +63 to +65
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
return db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
}, caches.DefaultFindFirstOp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment on lines +62 to +64
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
return db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
}, caches.DefaultFindFirstOp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

key, err := db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
return db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
}, caches.DefaultFindFirstOp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too

}

key, err := db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too

}

key, err := db.Query.FindLiveKeyByID(ctx, h.DB.RO(), req.KeyId)
key, _, err := h.LiveKeyCache.SWR(ctx, req.KeyId, func(ctx context.Context) (db.FindLiveKeyByIDRow, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too

Comment on lines 294 to +295
cache.Config[cache.ScopedKey, db.FindKeyAuthsByKeyAuthIdsRow]{
Fresh: 10 * time.Minute,
Fresh: time.Minute,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a 10 miniute TTL is fine for keyAuth

dispatcher,
cache.Config[cache.ScopedKey, db.FindKeyAuthsByIdsRow]{
Fresh: 10 * time.Minute,
Fresh: time.Minute,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

@chronark chronark closed this Jan 13, 2026
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.

Add caching for key endpoints that lookup keys by id / whoami endpoint

3 participants