Skip to content
Back to skills

Caching Strategy Designer

ASecurity

Decide what gets cached, where, and how it stays correct — candidate analysis first (read/write ratio, miss cost, staleness tolerance stated per item), layer choice (HTTP/CDN edge, in-process, distributed cache, DB materialization), invalidation designed BEFORE the cache ships (TTL, event-driven purge with backstop, write-through/behind — with the consistency envelope each buys), key design where tenant scoping is a correctness boundary (tenant-qualified keys; a NEW cache store's isolation de...

  • 4 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 11, 2026
businessgogitapidatabasefrontendperformance

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add ModernNomad-98/Project-Aegis --skill caching-strategy-designer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Caching Strategy Designer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Caching Strategy Designer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/modernnomad-98-caching-strategy-designer/badge)](https://www.skillsdirectory.com/skills/modernnomad-98-caching-strategy-designer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: caching-strategy-designer
description: Decide what gets cached, where, and how it stays correct — candidate analysis first (read/write ratio, miss cost, staleness tolerance stated per item), layer choice (HTTP/CDN edge, in-process, distributed cache, DB materialization), invalidation designed BEFORE the cache ships (TTL, event-driven purge with backstop, write-through/behind — with the consistency envelope each buys), key design where tenant scoping is a correctness boundary (tenant-qualified keys; a NEW cache store's isolation defers to multi-tenant-data-architect), stampede protection, failure semantics (cold start, cache-down, stale-on-error), and measurement (hit ratio via observability-operator; gates via performance-test-harness). Authorization results are never cached by default. Use when adding or redesigning a cache, choosing TTLs/invalidation, or when stale-data bugs implicate a cache. Do NOT use to hide chatty patterns (n-plus-one-detector fixes those first) or for browser asset caching (frontend-perf-engineer).
---

# Caching Strategy Designer

**Reading key:** HTTP means Hypertext Transfer Protocol; CDN means content
delivery network; DB means database; TTL means time to live, the expiry limit
for a cached value; API means application programming interface.

## Purpose

There are only two hard things in computer science, and this skill owns
one of them: every cache is a standing bet that staleness within a stated
window is acceptable, and most caching bugs are that window never having
been stated. This skill designs caching as a correctness feature with a
performance benefit — candidates justified by evidence, the staleness
envelope written down per item, invalidation designed before the cache
ships (not after the first stale-data incident), keys that carry the
tenant boundary, stampede protection for the expiry cliff, and failure
semantics for the day the cache is down. A cache without an invalidation
story is a bug that hasn't fired yet; this skill exists to make sure it
never ships in that state.

## Use When

- Use when: adding a cache to a hot read path — API responses, computed
  aggregates, configuration/reference data, session-adjacent lookups.
- Use when: choosing or revisiting TTLs, invalidation triggers, or the
  cache layer (edge/HTTP vs application vs distributed vs database
  materialization) for a workload.
- Use when: stale-data bugs implicate an existing cache and its
  invalidation/consistency design needs an overhaul.
- Use when: a latency budget (`latency-budget-architect`) or split
  verdict (`operational-vs-analytical-splitter`) prescribes "cache" and
  the actual design now needs doing.
- Do NOT use when: the read path is chatty (N+1 queries: one list query
  followed by one query per row, or other per-row lookups) —
  `n-plus-one-detector` *(manual-only)* fixes the pattern FIRST; caching a storm hides
  it and every miss replays it.
- Do NOT use when: deciding the tenant-isolation posture of a NEW cache
  STORE (shared cluster vs per-tenant separation) —
  `multi-tenant-data-architect` owns where a new store gets its tenant
  scoping; this skill designs keys/invalidation WITHIN that decision.
- Do NOT use when: the caching is browser asset/bundle-level (cache
  headers for static assets, immutable bundle URLs, service-worker
  strategies) — `frontend-perf-engineer` owns the frontend asset path;
  this skill covers shared HTTP/CDN caching of API/page responses.
- Do NOT use when: the "cache" is really a materialized analytical
  workload moving off the primary — that placement decision is
  `operational-vs-analytical-splitter`.
- Do NOT use when: designing rate-limit counters, queues, or locks that
  happen to live in a cache technology — those are not caches (no
  source of truth to fall back to) and their design belongs to the
  owning feature's architecture.

## Inputs to Inspect

1. The candidate's evidence: read/write ratio, request rate, computation
   or query cost per miss (what a cache actually saves), result size
   and cardinality (what it costs to hold).
2. The staleness question, asked per candidate and answered by an
   owner: how old may this data be when served, in seconds/minutes,
   and what breaks (billing? authorization? a dashboard number?) when
   the window is exceeded.
3. Write topology: every path that mutates the underlying data —
   including background jobs, admin edits, and OTHER services —
   because each one is an invalidation trigger or a staleness hole.
4. The tenant model: cached data's tenant ownership, the established
   isolation posture for stores (from `multi-tenant-data-architect`
   output where it exists), and any cross-tenant shared/reference data
   that is legitimately tenant-neutral.
5. Existing cache infrastructure and its behavior: layers already in
   play (edge, app, distributed), their eviction policies, memory
   limits, and current hit ratios if measured.
6. Failure tolerance: what the system must do when the cache is cold,
   down, or partially evicted — and the origin's capacity to absorb a
   full miss storm (this bounds TTL/warming choices).

## Workflow

When presenting read-path alternatives to a human after rejecting a cache,
define query tuning and materialization; explain why each fits, its user
benefit, drawback, and money/setup/maintenance cost or uncertainty. Recommend
the fit from measured access patterns before one choice question. Factual
staleness questions and any approval to add a store remain separate.

1. **Qualify the candidate — or reject it.** A cache earns its place
   with: high read/write ratio, meaningful miss cost, bounded result
   cardinality, and a stated staleness tolerance. Reject candidates
   that fail the interrogation: write-heavy data (invalidations exceed
   hits), unbounded key spaces (cache becomes a leak), zero staleness
   tolerance (see the authorization rule in Safety Rules), or a chatty
   pattern in disguise (→ `n-plus-one-detector` *(manual-only)* first).
2. **State the consistency envelope per item.** In writing, in the
   design: "served up to N seconds stale under normal operation, up to
   M during invalidation failure; never stale for <listed operations>".
   This sentence is the cache's contract; everything below implements
   it.
3. **Choose the layer.** Edge/HTTP-CDN for anonymous or coarsely-varied
   responses (careful: `Vary` and auth — a shared cache serving a
   personalized response is an incident); application-level in-process
   for tiny hot reference data (accepting per-instance divergence);
   distributed cache for shared, keyed, cross-instance data — the
   default for tenant-scoped items; database materialization when the
   data is queried in shapes a cache can't serve. State why the layer
   matches the envelope.
4. **Design the keys.** Explicit key schema: `<namespace>:<version>:
   <tenant-id>:<entity>:<discriminators>`. Tenant-qualified ALWAYS for
   tenant-owned data — key collisions across tenants are data leaks,
   not bugs. Version segment enables generation-based purge (bump the
   version = atomic invalidation of a family). Discriminators cover
   everything that varies the value (locale, role-visibility, plan) —
   under-keyed caches serve one user's view to another.
5. **Design invalidation BEFORE shipping.** Per item, choose and state:
   - *TTL-only:* honest for tolerance-bounded data; TTL = the envelope,
     not a guess.
   - *Event-driven purge:* mutation paths (ALL of them, from input 3)
     emit invalidation — precise but every missed path is a permanent
     staleness hole; pair with a backstop TTL always.
   - *Write-through / write-behind:* cache updated at write time —
     consistency at the cost of write-path coupling (write-behind adds
     loss risk; name it).
   - *Generation/version bump:* for families invalidated together.
   The backstop rule: every entry has a TTL even when event-driven —
   events fail, and the TTL caps the damage.
6. **Protect against stampedes.** For hot keys: single-flight/lock so
   one request recomputes while others wait or serve stale;
   probabilistic early refresh or TTL jitter so a family doesn't expire
   as one cliff; explicit cold-start/warming plan when a deploy or
   flush empties everything at once (the origin must survive the first
   minute — input 6 bounds this).
7. **Define failure semantics.** Cache down: fail-open to origin (with
   load shedding if the origin can't take it) or serve-stale-on-error
   (state the max-stale bound) — chosen per item's envelope; never
   fail-closed for availability-critical reads unless correctness
   demands it. Eviction pressure: what may be evicted early and what
   should be pinned/sized-for.
8. **Plan the measurement.** Hit ratio target per cache (and what a
   LOW ratio triggers — removal is a valid outcome; an unhit cache is
   pure risk), staleness-incident tracking, memory/eviction telemetry
   — wiring via `observability-operator` *(manual-only)*; latency effect verified
   through `performance-test-harness` gates where they exist.
9. **Deliver the design** in the Output Format, one card per cached
   item — the envelope, layer, keys, invalidation, stampede, failure,
   and measurement rows all filled; a cache card with an empty
   invalidation row does not ship.

Layer decision table, invalidation-pattern tradeoffs, key-schema
conventions, and stampede-protection patterns:
[references/invalidation-decision-sheet.md](references/invalidation-decision-sheet.md).

## Output Format

```
CACHING STRATEGY — <scope>
Rejected candidates: <item — why (write-heavy | unbounded keys | zero tolerance | chatty pattern → n-plus-one-detector)>
Read-path choice (if needed): <explained alternatives, costs/unknowns, pros/cons, recommendation + why>
Per-item cache card:
  <item>: evidence=<read/write, miss cost, cardinality>
  ENVELOPE: "≤ <N>s stale normal / ≤ <M>s on invalidation failure; never stale for: <ops>"
  layer=<edge|app|distributed|materialized> (why it fits the envelope)
  keys=<namespace>:<ver>:<tenant-id>:<entity>:<discriminators> (under-keying check done)
  invalidation=<TTL|event+backstop-TTL|write-through|generation> — mutation paths covered: <list>
  stampede=<single-flight | jitter | early-refresh | warming plan>
  failure=<fail-open|serve-stale(max)|fail-closed(why)> ; eviction posture=<...>
  measurement=<hit-ratio target; removal trigger; staleness tracking> → observability-operator
Store-level isolation: <existing posture applied | NEW store → multi-tenant-data-architect>
Verification: <before/after latency via performance-test-harness where wired>
```

## Safety Rules

- **Authorization results are not cacheable by default.** Permission
  checks, entitlement lookups, and tenant-membership answers have
  effectively zero staleness tolerance in the deny direction (revoked
  access must revoke). Caching them requires an explicit human-approved
  envelope with a revocation-propagation bound — surface it, never
  default it.
- **Tenant-qualified keys are a correctness boundary**: any cached
  tenant-owned value whose key lacks the tenant id is a cross-tenant
  leak waiting for a collision. This rule has no exceptions for
  "internal" caches.
- **Personalized responses never enter shared HTTP caches** — auth'd
  responses carry cache-control that forbids shared storage unless the
  variance is fully keyed and reviewed.

## Validation Checklist

- [ ] Any read-path choice shown to a human explains the options and a
      contextual recommendation before one question; no store action is inferred.

- [ ] Every cached item has a written consistency envelope with an
      owner-confirmed staleness number — no "we'll tune TTL later".
- [ ] Every card's invalidation row is filled; event-driven designs
      enumerate ALL mutation paths and carry a backstop TTL.
- [ ] Keys are tenant-qualified for tenant-owned data; discriminators
      cover everything that varies the value; a version segment exists.
- [ ] Rejected candidates are listed with reasons — the design shows
      what it declined to cache.
- [ ] Stampede protection exists for hot keys and the cold-start plan
      survives a full flush at peak.
- [ ] Failure semantics are stated per item (fail-open/serve-stale
      bounds), and the origin survives the miss storm.
- [ ] Hit-ratio targets and the removal trigger are defined; the
      measurement wiring is handed to observability-operator.
- [ ] No authorization/entitlement result is cached without the
      explicit approved envelope.

## Gotchas

- The second-hardest bug: an event-driven invalidation missing ONE
  mutation path (the admin bulk edit, the background reconciler) —
  staleness that only manifests for that path's changes. The
  mutation-path enumeration and backstop TTL exist for exactly this.
- Under-keying serves the wrong data, over-keying serves nothing:
  missing a discriminator (role, locale, plan) leaks one view to
  another audience; keying on request noise (timestamps, cursors)
  drives the hit ratio to zero. Both are key-schema review items.
- The expiry cliff: a popular family cached at deploy time with one
  TTL expires as one thundering herd. Jitter and early-refresh are
  cheap; the origin outage they prevent is not.
- Serve-stale-on-error without a bound quietly becomes
  serve-forever-on-error when the origin stays down — max-stale is
  part of the policy, and its exceedance is an alert, not a shrug.
- Write-behind loses writes on cache failure by design — it buys write
  latency at durability's expense; naming that tradeoff is mandatory,
  choosing it for money-adjacent data is a Stop Condition.
- In-process caches diverge per instance: N app instances = N
  independent staleness windows; fine for reference data, wrong for
  anything users compare across requests that land on different
  instances (sticky-session illusions).
- Negative caching cuts both ways: caching "not found" absorbs
  lookup storms for missing keys — and serves 404 for N seconds after
  the thing is created. State the negative TTL separately and shorter.
- Cache-aside recomputation races: two writers interleaving
  read-modify-write on the same key can resurrect stale data;
  single-flight plus compare-and-set (or versioned writes) where the
  value is mutable.

## Stop Conditions

- Asked to cache authorization/permission/entitlement results as an
  optimization → stop; apply the Safety Rule — explicit human approval
  with a revocation-propagation bound, or the answer is no.
- Asked to cache to hide a chatty pattern ("cache the page, it does
  400 queries") → refuse; route to `n-plus-one-detector` *(manual-only)* first, then
  revisit surviving candidates.
- No staleness tolerance can be stated for a candidate (the owner
  says "it must always be current") → the item is not cacheable as
  scoped; present explained read-path alternatives (query tuning →
  `query-plan-reader` (MANUAL-ONLY: recommend the user invoke it
  explicitly); materialization → the splitter) instead of inventing a
  tolerance.
- The design would introduce a NEW cache store whose tenant-isolation
  posture is undecided → pause the store decision and route it to
  `multi-tenant-data-architect`; key design resumes after.
- Write-behind proposed on billing/financial or otherwise
  loss-intolerant data → halt for explicit human sign-off; write loss
  on cache failure is that choice's designed behavior.
- Asked to also flush/purge/edit a LIVE production cache → decline
  execution; operational cache actions follow the ops approval path.

## Supporting Files

- [references/invalidation-decision-sheet.md](references/invalidation-decision-sheet.md)
  — layer decision table, invalidation-pattern tradeoff rows,
  key-schema conventions with the under/over-keying checks, stampede
  patterns (single-flight, jitter, early refresh), negative-caching
  guidance.
- `evals/evals.json` — behavior cases including the
  missed-mutation-path edge and the cache-authorization refusal.
- `evals/trigger-evals.json` — discrimination against
  `n-plus-one-detector`, `multi-tenant-data-architect`,
  `frontend-perf-engineer`, and `operational-vs-analytical-splitter`.

Files in this skill

  • SKILL.md14.9 KB
  • evals/evals.json5 KB
  • evals/trigger-evals.json3.9 KB
  • references/invalidation-decision-sheet.md4.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.