Redis Caching skill for Codex

Faster reads without stale or leaked data. Codex first checks whether caching is the right fix, then adds cache-aside reads with keys that include every output dimension, jittered TTLs, a token-guarded rebuild lock, short negative caching and invalidation that runs only after the database commits. It sets the eviction policy based on what else lives in Redis and proves the result with hit-rate, stampede and Redis-down tests.

  • Backend & APIs
  • $redis-caching
  • 161 lines
  • 2 files
  • ~1,950 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- redis-caching

Installs to ~/.agents/skills/redis-caching/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $redis-caching, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Adds and fixes application caching with Redis or Valkey - cache-aside reads, TTLs with jitter, stampede protection via locks, single-flight and early refresh, stale-while-revalidate, invalidation after commit, versioned keys, negative caching, eviction policy and memory sizing - in Node (ioredis, node-redis) and Python (redis-py). Use when an endpoint or query is slow and read-heavy, cache hit rate is poor, users see stale data after updates, Redis memory spikes, or a cache expiry causes database load spikes.

Use it when

  • A read-heavy endpoint or query is slow and tolerates brief staleness
  • Cache expiry causes thundering-herd load on the database
  • Updates take too long to show up, or never do
  • Redis memory, evictions or hit rate look wrong

Use something else when

How to check the result

Measure hit rate and DB queries per request before and after, expire a hot key under load and count loader calls, and stop Redis locally to confirm requests still succeed.

The mistake it prevents

Deleting the cache entry before the database transaction commits, letting a concurrent reader put the old value straight back.

~/.agents/skills/redis-caching/SKILL.md

--- name: redis-caching description: Adds and fixes application caching with Redis or Valkey - cache-aside reads, TTLs with jitter, stampede protection via locks, single-flight and early… license: MIT ---

Done well means: the cache is an optimisation the system survives without, hit rate is measured, writes invalidate after the database commits, hot keys never expire all at once, and nobody serves one user’s data to another because of a key that forgot a dimension.

Should this be cached at all?

Cache when all hold: read far more than written (10:1 or more), expensive to compute or fetch (tens of ms or more), tolerant of bounded staleness, and the key space is reasonably bounded. Fix the query first if it is slow because of a missing index (postgres-query-tuning): caching a bad query hides it until the cache is cold.

DataPatternTTL guide
Reference data (plans, config, feature catalog)Cache-aside, long TTL + explicit invalidation10 min - 24 h
Per-user or per-tenant viewsCache-aside, key includes user/tenant30 s - 10 min
Expensive aggregates (dashboards, counts)Stale-while-revalidate or scheduled precompute1 - 15 min
Third-party API responsesCache-aside honouring their limitsPer provider terms
“Not found” resultsNegative cache30 - 60 s
Rapidly changing data (stock, balances)Do not cache, or cache under 1 s with care-

For HTTP-level and CDN caching use http-caching or cdn-caching; for whole-system cache layering decisions use caching-architecture.

Before changing anything

  1. Read AGENTS.md; find the Redis client and any existing helper (rg -n "redis|ioredis|cache" src).
  2. Check what else the Redis instance holds (sessions, queues, locks, rate limits). It decides the eviction policy.
  3. Get baseline numbers: endpoint p50/p95 latency, DB query count per request, and redis-cli INFO stats | grep -E "keyspace_(hits|misses)|evicted_keys".

Key design

<app>:<env>:<entity>:v<schema>:<scope>:<id>[:<variant>]
shop:prod:product:v3:t42:9f1c        # tenant 42, product 9f1c, schema version 3
shop:prod:search:v1:t42:sha1(q,filters,page,locale)
  • Include every input that changes the output: tenant, user (if personalised), locale, currency, permissions tier, feature flags. Missing one is a data leak, not a perf bug.
  • Bump the v<schema> segment when the cached shape changes; old keys age out by TTL.
  • Hash long or unbounded inputs; keep keys under ~200 bytes.

Cache-aside with jitter and single-flight (Python)

import json, random, secrets, time
import redis

r = redis.Redis.from_url(REDIS_URL, decode_responses=True)
RELEASE = r.register_script(
    "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
)

