Skip to content

feat: add lookup cache for WhoCanAccess/WhatCanTargetAccess#367

Merged
orius123 merged 9 commits intomainfrom
feat/lookup-cache
Feb 12, 2026
Merged

feat: add lookup cache for WhoCanAccess/WhatCanTargetAccess#367
orius123 merged 9 commits intomainfrom
feat/lookup-cache

Conversation

@orius123
Copy link
Member

@orius123 orius123 commented Feb 6, 2026

Related Issues

Related to https://github.com/descope/etc/issues/13966

Description

Add lookup cache with Candidate Filtering for WhoCanAccess and WhatCanTargetAccess.

How It Works

1. Lookup cache (TTL-based) → candidates (may be stale superset)
          ↓
2. Check cache (always fresh) → verify each candidate
          ↓
3. Filter false positives → accurate result

This gives cache speed with real-time accuracy:

  • Lookup cache uses TTL (entries may be stale)
  • Check cache is always fresh (invalidated on mutations)
  • We verify stale candidates against the fresh Check cache

Cache Invalidation Strategy

Scenario Lookup Cache Visibility
Direct relation added (local) Target ADDED to candidates Immediate
Direct relation deleted Unchanged (candidate filtering) Immediate
Indirect relation changed Unchanged (candidate filtering) Immediate
Remote relation change Unchanged (candidate filtering) Immediate
New indirect relation Unchanged Up to TTL
Schema change / errors PURGED Immediate

Key insight: We don't purge the entire lookup cache on relation changes. Instead:

  • Removed access → Handled immediately via candidate filtering (CheckRelation returns false)
  • New direct access (local) → Added to existing cache entries immediately
  • New indirect access → Visible after TTL (can't know which lookups are affected without re-traversing the graph)

Configuration (env vars)

Variable Default Description
AUTHZCACHE_LOOKUP_CACHE_ENABLED true Enable/disable lookup cache
AUTHZCACHE_LOOKUP_CACHE_SIZE_PER_PROJECT 10000 Max entries per project
AUTHZCACHE_LOOKUP_CACHE_TTL_IN_SECONDS 60 TTL for cache entries
AUTHZCACHE_LOOKUP_CACHE_MAX_RESULT_SIZE 1000 Skip caching results larger than this

Benefits

  • No stale results for removed access: Candidates are always verified against fresh Check cache
  • Fast path: Cache hit + all checks pass = no remote calls
  • Efficient invalidation: Only add to cache, don't purge unnecessarily
  • Graceful degradation: Cache miss falls back to remote SDK call

Note

Defaults are conservative. After collecting Datadog metrics from authzservice (PR #1747), tune based on:

  • Cache size → query cardinality
  • TTL → mutation rate
  • Max result size → P99 result sizes

Must

  • Tests pass
  • Build passes

Add TTL-based lookup cache for authorization lookup queries.

Config (env vars):
- AUTHZCACHE_LOOKUP_CACHE_ENABLED (default: true)
- AUTHZCACHE_LOOKUP_CACHE_SIZE_PER_PROJECT (default: 10000)
- AUTHZCACHE_LOOKUP_CACHE_TTL_IN_SECONDS (default: 60)
- AUTHZCACHE_LOOKUP_CACHE_MAX_RESULT_SIZE (default: 1000)

The lookup cache is invalidated:
- On schema changes (full purge)
- On relation mutations (full purge)
- When TTL expires (per-entry)

Large results (> max_result_size) are not cached to prevent memory issues.
Copilot AI review requested due to automatic review settings February 6, 2026 13:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a TTL-based lookup cache for authorization lookup queries (WhoCanAccess and WhatCanTargetAccess) with configurable settings and cache invalidation strategies.

Changes:

  • Introduces a new lookup cache with TTL-based expiration for authorization queries
  • Adds configuration parameters for cache size, TTL, and result size limits
  • Implements cache invalidation on schema/relation changes

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/services/caches/projectauthzcache_mock.go Adds mock methods for lookup cache operations
internal/services/caches/projectauthzcache.go Implements lookup cache with TTL tracking and cache key generation
internal/services/authz_mock.go Adds mock methods for WhoCanAccess and WhatCanTargetAccess
internal/services/authz.go Integrates lookup cache into authorization service methods
internal/config/config.go Adds configuration functions for lookup cache settings

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Instead of returning stale cached results directly, we now:
1. Get cached lookup results as 'candidates' (may be stale)
2. Verify each candidate using the Check cache (always fresh)
3. Filter out candidates that fail the check

This ensures lookup results are always accurate while still
benefiting from the lookup cache for the candidate superset.

Also adds comprehensive tests for:
- WhoCanAccess cache miss/hit with filtering
- WhatCanTargetAccess cache miss/hit with filtering
- Stale candidate filtering behavior
Skip malformed entries instead of leaving nil in the slice.
Return cache miss if all entries are malformed.
When relations are added or deleted via UpdateCacheWithAddedRelations
or UpdateCacheWithDeletedRelations, the lookup cache (WhoCanAccess/
WhatCanTargetAccess) must also be purged to prevent stale results.

Previously, only the indirectRelationCache was purged, which could
lead to incorrect lookup results until TTL expiration.
When remote polling detects relation changes (resources or targets
modified), the lookup cache must also be purged to ensure new
candidates are visible.

Without this fix, new relations added remotely would not appear
in WhoCanAccess/WhatCanTargetAccess results until TTL expiration.
Instead of purging the entire lookup cache on relation changes:
- Direct relation added: Add target to existing WhoCanAccess/WhatCanTargetAccess
  cache entries (if present)
- Direct relation deleted: Rely on candidate filtering (CheckRelation returns
  false for deleted relations)
- Remote relation changes: Rely on candidate filtering

This avoids invalidating all cached lookups when only specific entries are
affected. Removed access is handled immediately via candidate filtering,
while new access is visible immediately for direct relations added locally.

Added comprehensive tests for direct/indirect relation removal filtering.
@yosiharan yosiharan self-requested a review February 12, 2026 12:54
Copy link
Contributor

@yosiharan yosiharan left a comment

Choose a reason for hiding this comment

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

Great work, excited to see the results!
Just had a couple of suggestions to reduce code repetition, nothing critical, lmk what you think 🙏

Address CR feedback from yosiharan:
- Remove redundant projectCache and mgmtSDK parameters from filter methods
- Replace checkRelationsWithCache with direct calls to existing Check method
- Delete duplicate checkRelationsWithCache method (~30 lines removed)
@orius123 orius123 requested a review from yosiharan February 12, 2026 17:14
@orius123 orius123 enabled auto-merge (squash) February 12, 2026 17:15
Copy link
Contributor

@yosiharan yosiharan left a comment

Choose a reason for hiding this comment

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

🚀🚀🚀

@orius123 orius123 merged commit 56bf34a into main Feb 12, 2026
14 checks passed
@orius123 orius123 deleted the feat/lookup-cache branch February 12, 2026 17:19
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.

3 participants