NestJS / Intermediate: policies, adapters and sources / Lesson 12
Cache identity, freshness and source budgets
Cache only a deliberate representation under the correct audience and bound the work needed to refill it.
On this page
Lesson goals
- Distinguish request memoization, shared caches and HTTP response caching.
- Define key scope, TTL units, invalidation and bounded fallback explicitly.
Name the cached contract and its authority
Nest caching integrations provide mechanisms, not an automatic freshness or authorization policy. A request memoization cache, shared entity cache and HTTP response cache have different identities and lifetimes. Choose whether a value is public, tenant-specific or principal-specific before selecting a key. A URL-only key can be unsafe for personalized responses that share a path. Keep authoritative permission decisions separate from stale descriptive content, and document what a missing entry means. Cache absence, source absence and source failure are different states; returning the same empty card for all three can conceal an outage or leak a policy error.
Record scope and units before installing a store
The JSON note declares a synthetic tenant-scoped card cache and an explicit millisecond TTL convention. Confirm the actual Nest/cache-manager/store version and unit behavior rather than copying numbers from older examples. Stores differ in serialization, expiry, errors and durability; an in-memory fallback is not equivalent to shared storage across workers. The record is not accepted configuration for a particular package. It also does not promise that cached publication state remains authoritative for access decisions. Keep version, audience and schema revision in the cache contract and review invalidation with every writer.
{"scope":"tenant-card","keyParts":["tenantId","courseId","schemaVersion"],"ttlUnit":"milliseconds-must-match-store","authority":"descriptive-cache-only","refill":"bounded-source-work","store":"not-configured-by-this-record"}
The record defines review assumptions. It creates no cache, sets no TTL and performs no source call.
Test audience identity independently of storage
The local function builds unambiguous keys from a synthetic array of components using JSON encoding. It distinguishes two tenants requesting the same course and separates schema versions. A key is coordination, not access control: the service must still validate the caller and source eligibility. Real stores can impose length and character constraints, so adapt the encoding deliberately. Do not include raw tokens or private personal values in keys that appear in logs and metrics. The assertions prove only this deterministic key convention, not cache isolation or expiry in a running backend.
function cardKey(tenant,id,version) {return JSON.stringify(["course-card",tenant,id,version]);}
console.assert(cardKey("lab-a","c1",1) !== cardKey("lab-b","c1",1));
console.assert(cardKey("lab-a","c1",1) !== cardKey("lab-a","c1",2));
console.assert(cardKey("lab-a","c1",1) === '["course-card","lab-a","c1",1]');
Tenant and schema changes produce different deterministic keys. No cache-manager or storage backend is used.
Review stale fills and failure meaning
Cache-aside invalidation can race with an older source read that repopulates stale data after a write. A shorter TTL limits duration under a policy but does not prove strong consistency. Consider source revisions or generation checks where the requirement justifies them. Do not negative-cache a source error as if the object were absent. After mutations, invalidate or update all relevant representations under a reviewed contract. A cache interceptor cannot know every semantic dependency unless the application supplies it. Test late fills and changed policy as explicit interleavings, not only a successful hit.
Bound misses, retries and source concurrency
A cache outage can send many requests to the authoritative source at once. Bound refill concurrency, request cardinality and source deadlines, and coalesce equivalent work where it preserves identity. Avoid a universal serve-stale rule for authorization-sensitive values. Propagate supported cancellation and choose one retry owner so several layers do not multiply attempts. Observe hit rate alongside stale results, source load and denied-access correctness. This course parses the policy and executes the key function only; it does not measure a cache engine, verify TTL timing or run a refill/load test against a service. Those need separate authorized evidence.
Common errors and fixes
Two users share a private cached response
- Why it happens
- The key omitted audience dimensions.
- How to fix it
- Include required scope or avoid shared caching for that representation.
- Prevent it next time
- Test the same path under different principals.
A source error becomes a long-lived empty result
- Why it happens
- Failure was negative-cached as legitimate absence.
- How to fix it
- Separate source failure and absent-object policies.
- Prevent it next time
- Test failure, absence and cache miss independently.
A TTL change keeps data far longer than intended
- Why it happens
- The storeโs time unit was assumed from an older integration.
- How to fix it
- Verify package/store unit semantics and convert explicitly.
- Prevent it next time
- Record units and expiry-boundary fixtures.
Check your understanding
1. Does adding tenantId to a cache key authorize access?
Show worked solution
No. It separates identity under the key convention. The service must still verify that the caller may use the requested tenant and object, and every producer/consumer must follow the same convention. Test denied scope and alternate code paths; a well-formed key alone cannot establish isolation in the real cache.
2. Why does a short TTL not prove consistency after invalidation?
Show worked solution
An older in-flight source read can fill stale data after the invalidation. TTL may bound how long that stale entry survives, but the race still exists. Review permitted interleavings and source revision checks according to freshness requirements, and do not present time-based expiry as a transaction or ordering guarantee.
Key takeaways
- Cache scope, units and freshness require explicit contracts.
- Bound cache fallback without weakening authority decisions.