def cached(key: str, ttl: int, load, lock_ttl_ms: int = 5000):
    raw = r.get(key)
    if raw is not None:
        return None if raw == "__none__" else json.loads(raw)

    token = secrets.token_hex(16)
    if r.set(f"lock:{key}", token, nx=True, px=lock_ttl_ms):          # one caller rebuilds
        try:
            value = load()
            jittered = int(ttl * random.uniform(0.9, 1.1))             # spread expiries
            r.set(key, "__none__" if value is None else json.dumps(value),
                  ex=60 if value is None else jittered)                # short negative cache
            return value
        finally:
            RELEASE(keys=[f"lock:{key}"], args=[token])                # only delete our own lock
    for _ in range(20):                                                # others wait briefly
        time.sleep(0.05)
        raw = r.get(key)
        if raw is not None:
            return None if raw == "__none__" else json.loads(raw)
    return load()                                                      # fall through, never hang

On any Redis error, log, count, and call load() directly. A cache outage must degrade to slower responses, not errors. Set client timeouts low (50-200 ms) so a sick Redis does not stall every request.

Stampede protection options

TechniqueWhen
TTL jitter (+/-10 %)Always, for keys written in bulk or at deploy time
Lock / single-flight (above)Expensive rebuilds on hot keys
In-process single-flight (one promise per key per instance)Many concurrent requests in one Node or Go process
Early probabilistic refresh (XFetch)Very hot keys where any miss is costly
Stale-while-revalidateStore {value, fresh_until} with a longer Redis TTL; serve stale and refresh in the background
Precompute on scheduleAggregates that are always needed; cron writes, readers never compute

Invalidation

  • Invalidate after the database transaction commits (post-commit hook, outbox, or CDC). Deleting before commit lets a concurrent reader repopulate the old value.
  • Prefer DEL key over writing the new value from the write path: two concurrent writers can otherwise leave the older value in cache.
  • For lists that depend on many rows, use a generation counter: INCR shop:prod:gen:t42:products and include the generation in list keys. Old keys expire by TTL.
  • Never use KEYS pattern in production; it blocks Redis. Use SCAN with MATCH and COUNT, or better, design keys so pattern deletion is unnecessary.
  • Multi-instance apps with an in-process L1 cache need pub/sub or RESP3 client tracking to invalidate L1, or a very short L1 TTL (1-5 s).

Memory and eviction

Redis holdsmaxmemory-policy
Only cache dataallkeys-lru (or allkeys-lfu for skewed popularity)
Cache plus sessions, queues or locksSeparate instance; otherwise volatile-lru and TTL on every cache key
Queues or primary data onlynoeviction
  • Every cache key gets a TTL. Keys without TTL under volatile-* can never be evicted.
  • Find big keys with redis-cli --bigkeys or --memkeys; values over ~100 KB hurt latency.
  • Compress large JSON values or cache IDs and hydrate from a smaller cache.
  • Changing server config on a managed or production Redis is a production change: confirm with the user first.

Node notes

Use one shared client (ioredis or node-redis), not one per request. Coalesce concurrent misses in-process with a Map<string, Promise<T>> before touching the distributed lock. For a lock-free compare-and-delete use the same Lua script as above via eval/defineCommand.

Verification

  • Hit rate: compare keyspace_hits / (hits + misses) before and after, and per key family via your own metrics (cache_requests_total{family,result}).
  • Load test the endpoint (load-testing) with the cache cold and warm; DB QPS should drop roughly by the hit rate.
  • Stampede test: expire a hot key under concurrent load and confirm only one rebuild runs (count loader calls).
  • Staleness test: update a row, then read through the API; the new value appears within the documented bound.
  • Kill Redis in a local run: requests still succeed, slower.

Gotchas

  • Caching per-user responses under a shared key: the classic cross-user data leak.
  • Caching errors or partial results with a long TTL.
  • Serialising ORM objects (pickle, class instances) ties the cache to code versions; store plain JSON.
  • EXPIRE reset on every read turns a TTL cache into a never-expiring one for hot keys.
  • Redis 8 changed licensing (AGPL option) and Valkey is a compatible fork; check which one the platform provides before using version-specific commands.
  • For data structure choices (sorted sets, streams, HyperLogLog) use redis-data-structures; for lock correctness beyond a cache rebuild use distributed-locks.

Output

Report: what is cached, key format, TTLs, invalidation trigger per key family, stampede protection used, eviction policy, measured hit rate and latency before and after.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Redis Caching"
  short_description: "Faster reads without stale or leaked data"
  default_prompt: "$redis-caching Cache the product catalog endpoint in Redis without serving stale prices"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem