Can Agents Run It? · Issue 2 · Evidence
All 125 answers
Five prompts that name no product, five models, five answers per model and prompt. Each prompt was sent as the only message, with no system prompt, at the provider's default settings. Under each answer: what it was counted as and why.
We counted what each answer actually sets up: first from its code (a container image, a Terraform engine, an install line; comments and fallback tips ignored), then from a named managed service, then from the line that states the choice. A client library never counts, because the redis Python client talks to Valkey too. Answers with mixed evidence were coded by hand, and the reason is shown.
A person asks for a shared cache A person asks for a job queue A person asks for a semantic cache A managed store on AWS An agent that runs the service itself
A person asks for a shared cache
Redis 25 of 25
Claude Opus 5.5 · Valkey in 0 of 5
answer 1 Redis6,459 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
ports:
- "6379:6379"
pip install "redis>=5" export REDIS_URL=redis://your-redis-host:6379/0
import functools
import hashlib
import json
import logging
import os
import time
import uuid
import redis
log = logging.getLogger(__name__)
# One pool per process; short timeouts so a sick Redis can't stall requests.
_pool = redis.ConnectionPool.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
socket_timeout=0.25,
socket_connect_timeout=0.25,
health_check_interval=30,
)
r = redis.Redis(connection_pool=_pool)
# Release a lock only if we still own it (avoids deleting another server's lock).
_RELEASE_LOCK = r.register_script("""
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
""")
def make_key(prefix: str, args, kwargs) -> str:
raw = json.dumps([args, kwargs], sort_keys=True, default=str)
digest = hashlib.sha256(raw.encode()).hexdigest()[:32]
return f"cache:v1:{prefix}:{digest}"
def shared_cache(prefix: str, ttl: int = 300, lock_ttl: int = 30, wait: float = 5.0):
"""
Cache a function's JSON-serializable result in Redis for `ttl` seconds.
Use on plain functions (not methods) so `self` doesn't end up in the key.
"""
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
key = make_key(prefix, args, kwargs)
# 1. Try the cache.
try:
hit = r.get(key)
except redis.RedisError as e:
log.warning("cache read failed, bypassing cache: %s", e)
return fn(*args, **kwargs)
if hit is not None:
return json.loads(hit)
# 2. Miss: try to become the one server that refreshes this key.
lock_key = f"{key}:lock"
token = uuid.uuid4().hex
try:
got_lock = r.set(lock_key, token, nx=True, ex=lock_ttl)
except redis.RedisError:
return fn(*args, **kwargs)
if not got_lock:
# Someone else is fetching. Poll briefly for their result.
deadline = time.monotonic() + wait
while time.monotonic() < deadline:
time.sleep(0.05)
try:
hit = r.get(key)
except redis.RedisError:
break
if hit is not None:
return json.loads(hit)
# Took too long; fetch ourselves rather than fail the request.
return fn(*args, **kwargs)
# 3. We hold the lock: fetch and populate.
try:
value = fn(*args, **kwargs)
try:
r.set(key, json.dumps(value), ex=ttl)
except redis.RedisError as e:
log.warning("cache write failed: %s", e)
return value
finally:
try:
_RELEASE_LOCK(keys=[lock_key], args=[token])
except redis.RedisError:
pass # lock expires on its own after lock_ttl
def invalidate(*args, **kwargs):
r.delete(make_key(prefix, args, kwargs))
wrapper.invalidate = invalidate
return wrapper
return decorator
import requests
from shared_cache import shared_cache
INVENTORY_URL = "https://inventory.internal/api"
@shared_cache(prefix="inventory:item", ttl=300)
def get_inventory(sku: str) -> dict:
resp = requests.get(f"{INVENTORY_URL}/items/{sku}", timeout=10)
resp.raise_for_status()
return resp.json()
# In your request handler:
item = get_inventory("SKU-123")
# If you learn the data changed (e.g., after a write), drop it early:
get_inventory.invalidate("SKU-123")
answer 2 Redis6,396 characters
counted from its code (a container image, a Terraform engine or an install line)
docker run -d --name redis -p 6379:6379 redis:7 \ redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
pip install redis
import functools
import hashlib
import json
import logging
import os
import random
import time
import uuid
import redis
log = logging.getLogger(__name__)
_redis = redis.Redis.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True,
socket_timeout=0.2, # never let the cache make requests slower
socket_connect_timeout=0.2,
health_check_interval=30,
retry_on_timeout=False,
)
_MISS = object()
# Delete the lock only if we still own it
_RELEASE_LOCK = _redis.register_script("""
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
""")
def _make_key(prefix, args, kwargs):
raw = json.dumps([args, kwargs], sort_keys=True, default=str)
return f"{prefix}:{hashlib.sha256(raw.encode()).hexdigest()[:32]}"
def _get(key):
try:
raw = _redis.get(key)
except redis.RedisError as e:
log.warning("cache get failed for %s: %s", key, e)
return _MISS
return _MISS if raw is None else json.loads(raw)
def _set(key, value, ttl):
try:
_redis.set(key, json.dumps(value, default=str), ex=ttl)
except redis.RedisError as e:
log.warning("cache set failed for %s: %s", key, e)
def _acquire_lock(lock_key, token, ttl):
try:
return bool(_redis.set(lock_key, token, nx=True, ex=ttl))
except redis.RedisError:
return True # Redis unavailable: just do the fetch ourselves
def _release_lock(lock_key, token):
try:
_RELEASE_LOCK(keys=[lock_key], args=[token])
except redis.RedisError:
pass # lock expires on its own
def shared_cache(prefix, ttl=300, jitter=30, lock_ttl=10, wait_for_peer=2.0):
"""Cache a function's JSON-serializable result in Redis, shared across servers."""
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
key = _make_key(prefix, args, kwargs)
value = _get(key)
if value is not _MISS:
return value
lock_key = f"{key}:lock"
token = uuid.uuid4().hex
have_lock = _acquire_lock(lock_key, token, lock_ttl)
if not have_lock:
# Another server is fetching this key; wait briefly for its result.
deadline = time.monotonic() + wait_for_peer
while time.monotonic() < deadline:
time.sleep(0.05)
value = _get(key)
if value is not _MISS:
return value
# Peer was too slow; fall through and fetch ourselves.
try:
value = fn(*args, **kwargs)
_set(key, value, ttl + random.randint(0, jitter))
return value
finally:
if have_lock:
_release_lock(lock_key, token)
def invalidate(*args, **kwargs):
try:
_redis.delete(_make_key(prefix, args, kwargs))
except redis.RedisError as e:
log.warning("cache invalidate failed: %s", e)
wrapper.invalidate = invalidate
return wrapper
return decorator
import requests
from shared_cache import shared_cache
INVENTORY_URL = "https://inventory.internal/api"
@shared_cache(prefix="inventory:v1", ttl=300)
def get_inventory(sku: str) -> dict:
resp = requests.get(f"{INVENTORY_URL}/items/{sku}", timeout=5)
resp.raise_for_status()
return resp.json()
redis-cli --scan --pattern 'inventory:v1:*' | head redis-cli TTL inventory:v1:<some-key> # should show ~300-330 redis-cli INFO stats | grep keyspace_ # hits vs misses
answer 3 Redis7,167 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
ports:
- "6379:6379"
export REDIS_URL=redis://your-redis-host:6379/0 # with TLS + auth: rediss://:password@host:6380/0
pip install redis
import functools
import json
import logging
import os
import time
import redis
log = logging.getLogger(__name__)
TTL_SECONDS = 300 # 5-minute expiry
LOCK_TTL_SECONDS = 15 # should exceed the slowest expected inventory call
WAIT_FOR_OTHER_FETCH = 5.0 # max seconds to wait for another server's in-flight fetch
NAMESPACE = "inventory:v1" # bump v1 -> v2 if the cached data shape changes
_pool = redis.ConnectionPool.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
socket_timeout=0.25, # a sick Redis must not slow down requests
socket_connect_timeout=0.25,
health_check_interval=30,
max_connections=50,
)
client = redis.Redis(connection_pool=_pool)
def make_key(*parts) -> str:
return NAMESPACE + ":" + ":".join(str(p) for p in parts)
def _read(key):
"""Returns (hit, value). Raises redis.RedisError on connection problems."""
raw = client.get(key)
if raw is None:
return False, None
return True, json.loads(raw)
def get_or_fetch(key: str, fetch, ttl: int = TTL_SECONDS):
"""
Cache-aside with stampede protection.
- Hit: return cached value.
- Miss: one server takes a short lock and calls `fetch`. Others wait
briefly for its result instead of all hammering the inventory service.
- Redis down: fail open and call `fetch` directly.
"""
# 1. Try the cache
try:
hit, value = _read(key)
if hit:
return value
except redis.RedisError as e:
log.warning("Redis read failed for %s, bypassing cache: %s", key, e)
return fetch()
# 2. Miss: try to become the one server that fetches
lock_key = key + ":lock"
try:
got_lock = bool(client.set(lock_key, "1", nx=True, ex=LOCK_TTL_SECONDS))
except redis.RedisError:
got_lock = True # can't coordinate, just fetch
if not got_lock:
# Someone else is fetching. Poll for their result.
deadline = time.monotonic() + WAIT_FOR_OTHER_FETCH
while time.monotonic() < deadline:
time.sleep(0.05)
try:
hit, value = _read(key)
if hit:
return value
except redis.RedisError:
break
# Took too long; fall through and fetch ourselves.
# 3. Fetch from the slow service and store it
try:
value = fetch()
except Exception:
if got_lock:
try:
client.delete(lock_key) # let another server retry immediately
except redis.RedisError:
pass
raise
try:
pipe = client.pipeline()
pipe.set(key, json.dumps(value), ex=ttl)
if got_lock:
pipe.delete(lock_key)
pipe.execute()
except redis.RedisError as e:
log.warning("Redis write failed for %s: %s", key, e)
return value
def invalidate(*parts):
"""Call this when you know inventory changed (e.g. after a write)."""
try:
client.delete(make_key(*parts))
except redis.RedisError as e:
log.warning("Redis invalidate failed: %s", e)
def cached(name: str, ttl: int = TTL_SECONDS):
"""Decorator: caches a function's result keyed by its positional args."""
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args):
key = make_key(name, *args)
return get_or_fetch(key, lambda: fn(*args), ttl)
wrapper.invalidate = lambda *args: invalidate(name, *args)
return wrapper
return decorator
# inventory_client.py
import requests
from cache import cached
INVENTORY_URL = "https://inventory.internal"
@cached("item")
def get_inventory(sku: str) -> dict:
resp = requests.get(f"{INVENTORY_URL}/items/{sku}", timeout=10)
resp.raise_for_status()
return resp.json()
# FastAPI example (Flask/Django work the same way)
from fastapi import FastAPI
from inventory_client import get_inventory
app = FastAPI()
@app.get("/products/{sku}")
def product(sku: str):
stock = get_inventory(sku) # Redis hit: ~1ms. Miss: one slow call, shared by all servers.
return {"sku": sku, "stock": stock}
get_inventory.invalidate(sku)
redis-cli KEYS 'inventory:v1:*' # see cached keys (use SCAN in prod, not KEYS) redis-cli TTL inventory:v1:item:ABC123 # seconds remaining, counts down from 300 redis-cli MONITOR # watch live traffic (dev only)
answer 4 Redis6,731 characters
counted from its code (a container image, a Terraform engine or an install line)
docker run -d --name redis -p 6379:6379 redis:7-alpine
export REDIS_URL="redis://:password@your-redis-host:6379/0" # use rediss:// if your provider requires TLS
pip install "redis>=5"
# cache.py
import functools
import hashlib
import json
import logging
import os
import random
import time
import redis
log = logging.getLogger(__name__)
TTL_SECONDS = 300 # 5 minutes
TTL_JITTER = 30 # +/- up to 30s to spread expirations
LOCK_TTL = 30 # max seconds a refill lock is held
LOCK_WAIT = 5.0 # how long other requests wait for the refill
KEY_PREFIX = "inv:v1" # bump v1 -> v2 to invalidate everything after a format change
# One pooled client per process; safe to share across threads.
_redis = redis.Redis.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
socket_timeout=0.25, # don't let a slow Redis slow down requests
socket_connect_timeout=0.25,
health_check_interval=30,
decode_responses=True,
)
def _make_key(func_name: str, args, kwargs) -> str:
raw = json.dumps([args, kwargs], sort_keys=True, default=str)
digest = hashlib.sha256(raw.encode()).hexdigest()[:32]
return f"{KEY_PREFIX}:{func_name}:{digest}"
def _get(key):
try:
val = _redis.get(key)
return None if val is None else json.loads(val)
except redis.RedisError as e:
log.warning("cache get failed for %s: %s", key, e)
return None
def _set(key, value):
try:
ttl = TTL_SECONDS + random.randint(-TTL_JITTER, TTL_JITTER)
_redis.set(key, json.dumps(value), ex=ttl)
except redis.RedisError as e:
log.warning("cache set failed for %s: %s", key, e)
def cached(func):
"""Cache-aside with a shared Redis cache, 5-min TTL, and stampede protection."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = _make_key(func.__qualname__, args, kwargs)
hit = _get(key)
if hit is not None:
return hit
# Miss: try to become the one request that refills this key.
lock_key = f"{key}:lock"
try:
got_lock = _redis.set(lock_key, "1", nx=True, ex=LOCK_TTL)
except redis.RedisError:
got_lock = True # Redis unavailable -> just call the service
if got_lock:
try:
value = func(*args, **kwargs)
_set(key, value)
return value
finally:
try:
_redis.delete(lock_key)
except redis.RedisError:
pass
# Someone else is refilling; poll briefly for their result.
deadline = time.monotonic() + LOCK_WAIT
while time.monotonic() < deadline:
time.sleep(0.05)
hit = _get(key)
if hit is not None:
return hit
# Refill is taking too long; fall back to calling the service ourselves.
return func(*args, **kwargs)
def invalidate(*args, **kwargs):
try:
_redis.delete(_make_key(func.__qualname__, args, kwargs))
except redis.RedisError as e:
log.warning("cache invalidate failed: %s", e)
wrapper.invalidate = invalidate
return wrapper
# inventory.py
import requests
from cache import cached
INVENTORY_URL = "https://inventory.internal"
@cached
def get_inventory(sku: str) -> dict:
resp = requests.get(f"{INVENTORY_URL}/items/{sku}", timeout=10)
resp.raise_for_status()
return resp.json()
# in your route handler (Flask, FastAPI, Django, etc.)
item = get_inventory("SKU-123") # first call: slow, then cached for ~5 min on ALL servers
# when you know the data changed (e.g. after a stock update you performed):
get_inventory.invalidate("SKU-123")
import redis.asyncio as aredis _redis = aredis.Redis.from_url(os.environ["REDIS_URL"], socket_timeout=0.25, decode_responses=True) # ...then `await _redis.get(key)`, `await _redis.set(...)`, `await asyncio.sleep(0.05)`, and `value = await func(...)`
answer 5 Redis6,459 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7
ports: ["6379:6379"]
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--save "" --appendonly no
pip install "redis>=5"
import functools
import hashlib
import json
import logging
import os
import time
import uuid
import redis
log = logging.getLogger(__name__)
_pool = redis.ConnectionPool.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
socket_timeout=0.25, # never let the cache make requests much slower
socket_connect_timeout=0.25,
health_check_interval=30,
max_connections=50,
)
r = redis.Redis(connection_pool=_pool)
DEFAULT_TTL = 300 # 5 minutes
PREFIX = "inv:v1:" # bump v1 -> v2 if the cached data shape changes
# Release the lock only if we still own it (it may have expired and been re-acquired).
_release = r.register_script("""
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
""")
def _make_key(fn, args, kwargs):
raw = json.dumps([args, kwargs], sort_keys=True, default=str)
return f"{PREFIX}{fn.__module__}.{fn.__qualname__}:{hashlib.sha1(raw.encode()).hexdigest()}"
def _safe_get(key):
try:
return r.get(key)
except redis.RedisError as e:
log.warning("cache get failed (%s): %s", key, e)
return None
def cached(ttl=DEFAULT_TTL, lock_ttl=10, wait=2.0):
"""Cache a function's JSON-serializable return value in Redis for `ttl` seconds."""
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
key = _make_key(fn, args, kwargs)
hit = _safe_get(key)
if hit is not None:
return json.loads(hit)
# Miss: try to become the one caller that refreshes this key.
lock_key, token = key + ":lock", uuid.uuid4().hex
try:
have_lock = bool(r.set(lock_key, token, nx=True, ex=lock_ttl))
except redis.RedisError:
have_lock = False # Redis is down, so just call through
if not have_lock:
# Someone else is fetching. Wait briefly for their result.
deadline = time.monotonic() + wait
while time.monotonic() < deadline:
time.sleep(0.05)
hit = _safe_get(key)
if hit is not None:
return json.loads(hit)
# Took too long. Fall through and fetch ourselves.
try:
value = fn(*args, **kwargs)
try:
r.set(key, json.dumps(value), ex=ttl)
except redis.RedisError as e:
log.warning("cache set failed (%s): %s", key, e)
return value
finally:
if have_lock:
try:
_release(keys=[lock_key], args=[token])
except redis.RedisError:
pass
def invalidate(*args, **kwargs):
try:
r.delete(_make_key(fn, args, kwargs))
except redis.RedisError as e:
log.warning("cache invalidate failed: %s", e)
wrapper.invalidate = invalidate
return wrapper
return decorator
# inventory_client.py
import requests
from cache import cached
@cached(ttl=300)
def get_inventory(sku: str) -> dict:
resp = requests.get(f"https://inventory.internal/items/{sku}", timeout=10)
resp.raise_for_status()
return resp.json()
# e.g. FastAPI or Flask handler, same idea either way
@app.get("/products/{sku}")
def product(sku: str):
return {"sku": sku, "inventory": get_inventory(sku)}
get_inventory.invalidate("ABC-123")
GPT-6 Sol · Valkey in 0 of 5
answer 1 Redis1,237 characters
counted from the line that states the choice
pip install redis export REDIS_URL="redis://your-redis-host:6379/0"
import json
import os
import redis
cache = redis.Redis.from_url(
os.environ["REDIS_URL"],
decode_responses=True,
socket_connect_timeout=0.2,
socket_timeout=0.2,
)
def get_inventory(sku: str) -> dict:
key = f"inventory:v1:{sku}"
try:
cached = cache.get(key)
if cached is not None:
return json.loads(cached)
except (redis.RedisError, ValueError):
pass # Keep the API working if Redis is unavailable.
result = fetch_inventory_from_service(sku) # Your existing call
try:
cache.setex(key, 300, json.dumps(result))
except redis.RedisError:
pass
return result
answer 2 Redis1,376 characters
counted from the line that states the choice
docker run -d --name inventory-cache \ -p 127.0.0.1:6379:6379 redis:7-alpine pip install redis export REDIS_URL=redis://localhost:6379/0
import json
import os
import redis
from redis.exceptions import RedisError
from your_app import inventory_service # Replace with your existing client
cache = redis.Redis.from_url(
os.environ["REDIS_URL"],
decode_responses=True,
socket_connect_timeout=0.2,
socket_timeout=0.2,
)
def get_inventory(sku: str):
key = f"inventory:v1:{sku}"
try:
cached = cache.get(key)
if cached is not None:
return json.loads(cached)
except RedisError:
pass # Redis outage shouldn't stop inventory requests.
result = inventory_service.get_inventory(sku)
try:
cache.set(key, json.dumps(result), ex=300)
except RedisError:
pass
return result
answer 3 Redis1,917 characters
counted from the line that states the choice
docker run -d --name inventory-cache \ -p 127.0.0.1:6379:6379 redis:7-alpine pip install redis requests
import hashlib
import json
import os
import redis
import requests
cache = redis.Redis.from_url(
os.environ["REDIS_URL"],
decode_responses=True,
)
INVENTORY_URL = os.environ["INVENTORY_URL"] # Your existing service endpoint
TTL_SECONDS = 300
def get_inventory(sku: str, tenant_id: str) -> dict:
# Include every input that can change the service's response.
inputs = json.dumps([tenant_id, sku], separators=(",", ":"))
key = "inventory:v1:" + hashlib.sha256(inputs.encode()).hexdigest()
try:
cached = cache.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
pass # Redis outage must not take down the API.
# Adapt params/authentication to your inventory service.
response = requests.get(
INVENTORY_URL,
params={"sku": sku, "tenant_id": tenant_id},
timeout=(2, 10),
)
response.raise_for_status()
inventory = response.json()
try:
cache.setex(key, TTL_SECONDS, json.dumps(inventory))
except redis.RedisError:
pass
return inventory
answer 4 Redis1,669 characters
counted from the line that states the choice
pip install redis
import json
import os
import redis
cache = redis.Redis.from_url(
os.environ["REDIS_URL"],
decode_responses=True,
socket_connect_timeout=0.2,
socket_timeout=0.2,
)
def get_inventory(sku: str):
key = f"inventory:v1:{sku}"
try:
cached = cache.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
pass # Redis outage shouldn't prevent an inventory lookup.
# Replace with your existing inventory-service call.
inventory = fetch_inventory_from_service(sku)
# Cache successful results only, including valid "out of stock" results.
try:
cache.set(key, json.dumps(inventory), ex=300)
except redis.RedisError:
pass
return inventory
answer 5 Redis1,730 characters
counted from the line that states the choice
pip install redis
import hashlib
import json
import os
import redis
cache = redis.Redis.from_url(
os.environ["REDIS_URL"],
decode_responses=True,
socket_connect_timeout=0.2,
socket_timeout=0.2,
)
def fetch_inventory(tenant_id: str, sku: str) -> dict:
# Replace with your existing inventory-service call.
raise NotImplementedError
def get_inventory(tenant_id: str, sku: str) -> dict:
identity = json.dumps([tenant_id, sku], separators=(",", ":"))
key = "inventory:v1:" + hashlib.sha256(identity.encode()).hexdigest()
try:
cached = cache.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
pass # Cache unavailable: use the inventory service.
result = fetch_inventory(tenant_id, sku) # Don't cache service errors.
try:
cache.set(key, json.dumps(result), ex=300) # Expires after 5 minutes.
except redis.RedisError:
pass
return result
DeepSeek V4.1 Flash · Valkey in 0 of 5
answer 1 Redis4,560 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru", "--save", "", "--appendonly", "no"]
ports:
- "6379:6379"
REDIS_URL=redis://localhost:6379/0 # prod example: # REDIS_URL=rediss://:password@host:6379/0
pip install "redis>=5.0"
# cache.py
import asyncio
import json
import os
import uuid
from typing import Any
import redis.asyncio as redis
from redis.exceptions import RedisError
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
CACHE_TTL = 300 # 5 minutes
LOCK_TTL = 10 # seconds
WAIT_TIMEOUT = 1.0 # seconds
redis_client = redis.from_url(REDIS_URL, decode_responses=True)
RELEASE_LOCK = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
async def get_inventory_item(sku: str) -> Any:
cache_key = f"inventory:item:{sku}"
lock_key = f"inventory:lock:{sku}"
# 1. Try cache
try:
cached = await redis_client.get(cache_key)
except RedisError:
cached = None
if cached is not None:
return json.loads(cached)
# 2. Prevent stampede across multiple API servers
token = uuid.uuid4().hex
got_lock = False
try:
got_lock = bool(
await redis_client.set(lock_key, token, nx=True, ex=LOCK_TTL)
)
except RedisError:
got_lock = False
if got_lock:
try:
# Double-check after acquiring lock
try:
cached = await redis_client.get(cache_key)
except RedisError:
cached = None
if cached is not None:
return json.loads(cached)
# Slow inventory call
data = await inventory_service.get_item(sku)
try:
await redis_client.set(cache_key, json.dumps(data), ex=CACHE_TTL)
except RedisError:
pass
return data
finally:
try:
await redis_client.eval(RELEASE_LOCK, 1, lock_key, token)
except RedisError:
pass
# 3. Someone else is already fetching. Wait briefly for cache to fill.
deadline = asyncio.get_event_loop().time() + WAIT_TIMEOUT
while asyncio.get_event_loop().time() < deadline:
await asyncio.sleep(0.05)
try:
cached = await redis_client.get(cache_key)
except RedisError:
cached = None
if cached is not None:
return json.loads(cached)
# 4. Fallback if lock holder is slow/failed or Redis is unavailable
data = await inventory_service.get_item(sku)
try:
await redis_client.set(cache_key, json.dumps(data), ex=CACHE_TTL)
except RedisError:
pass
return data
# main.py
from fastapi import FastAPI
from cache import get_inventory_item, redis_client
app = FastAPI()
@app.get("/inventory/{sku}")
async def read_inventory(sku: str):
return await get_inventory_item(sku)
@app.on_event("shutdown")
async def shutdown():
await redis_client.aclose()
await redis_client.delete(f"inventory:item:{sku}")
answer 2 Redis4,581 characters
counted from its code (a container image, a Terraform engine or an install line)
version: "3.8"
services:
redis:
image: redis:7-alpine
command: >
redis-server
--save ""
--appendonly no
--maxmemory 256mb
--maxmemory-policy allkeys-lru
ports:
- "6379:6379"
restart: unless-stopped
pip install "redis>=5.0.0"
redis>=5.0.0
import os
import redis.asyncio as redis
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
redis_client = redis.from_url(
REDIS_URL,
decode_responses=True,
)
import asyncio
import json
from redis.exceptions import RedisError
from cache import redis_client
INVENTORY_TTL_SECONDS = 300 # 5 minutes
LOCK_TTL_SECONDS = 10
async def fetch_inventory_from_service(sku: str) -> dict:
# Replace this with your real slow inventory service call.
await asyncio.sleep(1)
return {"sku": sku, "quantity": 42}
async def get_inventory(sku: str) -> dict:
key = f"inventory:v1:{sku}"
# 1. Try cache first
try:
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
except RedisError:
# Redis is down: degrade to direct service call
return await fetch_inventory_from_service(sku)
# 2. Cache miss. Use a short lock so only one process refreshes it.
lock_key = f"lock:{key}"
try:
acquired = await redis_client.set(
lock_key,
"1",
nx=True,
ex=LOCK_TTL_SECONDS,
)
except RedisError:
return await fetch_inventory_from_service(sku)
if acquired:
try:
# Double-check in case another process populated it
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
data = await fetch_inventory_from_service(sku)
await redis_client.set(
key,
json.dumps(data),
ex=INVENTORY_TTL_SECONDS,
)
return data
finally:
try:
await redis_client.delete(lock_key)
except RedisError:
pass
# 3. Another process is already fetching. Wait briefly for it.
for _ in range(20): # ~2 seconds max
await asyncio.sleep(0.1)
try:
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
except RedisError:
break
# Fallback if the lock holder is too slow or failed
return await fetch_inventory_from_service(sku)
from fastapi import FastAPI
from inventory import get_inventory
app = FastAPI()
@app.get("/inventory/{sku}")
async def inventory_endpoint(sku: str):
return await get_inventory(sku)
import redis
redis_client = redis.Redis.from_url(
"redis://localhost:6379/0",
decode_responses=True,
)
answer 3 Redis4,130 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
ports:
- "6379:6379"
docker compose up -d redis
pip install redis requests
import json
import os
import time
from typing import Any
import redis
import requests
from redis.exceptions import LockError, RedisError
REDIS_URL = os.environ["REDIS_URL"]
INVENTORY_URL = os.environ["INVENTORY_URL"].rstrip("/")
TTL_SECONDS = 300 # 5 minutes
LOCK_TTL_SECONDS = 10
redis_client = redis.Redis.from_url(
REDIS_URL,
decode_responses=True,
socket_timeout=0.25,
socket_connect_timeout=0.25,
health_check_interval=30,
)
def _key(item_id: str) -> str:
return f"inventory:v1:{item_id}"
def _fetch_from_inventory(item_id: str) -> dict[str, Any]:
resp = requests.get(
f"{INVENTORY_URL}/inventory/{item_id}",
timeout=5.0,
)
resp.raise_for_status()
return resp.json()
def get_inventory(item_id: str) -> dict[str, Any]:
key = _key(item_id)
# 1. Try cache
try:
cached = redis_client.get(key)
if cached is not None:
return json.loads(cached)
except (RedisError, json.JSONDecodeError):
pass
# 2. Stampede protection: only one request fetches per item
lock = redis_client.lock(f"{key}:lock", timeout=LOCK_TTL_SECONDS)
got_lock = False
try:
got_lock = lock.acquire(blocking=False)
except RedisError:
# Redis is down: degrade to direct inventory call
got_lock = True
if got_lock:
try:
data = _fetch_from_inventory(item_id)
try:
redis_client.set(key, json.dumps(data), ex=TTL_SECONDS)
except RedisError:
pass
return data
finally:
try:
lock.release()
except (RedisError, LockError):
pass
# 3. Someone else is fetching. Wait briefly, then fall back.
for _ in range(10):
time.sleep(0.05)
try:
cached = redis_client.get(key)
if cached is not None:
return json.loads(cached)
except (RedisError, json.JSONDecodeError):
break
return _fetch_from_inventory(item_id)
from flask import Flask, jsonify, abort
import requests
from inventory_cache import get_inventory
app = Flask(__name__)
@app.get("/inventory/<item_id>")
def inventory(item_id: str):
try:
return jsonify(get_inventory(item_id))
except requests.HTTPError as e:
if e.response.status_code == 404:
abort(404)
raise
export REDIS_URL=redis://localhost:6379/0 export INVENTORY_URL=http://inventory-service:8080
redis_client.delete(f"inventory:v1:{item_id}")
answer 4 Redis4,886 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--save ""
--appendonly no
ports:
- "6379:6379"
docker compose up -d
pip install fastapi uvicorn redis httpx
import asyncio
import json
import os
from contextlib import asynccontextmanager
import httpx
import redis.asyncio as redis
from fastapi import FastAPI, HTTPException
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
INVENTORY_URL = os.getenv("INVENTORY_URL", "http://localhost:8001")
CACHE_TTL = 300 # 5 minutes
LOCK_TTL = 10 # seconds
redis_client: redis.Redis | None = None
http_client: httpx.AsyncClient | None = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global redis_client, http_client
redis_client = redis.from_url(
REDIS_URL,
decode_responses=True,
socket_timeout=0.05,
socket_connect_timeout=0.05,
health_check_interval=30,
)
http_client = httpx.AsyncClient(base_url=INVENTORY_URL, timeout=2.0)
yield
await redis_client.aclose()
await http_client.aclose()
app = FastAPI(lifespan=lifespan)
async def fetch_inventory(sku: str) -> dict:
resp = await http_client.get(f"/inventory/{sku}")
if resp.status_code == 404:
raise HTTPException(status_code=404, detail="SKU not found")
resp.raise_for_status()
return resp.json()
async def get_inventory_cached(sku: str) -> dict:
key = f"inventory:v1:{sku}"
lock_key = f"inventory:lock:{sku}"
# 1. Fast path: cache hit.
try:
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
# Redis down: fall back to the inventory service.
return await fetch_inventory(sku)
# 2. Cache miss. Try to become the one process that refreshes it.
try:
got_lock = await redis_client.set(lock_key, "1", nx=True, ex=LOCK_TTL)
except redis.RedisError:
return await fetch_inventory(sku)
if got_lock:
try:
# Double-check: another request may have filled it.
try:
cached = await redis_client.get(key)
except redis.RedisError:
return await fetch_inventory(sku)
if cached is not None:
return json.loads(cached)
value = await fetch_inventory(sku)
try:
await redis_client.set(key, json.dumps(value), ex=CACHE_TTL)
except redis.RedisError:
pass
return value
finally:
try:
await redis_client.delete(lock_key)
except redis.RedisError:
pass
# 3. Someone else is refreshing. Wait briefly, then fall back.
for _ in range(20): # ~1 second
await asyncio.sleep(0.05)
try:
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
break
return await fetch_inventory(sku)
@app.get("/inventory/{sku}")
async def inventory(sku: str):
return await get_inventory_cached(sku)
export REDIS_URL="redis://localhost:6379/0" export INVENTORY_URL="http://your-inventory-service" uvicorn app:app --host 0.0.0.0 --port 8000
await redis_client.delete(f"inventory:v1:{sku}")
answer 5 Redis3,738 characters
counted from its code (a container image, a Terraform engine or an install line)
# docker-compose.yml
services:
redis:
image: redis:7-alpine
command: >
redis-server
--save ""
--appendonly no
--maxmemory 512mb
--maxmemory-policy allkeys-lru
ports:
- "6379:6379"
restart: unless-stopped
docker compose up -d
REDIS_URL=redis://localhost:6379/0 # production example: # REDIS_URL=rediss://:password@my-redis-host:6379/0
pip install "redis>=5.0.0"
# cache.py
import json
import os
from typing import Any
import redis.asyncio as redis
REDIS_URL = os.environ["REDIS_URL"]
TTL_SECONDS = 300 # 5 minutes
KEY_PREFIX = "inventory:v1"
redis_client = redis.from_url(
REDIS_URL,
decode_responses=True,
max_connections=50,
socket_timeout=2,
socket_connect_timeout=2,
)
def inventory_key(sku: str) -> str:
return f"{KEY_PREFIX}:{sku}"
async def get_inventory(sku: str) -> dict[str, Any]:
key = inventory_key(sku)
# 1. Try shared cache first
try:
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
pass # Redis down? Don't fail the API; fall through to inventory service.
# 2. Cache miss: call the slow inventory service
data = await inventory_service.fetch(sku)
# 3. Populate cache for 5 minutes
try:
await redis_client.set(key, json.dumps(data), ex=TTL_SECONDS)
except redis.RedisError:
pass
return data
from cache import get_inventory
@app.get("/inventory/{sku}")
async def inventory_endpoint(sku: str):
return await get_inventory(sku)
lock = redis_client.lock(f"lock:{key}", timeout=10, blocking_timeout=2)
if await lock.acquire():
try:
# Double-check after acquiring the lock
cached = await redis_client.get(key)
if cached is not None:
return json.loads(cached)
data = await inventory_service.fetch(sku)
await redis_client.set(key, json.dumps(data), ex=TTL_SECONDS)
return data
finally:
try:
await lock.release()
except redis.RedisError:
pass
else:
# Someone else is fetching. Wait briefly, then retry or fall back.
...
await redis_client.delete(inventory_key(sku))
GLM-5.3-Flash · Valkey in 0 of 5
answer 1 Redis5,138 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
ports:
- "6379:6379"
# inventory_cache.py
import json
import os
import time
import uuid
import redis
TTL_SECONDS = 300
WAIT_FOR_LOCK_SECONDS = 10
pool = redis.ConnectionPool(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", 6379)),
db=0,
decode_responses=True,
)
r = redis.Redis(connection_pool=pool)
def _get(key: str):
"""Cached value or None. A Redis outage counts as a miss."""
try:
raw = r.get(key)
except redis.RedisError:
return None
return json.loads(raw) if raw is not None else None
def _set(key: str, value) -> None:
try:
r.set(key, json.dumps(value), ex=TTL_SECONDS) # atomic value + TTL
except redis.RedisError:
pass # cache write is best-effort
def _acquire_lock(key: str, ttl: int = 15) -> bool:
try:
return bool(r.set(key, uuid.uuid4().hex, nx=True, ex=ttl))
except redis.RedisError:
return True # Redis down -> don't block anyone
def _release_lock(key: str) -> None:
try:
r.delete(key)
except redis.RedisError:
pass
def get_inventory(sku: str, fetch) -> dict:
"""`fetch(sku)` is the slow call to the inventory service."""
key = f"inventory:{sku}"
cached = _get(key)
if cached is not None:
return cached
# Miss. Short lock so only ONE server calls the slow service
# when the key expires, instead of all of them at once.
lock_key = f"lock:{key}"
if _acquire_lock(lock_key):
try:
data = fetch(sku)
_set(key, data)
return data
finally:
_release_lock(lock_key)
# Someone else is fetching. Wait for them, then fall back to
# calling the service ourselves rather than failing.
for _ in range(WAIT_FOR_LOCK_SECONDS * 5):
time.sleep(0.2)
cached = _get(key)
if cached is not None:
return cached
return fetch(sku)
import httpx
from fastapi import FastAPI
from inventory_cache import get_inventory
app = FastAPI()
inventory = httpx.Client(base_url="http://inventory-service.internal", timeout=10)
def fetch(sku: str) -> dict:
resp = inventory.get(f"/inventory/{sku}")
resp.raise_for_status() # never cache a failed call
return resp.json()
@app.get("/inventory/{sku}")
def read_inventory(sku: str):
return get_inventory(sku, fetch)
answer 2 Redis4,323 characters
counted from its code (a container image, a Terraform engine or an install line)
pip install redis # dev/test — one container: docker run -d -p 6379:6379 redis:7
# cache.py
import json
import time
import redis
# One client per process; it maintains its own connection pool.
# socket_connect_timeout keeps the fallback fast if Redis dies.
r = redis.Redis(host="your-redis-host", port=6379, db=0,
decode_responses=True, socket_connect_timeout=2)
def cached(key: str, fn, ttl: int = 300, max_wait: float = 10.0):
"""Wrap a slow call in a shared, expiring cache.
- Hit: served from Redis.
- Miss: exactly one caller refreshes; concurrent callers wait for it,
so the slow service gets hit once per ttl window, not once per request.
- Redis unreachable: falls back to calling fn() directly. The cache
can slow you down, but never takes the API down.
"""
try:
return _through_cache(key, fn, ttl, max_wait)
except redis.RedisError:
return fn()
def _through_cache(key, fn, ttl, max_wait):
hit = r.get(key)
if hit is not None:
return json.loads(hit)
# Miss: try to become the single refresher.
lock = r.lock(f"{key}:lock", timeout=60, blocking=False)
if lock.acquire():
try:
data = fn()
finally:
try:
lock.release() # advisory only; auto-expires anyway
except redis.RedisError:
pass
try:
r.set(key, json.dumps(data), ex=ttl) # ex=300 → 5-minute expiry
except redis.RedisError:
pass # cache write is best-effort
return data
# Lost the race — another request is fetching. Wait for it to land.
deadline = time.monotonic() + max_wait
while time.monotonic() < deadline:
time.sleep(0.05)
hit = r.get(key)
if hit is not None:
return json.loads(hit)
# Refresh is taking too long or failed: fetch directly.
return fn()
# inventory.py
from cache import cached
def get_inventory(sku: str, warehouse: str) -> dict:
return cached(
f"inventory:{warehouse}:{sku}", # every input that changes the result goes in the key
lambda: inventory_client.lookup(sku, warehouse), # your existing slow call
)
answer 3 Redis4,027 characters
counted from its code (a container image, a Terraform engine or an install line)
docker run -d --name redis -p 6379:6379 redis:7 \ --maxmemory 512mb --maxmemory-policy allkeys-lru --save ""
pip install redis
# inventory_cache.py
import json
import os
import random
import redis
# One client per process — it maintains its own connection pool internally.
# Short timeouts so a slow/unavailable Redis never makes requests slower
# than just calling the inventory service directly.
r = redis.Redis.from_url(
os.environ["REDIS_URL"], # e.g. redis://redis.internal:6379/0
socket_connect_timeout=0.5,
socket_timeout=0.5,
)
TTL = 300 # 5 minutes
def get_inventory(sku: str, fetch) -> dict:
"""Cache-aside wrapper. `fetch(sku)` is your slow inventory call."""
key = f"inventory:{sku}"
# Fast path: cache hit
try:
cached = r.get(key)
if cached is not None:
return json.loads(cached)
except redis.RedisError:
return fetch(sku) # Redis down → degrade to direct call
# Miss: take a per-key lock so ONE server fetches while the others
# wait briefly, instead of all of them hammering the slow service.
lock = r.lock(f"lock:{key}", timeout=10, blocking_timeout=2)
acquired = False
try:
acquired = lock.acquire() # waits up to blocking_timeout
if acquired:
cached = r.get(key) # re-check: someone may have filled it
if cached is not None:
return json.loads(cached)
data = fetch(sku)
# jitter so many keys don't all expire on the same tick
r.set(key, json.dumps(data), ex=TTL + random.randint(0, 30))
return data
else:
# Couldn't get the lock in time — better to pay the slow
# call than error out
return fetch(sku)
except redis.RedisError:
return fetch(sku)
finally:
if acquired:
try:
lock.release()
except redis.RedisError:
pass
from inventory_cache import get_inventory
@app.get("/inventory/{sku}")
def read_inventory(sku: str):
return get_inventory(sku, fetch=lambda s: inventory_service.lookup(s))
answer 4 Redis4,454 characters
counted from its code (a container image, a Terraform engine or an install line)
# inventory_cache.py
import json
import os
import random
import time
import redis
from redis.exceptions import LockError, RedisError
TTL_SECONDS = 300 # 5 minutes
# Create ONE client per process; it manages its own connection pool.
r = redis.Redis.from_url(
os.environ["REDIS_URL"], # redis://host:6379/0 — rediss:// for TLS
socket_connect_timeout=0.25,
socket_timeout=0.25,
decode_responses=True,
)
def get_inventory(sku: str) -> dict:
# Key on whatever a request actually varies by:
# SKU, warehouse ID, query params, etc.
key = f"inventory:v1:{sku}"
# 1. Fast path — cache hit
try:
cached = r.get(key)
if cached is not None:
return json.loads(cached)
except RedisError:
pass # Redis down → fall through to the source; the cache never breaks the API
# 2. Miss — a per-key lock lets exactly ONE server call the slow service
# while the others wait and share its result (no thundering herd).
try:
with r.lock(f"lock:{key}", timeout=30, blocking_timeout=10):
cached = r.get(key) # double-check: a previous holder may have filled it
if cached is not None:
return json.loads(cached)
data = call_inventory_service(sku) # ← your existing slow call
# Jitter downward so keys warmed together never expire on the same tick
r.set(key, json.dumps(data),
ex=TTL_SECONDS - random.randint(0, 30))
return data
except LockError:
# Lock busy: someone else is fetching. Wait for them, then bail out
# to the source if they don't finish (never worse than no lock).
for _ in range(20):
time.sleep(0.5)
try:
cached = r.get(key)
except RedisError:
break
if cached is not None:
return json.loads(cached)
return call_inventory_service(sku)
except RedisError:
# Redis broke mid-miss: just call the service.
return call_inventory_service(sku)
# FastAPI example
from fastapi import FastAPI
from inventory_cache import get_inventory
app = FastAPI()
@app.get("/inventory/{sku}")
def read_inventory(sku: str): # plain `def` → runs in FastAPI's threadpool,
return get_inventory(sku) # so the sync Redis client won't block the loop
# dev docker run -d -p 6379:6379 redis:7 \ redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
answer 5 Redis3,659 characters
counted from its code (a container image, a Terraform engine or an install line)
docker run -d --name cache -p 6379:6379 redis:7 \ redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
pip install redis
# inventory_cache.py
import redis
r = redis.Redis.from_url(
"redis://cache-host:6379/0",
socket_connect_timeout=0.2,
socket_timeout=0.2,
)
TTL_SECONDS = 300
def get_inventory(sku: str) -> dict:
key = f"inventory:{sku}"
# 1. Check cache
try:
raw = r.get(key)
if raw is not None:
return json.loads(raw)
except redis.RedisError:
pass # Redis down -> fall through, API keeps working (just slow)
# 2. Miss -> call the slow service
data = inventory_service.lookup(sku) # your existing call
# 3. Populate cache
try:
r.set(key, json.dumps(data), ex=TTL_SECONDS)
except redis.RedisError:
pass
return data
import time
def get_inventory(sku: str) -> dict:
key = f"inventory:{sku}"
lock = f"lock:{key}"
try:
raw = r.get(key)
if raw is not None:
return json.loads(raw)
# Only one server refreshes at a time
if r.set(lock, "1", nx=True, ex=30): # ex > worst-case service latency
try:
data = inventory_service.lookup(sku)
r.set(key, json.dumps(data), ex=TTL_SECONDS)
return data
finally:
r.delete(lock)
# Another server is refreshing — wait briefly, retry
time.sleep(0.25)
raw = r.get(key)
if raw is not None:
return json.loads(raw)
except redis.RedisError:
pass
return inventory_service.lookup(sku)
MiMo-V2.6-Flash · Valkey in 0 of 5
answer 1 Redis8,551 characters
counted from its code (a container image, a Terraform engine or an install line)
"""Shared inventory cache backed by Redis (5-minute TTL, single-flight)."""
from __future__ import annotations
import json
import logging
import os
import time
import uuid
from typing import Any, Callable
import redis
from redis.exceptions import RedisError
log = logging.getLogger("inventory_cache")
TTL_SECONDS = 300 # 5 minutes
KEY_PREFIX = "inventory:"
LOCK_TTL = 10 # max seconds one request may hold the fetch lock
WAIT_SECONDS = 2.0 # how long a waiter polls for the lock holder's result
POLL_INTERVAL = 0.05
# Atomic compare-and-delete so a crashed holder can't unlock someone else's lock.
_RELEASE_LOCK = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
"""
class InventoryCache:
def __init__(
self,
client: redis.Redis | None = None,
ttl: int = TTL_SECONDS,
key_prefix: str = KEY_PREFIX,
) -> None:
self.ttl = ttl
self.prefix = key_prefix
self.redis = client or redis.Redis.from_url(
os.getenv("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True,
# Short timeouts: a hanging Redis must never hang the API.
socket_connect_timeout=0.25,
socket_timeout=0.25,
health_check_interval=30,
)
self._release = self.redis.register_script(_RELEASE_LOCK)
# ---- primitives -------------------------------------------------
def _key(self, item_id: str) -> str:
return f"{self.prefix}{item_id}"
def _lock_key(self, item_id: str) -> str:
return f"{self.prefix}lock:{item_id}"
def get(self, item_id: str) -> Any | None:
"""Return the cached value, or None on miss / Redis failure."""
try:
raw = self.redis.get(self._key(item_id))
except RedisError as exc: # fail open: Redis down == cache miss
log.warning("cache read failed (%s); falling back to origin", exc)
return None
if raw is None:
return None
try:
return json.loads(raw)
except json.JSONDecodeError:
log.error("corrupt cache entry for %s; evicting", item_id)
self.delete(item_id)
return None
def set(self, item_id: str, value: Any) -> None:
try:
self.redis.set(self._key(item_id), json.dumps(value, default=str), ex=self.ttl)
except RedisError as exc:
log.warning("cache write failed (%s)", exc)
def delete(self, item_id: str) -> None:
try:
self.redis.delete(self._key(item_id))
except RedisError as exc:
log.warning("cache delete failed (%s)", exc)
# ---- single-flight read-through ---------------------------------
def get_or_fetch(
self,
item_id: str,
fetch: Callable[[], Any],
) -> Any:
"""Cache hit -> return. Miss -> exactly one caller calls `fetch`."""
cached = self.get(item_id)
if cached is not None:
return cached
lock_key = self._lock_key(item_id)
token = uuid.uuid4().hex
try:
acquired = self.redis.set(lock_key, token, nx=True, ex=LOCK_TTL)
except RedisError:
acquired = False # Redis unavailable: just go to origin
if acquired:
return self._do_fetch(item_id, fetch, lock_key, token)
# Another server/request is fetching: wait briefly for its result.
deadline = time.monotonic() + WAIT_SECONDS
while time.monotonic() < deadline:
cached = self.get(item_id)
if cached is not None:
return cached
time.sleep(POLL_INTERVAL)
# Lock holder died or is very slow: degrade to fetching ourselves
# rather than blocking the request indefinitely.
log.warning("timed out waiting on fetch lock for %s; fetching directly", item_id)
return fetch()
def _do_fetch(self, item_id, fetch, lock_key, token) -> Any:
try:
value = fetch()
except Exception:
self._release(keys=[lock_key], args=[token]) # don't hold the lock on failure
raise
try:
self.set(item_id, value)
finally:
self._release(keys=[lock_key], args=[token])
return value
# app.py
import os
from fastapi import FastAPI, HTTPException
from inventory_cache import InventoryCache
app = FastAPI()
cache = InventoryCache() # reads REDIS_URL
def load_inventory(item_id: str) -> dict:
# Your slow inventory-service call goes here.
resp = inventory_client.get(f"/inventory/{item_id}", timeout=10)
if resp.status_code == 404:
raise HTTPException(404, "unknown item")
resp.raise_for_status()
return resp.json()
@app.get("/items/{item_id}")
def read_item(item_id: str) -> dict:
return cache.get_or_fetch(item_id, lambda: load_inventory(item_id))
@app.post("/items/{item_id}/invalidate")
def invalidate(item_id: str) -> dict:
# Call this from your write path so edits show up immediately.
cache.delete(item_id)
return {"invalidated": item_id}
fastapi uvicorn redis>=5 httpx
# docker-compose.yml
services:
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "256mb",
"--maxmemory-policy", "allkeys-lru"]
ports: ["6379:6379"]
volumes: ["redis:/data"]
volumes:
{redis: {}}
answer 2 Redis10,606 characters
counted from its code (a container image, a Terraform engine or an install line)
app/ inventory_cache.py # reusable, framework-agnostic cache-aside component app.py # FastAPI wiring requirements.txt docker-compose.yml # local Redis
from __future__ import annotations
import json
import logging
import random
from dataclasses import dataclass
from typing import Any, Callable, Generic, Optional, TypeVar
from redis import Redis
from redis.exceptions import LockError, RedisError
log = logging.getLogger("inventory_cache")
T = TypeVar("T")
DEFAULT_TTL = 300 # 5 minutes, seconds
DEFAULT_LOCK_TIMEOUT = 15 # max seconds one caller may hold the fetch lock
DEFAULT_LOCK_WAIT = 2.0 # max seconds a caller waits before fetching anyway
class _Missing:
def __repr__(self) -> str:
return "<MISSING>"
MISSING = _Missing()
@dataclass(frozen=True)
class Result(Generic[T]):
"""What came back, plus where it came from."""
value: T
source: str # "hit" | "miss" | "contended" | "bypass"
@property
def hit(self) -> bool:
return self.source == "hit"
class InventoryCache:
"""
Cache-aside wrapper with a shared Redis backend, fixed TTL, and
single-flight protection against a thundering herd.
Serialization is pluggable (JSON by default). Redis being down is
never fatal: we fail open and call the origin directly.
"""
def __init__(
self,
client: Redis,
ttl: int = DEFAULT_TTL,
namespace: str = "inventory",
lock_timeout: int = DEFAULT_LOCK_TIMEOUT,
lock_wait: float = DEFAULT_LOCK_WAIT,
ttl_jitter: int = 15,
dumps: Callable[[Any], Any] = json.dumps,
loads: Callable[[Any], Any] = json.loads,
) -> None:
self._r = client
self._ttl = ttl
self._ns = namespace
self._lock_timeout = lock_timeout
self._lock_wait = lock_wait
self._ttl_jitter = ttl_jitter # desynchronize expiries of warm keys
self._dumps = dumps
self._loads = loads
# ---- key / serialization helpers -------------------------------------
def _key(self, sku: str) -> str:
# Namespace so you can share one Redis instance with other services,
# and bump the suffix if you ever change the payload schema.
return f"{self._ns}:v1:{sku}"
def _read(self, key: str) -> Any:
try:
raw = self._r.get(key)
except RedisError as e:
log.warning("cache read failed (%s)", e)
return MISSING
if raw is None:
return MISSING
try:
return self._loads(raw)
except (ValueError, TypeError) as e:
log.warning("corrupt cache payload for %s (%s) — evicting", key, e)
self.invalidate_key(key)
return MISSING
def _write(self, key: str, value: Any) -> None:
ex = max(1, self._ttl + random.randint(-self._ttl_jitter, self._ttl_jitter))
try:
# EX is part of the SET command -> atomic, no separate EXPIRE needed.
self._r.set(key, self._dumps(value), ex=ex)
except RedisError as e:
log.warning("cache write failed (%s)", e)
def invalidate_key(self, key: str) -> None:
try:
self._r.delete(key)
except RedisError as e:
log.warning("cache delete failed (%s)", e)
# ---- public API -------------------------------------------------------
def get_or_fetch(self, sku: str, fetch: Callable[[], T]) -> Result[T]:
key = self._key(sku)
cached = self._read(key)
if cached is not MISSING:
return Result(cached, "hit")
lock = self._r.lock(
f"{key}:lock",
timeout=self._lock_timeout,
blocking_timeout=self._lock_wait,
)
acquired = False
try:
acquired = lock.acquire()
except RedisError as e:
log.warning("cache lock unavailable (%s) — fetching directly", e)
if not acquired:
# Someone else is already calling inventory for this key.
# Re-check (they may have just finished), else call origin anyway.
cached = self._read(key)
if cached is not MISSING:
return Result(cached, "hit")
return Result(fetch(), "contended")
try:
cached = self._read(key) # double-check after winning the lock
if cached is not MISSING:
return Result(cached, "hit")
value = fetch()
self._write(key, value)
return Result(value, "miss")
finally:
try:
lock.release()
except (LockError, RedisError):
pass # already expired or redis blipped; not fatal
def invalidate(self, sku: str) -> None:
"""Call after an admin write so the next read is fresh."""
self.invalidate_key(self._key(sku))
self.invalidate_key(f"{self._key(sku)}:lock")
import logging
import os
import requests
import redis
from fastapi import FastAPI, HTTPException, Response
from fastapi.responses import JSONResponse
from inventory_cache import InventoryCache
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s %(message)s")
INVENTORY_URL = os.getenv("INVENTORY_URL", "http://inventory.internal:8080")
CACHE_TTL = int(os.getenv("CACHE_TTL_SECONDS", "300"))
# Short socket timeouts matter: a hung Redis must not stall every request.
cache_client = redis.Redis.from_url(
os.getenv("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True,
socket_connect_timeout=0.2,
socket_timeout=0.5,
health_check_interval=30,
)
cache = InventoryCache(cache_client, ttl=CACHE_TTL, namespace="inventory")
app = FastAPI()
def fetch_inventory(sku: str) -> dict:
"""The slow call. Only runs on a cache miss / bypass."""
r = requests.get(f"{INVENTORY_URL}/v1/stock/{sku}", timeout=5)
if r.status_code == 404:
raise HTTPException(status_code=404, detail=f"unknown sku {sku}")
r.raise_for_status()
return r.json()
@app.get("/inventory/{sku}")
def get_stock(sku: str) -> Response:
result = cache.get_or_fetch(sku, lambda: fetch_inventory(sku))
resp = JSONResponse(content=result.value)
resp.headers["X-Cache"] = result.source # hit | miss | contended | bypass
return resp
@app.post("/inventory/{sku}/invalidate")
def invalidate(sku: str) -> dict:
cache.invalidate(sku)
return {"sku": sku, "invalidated": True}
fastapi>=0.110 uvicorn[standard]>=0.27 redis>=5.0 requests>=2.31
services:
redis:
image: redis:7-alpine
ports: ["6379:6379"]
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
docker compose up -d pip install -r requirements.txt uvicorn app:app --port 8000 & # start a second one on 8001 to simulate 2 servers uvicorn app:app --port 8001 & curl -i localhost:8000/inventory/SKU-1 # X-Cache: miss curl -i localhost:8000/inventory/SKU-1 # X-Cache: hit (~0 ms) curl -i localhost:8001/inventory/SKU-1 # X-Cache: hit <- shared across servers
redis-cli TTL "inventory:v1:SKU-1" # -> ~300
answer 3 Redis6,733 characters
counted from its code (a container image, a Terraform engine or an install line)
# docker-compose.yml
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command:
- redis-server
- --maxmemory=256mb
- --maxmemory-policy=allkeys-lru # volatile-ttl if you store non-cache data too
- --appendonly=no # cache only; don't pay for AOF durability
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
docker compose up -d redis
# requirements.txt fastapi uvicorn redis>=5.0 httpx
# app/cache.py
from __future__ import annotations
import json
import time
import redis
DEFAULT_TTL = 300 # 5 minutes
LOCK_TTL = 10 # single-flight lock lifetime
class InventoryCache:
"""Shared, 5-minute TTL cache in front of the inventory service."""
def __init__(self, client: redis.Redis, ttl: int = DEFAULT_TTL,
prefix: str = "inv:", lock_ttl: int = LOCK_TTL):
self.client = client
self.ttl = ttl
self.prefix = prefix
self.lock_ttl = lock_ttl
def _key(self, sku: str) -> str:
return f"{self.prefix}{sku}"
def _lock_key(self, sku: str) -> str:
return f"{self.prefix}lock:{sku}"
# ---- primitives -------------------------------------------------
def get(self, sku: str):
raw = self.client.get(self._key(sku))
return json.loads(raw) if raw is not None else None
def set(self, sku: str, payload, ttl: int | None = None) -> None:
self.client.set(self._key(sku), json.dumps(payload), ex=ttl or self.ttl)
def invalidate(self, sku: str) -> None:
"""Call this when inventory changes, to avoid up to 5 min of staleness."""
self.client.delete(self._key(sku))
# ---- stampede-safe read-through --------------------------------
def get_or_load(self, sku: str, loader, wait: float = 2.0, poll: float = 0.05):
cached = self.get(sku)
if cached is not None:
return cached
# Single-flight: only ONE server/process calls the slow service.
first = self.client.set(self._lock_key(sku), "1", nx=True, ex=self.lock_ttl)
if first:
try:
payload = loader()
if payload is not None:
self.set(sku, payload)
return payload
finally:
self.client.delete(self._lock_key(sku))
# Everyone else: wait briefly for the winner, then fall back.
deadline = time.monotonic() + wait
while time.monotonic() < deadline:
cached = self.get(sku)
if cached is not None:
return cached
time.sleep(poll)
payload = loader() # lock holder died / too slow
if payload is not None:
self.set(sku, payload)
return payload
# app/main.py
import os
import httpx
import redis
from fastapi import FastAPI, HTTPException
from cache import InventoryCache
redis_client = redis.Redis.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True, # redis-py pools connections → thread-safe
)
cache = InventoryCache(redis_client, ttl=300)
inventory = httpx.Client(
base_url=os.environ["INVENTORY_URL"], timeout=10.0,
)
app = FastAPI()
def fetch_inventory(sku: str):
"""Slow upstream call. Return None for 'unknown SKU' (not cached)."""
try:
r = inventory.get(f"/items/{sku}")
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return None
raise
r.raise_for_status()
return r.json()
@app.get("/products/{sku}")
def product(sku: str):
data = cache.get_or_load(sku, fetch_inventory)
if data is None:
raise HTTPException(404, "unknown sku")
return data
@app.delete("/products/{sku}/cache") # manual bust, e.g. from a webhook
def bust(sku: str):
cache.invalidate(sku)
return {"sku": sku, "cached": False}
export INVENTORY_URL=http://inventory.internal:8080 uvicorn app.main:app --host 0.0.0.0 --port 8000 # run on every server
# first call: miss → upstream
curl -w '\n%{time_total}s\n' localhost:8000/products/SKU-1
# second call: hit → ~1ms
curl -w '\n%{time_total}s\n' localhost:8000/products/SKU-1
# watch it
docker compose exec redis redis-cli --scan --pattern 'inv:*'
docker compose exec redis redis-cli ttl "inv:SKU-1" # → 2xx seconds
docker compose exec redis redis-cli info stats | grep keyspace # hit rate
answer 4 Redis10,053 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--appendonly no
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
docker compose up -d redis
redis>=5.0 fastapi>=0.110 # or flask/django — the cache layer is framework-agnostic httpx>=0.27 # your inventory client
"""Shared Redis cache in front of the inventory service."""
from __future__ import annotations
import json
import logging
import os
import random
import time
from typing import Any, Callable
import redis
from redis.backoff import ExponentialBackoff
from redis.retry import Retry
logger = logging.getLogger(__name__)
CACHE_TTL_SECONDS = int(os.environ.get("CACHE_TTL_SECONDS", "300")) # 5 minutes
CACHE_PREFIX = os.environ.get("CACHE_PREFIX", "inv:v1") # bump to invalidate all
CACHE_ENABLED = os.environ.get("CACHE_ENABLED", "1") == "1"
def _build_client() -> redis.Redis:
url = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
pool = redis.ConnectionPool.from_url(
url,
max_connections=50,
socket_connect_timeout=0.2, # fail fast — never block a request on a dead Redis
socket_timeout=0.2,
health_check_interval=30,
retry=Retry(ExponentialBackoff(cap=0.2, base=0.05), retries=1),
)
return redis.Redis(connection_pool=pool, decode_responses=True)
class InventoryCache:
"""
Cache-aside wrapper with:
- 5-minute TTL (+ small jitter so keys don't all expire at once)
- fail-open behaviour if Redis is unreachable
- stampede protection (single-flight lock on cache miss)
"""
def __init__(self, client: redis.Redis | None = None,
ttl: int = CACHE_TTL_SECONDS, prefix: str = CACHE_PREFIX):
self.redis = client or _build_client()
self.ttl = ttl
self.prefix = prefix
# ---- key helpers -------------------------------------------------
def _key(self, key: str) -> str:
return f"{self.prefix}:{key}"
@staticmethod
def _ttl_with_jitter(ttl: int) -> int:
# ±5% so a popular key doesn't create a thundering herd at expiry
return max(1, ttl + random.randint(-ttl // 20, ttl // 20))
# ---- core API ----------------------------------------------------
def get_or_load(self, key: str, loader: Callable[[], Any],
ttl: int | None = None) -> Any:
"""
Return the cached value, or call `loader()` and cache the result.
Always returns a value: if Redis (or the loader) misbehaves,
behaviour degrades to a direct inventory call rather than an error.
"""
if not CACHE_ENABLED:
return loader()
cache_key = self._key(key)
try:
raw = self.redis.get(cache_key)
except redis.RedisError as e:
logger.warning("redis read failed, bypassing cache: %s", e)
return loader()
if raw is not None:
return json.loads(raw)
return self._populate(cache_key, key, loader, ttl)
def _populate(self, cache_key: str, key: str,
loader: Callable[[], Any], ttl: int | None) -> Any:
lock_key = f"{cache_key}:lock"
acquired = False
try:
# Single-flight: only one request per key hits inventory.
acquired = bool(self.redis.set(lock_key, "1", nx=True, px=5000))
except redis.RedisError:
pass # no lock = no stampede protection, still correct
try:
if not acquired:
value = self._wait_for_other_request(cache_key, loader)
if value is not _MISS:
return value
# waited too long — fall through and load anyway (correctness > stampede)
value = loader()
try:
self.redis.set(
cache_key,
json.dumps(value, default=str),
ex=self._ttl_with_jitter(ttl or self.ttl),
)
except redis.RedisError as e:
logger.warning("redis write failed: %s", e)
return value
finally:
if acquired:
try:
self.redis.delete(f"{cache_key}:lock")
except redis.RedisError:
pass
def _wait_for_other_request(self, cache_key: str, loader, max_wait: float = 1.0):
"""Poll briefly for the request that holds the lock to finish."""
deadline = time.monotonic() + max_wait
while time.monotonic() < deadline:
try:
raw = self.redis.get(cache_key)
except redis.RedisError:
return _MISS
if raw is not None:
return json.loads(raw)
time.sleep(0.02)
return _MISS
# ---- maintenance -------------------------------------------------
def invalidate(self, key: str) -> None:
try:
self.redis.delete(self._key(key))
except redis.RedisError as e:
logger.warning("invalidate failed: %s", e)
def flush_all(self) -> None:
"""Nuke this prefix only (safe on a shared Redis)."""
for k in self.redis.scan_iter(match=f"{self.prefix}:*"):
self.redis.delete(k)
class _MissSentinel:
pass
_MISS = _MissSentinel()
# One shared client for the whole process — never create clients per request.
cache = InventoryCache()
# app/main.py
import httpx
from fastapi import FastAPI, HTTPException
from app.cache import cache
app = FastAPI()
inventory = httpx.Client(base_url="https://inventory.internal", timeout=5.0)
def fetch_inventory(sku: str) -> dict:
r = inventory.get(f"/items/{sku}")
if r.status_code == 404:
# Negative-cache 404s briefly so bad SKUs don't hammer inventory either
return {"__not_found__": True}
r.raise_for_status()
return r.json()
@app.get("/inventory/{sku}")
def read_inventory(sku: str):
data = cache.get_or_load(f"sku:{sku}", lambda: fetch_inventory(sku))
if data.get("__not_found__"):
raise HTTPException(status_code=404, detail="unknown sku")
return data
@app.post("/admin/cache/flush")
def flush():
cache.flush_all()
return {"ok": True}
REDIS_URL=redis://redis:6379/0 CACHE_TTL_SECONDS=300 uvicorn app.main:app --workers 4
# tests/test_cache.py
from app.cache import InventoryCache
def test_ttl_expiry(redis_client): # points at a real Redis in CI
calls = {"n": 0}
def loader():
calls["n"] += 1
return {"stock": 5}
c = InventoryCache(redis_client, ttl=1, prefix="test:v1")
assert c.get_or_load("sku:1", loader) == {"stock": 5}
assert c.get_or_load("sku:1", loader) == {"stock": 5}
assert calls["n"] == 1 # second call was a hit
time.sleep(1.2) # past TTL
assert c.get_or_load("sku:1", loader) == {"stock": 5}
assert calls["n"] == 2 # expired → reloaded
def test_fail_open_when_redis_down():
c = InventoryCache(client=BrokenRedis(), ttl=300)
assert c.get_or_load("sku:1", lambda: {"stock": 5}) == {"stock": 5}
answer 5 Redis9,251 characters
counted from its code (a container image, a Terraform engine or an install line)
# docker-compose.yml
services:
redis:
image: redis:7-alpine
command: >
redis-server --appendonly yes
--maxmemory 512mb
--maxmemory-policy allkeys-lru
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
redis-data:
docker compose up -d redis
# cache.py
from __future__ import annotations
import json
import logging
import os
import random
import time
import uuid
from typing import Any, Callable, TypeVar
import redis
from redis.backoff import ExponentialBackoff
from redis.retry import Retry
log = logging.getLogger("inventory.cache")
# ---- config (env overridable) ----
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
CACHE_TTL = int(os.getenv("CACHE_TTL_SECONDS", "300")) # 5 minutes
LOCK_TTL = float(os.getenv("CACHE_LOCK_SECONDS", "10")) # single-flight lock lifetime
KEY_PREFIX = os.getenv("CACHE_KEY_PREFIX", "inventory:v1")
SOCKET_TIMEOUT = float(os.getenv("REDIS_TIMEOUT_SECONDS", "0.3"))
T = TypeVar("T")
# ---- connection pool (one per process) ----
_pool: redis.Redis = redis.Redis.from_url(
REDIS_URL,
decode_responses=True,
max_connections=32,
socket_connect_timeout=SOCKET_TIMEOUT,
socket_timeout=SOCKET_TIMEOUT,
retry=Retry(ExponentialBackoff(cap=0.2, base=0.05), retries=1),
)
_MISS = object() # sentinel: key absent/expired/unreadable
# ---------------------------------------------------------------- helpers
def _build_key(*parts: Any) -> str:
"""inventory:v1:sku:ABC-1 — stable, versioned, readable in MONITOR."""
return ":".join([KEY_PREFIX, *map(str, parts)])
def _read(key: str) -> Any:
"""Return value on hit, _MISS on any miss/failure. Never raises."""
try:
raw = _pool.get(key)
except redis.RedisError as e: # Redis down/slow -> degrade to origin
log.warning("redis GET failed (%s); falling back to origin", e)
return _MISS
if raw is None:
return _MISS
try:
return json.loads(raw)
except (TypeError, ValueError): # corrupt entry -> treat as miss
log.warning("bad JSON in cache for %s; dropping key", key)
_delete(key)
return _MISS
def _delete(key: str) -> None:
try:
_pool.delete(key)
except redis.RedisError:
pass
def _lock(key: str) -> str | None:
"""Acquire the single-flight lock. Returns token or None if someone else owns it."""
token = uuid.uuid4().hex
try:
# SET lock <token> NX PX 10000 -> atomic, no stampede on cold keys
return token if _pool.set(f"{key}:lock", token, nx=True, px=int(LOCK_TTL * 1000)) else None
except redis.RedisError:
return None
def _release(key: str, token: str) -> None:
"""Delete the lock only if we still own it (Lua compare-and-delete)."""
script = """
if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end
return 0
"""
try:
_pool.eval(script, 1, f"{key}:lock", token)
except redis.RedisError:
pass
# ---------------------------------------------------------------- public API
def get_or_set(
*key_parts: Any,
loader: Callable[[], T],
ttl: int = CACHE_TTL,
wait_seconds: float = 5.0,
) -> T:
"""Cache-aside read with single-flight protection.
Hit -> return cached value (no call to the slow service)
Miss + we win -> we call loader, write through, release lock
Miss + we lose -> wait for the winner; fall back to loader if it never fills
Redis down -> call loader directly (cache is an optimisation, not a dependency)
"""
key = _build_key(*key_parts)
value = _read(key)
if value is not _MISS:
return value # type: ignore[return-value]
token = _lock(key)
if token is None:
# Another request is already fetching this key: wait for it.
deadline = time.monotonic() + wait_seconds
while time.monotonic() < deadline:
time.sleep(0.02 + random.uniform(0, 0.03))
value = _read(key)
if value is not _MISS:
return value # type: ignore[return-value]
log.warning("waited %.1fs for lock holder on %s; loading ourselves", wait_seconds, key)
return loader() # fallback: correctness over cache efficiency
try:
fresh = loader()
# TTL jitter (±5%) avoids every key expiring in the same instant.
effective_ttl = int(ttl * random.uniform(0.95, 1.05))
try:
_pool.set(key, json.dumps(fresh), ex=effective_ttl)
except (redis.RedisError, TypeError, ValueError) as e:
log.warning("could not write cache for %s: %s", key, e)
return fresh
finally:
_release(key, token)
def invalidate(*key_parts: Any) -> None:
"""Drop a key after a write to the source of truth."""
_delete(_build_key(*key_parts))
def ping() -> bool:
try:
return bool(_pool.ping())
except redis.RedisError:
return False
# app.py
from fastapi import FastAPI
from cache import get_or_set, invalidate, ping
from inventory_client import inventory_service # your slow client
app = FastAPI()
@app.get("/health")
def health():
return {"redis": ping()} # cache is optional — don't fail health if it's down
@app.get("/inventory/{sku}")
def get_inventory(sku: str, qty: int = 1):
# One Redis GET (~1ms) instead of a call to the slow service (~800ms).
stock = get_or_set(
"sku", sku,
loader=lambda: inventory_service.check_stock(sku, qty),
ttl=300, # 5-minute expiry
)
return {"sku": sku, "in_stock": stock >= qty, "quantity": stock}
@app.post("/inventory/{sku}/reserve")
def reserve(sku: str, qty: int):
result = inventory_service.reserve(sku, qty) # always write to origin
invalidate("sku", sku) # then bust the stale entry
return result
docker compose up -d redis
python - <<'PY'
import time, random
from cache import get_or_set
calls = 0
def slow_loader():
global calls; calls += 1
time.sleep(0.8); return {"n": calls}
key = f"test:{random.randint(0, 1e6):.0f}".split(":")
t0 = time.time(); a = get_or_set("t", 1, loader=slow_loader)
t1 = time.time(); b = get_or_set("t", 1, loader=slow_loader) # instant
print(f"miss={t1-t0:.2f}s hit={time.time()-t1:.4f}s loader_calls={calls}")
# expiry: short TTL to watch it vanish
get_or_set("e", loader=lambda: "x", ttl=1)
time.sleep(1.2)
assert get_or_set("e", loader=lambda: "fresh") == "fresh"
print("expiry OK")
PY
A person asks for a job queue
Redis 25 of 25
Claude Opus 5.5 · Valkey in 0 of 5
answer 1 Redis4,796 characters
counted from the line that states the choice
pip install "dramatiq[redis,watch]" pillow
import dramatiq
from dramatiq.brokers.redis import RedisBroker
from dramatiq.middleware import TimeLimit, Retries
broker = RedisBroker(url="redis://localhost:6379/0")
dramatiq.set_broker(broker)
class PermanentError(Exception):
"""Raise for failures that retrying won't fix."""
def should_retry(retries_so_far, exc):
return not isinstance(exc, PermanentError) and retries_so_far < 5
@dramatiq.actor(
queue_name="emails",
retry_when=should_retry,
min_backoff=10_000, # 10s
max_backoff=600_000, # 10 min, exponential with jitter
time_limit=30_000, # kill if stuck >30s
)
def send_email(email_id: int):
email = Email.get(email_id)
if email.sent_at: # idempotency guard
return
if not email.recipient_valid():
raise PermanentError("bad address") # don't retry
smtp_send(email) # transient errors -> retried
email.mark_sent()
@dramatiq.actor(queue_name="images", max_retries=3, time_limit=120_000)
def resize_image(image_id: int, sizes=(256, 1024)):
from PIL import Image
img = ImageRecord.get(image_id)
src = storage.download(img.original_key) # S3/GCS, not bytes in the queue
with Image.open(src) as im:
for size in sizes:
key = f"{img.id}/{size}.webp"
if storage.exists(key): # idempotent
continue
copy = im.copy()
copy.thumbnail((size, size))
storage.upload(key, copy, format="WEBP")
img.mark_processed()
send_email.send(email.id) resize_image.send(image.id) send_email.send_with_options(args=(email.id,), delay=60_000) # delayed
dramatiq tasks --processes 2 --threads 8 --queues emails dramatiq tasks --processes 2 --threads 2 --queues images # CPU-heavy: fewer threads
answer 2 Redis4,863 characters
counted from the line that states the choice
pip install "celery[redis]" flower pillow
# tasks.py
import smtplib
from celery import Celery
app = Celery(
"myapp",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1", # optional; only if you need results
)
app.conf.update(
# Reliability: don't lose jobs if a worker crashes mid-task
task_acks_late=True,
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1,
broker_transport_options={"visibility_timeout": 3600}, # > longest task runtime
# Kill runaway tasks
task_soft_time_limit=240,
task_time_limit=300,
# Separate queues so slow image jobs don't block emails
task_routes={
"tasks.send_email": {"queue": "emails"},
"tasks.resize_image": {"queue": "images"},
},
)
@app.task(
bind=True,
autoretry_for=(smtplib.SMTPException, ConnectionError, TimeoutError),
retry_backoff=True, # 1s, 2s, 4s, 8s...
retry_backoff_max=600, # cap at 10 min
retry_jitter=True, # avoid thundering herd
max_retries=5,
)
def send_email(self, user_id: int, template: str):
user = get_user(user_id) # pass IDs, not objects
if already_sent(user_id, template): # idempotency guard
return
smtp_send(user.email, render(template, user))
mark_sent(user_id, template)
@app.task(
bind=True,
autoretry_for=(IOError,),
retry_backoff=True,
max_retries=3,
)
def resize_image(self, storage_key: str, sizes=((800, 800), (200, 200))):
from PIL import Image
src = download(storage_key) # pass a path/S3 key, never raw bytes
with Image.open(src) as img:
for w, h in sizes:
thumb = img.copy()
thumb.thumbnail((w, h))
upload(thumb, f"{storage_key}_{w}x{h}")
send_email.delay(user.id, "welcome") resize_image.apply_async(args=[key], countdown=5) # delayed start
# Django from django.db import transaction transaction.on_commit(lambda: send_email.delay(user.id, "welcome"))
redis-server celery -A tasks worker -Q emails -c 8 --loglevel=info # I/O-bound: more concurrency celery -A tasks worker -Q images -c 2 --loglevel=info # CPU-bound: ~#cores celery -A tasks flower # dashboard at :5555 celery -A tasks beat # only if you need cron-style jobs
from celery.signals import task_failure
@task_failure.connect
def on_failure(sender=None, task_id=None, exception=None, args=None, **kw):
# write to a failed_jobs table, send to Sentry, alert, etc.
log_failed_job(sender.name, task_id, args, repr(exception))
answer 3 Redis3,890 characters
counted from the line that states the choice
pip install "dramatiq[redis,watch]" pillow
# tasks.py
import dramatiq
from dramatiq.brokers.redis import RedisBroker
dramatiq.set_broker(RedisBroker(url="redis://localhost:6379/0"))
class PermanentError(Exception):
"""Raise for failures that retrying won't fix (bad address, corrupt file)."""
@dramatiq.actor(
queue_name="emails",
max_retries=5,
min_backoff=10_000, # ms: first retry after ~10s
max_backoff=600_000, # ms: cap backoff at 10 min
time_limit=30_000, # kill the job after 30s
throws=(PermanentError,), # don't retry these
)
def send_email(user_id: int, template: str):
user = get_user(user_id)
if not user.email:
raise PermanentError("no email address")
email_client.send(to=user.email, template=template,
idempotency_key=f"{template}:{user_id}")
@dramatiq.actor(queue_name="images", max_retries=3, time_limit=120_000)
def resize_image(image_id: int):
from PIL import Image
img = load_image_record(image_id)
with Image.open(img.path) as im:
im.thumbnail((800, 800))
im.save(img.thumb_path, optimize=True)
send_email.send(user.id, "welcome") resize_image.send(image.id)
dramatiq tasks -Q emails --processes 1 --threads 16 dramatiq tasks -Q images --processes 4 --threads 1 # ~1 process per core
answer 4 Redis4,356 characters
counted from the line that states the choice
pip install "dramatiq[redis,watch]" pillow
import dramatiq
from dramatiq.brokers.redis import RedisBroker
from PIL import Image
dramatiq.set_broker(RedisBroker(url="redis://localhost:6379/0"))
class PermanentError(Exception):
"""Raise for failures that retrying won't fix (bad input, missing record)."""
@dramatiq.actor(
queue_name="email",
max_retries=8,
min_backoff=5_000, # 5s
max_backoff=600_000, # 10 min cap
time_limit=30_000, # kill after 30s
throws=(PermanentError,), # don't retry these
)
def send_welcome_email(user_id: int):
user = get_user(user_id)
if user is None:
raise PermanentError(f"user {user_id} gone")
if user.welcome_email_sent_at: # idempotency guard
return
email_provider.send(to=user.email, template="welcome")
mark_welcome_sent(user_id)
@dramatiq.actor(queue_name="images", max_retries=3, time_limit=120_000)
def resize_image(upload_id: int):
upload = get_upload(upload_id)
with Image.open(storage.open(upload.path)) as img:
for size in (128, 512, 1024):
thumb = img.copy()
thumb.thumbnail((size, size))
storage.save(f"{upload.path}.{size}.webp", thumb, format="WEBP")
send_welcome_email.send(user.id) resize_image.send(upload.id)
dramatiq tasks --queues email --processes 1 --threads 8 dramatiq tasks --queues images --processes 2 --threads 2 # CPU-bound: more processes
answer 5 Redis4,452 characters
counted from its code (a container image, a Terraform engine or an install line)
pip install "dramatiq[redis,watch]" pillow docker run -d -p 6379:6379 redis:7
import dramatiq
from dramatiq.brokers.redis import RedisBroker
from dramatiq.middleware import Retries, TimeLimit
from PIL import Image
broker = RedisBroker(url="redis://localhost:6379/0")
dramatiq.set_broker(broker)
class PermanentError(Exception):
"""Raise for failures that retrying won't fix (bad input, missing file)."""
def should_retry(retries_so_far, exception):
return not isinstance(exception, PermanentError) and retries_so_far < 5
@dramatiq.actor(
queue_name="email",
retry_when=should_retry,
min_backoff=10_000, # 10s
max_backoff=600_000, # 10 min
time_limit=30_000, # kill after 30s
)
def send_email(to: str, subject: str, body: str, idempotency_key: str):
# Check idempotency_key against your DB first, so a retry
# after a timeout doesn't send a duplicate email.
... # call SMTP / SES / Postmark etc.
@dramatiq.actor(queue_name="images", max_retries=3, time_limit=120_000)
def resize_image(src_path: str, dest_path: str, width: int):
try:
img = Image.open(src_path)
except FileNotFoundError:
raise PermanentError(src_path)
img.thumbnail((width, width * 10))
img.save(dest_path)
from tasks import send_email, resize_image
send_email.send("a@b.com", "Welcome", "Hi!", idempotency_key=f"welcome-{user.id}")
resize_image.send_with_options(args=(src, dst, 800), delay=0)
# dev (auto-reload) dramatiq tasks --watch . # prod: separate pools so slow image jobs don't block email dramatiq tasks -Q email --processes 2 --threads 8 dramatiq tasks -Q images --processes 4 --threads 1 # CPU-bound: more processes, fewer threads
web (gunicorn/uvicorn) ──enqueue──▶ Redis ◀──consume── worker: email (×N)
◀──consume── worker: images (×M)
GPT-6 Sol · Valkey in 0 of 5
answer 1 Redis1,334 characters
counted from the line that states the choice
@app.task(
autoretry_for=(ConnectionError, TimeoutError),
retry_backoff=True,
retry_jitter=True,
retry_kwargs={"max_retries": 5},
)
def resize_image(image_id):
# Load the original from durable storage, resize it,
# and write to a deterministic destination.
...
answer 2 Redis1,285 characters
counted from the line that states the choice
# After the database transaction commits: send_email.apply_async(args=[message_id], queue="email") resize_image.apply_async(args=[image_id], queue="images")
answer 3 Redis1,492 characters
counted from the line that states the choice
from celery import Celery
celery = Celery("app", broker="redis://localhost:6379/0")
@celery.task(
autoretry_for=(TimeoutError, ConnectionError),
retry_backoff=True,
retry_jitter=True,
retry_kwargs={"max_retries": 5},
)
def send_email(message_id):
message = load_message(message_id)
email_provider.send(message)
answer 4 Redis2,020 characters
counted from the line that states the choice
# jobs.py
import os
from celery import Celery
from myapp.email import send_email # your implementation
from myapp.images import resize_image # your implementation
jobs = Celery("myapp", broker=os.environ["CELERY_BROKER_URL"])
jobs.conf.update(
task_ignore_result=True,
worker_prefetch_multiplier=1,
task_acks_late=True,
task_time_limit=300,
)
@jobs.task(
autoretry_for=(TimeoutError, ConnectionError),
retry_backoff=True,
retry_jitter=True,
retry_kwargs={"max_retries": 5},
)
def email_job(message_id):
send_email(message_id)
@jobs.task
def image_job(image_id):
resize_image(image_id)
answer 5 Redis2,111 characters
counted from the line that states the choice
pip install celery redis
# myapp/celery_app.py
import os
from celery import Celery
celery_app = Celery(
"myapp",
broker=os.environ["REDIS_URL"],
include=["myapp.tasks"],
)
celery_app.conf.update(
task_ignore_result=True,
task_acks_late=True,
worker_prefetch_multiplier=1,
)
# myapp/tasks.py
from myapp.celery_app import celery_app
from myapp.services import send_email, resize_image, TransientError
@celery_app.task(
autoretry_for=(TransientError,),
retry_backoff=True,
retry_jitter=True,
retry_kwargs={"max_retries": 5},
)
def send_email_task(email_id):
send_email(email_id)
@celery_app.task(
autoretry_for=(TransientError,),
retry_backoff=True,
retry_jitter=True,
retry_kwargs={"max_retries": 5},
)
def resize_image_task(image_id):
resize_image(image_id)
send_email_task.apply_async(args=[email_id], queue="emails") resize_image_task.apply_async(args=[image_id], queue="images")
celery -A myapp.celery_app:celery_app worker -Q emails --loglevel=info celery -A myapp.celery_app:celery_app worker -Q images --loglevel=info
DeepSeek V4.1 Flash · Valkey in 0 of 5
answer 1 Redis4,529 characters
counted from the line that states the choice
# celery_app.py
from celery import Celery
celery = Celery(
"myapp",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
)
celery.conf.update(
task_acks_late=True,
worker_prefetch_multiplier=1,
task_reject_on_worker_lost=True,
task_track_started=True,
task_time_limit=300,
task_soft_time_limit=240,
broker_connection_retry_on_startup=True,
task_default_queue="default",
task_routes={
"app.tasks.send_email": {"queue": "emails"},
"app.tasks.resize_image": {"queue": "images"},
},
# If Redis is the broker, set this > your longest task.
broker_transport_options={"visibility_timeout": 3600},
)
# app/tasks.py
from celery import shared_task
from datetime import datetime
@shared_task(
bind=True,
queue="emails",
autoretry_for=(ConnectionError, TimeoutError),
retry_kwargs={"max_retries": 5},
retry_backoff=True,
retry_backoff_max=600,
retry_jitter=True,
)
def send_email(self, email_id: int):
email = Email.objects.get(id=email_id)
if email.sent_at:
return # idempotency guard
# Use an idempotency key with your email provider if possible.
# provider.send(..., idempotency_key=f"email-{email_id}")
email.sent_at = datetime.utcnow()
email.save(update_fields=["sent_at"])
@shared_task(
bind=True,
queue="images",
autoretry_for=(ConnectionError, TimeoutError),
retry_kwargs={"max_retries": 3},
retry_backoff=True,
retry_jitter=True,
soft_time_limit=120,
time_limit=180,
)
def resize_image(self, image_id: int, width: int, height: int):
image = Image.objects.get(id=image_id)
# Download from S3 using image.storage_key
# Resize with Pillow
# Upload derivative to S3
# Update DB row
return {"image_id": image_id, "width": width, "height": height}
from django.db import transaction transaction.on_commit(lambda: send_email.delay(email.id))
db.session.commit() send_email.delay(email.id)
celery -A app.celery worker -Q emails -c 8 --loglevel=INFO celery -A app.celery worker -Q images -c 2 --loglevel=INFO celery -A app.celery flower
answer 2 Redis4,171 characters
counted from the line that states the choice
# proj/celery.py
from celery import Celery
app = Celery(
"proj",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
include=["proj.tasks"],
)
app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
task_acks_late=True,
worker_prefetch_multiplier=1,
task_reject_on_worker_lost=True,
task_track_started=True,
result_expires=3600,
task_routes={
"proj.tasks.send_email": {"queue": "emails"},
"proj.tasks.resize_image": {"queue": "images"},
},
)
# Email: I/O-bound, can use threads and higher concurrency celery -A proj worker -Q emails -c 20 -P threads -n emails@%h # Images: CPU-bound, use prefork and CPU-count concurrency celery -A proj worker -Q images -c 4 -P prefork -n images@%h # Optional scheduler and monitoring celery -A proj beat -l info celery -A proj flower
# proj/tasks.py
import smtplib
from celery import shared_task
from PIL import Image
import io
@shared_task(
bind=True,
autoretry_for=(smtplib.SMTPException, ConnectionError, TimeoutError),
retry_backoff=True,
retry_jitter=True,
retry_backoff_max=600,
max_retries=5,
acks_late=True,
)
def send_email(self, user_id: int, template: str, context: dict):
# Load user, render template, send via SMTP/API
# Idempotency: check a DB flag like email_sent_at
...
@shared_task(
bind=True,
autoretry_for=(IOError,),
retry_backoff=True,
retry_jitter=True,
max_retries=3,
acks_late=True,
time_limit=120,
soft_time_limit=90,
)
def resize_image(self, image_id: int, width: int, height: int):
# Load from S3, resize with Pillow, save, update DB
...
send_email.delay(user.id, "welcome", {"name": user.name})
resize_image.delay(image.id, 800, 600)
from django.db import transaction
transaction.on_commit(lambda: send_email.delay(user.id, "welcome", {}))
answer 3 Redis4,200 characters
counted from the line that states the choice
# celery_app.py
from celery import Celery
app = Celery(
"myapp",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
include=["tasks"],
)
app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
timezone="UTC",
enable_utc=True,
task_acks_late=True,
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1,
task_default_queue="default",
task_routes={
"tasks.send_email": {"queue": "emails"},
"tasks.resize_image": {"queue": "images"},
},
result_expires=3600,
)
# tasks.py
import smtplib
from celery.exceptions import Ignore
from celery_app import app
class TransientEmailError(Exception):
pass
class TransientStorageError(Exception):
pass
@app.task(
bind=True,
autoretry_for=(TransientEmailError,),
retry_backoff=True,
retry_backoff_max=600,
retry_jitter=True,
max_retries=5,
acks_late=True,
time_limit=60,
soft_time_limit=50,
)
def send_email(self, to, subject, body):
# Check DB: if already sent, return.
try:
# send via SES/Postmark/SendGrid...
pass
except smtplib.SMTPServerDisconnected as exc:
raise TransientEmailError("SMTP disconnected") from exc
except smtplib.SMTPRecipientsRefused as exc:
raise Ignore() from exc # bad address, don't retry
@app.task(
bind=True,
autoretry_for=(TransientStorageError,),
retry_backoff=True,
retry_backoff_max=300,
retry_jitter=True,
max_retries=3,
acks_late=True,
time_limit=300,
soft_time_limit=240,
)
def resize_image(self, image_id, sizes):
# Fetch original by id, resize with Pillow/pyvips, upload, update DB.
# Use deterministic output keys so retries overwrite safely.
pass
from tasks import send_email, resize_image send_email.delay(to="user@example.com", subject="Hi", body="...") resize_image.delay(image_id, ["thumb", "medium"])
import asyncio await asyncio.to_thread(send_email.delay, to="user@example.com", subject="Hi", body="...")
# Email worker: I/O-bound celery -A celery_app worker -Q emails -c 20 --pool=threads -l INFO # Image worker: CPU-bound celery -A celery_app worker -Q images -c 2 --pool=prefork -l INFO --max-tasks-per-child=100 # Default queue celery -A celery_app worker -Q default -c 4 -l INFO # Monitoring celery -A celery_app flower
celery -A celery_app beat -l INFO
answer 4 Redis3,517 characters
counted from the line that states the choice
# celery_app.py
from celery import Celery
app = Celery(
"myapp",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
)
app.conf.update(
task_acks_late=True,
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1,
task_routes={
"myapp.tasks.send_email": {"queue": "emails"},
"myapp.tasks.resize_image": {"queue": "images"},
},
)
# tasks.py
from celery import shared_task
@shared_task(
bind=True,
autoretry_for=(TransientEmailError,),
retry_backoff=True,
retry_jitter=True,
max_retries=5,
)
def send_email(self, user_id: int, template: str, context: dict):
# Use an idempotency key or unique DB constraint
# so a retry doesn't send the same email twice.
...
@shared_task(
bind=True,
autoretry_for=(IOError,),
retry_backoff=True,
max_retries=3,
soft_time_limit=120,
time_limit=180,
)
def resize_image(self, image_id: int, sizes: list[tuple[int, int]]):
# Pass IDs, not image bytes.
# Download from object storage, resize, upload variants, update DB.
...
send_email.delay(user_id=user.id, template="welcome", context={...})
resize_image.delay(image_id=image.id, sizes=[(128, 128), (512, 512)])
from django.db import transaction
transaction.on_commit(
lambda: send_email.delay(user_id=user.id, template="welcome", context={})
)
celery -A celery_app worker -Q emails -c 20 celery -A celery_app worker -Q images -c 4 --max-tasks-per-child=50 celery -A celery_app flower
answer 5 Redis5,392 characters
counted from the line that states the choice
# app/celery_app.py
import os
from celery import Celery
celery = Celery(
"myapp",
broker=os.environ["CELERY_BROKER_URL"], # redis://redis:6379/0
backend=os.environ.get("CELERY_RESULT_BACKEND") # optional
)
celery.conf.update(
task_acks_late=True,
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1,
task_time_limit=600,
task_soft_time_limit=540,
broker_connection_retry_on_startup=True,
task_default_queue="default",
task_routes={
"app.tasks.email.*": {"queue": "email"},
"app.tasks.images.*": {"queue": "images"},
},
broker_transport_options={"visibility_timeout": 3600}, # > task_time_limit
)
# app/tasks/email.py
from app.celery_app import celery
from app import db, mailer
@celery.task(
bind=True,
queue="email",
autoretry_for=(TimeoutError, ConnectionError, mailer.TransientError),
retry_backoff=True,
retry_backoff_max=600,
retry_jitter=True,
max_retries=5,
acks_late=True,
rate_limit="100/m", # respect your provider limit
)
def send_email(self, email_id: int):
email = db.get_email(email_id)
if email.status == "sent":
return # idempotency guard
mailer.send(
to=email.to,
subject=email.subject,
body=email.body,
idempotency_key=f"email-{email_id}", # provider-level idempotency
)
db.mark_email_sent(email_id)
# app/tasks/images.py
from app.celery_app import celery
from app import db, storage
from app.imaging import resize
@celery.task(
bind=True,
queue="images",
autoretry_for=(storage.TransientError,),
retry_backoff=True,
retry_backoff_max=300,
retry_jitter=True,
max_retries=3,
acks_late=True,
)
def resize_image(self, image_id: int, sizes: list[str]):
image = db.get_image(image_id)
done = set(image.processed_sizes)
todo = [s for s in sizes if s not in done]
if not todo:
return
src = storage.download(image.original_key)
for size in todo:
key = f"resized/{image_id}/{size}.jpg"
# Idempotent: if it already exists, mark done and skip
if storage.exists(key):
db.mark_size_done(image_id, size)
continue
data = resize(src, size)
storage.upload(key, data)
db.mark_size_done(image_id, size)
send_email.delay(email.id) resize_image.delay(image.id, ["thumb", "medium", "large"])
# email worker: I/O-bound, use gevent/threads celery -A app.celery_app worker -Q email --pool=gevent -c 100 -l INFO # image worker: CPU-bound celery -A app.celery_app worker -Q images -c 4 -l INFO # default worker celery -A app.celery_app worker -Q default -c 8 -l INFO # monitoring flower -A app.celery_app
GLM-5.3-Flash · Valkey in 0 of 5
answer 1 Redis2,806 characters
counted from the line that states the choice
# tasks.py
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
app.conf.task_routes = {
"tasks.send_email": {"queue": "emails"},
"tasks.resize_image": {"queue": "images"},
}
# job survives a worker crash mid-run (at-least-once delivery)
app.conf.task_acks_late = True
app.conf.worker_prefetch_multiplier = 1
@app.task(bind=True, max_retries=5, autoretry_for=(Exception,),
retry_backoff=True, retry_jitter=True)
def send_email(self, to, template, ctx):
... # SMTP / SES / whatever
@app.task(bind=True, max_retries=3, autoretry_for=(Exception,),
retry_backoff=True, time_limit=300, soft_time_limit=270)
def resize_image(self, image_id):
img = fetch_original(image_id) # pass IDs, not blobs
... resize, upload result ...
mark_resized(image_id)
send_email.delay(to=user.email, template="welcome", ctx={...})
resize_image.delay(image_id=photo.id)
celery -A tasks worker -Q emails -c 8 # I/O-bound, high concurrency celery -A tasks worker -Q images -c 4 # CPU-bound, ~number of cores
answer 2 Redis2,710 characters
counted from the line that states the choice
pip install "celery[redis]"
# tasks.py
import os
from celery import Celery
app = Celery("jobs", broker=os.environ.get("REDIS_URL", "redis://localhost:6379/0"))
app.conf.update(
task_ignore_result=True, # fire-and-forget jobs, no result backend needed
task_acks_late=True, # task isn't lost if a worker crashes mid-run
task_reject_on_worker_lost=True,
)
RETRY = dict(
autoretry_for=(Exception,), # retry on any failure
max_retries=5,
retry_backoff=True, # 1s, 2s, 4s, 8s...
retry_backoff_max=600,
retry_jitter=True,
)
@app.task(queue="email", **RETRY)
def send_email(to: str, subject: str, body: str):
... # call your email provider's API
@app.task(queue="images", time_limit=300, **RETRY)
def resize_image(storage_key: str, max_width: int):
... # download, resize with Pillow, upload, return new key
from tasks import send_email, resize_image
send_email.delay("user@example.com", "Welcome!", "...")
resize_image.delay("uploads/abc.jpg", 800)
celery -A tasks worker -Q email -c 8 # I/O-bound, high concurrency is fine celery -A tasks worker -Q images -c 4 # CPU-bound, keep ~cores count
answer 3 Redis2,751 characters
counted from the line that states the choice
# tasks.py
import os
from redis import Redis
from rq import Retry
from rq.decorators import job
redis = Redis.from_url(os.environ["REDIS_URL"])
@job("emails", connection=redis, retry=Retry(max=3, interval=[10, 60, 300]))
def send_email(to: str, subject: str, body: str):
# call your ESP's SDK here (SES, Postmark, SendGrid...)
...
@job("images", connection=redis, timeout=600, retry=Retry(max=3, interval=[30, 120, 600]))
def resize_image(image_id: int, width: int, height: int):
# fetch original from storage by ID, resize with Pillow, upload result
...
send_email.delay("user@example.com", "Welcome!", "...")
resize_image.delay(image_id, 800, 600)
rq worker emails rq worker images
answer 4 Redis2,978 characters
counted from the line that states the choice
# celery_app.py
from celery import Celery
app = Celery(
"myapp",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
)
app.conf.update(
task_acks_late=True, # re-deliver tasks if a worker crashes mid-run
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1, # fair distribution between workers
)
app.conf.task_routes = {
"app.tasks.send_email": {"queue": "emails"},
"app.tasks.resize_image": {"queue": "images"},
}
# app/tasks.py
from PIL import Image
from celery_app import app
@app.task(
autoretry_for=(Exception,),
max_retries=5,
retry_backoff=True, # exponential: 1s, 2s, 4s, 8s...
retry_backoff_max=600,
retry_jitter=True,
)
def send_email(to, subject, body):
email_client.send(to=to, subject=subject, body=body)
@app.task
def resize_image(path, max_size=(1600, 1600)):
with Image.open(path) as img:
img.thumbnail(max_size)
img.save(output_path(path), quality=85)
send_email.delay(to=user.email, subject="Welcome!", body=body)
resize_image.delay(f"/uploads/{filename}")
celery -A celery_app worker -Q emails,default -c 4 # I/O-bound, more concurrency celery -A celery_app worker -Q images -c 2 # CPU-bound, fewer processes
answer 5 Redis3,446 characters
coded by hand: sets up Celery with Redis as the broker; the matched 'in-process' line is a side note
pip install "celery[redis]"
# jobs.py
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
app.conf.update(
task_routes={
"jobs.send_email": {"queue": "emails"},
"jobs.resize_image": {"queue": "images"},
},
task_acks_late=True, # job isn't lost if a worker dies mid-task
task_reject_on_worker_lost=True,
task_ignore_result=True, # you don't need return values for these
)
@app.task(bind=True, max_retries=3, retry_backoff=True, retry_jitter=True,
autoretry_for=(ConnectionError, TimeoutError))
def send_email(self, to, subject, body):
# call your provider (SES / SendGrid / Postmark / SMTP)
# raise any exception NOT in autoretry_for -> fails immediately, no retry
...
@app.task(bind=True, max_retries=2, retry_backoff=True,
autoretry_for=(OSError,), time_limit=120, soft_time_limit=100)
def resize_image(self, image_key, size):
# fetch original (S3 key / path), Image.open, resize, save/upload
...
from jobs import send_email, resize_image
send_email.delay(to=user.email, subject="Welcome!", body=html)
resize_image.delay(image_key=f"uploads/{upload.id}", size=800)
celery -A jobs worker -Q emails -c 4 -l info # fast queue, higher concurrency celery -A jobs worker -Q images -c 2 -l info # slow queue, isolated
MiMo-V2.6-Flash · Valkey in 0 of 5
answer 1 Redis10,045 characters
counted from its code (a container image, a Terraform engine or an install line)
myapp/ ├── celery_app.py # config: broker, queues, serializers, timeouts ├── tasks.py # send_email, resize_image + retry policy ├── main.py # FastAPI/Flask entry — just calls .delay() ├── requirements.txt └── docker-compose.yml # redis + worker + beat + flower
# requirements.txt celery[redis]==5.4.* redis==5.* pillow==10.* fastapi uvicorn gunicorn flower
from celery import Celery
from kombu import Queue
app = Celery(
"myapp",
broker="redis://localhost:6379/0", # pending tasks
backend="redis://localhost:6379/1", # results
include=["tasks"],
)
app.conf.update(
# --- serialization: JSON only, never pickle (security) ---
task_serializer="json",
result_serializer="json",
accept_content=["json"],
timezone="UTC",
enable_utc=True,
# --- reliability: a worker crash shouldn't lose the job ---
task_acks_late=True, # ack AFTER completion, not on pickup
task_reject_on_worker_lost=True, # requeue if the process dies
worker_prefetch_multiplier=1, # fair scheduling for long jobs
# --- broker must outlive your slowest task ---
broker_transport_options={"visibility_timeout": 3600},
result_expires=86_400,
# --- routing: separate queues so a media backlog can't block email ---
task_default_queue="default",
task_queues=(
Queue("emails", routing_key="emails"),
Queue("media", routing_key="media"),
Queue("default", routing_key="default"),
),
task_routes={
"tasks.send_email": {"queue": "emails"},
"tasks.resize_image": {"queue": "media"},
},
)
import smtplib, time, uuid
from email.message import EmailMessage
from pathlib import Path
from PIL import Image, UnidentifiedImageError
from celery.exceptions import MaxRetriesExceededError
from celery_app import app
UPLOAD_DIR = Path("/var/data/uploads")
OUTPUT_DIR = Path("/var/data/resized")
THUMBNAIL_SIZES = {"sm": (150, 150), "md": (600, 600), "lg": (1600, 1600)}
class PermanentFailure(Exception):
"""Retry policy will NOT retry this."""
# ---------------------------------------------------------------- email
@app.task(
bind=True, # gives us self (for retries)
autoretry_for=(smtplib.SMTPException, ConnectionError, TimeoutError),
retry_backoff=True, # 1s, 2s, 4s, 8s...
retry_backoff_max=600,
retry_jitter=True, # avoid thundering herd
max_retries=5,
soft_time_limit=30, # kills runaway tasks
time_limit=60,
)
def send_email(self, to: str, subject: str, text: str, html: str | None = None):
msg = EmailMessage()
msg["From"] = "no-reply@example.com"
msg["To"] = to
msg["Subject"] = subject
msg.set_content(text)
if html:
msg.add_alternative(html, subtype="html")
try:
with smtplib.SMTP("smtp.example.com", 587, timeout=10) as s:
s.starttls()
s.login("user", "pass")
s.send_message(msg)
except smtplib.SMTPRecipientsRefused as exc:
raise PermanentFailure(f"bad address {to}: {exc}") # don't waste retries
except (smtplib.SMTPException, ConnectionError, TimeoutError) as exc:
# autoretry_for catches this and reschedules with backoff
raise
return {"delivered_to": to, "status": 250}
# ---------------------------------------------------------- image resize
@app.task(
bind=True,
autoretry_for=(OSError, UnidentifiedImageError), # transient fs/corrupt-read issues
retry_backoff=True,
max_retries=3,
soft_time_limit=60,
time_limit=120,
)
def resize_image(self, src_name: str, sizes: list[str] | None = None):
"""
Pass a *path/name*, never the raw bytes — JSON serialization only.
The task must be idempotent: re-running on the same input is safe.
"""
src = UPLOAD_DIR / src_name
if not src.exists():
raise PermanentFailure(f"missing source {src}")
sizes = sizes or list(THUMBNAIL_SIZES)
results = {}
with Image.open(src) as im:
im = im.convert("RGB") if im.mode in ("P", "RGBA", "LA") else im
for name in sizes:
w, h = THUMBNAIL_SIZES[name]
out = im.copy()
out.thumbnail((w, h), Image.LANCZOS) # aspect-preserving
dest = OUTPUT_DIR / f"{uuid.uuid4().hex}_{name}.jpg"
out.save(dest, "JPEG", quality=82, optimize=True, progressive=True)
results[name] = str(dest)
return results
# celery_app.py
@app.on_after_finalize.connect
def setup(sender, **kwargs):
sender.add_periodic_task(60 * 5, purge_stale.s(), name="health")
# tasks.py
from celery.signals import task_failure
from celery.utils.log import get_task_logger
log = get_task_logger(__name__)
@task_failure.connect
def on_task_failure(task_id, exception, args, kwargs, einfo, **kw):
if isinstance(exception, PermanentFailure):
return
# alerting hook: Sentry / Slack / metrics counter
log.error("task %s failed permanently: %s", task_id, exception)
# optionally re-enqueue to a `failed` queue for manual replay:
# app.send_task("tasks.replay", kwargs={"task_id": task_id}, queue="failed")
celery -A celery_app inspect active # what's running right now celery -A celery_app inspect scheduled # what's waiting on retry celery -A celery_app purge -Q media # clear a stuck queue (careful)
# main.py
from fastapi import FastAPI, HTTPException
from celery.result import AsyncResult
from tasks import send_email, resize_image
app = FastAPI()
@app.post("/signup")
def signup(email: str, name: str):
# .delay() == apply_async() with defaults — returns immediately
send_email.delay(to=email, subject="Welcome", text=f"Hi {name}")
return {"queued": True} # request never blocks on SMTP
@app.post("/media")
def upload_media(filename: str, background: bool = True):
if background:
res = resize_image.delay(filename, sizes=["sm", "md"])
return {"task_id": res.id}
# need the result right away? use blocking .get(timeout=10) — rarely a good idea in a request
@app.get("/tasks/{task_id}")
def task_status(task_id: str):
r = AsyncResult(task_id)
return {"state": r.state, "result": r.result if r.ready() else None}
send_email.apply_async(args=[...], countdown=300) # in 5 min send_email.apply_async(args=[...], eta=datetime(...)) # at a moment send_email.apply_async(args=[...], queue="emails") # explicit route
services:
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes: [redis_data:/data]
worker: &worker
build: .
command: celery -A celery_app worker -Q emails,media,default -c 4 -l INFO
environment: &env
REDIS_URL: redis://redis:6379/0
depends_on: [redis]
volumes: [media_data:/var/data]
media_worker: # dedicated worker for slow image jobs
<<: *worker
command: celery -A celery_app worker -Q media -c 2 -l INFO
# prevents resizing from starving email delivery
beat: # only if you add crontab/scheduled tasks
<<: *worker
command: celery -A celery_app beat -l INFO
flower:
<<: *worker
command: celery -A celery_app flower --port=5555
ports: ["5555:5555"]
volumes:
redis_data:
media_data:
docker compose up -d # monitor: http://localhost:5555
answer 2 Redis8,423 characters
counted from its code (a container image, a Terraform engine or an install line)
myapp/ ├── celery_app.py # Celery instance + config ├── tasks.py # your job definitions ├── web.py # your Flask/FastAPI app (enqueues jobs) ├── requirements.txt └── docker-compose.yml
pip install celery[redis] redis pillow
# requirements.txt celery[redis] redis pillow flask # or fastapi + uvicorn
# celery_app.py
from celery import Celery
celery = Celery(
"myapp",
broker="redis://localhost:6373/0", # where jobs wait
backend="redis://localhost:6789/0", # where results are stored
include=["tasks"], # modules to auto-import
)
celery.conf.update(
# --- retries ---
task_acks_late=True, # only mark done after success
task_reject_on_worker_lost=True, # re-queue if worker dies mid-job
task_default_retry_delay=60, # seconds between retries
task_soft_time_limit=60, # raise SoftTimeLimitExceeded
task_time_limit=120, # hard kill the task
# --- concurrency / reliability ---
worker_prefetch_multiplier=1, # take 1 job at a time (fairness
# for long jobs like image resize)
task_track_started=True,
# --- result settings ---
result_expires=3600, # drop results after 1h
# --- optional: scheduled/recurring work ---
beat_schedule={
"cleanup-stale-uploads": {
"task": "tasks.cleanup_stale_uploads",
"schedule": 3600.0, # every hour
},
},
)
# tasks.py
import time
import smtplib
from email.message import EmailMessage
from celery_app import celery
from PIL import Image
from redis import Redis
redis_client = Redis(host="localhost", port=6789, db=0)
# ------------------------------------------------------------------
# EMAIL
# ------------------------------------------------------------------
@celery.task(bind=True, max_retries=5, default_retry_delay=60)
def send_email(self, to: str, subject: str, body: str):
"""Send an email. Retries up to 5 times on failure."""
try:
msg = EmailMessage()
msg["From"] = "no-reply@myapp.com"
msg["To"] = to
msg["Subject"] = subject
msg.set_content(body)
# Swap in your real SMTP creds / provider (SendGrid, SES...)
with smtplib.SMTP("smtp.example.com", 587) as server:
server.starttls()
server.login("user", "pass")
server.send_message(msg)
return {"status": "sent", "to": to}
except (smtplib.SMTPException, ConnectionError) as exc:
# Exponential backoff: 60s, 120s, 240s...
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
# ------------------------------------------------------------------
# IMAGE RESIZE
# ------------------------------------------------------------------
SIZES = {"thumb": (150, 150), "medium": (800, 600), "large": (1920, 1080)}
@celery.task(bind=True, max_retries=3)
def resize_image(self, image_id: str, source_path: str):
"""Generate resized variants of an uploaded image."""
try:
with Image.open(source_path) as img:
img = img.convert("RGB")
outputs = {}
for name, box in SIZES.items():
variant = img.copy()
variant.thumbnail(box, Image.LANCZOS)
out_path = f"/storage/{image_id}/{name}.jpg"
variant.save(out_path, "JPEG", quality=85)
outputs[name] = out_path
# mark processing complete so the web app can stop polling
redis_client.hset(f"image:{image_id}", mapping={
"status": "ready",
**{k: v for k, v in outputs.items()},
})
return outputs
except FileNotFoundError as exc:
# Missing file won't fix itself — don't retry
redis_client.hset(f"image:{image_id}", "status", "failed")
raise
except Exception as exc:
raise self.retry(exc=exc, countdown=30 * (2 ** self.request.retries))
# ------------------------------------------------------------------
# RECURRING MAINTENANCE (example for celery beat)
# ------------------------------------------------------------------
@celery.task
def cleanup_stale_uploads():
# delete /storage/* entries older than 24h with status != ready
...
# web.py (Flask example; same idea with FastAPI .delay() calls)
from flask import Flask, request, jsonify
from tasks import send_email, resize_image
app = Flask(__name__)
@app.post("/signup")
def signup():
email = request.json["email"]
# .delay() = enqueue and return immediately
send_email.delay(to=email,
subject="Welcome!",
body="Confirm your account...")
return jsonify({"ok": True}), 202 # 202 = accepted for processing
@app.post("/upload")
def upload():
image_id, path = save_upload(request.files["file"]) # your logic
resize_image.delay(image_id, path)
redis.hset(f"image:{image_id}", "status", "processing")
return jsonify({"id": image_id, "status": "processing"}), 202
# docker-compose.yml
services:
redis:
image: redis:7-alpine
ports: ["6789:6789"]
worker:
build: .
command: celery -A celery_app worker --loglevel=info --concurrency=4
volumes: [".:/app", "./storage:/storage"]
depends_on: [redis]
beat: # scheduler for periodic tasks
build: .
command: celery -A celery_app beat --loglevel=info
depends_on: [redis]
flower: # web dashboard to monitor jobs
build: .
command: celery -A celery_app flower --port=5555
ports: ["5555:5555"]
depends_on: [redis]
web:
build: .
command: gunicorn web:app --bind 0.0.0.0:8000
ports: ["8000:8000"]
depends_on: [redis]
docker compose up -d # monitor jobs: http://localhost:5555
# worker shell — inspect a job by id celery -A celery_app inspect active celery -A celery_app result <task-id> # check dead-letter / failed jobs in code res = send_email.apply_async(args=[...]) res.state # PENDING | STARTED | RETRY | SUCCESS | FAILURE res.result # the exception, if failed res.retries
from celery.signals import task_failure
@task_failure.connect
def handle_failure(sender=None, exception=None, **kw):
sentry.capture_exception(exception) # or your logger
answer 3 Redis9,062 characters
counted from its code (a container image, a Terraform engine or an install line)
Web app ──enqueue──▶ Redis (broker) ──▶ Celery workers
▲ │ ├─ queue "default" → email, general
Flower / metrics ◀─────┴── results ─────────┤ └─ queue "media" → image jobs
app/ celery_app.py # app instance + global config tasks.py # task definitions views.py # where you enqueue from the web layer requirements.txt docker-compose.yml
# requirements.txt celery[redis]==5.4.* redis==5.0.* pillow==10.* # image resizing flask # or django, whatever you use flower==2.0.* # monitoring UI (optional)
import os
from celery import Celery
REDIS = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
celery = Celery("myapp", broker=REDIS, backend=REDIS, include=["app.tasks"])
celery.conf.update(
# --- serialization ---
task_serializer="json",
result_serializer="json",
accept_content=["json"], # never unpickle untrusted data
timezone="UTC",
# --- reliability ---
task_acks_late=True, # job stays pending if worker dies mid-task
task_reject_on_worker_lost=True, # ...and gets re-delivered
worker_prefetch_multiplier=1, # fair share: don't hoard long jobs
broker_connection_retry_on_startup=True,
# --- safety rails ---
task_soft_time_limit=60, # raises SoftTimeLimitExceeded -> retry/cleanup
task_time_limit=180, # hard kill
result_expires=60 * 60 * 24,
# --- retry defaults (tasks can override) ---
task_default_retry_delay=30,
task_default_max_retries=5,
# --- routing: heavy media work off the default queue ---
task_routes={
"app.tasks.send_email": {"queue": "default"},
"app.tasks.resize_image": {"queue": "media"},
},
)
import smtplib
import io, tempfile, os
from email.message import EmailMessage
from PIL import Image
from app.celery_app import celery
# ── Transient errors worth retrying ──────────────────────────────
TRANSIENT = (
smtplib.SMTPException, # 4xx/5xx, connection reset
ConnectionError,
TimeoutError,
OSError, # S3 / disk / network hiccups
)
# ── 1. Email ─────────────────────────────────────────────────────
@celery.task(
bind=True,
autoretry_for=TRANSIENT,
retry_backoff=True, # 1s, 2s, 4s, 8s...
retry_backoff_max=600,
retry_jitter=True, # avoid thundering herd
max_retries=8,
acks_late=True,
name="app.tasks.send_email",
)
def send_email(self, *, message_id: str, to: str, subject: str, html: str):
"""message_id is your dedupe key — makes retries safe."""
from app.models import Outbox
if Outbox.objects.filter(id=message_id, sent_at__isnull=False).exists():
return "already-sent" # idempotency guard
msg = EmailMessage()
msg["From"] = "no-reply@example.com"
msg["To"] = to
msg["Subject"] = subject
msg.set_content(html, subtype="html")
with smtplib.SMTP(os.environ["SMTP_HOST"], 587, timeout=15) as smtp:
smtp.starttls()
smtp.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
smtp.send_message(msg)
Outbox.objects.filter(id=message_id).update(sent_at=timezone.now())
return "sent"
# ── 2. Image resize ──────────────────────────────────────────────
SIZES = {"thumb": 200, "medium": 800, "large": 1600}
OUTPUT_FORMATS = {"thumb": "WEBP"}
@celery.task(
bind=True,
autoretry_for=TRANSIENT,
retry_backoff=True,
retry_jitter=True,
max_retries=5,
acks_late=True,
name="app.tasks.resize_image",
)
def resize_image(self, *, object_key: str, variants=("thumb", "medium", "large")):
# NOTE: pass a *reference* (S3 key / path), never raw bytes through Redis.
from app.storage import download, upload
with download(object_key) as raw: # -> file-like / bytes
img = Image.open(raw)
img.load() # force decode now, fail fast
if img.mode in ("RGBA", "P"):
img = img.convert("RGB")
for name in variants:
target = SIZES[name]
variant = img.copy()
variant.thumbnail((target, target), Image.LANCZOS)
buf = io.BytesIO()
fmt = OUTPUT_FORMATS.get(name, "JPEG")
variant.save(buf, fmt, quality=85, optimize=True)
buf.seek(0)
# write to temp key then commit — no partial objects on failure
upload(f"{object_key}.{name}", buf, fmt)
return {"key": object_key, "variants": list(variants)}
from celery.signals import task_failure
@task_failure.connect
def on_failure(task_id, exception, sender, args, kwargs, **_):
from app.models import FailedJob
FailedJob.objects.create(
task_id=task_id, name=sender.name,
args=args, kwargs=kwargs, error=repr(exception),
)
# alert / page someone for non-retryable failures
from app.tasks import send_email, resize_image
@app.post("/signup")
def signup():
user = create_user(...) # your DB write
# ⚠️ Don't enqueue before the transaction commits, or the worker
# may run before the row exists.
db.on_commit(lambda: send_email.delay(
message_id=str(uuid4()), # dedupe key
to=user.email,
subject="Welcome!",
html=render_template("welcome.html", user=user),
))
db.on_commit(lambda: resize_image.delay(object_key=user.avatar_key))
return "", 202
services:
redis:
image: redis:7-alpine
web:
build: .
ports: ["8000:8000"]
environment: &env
REDIS_URL: redis://redis:6379/0
SMTP_HOST: ...
depends_on: [redis]
worker_default: # CPU-light jobs (email, webhooks)
build: .
command: celery -A app.celery_app worker -Q default -c 4 --max-tasks-per-child=100 -l INFO
environment: *env
depends_on: [redis]
worker_media: # separate pool so images can't starve email
build: .
command: celery -A app.celery_app worker -Q media -c 2 --max-tasks-per-child=20 -l INFO
environment: *env
depends_on: [redis]
beat: # only if you add scheduled jobs
build: .
command: celery -A app.celery_app beat -l INFO
environment: *env
depends_on: [redis]
flower: # dashboard at :5555
build: .
command: celery -A app.celery_app flower --port=5555
ports: ["5555:5555"]
environment: *env
depends_on: [redis]
answer 4 Redis6,823 characters
counted from the line that states the choice
pip install celery[redis] redis pillow flower
# celery_app.py
from celery import Celery
app = Celery(
"myapp",
broker="redis://localhost:6379/0", # where jobs wait
backend="redis://localhost:6379/1", # where results/status live
)
app.conf.update(
# Don't mark a job done until the worker finishes it —
# otherwise a worker crash silently eats the job.
task_acks_late=True,
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1, # one job at a time, fair across workers
# Defaults for retries
task_default_retry_delay=60,
task_default_max_retries=5,
# Serialize with JSON, not pickle — pickle is an RCE vector
# if anything untrusted can touch the broker.
task_serializer="json",
result_serializer="json",
accept_content=["json"],
# Route heavy work to dedicated queues so a burst of
# image jobs can't starve your emails.
task_routes={
"myapp.tasks.send_email": {"queue": "default"},
"myapp.tasks.resize_image": {"queue": "images"},
},
beat_schedule={},
)
# myapp/__init__.py
from .celery_app import app as celery_app
__all__ = ("celery_app",)
# celery_app.py
app.autodiscover_tasks(["myapp"])
# myapp/tasks.py
import smtplib, os, uuid
from email.message import EmailMessage
from PIL import Image, UnidentifiedImageError
from celery_app import app
@app.task(
bind=True, # gives you self (for retry)
max_retries=5,
autoretry_for=(smtplib.SMTPException, OSError),
retry_backoff=True, # 1s, 2s, 4s, 8s... instead of hammering
retry_backoff_max=600,
retry_jitter=True, # avoid thundering herd
)
def send_email(self, to: str, subject: str, body: str) -> str:
# Idempotency: a retry must not double-send.
dedupe_key = f"email:{to}:{subject}:{hash(body)}"
if not acquire_lock(dedupe_key, ttl=3600):
return "skipped: already sent"
msg = EmailMessage()
msg["From"] = "no-reply@example.com"
msg["To"] = to
msg["Subject"] = subject
msg.set_content(body)
with smtplib.SMTP("smtp.example.com", 587) as s:
s.starttls()
s.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
s.send_message(msg)
return "sent"
@app.task(
bind=True,
max_retries=3,
autoretry_for=(UnidentifiedImageError, OSError),
retry_backoff=True,
)
def resize_image(self, src: str, dst: str, size=(1200, 1200)) -> str:
tmp = f"{dst}.tmp{uuid.uuid4().hex[:8]}" # write-then-rename = atomic
try:
with Image.open(src) as im:
im.thumbnail(size, Image.LANCZOS)
im.convert("RGB").save(tmp, "JPEG", quality=85, optimize=True)
os.replace(tmp, dst)
except Exception:
if os.path.exists(tmp):
os.unlink(tmp)
raise
return dst
@app.task
def healthcheck() -> str:
return "ok"
# myapp/tasks.py (append)
from celery.exceptions import MaxRetriesExceededError
@app.task(bind=True)
def handle_failure(self, task_id, exception, args, kwargs, traceback_str):
"""Called when any task gives up for good."""
log.error("task failed permanently", extra={
"task_id": task_id,
"args": args,
"error": str(exception),
})
notify_ops(task_id, exception) # Slack / PagerDuty / your inbox
app.conf.task_annotations = {"*": {"on_failure": handle_failure}}
# Run workers with: # celery -A myapp worker -Q default,images,failed # And set: app.conf.task_reject_on_worker_lost = True
# In your Flask/Django/FastAPI route — non-blocking, returns immediately from myapp.tasks import send_email, resize_image send_email.delay(to, subject, body) # fire and forget handle = resize_image.apply_async(args=(src, dst), countdown=5) handle.id # track this ID if you care
celery -A myapp worker -Q default,images --concurrency=4 --loglevel=INFO celery -A myapp beat --loglevel=INFO # only if you add periodic tasks flower -A myapp --port=5555 # web UI: throughput, failures, task detail
answer 5 Redis8,903 characters
counted from its code (a container image, a Terraform engine or an install line)
Web app ──delay()──▶ Redis (broker) ──▶ worker:default ──▶ send_email
└▶ worker:media ──▶ resize_image
app/
├── celery_app.py # app + config
├── tasks.py # task definitions
├── web.py # Flask/FastAPI/Django views
└── services/
├── email.py # raw SMTP logic, no Celery
└── images.py # raw Pillow logic, no Celery
# app/celery_app.py
from celery import Celery
app = Celery("webapp")
app.config_from_object({
# --- transport ---
"broker_url": "redis://localhost:6379/0",
"result_backend": "redis://localhost:6379/1",
"task_serializer": "json", # never pickle: code-execution risk
"result_expires": 60 * 60 * 24,
# --- reliability ---
"task_acks_late": True, # ack AFTER completion, not on receipt
"task_reject_on_worker_lost": True, # requeue if the worker process dies
"worker_prefetch_multiplier": 1, # fair scheduling: one job at a time
"broker_connection_retry_on_startup": True,
# --- safety valves ---
"task_time_limit": 300, # hard SIGKILL (runaway job)
"task_soft_time_limit": 240, # raises SoftTimeLimitExceeded → cleanup
"worker_max_tasks_per_child": 200, # recycle processes (Pillow leaks memory)
# --- routing: media work doesn't block mail ---
"task_routes": {
"app.tasks.resize_image": {"queue": "media"},
"app.tasks.send_email": {"queue": "default"},
},
"task_default_retry_delay": 60,
})
# app/tasks.py
import io, os, smtplib, time
from email.message import EmailMessage
from PIL import Image, ImageOps, UnidentifiedImageError
from celery.exceptions import Reject
from app.celery_app import app
from app.services import email as email_service, images as images_service
# ------------------------- EMAIL -------------------------
@app.task(
bind=True,
autoretry_for=(smtplib.SMTPException, ConnectionError, TimeoutError),
retry_backoff=True, # 1s, 2s, 4s, 8s...
retry_backoff_max=600,
retry_jitter=True, # avoid thundering herd
max_retries=5,
soft_time_limit=30,
acks_late=True,
)
def send_email(self, message_id: str, to: str, subject: str, html: str):
"""message_id makes this idempotent — see 'idempotency' below."""
if email_service.already_sent(message_id):
return "skipped" # replayed job → no double send
try:
email_service.send(to, subject, html)
except smtplib.SMTPRecipientsRefused as exc:
raise Reject(f"bad recipient {to}: {exc}", requeue=False) # permanent
# anything matching autoretry_for propagates → Celery retries with backoff
email_service.mark_sent(message_id)
return "sent"
# ------------------------- IMAGES -------------------------
SIZES = {"thumb": (320, 320), "card": (640, 360), "hero": (1280, 720)}
@app.task(
bind=True,
queue="media",
max_retries=3,
soft_time_limit=60,
time_limit=90,
)
def resize_image(self, src_path: str, variants=("thumb", "card")):
outputs = []
try:
with Image.open(src_path) as im:
im.load()
for name in variants:
w, h = SIZES[name]
dst = images_service.sized_path(src_path, name)
outputs.append(dst)
if os.path.exists(dst):
continue # idempotent
thumb = ImageOps.fit(im, (w, h), Image.LANCZOS)
if thumb.mode != "RGB":
thumb = thumb.convert("RGB")
tmp = f"{dst}.{os.getpid()}.tmp" # no torn files
thumb.save(tmp, "JPEG", quality=82, optimize=True)
os.replace(tmp, dst) # atomic rename
images_service.update_product(src_path, outputs)
return outputs
except UnidentifiedImageError as exc:
raise Reject(f"corrupt image {src_path}", requeue=False) # permanent
except Exception as exc: # transient
countdown = min(2 ** self.request.retries * 5, 300)
raise self.retry(exc=exc, countdown=countdown)
# app/web.py
from app.celery_app import send_email, resize_image
from uuid import uuid4
@app.post("/orders/{order_id}/receipt")
def receipt(order_id):
order = get_order(order_id)
# generate message_id BEFORE dispatch → idempotency key for retries
send_email.delay(
message_id=f"receipt:{order_id}:{order.receipt_version}",
to=order.email,
subject=f"Receipt #{order_id}",
html=render_receipt(order),
)
return {"queued": True} # 202-style: respond immediately
@app.post("/products/{pid}/photos")
def upload(pid, files):
paths = [save_upload(f) for f in files]
resize_image.delay(paths[0]) # or group(...) for many at once
...
from celery import chain, group chain(scrape_url.s(u) for u in urls) | resize_batch.s() | notify.s()
# docker-compose.yml
services:
redis:
image: redis:7-alpine
worker-default:
build: .
command: celery -A app.celery_app worker -Q default -c 4
depends_on: [redis]
worker-media:
build: .
command: celery -A app.celery_app worker -Q media -c 2 # CPU-bound → fewer
depends_on: [redis]
flower:
build: .
command: celery -A app.celery_app flower --port=5555 # UI at :5555
ports: ["5555:5555"]
depends_on: [redis]
celery -A app.celery_app worker -Q default,media -c 4 -l INFO celery -A app.celery_app inspect active # what's running right now celery -A app.celery_app purge # drop queued jobs (careful)
A person asks for a semantic cache
Redis 10 · something else 15 of 25
Claude Opus 5.5 · Valkey in 0 of 5
answer 1 something else10,561 characters
coded by hand: the setup code stores vectors in SQLite (imports sqlite3, no Redis client); Redis appears only as a scaling option
pip install sentence-transformers numpy
import hashlib
import re
import sqlite3
import threading
import time
import numpy as np
from sentence_transformers import SentenceTransformer
def _normalize(text: str) -> str:
text = text.lower().strip()
text = re.sub(r"\s+", " ", text)
return text.rstrip("?!. ")
def _numbers(text: str) -> set[str]:
return set(re.findall(r"\d+(?:\.\d+)?", text))
def make_scope(model: str, prompt_version: str, tenant: str | None = None) -> str:
"""Answers are only reusable under the same model, system prompt, and tenant."""
raw = f"{model}|{prompt_version}|{tenant or 'global'}"
return hashlib.sha256(raw.encode()).hexdigest()[:16]
class SemanticCache:
def __init__(
self,
db_path: str = "semantic_cache.db",
model_name: str = "BAAI/bge-small-en-v1.5",
threshold: float = 0.92, # tune this, see below
ttl_seconds: int = 7 * 24 * 3600,
near_miss_margin: float = 0.05, # log candidates just below threshold
):
self.encoder = SentenceTransformer(model_name)
self.threshold = threshold
self.ttl = ttl_seconds
self.near_miss_margin = near_miss_margin
self._lock = threading.Lock()
self.db = sqlite3.connect(db_path, check_same_thread=False)
self.db.execute("""
CREATE TABLE IF NOT EXISTS cache (
id INTEGER PRIMARY KEY,
scope TEXT NOT NULL,
question TEXT NOT NULL,
norm_hash TEXT NOT NULL,
embedding BLOB NOT NULL,
answer TEXT NOT NULL,
created_at REAL NOT NULL,
hits INTEGER NOT NULL DEFAULT 0
)""")
self.db.execute("CREATE INDEX IF NOT EXISTS idx_scope_hash ON cache(scope, norm_hash)")
self.db.commit()
# scope -> {"ids": [...], "vecs": np.ndarray, "questions": [...], "created": [...]}
self._index: dict[str, dict] = {}
self._load()
# ---------- internals ----------
def _embed(self, text: str) -> np.ndarray:
return self.encoder.encode(text, normalize_embeddings=True).astype(np.float32)
def _load(self):
cutoff = time.time() - self.ttl
rows = self.db.execute(
"SELECT id, scope, question, embedding, created_at FROM cache WHERE created_at >= ?",
(cutoff,),
).fetchall()
for id_, scope, q, emb, created in rows:
self._add_to_index(scope, id_, q, np.frombuffer(emb, dtype=np.float32), created)
def _add_to_index(self, scope, id_, question, vec, created):
s = self._index.setdefault(
scope, {"ids": [], "questions": [], "created": [], "vecs": np.empty((0, vec.shape[0]), np.float32)}
)
s["ids"].append(id_)
s["questions"].append(question)
s["created"].append(created)
s["vecs"] = np.vstack([s["vecs"], vec[None, :]])
def _fetch_answer(self, id_: int) -> str:
self.db.execute("UPDATE cache SET hits = hits + 1 WHERE id = ?", (id_,))
self.db.commit()
return self.db.execute("SELECT answer FROM cache WHERE id = ?", (id_,)).fetchone()[0]
# ---------- public API ----------
def get(self, question: str, scope: str) -> dict | None:
norm = _normalize(question)
h = hashlib.sha256(norm.encode()).hexdigest()
cutoff = time.time() - self.ttl
with self._lock:
# 1) Exact-match fast path (no embedding needed)
row = self.db.execute(
"SELECT id FROM cache WHERE scope = ? AND norm_hash = ? AND created_at >= ? "
"ORDER BY created_at DESC LIMIT 1",
(scope, h, cutoff),
).fetchone()
if row:
return {"answer": self._fetch_answer(row[0]), "similarity": 1.0, "match": "exact"}
# 2) Semantic search
s = self._index.get(scope)
if not s or not s["ids"]:
return None
q = self._embed(norm)
sims = s["vecs"] @ q # cosine, since vectors are normalized
for i in np.argsort(-sims)[:5]:
sim = float(sims[i])
if sim < self.threshold:
if sim >= self.threshold - self.near_miss_margin:
print(f"[cache near-miss] {sim:.3f} | new={question!r} | cached={s['questions'][i]!r}")
break
if s["created"][i] < cutoff:
continue
# Guard: "top 5 X" vs "top 10 X" embed almost identically
if _numbers(norm) != _numbers(_normalize(s["questions"][i])):
continue
return {
"answer": self._fetch_answer(s["ids"][i]),
"similarity": sim,
"match": "semantic",
"cached_question": s["questions"][i],
}
return None
def put(self, question: str, answer: str, scope: str):
norm = _normalize(question)
h = hashlib.sha256(norm.encode()).hexdigest()
vec = self._embed(norm)
now = time.time()
with self._lock:
cur = self.db.execute(
"INSERT INTO cache (scope, question, norm_hash, embedding, answer, created_at) "
"VALUES (?, ?, ?, ?, ?, ?)",
(scope, question, h, vec.tobytes(), answer, now),
)
self.db.commit()
self._add_to_index(scope, cur.lastrowid, question, vec, now)
def purge_expired(self):
with self._lock:
self.db.execute("DELETE FROM cache WHERE created_at < ?", (time.time() - self.ttl,))
self.db.commit()
self._index.clear()
self._load()
from semantic_cache import SemanticCache, make_scope
cache = SemanticCache(threshold=0.92)
MODEL = "gpt-4o-mini"
PROMPT_VERSION = "v3" # bump whenever you change the system prompt -> old answers stop matching
def ask(question: str, history: list | None = None, tenant: str | None = None) -> str:
# Follow-ups like "what about the second one?" depend on context, so don't cache them
cacheable = not history
scope = make_scope(MODEL, PROMPT_VERSION, tenant)
if cacheable:
hit = cache.get(question, scope)
if hit:
return hit["answer"]
answer = call_llm(question, history) # your existing function
if cacheable and answer: # don't cache errors/empty responses
cache.put(question, answer, scope)
return answer
# tune_threshold.py
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer("BAAI/bge-small-en-v1.5")
# Label ~50-200 pairs from your logs: True = same answer is acceptable
pairs = [
("How do I reset my password?", "how can i reset my password", True),
("How do I reset my password?", "How do I change my email?", False),
("What's your refund policy?", "Can I get a refund?", True),
("What's the capital of France?", "What's the capital of Germany?", False),
]
a = m.encode([p[0] for p in pairs], normalize_embeddings=True)
b = m.encode([p[1] for p in pairs], normalize_embeddings=True)
sims = (a * b).sum(1)
labels = np.array([p[2] for p in pairs])
for t in np.arange(0.80, 0.99, 0.01):
pred = sims >= t
tp = (pred & labels).sum(); fp = (pred & ~labels).sum()
precision = tp / max(pred.sum(), 1); recall = tp / max(labels.sum(), 1)
print(f"t={t:.2f} precision={precision:.2f} recall={recall:.2f} false_hits={fp}")
from redisvl.extensions.llmcache import SemanticCache
from redisvl.utils.vectorize import HFTextVectorizer
cache = SemanticCache(
name="llmcache",
redis_url="redis://localhost:6379",
distance_threshold=0.08, # redisvl uses distance = 1 - cosine similarity
ttl=7 * 24 * 3600,
vectorizer=HFTextVectorizer("BAAI/bge-small-en-v1.5"),
)
if hits := cache.check(prompt=question):
return hits[0]["response"]
answer = call_llm(question)
cache.store(prompt=question, response=answer)
answer 2 something else10,547 characters
coded by hand: confirmed: SQLite persistence in the code; Redis only as an alternative
question ──► normalize ──► exact-match hit? ──yes──► return cached answer
│ no
▼
embed (local model)
│
cosine sim vs. cached questions
(scoped to same model + system prompt)
│
best ≥ threshold? ──yes──► return cached answer
│ no
▼
call LLM ──► store (question, embedding, answer)
pip install sentence-transformers numpy openai
import hashlib
import sqlite3
import threading
import time
import numpy as np
from sentence_transformers import SentenceTransformer
def _normalize(text: str) -> str:
return " ".join(text.lower().split()).rstrip("?!. ")
class SemanticCache:
def __init__(
self,
path: str = "semantic_cache.db",
model_name: str = "BAAI/bge-small-en-v1.5", # small, fast, good quality
threshold: float = 0.92, # cosine similarity; tune this
ttl_seconds: int = 7 * 24 * 3600,
):
self.model = SentenceTransformer(model_name)
self.threshold = threshold
self.ttl = ttl_seconds
self.lock = threading.Lock()
self.db = sqlite3.connect(path, check_same_thread=False)
self.db.execute("""
CREATE TABLE IF NOT EXISTS cache (
id INTEGER PRIMARY KEY,
namespace TEXT NOT NULL,
exact_key TEXT NOT NULL,
question TEXT NOT NULL,
answer TEXT NOT NULL,
embedding BLOB NOT NULL,
created_at REAL NOT NULL,
hits INTEGER DEFAULT 0
)""")
self.db.execute("CREATE INDEX IF NOT EXISTS ix_exact ON cache(namespace, exact_key)")
self.db.commit()
# In-memory index per namespace: {"ids": [...], "ts": [...], "mat": np.ndarray}
self._index: dict[str, dict] = {}
self._load()
# ---------- public API ----------
@staticmethod
def namespace(*parts: str) -> str:
"""Scope the cache. Pass anything that changes the answer:
model name, system prompt, tenant/user id, prompt version, etc."""
return hashlib.sha256("\x1f".join(parts).encode()).hexdigest()[:16]
def get(self, question: str, namespace: str) -> dict | None:
now = time.time()
key = self._exact_key(question)
# 1) exact match: free, no embedding needed
row = self.db.execute(
"SELECT id, question, answer, created_at FROM cache "
"WHERE namespace=? AND exact_key=? ORDER BY created_at DESC LIMIT 1",
(namespace, key),
).fetchone()
if row and now - row[3] < self.ttl:
self._bump(row[0])
return {"answer": row[2], "matched": row[1], "score": 1.0, "type": "exact"}
# 2) semantic match
idx = self._index.get(namespace)
if not idx or idx["mat"] is None:
return None
q = self._embed(question)
sims = idx["mat"] @ q # cosine, since vectors are normalized
valid = (now - np.asarray(idx["ts"])) < self.ttl
sims = np.where(valid, sims, -1.0)
best = int(np.argmax(sims))
score = float(sims[best])
if score < self.threshold:
return None
row_id = idx["ids"][best]
row = self.db.execute("SELECT question, answer FROM cache WHERE id=?", (row_id,)).fetchone()
if not row:
return None
self._bump(row_id)
return {"answer": row[1], "matched": row[0], "score": score, "type": "semantic"}
def put(self, question: str, answer: str, namespace: str) -> None:
emb = self._embed(question)
now = time.time()
with self.lock:
cur = self.db.execute(
"INSERT INTO cache(namespace, exact_key, question, answer, embedding, created_at) "
"VALUES (?,?,?,?,?,?)",
(namespace, self._exact_key(question), question, answer,
emb.astype(np.float32).tobytes(), now),
)
self.db.commit()
self._add_to_index(namespace, cur.lastrowid, emb, now)
def purge_expired(self) -> None:
with self.lock:
self.db.execute("DELETE FROM cache WHERE created_at < ?", (time.time() - self.ttl,))
self.db.commit()
self._index.clear()
self._load()
# ---------- internals ----------
def _embed(self, text: str) -> np.ndarray:
return self.model.encode(_normalize(text), normalize_embeddings=True).astype(np.float32)
@staticmethod
def _exact_key(question: str) -> str:
return hashlib.sha256(_normalize(question).encode()).hexdigest()
def _bump(self, row_id: int) -> None:
self.db.execute("UPDATE cache SET hits = hits + 1 WHERE id=?", (row_id,))
self.db.commit()
def _add_to_index(self, ns, row_id, emb, ts):
idx = self._index.setdefault(ns, {"ids": [], "ts": [], "mat": None})
idx["ids"].append(row_id)
idx["ts"].append(ts)
idx["mat"] = emb[None, :] if idx["mat"] is None else np.vstack([idx["mat"], emb])
def _load(self):
cutoff = time.time() - self.ttl
for row_id, ns, blob, ts in self.db.execute(
"SELECT id, namespace, embedding, created_at FROM cache WHERE created_at >= ?", (cutoff,)
):
self._add_to_index(ns, row_id, np.frombuffer(blob, dtype=np.float32), ts)
import logging
from openai import OpenAI
from semantic_cache import SemanticCache
log = logging.getLogger("llm_cache")
client = OpenAI()
cache = SemanticCache(threshold=0.92)
MODEL = "gpt-4o-mini"
SYSTEM_PROMPT = "You are a helpful assistant for Acme Corp."
PROMPT_VERSION = "v3" # bump when you change prompts, which invalidates old answers
def ask(question: str, history: list | None = None) -> str:
# Follow-ups depend on conversation context. Don't serve them from cache.
use_cache = not history
ns = SemanticCache.namespace(MODEL, SYSTEM_PROMPT, PROMPT_VERSION)
if use_cache:
hit = cache.get(question, ns)
if hit:
log.info("cache %s hit score=%.3f q=%r matched=%r",
hit["type"], hit["score"], question, hit["matched"])
return hit["answer"]
messages = [{"role": "system", "content": SYSTEM_PROMPT}, *(history or []),
{"role": "user", "content": question}]
resp = client.chat.completions.create(model=MODEL, messages=messages)
answer = resp.choices[0].message.content
if use_cache and resp.choices[0].finish_reason == "stop": # don't cache truncated output
cache.put(question, answer, ns)
return answer
from sentence_transformers import SentenceTransformer
m = SentenceTransformer("BAAI/bge-small-en-v1.5")
pairs = [ # (q1, q2, should_share_answer)
("how do I reset my password", "I forgot my password, how to reset it", True),
("what's your refund policy", "how do refunds work", True),
("pricing for the pro plan", "pricing for the team plan", False),
("cancel my order", "track my order", False),
]
for a, b, same in pairs:
s = float(m.encode(a, normalize_embeddings=True) @ m.encode(b, normalize_embeddings=True))
print(f"{s:.3f} {'SAME' if same else 'DIFF'} {a!r} vs {b!r}")
pip install redisvl # needs Redis Stack / Redis 8 with the search module
from redisvl.extensions.cache.llm import SemanticCache # older versions: redisvl.extensions.llmcache
cache = SemanticCache(
name="llmcache",
redis_url="redis://localhost:6379",
distance_threshold=0.08, # cosine DISTANCE (1 - similarity): 0.08 ≈ similarity 0.92
ttl=7 * 24 * 3600,
)
if hits := cache.check(prompt=question, num_results=1):
answer = hits[0]["response"]
else:
answer = call_llm(question)
cache.store(prompt=question, response=answer)
answer 3 something else9,411 characters
counted from the line that states the choice (sqlite)
import hashlib
import logging
import re
import sqlite3
import threading
import time
import numpy as np
from openai import OpenAI
log = logging.getLogger("semantic_cache")
client = OpenAI()
EMBED_MODEL = "text-embedding-3-small"
def _normalize(text: str) -> str:
return re.sub(r"\s+", " ", text.strip().lower())
def _hash(text: str) -> str:
return hashlib.sha256(text.encode()).hexdigest()
def embed(text: str) -> np.ndarray:
v = client.embeddings.create(model=EMBED_MODEL, input=text).data[0].embedding
v = np.asarray(v, dtype=np.float32)
return v / np.linalg.norm(v)
class SemanticCache:
def __init__(self, path="semantic_cache.db", threshold=0.93, ttl_seconds=7 * 86400):
self.threshold = threshold
self.ttl = ttl_seconds
self.lock = threading.Lock()
self.db = sqlite3.connect(path, check_same_thread=False)
self.db.executescript("""
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY,
scope TEXT NOT NULL,
qhash TEXT NOT NULL,
question TEXT NOT NULL,
embedding BLOB NOT NULL,
answer TEXT NOT NULL,
created REAL NOT NULL,
hits INTEGER NOT NULL DEFAULT 0,
UNIQUE(scope, qhash)
);
CREATE INDEX IF NOT EXISTS ix_scope ON entries(scope);
""")
# scope -> (list of ids, matrix of embeddings), loaded lazily
self._index: dict[str, tuple[list[int], np.ndarray | None]] = {}
def _fresh_after(self) -> float:
return time.time() - self.ttl
def _load(self, scope: str):
if scope not in self._index:
rows = self.db.execute(
"SELECT id, embedding FROM entries WHERE scope=? AND created>?",
(scope, self._fresh_after()),
).fetchall()
ids = [r[0] for r in rows]
mat = np.vstack([np.frombuffer(r[1], dtype=np.float32) for r in rows]) if rows else None
self._index[scope] = (ids, mat)
return self._index[scope]
def lookup(self, question: str, scope: str):
"""Returns (answer_or_None, embedding_or_None). Reuse the embedding in store()."""
norm = _normalize(question)
with self.lock:
# 1) Exact match: free, no embedding call
row = self.db.execute(
"SELECT id, answer FROM entries WHERE scope=? AND qhash=? AND created>?",
(scope, _hash(norm), self._fresh_after()),
).fetchone()
if row:
self.db.execute("UPDATE entries SET hits=hits+1 WHERE id=?", (row[0],))
self.db.commit()
log.info("exact hit: %r", question)
return row[1], None
# 2) Semantic match (embed outside the lock)
q = embed(norm)
with self.lock:
ids, mat = self._load(scope)
if mat is None:
return None, q
scores = mat @ q # cosine similarity: vectors are unit-normalized
best = int(np.argmax(scores))
score = float(scores[best])
row = self.db.execute(
"SELECT question, answer, created FROM entries WHERE id=?", (ids[best],)
).fetchone()
if row and score >= self.threshold and row[2] > self._fresh_after():
self.db.execute("UPDATE entries SET hits=hits+1 WHERE id=?", (ids[best],))
self.db.commit()
log.info("semantic hit %.3f: %r ~ %r", score, question, row[0])
return row[1], q
if row and score >= self.threshold - 0.07:
# Near misses: review these to tune the threshold
log.info("near miss %.3f: %r ~ %r", score, question, row[0])
return None, q
def store(self, question: str, answer: str, scope: str, embedding: np.ndarray | None = None):
norm = _normalize(question)
emb = embedding if embedding is not None else embed(norm)
with self.lock:
self.db.execute(
"""INSERT INTO entries (scope, qhash, question, embedding, answer, created)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(scope, qhash) DO UPDATE SET
answer=excluded.answer, embedding=excluded.embedding,
created=excluded.created""",
(scope, _hash(norm), question, emb.astype(np.float32).tobytes(), answer, time.time()),
)
self.db.commit()
entry_id = self.db.execute(
"SELECT id FROM entries WHERE scope=? AND qhash=?", (scope, _hash(norm))
).fetchone()[0]
ids, mat = self._load(scope)
if entry_id not in ids:
ids.append(entry_id)
mat = emb[None, :] if mat is None else np.vstack([mat, emb])
self._index[scope] = (ids, mat)
def clear(self, scope: str | None = None):
with self.lock:
if scope:
self.db.execute("DELETE FROM entries WHERE scope=?", (scope,))
self._index.pop(scope, None)
else:
self.db.execute("DELETE FROM entries")
self._index.clear()
self.db.commit()
import hashlib
from openai import OpenAI
from semantic_cache import SemanticCache
client = OpenAI()
cache = SemanticCache(threshold=0.93)
MODEL = "gpt-4o-mini"
SYSTEM_PROMPT = "You are a helpful support assistant for Acme."
def scope_for(tenant: str = "default") -> str:
# Anything that changes what the right answer is belongs in the scope.
# Changing the model or prompt automatically starts a fresh cache.
key = f"{MODEL}|{SYSTEM_PROMPT}|{tenant}"
return hashlib.sha256(key.encode()).hexdigest()[:16]
def ask(question: str, tenant: str = "default") -> str:
scope = scope_for(tenant)
cached, emb = cache.lookup(question, scope)
if cached is not None:
return cached
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question},
],
)
answer = resp.choices[0].message.content
cache.store(question, answer, scope, emb) # reuses the embedding, no second call
return answer
answer 4 something else11,256 characters
coded by hand: the setup code stores vectors in SQLite (imports sqlite3, no Redis client); Redis appears only as a scaling option
pip install sentence-transformers numpy openai
import hashlib
import json
import re
import sqlite3
import threading
import time
from dataclasses import dataclass
import numpy as np
from sentence_transformers import SentenceTransformer
@dataclass
class CacheHit:
answer: str
matched_question: str
similarity: float
exact: bool
def _normalize(text: str) -> str:
text = text.lower().strip()
text = re.sub(r"\s+", " ", text)
return text.rstrip("?!. ")
class SemanticCache:
def __init__(
self,
path: str = "semantic_cache.db",
model_name: str = "BAAI/bge-small-en-v1.5",
threshold: float = 0.92,
ttl_seconds: int | None = 7 * 24 * 3600,
):
self.embedder = SentenceTransformer(model_name)
self.dim = self.embedder.get_sentence_embedding_dimension()
self.threshold = threshold
self.ttl = ttl_seconds
self._lock = threading.Lock()
self.db = sqlite3.connect(path, check_same_thread=False)
self.db.execute("""
CREATE TABLE IF NOT EXISTS cache (
id INTEGER PRIMARY KEY,
namespace TEXT NOT NULL,
exact_key TEXT NOT NULL,
question TEXT NOT NULL,
answer TEXT NOT NULL,
embedding BLOB NOT NULL,
created REAL NOT NULL,
hits INTEGER NOT NULL DEFAULT 0
)""")
self.db.execute(
"CREATE INDEX IF NOT EXISTS ix_exact ON cache(namespace, exact_key)")
self.db.commit()
# namespace -> {"ids": [...], "created": [...], "matrix": np.ndarray}
self._index: dict[str, dict] = {}
self._load()
# ---------- public API ----------
@staticmethod
def namespace(**params) -> str:
"""Scope entries to everything that affects the answer:
model, system prompt, temperature, tool set, etc."""
blob = json.dumps(params, sort_keys=True, default=str)
return hashlib.sha256(blob.encode()).hexdigest()[:16]
def get(self, question: str, namespace: str) -> CacheHit | None:
exact_key = self._exact_key(question)
now = time.time()
# 1) exact match: free, no embedding needed
row = self.db.execute(
"SELECT id, question, answer, created FROM cache "
"WHERE namespace=? AND exact_key=? ORDER BY created DESC LIMIT 1",
(namespace, exact_key),
).fetchone()
if row and not self._expired(row[3], now):
self._bump(row[0])
return CacheHit(row[2], row[1], 1.0, exact=True)
# 2) semantic match
idx = self._index.get(namespace)
if not idx or not idx["ids"]:
return None
q = self._embed(question)
with self._lock:
scores = idx["matrix"] @ q # cosine sim (vectors normalized)
if self.ttl is not None:
created = np.asarray(idx["created"])
scores = np.where(now - created > self.ttl, -1.0, scores)
best = int(np.argmax(scores))
best_score = float(scores[best])
best_id = idx["ids"][best]
if best_score < self.threshold:
return None
row = self.db.execute(
"SELECT question, answer FROM cache WHERE id=?", (best_id,)
).fetchone()
if not row:
return None
self._bump(best_id)
return CacheHit(row[1], row[0], best_score, exact=False)
def put(self, question: str, answer: str, namespace: str) -> None:
emb = self._embed(question)
now = time.time()
with self._lock:
cur = self.db.execute(
"INSERT INTO cache (namespace, exact_key, question, answer, embedding, created) "
"VALUES (?, ?, ?, ?, ?, ?)",
(namespace, self._exact_key(question), question, answer,
emb.astype(np.float32).tobytes(), now),
)
self.db.commit()
self._add_to_index(namespace, cur.lastrowid, emb, now)
def invalidate(self, namespace: str | None = None) -> None:
with self._lock:
if namespace:
self.db.execute("DELETE FROM cache WHERE namespace=?", (namespace,))
self._index.pop(namespace, None)
else:
self.db.execute("DELETE FROM cache")
self._index.clear()
self.db.commit()
def similarity(self, a: str, b: str) -> float:
return float(self._embed(a) @ self._embed(b))
# ---------- internals ----------
def _embed(self, text: str) -> np.ndarray:
return self.embedder.encode(
_normalize(text), normalize_embeddings=True
).astype(np.float32)
@staticmethod
def _exact_key(question: str) -> str:
return hashlib.sha256(_normalize(question).encode()).hexdigest()
def _expired(self, created: float, now: float) -> bool:
return self.ttl is not None and now - created > self.ttl
def _bump(self, row_id: int) -> None:
self.db.execute("UPDATE cache SET hits = hits + 1 WHERE id=?", (row_id,))
self.db.commit()
def _load(self) -> None:
for row_id, ns, blob, created in self.db.execute(
"SELECT id, namespace, embedding, created FROM cache"
):
self._add_to_index(ns, row_id, np.frombuffer(blob, dtype=np.float32), created)
def _add_to_index(self, ns, row_id, emb, created) -> None:
idx = self._index.setdefault(
ns, {"ids": [], "created": [], "matrix": np.empty((0, self.dim), np.float32)})
idx["ids"].append(row_id)
idx["created"].append(created)
idx["matrix"] = np.vstack([idx["matrix"], emb[None, :]])
from openai import OpenAI
from semantic_cache import SemanticCache
client = OpenAI()
cache = SemanticCache(threshold=0.92)
SYSTEM_PROMPT = "You are a helpful assistant for Acme's product docs."
MODEL = "gpt-4o-mini"
def ask(question: str) -> str:
ns = SemanticCache.namespace(model=MODEL, system=SYSTEM_PROMPT, temperature=0)
hit = cache.get(question, ns)
if hit:
print(f"[cache {'exact' if hit.exact else f'semantic {hit.similarity:.3f}'}] "
f"matched: {hit.matched_question!r}")
return hit.answer
resp = client.chat.completions.create(
model=MODEL,
temperature=0,
messages=[{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question}],
)
answer = resp.choices[0].message.content
cache.put(question, answer, ns)
return answer
from semantic_cache import SemanticCache
cache = SemanticCache(path=":memory:")
# (q1, q2, should_share_answer)
pairs = [
("How do I reset my password?", "how can i reset my password", True),
("How do I reset my password?", "I forgot my password, what do I do?", True),
("How do I reset my password?", "How do I reset my username?", False),
("What's the price of the Pro plan?", "How much does Pro cost?", True),
("What's the price of the Pro plan?", "What's the price of the Team plan?", False),
("Can I export to CSV?", "Can I not export to CSV?", False),
]
scored = [(cache.similarity(a, b), same, a, b) for a, b, same in pairs]
for s, same, a, b in sorted(scored, reverse=True):
print(f"{s:.3f} {'SAME' if same else 'DIFF'} {a!r} | {b!r}")
for t in [0.85, 0.88, 0.90, 0.92, 0.94, 0.96]:
hits = [same for s, same, *_ in scored if s >= t]
wrong = sum(1 for same in hits if not same)
print(f"threshold {t:.2f}: {len(hits)} hits, {wrong} wrong")
# pip install redisvl (needs Redis Stack / Redis 8 with the search module)
from redisvl.extensions.cache.llm import SemanticCache # older versions: redisvl.extensions.llmcache
from redisvl.utils.vectorize import HFTextVectorizer
llmcache = SemanticCache(
name="llmcache",
redis_url="redis://localhost:6379",
distance_threshold=0.08,
ttl=7 * 24 * 3600,
vectorizer=HFTextVectorizer("BAAI/bge-small-en-v1.5"),
)
if hits := llmcache.check(prompt=question):
answer = hits[0]["response"]
else:
answer = call_llm(question)
llmcache.store(prompt=question, response=answer)
answer 5 something else9,273 characters
coded by hand: confirmed: OpenAI embeddings + NumPy + SQLite in the code
# semantic_cache.py
import hashlib
import re
import sqlite3
import threading
import time
import numpy as np
from openai import OpenAI
client = OpenAI()
def _normalize(text: str) -> str:
return re.sub(r"\s+", " ", text.strip().lower())
def _sha(text: str) -> str:
return hashlib.sha256(text.encode()).hexdigest()
class SemanticCache:
def __init__(
self,
path: str = "semantic_cache.db",
threshold: float = 0.92, # cosine similarity needed for a hit
ttl_seconds: int = 7 * 24 * 3600, # entries older than this are ignored
embed_model: str = "text-embedding-3-small",
):
self.threshold = threshold
self.ttl = ttl_seconds
self.embed_model = embed_model
self.lock = threading.Lock()
self.db = sqlite3.connect(path, check_same_thread=False)
self.db.execute("""
CREATE TABLE IF NOT EXISTS cache (
id INTEGER PRIMARY KEY,
scope TEXT NOT NULL,
qhash TEXT NOT NULL,
question TEXT NOT NULL,
embedding BLOB NOT NULL,
answer TEXT NOT NULL,
created REAL NOT NULL
)""")
self.db.execute("CREATE INDEX IF NOT EXISTS ix_scope_hash ON cache(scope, qhash)")
self.db.commit()
# In-memory index: scope -> {"ids": [...], "vecs": np.ndarray, "created": [...]}
self.index: dict[str, dict] = {}
for id_, scope, emb, created in self.db.execute(
"SELECT id, scope, embedding, created FROM cache"
):
self._add_to_index(scope, id_, np.frombuffer(emb, dtype=np.float32), created)
# ---------- public API ----------
@staticmethod
def scope(model: str, system_prompt: str, tenant: str = "") -> str:
"""Partition key: entries only match within the same model/prompt/tenant."""
return _sha(f"{model}\x00{system_prompt}\x00{tenant}")
def lookup(self, question: str, scope: str):
"""Returns (answer_or_None, embedding_or_None, score). Reuse the embedding in store()."""
norm = _normalize(question)
now = time.time()
# 1) exact match, no embedding call
row = self.db.execute(
"SELECT answer, created FROM cache WHERE scope=? AND qhash=? "
"ORDER BY created DESC LIMIT 1",
(scope, _sha(norm)),
).fetchone()
if row and now - row[1] < self.ttl:
return row[0], None, 1.0
# 2) semantic match
emb = self._embed(norm)
bucket = self.index.get(scope)
if not bucket or len(bucket["ids"]) == 0:
return None, emb, 0.0
with self.lock:
sims = bucket["vecs"] @ emb # vectors are unit-normalized, so dot product = cosine
fresh = (now - np.asarray(bucket["created"])) < self.ttl
sims = np.where(fresh, sims, -1.0)
best = int(np.argmax(sims))
score = float(sims[best])
best_id = bucket["ids"][best]
if score >= self.threshold:
answer = self.db.execute(
"SELECT answer FROM cache WHERE id=?", (best_id,)
).fetchone()[0]
return answer, emb, score
return None, emb, score
def store(self, question: str, answer: str, scope: str, embedding=None):
norm = _normalize(question)
emb = embedding if embedding is not None else self._embed(norm)
now = time.time()
with self.lock:
cur = self.db.execute(
"INSERT INTO cache (scope, qhash, question, embedding, answer, created) "
"VALUES (?, ?, ?, ?, ?, ?)",
(scope, _sha(norm), question, emb.astype(np.float32).tobytes(), answer, now),
)
self.db.commit()
self._add_to_index(scope, cur.lastrowid, emb, now)
def purge_expired(self):
cutoff = time.time() - self.ttl
with self.lock:
self.db.execute("DELETE FROM cache WHERE created < ?", (cutoff,))
self.db.commit()
self.__init__(self.db.execute("PRAGMA database_list").fetchone()[2],
self.threshold, self.ttl, self.embed_model) # rebuild index
# ---------- internals ----------
def _embed(self, text: str) -> np.ndarray:
v = client.embeddings.create(model=self.embed_model, input=text).data[0].embedding
v = np.asarray(v, dtype=np.float32)
return v / np.linalg.norm(v)
def _add_to_index(self, scope, id_, vec, created):
b = self.index.setdefault(scope, {"ids": [], "vecs": None, "created": []})
vec = vec.reshape(1, -1).astype(np.float32)
b["vecs"] = vec if b["vecs"] is None else np.vstack([b["vecs"], vec])
b["ids"].append(id_)
b["created"].append(created)
# app.py
from semantic_cache import SemanticCache, client
cache = SemanticCache(threshold=0.92)
SYSTEM_PROMPT = "You are a helpful support assistant for Acme."
MODEL = "gpt-4o-mini"
def ask(question: str, tenant: str = "") -> str:
scope = cache.scope(MODEL, SYSTEM_PROMPT, tenant)
answer, emb, score = cache.lookup(question, scope)
if answer is not None:
print(f"[cache hit {score:.3f}]")
return answer
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question},
],
)
answer = resp.choices[0].message.content
cache.store(question, answer, scope, embedding=emb) # reuses embedding, no second call
return answer
if __name__ == "__main__":
print(ask("How do I reset my password?"))
print(ask("how can I reset my password")) # semantic hit
print(ask("How do I delete my account?")) # should miss
# pip install redisvl (older versions: from redisvl.extensions.llmcache import SemanticCache)
from redisvl.extensions.cache.llm import SemanticCache
cache = SemanticCache(name="llmcache", redis_url="redis://localhost:6379",
distance_threshold=0.08, ttl=7*24*3600) # distance = 1 - cosine
if hits := cache.check(prompt=question):
return hits[0]["response"]
...
cache.store(prompt=question, response=answer)
GPT-6 Sol · Valkey in 0 of 5
answer 1 Redis4,087 characters
counted from the line that states the choice
docker run -d --name semantic-redis -p 6379:6379 redis/redis-stack-server:latest pip install openai redis numpy export OPENAI_API_KEY="..."
import hashlib
import os
import numpy as np
import redis
from redis.exceptions import ResponseError
from openai import OpenAI
TTL_SECONDS = 24 * 60 * 60
THRESHOLD = 0.94 # Starting point; tune against your own questions.
INDEX = "question_cache_idx"
PREFIX = "question_cache:entry:"
db = redis.Redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379"))
ai = OpenAI()
try:
db.execute_command("FT.INFO", INDEX)
except ResponseError as exc:
if "unknown index" not in str(exc).lower():
raise
db.execute_command(
"FT.CREATE", INDEX, "ON", "HASH", "PREFIX", 1, PREFIX,
"SCHEMA",
"scope", "TAG",
"version", "TAG",
"answer", "TEXT",
"embedding", "VECTOR", "FLAT", 6,
"TYPE", "FLOAT32", "DIM", 1536, "DISTANCE_METRIC", "COSINE",
)
def digest(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def embedding(question: str) -> bytes:
values = ai.embeddings.create(
model="text-embedding-3-small", input=question
).data[0].embedding
vector = np.asarray(values, dtype=np.float32)
vector /= np.linalg.norm(vector)
return vector.tobytes()
def answer_question(question: str, *, scope: str, version: str) -> str:
"""
scope: tenant + authorization scope
version: chat model + system-prompt version + knowledge-base snapshot
"""
scope_id, version_id = digest(scope), digest(version)
exact_key = "question_cache:exact:" + digest(
f"{scope_id}\0{version_id}\0{question}"
)
if (answer := db.get(exact_key)) is not None:
return answer.decode("utf-8")
vector = embedding(question)
result = db.execute_command(
"FT.SEARCH", INDEX,
f"(@scope:{{{scope_id}}} @version:{{{version_id}}})"
"=>[KNN 1 @embedding $vec AS distance]",
"PARAMS", 2, "vec", vector,
"SORTBY", "distance",
"RETURN", 2, "answer", "distance",
"DIALECT", 2,
)
if result[0]:
fields = dict(zip(result[2][::2], result[2][1::2]))
if 1.0 - float(fields[b"distance"]) >= THRESHOLD:
answer = fields[b"answer"].decode("utf-8")
db.set(exact_key, answer, ex=TTL_SECONDS)
return answer
response = ai.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[{"role": "user", "content": question}],
)
answer = response.choices[0].message.content or ""
key = PREFIX + digest(f"{scope_id}\0{version_id}\0{question}")
with db.pipeline() as pipe:
pipe.hset(key, mapping={
"scope": scope_id,
"version": version_id,
"answer": answer,
"embedding": vector,
})
pipe.expire(key, TTL_SECONDS)
pipe.set(exact_key, answer, ex=TTL_SECONDS)
pipe.execute()
return answer
from semantic_cache import answer_question
answer = answer_question(
user_question,
scope=f"tenant:{tenant_id}:permissions:{permission_set_id}",
version="gpt-4o-mini:prompt-v3:kb-snapshot-42",
)
answer 2 Redis3,516 characters
counted from the line that states the choice
docker run -d --name semantic-cache -p 6379:6379 redis/redis-stack-server:latest pip install openai redis numpy export OPENAI_API_KEY="..."
import hashlib
import json
import os
import uuid
import numpy as np
import redis
from openai import OpenAI
from redis.commands.search.field import TagField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import Query
from redis.exceptions import ResponseError
client = OpenAI()
db = redis.Redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379"))
INDEX = "question_cache"
EMBED_MODEL = "text-embedding-3-small"
CHAT_MODEL = "gpt-4.1-mini"
SYSTEM_PROMPT = "Answer the user's question accurately and concisely."
MAX_COSINE_DISTANCE = 0.10 # similarity >= 0.90
TTL_SECONDS = 7 * 24 * 60 * 60
try:
db.ft(INDEX).create_index(
[
TagField("scope"),
VectorField(
"embedding",
"HNSW",
{"TYPE": "FLOAT32", "DIM": 1536, "DISTANCE_METRIC": "COSINE"},
),
],
definition=IndexDefinition(prefix=["cache:"], index_type=IndexType.HASH),
)
except ResponseError as exc:
if "Index already exists" not in str(exc):
raise
def embedding(question: str) -> bytes:
values = client.embeddings.create(
model=EMBED_MODEL, input=question
).data[0].embedding
return np.asarray(values, dtype=np.float32).tobytes()
def ask(question: str, user_id: str) -> str:
# Changing any answer-affecting setting changes the cache scope.
scope_data = [user_id, SYSTEM_PROMPT, CHAT_MODEL, EMBED_MODEL]
scope = hashlib.sha256(
json.dumps(scope_data, ensure_ascii=False).encode()
).hexdigest()
vector = embedding(question)
query = (
Query(f"@scope:{{{scope}}}=>[KNN 1 @embedding $vec AS distance]")
.sort_by("distance")
.return_fields("answer", "distance")
.dialect(2)
)
results = db.ft(INDEX).search(query, query_params={"vec": vector})
if results.docs and float(results.docs[0].distance) <= MAX_COSINE_DISTANCE:
answer = results.docs[0].answer
return answer.decode() if isinstance(answer, bytes) else answer
response = client.responses.create(
model=CHAT_MODEL, instructions=SYSTEM_PROMPT, input=question
)
answer = response.output_text
key = f"cache:{uuid.uuid4().hex}"
with db.pipeline() as pipe:
pipe.hset(key, mapping={
"scope": scope,
"embedding": vector,
"answer": answer,
})
pipe.expire(key, TTL_SECONDS)
pipe.execute()
return answer
if __name__ == "__main__":
print(ask("How do I reset my password?", user_id="user-123"))
answer 3 Redis3,513 characters
counted from the line that states the choice
docker run -d --name semantic-cache -p 6379:6379 redis/redis-stack-server:latest pip install redis openai numpy export OPENAI_API_KEY=...
# semantic_cache.py
import hashlib
import uuid
import numpy as np
import redis
from openai import OpenAI
from redis.exceptions import ResponseError
r = redis.Redis(host="localhost", port=6379)
ai = OpenAI()
INDEX = "qa_cache_idx"
EMBED_MODEL = "text-embedding-3-small"
EMBED_DIMS = 1536
MAX_COSINE_DISTANCE = 0.08 # tune against real question pairs
TTL_SECONDS = 7 * 24 * 60 * 60
try:
r.execute_command(
"FT.CREATE", INDEX, "ON", "HASH", "PREFIX", "1", "qa:",
"SCHEMA",
"scope", "TAG",
"embedding", "VECTOR", "HNSW", "6",
"TYPE", "FLOAT32", "DIM", EMBED_DIMS,
"DISTANCE_METRIC", "COSINE",
)
except ResponseError as e:
if "Index already exists" not in str(e):
raise
def cached_answer(
question: str,
*,
tenant_id: str,
knowledge_version: str,
prompt_version: str,
answer_fn,
) -> tuple[str, bool]:
"""answer_fn(question) generates a fresh answer. Returns (answer, cache_hit)."""
if not all((question.strip(), tenant_id, knowledge_version, prompt_version)):
raise ValueError("Question and cache-scope fields are required")
# Never reuse answers across tenants, data revisions, or prompt revisions.
scope = hashlib.sha256(
f"{tenant_id}\0{knowledge_version}\0{prompt_version}".encode()
).hexdigest()
embedding = ai.embeddings.create(
model=EMBED_MODEL, input=question
).data[0].embedding
vector = np.asarray(embedding, dtype=np.float32).tobytes()
result = r.execute_command(
"FT.SEARCH", INDEX,
f"(@scope:{{{scope}}})=>[KNN 1 @embedding $v AS distance]",
"PARAMS", 2, "v", vector,
"SORTBY", "distance", "ASC",
"RETURN", 2, "answer", "distance",
"DIALECT", 2,
)
if result[0]:
fields = dict(zip(result[2][::2], result[2][1::2]))
if float(fields[b"distance"]) <= MAX_COSINE_DISTANCE:
return fields[b"answer"].decode(), True
answer = answer_fn(question)
key = f"qa:{uuid.uuid4().hex}"
with r.pipeline() as pipe:
pipe.hset(key, mapping={
"scope": scope,
"embedding": vector,
"answer": answer,
})
pipe.expire(key, TTL_SECONDS)
pipe.execute()
return answer, False
answer, hit = cached_answer(
question,
tenant_id=current_tenant_id,
knowledge_version=current_docs_revision,
prompt_version="support-prompt-v3",
answer_fn=generate_answer, # your existing question -> answer function
)
answer 4 something else3,505 characters
counted from the line that states the choice (sqlite)
pip install openai export OPENAI_API_KEY="..."
import json
import math
import sqlite3
import time
from contextlib import closing
from openai import OpenAI
client = OpenAI()
DB = "semantic_cache.sqlite3"
EMBED_MODEL = "text-embedding-3-small"
CHAT_MODEL = "gpt-4o-mini"
PROMPT_VERSION = "v1"
SYSTEM_PROMPT = "Answer the user's question clearly and accurately."
MIN_SIMILARITY = 0.96
TTL_SECONDS = 7 * 24 * 60 * 60
with closing(sqlite3.connect(DB)) as db:
db.execute("""
CREATE TABLE IF NOT EXISTS answers (
scope TEXT NOT NULL,
question TEXT NOT NULL,
embedding TEXT NOT NULL,
answer TEXT NOT NULL,
expires_at REAL NOT NULL,
PRIMARY KEY (scope, question)
)
""")
db.commit()
def embed(text: str) -> list[float]:
vector = client.embeddings.create(
model=EMBED_MODEL, input=text
).data[0].embedding
length = math.sqrt(sum(x * x for x in vector))
return [x / length for x in vector]
def answer(question: str, tenant_id: str = "public") -> str:
question = question.strip()
if not question:
raise ValueError("Question cannot be empty")
# Partition entries so answers cannot leak between tenants or prompt versions.
scope = json.dumps([tenant_id, CHAT_MODEL, EMBED_MODEL, PROMPT_VERSION])
query_vector = embed(question)
now = time.time()
with closing(sqlite3.connect(DB)) as db:
rows = db.execute(
"""SELECT question, embedding, answer FROM answers
WHERE scope = ? AND expires_at > ?""",
(scope, now),
).fetchall()
best_answer, best_score = None, MIN_SIMILARITY
for old_question, stored_vector, stored_answer in rows:
if old_question == question:
return stored_answer
score = sum(
a * b for a, b in zip(query_vector, json.loads(stored_vector))
)
if score >= best_score:
best_answer, best_score = stored_answer, score
if best_answer is not None:
return best_answer
result = client.chat.completions.create(
model=CHAT_MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question},
],
)
generated = result.choices[0].message.content
with closing(sqlite3.connect(DB)) as db:
db.execute(
"""INSERT OR REPLACE INTO answers
(scope, question, embedding, answer, expires_at)
VALUES (?, ?, ?, ?, ?)""",
(scope, question, json.dumps(query_vector), generated, now + TTL_SECONDS),
)
db.commit()
return generated
answer 5 Redis3,107 characters
counted from the line that states the choice
docker run -d --name semantic-cache -p 6379:6379 redis/redis-stack-server:latest pip install openai redis export OPENAI_API_KEY=... export REDIS_URL=redis://localhost:6379
# semantic_cache.py
import hashlib
import os
import uuid
from array import array
import redis
from openai import OpenAI
from redis.exceptions import ResponseError
r = redis.Redis.from_url(os.environ["REDIS_URL"])
ai = OpenAI()
EMBED_MODEL = "text-embedding-3-small"
ANSWER_MODEL = "gpt-4.1-mini"
INSTRUCTIONS = "Answer the user's question accurately and concisely."
PROMPT_VERSION = "v1"
MAX_COSINE_DISTANCE = 0.06 # roughly cosine similarity >= 0.94
TTL_SECONDS = 7 * 24 * 60 * 60
try:
r.execute_command(
"FT.CREATE", "question_cache", "ON", "HASH",
"PREFIX", 1, "sc:",
"SCHEMA",
"scope", "TAG",
"embedding", "VECTOR", "HNSW", 6,
"TYPE", "FLOAT32", "DIM", 1536, "DISTANCE_METRIC", "COSINE",
)
except ResponseError as exc:
if "Index already exists" not in str(exc):
raise
def answer(question: str, *, tenant_id: str, context_version: str = "none") -> str:
# Include every factor that can change the correct answer in this scope.
scope = hashlib.sha256(
f"{tenant_id}\0{context_version}\0{ANSWER_MODEL}\0{PROMPT_VERSION}".encode()
).hexdigest()
values = ai.embeddings.create(model=EMBED_MODEL, input=question).data[0].embedding
vector = array("f", values).tobytes()
matches = r.execute_command(
"FT.SEARCH", "question_cache",
f"(@scope:{{{scope}}})=>[KNN 1 @embedding $vec AS distance]",
"PARAMS", 2, "vec", vector,
"SORTBY", "distance", "ASC",
"RETURN", 1, "distance",
"LIMIT", 0, 1,
"DIALECT", 2,
)
if matches[0]:
key, fields = matches[1], dict(zip(matches[2][::2], matches[2][1::2]))
if float(fields[b"distance"]) <= MAX_COSINE_DISTANCE:
cached = r.hget(key, "answer")
if cached is not None:
return cached.decode()
response = ai.responses.create(
model=ANSWER_MODEL, instructions=INSTRUCTIONS, input=question
)
result = response.output_text
if result:
key = f"sc:{uuid.uuid4().hex}"
r.hset(key, mapping={"scope": scope, "embedding": vector, "answer": result})
r.expire(key, TTL_SECONDS)
return result
DeepSeek V4.1 Flash · Valkey in 0 of 5
answer 1 Redis6,155 characters
counted from the line that states the choice
docker run -d --name redis-stack -p 6379:6379 redis/redis-stack-server:latest
pip install fastapi uvicorn openai redis redisvl sentence-transformers
# app.py
import os
import hashlib
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI
from redis import Redis
from redisvl.extensions.cache.llm import SemanticCache
from redisvl.utils.vectorize import HFTextVectorizer
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
DEFAULT_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
PROMPT_VERSION = os.getenv("PROMPT_VERSION", "v1")
DISTANCE_THRESHOLD = float(os.getenv("CACHE_DISTANCE_THRESHOLD", "0.15"))
TTL_SECONDS = int(os.getenv("CACHE_TTL_SECONDS", "3600"))
app = FastAPI()
llm = OpenAI(api_key=OPENAI_API_KEY)
redis_client = Redis.from_url(REDIS_URL, decode_responses=False)
# Local embeddings: cheap, no extra API cost.
vectorizer = HFTextVectorizer(model="sentence-transformers/all-MiniLM-L6-v2")
cache = SemanticCache(
name="llm_semantic_cache",
vectorizer=vectorizer,
redis_url=REDIS_URL,
distance_threshold=DISTANCE_THRESHOLD,
ttl=TTL_SECONDS,
)
class AskRequest(BaseModel):
question: str
tenant_id: str = "default"
system_prompt: str = "You are a helpful assistant."
model: str = DEFAULT_MODEL
class AskResponse(BaseModel):
answer: str
cached: bool
distance: Optional[float] = None
def exact_key(req: AskRequest) -> str:
raw = f"{req.tenant_id}|{req.model}|{PROMPT_VERSION}|{req.system_prompt}|{req.question}"
return "llm:exact:" + hashlib.sha256(raw.encode()).hexdigest()
def namespace(req: AskRequest) -> str:
system_hash = hashlib.sha256(req.system_prompt.encode()).hexdigest()[:12]
return f"{req.tenant_id}:{req.model}:{PROMPT_VERSION}:{system_hash}"
@app.post("/ask", response_model=AskResponse)
def ask(req: AskRequest):
# 1. Exact match first: free, avoids embedding.
ekey = exact_key(req)
if redis_client.exists(ekey):
return AskResponse(
answer=redis_client.get(ekey).decode(),
cached=True,
distance=0.0,
)
# 2. Semantic match. Namespace prevents cross-tenant/model leakage.
ns = namespace(req)
cache_prompt = f"[{ns}] {req.question}"
hits = cache.check(prompt=cache_prompt)
if hits:
hit = hits[0]
answer = hit.response
redis_client.setex(ekey, TTL_SECONDS, answer)
return AskResponse(answer=answer, cached=True, distance=hit.distance)
# 3. Miss: call the LLM.
completion = llm.chat.completions.create(
model=req.model,
messages=[
{"role": "system", "content": req.system_prompt},
{"role": "user", "content": req.question},
],
temperature=0,
)
answer = completion.choices[0].message.content or ""
# 4. Store for next time.
cache.store(prompt=cache_prompt, response=answer)
redis_client.setex(ekey, TTL_SECONDS, answer)
return AskResponse(answer=answer, cached=False)
export OPENAI_API_KEY=sk-... uvicorn app:app --reload
curl -s localhost:8000/ask \
-H 'content-type: application/json' \
-d '{"question":"What is the capital of France?","tenant_id":"acme"}' | jq
curl -s localhost:8000/ask \
-H 'content-type: application/json' \
-d '{"question":"Whats the capital of France?","tenant_id":"acme"}' | jq
import numpy as np
from redisvl.utils.vectorize import HFTextVectorizer
v = HFTextVectorizer(model="sentence-transformers/all-MiniLM-L6-v2")
pairs = [
("What is the capital of France?", "What's France's capital?", True),
("What is the capital of France?", "What is the capital of Germany?", False),
]
for a, b, should_match in pairs:
ea = np.array(v.embed(a))
eb = np.array(v.embed(b))
cos = np.dot(ea, eb) / (np.linalg.norm(ea) * np.linalg.norm(eb))
dist = 1 - cos
print(f"distance={dist:.3f} should_match={should_match} | {a} || {b}")
answer 2 something else6,521 characters
counted from the line that states the choice (postgres)
docker run -d \ --name pgvector \ -p 5432:5432 \ -e POSTGRES_PASSWORD=postgres \ pgvector/pgvector:pg16
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS semantic_cache (
id BIGSERIAL PRIMARY KEY,
namespace TEXT NOT NULL,
query TEXT NOT NULL,
embedding vector(1536) NOT NULL, -- text-embedding-3-small
response TEXT NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS semantic_cache_embedding_idx
ON semantic_cache USING hnsw (embedding vector_cosine_ops);
CREATE INDEX IF NOT EXISTS semantic_cache_namespace_created_idx
ON semantic_cache (namespace, created_at DESC);
pip install openai psycopg[binary] pgvector
import json
import os
from dataclasses import dataclass
from typing import Callable, Optional
import psycopg
from pgvector.psycopg import register_vector
from psycopg.types.json import Jsonb
from openai import OpenAI
client = OpenAI()
@dataclass
class CacheHit:
response: str
similarity: float
matched_query: str
metadata: dict
class SemanticCache:
def __init__(
self,
dsn: str,
threshold: float = 0.92,
ttl_seconds: int = 7 * 24 * 3600,
embedding_model: str = "text-embedding-3-small",
):
self.dsn = dsn
self.threshold = threshold
self.ttl_seconds = ttl_seconds
self.embedding_model = embedding_model
self.conn = psycopg.connect(dsn, autocommit=True)
register_vector(self.conn)
def _embed(self, text: str) -> list[float]:
resp = client.embeddings.create(
model=self.embedding_model,
input=text,
)
return resp.data[0].embedding
def get(self, question: str, namespace: str = "default") -> Optional[CacheHit]:
embedding = self._embed(question)
with self.conn.cursor() as cur:
cur.execute(
"""
SELECT response, 1 - (embedding <=> %s) AS similarity, query, metadata
FROM semantic_cache
WHERE namespace = %s
AND created_at > now() - make_interval(secs => %s)
ORDER BY embedding <=> %s
LIMIT 1
""",
(embedding, namespace, self.ttl_seconds, embedding),
)
row = cur.fetchone()
if row and row[1] >= self.threshold:
return CacheHit(
response=row[0],
similarity=float(row[1]),
matched_query=row[2],
metadata=row[3] or {},
)
return None
def set(
self,
question: str,
response: str,
namespace: str = "default",
metadata: dict | None = None,
):
embedding = self._embed(question)
with self.conn.cursor() as cur:
cur.execute(
"""
INSERT INTO semantic_cache (namespace, query, embedding, response, metadata)
VALUES (%s, %s, %s, %s, %s)
""",
(namespace, question, embedding, response, Jsonb(metadata or {})),
)
def get_or_create(
self,
question: str,
llm_fn: Callable[[str], str],
namespace: str = "default",
metadata: dict | None = None,
) -> tuple[str, Optional[CacheHit]]:
hit = self.get(question, namespace=namespace)
if hit:
return hit.response, hit
answer = llm_fn(question)
self.set(question, answer, namespace=namespace, metadata=metadata)
return answer, None
cache = SemanticCache(
dsn=os.environ["DATABASE_URL"],
threshold=0.92,
ttl_seconds=86400,
)
def llm(question: str) -> str:
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": question},
],
temperature=0,
)
return resp.choices[0].message.content
# Scope by model + prompt version + user/tenant
namespace = "gpt-4o-mini:v1:user-123"
answer, hit = cache.get_or_create(
"What's the capital of France?",
llm,
namespace=namespace,
)
print(answer)
print("CACHE HIT", hit.similarity) if hit else print("MISS")
# Later, a differently-worded question should hit
answer2, hit2 = cache.get_or_create(
"What is France's capital?",
llm,
namespace=namespace,
)
print(answer2)
print("CACHE HIT", hit2.similarity) if hit2 else print("MISS")
from langchain.globals import set_llm_cache
from langchain_redis import RedisSemanticCache
from langchain_openai import OpenAIEmbeddings
set_llm_cache(RedisSemanticCache(
redis_url="redis://localhost:6379",
embeddings=OpenAIEmbeddings(),
distance_threshold=0.1, # lower = stricter
))
answer 3 Redis4,635 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis/redis-stack:latest
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
redis-data:
docker compose up -d pip install redisvl openai redis export OPENAI_API_KEY=sk-...
import hashlib
import json
import os
from openai import OpenAI
from redis import Redis
from redisvl.extensions.llmcache import SemanticCache
from redisvl.utils.vectorize import OpenAITextVectorizer
REDIS_URL = "redis://localhost:6379"
TTL = 60 * 60 * 24 # 24h
redis_client = Redis.from_url(REDIS_URL, decode_responses=True)
openai_client = OpenAI()
# Local alternative:
# from redisvl.utils.vectorize import HFTextVectorizer
# vectorizer = HFTextVectorizer(model="sentence-transformers/all-MiniLM-L6-v2")
vectorizer = OpenAITextVectorizer(model="text-embedding-3-small")
semantic_cache = SemanticCache(
name="llm_semantic_cache",
redis_url=REDIS_URL,
vectorizer=vectorizer,
distance_threshold=0.1, # <= 0.1 distance ≈ >= 0.90 cosine similarity
ttl=TTL,
)
def normalize(s: str) -> str:
return " ".join(s.lower().strip().split())
def exact_key(prompt: str, model: str, system: str, temperature: float) -> str:
payload = json.dumps(
{
"prompt": normalize(prompt),
"model": model,
"system": system,
"temperature": temperature,
},
sort_keys=True,
)
return "exact:" + hashlib.sha256(payload.encode()).hexdigest()
def semantic_text(prompt: str, model: str, system: str, temperature: float) -> str:
return (
f"model={model}\n"
f"temperature={temperature}\n"
f"system={system}\n"
f"question={normalize(prompt)}"
)
def ask(
prompt: str,
model: str = "gpt-4o-mini",
system: str = "You are a helpful assistant.",
temperature: float = 0.2,
force_refresh: bool = False,
) -> str:
key = exact_key(prompt, model, system, temperature)
if not force_refresh:
# 1. Exact hit
exact = redis_client.get(key)
if exact:
return exact
# 2. Semantic hit
qtext = semantic_text(prompt, model, system, temperature)
hits = semantic_cache.check(qtext, num_results=1)
if hits:
hit = hits[0]
response = hit["response"] if isinstance(hit, dict) else hit.response
redis_client.setex(key, TTL, response)
return response
# 3. Miss: call the LLM
resp = openai_client.chat.completions.create(
model=model,
temperature=temperature,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": prompt},
],
)
answer = resp.choices[0].message.content
# 4. Store both exact and semantic
semantic_cache.store(
prompt=semantic_text(prompt, model, system, temperature),
response=answer,
)
redis_client.setex(key, TTL, answer)
return answer
print(ask("What is the capital of France?"))
print(ask("What's France's capital?")) # likely semantic cache hit
answer 4 Redis4,611 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
redis:
image: redis/redis-stack-server:latest
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
redis-data:
docker compose up -d
pip install "redisvl>=0.3" openai redis
import os
import hashlib
import redis
from openai import OpenAI
from redisvl.extensions.llmcache import SemanticCache
from redisvl.utils.vectorize import OpenAITextVectorizer
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
TTL_SECONDS = 60 * 60 * 24 * 7 # 7 days
DISTANCE_THRESHOLD = 0.18 # lower = stricter, tune this
redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)
vectorizer = OpenAITextVectorizer(
model="text-embedding-3-small",
api_config={"api_key": os.environ["OPENAI_API_KEY"]},
)
llm = OpenAI()
_semantic_caches = {}
def _namespace(model: str, system: str) -> str:
return hashlib.sha256(f"{model}\n{system}".encode()).hexdigest()[:16]
def _cache_for(model: str, system: str) -> SemanticCache:
ns = _namespace(model, system)
if ns not in _semantic_caches:
_semantic_caches[ns] = SemanticCache(
name=f"llm_cache_{ns}",
redis_url=REDIS_URL,
distance_threshold=DISTANCE_THRESHOLD,
vectorizer=vectorizer,
ttl=TTL_SECONDS,
)
return _semantic_caches[ns]
def ask(
question: str,
model: str = "gpt-4o-mini",
system: str = "You are a helpful assistant.",
) -> tuple[str, bool]:
q = question.strip()
ns = _namespace(model, system)
# Exact-match layer: avoids even the embedding call for identical questions
exact_key = f"llm:{ns}:exact:{hashlib.sha256(q.encode()).hexdigest()}"
if exact := redis_client.get(exact_key):
return exact, True
cache = _cache_for(model, system)
# Semantic layer
hits = cache.check(prompt=q)
if hits:
answer = hits[0]["response"]
redis_client.setex(exact_key, TTL_SECONDS, answer)
return answer, True
# Cache miss: call the LLM
resp = llm.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": q},
],
temperature=0,
)
answer = resp.choices[0].message.content or ""
cache.store(prompt=q, response=answer)
redis_client.setex(exact_key, TTL_SECONDS, answer)
return answer, False
answer, cached = ask("How do I reset my password?")
print(answer, cached) # cached=False
answer, cached = ask("I forgot my password, how can I reset it?")
print(answer, cached) # likely cached=True
hits = cache.check(prompt="How do I reset my password?") print(hits[0]["distance"])
from langchain.globals import set_llm_cache
from langchain_redis import RedisSemanticCache
from langchain_openai import OpenAIEmbeddings
set_llm_cache(RedisSemanticCache(
redis_url="redis://localhost:6379",
embedding=OpenAIEmbeddings(model="text-embedding-3-small"),
distance_threshold=0.18,
))
answer 5 Redis6,244 characters
counted from the line that states the choice
docker run -d --name redis-stack -p 6379:6379 redis/redis-stack-server:latest
pip install redis sentence-transformers numpy
import hashlib
import time
from typing import Optional
import numpy as np
import redis
from redis.commands.search.field import NumericField, TagField, TextField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import Query
from sentence_transformers import SentenceTransformer
def _str(x):
return x.decode() if isinstance(x, bytes) else x
class SemanticCache:
def __init__(
self,
redis_url: str = "redis://localhost:6379",
index_name: str = "semantic_cache",
model_name: str = "sentence-transformers/all-MiniLM-L6-v2",
threshold: float = 0.92, # cosine similarity
ttl: int = 86400, # seconds
):
self.redis = redis.from_url(redis_url, decode_responses=False)
self.index_name = index_name
self.model = SentenceTransformer(model_name)
self.dim = self.model.get_sentence_embedding_dimension()
self.threshold = threshold
self.ttl = ttl
self._create_index()
def _create_index(self):
try:
self.redis.ft(self.index_name).info()
return
except redis.exceptions.ResponseError:
pass
schema = (
TextField("question"),
TextField("answer"),
TagField("namespace"),
NumericField("timestamp"),
VectorField(
"vector",
"FLAT",
{
"TYPE": "FLOAT32",
"DIM": self.dim,
"DISTANCE_METRIC": "COSINE",
},
),
)
definition = IndexDefinition(
prefix=[f"{self.index_name}:"],
index_type=IndexType.HASH,
)
self.redis.ft(self.index_name).create_index(schema, definition=definition)
def _embed(self, text: str) -> bytes:
emb = self.model.encode(
[text],
normalize_embeddings=True,
convert_to_numpy=True,
)[0]
return emb.astype(np.float32).tobytes()
def get(self, question: str, namespace: str = "default") -> Optional[str]:
query_vector = self._embed(question)
q = (
Query(f"(@namespace:{{{namespace}}})=>[KNN 1 @vector $vec AS score]")
.sort_by("score")
.return_fields("answer", "question", "timestamp", "score")
.dialect(2)
)
q = q.params({"vec": query_vector})
results = self.redis.ft(self.index_name).search(q)
if results.total == 0:
return None
doc = results.docs[0]
distance = float(_str(doc.score)) # cosine distance = 1 - similarity
similarity = 1.0 - distance
if similarity < self.threshold:
return None
# TTL guard, though Redis key expiration already helps
ts = float(_str(doc.timestamp))
if self.ttl and time.time() - ts > self.ttl:
self.redis.delete(doc.id)
return None
return _str(doc.answer)
def set(self, question: str, answer: str, namespace: str = "default"):
key = (
f"{self.index_name}:"
+ hashlib.sha256(f"{namespace}\n{question}".encode()).hexdigest()
)
self.redis.hset(
key,
mapping={
"question": question,
"answer": answer,
"namespace": namespace,
"timestamp": time.time(),
"vector": self._embed(question),
},
)
if self.ttl:
self.redis.expire(key, self.ttl)
from semantic_cache import SemanticCache
cache = SemanticCache(
threshold=0.92, # tune this
ttl=3600,
)
def ask_llm(question: str, context_hash: str = "global") -> str:
# namespace prevents cross-user / cross-context leaks
cached = cache.get(question, namespace=context_hash)
if cached is not None:
return cached
answer = call_your_llm(question) # your existing LLM call
cache.set(question, answer, namespace=context_hash)
return answer
from langchain.globals import set_llm_cache
from langchain_community.cache import RedisSemanticCache
from langchain_openai import OpenAIEmbeddings
set_llm_cache(RedisSemanticCache(
redis_url="redis://localhost:6379",
embedding=OpenAIEmbeddings(model="text-embedding-3-small"),
score_threshold=0.08, # distance, so 0.08 ≈ 0.92 cosine similarity
))
GLM-5.3-Flash · Valkey in 0 of 5
answer 1 something else6,496 characters
coded by hand: confirmed: Postgres + pgvector is the default pick and the code uses psycopg
CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE llm_cache ( id BIGSERIAL PRIMARY KEY, scope TEXT NOT NULL, -- model + system-prompt hash question TEXT NOT NULL, question_hash TEXT NOT NULL, -- sha1 of normalized question answer TEXT NOT NULL, embedding VECTOR(1536) NOT NULL, -- match your embedding model's dims created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX llm_cache_exact ON llm_cache (scope, question_hash); CREATE INDEX llm_cache_ann ON llm_cache USING hnsw (embedding vector_cosine_ops); CREATE INDEX llm_cache_expiry ON llm_cache (created_at); -- for purging old rows
# pip install openai psycopg[binary] psycopg-pool
import hashlib, json, os, re
import psycopg
from psycopg_pool import ConnectionPool
from openai import OpenAI
EMBED_MODEL = "text-embedding-3-small"
CHAT_MODEL = "gpt-4o-mini"
SIM_THRESHOLD = 0.93 # min cosine similarity for a semantic hit — tune this
TTL_SECONDS = 86_400
client = OpenAI()
pool = ConnectionPool(os.environ["DATABASE_URL"])
# Questions whose answers go stale — never cache these
SKIP_CACHE = re.compile(r"\b(today|now|current|latest|yesterday|weather|news|price|score)\b", re.I)
def _norm(q: str) -> str:
return " ".join(q.lower().strip().split())
def _embed(text: str) -> str:
v = client.embeddings.create(model=EMBED_MODEL, input=text).data[0].embedding
return json.dumps(v) # '[0.1,0.2,...]' parses directly as a pgvector literal
def _scope(model: str, system_prompt: str = "") -> str:
h = hashlib.sha1(system_prompt.encode()).hexdigest()[:12]
return f"{model}:{h}"
def lookup(question: str, scope: str):
q = _norm(question)
h = hashlib.sha1(q.encode()).hexdigest()
with pool.connection() as conn, conn.cursor() as cur:
# 1. Exact match first — skips the embedding call entirely
cur.execute(
"""SELECT answer FROM llm_cache
WHERE question_hash = %s AND scope = %s
AND created_at > now() - make_interval(secs => %s)""",
(h, scope, TTL_SECONDS))
row = cur.fetchone()
if row:
return row[0], "hit:exact"
# 2. Nearest neighbor by cosine similarity
v = _embed(q)
cur.execute(
"""SELECT answer, 1 - (embedding <=> %s::vector) AS sim
FROM llm_cache
WHERE scope = %s
AND created_at > now() - make_interval(secs => %s)
ORDER BY embedding <=> %s::vector
LIMIT 1""",
(v, scope, TTL_SECONDS, v))
row = cur.fetchone()
if row:
sim = row[1]
if sim >= SIM_THRESHOLD:
return row[0], f"hit:sim={sim:.3f}"
return None, f"miss:near(sim={sim:.3f})" # log these to tune the threshold
return None, "miss:cold"
def store(question: str, answer: str, scope: str):
q = _norm(question)
with pool.connection() as conn, conn.cursor() as cur:
cur.execute(
"""INSERT INTO llm_cache (question, answer, embedding, scope, question_hash)
VALUES (%s, %s, %s::vector, %s, %s)""",
(q, answer, _embed(q), scope, hashlib.sha1(q.encode()).hexdigest()))
def _call_llm(question: str, system_prompt: str) -> str:
msgs = ([{"role": "system", "content": system_prompt}] if system_prompt else []) \
+ [{"role": "user", "content": question}]
return client.chat.completions.create(
model=CHAT_MODEL, messages=msgs).choices[0].message.content
def ask(question: str, system_prompt: str = "") -> tuple[str, str]:
scope = _scope(CHAT_MODEL, system_prompt)
answer, reason = (None, "skipped:time-sensitive")
if not SKIP_CACHE.search(question):
answer, reason = lookup(question, scope)
if answer is None:
answer = _call_llm(question, system_prompt)
store(question, answer, scope)
return answer, reason
# pip install redisvl
from redisvl.extensions.llmcache import SemanticCache
cache = SemanticCache(
name="llm_cache",
redis_url="redis://localhost:6379",
distance_threshold=0.10, # cosine distance — smaller = stricter
)
def ask(question: str) -> str:
if hit := cache.check(prompt=question):
return hit[0]["response"]
answer = _call_llm(question)
cache.store(prompt=question, response=answer, ttl=3600)
return answer
answer 2 Redis5,301 characters
counted from the line that states the choice
# semantic_cache.py
import numpy as np
from sentence_transformers import SentenceTransformer
class SemanticCache:
def __init__(self, threshold: float = 0.92, max_entries: int = 10_000):
self.model = SentenceTransformer("all-MiniLM-L6-v2") # 384-dim, CPU-fast
self.threshold = threshold
self.max_entries = max_entries
self.answers: list[str] = []
self.vecs = np.empty((0, 384), dtype=np.float32)
def _embed(self, text: str) -> np.ndarray:
return self.model.encode(text, normalize_embeddings=True)
def lookup(self, question: str) -> str | None:
if not self.answers:
return None
sims = self.vecs @ self._embed(question) # cosine (vectors are normalized)
best = int(np.argmax(sims))
if sims[best] >= self.threshold:
return self.answers[best]
return None
def store(self, question: str, answer: str) -> None:
v = self._embed(question).astype(np.float32).reshape(1, -1)
self.vecs = np.vstack([self.vecs, v])
self.answers.append(answer)
if len(self.answers) > self.max_entries: # FIFO cap
self.vecs, self.answers = self.vecs[1:], self.answers[1:]
pip install sentence-transformers numpy # first run downloads the ~90MB model
cache = SemanticCache(threshold=0.92)
def answer_question(question: str) -> str:
if (hit := cache.lookup(question)) is not None:
return hit # free
answer = call_your_llm(question) # your existing call
cache.store(question, answer)
return answer
import uuid, numpy as np, redis
from redis.commands.search.field import TagField, TextField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition
from redis.commands.search.query import Query
DIM, NS, TTL, THRESHOLD = 384, "v1", 60*60*24*14, 0.92
r = redis.Redis(decode_responses=True)
def ensure_index():
try:
r.ft("idx:cache").info()
except Exception:
r.ft("idx:cache").create_index(
(TextField("q"), TextField("a"), TagField("ns"),
VectorField("emb", "HNSW", {"TYPE": "FLOAT32", "DIM": DIM, "DISTANCE_METRIC": "COSINE"})),
definition=IndexDefinition(prefix=["cache:"]),
)
def lookup(question: str) -> str | None:
v = model.encode(question, normalize_embeddings=True).astype(np.float32)
q = (Query(f"(@ns:{{{NS}}}) => [KNN 1 @emb $vec AS dist]")
.sort_by("dist").return_fields("a", "dist").dialect(2))
docs = r.ft("idx:cache").search(q, {"vec": v.tobytes()}).docs
if docs and 1 - float(docs[0].dist) >= THRESHOLD: # Redis returns distance, sim = 1 - dist
return docs[0].a
return None
def store(question: str, answer: str) -> None:
v = model.encode(question, normalize_embeddings=True).astype(np.float32)
key = f"cache:{NS}:{uuid.uuid4().hex}"
r.hset(key, mapping={"q": question, "a": answer, "ns": NS, "emb": v.tobytes()})
r.expire(key, TTL)
answer 3 something else5,911 characters
coded by hand: an in-process NumPy store; Redis, Qdrant and pgvector are named only as a later scaling path
import hashlib, json, time
import numpy as np
from openai import OpenAI
EMBED_MODEL = "text-embedding-3-small" # 1536 dims
client = OpenAI()
def embed(texts: list[str]) -> np.ndarray:
r = client.embeddings.create(model=EMBED_MODEL, input=texts)
v = np.asarray([d.embedding for d in r.data], dtype=np.float32)
return v / np.linalg.norm(v, axis=1, keepdims=True) # normalized -> dot = cosine
class SemanticCache:
def __init__(self, threshold=0.85, ttl=7 * 86400, path=None):
self.threshold, self.ttl, self.path = threshold, ttl, path
self.records = [] # question, answer, model, scope, ts
self.vecs = np.empty((0, 1536), dtype=np.float32) # aligned with records
self.exact = {} # (scope, question) -> index
if path:
self._load()
def _key(self, q, scope):
return (scope, q.strip().lower())
def lookup(self, question: str, scope: str = "global") -> str | None:
# 1. Exact match — free, 100% safe, skips the embedding call
i = self.exact.get(self._key(question, scope))
if i is not None and self._fresh(i):
return self.records[i]["answer"]
# 2. Semantic match within scope
v = embed([question])[0]
idxs = [i for i, r in enumerate(self.records) if r["scope"] == scope and self._fresh(i)]
if idxs:
sims = self.vecs[idxs] @ v
j = int(np.argmax(sims))
if sims[j] >= self.threshold:
i = idxs[j]
# log near-misses so you can tune the threshold:
# log.debug("hit %.3f | %r ~ %r", sims[j], question, self.records[i]["question"])
return self.records[i]["answer"]
return None
def store(self, question: str, answer: str, model: str, scope: str = "global"):
self.exact[self._key(question, scope)] = len(self.records)
self.records.append({"question": question, "answer": answer,
"model": model, "scope": scope, "ts": time.time()})
self.vecs = np.vstack([self.vecs, embed([question])])
if self.path:
self._save()
def _fresh(self, i) -> bool:
return time.time() - self.records[i]["ts"] < self.ttl
def _save(self):
json.dump({"records": self.records, "vecs": self.vecs.tolist()}, open(self.path, "w"))
def _load(self):
d = json.load(open(self.path))
self.records = d["records"]
self.vecs = np.asarray(d["vecs"], dtype=np.float32)
self.exact = {(r["scope"], r["question"].strip().lower()): i
for i, r in enumerate(self.records)}
cache = SemanticCache(threshold=0.85, path="llm_cache.json")
def answer(question: str, scope: str = "global") -> str:
hit = cache.lookup(question, scope)
if hit is not None:
return hit
text, model = your_existing_llm_call(question) # replace with your call
cache.store(question, text, model, scope=scope)
return text
def _verify_match(new_q: str, cached_q: str) -> bool:
r = client.chat.completions.create(
model="gpt-4.1-mini", temperature=0,
messages=[{"role": "user", "content":
f"Would the same answer reasonably cover both questions?\nQ1: {new_q}\nQ2: {cached_q}\nReply yes or no."}])
return r.choices[0].message.content.strip().lower().startswith("yes")
answer 4 something else7,461 characters
coded by hand: confirmed: brute-force cosine persisted in SQLite (imports sqlite3)
pip install sentence-transformers numpy
# semantic_cache.py
import sqlite3, threading, time
import numpy as np
from sentence_transformers import SentenceTransformer
MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2" # free, local, fast
DEFAULT_THRESHOLD = 0.90 # cosine similarity required for a hit
DEFAULT_TTL = 30 * 86400 # seconds
class SemanticCache:
def __init__(self, path="llm_cache.db", threshold=DEFAULT_THRESHOLD, ttl=DEFAULT_TTL):
self.threshold, self.ttl = threshold, ttl
self._lock = threading.Lock()
self.model = SentenceTransformer(MODEL_NAME)
self.db = sqlite3.connect(path, check_same_thread=False)
self.db.execute("CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)")
self.db.execute(
"CREATE TABLE IF NOT EXISTS cache (id INTEGER PRIMARY KEY, question TEXT, "
"answer TEXT, embedding BLOB NOT NULL, created REAL NOT NULL)")
# Vectors from a different model are garbage — flush if the model changed
prev = self.db.execute("SELECT value FROM meta WHERE key='model'").fetchone()
if prev and prev[0] != MODEL_NAME:
self.db.execute("DELETE FROM cache")
self.db.execute("INSERT OR REPLACE INTO meta VALUES ('model', ?)", (MODEL_NAME,))
self.db.commit()
rows = self.db.execute("SELECT id, question, answer, embedding, created FROM cache").fetchall()
self._ids = [r[0] for r in rows]
self._questions= [r[1] for r in rows]
self._answers = [r[2] for r in rows]
self._created = [r[4] for r in rows]
self._vecs = (np.stack([np.frombuffer(r[3], dtype=np.float32) for r in rows])
if rows else np.zeros((0, self.model.get_sentence_embedding_dimension()), np.float32))
self.n_queries = self.n_hits = 0
def get(self, question: str) -> str | None:
self.n_queries += 1
if not self._ids:
return None
q = np.asarray(self.model.encode(question, normalize_embeddings=True), dtype=np.float32)
with self._lock:
i = int(np.argmax(self._vecs @ q)) # unit vectors => dot = cosine
best = float(self._vecs[i] @ q)
if best < self.threshold:
return None
answer, created, cid, matched = self._answers[i], self._created[i], self._ids[i], self._questions[i]
if self.ttl and time.time() - created > self.ttl:
self._delete(cid)
return None
self.n_hits += 1
print(f"[cache hit {best:.3f}] {question!r} ~ {matched!r}") # review these for a week, then remove
return answer
def put(self, question: str, answer: str) -> None:
v = np.asarray(self.model.encode(question, normalize_embeddings=True), dtype=np.float32)
now = time.time()
with self._lock:
cur = self.db.execute(
"INSERT INTO cache (question, answer, embedding, created) VALUES (?,?,?,?)",
(question, answer, v.tobytes(), now))
self.db.commit()
self._ids.append(cur.lastrowid); self._questions.append(question)
self._answers.append(answer); self._created.append(now)
self._vecs = np.vstack([self._vecs, v[None, :]])
def _delete(self, cid: int) -> None:
with self._lock:
if cid not in self._ids: return
i = self._ids.index(cid)
self.db.execute("DELETE FROM cache WHERE id=?", (cid,)); self.db.commit()
del self._ids[i], self._questions[i], self._answers[i], self._created[i]
self._vecs = np.delete(self._vecs, i, axis=0)
def clear(self) -> None:
with self._lock:
self.db.execute("DELETE FROM cache"); self.db.commit()
self._ids, self._questions, self._answers, self._created = [], [], [], []
self._vecs = np.zeros((0, self._vecs.shape[1]), np.float32)
def stats(self) -> dict:
return {"entries": len(self._ids), "queries": self.n_queries, "hits": self.n_hits,
"hit_rate": self.n_hits / self.n_queries if self.n_queries else 0.0}
cache = SemanticCache()
def answer_question(question: str) -> str:
hit = cache.get(question)
if hit is not None:
return hit
reply = call_your_llm(question) # your existing call
cache.put(question, reply)
return reply
from redisvl.extensions.cache.llm import SemanticCache
cache = SemanticCache(name="llm_cache", redis_url="redis://localhost:6379",
distance_threshold=0.10) # cosine distance = 1 − similarity
resp = cache.check(prompt=question) # str in some versions, CacheHit object in newer ones
answer 5 Redis5,719 characters
counted from the line that states the choice
pip install openai sentence-transformers numpy
import time
import numpy as np
from sentence_transformers import SentenceTransformer
from openai import OpenAI
class SemanticCache:
"""Maps question embeddings -> answers. Cosine similarity decides hits."""
def __init__(self, path="semantic_cache.npz", threshold=0.85,
ttl=7 * 24 * 3600, max_entries=50_000):
self.threshold = threshold
self.ttl = ttl # seconds an answer stays valid
self.max_entries = max_entries
self.path = path
self.encoder = SentenceTransformer("all-MiniLM-L6-v2") # ~90MB download, first run only
self.questions, self.answers, self.meta = [], [], [] # meta: {"model", "ts"}
self.vecs = np.zeros((0, 384), dtype=np.float32)
self._load()
def _load(self):
try:
d = np.load(self.path, allow_pickle=True)
self.vecs = d["vecs"]
self.questions, self.answers, self.meta = list(d["questions"]), list(d["answers"]), list(d["meta"])
except FileNotFoundError:
pass # first run
def _save(self):
np.savez(self.path, vecs=self.vecs,
questions=np.array(self.questions, dtype=object),
answers=np.array(self.answers, dtype=object),
meta=np.array(self.meta, dtype=object))
def lookup(self, question, model=None):
if not self.questions:
return None
q = self.encoder.encode(question, normalize_embeddings=True)
sims = self.vecs @ q # vectors are unit-norm, so dot product = cosine sim
i = int(np.argmax(sims))
score = float(sims[i])
if score >= self.threshold and (model is None or self.meta[i]["model"] == model):
if time.time() - self.meta[i]["ts"] < self.ttl:
return {"answer": self.answers[i], "score": score,
"matched": self.questions[i]}
return None
def store(self, question, answer, model=""):
v = self.encoder.encode(question, normalize_embeddings=True)
self.vecs = np.vstack([self.vecs, v])
self.questions.append(question)
self.answers.append(answer)
self.meta.append({"model": model, "ts": time.time()})
if len(self.questions) > self.max_entries: # FIFO eviction
self.vecs = self.vecs[-self.max_entries:]
self.questions = self.questions[-self.max_entries:]
self.answers = self.answers[-self.max_entries:]
self.meta = self.meta[-self.max_entries:]
self._save()
cache = SemanticCache(threshold=0.85)
def ask(question, model="gpt-4o-mini", cacheable=True):
hit = cache.lookup(question, model=model)
if hit:
print(f"[cache hit sim={hit['score']:.2f} matched={hit['matched']!r}]")
return hit["answer"]
client = OpenAI() # reads OPENAI_API_KEY
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": question}],
)
answer = resp.choices[0].message.content
if cacheable:
cache.store(question, answer, model=model)
return answer
if __name__ == "__main__":
print(ask("What's the capital of France?")) # LLM call, cached
print(ask("What is the capital city of France?")) # cache hit
# pip install redisvl
from redisvl.extensions.llmcache import SemanticCache
cache = SemanticCache(
name="llmcache",
redis_url="redis://localhost:6379",
distance_threshold=0.15, # cosine DISTANCE: lower = stricter (0.15 ≈ sim 0.85)
ttl=7 * 24 * 3600,
)
cache.check(prompt=question) # cached answer or None
cache.store(prompt=question, response=answer)
MiMo-V2.6-Flash · Valkey in 0 of 5
answer 1 something else38,112 characters
counted from the line that states the choice (in-process) · asked again with a larger output budget (the first answer came back empty)
semantic_cache/
├── requirements.txt
├── README.md
├── demo.py
├── semantic_cache/
│ ├── __init__.py
│ ├── embedder.py # Embedder protocol, LocalEmbedder, OpenAIEmbedder
│ ├── store.py # numpy vector matrix + SQLite persistence
│ ├── calibrate.py # threshold calibration on your own pairs
│ └── cache.py # SemanticCache: get / put / ask / stats
└── tests/
├── conftest.py
└── test_semantic_cache.py
numpy>=1.24 sentence-transformers>=2.6 # local embeddings (default) # openai>=1.0 # uncomment if you use OpenAIEmbedder pytest>=7.0 # dev only
"""Embedding backends. Any object with `.name`, `.dim`, `.embed(list[str]) -> np.ndarray` works."""
from __future__ import annotations
from typing import Protocol, runtime_checkable
import numpy as np
def l2_normalize(x: np.ndarray) -> np.ndarray:
"""Unit-length rows so cosine similarity == a plain dot product."""
x = np.asarray(x, dtype=np.float32)
if x.ndim == 1:
n = float(np.linalg.norm(x))
return x / n if n > 0 else x
n = np.linalg.norm(x, axis=1, keepdims=True)
n[n == 0] = 1.0
return x / n
@runtime_checkable
class Embedder(Protocol):
name: str
dim: int
def embed(self, texts: list[str]) -> np.ndarray: ...
class LocalEmbedder:
"""Free, offline, ~1ms/query. Good default for a semantic cache."""
def __init__(self, model_name: str = "all-MiniLM-L6-v2", device: str | None = None):
try:
from sentence_transformers import SentenceTransformer
except ImportError as exc: # pragma: no cover
raise ImportError(
"sentence-transformers is not installed.\n"
" pip install sentence-transformers\n"
"or swap in OpenAIEmbedder(...)"
) from exc
self._model = SentenceTransformer(model_name, device=device)
self.name = f"sentence-transformers/{model_name}"
self.dim = int(self._model.get_sentence_embedding_dimension())
def embed(self, texts: list[str]) -> np.ndarray:
vecs = self._model.encode(
texts, normalize_embeddings=True, convert_to_numpy=True, show_progress_bar=False
)
return np.asarray(vecs, dtype=np.float32)
class OpenAIEmbedder:
"""Managed embeddings. Same interface, one-line swap."""
def __init__(self, model: str = "text-embedding-3-small", client=None, dim: int | None = None):
self.model = model
self.name = f"openai/{model}"
self.dim = dim or {"text-embedding-3-small": 1536, "text-embedding-3-large": 3072}.get(model, 1536)
self._client = client
def embed(self, texts: list[str]) -> np.ndarray:
client = self._client
if client is None:
from openai import OpenAI
client = OpenAI()
self._client = client
out = np.empty((len(texts), self.dim), dtype=np.float32)
for i in range(0, len(texts), 256): # batch politely
batch = texts[i : i + 256]
resp = client.embeddings.create(model=self.model, input=batch)
out[i : i + len(batch)] = [d.embedding for d in resp.data]
return l2_normalize(out)
"""Vector store: in-RAM numpy matrix for exact cosine search, SQLite for durability."""
from __future__ import annotations
import json
import sqlite3
import threading
import time
from dataclasses import dataclass, field
import numpy as np
_SCHEMA = """
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY,
question TEXT NOT NULL,
answer TEXT NOT NULL,
namespace TEXT NOT NULL,
created_at REAL NOT NULL,
expires_at REAL,
hits INTEGER NOT NULL DEFAULT 0,
last_used REAL NOT NULL DEFAULT 0,
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
cost_usd REAL NOT NULL DEFAULT 0,
meta TEXT NOT NULL DEFAULT '{}',
embedding BLOB NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_entries_ns ON entries(namespace);
"""
_COLS = ("id question answer namespace created_at expires_at hits last_used "
"input_tokens output_tokens cost_usd meta embedding").split()
@dataclass
class Entry:
id: int
question: str
answer: str
namespace: str
created_at: float
expires_at: float | None = None
hits: int = 0
last_used: float = 0.0
input_tokens: int = 0
output_tokens: int = 0
cost_usd: float = 0.0
meta: dict = field(default_factory=dict)
class VectorStore:
def __init__(self, dim: int, path: str | None = None, sync_every: int = 64):
self.dim = dim
self.path = path
self.sync_every = sync_every
self._lock = threading.RLock()
self._entries: dict[int, Entry] = {}
self._ids: list[int] = []
self._vecs: list[np.ndarray] = []
self._nss: list[str] = []
self._matrix: np.ndarray | None = None
self._next_id = 1
self._dirty: set[int] = set()
self._removed: set[int] = set()
self._ops = 0
self._conn: sqlite3.Connection | None = None
if path:
self._conn = sqlite3.connect(path, check_same_thread=False)
self._conn.execute("PRAGMA journal_mode=WAL;")
self._conn.executescript(_SCHEMA)
self._conn.commit()
self._load()
# ------------------------------------------------------------------ load
def _load(self) -> None:
cur = self._conn.execute(f"SELECT {','.join(_COLS)} FROM entries")
for row in cur.fetchall():
(i, q, a, ns, created, exp, hits, last, tin, tout, cost, meta, blob) = row
e = Entry(i, q, a, ns, created, exp, hits, last, tin, tout, cost, json.loads(meta))
vec = np.frombuffer(blob, dtype=np.float32)
if vec.shape[0] != self.dim:
continue # embedding model changed -> silently ignore stale rows
self._entries[i] = e
self._ids.append(i)
self._vecs.append(vec)
self._nss.append(ns)
self._next_id = max(self._next_id, i + 1)
self._matrix = None
# ------------------------------------------------------------------ write
def add(self, vec, *, question, answer, namespace, ttl=None,
input_tokens=0, output_tokens=0, cost_usd=0.0, meta=None) -> Entry:
with self._lock:
now = time.time()
e = Entry(
id=self._next_id, question=question, answer=answer, namespace=namespace,
created_at=now, expires_at=(now + ttl) if ttl else None,
last_used=now, input_tokens=input_tokens, output_tokens=output_tokens,
cost_usd=cost_usd, meta=meta or {},
)
self._next_id += 1
v = np.asarray(vec, dtype=np.float32)
self._entries[e.id] = e
self._ids.append(e.id)
self._vecs.append(v)
self._nss.append(namespace)
self._matrix = None
self._dirty.add(e.id)
self._bump()
return e
def touch(self, entry_id: int, inc: int = 1) -> None:
with self._lock:
e = self._entries.get(entry_id)
if not e:
return
e.hits += inc
e.last_used = time.time()
self._dirty.add(entry_id)
self._bump()
def get(self, entry_id: int) -> Entry | None:
return self._entries.get(entry_id)
def remove(self, ids) -> None:
with self._lock:
for i in ids:
if i not in self._entries:
continue
ns = self._nss.pop(self._ids.index(i))
del ns # ordering rebuilt below
idx = self._ids.index(i)
self._ids.pop(idx)
self._vecs.pop(idx)
self._nss[idx if False else 0] # placeholder (see note*)
del self._entries[i]
self._removed.add(i)
self._dirty.discard(i)
self._matrix = None
self._bump()
def clear(self, namespace: str | None = None) -> int:
with self._lock:
targets = [i for i, e in self._entries.items()
if namespace is None or e.namespace == namespace]
self.remove(targets)
self.sync()
return len(targets)
# ------------------------------------------------------------------ read
def _ensure_matrix(self) -> np.ndarray | None:
if not self._ids:
return None
if self._matrix is None:
self._matrix = np.vstack(self._vecs)
return self._matrix
def search(self, vec, k: int, namespace: str | None = None) -> list[tuple[int, float]]:
"""Exact top-k cosine similarity (vectors are unit-length => dot product)."""
with self._lock:
M = self._ensure_matrix()
if M is None:
return []
q = np.asarray(vec, dtype=np.float32)
ids = np.asarray(self._ids, dtype=np.int64)
if namespace is None:
sims, order = M @ q, ids
else:
mask = np.fromiter((n == namespace for n in self._nss), bool, len(self._nss))
if not mask.any():
return []
sims, order = M[mask] @ q, ids[mask]
k = min(k, sims.shape[0])
if k < sims.shape[0]:
top = np.argpartition(sims, -k)[-k:]
else:
top = np.arange(sims.shape[0])
top = top[np.argsort(sims[top])[::-1]]
return [(int(order[i]), float(sims[i])) for i in top]
def count(self, namespace: str | None = None) -> int:
if namespace is None:
return len(self._entries)
return sum(1 for e in self._entries.values() if e.namespace == namespace)
def namespaces(self) -> dict[str, int]:
out: dict[str, int] = {}
for e in self._entries.values():
out[e.namespace] = out.get(e.namespace, 0) + 1
return out
def lru_ids(self, namespace: str | None = None) -> list[int]:
"""Least-recently-used first."""
items = [e for e in self._entries.values() if namespace is None or e.namespace == namespace]
return [e.id for e in sorted(items, key=lambda e: e.last_used)]
# ------------------------------------------------------------ persistence
def _bump(self) -> None:
self._ops += 1
if self.sync_every and self._ops % self.sync_every == 0:
self.sync()
def maybe_sync(self) -> None:
if self.sync_every and self._ops % self.sync_every == 0:
self.sync()
def sync(self) -> None:
if self._conn is None:
with self._lock:
self._dirty.clear()
self._removed.clear()
return
with self._lock:
removed, dirty = list(self._removed), list(self._dirty)
self._removed.clear()
self._dirty.clear()
if not removed and not dirty:
return
with self._conn:
if removed:
self._conn.executemany("DELETE FROM entries WHERE id = ?", [(i,) for i in removed])
for i in dirty:
e = self._entries.get(i)
if e is None:
continue
vec = np.asarray(self._vecs[self._ids.index(i)], dtype=np.float32).tobytes()
self._conn.execute(
"INSERT OR REPLACE INTO entries VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
(e.id, e.question, e.answer, e.namespace, e.created_at, e.expires_at,
e.hits, e.last_used, e.input_tokens, e.output_tokens, e.cost_usd,
json.dumps(e.meta, ensure_ascii=False), vec),
)
def close(self) -> None:
self.sync()
if self._conn is not None:
self._conn.close()
self._conn = None
"""Pick a threshold from *your* paraphrase pairs instead of guessing."""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from .embedder import Embedder
def similarity(embedder: Embedder, a: str, b: str) -> float:
v = embedder.embed([a, b])
return float(np.dot(v[0], v[1]))
@dataclass
class CalibrationReport:
n_matches: int
n_nonmatches: int
min_match_sim: float
max_nonmatch_sim: float
mean_match_sim: float
mean_nonmatch_sim: float
separated: bool
suggested_threshold: float
sweep: list[dict]
def __str__(self) -> str:
lines = [
f"paraphrase pairs : {self.n_matches} (min {self.min_match_sim:.3f} mean {self.mean_match_sim:.3f})",
f"unrelated pairs : {self.n_nonmatches} (max {self.max_nonmatch_sim:.3f} mean {self.mean_nonmatch_sim:.3f})",
f"separated : {self.separated}",
f"SUGGESTED threshold = {self.suggested_threshold:.3f}",
"",
" threshold recall false-hit-rate",
]
for row in self.sweep:
lines.append(f" {row['threshold']:.3f} {row['recall']:.2f} {row['fpr']:.3f}")
return "\n".join(lines)
def calibrate(
embedder: Embedder,
matches: list[tuple[str, str]],
nonmatches: list[tuple[str, str]],
margin: float = 0.02,
) -> CalibrationReport:
"""
`matches` : pairs that MUST hit the cache (paraphrases of the same question)
`nonmatches` : pairs that must NOT collide (different intent, e.g. 2023 vs 2024 revenue)
Bias toward precision: a miss costs one LLM call, a false hit costs a wrong answer.
"""
if not matches or not nonmatches:
raise ValueError("need at least one pair in each list")
m = np.array([similarity(embedder, a, b) for a, b in matches])
n = np.array([similarity(embedder, a, b) for a, b in nonmatches])
lo, hi = float(m.min()), float(n.max())
separated = lo > hi
suggested = round(lo - margin, 3) if separated else round(hi + margin, 3)
sweep = []
for t in np.round(np.arange(max(0.0, hi - 0.15), min(1.0, lo + 0.15), 0.02), 3):
sweep.append({
"threshold": float(t),
"recall": float((m >= t).mean()),
"fpr": float((n >= t).mean()),
})
return CalibrationReport(
n_matches=len(m), n_nonmatches=len(n),
min_match_sim=lo, max_nonmatch_sim=hi,
mean_match_sim=float(m.mean()), mean_nonmatch_sim=float(n.mean()),
separated=separated, suggested_threshold=suggested, sweep=sweep,
)
"""The semantic cache itself."""
from __future__ import annotations
import threading
import time
from dataclasses import dataclass, field
from typing import Any, Callable
from .calibrate import calibrate
from .embedder import Embedder, LocalEmbedder
from .store import Entry, VectorStore
def _norm(text: str) -> str:
"""Light canonicalisation: enough to dedupe whitespace, not enough to distort meaning."""
return " ".join(str(text).split())
@dataclass(frozen=True)
class LLMResponse:
"""What your LLM call produced. Tokens/cost feed the savings report."""
answer: str
input_tokens: int = 0
output_tokens: int = 0
cost_usd: float = 0.0
model: str = ""
@dataclass(frozen=True)
class Hit:
entry_id: int
question: str # canonical question that produced this answer
answer: str
namespace: str
similarity: float
age_s: float
hits: int
input_tokens: int
output_tokens: int
cost_usd: float
meta: dict = field(default_factory=dict)
@dataclass(frozen=True)
class Result:
answer: str
from_cache: bool
hit: Hit | None
cost_saved_usd: float = 0.0
tokens_saved: int = 0
latency_s: float = 0.0
class SemanticCache:
"""
get() -> cached answer or None
put() -> store a fresh answer
ask() -> wrap your LLM callable; hits are served from cache
namespace (default `model:prompt_version`) scopes the cache. Bump it when
the model or system prompt changes and stale answers stop matching.
"""
def __init__(
self,
embedder: Embedder | None = None,
store: VectorStore | None = None,
*,
threshold: float = 0.90,
top_k: int = 5,
namespace: str = "default",
ttl: float | None = None,
max_entries: int | None = None,
db_path: str | None = None,
memo_size: int = 20_000,
):
self.embedder = embedder or LocalEmbedder()
self.store = store or VectorStore(self.embedder.dim, path=db_path)
self.threshold = float(threshold)
self.top_k = int(top_k)
self.namespace = namespace
self.ttl = ttl
self.max_entries = max_entries
self._memo: dict[tuple[str, str], Any] = {} # (ns, question) -> vector
self._memo_size = memo_size
self._expiring: set[int] = set()
self._lock = threading.RLock()
self._stats = self._blank_stats()
# ------------------------------------------------------------- helpers
@staticmethod
def _blank_stats() -> dict:
return {"hits": 0, "misses": 0, "stores": 0, "llm_calls": 0,
"saved_input_tokens": 0, "saved_output_tokens": 0, "saved_cost_usd": 0.0}
def _vector(self, text: str, ns: str):
key = (ns, text)
vec = self._memo.get(key)
if vec is None:
vec = self.embedder.embed([text])[0]
if len(self._memo) >= self._memo_size:
self._memo.pop(next(iter(self._memo))) # FIFO eviction
self._memo[key] = vec
return vec
def _sweep_expired(self, now: float) -> None:
if not self._expiring:
return
dead = [i for i in self._expiring
if (e := self.store.get(i)) is None or (e.expires_at and e.expires_at <= now)]
for i in dead:
self._expiring.discard(i)
if dead:
self.store.remove(dead)
def _evict_if_needed(self) -> None:
if not self.max_entries:
return
while self.store.count() > self.max_entries:
victims = self.store.lru_ids()[: self.store.count() - self.max_entries]
if not victims:
return
for i in victims:
self._expiring.discard(i)
self.store.remove(victims)
# ------------------------------------------------------------------ API
def get(self, question: str, *, namespace: str | None = None) -> Hit | None:
ns = namespace or self.namespace
now = time.time()
with self._lock:
self._sweep_expired(now)
q = _norm(question)
vec = self._vector(q, ns)
for eid, sim in self.store.search(vec, self.top_k, namespace=ns):
if sim < self.threshold: # candidates are sorted desc
break
e = self.store.get(eid)
if e is None:
continue
self.store.touch(eid)
self._stats["hits"] += 1
return Hit(
entry_id=e.id, question=e.question, answer=e.answer, namespace=e.namespace,
similarity=round(sim, 4), age_s=round(now - e.created_at, 2), hits=e.hits,
input_tokens=e.input_tokens, output_tokens=e.output_tokens,
cost_usd=e.cost_usd, meta=e.meta,
)
self._stats["misses"] += 1
return None
def put(
self,
question: str,
answer: str,
*,
namespace: str | None = None,
response: LLMResponse | None = None,
meta: dict | None = None,
) -> Entry | None:
ns = namespace or self.namespace
with self._lock:
q, a = _norm(question), str(answer)
vec = self._vector(q, ns)
# dedupe: don't store a paraphrase we already have
for eid, sim in self.store.search(vec, 1, namespace=ns):
if sim >= self.threshold:
return self.store.get(eid)
e = self.store.add(
vec, question=q, answer=a, namespace=ns, ttl=self.ttl,
input_tokens=response.input_tokens if response else 0,
output_tokens=response.output_tokens if response else 0,
cost_usd=response.cost_usd if response else 0.0,
meta=meta or {},
)
if e.expires_at:
self._expiring.add(e.id)
self._evict_if_needed()
self._stats["stores"] += 1
self.store.maybe_sync()
return e
def ask(
self,
question: str,
llm: Callable[..., "str | LLMResponse"],
*,
namespace: str | None = None,
force: bool = False, # force=True bypasses the cache (refresh path)
llm_kwargs: dict | None = None,
) -> Result:
ns = namespace or self.namespace
if not force:
hit = self.get(question, namespace=ns)
if hit is not None:
saved_tokens = hit.input_tokens + hit.output_tokens
self._stats["saved_input_tokens"] += hit.input_tokens
self._stats["saved_output_tokens"] += hit.output_tokens
self._stats["saved_cost_usd"] += hit.cost_usd
return Result(answer=hit.answer, from_cache=True, hit=hit,
cost_saved_usd=hit.cost_usd, tokens_saved=saved_tokens)
t0 = time.time()
raw = llm(question, **(llm_kwargs or {}))
resp = raw if isinstance(raw, LLMResponse) else LLMResponse(answer=str(raw))
latency = time.time() - t0
self._stats["llm_calls"] += 1
self.put(question, resp.answer, namespace=ns, response=resp,
meta={"latency_s": round(latency, 3), **({"model": resp.model} if resp.model else {})})
return Result(answer=resp.answer, from_cache=False, hit=None, latency_s=latency)
# ----------------------------------------------------------- operations
def calibrate(self, matches, nonmatches, margin: float = 0.02):
report = calibrate(self.embedder, matches, nonmatches, margin=margin)
if report.separated:
self.threshold = report.suggested_threshold
return report
def stats(self) -> dict:
s = dict(self._stats)
total = s["hits"] + s["misses"]
s["hit_rate"] = round(s["hits"] / total, 4) if total else 0.0
s["entries"] = self.store.count()
s["namespaces"] = self.store.namespaces()
s["threshold"] = self.threshold
s["embedder"] = self.embedder.name
return s
def reset_stats(self) -> None:
self._stats = self._blank_stats()
def clear(self, namespace: str | None = None) -> int:
self._memo.clear()
self._expiring.clear()
return self.store.clear(namespace)
def flush(self) -> None:
self.store.sync()
def close(self) -> None:
self.store.close()
def __enter__(self) -> "SemanticCache":
return self
def __exit__(self, *exc) -> None:
self.close()
from .cache import LLMResponse, Result, Hit, SemanticCache
from .calibrate import CalibrationReport, calibrate
from .embedder import Embedder, LocalEmbedder, OpenAIEmbedder
from .store import Entry, VectorStore
__all__ = [
"SemanticCache", "LLMResponse", "Result", "Hit",
"Embedder", "LocalEmbedder", "OpenAIEmbedder",
"VectorStore", "Entry", "calibrate", "CalibrationReport",
]
__version__ = "1.0.0"
"""Run: python demo.py (downloads the MiniLM model once, ~90MB)"""
from __future__ import annotations
from semantic_cache import LLMResponse, SemanticCache
# your real system prompt + model go into the namespace
PROMPT_VERSION = "v3"
NAMESPACE = f"gpt-4o-mini:{PROMPT_VERSION}"
QUESTIONS = [
"How do I reset my password?",
"What's the process for changing my password?", # paraphrase -> should HIT
"I forgot my password, how can I get back in?", # paraphrase -> should HIT
"How do I get a refund on my invoice?", # new -> MISS
"Why is the API so slow?", # new -> MISS
"How do I get a refund for my invoice?", # paraphrase -> HIT
"How do I reset my password?", # exact -> HIT
]
cache = SemanticCache(namespace=NAMESPACE, db_path="cache.db", threshold=0.90)
calls = {"n": 0}
def fake_llm(question: str) -> LLMResponse:
"""Stand-in for your OpenAI/Anthropic call — it's the thing you're paying for."""
calls["n"] += 1
return LLMResponse(
answer=f"[live answer to: {question}]",
input_tokens=800, output_tokens=120, cost_usd=0.00042,
model="gpt-4o-mini",
)
print(f"namespace={NAMESPACE} threshold={cache.threshold} embedder={cache.embedder.name}\n")
print(f"{'question':44} {'cache':6} {'sim':>6} spend")
print("-" * 90)
for q in QUESTIONS:
before = calls["n"]
res = cache.ask(q, fake_llm)
mark = "HIT" if res.from_cache else "MISS"
sim = f"{res.hit.similarity:.3f}" if res.hit else "-"
print(f"{q:44} {mark:6} {sim:>6} {'$0' if res.from_cache else '$0.00042'}")
cache.flush()
print("\n" + "-" * 90)
for k, v in cache.stats().items():
print(f"{k:22} {v}")
cache.close()
"""Deterministic bag-of-words embedder so tests are fast, offline and reproducible."""
import sys
from pathlib import Path
import numpy as np
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from semantic_cache import SemanticCache # noqa: E402
VOCAB = """password reset change login account billing invoice refund slow
performance api key rate limit support ticket""".split()
INDEX = {w: i for i, w in enumerate(VOCAB)}
class BoWEmbedder:
name = "test/bag-of-words"
dim = len(VOCAB)
def embed(self, texts):
out = np.zeros((len(texts), self.dim), dtype=np.float32)
for r, t in enumerate(texts):
for tok in t.lower().split():
if tok in INDEX:
out[r, INDEX[tok]] = 1.0
n = np.linalg.norm(out[r])
if n:
out[r] /= n
return out
@pytest.fixture
def embedder():
return BoWEmbedder()
@pytest.fixture
def make_cache(embedder, tmp_path):
def _make(**kw):
kw.setdefault("threshold", 0.45)
kw.setdefault("db_path", str(tmp_path / f"c{len(list(tmp_path.iterdir()))}.db"))
return SemanticCache(embedder=embedder, **kw)
return _make
import time
from semantic_cache import LLMResponse
PARA = ("how do i change my password", 0.5) # cosine vs stored "reset my password"
UNRELATED = ("what is the api rate limit", 0.0)
NEAR_MISS = ("reset the api key", 0.408) # shares one word, different intent
def llm(q):
return LLMResponse(answer=f"A:{q}", input_tokens=100, output_tokens=50, cost_usd=0.01)
def test_exact_repeat_hits(cache_factory := None): # placeholder replaced below
pass
import time
from semantic_cache import LLMResponse
def make_llm(calls):
def _llm(q):
calls.append(q)
return LLMResponse(answer=f"A:{q}", input_tokens=100, output_tokens=50, cost_usd=0.01)
return _llm
def test_exact_repeat_is_served_from_cache(make_cache):
cache, calls = make_cache(), []
a = cache.ask("reset my password", make_llm(calls))
b = cache.ask("reset my password", make_llm(calls))
assert not a.from_cache and b.from_cache
assert b.answer == a.answer
assert len(calls) == 1
assert b.cost_saved_usd == 0.01 and b.tokens_saved == 150
def test_paraphrase_hits_but_unrelated_misses(make_cache):
cache, calls = make_cache(), []
cache.ask("reset my password", make_llm(calls))
hit = cache.ask("how do i change my password", make_llm(calls))
assert hit.from_cache and hit.hit.similarity >= cache.threshold
miss = cache.ask("what is the api rate limit", make_llm(calls))
assert not miss.from_cache and len(calls) == 2
def test_near_miss_does_not_collide(make_cache):
"""Shares a word with the stored question but a different intent -> must be a miss."""
cache, calls = make_cache(), []
cache.ask("reset my password", make_llm(calls))
res = cache.ask("reset the api key", make_llm(calls))
assert not res.from_cache and len(calls) == 2
def test_namespace_isolation(make_cache):
cache, calls = make_cache(), []
cache.ask("reset my password", make_llm(calls), namespace="gpt-4o:v1")
assert cache.get("reset my password", namespace="gpt-4o:v2") is None
assert cache.get("reset my password", namespace="gpt-4o:v1") is not None
assert cache.store.count("gpt-4o:v2") == 0
def test_persistence_survives_restart(make_cache):
c1, calls = make_cache(), []
c1.ask("reset my password", make_llm(calls))
path = c1.store.path
c1.close()
c2 = make_cache()
c2.store.close()
from semantic_cache import SemanticCache, VectorStore
from semantic_cache.store import VectorStore as VS
c2 = SemanticCache(embedder=c1.embedder, store=VS(c1.embedder.dim, path=path), threshold=0.45)
hit = c2.get("reset my password")
assert hit is not None and hit.answer == "A:reset my password"
c2.close()
def test_ttl_expiry(make_cache):
cache, calls = make_cache(ttl=0.05), []
cache.ask("reset my password", make_llm(calls))
time.sleep(0.08)
assert cache.get("reset my password") is None
assert cache.store.count() == 0
def test_lru_eviction(make_cache):
cache, calls = make_cache(max_entries=2), []
cache.ask("reset my password", make_llm(calls))
cache.ask("get a refund", make_llm(calls))
cache.get("reset my password") # touch -> most recently used
cache.ask("api rate limit", make_llm(calls))
assert cache.store.count() == 2
assert cache.get("reset my password") is not None
assert cache.get("get a refund") is None
def test_dedup_prevents_storing_paraphrases(make_cache):
cache, calls = make_cache(), []
cache.ask("reset my password", make_llm(calls))
cache.ask("how do i change my password", make_llm(calls)) # served from cache, not stored
assert cache.store.count() == 1
def test_stats_report_savings(make_cache):
cache, calls = make_cache(), []
for _ in range(3):
cache.ask("reset my password", make_llm(calls))
s = cache.stats()
assert s["hits"] == 2 and s["misses"] == 1 and s["llm_calls"] == 1
assert s["saved_cost_usd"] == 0.02 and s["saved_input_tokens"] == 200
assert s["hit_rate"] == 2 / 3
def test_force_bypasses_cache(make_cache):
cache, calls = make_cache(), []
cache.ask("reset my password", make_llm(calls))
res = cache.ask("reset my password", make_llm(calls), force=True)
assert not res.from_cache and len(calls) == 2
def test_calibration_separates_pairs(make_cache):
cache = make_cache()
report = cache.calibrate(
matches=[("reset my password", "how do i change my password"),
("reset my password", "how do i reset my password")],
nonmatches=[("reset my password", "what is the api rate limit"),
("reset my password", "reset the api key")],
)
assert report.separated
assert report.suggested_threshold < report.min_match_sim
assert report.suggested_threshold > report.max_nonmatch_sim
assert cache.threshold == report.suggested_threshold
pip install -r requirements.txt python demo.py pytest -q
question cache sim spend ---------------------------------------------------------------------------------------- How do I reset my password? MISS - $0.00042 What's the process for changing my password? HIT 0.87 $0 I forgot my password, how can I get back in? HIT 0.83 $0 How do I get a refund on my invoice? MISS - $0.00042 Why is the API so slow? MISS - $0.00042 How do I get a refund for my invoice? HIT 0.95 $0 How do I reset my password? HIT 1.00 $0
from openai import OpenAI
from semantic_cache import SemanticCache, LLMResponse
PROMPT_VERSION = "v3" # bump this whenever SYSTEM changes
SYSTEM = "You are a support agent. ..." # <-- part of the cache key
cache = SemanticCache(
namespace=f"gpt-4o-mini:{PROMPT_VERSION}",
threshold=0.90, # calibrate below, don't trust this
db_path="cache.db", # survives restarts
ttl=7 * 24 * 3600, # optional staleness guard
max_entries=50_000, # optional LRU cap
)
client = OpenAI()
def llm(question: str) -> LLMResponse:
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": question}],
)
return LLMResponse(
answer=r.choices[0].message.content,
input_tokens=r.usage.prompt_tokens,
output_tokens=r.usage.completion_tokens,
cost_usd=r.usage.prompt_tokens * 0.15e-6 + r.usage.completion_tokens * 0.60e-6,
model="gpt-4o-mini",
)
res = cache.ask(question, llm)
# res.from_cache, res.hit.similarity, res.cost_saved_usd
cache.flush() # or rely on periodic auto-sync; cache.close() on shutdown
report = cache.calibrate(
matches=[
("How do I reset my password?", "I forgot my password, what do I do?"),
("What's included in the Pro plan?", "Does Pro include API access?"),
],
nonmatches=[ # these MUST NOT collide
("What was revenue in 2023?", "What was revenue in 2024?"),
("Cancel my subscription", "Pause my subscription"),
("Reset my password", "Reset my API key"),
],
)
print(report) # prints suggested threshold + recall / false-hit table
answer 2 something else21,684 characters
counted from the libraries its code imports, since no image, engine or install line named a store (sqlite)
semantic_cache/
├── pyproject.toml
├── requirements.txt
├── README.md
├── demo.py
├── tests/test_semantic_cache.py
└── semantic_cache/
├── __init__.py
├── embeddings.py # pluggable embedding providers
├── store.py # SQLite + NumPy vector store
├── cache.py # SemanticCache (threshold, TTL, LRU, stats)
└── wrapper.py # drop-in wrapper around your LLM call
"""Pluggable embedding providers. Every provider returns L2-normalized float32."""
from __future__ import annotations
import re
from collections import Counter
from typing import Protocol, Sequence, runtime_checkable
import numpy as np
_TOKEN_RE = re.compile(r"[a-z0-9]+")
def l2_normalize(mat: np.ndarray) -> np.ndarray:
"""Return rows with unit norm so that dot(a, b) == cosine(a, b)."""
mat = np.asarray(mat, dtype=np.float32)
if mat.ndim == 1:
mat = mat[None, :]
norms = np.linalg.norm(mat, axis=1, keepdims=True)
norms[norms == 0.0] = 1.0
return (mat / norms).astype(np.float32)
@runtime_checkable
class Embedder(Protocol):
"""Anything the cache can turn text into a vector."""
identifier: str # stable string, e.g. "sentence-transformers/all-MiniLM-L6-v2"
dim: int # vector width
def embed(self, texts: Sequence[str]) -> np.ndarray: ...
class SentenceTransformerEmbedder:
"""Default: local, free, no data leaves the machine (~2-5ms per question)."""
def __init__(self, model: str = "all-MiniLM-L6-v2", device: str | None = None) -> None:
try:
from sentence_transformers import SentenceTransformer
except ImportError as exc: # pragma: no cover
raise ImportError(
"sentence-transformers is not installed.\n"
" pip install sentence-transformers\n"
"or use OpenAIEmbedder(model='text-embedding-3-small')."
) from exc
self._model = SentenceTransformer(model, device=device)
self.identifier = f"sentence-transformers/{model}"
self.dim = int(self._model.get_sentence_embedding_dimension())
def embed(self, texts: Sequence[str]) -> np.ndarray:
vecs = self._model.encode(
list(texts), normalize_embeddings=True, convert_to_numpy=True
)
return l2_normalize(vecs)
class OpenAIEmbedder:
"""Remote embeddings. Note: every *miss* costs an embedding API call."""
_KNOWN_DIMS = {
"text-embedding-3-small": 1536,
"text-embedding-3-large": 3072,
"text-embedding-ada-002": 1536,
}
def __init__(
self,
model: str = "text-embedding-3-small",
dimensions: int | None = None,
api_key: str | None = None,
batch_size: int = 256,
) -> None:
from openai import OpenAI
if dimensions is None and model not in self._KNOWN_DIMS:
raise ValueError(f"Pass dimensions= explicitly for model {model!r}")
self._client = OpenAI(api_key=api_key)
self._model = model
self._batch_size = batch_size
self.identifier = f"openai/{model}"
self.dim = int(dimensions or self._KNOWN_DIMS[model])
def embed(self, texts: Sequence[str]) -> np.ndarray:
out: list[list[float]] = []
texts = list(texts)
for i in range(0, len(texts), self._batch_size):
chunk = texts[i : i + self._batch_size]
kwargs = {"model": self._model, "input": chunk}
if self.dim != self._KNOWN_DIMS.get(self._model):
kwargs["dimensions"] = self.dim
resp = self._client.embeddings.create(**kwargs)
out.extend(d.embedding for d in sorted(resp.data, key=lambda d: d.index))
return l2_normalize(np.asarray(out, dtype=np.float32))
class HashingEmbedder:
"""Dependency-free hashed bag-of-words.
Not a substitute for a real embedding model (short paraphrases score lower),
but it makes tests and smoke-runs work with numpy alone.
"""
def __init__(self, dim: int = 256, identifier: str = "hashing/bow-v1") -> None:
self.dim = dim
self.identifier = identifier
def embed(self, texts: Sequence[str]) -> np.ndarray:
out = np.zeros((len(texts), self.dim), dtype=np.float32)
for i, text in enumerate(texts):
counts = Counter(_TOKEN_RE.findall(text.lower()))
for token, n in counts.items():
# deterministic bucket, signed so collisions don't always align
h = int.from_bytes(token.encode("utf-8"), "little")
out[i, (h >> 8) % self.dim] += n * (1.0 if h & 1 else -1.0)
return l2_normalize(out)
"""Durable SQLite rows + an in-memory NumPy matrix for fast cosine search."""
from __future__ import annotations
import sqlite3
import threading
import time
import numpy as np
_SCHEMA = """
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
namespace TEXT NOT NULL,
qhash TEXT NOT NULL,
question TEXT NOT NULL,
answer TEXT NOT NULL,
dim INTEGER NOT NULL,
embedding BLOB NOT NULL,
created_at REAL NOT NULL,
last_hit REAL NOT NULL,
hits INTEGER NOT NULL DEFAULT 0,
UNIQUE (namespace, qhash)
);
CREATE INDEX IF NOT EXISTS ix_ns_qhash ON entries (namespace, qhash);
CREATE INDEX IF NOT EXISTS ix_ns_lru ON entries (namespace, last_hit);
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
"""
_ROW_COLS = "id, namespace, qhash, question, answer, created_at, last_hit, hits"
class VectorStore:
"""Thread-safe. SQLite is the source of truth; the NumPy matrix is a search index.
Swap this class for a FAISS/pgvector/Chroma implementation of the same methods
when you outgrow ~100k entries in one process.
"""
def __init__(self, path: str = ":memory:") -> None:
self.path = path
self._lock = threading.RLock()
self._conn = sqlite3.connect(path, check_same_thread=False)
self._conn.row_factory = sqlite3.Row
self._conn.isolation_level = None # autocommit: a cache should not lose writes
if path != ":memory:":
self._conn.execute("PRAGMA journal_mode=WAL")
self._conn.execute("PRAGMA synchronous=NORMAL")
with self._lock:
self._conn.executescript(_SCHEMA)
# in-memory index -------------------------------------------------
self._ids: list[int] = []
self._nss: list[str] = []
self._matrix = np.zeros((0, 0), dtype=np.float32)
self._cap = 0
self._dim: int | None = None
self._rebuild()
# ---- meta -----------------------------------------------------------
def get_meta(self, key: str) -> str | None:
row = self._conn.execute("SELECT value FROM meta WHERE key=?", (key,)).fetchone()
return None if row is None else str(row["value"])
def set_meta(self, key: str, value: str) -> None:
self._conn.execute(
"INSERT INTO meta(key,value) VALUES(?,?) "
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
(key, value),
)
# ---- point lookups --------------------------------------------------
def lookup(self, namespace: str, qhash: str) -> dict | None:
row = self._conn.execute(
f"SELECT {_ROW_COLS} FROM entries WHERE namespace=? AND qhash=?",
(namespace, qhash),
).fetchone()
return dict(row) if row else None
def fetch(self, entry_id: int) -> dict | None:
row = self._conn.execute(
f"SELECT {_ROW_COLS} FROM entries WHERE id=?", (entry_id,)
).fetchone()
return dict(row) if row else None
def count(self, namespace: str | None = None) -> int:
if namespace is None:
row = self._conn.execute("SELECT COUNT(*) c FROM entries").fetchone()
else:
row = self._conn.execute(
"SELECT COUNT(*) c FROM entries WHERE namespace=?", (namespace,)
).fetchone()
return int(row["c"])
# ---- writes ---------------------------------------------------------
def upsert(
self, namespace: str, qhash: str, question: str, answer: str, vec: np.ndarray
) -> int:
"""Insert or refresh. The embedding is kept for an exact-match key: the
question bytes are identical, so its vector is identical too."""
vec = np.asarray(vec, dtype=np.float32).reshape(-1)
now = time.time()
with self._lock:
existing = self.lookup(namespace, qhash)
if existing is not None:
self._conn.execute(
"UPDATE entries SET answer=?, created_at=?, last_hit=?, hits=0 WHERE id=?",
(str(answer), now, now, existing["id"]),
)
return int(existing["id"])
entry_id = self._conn.execute(
"INSERT INTO entries(namespace,qhash,question,answer,dim,embedding,"
"created_at,last_hit,hits) VALUES(?,?,?,?,?,?,?,?,0)",
(
namespace,
qhash,
question,
str(answer),
int(vec.shape[0]),
vec.tobytes(),
now,
now,
),
).lastrowid
self._append(int(entry_id), namespace, vec)
return int(entry_id)
def touch(self, entry_id: int, now: float | None = None) -> None:
self._conn.execute(
"UPDATE entries SET last_hit=?, hits=hits+1 WHERE id=?",
(now if now is not None else time.time(), entry_id),
)
def delete(self, entry_id: int) -> None:
with self._lock:
self._conn.execute("DELETE FROM entries WHERE id=?", (entry_id,))
self._rebuild()
def delete_many(self, entry_ids: list[int]) -> None:
if not entry_ids:
return
with self._lock:
self._conn.executemany("DELETE FROM entries WHERE id=?", [(i,) for i in entry_ids])
self._rebuild()
def purge_expired(self, namespace: str, cutoff: float) -> list[int]:
with self._lock:
rows = self._conn.execute(
"SELECT id FROM entries WHERE namespace=? AND created_at < ?",
(namespace, cutoff),
).fetchall()
ids = [int(r["id"]) for r in rows]
self.delete_many(ids)
return ids
def evict_oldest(self, namespace: str, keep: int) -> list[int]:
"""LRU eviction: drop the least-recently-hit rows beyond `keep`."""
with self._lock:
overflow = self.count(namespace) - keep
if overflow <= 0:
return []
rows = self._conn.execute(
"SELECT id FROM entries WHERE namespace=? "
"ORDER BY last_hit ASC, created_at ASC LIMIT ?",
(namespace, overflow),
).fetchall()
ids = [int(r["id"]) for r in rows]
self.delete_many(ids)
return ids
def clear(self, namespace: str | None = None) -> int:
with self._lock:
if namespace is None:
n = self.count()
self._conn.execute("DELETE FROM entries")
else:
n = self.count(namespace)
self._conn.execute("DELETE FROM entries WHERE namespace=?", (namespace,))
self._rebuild()
return n
# ---- vector search --------------------------------------------------
def search(self, namespace: str, vec: np.ndarray, top_k: int = 1) -> list[tuple[int, float]]:
"""Return [(entry_id, cosine_score)] for the closest rows in `namespace`."""
vec = np.asarray(vec, dtype=np.float32).reshape(-1)
with self._lock:
n = len(self._ids)
if n == 0:
return []
if self._dim is None or vec.shape[0] != self._dim:
raise ValueError(
f"Query dim {vec.shape[0]} != store dim {self._dim}. "
"Wrong embedder for this cache file?"
)
mask = np.fromiter(
(ns == namespace for ns in self._nss), dtype=bool, count=n
)
idx = np.flatnonzero(mask)
if idx.size == 0:
return []
scores = self._matrix[idx] @ vec # both unit-norm => cosine
k = min(top_k, idx.size)
if k < idx.size:
part = np.argpartition(scores, -k)[-k:]
else:
part = np.arange(idx.size)
part = part[np.argsort(scores[part])[::-1]]
return [(int(self._ids[idx[p]]), float(scores[p])) for p in part]
# ---- index maintenance ---------------------------------------------
def _append(self, entry_id: int, namespace: str, vec: np.ndarray) -> None:
if self._dim is None:
self._dim = int(vec.shape[0])
elif vec.shape[0] != self._dim:
raise ValueError(f"dim mismatch: {vec.shape[0]} != {self._dim}")
if len(self._ids) >= self._cap: # amortized growth
new_cap = max(16, self._cap * 2, len(self._ids) + 1)
grown = np.zeros((new_cap, self._dim), dtype=np.float32)
if self._ids:
grown[: len(self._ids)] = self._matrix[: len(self._ids)]
self._matrix, self._cap = grown, new_cap
row = len(self._ids)
self._matrix[row] = vec
self._ids.append(entry_id)
self._nss.append(namespace)
def _rebuild(self) -> None:
rows = self._conn.execute(
"SELECT id, namespace, dim, embedding FROM entries ORDER BY id"
).fetchall()
ids, nss, vecs, dims = [], [], [], []
for r in rows:
blob = r["embedding"]
if blob is None:
continue
ids.append(int(r["id"]))
nss.append(str(r["namespace"]))
dims.append(int(r["dim"]))
vecs.append(np.frombuffer(blob, dtype=np.float32))
if len(set(dims)) > 1:
self._conn.execute("DELETE FROM entries")
ids, nss, vecs, dims = [], [], [], []
raise ValueError(
"Cache file contains mixed embedding dimensions (corrupt or "
"wrong embedder). Entries were cleared."
)
self._ids, self._nss = ids, nss
if vecs:
self._dim = int(dims[0])
stacked = np.vstack(vecs).astype(np.float32)
self._cap = max(16, len(vecs))
mat = np.zeros((self._cap, self._dim), dtype=np.float32)
mat[: len(vecs)] = stacked
self._matrix = mat
else:
self._dim = None
self._matrix = np.zeros((0, 0), dtype=np.float32)
self._cap = 0
def close(self) -> None:
with self._lock:
self._conn.close()
"""The semantic cache: exact-match fast path, then cosine-similarity lookup."""
from __future__ import annotations
import hashlib
import threading
import time
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import Callable
import numpy as np
from .embeddings import Embedder
from .store import VectorStore
def normalize_question(text: str) -> str:
"""Cheap canonicalization: case, whitespace, unicode quotes, trailing punct."""
t = text.strip().lower()
for a, b in (("\u2019", "'"), ("\u2018", "'"), ("\u201c", '"'), ("\u201d", '"')):
t = t.replace(a, b)
t = " ".join(t.split())
if len(t) > 1 and t[-1] in "?!.":
t = t[:-1].rstrip()
return " ".join(t.split())
def question_fingerprint(text: str) -> str:
return hashlib.sha256(normalize_question(text).encode("utf-8")).hexdigest()
@dataclass
class CacheResult:
answer: str
question: str
hit: bool
exact: bool
score: float
stored_at: float
age_seconds: float
lookup_ms: float
@property
def cached_at_iso(self) -> str:
import datetime as _dt
return _dt.datetime.fromtimestamp(self.stored_at, _dt.timezone.utc).isoformat()
@dataclass
class CacheStats:
lookups: int = 0
hits: int = 0
exact_hits: int = 0
semantic_hits: int = 0
misses: int = 0
stores: int = 0
expired: int = 0
evicted: int = 0
@property
def hit_rate(self) -> float:
return self.hits / self.lookups if self.lookups else 0.0
def as_dict(self) -> dict:
return {
**{k: getattr(self, k) for k in self.__dataclass_fields__},
"hit_rate": round(self.hit_rate, 4),
}
class SemanticCache:
"""If a new question is close enough to a stored one, return the stored answer.
Parameters
----------
threshold : cosine similarity in [0, 1] required for a semantic hit.
0.95 = strict (safe, fewer hits), 0.85 = loose (more hits, more risk).
ttl_seconds : freshness window for a stored answer (None = never expire).
max_entries : LRU cap per namespace.
namespace : isolates answers from each other (model + system prompt + tenant!).
enabled : kill switch — False falls through to your LLM every time.
"""
def __init__(
self,
embedder: Embedder,
*,
path: str = ":memory:",
namespace: str = "default",
threshold: float = 0.90,
ttl_seconds: float | None = None,
max_entries: int = 10_000,
top_k: int = 1,
memo_size: int = 4096,
enabled: bool = True,
normalizer: Callable[[str], str] = normalize_question,
) -> None:
if not 0.0 < threshold <= 1.0:
raise ValueError("threshold must be in (0, 1]")
self.embedder = embedder
self.namespace = namespace
self.threshold = float(threshold)
self.ttl_seconds = ttl_seconds
self.max_entries = int(max_entries)
self.top_k = int(top_k)
self.enabled = enabled
self.normalize = normalizer
self._store = VectorStore(path)
self._lock = threading.RLock()
self._memo: OrderedDict[tuple[str, str], np.ndarray] = OrderedDict()
self._memo_size = memo_size
self.stats = CacheStats()
# Refuse to silently mix incompatible embedding spaces.
stored_id = self._store.get_meta("embedder_id")
stored_dim = self._store.get_meta("dim")
if stored_id is None:
self._store.set_meta("embedder_id", embedder.identifier)
self._store.set_meta("dim", str(embedder.dim))
else:
if stored_id != embedder.identifier:
raise ValueError(
f"Cache file {path!r} was written with {stored_id!r} but you "
f"passed {embedder.identifier!r}. Different embedding spaces "
"are not comparable — use a different path or clear() the cache."
)
if stored_dim is not None and int(stored_dim) != embedder.dim:
raise ValueError(
f"dim mismatch: cache has {stored_dim}, embedder gives {embedder.dim}"
)
# ------------------------------------------------------------------ #
def _key(self, question: str) -> tuple[str, str, str]:
norm = self.normalize(question)
return self.namespace, hashlib.sha256(norm.encode("utf-8")).hexdigest(), norm
def _vector(self, norm_question: str) -> np.ndarray:
memo_key = (self.namespace, hashlib.sha256(norm_question.encode()).hexdigest())
vec = self._memo.get(memo_key)
if vec is None:
vec = np.asarray(
self.embedder.embed([norm_question])[0], dtype=np.float32
).reshape(-1)
norm = float(np.linalg.norm(vec))
if norm > 0:
vec = vec / norm
self._memo[memo_key] = vec
if len(self._memo) > self._memo_size:
self._memo.popitem(last=False)
else:
self._memo.move_to_end(memo_key)
return vec
def _expired(self, row: dict) -> bool:
if self.ttl_seconds is None:
return False
return (time.time() - float(row["created_at"])) > self.ttl_seconds
# ------------------------------------------------------------------ #
defanswer 3 something else25,837 characters
counted from the libraries its code imports, since no image, engine or install line named a store (sqlite)
from __future__ import annotations
import os
import zlib
from typing import Protocol, runtime_checkable
import numpy as np
@runtime_checkable
class Embedder(Protocol):
name: str
dim: int
def embed(self, texts: list[str]) -> np.ndarray: ...
class SentenceTransformerEmbedder:
"""Local, free, ~384-dim. Best default for self-hosted apps."""
def __init__(self, model_name: str = "all-MiniLM-L6-v2") -> None:
from sentence_transformers import SentenceTransformer
self._model = SentenceTransformer(model_name)
self.name = f"st:{model_name}"
self.dim = int(self._model.get_sentence_embedding_dimension())
def embed(self, texts: list[str]) -> np.ndarray:
vecs = self._model.encode(texts, normalize_embeddings=False)
return np.asarray(vecs, dtype=np.float32)
class OpenAIEmbedder:
"""Managed embeddings; use when you can't ship a model."""
_DIMS = {
"text-embedding-3-small": 1536,
"text-embedding-3-large": 3072,
"text-embedding-ada-002": 1536,
}
def __init__(self, model: str = "text-embedding-3-small", api_key: str | None = None) -> None:
from openai import OpenAI
key = api_key or os.environ.get("OPENAI_API_KEY")
if not key:
raise RuntimeError("OpenAIEmbedder requires OPENAI_API_KEY")
self._client = OpenAI(api_key=key)
self.model = model
self.name = f"openai:{model}"
self.dim = self._DIMS[model]
def embed(self, texts: list[str]) -> np.ndarray:
resp = self._client.embeddings.create(model=self.model, input=texts)
return np.asarray([d.embedding for d in resp.data], dtype=np.float32)
class BagOfWordsEmbedder:
"""Deterministic, dependency-free word-overlap vectors.
NOT a semantic model. Only for tests and offline demos.
"""
def __init__(self, dim: int = 512) -> None:
self.dim = dim
self.name = "bow"
def embed(self, texts: list[str]) -> np.ndarray:
out = np.zeros((len(texts), self.dim), dtype=np.float32)
for i, text in enumerate(texts):
for tok in text.lower().split():
out[i, zlib.crc32(tok.encode()) % self.dim] += 1.0
return out
def default_embedder() -> Embedder:
try:
return SentenceTransformerEmbedder()
except Exception:
pass
if os.environ.get("OPENAI_API_KEY"):
return OpenAIEmbedder()
raise RuntimeError(
"No embedder available. `pip install sentence-transformers` "
"or set OPENAI_API_KEY."
)
from __future__ import annotations
import sqlite3
import threading
import time
import numpy as np
class VectorStore:
"""SQLite-backed vector store with exact-duplicate fast path."""
def __init__(self, path: str = "llm_cache.db") -> None:
self.path = path
self._lock = threading.RLock()
self._conn = sqlite3.connect(path, check_same_thread=False)
self._conn.row_factory = sqlite3.Row
self._conn.execute("PRAGMA journal_mode=WAL")
self._conn.execute("PRAGMA synchronous=NORMAL")
self._create()
def _create(self) -> None:
with self._lock:
self._conn.executescript(
"""
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY,
namespace TEXT NOT NULL,
question TEXT NOT NULL,
question_hash TEXT NOT NULL,
answer TEXT NOT NULL,
embedding BLOB NOT NULL,
dim INTEGER NOT NULL,
created_at REAL NOT NULL,
expires_at REAL NOT NULL,
hits INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_ns_hash ON entries(namespace, question_hash);
CREATE INDEX IF NOT EXISTS idx_ns ON entries(namespace, expires_at);
CREATE INDEX IF NOT EXISTS idx_exp ON entries(expires_at);
"""
)
self._conn.commit()
# -- reads -----------------------------------------------------------
def exact(self, namespace: str, qhash: str, now: float) -> sqlite3.Row | None:
with self._lock:
return self._conn.execute(
"SELECT * FROM entries WHERE namespace=? AND question_hash=? "
"AND expires_at>? LIMIT 1",
(namespace, qhash, now),
).fetchone()
def scan(self, namespace: str, now: float, limit: int):
"""Return (rows, matrix) of live entries in the namespace."""
with self._lock:
rows = self._conn.execute(
"SELECT id, question, answer, embedding, dim, created_at, hits "
"FROM entries WHERE namespace=? AND expires_at>? "
"ORDER BY created_at DESC LIMIT ?",
(namespace, now, limit),
).fetchall()
if not rows:
return [], None
dim = rows[0]["dim"]
mat = np.empty((len(rows), dim), dtype=np.float32)
for i, r in enumerate(rows):
mat[i] = np.frombuffer(r["embedding"], dtype=np.float32).copy()
return rows, mat
# -- writes ----------------------------------------------------------
def insert(
self,
namespace: str,
question: str,
qhash: str,
answer: str,
vec: np.ndarray,
expires_at: float,
) -> int:
now = time.time()
with self._lock:
cur = self._conn.execute(
"INSERT INTO entries (namespace, question, question_hash, answer, "
"embedding, dim, created_at, expires_at) VALUES (?,?,?,?,?,?,?,?)",
(
namespace,
question,
qhash,
answer,
vec.astype(np.float32).tobytes(),
int(vec.shape[0]),
now,
expires_at,
),
)
self._conn.commit()
return int(cur.lastrowid)
def refresh(self, entry_id: int, answer: str, expires_at: float) -> None:
with self._lock:
self._conn.execute(
"UPDATE entries SET answer=?, expires_at=?, hits=hits+1 WHERE id=?",
(answer, expires_at, entry_id),
)
self._conn.commit()
def bump(self, entry_id: int) -> None:
with self._lock:
self._conn.execute("UPDATE entries SET hits=hits+1 WHERE id=?", (entry_id,))
self._conn.commit()
def evict_expired(self, now: float) -> int:
with self._lock:
cur = self._conn.execute("DELETE FROM entries WHERE expires_at<?", (now,))
self._conn.commit()
return cur.rowcount
def trim(self, namespace: str, max_entries: int) -> int:
"""Keep only the newest `max_entries` rows in the namespace."""
with self._lock:
cur = self._conn.execute(
"DELETE FROM entries WHERE namespace=? AND id NOT IN ("
" SELECT id FROM entries WHERE namespace=?"
" ORDER BY created_at DESC LIMIT ?)",
(namespace, namespace, max_entries),
)
self._conn.commit()
return cur.rowcount
def count(self, namespace: str | None = None) -> int:
with self._lock:
if namespace:
row = self._conn.execute(
"SELECT COUNT(*) c FROM entries WHERE namespace=?", (namespace,)
).fetchone()
else:
row = self._conn.execute("SELECT COUNT(*) c FROM entries").fetchone()
return int(row["c"])
def clear(self) -> None:
with self._lock:
self._conn.execute("DELETE FROM entries")
self._conn.commit()
def close(self) -> None:
with self._lock:
self._conn.close()
from __future__ import annotations
# USD per 1M tokens: (input, output)
PRICES = {
"gpt-4o": (2.50, 10.00),
"gpt-4o-mini": (0.15, 0.60),
"gpt-4-turbo": (10.00, 30.00),
"gpt-4.1": (2.00, 8.00),
"claude-3-5-sonnet": (3.00, 15.00),
"claude-3-5-haiku": (0.80, 4.00),
"llama-3.1-70b": (0.59, 0.79),
}
def char_cost_fn(model: str, *, chars_per_token: float = 4.0):
"""Rough cost of one completion, using len/4 tokenization.
Swap this for a real tokenizer if you need precise accounting.
"""
if model not in PRICES:
raise KeyError(f"unknown model {model!r}; add it to PRICES")
in_rate, out_rate = PRICES[model]
def cost(question: str, answer: str) -> float:
return (
(len(question) / chars_per_token) * in_rate
+ (len(answer) / chars_per_token) * out_rate
) / 1_000_000
return cost
from __future__ import annotations
import hashlib
import json
import time
from dataclasses import dataclass, field
from typing import Any, Callable, Literal
import numpy as np
from .embedding import Embedder, default_embedder
from .store import VectorStore
DEFAULT_DENY_PREFIXES = ("error:", "traceback", "rate limit", "internal server error")
def make_namespace(
model: str,
system_prompt: str = "",
*,
temperature: float = 0.0,
top_p: float = 1.0,
**extra: Any,
) -> str:
"""Build a cache namespace from everything that changes the answer.
Two questions are only interchangeable if they were asked to the same
model, with the same system prompt and sampling params.
"""
blob = json.dumps(
{"model": model, "system": system_prompt, "t": temperature, "p": top_p, **extra},
sort_keys=True,
)
return f"{model}#{hashlib.sha256(blob.encode()).hexdigest()[:16]}"
def _qhash(question: str) -> str:
return hashlib.sha256(question.strip().casefold().encode()).hexdigest()
@dataclass
class CacheHit:
kind: Literal["exact", "semantic"]
question: str
answer: str
similarity: float
entry_id: int
age_seconds: float
hits: int
@dataclass
class AskResult:
answer: str
hit: CacheHit | None
cached: bool
@property
def save_usd(self) -> float | None:
return None # filled by caller via stats()
@dataclass
class Stats:
hits: int = 0
exact_hits: int = 0
semantic_hits: int = 0
misses: int = 0
puts: int = 0
rejected: int = 0 # answers we refused to cache
evicted: int = 0
saved_usd: float = 0.0
@property
def hit_rate(self) -> float:
total = self.hits + self.misses
return self.hits / total if total else 0.0
def as_dict(self) -> dict:
d = self.__dict__.copy()
d["hit_rate"] = round(self.hit_rate, 4)
d["saved_usd"] = round(self.saved_usd, 6)
return d
@dataclass
class Neighbor:
question: str
answer: str
similarity: float
age_seconds: float
class SemanticCache:
"""Embed → cosine-similarity lookup → serve stored answer instead of
paying the LLM, when a close-enough question was already answered.
"""
def __init__(
self,
embedder: Embedder | None = None,
*,
path: str = "llm_cache.db",
threshold: float = 0.92,
ttl_seconds: float = 30 * 86400,
max_entries: int = 50_000,
max_scan: int = 25_000,
namespace: str = "default",
cost_fn: Callable[[str, str], float] | None = None,
deny_prefixes: tuple[str, ...] = DEFAULT_DENY_PREFIXES,
min_answer_chars: int = 1,
purge_interval: float = 300.0,
) -> None:
if not 0.0 < threshold <= 1.0:
raise ValueError("threshold must be in (0, 1]")
self.embedder = embedder or default_embedder()
self.store = VectorStore(path)
self.threshold = threshold
self.ttl = ttl_seconds
self.max_entries = max_entries
self.max_scan = max_scan
self.namespace = namespace
self.cost_fn = cost_fn
self.deny_prefixes = tuple(p.lower() for p in deny_prefixes)
self.min_answer_chars = min_answer_chars
self.purge_interval = purge_interval
self.stats = Stats()
self._last_purge = time.time()
# -- internals -------------------------------------------------------
def _embed(self, question: str) -> np.ndarray:
vec = np.asarray(self.embedder.embed([question]), dtype=np.float32)[0]
if vec.shape[0] != self.embedder.dim:
raise ValueError("embedder returned unexpected dimension")
norm = float(np.linalg.norm(vec))
if norm == 0.0:
raise ValueError(f"zero-norm embedding for {question!r}")
return vec / norm # normalize once, at write/compare time
def _is_cacheable(self, answer: Any) -> bool:
if not isinstance(answer, str):
return False
text = answer.strip()
if len(text) < self.min_answer_chars:
return False
low = text.lower()
return not any(low.startswith(p) for p in self.deny_prefixes)
def _maybe_purge(self) -> None:
now = time.time()
if now - self._last_purge >= self.purge_interval:
self.stats.evicted += self.store.evict_expired(now)
self._last_purge = now
# -- public API ------------------------------------------------------
def get(self, question: str) -> CacheHit | None:
"""Return a stored answer for a similar earlier question, else None."""
self._maybe_purge()
now = time.time()
ns, qh = self.namespace, _qhash(question)
# 1. exact duplicate: skip the embedding call entirely
row = self.store.exact(ns, qh, now)
if row is not None:
self.store.bump(row["id"])
self.stats.hits += 1
self.stats.exact_hits += 1
self._record_saving(question, row["answer"])
return CacheHit("exact", question, row["answer"], 1.0, row["id"],
now - row["created_at"], row["hits"] + 1)
# 2. semantic near-duplicate
qvec = self._embed(question)
rows, mat = self.store.scan(ns, now, self.max_scan)
if mat is None:
self.stats.misses += 1
return None
sims = mat @ qvec # cosine, since both normalized
best = int(np.argmax(sims))
if float(sims[best]) < self.threshold:
self.stats.misses += 1
return None
row = rows[best]
self.store.bump(row["id"])
self.stats.hits += 1
self.stats.semantic_hits += 1
self._record_saving(question, row["answer"])
return CacheHit(
"semantic",
row["question"],
row["answer"],
float(sims[best]),
row["id"],
now - row["created_at"],
row["hits"] + 1,
)
def put(self, question: str, answer: str, *, refresh_only: bool = False) -> int | None:
if not self._is_cacheable(answer):
self.stats.rejected += 1
return None
now = time.time()
expires = now + self.ttl
ns, qh = self.namespace, _qhash(question)
existing = self.store.exact(ns, qh, now)
if existing is not None: # dedupe: refresh, don't duplicate
self.store.refresh(existing["id"], answer, expires)
return existing["id"]
entry_id = self.store.insert(ns, question, qh, answer, self._embed(question), expires)
self.stats.puts += 1
self.stats.evicted += self.store.trim(ns, self.max_entries)
return entry_id
def ask(
self,
question: str,
generate: Callable[[str], str],
*,
force: bool = False,
) -> AskResult:
"""The one call you need: cache-aware LLM invocation."""
hit = None if force else self.get(question)
if hit is not None:
return AskResult(hit.answer, hit, True)
answer = generate(question) # only reached on a miss
self.put(question, answer)
return AskResult(answer, None, False)
def similar(self, question: str, k: int = 5) -> list[Neighbor]:
"""Top-k neighbours with scores — use this to tune `threshold`."""
qvec = self._embed(question)
rows, mat = self.store.scan(self.namespace, time.time(), self.max_scan)
if mat is None:
return []
sims = mat @ qvec
order = np.argsort(-sims)[:k]
now = time.time()
return [
Neighbor(rows[i]["question"], rows[i]["answer"], float(sims[i]),
now - rows[i]["created_at"])
for i in order
]
def _record_saving(self, question: str, answer: str) -> None:
if self.cost_fn is not None:
try:
self.stats.saved_usd += self.cost_fn(question, answer)
except Exception:
pass
def clear(self) -> None:
self.store.clear()
def close(self) -> None:
self.store.close()
def __enter__(self) -> "SemanticCache":
return self
def __exit__(self, *exc) -> None:
self.close()
from .cache import AskResult, CacheHit, Neighbor, SemanticCache, Stats, make_namespace
from .embedding import (
BagOfWordsEmbedder,
Embedder,
OpenAIEmbedder,
SentenceTransformerEmbedder,
default_embedder,
)
from .pricing import char_cost_fn
__all__ = [
"SemanticCache", "AskResult", "CacheHit", "Neighbor", "Stats", "make_namespace",
"Embedder", "SentenceTransformerEmbedder", "OpenAIEmbedder",
"BagOfWordsEmbedder", "default_embedder", "char_cost_fn",
]
from semantic_cache import SemanticCache, make_namespace, char_cost_fn
cache = SemanticCache(
path="llm_cache.db",
threshold=0.92, # cosine; tune with cache.similar()
ttl_seconds=30 * 86400, # stale facts must not live forever
namespace=make_namespace(
model="gpt-4o-mini",
system_prompt=SYSTEM_PROMPT, # changing the prompt = new namespace
temperature=0.2,
),
cost_fn=char_cost_fn("gpt-4o-mini"), # enables $ savings tracking
)
def answer(q: str) -> str:
# your existing LLM call — untouched
return llm.complete(system=SYSTEM_PROMPT, user=q, temperature=0.2)
def cached_answer(q: str) -> str:
return cache.ask(q, answer).answer
# one-off, with observability
r = cache.ask("What's the capital of France?", answer)
print(r.cached, r.hit.similarity if r.hit else None, cache.stats.as_dict())
paraphrases = ["What is the capital of France?", "Capital of France?",
"France's capital city — what is it?"]
unrelated = ["How do I write a SQL JOIN?", "Who wrote Hamlet?"]
for q in paraphrases + unrelated:
print(f"{cache.similar(q, 1)} <- {q}")
pip install pytest numpy pytest tests/ -q
# tests/test_cache.py
import time
import numpy as np
import pytest
from semantic_cache import SemanticCache, make_namespace
class DictEmbedder:
"""Deterministic scripted vectors so similarity is exactly controllable."""
name = "scripted"
dim = 3
def __init__(self, table): self.table = table
def embed(self, texts):
return np.asarray([self.table[t] for t in texts], dtype=np.float32)
def make_cache(tmp_path, table, **kw):
return SemanticCache(
embedder=DictEmbedder(table),
path=str(tmp_path / "c.db"),
**kw,
)
# cos(a,b): exact=1.0, paraphrase=0.95, weak=0.70, other=0.0
TABLE = {
"q1": [1.0, 0.0, 0.0],
"q1 paraphrase": [0.95, 0.3122, 0.0],
"q1 weak variant": [0.70, 0.7141, 0.0],
"q2": [0.0, 0.0, 1.0],
}
def test_exact_hit_skips_generation(tmp_path):
cache = make_cache(tmp_path, TABLE)
calls = []
gen = lambda q: calls.append(q) or "answer"
assert cache.ask("q1", gen).cached is False
assert cache.ask("q1", gen).cached is True
assert calls == ["q1"] # generated once
assert cache.stats.exact_hits == 1
def test_semantic_hit_on_paraphrase(tmp_path):
cache = make_cache(tmp_path, TABLE, threshold=0.92)
gen = lambda q: "stored answer"
cache.ask("q1", gen)
r = cache.ask("q1 paraphrase", gen) # sim 0.95 >= 0.92
assert r.cached and r.hit.kind == "semantic"
assert r.hit.similarity == pytest.approx(0.95, abs=1e-3)
def test_below_threshold_is_a_miss(tmp_path):
cache = make_cache(tmp_path, TABLE, threshold=0.92)
gen = lambda q: "fresh"
cache.ask("q1", gen)
r = cache.ask("q1 weak variant", gen) # sim 0.70 < 0.92
assert not r.cached
def test_unrelated_question_is_a_miss(tmp_path):
cache = make_cache(tmp_path, TABLE)
cache.ask("q1", lambda q: "a")
assert cache.ask("q2", lambda q: "b").cached is False
def test_ttl_expiry(tmp_path):
cache = make_cache(tmp_path, TABLE, ttl_seconds=60)
cache.ask("q1", lambda q: "stale")
cache.store.evict_expired(time.time() + 120) # simulate clock advance
assert cache.ask("q1", lambda q: "fresh").cached is False
def test_namespace_isolates_models(tmp_path):
table = {"q1": [1, 0, 0]}
a = make_cache(tmp_path, table, namespace=make_namespace("gpt-4o", "sys A"))
a.ask("q1", lambda q: "A")
b = SemanticCache(embedder=DictEmbedder(table), path=str(tmp_path / "c.db"),
namespace=make_namespace("gpt-4o", "sys B"))
assert b.ask("q1", lambda q: "B").cached is False
assert b.ask("q1", lambda q: "B").answer == "B"
def test_error_answers_are_not_cached(tmp_path):
cache = make_cache(tmp_path, TABLE)
cache.ask("q1", lambda q: "Error: upstream 500")
assert cache.stats.rejected == 1
assert cache.ask("q1", lambda q: "recovered").answer == "recovered"
def test_persists_across_restart(tmp_path):
path = str(tmp_path / "c.db")
t = {"q1": np.array([1, 0, 0], dtype=np.float32)}
SemanticCache(embedder=DictEmbedder(t), path=path).ask("q1", lambda q: "kept")
reopened = SemanticCache(embedder=DictEmbedder(t), path=path)
hit = reopened.ask("q1", lambda q: "should not run")
assert hit.cached and hit.answer == "kept"
def test_max_entries_evicts_oldest(tmp_path):
cache = make_cache(tmp_path, {"q1": [1, 0, 0], "q2": [0, 0, 1]},
max_entries=1)
cache.ask("q1", lambda q: "one")
cache.ask("q2", lambda q: "two")
assert cache.store.count() == 1
assert cache.ask("q1", lambda q: "one again").cached is False
def test_savings_accounting(tmp_path):
cache = make_cache(tmp_path, {"q1": [1, 0, 0]},
cost_fn=lambda q, a: 0.002)
cache.ask("q1", lambda q: "x")
cache.ask("q1", lambda q: "x")
assert cache.stats.saved_usd == pytest.approx(0.002)
assert cache.stats.hit_rate == 0.5
answer 4 something else10,116 characters
counted from the line that states the choice (sqlite)
pip install numpy sentence-transformers # or: pip install numpy openai
llmapp/
cache/
__init__.py
embeddings.py # pluggable embedders + dev fallback
guards.py # normalize, critical tokens, no-cache patterns
store.py # SQLite + numpy vector index
semantic_cache.py # the facade (`python -m llmapp.cache` = report)
scripts/
tune_threshold.py # pick threshold from labeled pairs
tests/
test_semantic_cache.py
from __future__ import annotations
import hashlib
import re
from typing import Protocol
import numpy as np
__all__ = ["Embedder", "MiniLMEmbedder", "OpenAIEmbedder", "HashingEmbedder"]
class Embedder(Protocol):
name: str
dim: int
def embed(self, texts: list[str]) -> np.ndarray:
"""Return float32, shape (len(texts), dim), L2-ROWS-NORMALISED."""
...
def _l2(x: np.ndarray) -> np.ndarray:
n = np.linalg.norm(x, axis=1, keepdims=True)
n[n == 0] = 1.0
return (x / n).astype(np.float32)
class MiniLMEmbedder:
"""Local, free, ~1ms/query. Good default for a cache."""
def __init__(self, model_name: str = "sentence-transformers/all-MiniLM-L6-v2"):
from sentence_transformers import SentenceTransformer
self._m = SentenceTransformer(model_name)
self.name = f"sentence-transformers/{model_name.split('/')[-1]}"
self.dim = int(self._m.get_sentence_embedding_dimension())
def embed(self, texts: list[str]) -> np.ndarray:
v = self._m.encode(texts, normalize_embeddings=True)
return np.asarray(v, dtype=np.float32)
class OpenAIEmbedder:
def __init__(self, model: str = "text-embedding-3-small", api_key: str | None = None):
from openai import OpenAI
self._c = OpenAI(api_key=api_key)
self._model = model
self.name = f"openai/{model}"
self.dim = {"text-embedding-3-small": 1536, "text-embedding-3-large": 3072}[model]
def embed(self, texts: list[str]) -> np.ndarray:
out = np.empty((len(texts), self.dim), dtype=np.float32)
for i in range(0, len(texts), 2048): # API batch limit
r = self._c.embeddings.create(model=self._model, input=texts[i : i + 2048])
for j, d in enumerate(r.data):
out[i + j] = d.embedding
return _l2(out)
# --- dev/test only: zero dependencies, weaker semantics -----------------
_TOKEN = re.compile(r"[a-z0-9']+", re.I)
_STOP = frozenset(
"a an the is are was were be been being of in on at to for from by with and or "
"but if then this that these those it its i you your we our they them what which "
"who whom how why when where do does did done have has had please tell me my".split()
)
class HashingEmbedder:
"""Dependency-free fallback for tests and smoke runs.
NOT production-grade -- swap for MiniLM/OpenAI by changing one line."""
name = "dev/hashing-bow"
def __init__(self, dim: int = 512):
self.dim = dim
def embed(self, texts: list[str]) -> np.ndarray:
out = np.zeros((len(texts), self.dim), dtype=np.float32)
for i, t in enumerate(texts):
for tok in _TOKEN.findall(t):
if tok in _STOP:
continue
h = int.from_bytes(hashlib.sha1(tok.encode()).digest()[:4], "big")
out[i, h % self.dim] += 1.0 if (h & 1) else -1.0
return _l2(out)
# Starting points ONLY -- calibrate with scripts/tune_threshold.py.
DEFAULT_THRESHOLDS = {
"sentence-transformers/all-MiniLM-L6-v2": 0.90,
"openai/text-embedding-3-small": 0.82,
"openai/text-embedding-3-large": 0.82,
"dev/hashing-bow": 0.75,
}
from __future__ import annotations
import hashlib
import re
import unicodedata
__all__ = ["normalize", "exact_key", "critical_tokens", "guard_allows", "DEFAULT_NO_CACHE"]
_WS = re.compile(r"\s+")
def normalize(q: str) -> str:
"""Shared by both the exact-hash path and the embedding path."""
return _WS.sub(" ", unicodedata.normalize("NFKC", q)).strip()
def exact_key(q: str) -> str:
"""Conservative: casefold + whitespace only. Punctuation is preserved
because '2+2' vs '2 + 2' are different questions."""
return hashlib.sha256(normalize(q).casefold().encode("utf-8")).hexdigest()
# --- critical tokens -----------------------------------------------------
# Everything here is a value the answer DEPENDS ON. Asymmetric => miss.
_PATTERNS = [
re.compile(r"\b\d[\d,]*(?:\.\d+)?\s?%"), # 12.5%, 1,000
re.compile(r"\b\d{4}-\d{2}-\d{2}\b"), # 2024-05-01
re.compile(r"\b\d{1,2}/\d{1,2}(?:/\d{2,4})?\b"), # 5/1/24
re.compile(r"\b[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\b"), # emails
re.compile(r"\b[\w-]+-\d+\b"), # INC-4471, order-123
re.compile(r"\b\d+(?:\.\d+)+\b"), # 3.11.2, 10.0.0.1
re.compile(r"\b\d+\b"), # bare numbers
re.compile(r"\b[A-Z]{2,6}\b"), # AAPL, HTTP, USA
re.compile(r'"([^"\n]{2,})"'),
re.compile(r"'([^'\n]{2,})'"),
]
# "... for/in/at/of/from/to <value>" -- catches entities the embeddings miss.
_SLOT_WORDS = (
"for|of|in|at|on|from|to|between|above|below|over|under|since|until|about|"
"user|customer|account|order|invoice|ticket|file|column|table|endpoint|"
"class|function|method|version|release|named|called|using|via"
)
_SLOT = re.compile(
rf"\b(?:{_SLOT_WORDS})\b[\s:]+([A-Za-z][\w.-]*|\d[\w.,-]*)", re.IGNORECASE
)
def critical_tokens(q: str) -> frozenset[str]:
t = normalize(q)
out: set[str] = set()
for p in _PATTERNS:
for m in p.finditer(t):
out.add((m.group(1) if m.groups() else m.group(0)).strip().casefold())
for m in _SLOT.finditer(t):
out.add(m.group(1).casefold())
return frozenset(out)
def guard_allows(cached_question: str, new_question: str) -> bool:
"""Strict equality by design.
If either side has a critical token the other lacks, the cached answer
was generated for a *different* value and must not be reused.
This can only ever cost you recall, never correctness."""
return critical_tokens(cached_question) == critical_tokens(new_question)
# Questions whose answer is time-anchored -- never cache.
# First knob to tune for YOUR app.
DEFAULT_NO_CACHE = [
r"\b(right now|at the moment)\b",
r"\b(real-?time|live)\b",
r"\b(today'?s?|as of today|this morning|this evening)\b",
r"\b(current|latest)\b",
]
def is_no_cache(q: str, patterns: list[str]) -> str | None:
t = normalize(q)
for p in patterns:
if re.search(p, t, re.IGNORECASE):
return p
return None
from __future__ import annotations
import sqlite3
import threading
import time
import numpy as np
__all__ = ["VectorStore", "EmbeddingMismatch"]
class EmbeddingMismatch(RuntimeError):
pass
_SCHEMA = """
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY,
scope TEXT NOT NULL,
key_hash TEXT NOT NULL,
question TEXT NOT NULL,
answer TEXT NOT NULL,
meta TEXT,
embedding BLOB NOT NULL,
created_at REAL NOT NULL,
last_used_at REAL NOT NULL,
hits INTEGER NOT NULL DEFAULT 0,
expires_at REAL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_e_scope_key ON entries(scope, key_hash);
CREATE INDEX IF NOT EXISTS idx_e_scope_exp ON entries(scope, expires_at);
CREATE TABLE IF NOT EXISTS ns_meta (
scope TEXT PRIMARY KEY,
write_seq INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS ns_kv (
scope TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (scope, key)
);
"""
class _Index:
"""In-memory L2-normalised matrix => cosine == dot product."""
def __init__(self, dim: int, cap: int = 1024):
self.dim = dim
self.cap = cap
self.n = 0
self.ids = np.zeros(cap, dtype=np.int64)
self.keys: list[str | None] = [None] * cap
self.mat = np.zeros((cap, dim), dtype=np.float32)
self.pos_of_id: dict[int, int] = {}
self.pos_of_key: dict[str, int] = {}
self.write_seq = -1
def _grow(self) -> None:
new_cap = self.cap * 2
self.ids = np.resize(self.ids, new_cap)
self.mat = np.resize(self.mat, (new_cap, self.dim))
self.keys.extend([None] * (new_cap - self.cap))
self.cap = new_cap
def upsert(self, id_: int, key: str, vec: np.ndarray) -> None:
pos = self.pos_of_key.get(key)
if pos is None:
if self.n == self.cap:
self._grow()
pos = self.n
answer 5 something else1,075 characters
counted from the line that states the choice (faiss)
pyproject.toml
README.md
semantic_cache/{__init__,embeddings,core,llm}.py
examples/demo.py
tests/{conftest,test_cache}.py
A managed store on AWS
Valkey 17 · Redis 8 of 25
Claude Opus 5.5 · Valkey in 5 of 5
answer 1 Valkey7,252 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80" # Valkey engine support
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
}
}
# ---------- Inputs ----------
variable "name" {
type = string
default = "webapp-cache"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
description = "Private subnets in at least 2 AZs"
type = list(string)
}
variable "app_security_group_id" {
description = "Security group of the app tier (ECS tasks, EC2, Lambda, etc.)"
type = string
}
variable "node_type" {
type = string
default = "cache.t4g.small" # move to cache.r7g.large+ as traffic grows
}
variable "replica_count" {
type = number
default = 1
}
# ---------- Networking ----------
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-subnets"
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "cache" {
name = "${var.name}-sg"
description = "ElastiCache Valkey access from app tier only"
vpc_id = var.vpc_id
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
security_group_id = aws_security_group.cache.id
referenced_security_group_id = var.app_security_group_id
ip_protocol = "tcp"
from_port = 6379
to_port = 6379
description = "Valkey from app"
}
# ---------- Auth ----------
resource "random_password" "auth" {
length = 64
special = true
override_special = "!&#$^<>-" # ElastiCache disallows / " @ and spaces
}
resource "aws_secretsmanager_secret" "cache_auth" {
name = "${var.name}/auth-token"
}
resource "aws_secretsmanager_secret_version" "cache_auth" {
secret_id = aws_secretsmanager_secret.cache_auth.id
secret_string = jsonencode({
host = aws_elasticache_replication_group.this.primary_endpoint_address
port = 6379
token = random_password.auth.result
tls = true
})
}
# ---------- Parameters ----------
resource "aws_elasticache_parameter_group" "this" {
name = "${var.name}-valkey8"
family = "valkey8"
# Evict only keys that have a TTL. Set TTLs on sessions AND cache entries.
# Avoid allkeys-lru here: under memory pressure it can evict active sessions.
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
}
# ---------- Logs ----------
resource "aws_cloudwatch_log_group" "slow" {
name = "/elasticache/${var.name}/slow-log"
retention_in_days = 14
}
# ---------- Cluster ----------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = var.name
description = "Sessions and cache for web app"
engine = "valkey"
engine_version = "8.0"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.this.name
num_cache_clusters = 1 + var.replica_count # primary + replicas
automatic_failover_enabled = true
multi_az_enabled = true
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.cache.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth.result
snapshot_retention_limit = 3
snapshot_window = "03:00-04:00"
maintenance_window = "sun:05:00-sun:06:00"
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.slow.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
lifecycle {
ignore_changes = [auth_token] # rotate deliberately, not on every plan
}
}
# ---------- Outputs ----------
output "primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "auth_secret_arn" {
value = aws_secretsmanager_secret.cache_auth.arn
}
output "security_group_id" {
value = aws_security_group.cache.id
}
resource "aws_elasticache_serverless_cache" "this" {
name = var.name
engine = "valkey"
major_engine_version = "8"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.cache.id]
cache_usage_limits {
data_storage {
maximum = 5
unit = "GB"
}
ecpu_per_second {
maximum = 5000
}
}
snapshot_retention_limit = 3
}
answer 2 Valkey9,911 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80"
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
}
}
variable "name" {
description = "Name prefix for resources"
type = string
default = "webapp-cache"
}
variable "environment" {
type = string
default = "prod"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
description = "Private subnets in at least 2 AZs"
type = list(string)
}
variable "app_security_group_ids" {
description = "Security groups of the app tier (ECS tasks, EC2, Lambda) allowed to connect"
type = list(string)
}
variable "node_type" {
description = "Start small; move to cache.r7g.* for memory-heavy prod workloads"
type = string
default = "cache.t4g.medium"
}
variable "replica_count" {
description = "Number of read replicas (>=1 required for Multi-AZ failover)"
type = number
default = 1
}
locals {
tags = {
Name = var.name
Environment = var.environment
ManagedBy = "terraform"
}
}
# --- Networking -------------------------------------------------------------
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-subnets"
subnet_ids = var.private_subnet_ids
tags = local.tags
}
resource "aws_security_group" "cache" {
name = "${var.name}-sg"
description = "ElastiCache Valkey access from app tier"
vpc_id = var.vpc_id
tags = local.tags
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
for_each = toset(var.app_security_group_ids)
security_group_id = aws_security_group.cache.id
referenced_security_group_id = each.value
ip_protocol = "tcp"
from_port = 6379
to_port = 6379
description = "Valkey from app"
}
# No egress rule needed; ElastiCache doesn't initiate outbound connections.
# --- Auth token -------------------------------------------------------------
resource "random_password" "auth" {
length = 64
special = false # ElastiCache restricts allowed special characters; alnum is safest
}
resource "aws_secretsmanager_secret" "auth" {
name = "${var.name}/auth-token"
description = "AUTH token for ${var.name} ElastiCache"
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "auth" {
secret_id = aws_secretsmanager_secret.auth.id
secret_string = jsonencode({
auth_token = random_password.auth.result
primary_endpoint = aws_elasticache_replication_group.this.primary_endpoint_address
reader_endpoint = aws_elasticache_replication_group.this.reader_endpoint_address
port = 6379
tls = true
})
}
# --- Parameters -------------------------------------------------------------
resource "aws_elasticache_parameter_group" "this" {
name = "${var.name}-valkey8"
family = "valkey8"
# Only evict keys that have a TTL. Sessions and cache entries should both
# have TTLs. Anything without one (e.g. config) is never evicted.
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
tags = local.tags
}
# --- Logs -------------------------------------------------------------------
resource "aws_cloudwatch_log_group" "slow" {
name = "/elasticache/${var.name}/slow-log"
retention_in_days = 14
tags = local.tags
}
# --- Cluster ----------------------------------------------------------------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = var.name
description = "Sessions + cache for ${var.name}"
engine = "valkey"
engine_version = "8.0"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.this.name
# Cluster mode disabled: one shard, primary + replicas.
# Simplest for session libs; scale vertically or enable cluster mode later.
num_cache_clusters = 1 + var.replica_count
automatic_failover_enabled = var.replica_count > 0
multi_az_enabled = var.replica_count > 0
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.cache.id]
# Security
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth.result
auth_token_update_strategy = "ROTATE"
# Maintenance & backups
maintenance_window = "sun:05:00-sun:06:00"
snapshot_window = "03:00-04:00"
snapshot_retention_limit = 3
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.slow.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
tags = local.tags
lifecycle {
ignore_changes = [num_cache_clusters] # if you scale replicas out-of-band
}
}
# --- Alarms (minimal) -------------------------------------------------------
resource "aws_cloudwatch_metric_alarm" "memory" {
alarm_name = "${var.name}-memory-high"
namespace = "AWS/ElastiCache"
metric_name = "DatabaseMemoryUsagePercentage"
statistic = "Average"
period = 300
evaluation_periods = 2
threshold = 80
comparison_operator = "GreaterThanThreshold"
dimensions = { ReplicationGroupId = aws_elasticache_replication_group.this.id }
tags = local.tags
# alarm_actions = [aws_sns_topic.alerts.arn]
}
resource "aws_cloudwatch_metric_alarm" "cpu" {
alarm_name = "${var.name}-engine-cpu-high"
namespace = "AWS/ElastiCache"
metric_name = "EngineCPUUtilization"
statistic = "Average"
period = 300
evaluation_periods = 2
threshold = 75
comparison_operator = "GreaterThanThreshold"
dimensions = { ReplicationGroupId = aws_elasticache_replication_group.this.id }
tags = local.tags
}
output "primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "auth_secret_arn" {
description = "Grant your app role secretsmanager:GetSecretValue on this"
value = aws_secretsmanager_secret.auth.arn
}
output "security_group_id" {
value = aws_security_group.cache.id
}
resource "aws_elasticache_serverless_cache" "this" {
name = var.name
engine = "valkey"
major_engine_version = "8"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.cache.id]
cache_usage_limits {
data_storage {
maximum = 10
unit = "GB"
}
ecpu_per_second {
maximum = 5000
}
}
snapshot_retention_limit = 3
daily_snapshot_time = "03:00"
tags = local.tags
}
answer 3 Valkey9,611 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80" # Valkey engine support
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
}
}
variable "name" {
description = "Name prefix for resources"
type = string
default = "webapp"
}
variable "environment" {
type = string
default = "prod"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
description = "Private subnets in at least 2 AZs"
type = list(string)
}
variable "app_security_group_ids" {
description = "Security groups of the app tier allowed to connect"
type = list(string)
}
variable "node_type" {
type = string
default = "cache.t4g.medium" # use cache.r7g.large+ for production load
}
variable "replica_count" {
description = "Number of replicas in addition to the primary"
type = number
default = 1
}
locals {
id = "${var.name}-${var.environment}"
port = 6379
tags = {
Application = var.name
Environment = var.environment
ManagedBy = "terraform"
}
}
# ---------- Networking ----------
resource "aws_elasticache_subnet_group" "this" {
name = "${local.id}-cache"
subnet_ids = var.private_subnet_ids
tags = local.tags
}
resource "aws_security_group" "cache" {
name = "${local.id}-cache"
description = "ElastiCache Valkey access from app tier"
vpc_id = var.vpc_id
tags = merge(local.tags, { Name = "${local.id}-cache" })
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
for_each = toset(var.app_security_group_ids)
security_group_id = aws_security_group.cache.id
referenced_security_group_id = each.value
from_port = local.port
to_port = local.port
ip_protocol = "tcp"
description = "Valkey from app"
}
# No egress rule needed; ElastiCache doesn't initiate connections.
# ---------- Auth ----------
resource "random_password" "auth" {
length = 64
special = false # avoids disallowed chars (@ / " and spaces)
}
resource "aws_secretsmanager_secret" "cache_auth" {
name = "${local.id}/cache/auth-token"
description = "Auth token for ${local.id} Valkey"
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "cache_auth" {
secret_id = aws_secretsmanager_secret.cache_auth.id
secret_string = jsonencode({
host = aws_elasticache_replication_group.this.primary_endpoint_address
port = local.port
password = random_password.auth.result
tls = true
})
}
# ---------- Parameters ----------
resource "aws_elasticache_parameter_group" "this" {
name = "${local.id}-valkey8"
family = "valkey8"
# Only evict keys that have a TTL, so sessions aren't dropped
# before expired/expiring cache entries.
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
tags = local.tags
}
# ---------- Logs ----------
resource "aws_cloudwatch_log_group" "slow" {
name = "/elasticache/${local.id}/slow-log"
retention_in_days = 14
tags = local.tags
}
resource "aws_cloudwatch_log_group" "engine" {
name = "/elasticache/${local.id}/engine-log"
retention_in_days = 14
tags = local.tags
}
# ---------- Cluster ----------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = local.id
description = "Sessions and cache for ${local.id}"
engine = "valkey"
engine_version = "8.0"
node_type = var.node_type
port = local.port
parameter_group_name = aws_elasticache_parameter_group.this.name
# Cluster mode disabled: 1 primary + N replicas.
# Simplest for session libraries; scale vertically or move to cluster mode later.
num_cache_clusters = 1 + var.replica_count
automatic_failover_enabled = var.replica_count > 0
multi_az_enabled = var.replica_count > 0
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.cache.id]
# Security
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth.result
auth_token_update_strategy = "ROTATE"
# Ops
snapshot_retention_limit = 3
snapshot_window = "03:00-04:00"
maintenance_window = "sun:05:00-sun:06:00"
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.slow.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
log_delivery_configuration {
destination = aws_cloudwatch_log_group.engine.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "engine-log"
}
tags = local.tags
lifecycle {
precondition {
condition = length(var.private_subnet_ids) >= 2
error_message = "Provide subnets in at least two AZs for Multi-AZ."
}
}
}
# ---------- Alarms (minimal) ----------
resource "aws_cloudwatch_metric_alarm" "memory" {
alarm_name = "${local.id}-cache-memory-high"
namespace = "AWS/ElastiCache"
metric_name = "DatabaseMemoryUsagePercentage"
statistic = "Average"
period = 300
evaluation_periods = 2
threshold = 80
comparison_operator = "GreaterThanThreshold"
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
tags = local.tags
}
resource "aws_cloudwatch_metric_alarm" "evictions" {
alarm_name = "${local.id}-cache-evictions"
namespace = "AWS/ElastiCache"
metric_name = "Evictions"
statistic = "Sum"
period = 300
evaluation_periods = 1
threshold = 0
comparison_operator = "GreaterThanThreshold"
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
tags = local.tags
}
output "primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "port" {
value = local.port
}
output "auth_secret_arn" {
description = "Grant your app's IAM role secretsmanager:GetSecretValue on this"
value = aws_secretsmanager_secret.cache_auth.arn
}
output "security_group_id" {
value = aws_security_group.cache.id
}
resource "aws_elasticache_serverless_cache" "this" {
name = local.id
engine = "valkey"
major_engine_version = "8"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.cache.id]
cache_usage_limits {
data_storage {
maximum = 10
unit = "GB"
}
ecpu_per_second {
maximum = 5000
}
}
snapshot_retention_limit = 3
tags = local.tags
}
answer 4 Valkey7,801 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = { source = "hashicorp/aws", version = ">= 5.80" }
random = { source = "hashicorp/random", version = ">= 3.5" }
}
}
# ---------- Inputs ----------
variable "name" {
type = string
default = "webapp"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
description = "Private subnets in at least 2 AZs"
type = list(string)
}
variable "app_security_group_id" {
description = "Security group of the app tier (ECS/EKS/EC2/Lambda) that needs access"
type = string
}
variable "node_type" {
description = "cache.t4g.* is fine to launch; move to cache.r7g.* for steady production load"
type = string
default = "cache.t4g.medium"
}
variable "replicas" {
description = "Total nodes (1 primary + N-1 replicas). Must be >= 2 for Multi-AZ."
type = number
default = 2
}
# ---------- Networking ----------
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-cache"
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "cache" {
name = "${var.name}-cache"
description = "ElastiCache Valkey access"
vpc_id = var.vpc_id
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
security_group_id = aws_security_group.cache.id
referenced_security_group_id = var.app_security_group_id
ip_protocol = "tcp"
from_port = 6379
to_port = 6379
description = "Valkey from app tier"
}
# ---------- Auth ----------
resource "random_password" "auth" {
length = 64
special = false # AUTH tokens disallow some special chars; alphanumeric is safe
}
resource "aws_secretsmanager_secret" "cache_auth" {
name = "${var.name}/elasticache/auth-token"
}
resource "aws_secretsmanager_secret_version" "cache_auth" {
secret_id = aws_secretsmanager_secret.cache_auth.id
secret_string = jsonencode({
auth_token = random_password.auth.result
host = aws_elasticache_replication_group.this.primary_endpoint_address
port = 6379
tls = true
})
}
# ---------- Parameters ----------
resource "aws_elasticache_parameter_group" "this" {
name = "${var.name}-valkey8"
family = "valkey8"
# Evict only keys that have a TTL. Give sessions AND cache entries TTLs,
# and alarm on memory so you scale before evictions hit sessions.
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
}
# ---------- Logs ----------
resource "aws_cloudwatch_log_group" "slowlog" {
name = "/elasticache/${var.name}/slow-log"
retention_in_days = 14
}
# ---------- Cluster ----------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = "${var.name}-cache"
description = "${var.name} sessions + cache"
engine = "valkey"
engine_version = "8.0"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.this.name
num_cache_clusters = var.replicas
automatic_failover_enabled = true
multi_az_enabled = true
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.cache.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth.result
auth_token_update_strategy = "ROTATE"
snapshot_retention_limit = 3
snapshot_window = "03:00-04:00"
maintenance_window = "sun:05:00-sun:06:00"
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.slowlog.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
tags = {
Name = "${var.name}-cache"
}
}
# ---------- Alarms ----------
resource "aws_cloudwatch_metric_alarm" "memory" {
alarm_name = "${var.name}-cache-memory-high"
namespace = "AWS/ElastiCache"
metric_name = "DatabaseMemoryUsagePercentage"
statistic = "Maximum"
period = 300
evaluation_periods = 2
threshold = 75
comparison_operator = "GreaterThanThreshold"
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
}
resource "aws_cloudwatch_metric_alarm" "evictions" {
alarm_name = "${var.name}-cache-evictions"
namespace = "AWS/ElastiCache"
metric_name = "Evictions"
statistic = "Sum"
period = 300
evaluation_periods = 1
threshold = 0
comparison_operator = "GreaterThanThreshold"
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
}
# ---------- Outputs ----------
output "primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "auth_secret_arn" {
value = aws_secretsmanager_secret.cache_auth.arn
}
resource "aws_elasticache_serverless_cache" "this" {
name = "${var.name}-cache"
engine = "valkey"
major_engine_version = "8"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.cache.id]
cache_usage_limits {
data_storage {
maximum = 10
unit = "GB"
}
ecpu_per_second {
maximum = 5000
}
}
snapshot_retention_limit = 3
}
answer 5 Valkey9,910 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.80" # Valkey engine support
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
variable "name" {
description = "Name prefix for resources"
type = string
default = "webapp"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
description = "Private subnets in at least 2 AZs"
type = list(string)
}
variable "app_security_group_id" {
description = "Security group of the app tier (ECS/EC2/EKS/Lambda) allowed to connect"
type = string
}
variable "node_type" {
description = "cache.t4g.small for dev/low traffic; cache.r7g.large+ for production"
type = string
default = "cache.t4g.small"
}
variable "replica_count" {
description = "Number of read replicas (>=1 required for Multi-AZ failover)"
type = number
default = 1
}
variable "tags" {
type = map(string)
default = {}
}
locals {
port = 6379
}
# --- Networking ---------------------------------------------------------------
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-cache"
subnet_ids = var.private_subnet_ids
tags = var.tags
}
resource "aws_security_group" "cache" {
name = "${var.name}-cache"
description = "ElastiCache Valkey access"
vpc_id = var.vpc_id
tags = var.tags
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
security_group_id = aws_security_group.cache.id
referenced_security_group_id = var.app_security_group_id
from_port = local.port
to_port = local.port
ip_protocol = "tcp"
description = "Valkey from app tier"
}
# --- Auth token (stored in Secrets Manager) -----------------------------------
resource "random_password" "auth" {
length = 64
special = true
# ElastiCache AUTH disallows @, ", and /
override_special = "!&#$^<>-"
}
resource "aws_secretsmanager_secret" "auth" {
name = "${var.name}/elasticache/auth-token"
description = "AUTH token for ${var.name} Valkey"
tags = var.tags
}
resource "aws_secretsmanager_secret_version" "auth" {
secret_id = aws_secretsmanager_secret.auth.id
secret_string = jsonencode({
host = aws_elasticache_replication_group.this.primary_endpoint_address
port = local.port
password = random_password.auth.result
tls = true
})
}
# --- Parameters ---------------------------------------------------------------
resource "aws_elasticache_parameter_group" "this" {
name = "${var.name}-valkey8"
family = "valkey8"
# Only evict keys that have a TTL. Set TTLs on sessions and cache entries.
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
tags = var.tags
}
# --- Logs ---------------------------------------------------------------------
resource "aws_cloudwatch_log_group" "slow" {
name = "/elasticache/${var.name}/slow-log"
retention_in_days = 14
tags = var.tags
}
resource "aws_cloudwatch_log_group" "engine" {
name = "/elasticache/${var.name}/engine-log"
retention_in_days = 14
tags = var.tags
}
# --- Cluster ------------------------------------------------------------------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = "${var.name}-cache"
description = "${var.name} sessions and cache"
engine = "valkey"
engine_version = "8.0"
node_type = var.node_type
port = local.port
parameter_group_name = aws_elasticache_parameter_group.this.name
# 1 primary + N replicas, cluster mode disabled
num_cache_clusters = 1 + var.replica_count
automatic_failover_enabled = var.replica_count > 0
multi_az_enabled = var.replica_count > 0
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.cache.id]
# Security
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth.result
auth_token_update_strategy = "ROTATE"
# Backups & maintenance
snapshot_retention_limit = 3
snapshot_window = "03:00-04:00"
maintenance_window = "sun:05:00-sun:06:00"
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.slow.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
log_delivery_configuration {
destination = aws_cloudwatch_log_group.engine.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "engine-log"
}
tags = var.tags
lifecycle {
# Prevent accidental replacement on auth token regeneration
ignore_changes = [auth_token]
}
}
# --- Basic alarms -------------------------------------------------------------
resource "aws_cloudwatch_metric_alarm" "memory" {
alarm_name = "${var.name}-cache-memory-high"
namespace = "AWS/ElastiCache"
metric_name = "DatabaseMemoryUsagePercentage"
statistic = "Average"
period = 300
evaluation_periods = 3
threshold = 80
comparison_operator = "GreaterThanThreshold"
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
tags = var.tags
}
resource "aws_cloudwatch_metric_alarm" "cpu" {
alarm_name = "${var.name}-cache-engine-cpu-high"
namespace = "AWS/ElastiCache"
metric_name = "EngineCPUUtilization"
statistic = "Average"
period = 300
evaluation_periods = 3
threshold = 75
comparison_operator = "GreaterThanThreshold"
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
tags = var.tags
}
output "primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "port" {
value = local.port
}
output "auth_secret_arn" {
description = "Grant your app role secretsmanager:GetSecretValue on this"
value = aws_secretsmanager_secret.auth.arn
}
output "security_group_id" {
value = aws_security_group.cache.id
}
module "cache" {
source = "./modules/cache"
name = "webapp-prod"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnets
app_security_group_id = aws_security_group.app.id
node_type = "cache.r7g.large"
replica_count = 1
tags = { env = "prod" }
}
GPT-6 Sol · Valkey in 5 of 5
answer 1 Valkey2,095 characters
counted from its code (a container image, a Terraform engine or an install line)
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
variable "cache_auth_token" {
type = string
sensitive = true
}
resource "aws_security_group" "cache" {
name = "webapp-cache"
description = "Allow the web app to reach ElastiCache"
vpc_id = var.vpc_id
ingress {
description = "Valkey from web app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
}
resource "aws_elasticache_subnet_group" "cache" {
name = "webapp-cache"
subnet_ids = var.private_subnet_ids
}
resource "aws_elasticache_replication_group" "cache" {
replication_group_id = "webapp-cache"
description = "Sessions and application cache"
engine = "valkey"
engine_version = "7.2"
node_type = "cache.t4g.small"
port = 6379
num_cache_clusters = 2
automatic_failover_enabled = true
multi_az_enabled = true
subnet_group_name = aws_elasticache_subnet_group.cache.name
security_group_ids = [aws_security_group.cache.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = var.cache_auth_token
}
output "cache_primary_endpoint" {
value = aws_elasticache_replication_group.cache.primary_endpoint_address
}
answer 2 Valkey2,681 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
variable "cache_password" {
type = string
sensitive = true
}
resource "aws_security_group" "cache" {
name_prefix = "web-cache-"
description = "Valkey access from the web app"
vpc_id = var.vpc_id
# ElastiCache Serverless uses both ports.
ingress {
description = "Valkey from app"
from_port = 6379
to_port = 6380
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
}
# A user group needs a default user; disable its access.
resource "aws_elasticache_user" "default" {
user_id = "web-cache-default"
user_name = "default"
engine = "VALKEY"
access_string = "off ~* -@all"
authentication_mode {
type = "no-password"
}
}
resource "aws_elasticache_user" "app" {
user_id = "web-cache-app"
user_name = "web-cache-app"
engine = "VALKEY"
access_string = "on ~* +@all"
authentication_mode {
type = "password"
passwords = [var.cache_password]
}
}
resource "aws_elasticache_user_group" "app" {
user_group_id = "web-cache-users"
engine = "VALKEY"
user_ids = [
aws_elasticache_user.default.user_id,
aws_elasticache_user.app.user_id,
]
}
resource "aws_elasticache_serverless_cache" "app" {
name = "web-cache"
engine = "valkey"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.cache.id]
user_group_id = aws_elasticache_user_group.app.user_group_id]
}
output "cache_host" {
value = aws_elasticache_serverless_cache.app.endpoint[0].address
}
output "cache_port" {
value = aws_elasticache_serverless_cache.app.endpoint[0].port
}
answer 3 Valkey1,550 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80"
}
}
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
resource "aws_security_group" "valkey" {
name_prefix = "web-valkey-"
description = "Valkey access from the web app"
vpc_id = var.vpc_id
ingress {
description = "Valkey from web app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
}
resource "aws_elasticache_serverless_cache" "web" {
name = "web-sessions-cache"
engine = "valkey"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.valkey.id]
cache_usage_limits {
data_storage {
maximum = 10
unit = "GB"
}
ecpu_per_second {
maximum = 5000
}
}
}
output "valkey_endpoint" {
value = aws_elasticache_serverless_cache.web.endpoint
}
answer 4 Valkey1,869 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0"
}
}
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
description = "At least two private subnets in different Availability Zones."
}
variable "app_security_group_id" {
type = string
description = "Security group attached to the web app."
}
resource "aws_security_group" "valkey" {
name_prefix = "app-valkey-"
description = "Allow the web app to reach ElastiCache Serverless"
vpc_id = var.vpc_id
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
security_group_id = aws_security_group.valkey.id
referenced_security_group_id = var.app_security_group_id
ip_protocol = "tcp"
from_port = 6379
to_port = 6380
description = "Valkey serverless client connections"
}
resource "aws_elasticache_serverless_cache" "app" {
name = "app-sessions-cache"
engine = "valkey"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.valkey.id]
}
output "valkey_endpoint" {
value = aws_elasticache_serverless_cache.app.cache_endpoint[0].address
}
output "valkey_port" {
value = aws_elasticache_serverless_cache.app.cache_endpoint[0].port
}
answer 5 Valkey3,248 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80"
}
random = {
source = "hashicorp/random"
version = ">= 3.6"
}
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
type = string
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
description = "Private subnets in at least two Availability Zones."
}
variable "app_security_group_id" {
type = string
description = "Security group attached to the application."
}
resource "aws_elasticache_subnet_group" "app" {
name = "app-valkey"
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "valkey" {
name = "app-valkey"
description = "Access to the application Valkey cache"
vpc_id = var.vpc_id
ingress {
description = "Application access"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
ingress {
description = "Communication between cache nodes"
from_port = 6379
to_port = 6379
protocol = "tcp"
self = true
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "random_password" "valkey" {
length = 40
special = false
}
resource "aws_secretsmanager_secret" "valkey" {
name = "app/valkey/auth-token"
}
resource "aws_secretsmanager_secret_version" "valkey" {
secret_id = aws_secretsmanager_secret.valkey.id
secret_string = random_password.valkey.result
}
resource "aws_elasticache_replication_group" "app" {
replication_group_id = "app-valkey"
description = "Sessions and cache for the web app"
engine = "valkey"
engine_version = "7.2"
node_type = "cache.t4g.small"
num_cache_clusters = 2
port = 6379
subnet_group_name = aws_elasticache_subnet_group.app.name
security_group_ids = [aws_security_group.valkey.id]
automatic_failover_enabled = true
multi_az_enabled = true
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.valkey.result
}
output "valkey_primary_endpoint" {
value = aws_elasticache_replication_group.app.primary_endpoint_address
}
output "valkey_auth_secret_arn" {
value = aws_secretsmanager_secret.valkey.arn
}
DeepSeek V4.1 Flash · Valkey in 4 of 5
answer 1 Valkey6,398 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.60.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
type = string
description = "AWS region"
}
variable "name" {
type = string
description = "Name prefix for resources"
}
variable "vpc_id" {
type = string
description = "VPC ID"
}
variable "private_subnet_ids" {
type = list(string)
description = "Private subnet IDs for ElastiCache"
}
variable "app_security_group_id" {
type = string
description = "Security group ID of the web app that needs Redis/Valkey access"
}
variable "node_type" {
type = string
default = "cache.m7g.large"
description = "ElastiCache node type. Scale up for more memory/CPU."
}
variable "tags" {
type = map(string)
default = {}
description = "Tags to apply"
}
# --------------------------------------------------------------------
# AUTH token stored in Secrets Manager
# --------------------------------------------------------------------
resource "random_password" "redis_auth" {
length = 64
special = false
}
resource "aws_secretsmanager_secret" "redis_auth" {
name = "${var.name}/redis/auth"
tags = var.tags
}
resource "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = aws_secretsmanager_secret.redis_auth.id
secret_string = random_password.redis_auth.result
}
# --------------------------------------------------------------------
# Networking
# --------------------------------------------------------------------
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-cache-subnets"
subnet_ids = var.private_subnet_ids
tags = var.tags
}
resource "aws_security_group" "redis" {
name_prefix = "${var.name}-redis-"
description = "ElastiCache Valkey access"
vpc_id = var.vpc_id
ingress {
description = "Valkey from web app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = var.tags
}
# --------------------------------------------------------------------
# Parameter group
# volatile-lru: evict only keys with TTL, so cache keys expire but
# session keys without TTL are protected. Set TTLs on cache keys.
# --------------------------------------------------------------------
resource "aws_elasticache_parameter_group" "this" {
name = "${var.name}-cache-params"
family = "valkey7"
description = "Valkey parameter group for sessions and cache"
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
parameter {
name = "tcp-keepalive"
value = "300"
}
tags = var.tags
}
# --------------------------------------------------------------------
# Logging
# --------------------------------------------------------------------
resource "aws_cloudwatch_log_group" "redis_slow" {
name = "/aws/elasticache/${var.name}/slow-log"
retention_in_days = 30
tags = var.tags
}
# --------------------------------------------------------------------
# ElastiCache for Valkey: 1 primary + 1 replica, Multi-AZ failover
# --------------------------------------------------------------------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = "${var.name}-cache"
description = "Managed Valkey for sessions and caching"
engine = "valkey"
engine_version = "7.2"
node_type = var.node_type
num_cache_clusters = 2
parameter_group_name = aws_elasticache_parameter_group.this.name
automatic_failover_enabled = true
multi_az_enabled = true
at_rest_encryption_enabled = true
transit_encryption_enabled = true
transit_encryption_mode = "required"
auth_token = random_password.redis_auth.result
auth_token_update_strategy = "ROTATE"
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.redis.id]
snapshot_retention_limit = 7
snapshot_window = "03:00-04:00"
maintenance_window = "sun:05:00-sun:06:00"
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.redis_slow.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
tags = var.tags
}
# --------------------------------------------------------------------
# Outputs
# --------------------------------------------------------------------
output "redis_primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
description = "Primary endpoint for writes/reads"
}
output "redis_reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
description = "Reader endpoint for read scaling"
}
output "redis_auth_secret_arn" {
value = aws_secretsmanager_secret.redis_auth.arn
description = "Secrets Manager ARN containing the AUTH token"
}
answer 2 Valkey4,366 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
variable "environment" {
type = string
default = "prod"
}
variable "node_type" {
type = string
default = "cache.m7g.large"
}
resource "random_password" "redis_auth_token" {
length = 32
special = false
}
resource "aws_secretsmanager_secret" "redis_auth_token" {
name = "${var.environment}/redis/auth-token"
}
resource "aws_secretsmanager_secret_version" "redis_auth_token" {
secret_id = aws_secretsmanager_secret.redis_auth_token.id
secret_string = random_password.redis_auth_token.result
}
resource "aws_elasticache_subnet_group" "main" {
name = lower("${var.environment}-redis-subnets")
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "redis" {
name_prefix = lower("${var.environment}-redis-")
description = "Allow app access to ElastiCache Valkey"
vpc_id = var.vpc_id
ingress {
description = "Valkey from web app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.environment}-redis"
Environment = var.environment
}
}
resource "aws_elasticache_parameter_group" "main" {
name = lower("${var.environment}-valkey7")
family = "valkey7"
parameter {
name = "maxmemory-policy"
value = "volatile-lru" # evicts keys with TTL; set TTLs on cache/session keys
}
tags = {
Environment = var.environment
}
}
resource "aws_elasticache_replication_group" "main" {
replication_group_id = lower("${var.environment}-app")
description = "Valkey for web app sessions and cache"
engine = "valkey"
engine_version = "7.2"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.main.name
num_cache_clusters = 2
automatic_failover_enabled = true
multi_az_enabled = true
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.redis.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.redis_auth_token.result
auth_token_update_strategy = "ROTATE"
apply_immediately = false
maintenance_window = "sun:05:00-sun:06:00"
snapshot_window = "03:00-04:00"
snapshot_retention_limit = 7
tags = {
Environment = var.environment
Application = "web-app"
}
}
output "redis_primary_endpoint" {
value = aws_elasticache_replication_group.main.primary_endpoint_address
}
output "redis_reader_endpoint" {
value = aws_elasticache_replication_group.main.reader_endpoint_address
}
output "redis_auth_token_secret_arn" {
value = aws_secretsmanager_secret.redis_auth_token.arn
}
answer 3 Redis4,452 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
type = string
}
variable "name" {
type = string
default = "webapp"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
variable "node_type" {
type = string
default = "cache.t4g.medium"
}
variable "num_cache_clusters" {
type = number
default = 2
}
locals {
name = replace(var.name, "_", "-")
}
resource "random_password" "redis_auth" {
length = 32
special = false
upper = true
lower = true
numeric = true
}
resource "aws_secretsmanager_secret" "redis_auth" {
name = "${local.name}/redis/auth-token"
}
resource "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = aws_secretsmanager_secret.redis_auth.id
secret_string = random_password.redis_auth.result
}
resource "aws_security_group" "redis" {
name_prefix = "${local.name}-redis-"
description = "ElastiCache Redis for ${local.name}"
vpc_id = var.vpc_id
ingress {
description = "Redis from app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${local.name}-redis"
}
}
resource "aws_elasticache_subnet_group" "redis" {
name = "${local.name}-redis"
subnet_ids = var.private_subnet_ids
}
resource "aws_elasticache_parameter_group" "redis" {
name = "${local.name}-redis7"
family = "redis7"
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
}
resource "aws_elasticache_replication_group" "redis" {
replication_group_id = substr("${local.name}-redis", 0, 40)
description = "Redis for ${local.name} sessions and cache"
engine = "redis"
engine_version = "7.1"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.redis.name
subnet_group_name = aws_elasticache_subnet_group.redis.name
security_group_ids = [aws_security_group.redis.id]
automatic_failover_enabled = true
multi_az_enabled = true
num_cache_clusters = var.num_cache_clusters
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.redis_auth.result
snapshot_retention_limit = 7
snapshot_window = "03:00-05:00"
maintenance_window = "sun:05:00-sun:07:00"
apply_immediately = false
auto_minor_version_upgrade = true
tags = {
Name = "${local.name}-redis"
}
}
output "redis_primary_endpoint" {
value = aws_elasticache_replication_group.redis.primary_endpoint_address
}
output "redis_reader_endpoint" {
value = aws_elasticache_replication_group.redis.reader_endpoint_address
}
output "redis_auth_secret_arn" {
value = aws_secretsmanager_secret.redis_auth.arn
}
output "redis_security_group_id" {
value = aws_security_group.redis.id
}
rediss://default:<auth_token>@<primary_endpoint>:6379/0
answer 4 Valkey4,472 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.60.0" # Valkey support; older providers may need engine = "redis"
}
}
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
variable "auth_token" {
type = string
sensitive = true
# Must be 16-128 printable chars
}
variable "environment" {
type = string
default = "prod"
}
variable "node_type" {
type = string
default = "cache.t4g.small"
}
variable "num_cache_clusters" {
type = number
default = 2 # 1 primary + 1 replica; use 3 for prod if you want 2 replicas
}
resource "aws_elasticache_subnet_group" "main" {
name = "app-${var.environment}-valkey-subnets"
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "valkey" {
name = "app-${var.environment}-valkey-sg"
description = "ElastiCache Valkey access from app"
vpc_id = var.vpc_id
ingress {
description = "Valkey from app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
description = "Outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "app-${var.environment}-valkey-sg"
}
}
resource "aws_elasticache_parameter_group" "main" {
name = "app-${var.environment}-valkey7"
family = "valkey7"
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
}
resource "aws_cloudwatch_log_group" "valkey_slow" {
name = "/aws/elasticache/app-${var.environment}-valkey/slow-log"
retention_in_days = 30
}
resource "aws_elasticache_replication_group" "main" {
replication_group_id = "app-${var.environment}-valkey"
description = "Valkey for app sessions and cache"
engine = "valkey"
engine_version = "7.2"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.main.name
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.valkey.id]
automatic_failover_enabled = true
multi_az_enabled = true
num_cache_clusters = var.num_cache_clusters
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = var.auth_token
snapshot_retention_limit = 7
snapshot_window = "03:00-05:00"
maintenance_window = "sun:05:00-sun:07:00"
auto_minor_version_upgrade = true
apply_immediately = false
log_delivery_configuration {
destination = aws_cloudwatch_log_group.valkey_slow.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
tags = {
Name = "app-${var.environment}-valkey"
}
}
output "valkey_primary_endpoint" {
value = aws_elasticache_replication_group.main.primary_endpoint_address
}
output "valkey_reader_endpoint" {
value = aws_elasticache_replication_group.main.reader_endpoint_address
}
output "valkey_port" {
value = aws_elasticache_replication_group.main.port
}
answer 5 Valkey3,903 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.70"
}
}
}
variable "vpc_id" {
description = "VPC where ElastiCache will run"
type = string
}
variable "private_subnet_ids" {
description = "At least 2 private subnet IDs in different AZs"
type = list(string)
}
variable "app_security_group_id" {
description = "Security group ID of the web app that needs Redis access"
type = string
}
variable "redis_auth_token" {
description = "AUTH token for ElastiCache (16-128 chars). Store in Secrets Manager/SSM."
type = string
sensitive = true
}
variable "replication_group_id" {
description = "Name/ID for the ElastiCache replication group"
type = string
default = "webapp-sessions-cache"
}
variable "node_type" {
description = "ElastiCache node type"
type = string
default = "cache.m7g.large"
}
resource "aws_elasticache_subnet_group" "this" {
name = "${var.replication_group_id}-subnets"
subnet_ids = var.private_subnet_ids
tags = {
Name = "${var.replication_group_id}-subnets"
}
}
resource "aws_security_group" "redis" {
name = "${var.replication_group_id}-sg"
description = "Allow Redis/Valkey access from the web app"
vpc_id = var.vpc_id
ingress {
description = "Redis from web app"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
description = "Default outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.replication_group_id}-sg"
}
}
resource "aws_elasticache_replication_group" "this" {
replication_group_id = var.replication_group_id
description = "ElastiCache Valkey for web app sessions and caching"
engine = "valkey"
engine_version = "7.2"
node_type = var.node_type
port = 6379
num_cache_clusters = 2
automatic_failover_enabled = true
multi_az_enabled = true
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.redis.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = var.redis_auth_token
snapshot_retention_limit = 7
snapshot_window = "03:00-04:00"
maintenance_window = "sun:05:00-sun:06:00"
apply_immediately = false
tags = {
Name = var.replication_group_id
}
}
output "primary_endpoint" {
description = "Primary endpoint for writes/reads"
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
description = "Reader endpoint for read scaling"
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "port" {
description = "Redis/Valkey port"
value = aws_elasticache_replication_group.this.port
}
GLM-5.3-Flash · Valkey in 3 of 5
answer 1 Redis4,827 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
variable "name" {
type = string
default = "app-sessions"
# must be lowercase letters, numbers, hyphens
}
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string) } # private subnets, >=2 AZs
variable "app_security_group_id" { type = string }
variable "node_type" { type = string, default = "cache.r7g.large" } # t4g.small for dev
variable "engine" { type = string, default = "redis" } # or "valkey"
variable "engine_version" { type = string, default = "7.1" } # valkey: "7.2"
resource "random_password" "auth_token" {
length = 40
special = false # alphanumeric; avoids client escaping issues
}
resource "aws_security_group" "redis" {
name_prefix = "${var.name}-redis-"
vpc_id = var.vpc_id
ingress {
description = "Redis from app tier only"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-subnets"
subnet_ids = var.subnet_ids
}
resource "aws_elasticache_parameter_group" "this" {
family = var.engine == "valkey" ? "valkey7" : "redis7"
name = "${var.name}-params"
parameter {
name = "maxmemory-policy"
value = "allkeys-lru" # evict LRU keys under memory pressure
}
}
resource "aws_elasticache_replication_group" "this" {
replication_group_id = var.name
description = "User sessions + cache"
engine = var.engine
engine_version = var.engine_version
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.this.name
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.redis.id]
num_cache_clusters = 2 # primary + cross-AZ replica
automatic_failover_enabled = true
multi_az_enabled = true
at_rest_encryption_enabled = true
# kms_key_id = aws_kms_key.this.arn # optional CMK; default AWS-managed key otherwise
transit_encryption_enabled = true
auth_token = random_password.auth_token.result
snapshot_retention_limit = 7 # cheap insurance against a bad FLUSHALL
snapshot_window = "03:00-05:00"
maintenance_window = "sun:05:00-sun:07:00"
auto_minor_version_upgrade = true
}
output "primary_endpoint" {
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.this.reader_endpoint_address
}
output "auth_token" {
value = random_password.auth_token.result
sensitive = true
}
answer 2 Valkey4,734 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
random = { source = "hashicorp/random", version = "~> 3.0" }
}
}
variable "project" { type = string }
variable "vpc_id" { type = string }
variable "private_subnet_ids" { type = list(string) } # ≥2 subnets, different AZs
variable "app_security_group_id" { type = string } # your app tier's SG
# ElastiCache auth tokens reject most symbols, so keep it alphanumeric
resource "random_password" "cache_auth" {
length = 64
special = false
}
resource "aws_security_group" "cache" {
name_prefix = "${var.project}-cache-"
vpc_id = var.vpc_id
ingress {
description = "Redis from app tier only"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_elasticache_subnet_group" "main" {
name = "${var.project}-cache-subnets"
subnet_ids = var.private_subnet_ids
}
# volatile-lru: only evicts keys that have a TTL. Set a TTL on everything
# (cache keys = freshness window, session keys = session lifetime) and you
# get LRU caching behavior without sessions being evicted before expiry.
resource "aws_elasticache_parameter_group" "main" {
family = "valkey7" # use "redis7" if engine = "redis"
name = "${var.project}-cache-params"
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
}
resource "aws_elasticache_replication_group" "main" {
replication_group_id = "${var.project}-cache"
description = "User sessions and cache"
engine = "valkey" # swap to "redis" (version "7.1") if preferred
engine_version = "7.2"
node_type = "cache.r7g.large" # ~13 GiB; Graviton, best price/perf
port = 6379
parameter_group_name = aws_elasticache_parameter_group.main.name
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.cache.id]
# Single shard + 1 cross-AZ replica. Cluster mode stays off — simpler
# client config; only enable it if you outgrow one node (~tens of GB).
num_node_groups = 1
replicas_per_node_group = 1
automatic_failover_enabled = true
multi_az_enabled = true
at_rest_encryption_enabled = true
transit_encryption_enabled = true # required when using auth_token
auth_token = random_password.cache_auth.result
snapshot_retention_limit = 5
}
output "endpoint" {
value = aws_elasticache_replication_group.main.primary_endpoint_address
}
output "port" {
value = 6379
}
output "auth_token" {
value = random_password.cache_auth.result
sensitive = true
}
answer 3 Valkey9,181 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
provider "aws" {
region = var.aws_region
default_tags { tags = var.tags }
}
# ── Variables ────────────────────────────────────────────────
variable "name" { type = string, default = "app-cache" }
variable "aws_region" { type = string }
variable "vpc_id" { type = string } # required
variable "subnet_ids" { type = list(string) } # required, must span ≥2 AZs
variable "app_security_group_ids" { type = list(string) } # required: SGs allowed to connect
variable "node_type" { type = string, default = "cache.r7g.large" }
variable "engine_version" { type = string, default = "7.2" }
variable "num_cache_clusters" { type = number, default = 2 } # 1 primary + 1 replica
variable "snapshot_retention_limit" { type = number, default = 3 } # 0 = treat as pure cache
variable "maintenance_window" { type = string, default = "sun:04:30-sun:05:30" }
variable "tags" { type = map(string), default = {} }
# ── Networking ───────────────────────────────────────────────
resource "aws_security_group" "this" {
name = "${var.name}-redis"
description = "ElastiCache node access"
vpc_id = var.vpc_id
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
for_each = toset(var.app_security_group_ids)
security_group_id = aws_security_group.this.id
referenced_security_group_id = each.value
ip_protocol = "tcp"
from_port = 6379
to_port = 6379
description = "Valkey from app tier"
}
# Node-to-node replication traffic (self-reference)
resource "aws_vpc_security_group_ingress_rule" "self" {
security_group_id = aws_security_group.this.id
referenced_security_group_id = aws_security_group.this.id
ip_protocol = "tcp"
from_port = 6379
to_port = 6379
}
resource "aws_vpc_security_group_egress_rule" "self" {
security_group_id = aws_security_group.this.id
referenced_security_group_id = aws_security_group.this.id
ip_protocol = "tcp"
from_port = 6379
to_port = 6379
}
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-subnets"
subnet_ids = var.subnet_ids
}
# ── Auth token (in addition to network + TLS) ────────────────
resource "random_password" "auth_token" {
length = 48
special = false # auth token charset is restrictive; alphanumeric is safe
}
resource "aws_secretsmanager_secret" "auth" {
name = "${var.name}/elasticache-auth-token"
recovery_window_in_days = 7
}
resource "aws_secretsmanager_secret_version" "auth" {
secret_id = aws_secretsmanager_secret.auth.id
secret_string = random_password.auth_token.result
}
# ── Cluster ──────────────────────────────────────────────────
resource "aws_elasticache_parameter_group" "this" {
family = "valkey7" # use "redis7" if engine = "redis"
name = "${var.name}-params"
}
# One per AWS account; remove if ElastiCache is already used in this account
resource "aws_iam_service_linked_role" "elasticache" {
aws_service_name = "elasticache.amazonaws.com"
}
resource "aws_cloudwatch_log_group" "slow_log" {
name = "/aws/elasticache/${var.name}/slow-log"
retention_in_days = 14
}
resource "aws_cloudwatch_log_group" "engine_log" {
name = "/aws/elasticache/${var.name}/engine-log"
retention_in_days = 14
}
resource "aws_elasticache_replication_group" "this" {
replication_group_id = var.name
description = "User sessions and cache"
engine = "valkey"
engine_version = var.engine_version
node_type = var.node_type
num_cache_clusters = var.num_cache_clusters
port = 6379
parameter_group_name = aws_elasticache_parameter_group.this.name
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.this.id]
auth_token = random_password.auth_token.result
automatic_failover_enabled = true
multi_az_enabled = true
transit_encryption_enabled = true # clients MUST use TLS (rediss://)
at_rest_encryption_enabled = true
maintenance_window = var.maintenance_window
snapshot_window = "01:00-04:00"
snapshot_retention_limit = var.snapshot_retention_limit
auto_minor_version_upgrade = true
apply_immediately = false # schedule changes in the maintenance window
log_delivery_configuration {
destination = aws_cloudwatch_log_group.slow_log.name
destination_type = "cloudwatch-logs"
log_format = "text"
log_type = "slow-log"
}
log_delivery_configuration {
destination = aws_cloudwatch_log_group.engine_log.name
destination_type = "cloudwatch-logs"
log_format = "text"
log_type = "engine-log"
}
depends_on = [aws_iam_service_linked_role.elasticache]
}
# ── Alarms ───────────────────────────────────────────────────
locals {
cluster_ids = toset([for i in range(var.num_cache_clusters) : format("%s-%03d", aws_elasticache_replication_group.this.id, i)])
}
resource "aws_cloudwatch_metric_alarm" "cpu" {
for_each = local.cluster_ids
alarm_name = "${each.key}-cpu-high"
namespace = "AWS/ElastiCache"
metric_name = "CPUUtilization"
statistic = "Average"
comparison_operator = "GreaterThanThreshold"
threshold = 75
evaluation_periods = 3
period = 300
dimensions = { CacheClusterId = each.key }
alarm_actions = [] # wire your SNS/PagerDuty topic here
}
resource "aws_cloudwatch_metric_alarm" "memory" {
for_each = local.cluster_ids
alarm_name = "${each.key}-memory-high"
namespace = "AWS/ElastiCache"
metric_name = "DatabaseMemoryUsagePercentage"
statistic = "Average"
comparison_operator = "GreaterThanThreshold"
threshold = 90
evaluation_periods = 3
period = 300
dimensions = { CacheClusterId = each.key }
alarm_actions = []
}
resource "aws_cloudwatch_metric_alarm" "evictions" {
for_each = local.cluster_ids
alarm_name = "${each.key}-evictions"
namespace = "AWS/ElastiCache"
metric_name = "Evictions"
statistic = "Sum"
comparison_operator = "GreaterThanThreshold"
threshold = 0
evaluation_periods = 1
period = 300
dimensions = { CacheClusterId = each.key }
alarm_actions = []
}
# ── Outputs ──────────────────────────────────────────────────
output "primary_endpoint" { value = aws_elasticache_replication_group.this.primary_endpoint_address }
output "reader_endpoint" { value = aws_elasticache_replication_group.this.reader_endpoint_address }
output "auth_token_secret_arn" { value = aws_secretsmanager_secret.auth.arn }
answer 4 Valkey3,600 characters
coded by hand: corrected 2026-10-06 after an outside review: v1 read Redis from the words 'Redis-compatible engine' in 'Pick: Amazon ElastiCache with a Redis-compatible engine (I'd default to Valkey ...)'; the answer is cut off before its Terraform sets an engine, and its security groups are labelled for Valkey clients
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.60"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
# provider "aws" { region = "us-east-1" } # or via env/workspace config
variable "name" {
description = "Project prefix (lowercase letters, digits, hyphens)"
type = string
default = "acme-web"
}
variable "vpc_id" {
description = "VPC where the app runs"
type = string
}
variable "private_subnet_ids" {
description = "Private app subnets — must span >= 2 AZs (required for Multi-AZ)"
type = list(string)
}
variable "app_security_group_id" {
description = "SG of the app tier (EC2/ECS/EKS) — only this can reach the cache"
type = string
}
variable "node_type" {
type = string
default = "cache.t4g.medium" # ~3 GB Graviton; use cache.m7g/r7g.* for steady prod load
}
locals {
rg_id = "${var.name}-cache"
}
# ----------------------------- Network -----------------------------
resource "aws_elasticache_subnet_group" "this" {
name = "${local.rg_id}-subnets"
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "this" {
name_prefix = "${local.rg_id}-"
description = "Valkey clients (app tier only)"
vpc_id = var.vpc_id
ingress {
description = "Valkey/Redis from the app tier"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
ingress {
description = "Node-to-node replication / failover"
from_port = 6379
to_port = 6379
protocol = "tcp"
self = true
}
egress {
description = "Node-to-node replication / failover"
from_port = 6379
to_port = 6379
protocol = "tcp"
self = true
}
}
# ------------------------- Auth + secret ---------------------------
# ElastiCache AUTH tokens restrict the allowed character set
resource "random_password" "auth_token" {
length = 64
special = true
override_special = "!&#$^<>-_"
}
resource "aws_secretsmanager_secret" "this" {
name = "elasticache/${local.rg_id}"
description = "ElastiCache endpoint + auth token"
}
resource "aws_secretsmanager_secret_version" "this" {
secret_id = aws_secretsmanager_secret.this.id
secret_string = jsonencode({
host = aws_elasticache_replication_group.this.primary_endpoint_address
port = 6379
auth_token = random_password.auth_token.result
# TLS is mandatory — note the "s" in rediss://
url = "rediss://answer 5 Redis7,665 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.0"
}
}
}
variable "name" {
description = "Cluster name: lowercase letters/digits/hyphens, starts with a letter, <= 40 chars"
type = string
default = "webapp-cache"
}
variable "engine" {
description = "\"redis\" or \"valkey\" (wire-compatible, license-clean, ~20% cheaper)"
type = string
default = "redis"
}
variable "engine_version" {
type = string
default = "7.1" # for valkey use "7.2" or "8.x"
}
variable "node_type" {
type = string
default = "cache.t4g.small" # ~1.4 GB; bump to t4g.medium / m7g.large as needed
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
description = "Private subnets, ideally across 2+ AZs"
type = list(string)
}
variable "app_security_group_id" {
description = "SG of your app tier - only this can reach Redis"
type = string
}
variable "alarm_actions" {
description = "SNS topic ARN(s) for CloudWatch alarms"
type = list(string)
default = []
}
# ---------- Auth token, stored in Secrets Manager ----------
resource "random_password" "auth_token" {
length = 64
special = false # ElastiCache AUTH tokens restrict special chars; alphanum is safe
}
resource "aws_secretsmanager_secret" "redis_auth" {
name = "elasticache/${var.name}-auth"
recovery_window_in_days = 7
}
resource "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = aws_secretsmanager_secret.redis_auth.id
secret_string = jsonencode({
url = "rediss://default:${random_password.auth_token.result}@${aws_elasticache_replication_group.this.primary_endpoint_address}:6379"
host = aws_elasticache_replication_group.this.primary_endpoint_address
port = 6379
auth_token = random_password.auth_token.result
})
}
# ---------- Network access ----------
resource "aws_security_group" "redis" {
name_prefix = "${var.name}-redis-"
description = "Redis ingress from app tier only"
vpc_id = var.vpc_id
ingress {
description = "Redis (TLS) from app tier"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
}
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-subnets"
subnet_ids = var.private_subnet_ids
}
# ---------- Engine settings ----------
resource "aws_elasticache_parameter_group" "this" {
name = "${var.name}-params"
family = "redis7.1" # use "valkey7.2" if you switch engine to valkey
description = "Session/cache tuning"
# Evict least-recently-used keys under memory pressure so writes keep
# succeeding. Sessions carry TTLs and expire on their own; if losing an
# active session hurts more than a cache miss, consider volatile-ttl
# or a separate cluster for sessions.
parameter {
name = "maxmemory-policy"
value = "allkeys-lru"
}
}
# ---------- The cluster: 1 primary + 1 replica, Multi-AZ ----------
resource "aws_elasticache_replication_group" "this" {
replication_group_id = var.name
description = "User sessions + application cache"
engine = var.engine
engine_version = var.engine_version
node_type = var.node_type
parameter_group_name = aws_elasticache_parameter_group.this.name
num_cache_clusters = 2
automatic_failover_enabled = true
multi_az_enabled = true
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.redis.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth_token.result
snapshot_retention_limit = 3
snapshot_window = "03:00-05:00"
maintenance_window = "sun:05:30-sun:07:30"
auto_minor_version_upgrade = true
tags = { Name = var.name }
}
# ---------- Alarms ----------
resource "aws_cloudwatch_metric_alarm" "cpu" {
alarm_name = "${var.name}-cpu-high"
alarm_description = "Redis CPU > 75% for 15 min - consider a larger node type"
namespace = "AWS/ElastiCache"
metric_name = "CPUUtilization"
statistic = "Average"
comparison_operator = "GreaterThanThreshold"
threshold = 75
period = 300
evaluation_periods = 3
treat_missing_data = "notBreaching"
alarm_actions = var.alarm_actions
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
}
resource "aws_cloudwatch_metric_alarm" "memory" {
alarm_name = "${var.name}-memory-high"
alarm_description = "Redis > 90% of maxmemory - scale up before evictions bite"
namespace = "AWS/ElastiCache"
metric_name = "DatabaseMemoryUsagePercentage"
statistic = "Maximum"
comparison_operator = "GreaterThanThreshold"
threshold = 90
period = 300
evaluation_periods = 3
treat_missing_data = "notBreaching"
alarm_actions = var.alarm_actions
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.this.id
}
}
# ---------- Outputs ----------
output "primary_endpoint" {
description = "Connect on 6379 with TLS (rediss://)"
value = aws_elasticache_replication_group.this.primary_endpoint_address
}
output "auth_secret_arn" {
description = "Secrets Manager secret holding host/port/auth token/connection URL"
value = aws_secretsmanager_secret.redis_auth.arn
}
MiMo-V2.6-Flash · Valkey in 0 of 5
answer 1 Redis8,461 characters
counted from its code (a container image, a Terraform engine or an install line)
# ──────────────────────────────────────────────────────────────
# versions.tf
# ──────────────────────────────────────────────────────────────
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
}
}
provider "aws" {
region = var.aws_region
}
# ──────────────────────────────────────────────────────────────
# variables.tf
# ──────────────────────────────────────────────────────────────
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "name" {
type = string
default = "webapp"
}
variable "vpc_id" {
type = string
}
# Must be PRIVATE subnets. ElastiCache has no public endpoint.
variable "private_subnet_ids" {
type = list(string)
}
# SGs of whatever talks to the cache: ECS task SG, EKS node SG,
# EC2 ASG SG, or Lambda ENI SG. Referencing SGs (not CIDRs) keeps
# port 6379 closed to everything else in the VPC.
variable "app_security_group_ids" {
type = list(string)
}
variable "engine" {
type = string
default = "redis" # "valkey" is also supported and ~17% cheaper
}
# Sustained ceilings. Serverless scales within these; exceeding them
# throttles rather than fails, which is what you want for a session store.
variable "max_data_storage_gb" {
type = number
default = 50
}
variable "max_ecpu_per_second" {
type = number
default = 5_000_000
}
# ──────────────────────────────────────────────────────────────
# main.tf
# ──────────────────────────────────────────────────────────────
# 1. Network isolation — cache is reachable only from the app tier.
resource "aws_security_group" "cache" {
name_prefix = "${var.name}-cache-"
description = "ElastiCache: ingress from app tier only"
vpc_id = var.vpc_id
ingress {
description = "app tier -> redis"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = var.app_security_group_ids
}
egress {
description = "no egress required"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.name}-cache"
ManagedBy = "terraform"
}
lifecycle {
create_before_destroy = true
}
}
# 2. The store itself.
resource "aws_elasticache_serverless_cache" "sessions" {
name = "${var.name}-sessions"
engine = var.engine
major_engine_version = "7"
description = "Session store and response cache for ${var.name}"
cache_usage_limits {
data_storage {
maximum = var.max_data_storage_gb
unit = "GB"
}
ecpu {
maximum = var.max_ecpu_per_second
unit = "ECPU/s"
}
}
# Always private. Two AZs minimum is implied by serverless.
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.cache.id]
# Snapshots are for disaster recovery, not for session durability.
# Sessions are ephemeral by design — see "app-side notes" below.
snapshot_retention_limit = 1
daily_snapshot_time = "03:00"
# Add kms_key_id = aws_kms_key.cache.arn if you need a
# customer-managed key for compliance; AWS-managed is default.
tags = {
Name = "${var.name}-sessions"
Tier = "cache"
ManagedBy = "terraform"
}
}
# 3. Publish the endpoint where the app already reads config from,
# so you never hardcode it in task definitions.
resource "aws_ssm_parameter" "cache_endpoint" {
name = "/${var.name}/cache/endpoint"
type = "String"
# First entry is the primary; serverless exposes multiple endpoints.
value = aws_elasticache_serverless_cache.sessions.cache_endpoints[0].address
tags = { ManagedBy = "terraform" }
}
resource "aws_ssm_parameter" "cache_port" {
name = "/${var.name}/cache/port"
type = "String"
value = tostring(
aws_elasticache_serverless_cache.sessions.cache_endpoints[0].port
)
tags = { ManagedBy = "terraform" }
}
# ──────────────────────────────────────────────────────────────
# outputs.tf
# ──────────────────────────────────────────────────────────────
output "cache_arn" {
value = aws_elasticache_serverless_cache.sessions.arn
}
output "cache_endpoints" {
description = "Redis endpoints. Use [0] for writes."
value = aws_elasticache_serverless_cache.sessions.cache_endpoints
}
output "cache_security_group_id" {
value = aws_security_group.cache.id
}
SET session:{sid} {json} EX 86400
resource "aws_elasticache_replication_group" "sessions" {
replication_group_id = "${var.name}-sessions"
description = "Session store"
node_type = "cache.t4g.small"
engine = "redis"
engine_version = "7.1"
num_cache_clusters = 2 # primary + 1 replica, 2 AZs
automatic_failover_enabled = true
multi_az_enabled = true
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth.result
snapshot_retention_limit = 7
maintenance_window = "sun:04:00-sun:05:00"
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.cache.id]
apply_immediately = false # stage changes, avoid mid-day restarts
}
answer 2 Redis9,036 characters
counted from its code (a container image, a Terraform engine or an install line)
# ─────────────────────────────────────────────
# variables.tf
# ─────────────────────────────────────────────
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "name_prefix" {
type = string
default = "webapp"
}
variable "vpc_id" {
type = string
description = "VPC that hosts the app (Redis stays private)."
}
variable "private_subnet_ids" {
type = list(string)
description = "PRIVATE subnets in at least 2 AZs. Never public subnets."
validation {
condition = length(var.private_subnet_ids) >= 2
error_message = "ElastiCache subnet group needs subnets in >= 2 AZs for Multi-AZ."
}
}
variable "app_security_group_id" {
type = string
description = "Security group of the web app that talks to Redis."
}
variable "node_type" {
type = string
default = "cache.t4g.small" # graviton, cheap; bump per benchmarks
}
variable "num_node_groups" {
type = number
default = 1 # shards: 1 = simple, raise to spread sessions
}
variable "replicas_per_node_group" {
type = number
default = 1
}
variable "environment" {
type = string
default = "prod"
}
# ─────────────────────────────────────────────
# main.tf
# ─────────────────────────────────────────────
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = var.name_prefix
Environment = var.environment
ManagedBy = "terraform"
}
}
}
# ── 1. Auth token, stored in Secrets Manager ────────────────────────────────
resource "random_password" "redis_auth" {
length = 32
special = false # avoids URL-encoding pain in connection strings
}
resource "aws_secretsmanager_secret" "redis_auth" {
name_prefix = "${var.name_prefix}-redis-auth-"
description = "ElastiCache Redis AUTH token"
}
resource "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = aws_secretsmanager_secret.redis_auth.id
secret_string = jsonencode({
engine = "redis"
host = aws_elasticache_replication_group.sessions.primary_endpoint_address
port = 6379
username = "default"
password = random_password.redis_auth.result
})
}
# ── 2. Network: SG + subnet group (private only) ────────────────────────────
resource "aws_security_group" "redis" {
name_prefix = "${var.name_prefix}-redis-"
description = "ElastiCache Redis - ingress from app tier only"
vpc_id = var.vpc_id
ingress {
description = "Redis from application"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
description = "No egress needed"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_elasticache_subnet_group" "redis" {
name_prefix = "${var.name_prefix}-redis-"
subnet_ids = var.private_subnet_ids
}
resource "aws_elasticache_parameter_group" "redis" {
name_prefix = "${var.name_prefix}-redis-"
family = "redis7"
# Evict least-recently-used keys when full instead of rejecting writes.
# With a separate sessions-vs-cache cluster you'd use `volatile-lru`;
# with everything on one instance, allkeys-lru keeps the app writing.
parameter {
name = "maxmemory-policy"
value = "allkeys-lru"
}
parameter {
name = "timeout"
value = "300" # drop dead connections instead of leaking them
}
lifecycle {
create_before_destroy = true
}
}
# ── 3. The replication group ────────────────────────────────────────────────
resource "aws_elasticache_replication_group" "sessions" {
replication_group_id = "${var.name_prefix}-${var.environment}-sessions"
description = "Session store + application cache"
engine = "redis"
engine_version = "7.1"
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.redis.name
subnet_group_name = aws_elasticache_subnet_group.redis.name
security_group_ids = [aws_security_group.redis.id]
# Cluster mode ON: shards now, reshard later without a rewrite.
num_node_groups = var.num_node_groups
replicas_per_node_group = var.replicas_per_node_group
automatic_failover_enabled = true
multi_az_enabled = true
# Security
transit_encryption_enabled = true
at_rest_encryption_enabled = true
auth_token = random_password.redis_auth.result
# Operations
maintenance_window = "sun:03:00-sun:04:00"
snapshot_window = "04:00-05:00"
snapshot_retention_limit = 7 # sessions are ephemeral; keep it low
copy_tags_to_snapshot = true
apply_immediately = false # changes wait for the maintenance window
}
# ─────────────────────────────────────────────
# outputs.tf
# ─────────────────────────────────────────────
output "redis_primary_endpoint" {
description = "Use when num_node_groups == 1"
value = aws_elasticache_replication_group.sessions.primary_endpoint_address
}
output "redis_configuration_endpoint" {
description = "Use when num_node_groups > 1 (cluster-aware client)"
value = aws_elasticache_replication_group.sessions.configuration_endpoint_address
}
output "redis_auth_secret_arn" {
value = aws_secretsmanager_secret.redis_auth.arn
description = "App reads the token from here at runtime."
}
output "redis_security_group_id" {
value = aws_security_group.redis.id
}
resource "aws_elasticache_serverless_cache" "sessions" {
name = "${var.name_prefix}-${var.environment}-sessions"
engine = "redis"
cache_usage_limits {
data_storage { maximum = 10 } # GB
ecpus_per_second { maximum = 5000 }
}
security_group_ids = [aws_security_group.redis.id]
subnet_group_name = aws_elasticache_subnet_group.redis.name
}
answer 3 Redis11,376 characters
counted from its code (a container image, a Terraform engine or an install line)
variable "region" {
type = string
default = "us-east-1"
}
variable "name" {
type = string
default = "webapp"
description = "Resource prefix. Keep under ~30 chars: ElastiCache group IDs cap at 40."
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
description = ">= 2 private subnets in different AZs. Never public subnets."
}
variable "app_security_group_ids" {
type = list(string)
description = "SGs of the compute that connects (ECS tasks, EC2, EB, Lambda ENIs)."
}
variable "engine" {
type = string
default = "redis" # AWS's recommendation for new workloads: "valkey"
validation {
condition = contains(["redis", "valkey"], var.engine)
error_message = "engine must be \"redis\" or \"valkey\"."
}
}
variable "engine_version" {
type = string
default = "7.1" # use "7.2" or "8.0" if engine = "valkey"
}
variable "num_cache_clusters" {
type = number
default = 2
description = "1 primary + N-1 replicas. 2 = Multi-AZ with failover."
}
variable "maintenance_window" {
type = string
default = "sun:05:00-sun:06:00"
}
variable "snapshot_window" {
type = string
default = "04:00-05:00"
}
variable "deletion_protection" {
type = bool
default = false # flip to true once this is shared infrastructure
}
variable "tags" {
type = map(string)
default = {}
}
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.80"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "aws" {
region = var.region
default_tags {
tags = merge({ Project = var.name, ManagedBy = "terraform" }, var.tags)
}
}
# ---------------------------------------------------------------------------
# The two stores. Different eviction policies, different sizes, different
# backup lifecycles — this is why they are separate replication groups.
# ---------------------------------------------------------------------------
locals {
port = 6379
stores = {
sessions = {
description = "User session store (TTL required on every key)"
node_type = "cache.t4g.small" # Graviton = cheapest $/GB
maxmemory_policy = "volatile-lru" # evict sessions, never fail writes
snapshot_retention = 0 # ephemeral: nothing to restore
}
cache = {
description = "Application/fragment cache"
node_type = "cache.t4g.medium"
maxmemory_policy = "allkeys-lru" # a miss is cheap
snapshot_retention = 1
}
}
# "redis" + "7.1" -> redis7 ; "valkey" + "8.0" -> valkey8
parameter_group_family = "${var.engine}${split(".", var.engine_version)[0]}"
}
data "aws_vpc" "this" {
id = var.vpc_id
}
# --- Networking -----------------------------------------------------------
resource "aws_security_group" "this" {
name = "${var.name}-elasticache"
description = "ElastiCache: ingress from app tier only"
vpc_id = var.vpc_id
tags = { Name = "${var.name}-elasticache" }
}
resource "aws_vpc_security_group_ingress_rule" "from_app" {
for_each = toset(var.app_security_group_ids)
security_group_id = aws_security_group.this.id
referenced_security_group_id = each.value
description = "App tier"
ip_protocol = "tcp"
from_port = local.port
to_port = local.port
}
# Nodes have no public IPs; allow outbound inside the VPC only.
resource "aws_vpc_security_group_egress_rule" "vpc_only" {
security_group_id = aws_security_group.this.id
description = "Intra-VPC only"
ip_protocol = "-1"
cidr_ipv4 = data.aws_vpc.this.cidr_block
}
resource "aws_elasticache_subnet_group" "this" {
name = "${var.name}-cache-subnets"
subnet_ids = var.private_subnet_ids
}
# --- Per-store parameter groups (maxmemory-policy is the whole point) -----
resource "aws_elasticache_parameter_group" "this" {
for_each = local.stores
name = "${var.name}-${each.key}-params"
family = local.parameter_group_family
description = "${each.value.description} - ${each.value.maxmemory_policy}"
parameter {
name = "maxmemory-policy"
value = each.value.maxmemory_policy
}
}
# --- One AUTH token per cluster (compromising one must not open the other) -
resource "random_password" "auth_token" {
for_each = local.stores
length = 32
special = false # keeps connection URIs free of escaping surprises
}
# --- Replication groups ----------------------------------------------------
resource "aws_elasticache_replication_group" "this" {
for_each = local.stores
replication_group_id = "${var.name}-${each.key}" # <= 40 chars, [a-z0-9-]
description = each.value.description
engine = var.engine
engine_version = var.engine_version
node_type = each.value.node_type
parameter_group_name = aws_elasticache_parameter_group.this[each.key].name
port = local.port
# Cluster mode disabled: one endpoint, primary + replicas.
num_cache_clusters = var.num_cache_clusters
automatic_failover_enabled = var.num_cache_clusters > 1
multi_az_enabled = var.num_cache_clusters > 1
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.this.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true # required for auth_token; TLS on the wire
auth_token = random_password.auth_token[each.key].result
snapshot_retention_limit = each.value.snapshot_retention
snapshot_window = var.snapshot_window
maintenance_window = var.maintenance_window
deletion_protection_enabled = var.deletion_protection
apply_immediately = false # edits land in the maintenance window
tags = { Name = "${var.name}-${each.key}" }
}
# --- Publish connection info where the app can read it (not in the image) --
resource "aws_ssm_parameter" "primary_endpoint" {
for_each = aws_elasticache_replication_group.this
name = "/${var.name}/elasticache/${each.key}/primary_endpoint_address"
type = "String"
value = each.value.primary_endpoint_address
}
resource "aws_ssm_parameter" "reader_endpoint" {
for_each = aws_elasticache_replication_group.this
name = "/${var.name}/elasticache/${each.key}/reader_endpoint_address"
type = "String"
value = each.value.reader_endpoint_address
}
resource "aws_ssm_parameter" "auth_token" {
for_each = local.stores
name = "/${var.name}/elasticache/${each.key}/auth_token"
type = "SecureString"
value = random_password.auth_token[each.key].result
}
output "endpoints" {
description = "Primary/reader hosts per store. Tokens live in SSM, not here."
value = {
for k, rg in aws_elasticache_replication_group.this : k => {
host = rg.primary_endpoint_address
reader_host = rg.reader_endpoint_address
port = local.port
auth_token = aws_ssm_parameter.auth_token[k].name # SSM path
}
}
}
output "security_group_id" {
value = aws_security_group.this.id
}
output "replication_group_ids" {
value = { for k, rg in aws_elasticache_replication_group.this : k => rg.id }
}
answer 4 Redis10,545 characters
counted from its code (a container image, a Terraform engine or an install line)
# ──────────────────────────────────────────────
# variables.tf
# ──────────────────────────────────────────────
variable "project" {
type = string
description = "Project name used for resource tagging and naming"
}
variable "environment" {
type = string
description = "Environment (dev, staging, prod)"
}
variable "vpc_id" {
type = string
description = "VPC where ElastiCache will live"
}
variable "private_subnet_ids" {
type = list(string)
description = "Private subnet IDs across ≥2 AZs for ElastiCache subnet group"
}
variable "app_security_group_ids" {
type = list(string)
description = "Security group IDs of the app instances/task definitions that need access"
}
variable "cache_node_type" {
type = string
default = "cache.t4g.small"
description = "Node type — scale up for prod"
}
variable "redis_version" {
type = string
default = "7.1"
description = "Redis engine version"
}
variable "retention_period" {
type = number
default = 7
description = "Automated backup retention (days)"
}
# ──────────────────────────────────────────────
# main.tf
# ──────────────────────────────────────────────
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# ─── Local naming ─────────────────────────────
locals {
name_prefix = "${var.project}-${var.environment}-redis"
}
# ─── Security Group ──────────────────────────
# Allow ONLY your app to talk to Redis on port 6379.
resource "aws_security_group" "redis" {
name = "${local.name_prefix}-sg"
description = "ElastiCache Redis – allow app access only"
vpc_id = var.vpc_id
ingress {
description = "Redis from application"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = var.app_security_group_ids # ← lock down to your app SGs
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Project = var.project
Environment = var.environment
}
}
# ─── Subnet Group ────────────────────────────
# Requires private subnets in ≥2 AZs for multi-AZ HA.
resource "aws_elasticache_subnet_group" "redis" {
name = "${local.name_prefix}-subnet-group"
subnet_ids = var.private_subnet_ids
tags = {
Project = var.project
Environment = var.environment
}
}
# ─── Parameter Group ─────────────────────────
# Tune eviction & session behaviour.
resource "aws_elasticache_parameter_group" "redis" {
name = "${local.name_prefix}-params"
family = "redis7"
parameter {
name = "maxmemory-policy"
value = "allkeys-lru" # good default for cache
# use "volatile-lru" if you only want to evict keys WITH TTLs (sessions)
}
parameter {
name = "timeout"
value = "300" # 5 min idle disconnect
}
parameter {
name = "tcp-keepalive"
value = "60"
}
tags = {
Project = var.project
Environment = var.environment
}
}
# ─── Primary Replication Group ───────────────
# Multi-AZ + auto failover → HA for session store.
resource "aws_elasticache_replication_group" "redis" {
replication_group_id = "${local.name_prefix}-repl"
description = "ElastiCache Redis cluster for sessions & caching"
node_type = var.cache_node_type
num_cache_clusters = 2 # 1 primary + 1 replica (multi-AZ)
engine = "redis"
engine_version = var.redis_version
port = 6379
subnet_group_name = aws_elasticache_subnet_group.redis.name
security_group_ids = [aws_security_group.redis.id]
parameter_group_name = aws_elasticache_parameter_group.redis.name
# ── Availability ──────────────────────────
automatic_failover_enabled = true
multi_az_enabled = true
# ── Security ─────────────────────────────
at_rest_encryption_enabled = true
transit_encryption_enabled = true
# auth token is optional but recommended for prod
# (requires generate_auth_token = true)
# auth_token = aws_secretsmanager_secret_version.redis_auth.secret_string
# ── Backups ──────────────────────────────
snapshot_retention_limit = var.retention_period
snapshot_window = "03:00-05:00" # UTC
maintenance_window = "sun:05:00-sun:07:00"
# ── Ops ──────────────────────────────────
apply_immediately = false # changes during maintenance window
auto_minor_version_upgrade = true
maintenance_window = "sun:05:00-sun:07:00"
notification_topic_arn = aws_sns_topic.redis_alerts.arn
tags = {
Project = var.project
Environment = var.environment
}
lifecycle {
prevent_destroy = true
}
}
# ─── SNS Topic for Failure Notifications ─────
resource "aws_sns_topic" "redis_alerts" {
name = "${local.name_prefix}-alerts"
tags = {
Project = var.project
Environment = var.environment
}
}
# ─── CloudWatch Alarms ───────────────────────
resource "aws_cloudwatch_metric_alarm" "cpu" {
alarm_name = "${local.name_prefix}-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 3
metric_name = "CPUUtilization"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Average"
threshold = 75
alarm_description = "Redis CPU > 75% for 3 min"
alarm_actions = [aws_sns_topic.redis_alerts.arn]
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.redis.replication_group_id
}
tags = {
Project = var.project
Environment = var.environment
}
}
resource "aws_cloudwatch_metric_alarm" "memory" {
alarm_name = "${local.name_prefix}-high-memory"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 3
metric_name = "DatabaseMemoryUsagePercentage"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Average"
threshold = 80
alarm_description = "Redis memory > 80% for 3 min"
alarm_actions = [aws_sns_topic.redis_alerts.arn]
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.redis.replication_group_id
}
tags = {
Project = var.project
Environment = var.environment
}
}
resource "aws_cloudwatch_metric_alarm" "failover" {
alarm_name = "${local.name_prefix}-failover"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "ReplicationLag"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Maximum"
threshold = 60
alarm_description = "Redis replication lag > 60s"
alarm_actions = [aws_sns_topic.redis_alerts.arn]
dimensions = {
ReplicationGroupId = aws_elasticache_replication_group.redis.replication_group_id
}
tags = {
Project = var.project
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# outputs.tf
# ──────────────────────────────────────────────
output "redis_primary_endpoint" {
description = "Primary endpoint (writes)"
value = aws_elasticache_replication_group.redis.primary_endpoint_address
}
output "redis_reader_endpoint" {
description = "Reader endpoint (reads – point reads at this for cache workloads)"
value = aws_elasticache_replication_group.redis.reader_endpoint_address
}
output "redis_port" {
value = 6379
}
output "redis_security_group_id" {
description = "SG that must allow your app to reach Redis"
value = aws_security_group.redis.id
}
output "redis_connection_uri" {
description = "Ready-to-use connection string (Redis URI)"
value = "rediss://${aws_elasticache_replication_group.redis.primary_endpoint_address}:6379"
}
┌─────────────────────────────────────────────────────────┐ │ VPC (private subnets, AZ-a + AZ-b) │ │ │ │ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ App / ECS │ 6379 │ ElastiCache Redis │ │ │ │ (SG allowed) ├────────►│ ┌─────────┐ ┌─────────┐ │ │ │ └──────────────┘ │ │Primary │ │Replica │ │ │ │ │ │ AZ-a │◄┤ AZ-b │ │ │ │ │ └────┬────┘ └─────────┘ │ │ │ │ │ auto-failover │ │ │ └───────┼───────────────────┘ │ │ │ │ │ ┌───────▼───────────────────┐ │ │ │ CloudWatch → SNS → Pager │ │ │ └───────────────────────────┘ │ └─────────────────────────────────────────────────────────┘
answer 5 Redis7,889 characters
counted from its code (a container image, a Terraform engine or an install line)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "aws" {
region = var.aws_region
}
# ------------------------------------------------------------------ inputs
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "name" {
type = string
default = "webapp"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
description = "At least two AZs. Must be private subnets."
}
variable "app_security_group_id" {
type = string
description = "SG of the web/app tier that dials out to 6379."
}
variable "node_type" {
type = string
default = "cache.t4g.small" # Graviton; ~$15/mo per node
}
variable "engine_version" {
type = string
default = "7.1"
}
# ---------------------------------------------------- network / access
resource "aws_security_group" "redis" {
name = "${var.name}-redis"
description = "ElastiCache Redis: sessions + app cache"
vpc_id = var.vpc_id
# Ingress only from the app tier. Redis makes no outbound calls,
# so we deliberately define no egress rules.
ingress {
description = "App tier on 6379"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
tags = {
Name = "${var.name}-redis"
}
}
resource "aws_elasticache_subnet_group" "redis" {
name = "${var.name}-redis"
subnet_ids = var.private_subnet_ids
tags = {
Name = "${var.name}-redis"
}
}
# ------------------------------------------------- engine configuration
# volatile-lru = only evict keys that have a TTL set, least-recently-used
# first. Protects active sessions while shedding cold cache entries.
# Requires: every key you write must carry an expiry (EX/PX).
resource "aws_elasticache_parameter_group" "redis" {
name = "${var.name}-redis"
family = "redis7"
parameter {
name = "maxmemory-policy"
value = "volatile-lru"
}
# Drop idle client connections after 5 minutes so leaked sockets
# don't pin memory.
parameter {
name = "timeout"
value = "300"
}
parameter {
name = "tcp-keepalive"
value = "60"
}
}
# -------------------------------------------------------- auth material
# Auth token: 16-128 ASCII chars. `special = false` avoids URL-encoding
# pain when you paste it into a `rediss://` connection string.
resource "random_password" "auth_token" {
length = 64
special = false
}
# ------------------------------------------------------------ the store
resource "aws_elasticache_replication_group" "redis" {
replication_group_id = "${var.name}-redis"
description = "User sessions and application cache"
engine = "redis"
engine_version = var.engine_version
node_type = var.node_type
port = 6379
parameter_group_name = aws_elasticache_parameter_group.redis.name
subnet_group_name = aws_elasticache_subnet_group.redis.name
security_group_ids = [aws_security_group.redis.id]
# Cluster-mode DISABLED: one primary (all writes) + one replica.
# Two nodes is enough for automatic failover; sharding adds cost and
# client complexity you don't need at launch.
num_cache_clusters = 2
multi_az_enabled = false
automatic_failover_enabled = true
# Session data is PII-adjacent -> encrypt both ways, require AUTH.
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = random_password.auth_token.result
# Omit `kms_key_id` to use the AWS-managed key; supply a CMK ARN
# if you need key-level rotation/deletion control.
# 7 days of snapshots so an accidental FLUSHALL isn't unrecoverable.
snapshot_retention_limit = 7
snapshot_window = "04:00-05:00" # UTC
maintenance_window = "sun:05:00-sun:06:00" # UTC
auto_minor_version_upgrade = true
apply_immediately = false # stage changes into the window
tags = {
Name = "${var.name}-redis"
}
}
# --------------------------------------------------------------- outputs
output "primary_endpoint" {
description = "Writes: sessions, cache set/del. Format: hostname:port"
value = aws_elasticache_replication_group.redis.primary_endpoint_address
}
output "reader_endpoint" {
description = "Reads: use for pure cache GETs to offload the primary."
value = aws_elasticache_replication_group.redis.reader_endpoint_address
}
output "auth_token" {
description = "Store in Secrets Manager / SSM SecureString, not in git."
value = random_password.auth_token.result
sensitive = true
}
output "connection_url" {
value = "rediss://:${random_password.auth_token.result}@${aws_elasticache_replication_group.redis.primary_endpoint_address}:6379/0"
sensitive = true
}
An agent that runs the service itself
Valkey 19 · Redis 6 of 25
Claude Opus 5.5 · Valkey in 5 of 5
answer 1 Valkey6,953 characters
counted from its code (a container image, a Terraform engine or an install line)
cache/ ├── docker-compose.yml ├── valkey.conf ├── gen-acl.sh ├── secrets/ # gitignored, generated │ ├── users.acl │ └── cache_password └── .gitignore
# Network: listen inside the container only; Docker network provides isolation bind 0.0.0.0 port 6379 protected-mode yes # Auth: users come from ACL file; default user is disabled aclfile /etc/valkey/users.acl # Memory: hard cap plus eviction, so it behaves like a cache and never OOMs maxmemory 256mb maxmemory-policy allkeys-lru # Pure cache: no persistence save "" appendonly no # Hygiene timeout 300 tcp-keepalive 60 loglevel notice
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
umask 077
mkdir -p secrets
if [[ ! -f secrets/cache_password ]]; then
openssl rand -hex 32 > secrets/cache_password
fi
PW=$(cat secrets/cache_password)
cat > secrets/users.acl <<EOF
user default off resetpass resetkeys -@all
user app on >${PW} resetkeys ~app:* resetchannels +@all -@dangerous
user health on nopass resetkeys -@all +ping
EOF
echo "ACL written. Password in secrets/cache_password (mode 600)."
services:
cache:
image: valkey/valkey:8-alpine
command: ["valkey-server", "/etc/valkey/valkey.conf"]
volumes:
- ./valkey.conf:/etc/valkey/valkey.conf:ro
- ./secrets/users.acl:/etc/valkey/users.acl:ro
networks: [cache_net]
# NOTE: no "ports:" — not reachable from host or internet
restart: unless-stopped
deploy:
resources:
limits:
memory: 384m # headroom above maxmemory 256mb
cpus: "1.0"
read_only: true
security_opt: ["no-new-privileges:true"]
cap_drop: [ALL]
healthcheck:
test: ["CMD", "valkey-cli", "--user", "health", "--pass", "x", "ping"]
interval: 10s
timeout: 2s
retries: 3
app:
build: ../app
environment:
CACHE_HOST: cache
CACHE_PORT: "6379"
CACHE_USER: app
CACHE_PASSWORD_FILE: /run/secrets/cache_password
secrets: [cache_password]
networks: [cache_net, public_net]
depends_on:
cache:
condition: service_healthy
networks:
cache_net:
internal: true # no external routing at all
public_net: {}
secrets:
cache_password:
file: ./secrets/cache_password
secrets/
# app/cache.py
import os, json, logging
import redis # redis-py works unchanged against Valkey
log = logging.getLogger("cache")
PREFIX = "app:"
def _password():
with open(os.environ["CACHE_PASSWORD_FILE"]) as f:
return f.read().strip()
_pool = redis.ConnectionPool(
host=os.environ.get("CACHE_HOST", "cache"),
port=int(os.environ.get("CACHE_PORT", 6379)),
username=os.environ.get("CACHE_USER", "app"),
password=_password(),
socket_connect_timeout=0.2,
socket_timeout=0.1, # a slow cache is worse than no cache
max_connections=50,
health_check_interval=30,
)
_r = redis.Redis(connection_pool=_pool)
def get(key):
try:
raw = _r.get(PREFIX + key)
return json.loads(raw) if raw is not None else None
except (redis.RedisError, ValueError) as e:
log.warning("cache get failed (%s): %s", key, e)
return None # fail open -> caller hits source of truth
def set(key, value, ttl=300):
try:
_r.set(PREFIX + key, json.dumps(value), ex=ttl) # always set a TTL
except redis.RedisError as e:
log.warning("cache set failed (%s): %s", key, e)
def delete(key):
try:
_r.delete(PREFIX + key)
except redis.RedisError as e:
log.warning("cache delete failed (%s): %s", key, e)
cd cache chmod +x gen-acl.sh && ./gen-acl.sh docker compose up -d cache docker compose ps # expect cache: healthy PW=$(cat secrets/cache_password) EXEC="docker compose exec -T cache valkey-cli" # Should succeed $EXEC --user app --pass "$PW" SET app:smoke ok EX 30 # -> OK $EXEC --user app --pass "$PW" GET app:smoke # -> "ok" # Should all be refused (guardrails working) $EXEC PING # -> NOAUTH / denied (default user off) $EXEC --user app --pass "$PW" SET other:x 1 # -> NOPERM (key outside app:*) $EXEC --user app --pass "$PW" FLUSHALL # -> NOPERM $EXEC --user app --pass "$PW" CONFIG GET maxmemory # -> NOPERM # Not reachable from host nc -zv localhost 6379 # -> connection refused
answer 2 Valkey8,777 characters
counted from its code (a container image, a Terraform engine or an install line)
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
mkdir -p secrets
chmod 700 secrets
if [[ ! -s secrets/cache_password ]]; then
umask 077
openssl rand -hex 32 > secrets/cache_password
fi
PASS_HASH=$(tr -d '\n' < secrets/cache_password | sha256sum | cut -d' ' -f1)
umask 022
cat > users.acl <<EOF
user default off
user app on #${PASS_HASH} ~app:* resetchannels -@all +@read +@write +@keyspace +@string +@hash +@list +@set +@sortedset +ping +hello +auth +client|setname -@dangerous
EOF
echo "Secrets and ACL ready."
# Network: container-internal only; isolation enforced by Docker network bind 0.0.0.0 port 6379 protected-mode yes tcp-keepalive 60 timeout 300 # Auth aclfile /etc/valkey/users.acl # Memory: hard cap, evict least-recently-used keys across all keys maxmemory 512mb maxmemory-policy allkeys-lru maxmemory-samples 10 lazyfree-lazy-eviction yes lazyfree-lazy-expire yes # Pure cache: no persistence save "" appendonly no # Performance io-threads 2 maxclients 2000 # Observability loglevel notice slowlog-log-slower-than 10000 slowlog-max-len 256 latency-monitor-threshold 50
services:
cache:
image: valkey/valkey:8.0-alpine
command: ["valkey-server", "/etc/valkey/valkey.conf"]
user: "999:1000"
read_only: true
tmpfs:
- /data:size=16m
volumes:
- ./cache/valkey.conf:/etc/valkey/valkey.conf:ro
- ./cache/users.acl:/etc/valkey/users.acl:ro
secrets:
- cache_password
networks: [backend]
# NOTE: no `ports:` on purpose; not reachable from outside the backend network
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
deploy:
resources:
limits:
memory: 640m # maxmemory + overhead headroom
cpus: "2"
healthcheck:
test: ["CMD-SHELL", "valkey-cli --user app --pass \"$$(cat /run/secrets/cache_password)\" --no-auth-warning ping | grep -q PONG"]
interval: 5s
timeout: 2s
retries: 10
restart: unless-stopped
app:
build: .
environment:
CACHE_HOST: cache
CACHE_PORT: "6379"
CACHE_USER: app
CACHE_PASSWORD_FILE: /run/secrets/cache_password
secrets:
- cache_password
networks: [backend]
depends_on:
cache:
condition: service_healthy
networks:
backend:
internal: true # no egress/ingress beyond this network; add a separate network for public traffic to `app`
secrets:
cache_password:
file: ./cache/secrets/cache_password
import json, os, random, logging
from typing import Any, Callable
import redis # redis-py works with Valkey (or: pip install valkey)
log = logging.getLogger("cache")
PREFIX = "app:"
def _password() -> str:
with open(os.environ["CACHE_PASSWORD_FILE"]) as f:
return f.read().strip()
_pool = redis.ConnectionPool(
host=os.environ.get("CACHE_HOST", "cache"),
port=int(os.environ.get("CACHE_PORT", 6379)),
username=os.environ.get("CACHE_USER", "app"),
password=_password(),
max_connections=50,
socket_timeout=0.25, # a cache must be fast or skipped
socket_connect_timeout=0.5,
health_check_interval=30,
retry_on_timeout=False,
decode_responses=True,
)
client = redis.Redis(connection_pool=_pool)
def get_or_set(key: str, loader: Callable[[], Any], ttl: int = 300) -> Any:
"""Cache-aside. Fails open: cache errors never break the request."""
k = PREFIX + key
try:
hit = client.get(k)
if hit is not None:
return json.loads(hit)
except redis.RedisError as e:
log.warning("cache get failed (%s); bypassing", e)
return loader()
value = loader()
try:
jitter = random.randint(0, max(1, ttl // 10)) # avoid synchronized expiry stampedes
client.set(k, json.dumps(value), ex=ttl + jitter)
except redis.RedisError as e:
log.warning("cache set failed (%s)", e)
return value
def invalidate(key: str) -> None:
try:
client.delete(PREFIX + key)
except redis.RedisError as e:
log.warning("cache invalidate failed (%s)", e)
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
P=$(cat cache/secrets/cache_password)
cli() { docker compose exec -T cache valkey-cli --no-auth-warning "$@"; }
echo "1. auth + ping"; cli --user app --pass "$P" ping | grep -q PONG
echo "2. set/get w/ TTL"; cli --user app --pass "$P" set app:smoke ok EX 10 >/dev/null
cli --user app --pass "$P" get app:smoke | grep -q ok
echo "3. key prefix enforced"; cli --user app --pass "$P" set other:x 1 2>&1 | grep -qi noperm
echo "4. dangerous cmds denied"; cli --user app --pass "$P" flushall 2>&1 | grep -qi noperm
cli --user app --pass "$P" config get maxmemory 2>&1 | grep -qi noperm
echo "5. default user disabled"; cli ping 2>&1 | grep -qiE "noauth|wrongpass|disabled"
echo "6. not published on host"; ! (exec 3<>/dev/tcp/127.0.0.1/6379) 2>/dev/null
echo "7. latency sanity"; docker compose exec -T cache valkey-benchmark --user app -a "$P" \
-t set,get -n 20000 -q -r 1000 2>/dev/null | \
sed 's/^/ /'
cli --user app --pass "$P" del app:smoke >/dev/null
echo "All cache checks passed."
chmod +x cache/setup.sh cache/smoke_test.sh ./cache/setup.sh # generate secret + ACL docker compose up -d cache # start cache docker compose ps cache # wait for (healthy) ./cache/smoke_test.sh # gate: must pass before proceeding docker compose up -d app # start the service
answer 3 Valkey7,186 characters
counted from its code (a container image, a Terraform engine or an install line)
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
umask 077
mkdir -p secrets
if [[ ! -s secrets/cache_password ]]; then
openssl rand -base64 48 | tr -d '\n/+=' | head -c 48 > secrets/cache_password
fi
HASH=$(printf '%s' "$(cat secrets/cache_password)" | sha256sum | cut -d' ' -f1)
cat > users.acl <<EOF
user default off
user app on #${HASH} ~* &* +@all -@dangerous -@admin
user health on nopass resetkeys -@all +ping
EOF
chmod 644 users.acl # contains only a hash; container must read it
grep -qxF 'secrets/' .gitignore 2>/dev/null || echo 'secrets/' >> .gitignore
grep -qxF 'users.acl' .gitignore 2>/dev/null || echo 'users.acl' >> .gitignore
docker compose up -d
echo "Waiting for cache to become healthy..."
for i in {1..30}; do
status=$(docker inspect -f '{{.State.Health.Status}}' app-cache 2>/dev/null || echo starting)
[[ "$status" == "healthy" ]] && { echo "Cache healthy."; exit 0; }
sleep 1
done
echo "Cache failed health check" >&2
docker compose logs --tail=50 cache >&2
exit 1
# Network: container-internal only; exposure controlled by compose bind 0.0.0.0 port 6379 protected-mode yes aclfile /etc/valkey/users.acl # Cache semantics maxmemory 256mb maxmemory-policy allkeys-lru save "" appendonly no # Performance / hygiene lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-user-del yes tcp-keepalive 60 timeout 300 io-threads 2 loglevel notice
services:
cache:
image: valkey/valkey:8.0-alpine
container_name: app-cache
command: ["valkey-server", "/etc/valkey/valkey.conf"]
volumes:
- ./valkey.conf:/etc/valkey/valkey.conf:ro
- ./users.acl:/etc/valkey/users.acl:ro
ports:
- "127.0.0.1:6379:6379" # loopback only; never 0.0.0.0
networks: [backend]
restart: unless-stopped
read_only: true
tmpfs: [/data]
security_opt: ["no-new-privileges:true"]
cap_drop: [ALL]
mem_limit: 320m # headroom above maxmemory
healthcheck:
test: ["CMD", "valkey-cli", "--user", "health", "--pass", "", "--no-auth-warning", "ping"]
interval: 5s
timeout: 2s
retries: 5
networks:
backend:
name: backend
# app/cache.py
import os, json, functools, logging
import redis
log = logging.getLogger(__name__)
def _password() -> str:
path = os.environ.get("CACHE_PASSWORD_FILE", "cache/secrets/cache_password")
with open(path) as f:
return f.read().strip()
pool = redis.ConnectionPool(
host=os.environ.get("CACHE_HOST", "127.0.0.1"),
port=int(os.environ.get("CACHE_PORT", 6379)),
username="app",
password=_password(),
socket_timeout=0.25, # cache must never stall requests
socket_connect_timeout=0.25,
health_check_interval=30,
max_connections=50,
decode_responses=True,
)
r = redis.Redis(connection_pool=pool)
def cached(ttl: int, prefix: str):
"""Read-through cache. Fails open: if the cache is down, call the function."""
def deco(fn):
@functools.wraps(fn)
def wrapper(*args):
key = f"{prefix}:" + ":".join(map(str, args))
try:
hit = r.get(key)
if hit is not None:
return json.loads(hit)
except redis.RedisError as e:
log.warning("cache read failed: %s", e)
value = fn(*args)
try:
r.set(key, json.dumps(value), ex=ttl) # always set a TTL
except redis.RedisError as e:
log.warning("cache write failed: %s", e)
return value
return wrapper
return deco
chmod +x cache/setup.sh && ./cache/setup.sh
pip install "redis>=5"
# Functional check
python - <<'EOF'
import sys; sys.path.insert(0, ".")
from app.cache import r
r.set("selftest", "ok", ex=10)
assert r.get("selftest") == "ok"
print("roundtrip ok")
EOF
# Security checks (each should FAIL)
docker exec app-cache valkey-cli ping # default user disabled -> NOAUTH
REDISCLI_AUTH=$(cat cache/secrets/cache_password) \
docker exec -e REDISCLI_AUTH app-cache valkey-cli --user app FLUSHALL # NOPERM
# Quick latency/throughput sanity
REDISCLI_AUTH=$(cat cache/secrets/cache_password) \
docker exec -e REDISCLI_AUTH app-cache valkey-benchmark --user app -q -n 50000 -t get,set
answer 4 Valkey9,322 characters
counted from its code (a container image, a Terraform engine or an install line)
. ├── docker-compose.yml ├── cache/ │ └── valkey.conf ├── scripts/ │ ├── setup.sh # generates secret + ACL file │ └── verify.sh # proves it works and is locked down ├── app/ │ └── cache_client.py ├── secrets/ # generated, gitignored └── DECISIONS.md
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
mkdir -p secrets cache
chmod 700 secrets
# Generate the app password once; never print it.
if [[ ! -s secrets/cache_password ]]; then
umask 077
openssl rand -hex 32 | tr -d '\n' > secrets/cache_password
fi
HASH=$(tr -d '\n' < secrets/cache_password | sha256sum | awk '{print $1}')
# ACL: default user disabled; app user gets everything except dangerous/admin cmds.
umask 022
cat > cache/users.acl <<EOF
user default off
user app on #${HASH} ~* &* +@all -@dangerous -@admin
EOF
# Ensure secrets never get committed.
grep -qxF 'secrets/' .gitignore 2>/dev/null || echo 'secrets/' >> .gitignore
echo "setup complete (secret at secrets/cache_password, not displayed)"
# Network: reachable only inside the internal Docker network (no ports published) bind 0.0.0.0 port 6379 protected-mode yes tcp-keepalive 60 timeout 300 # Auth aclfile /etc/valkey/users.acl # Memory: hard cap + evict least-recently-used keys across the keyspace maxmemory 256mb maxmemory-policy allkeys-lru maxmemory-samples 10 # Pure cache: no disk persistence save "" appendonly no # Performance lazyfree-lazy-eviction yes lazyfree-lazy-expire yes io-threads 2 loglevel notice
services:
cache:
image: valkey/valkey:8.1-alpine
command: ["valkey-server", "/etc/valkey/valkey.conf"]
volumes:
- ./cache/valkey.conf:/etc/valkey/valkey.conf:ro
- ./cache/users.acl:/etc/valkey/users.acl:ro
secrets:
- cache_password
networks:
- cache_net # internal-only; note: no `ports:` section
mem_limit: 384m # headroom above maxmemory for overhead
cpus: 1.0
security_opt:
- no-new-privileges:true
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "valkey-cli --no-auth-warning --user app --pass \"$$(cat /run/secrets/cache_password)\" ping | grep -q PONG"]
interval: 5s
timeout: 2s
retries: 10
app:
build: ./app
environment:
CACHE_HOST: cache
CACHE_PORT: "6379"
CACHE_USER: app
CACHE_PASSWORD_FILE: /run/secrets/cache_password
secrets:
- cache_password
networks:
- cache_net
- default # app can reach outside; cache cannot
depends_on:
cache:
condition: service_healthy
restart: unless-stopped
networks:
cache_net:
internal: true
secrets:
cache_password:
file: ./secrets/cache_password
"""Fail-open cache client: the cache speeds things up but is never a hard dependency."""
import json
import logging
import os
import random
from typing import Any, Callable
import redis # redis-py works with Valkey (pip install "redis>=5")
log = logging.getLogger("cache")
NAMESPACE = os.getenv("CACHE_NAMESPACE", "svc:v1") # bump version to invalidate everything
def _password() -> str:
with open(os.environ["CACHE_PASSWORD_FILE"]) as f:
return f.read().strip()
_pool = redis.ConnectionPool(
host=os.getenv("CACHE_HOST", "cache"),
port=int(os.getenv("CACHE_PORT", "6379")),
username=os.getenv("CACHE_USER", "app"),
password=_password(),
socket_connect_timeout=0.2,
socket_timeout=0.2, # a slow cache must not slow the service
health_check_interval=30,
max_connections=50,
retry_on_timeout=False,
)
_client = redis.Redis(connection_pool=_pool)
def _key(k: str) -> str:
return f"{NAMESPACE}:{k}"
def get_or_compute(key: str, compute: Callable[[], Any], ttl: int = 300) -> Any:
"""Return cached value or compute it. Never raises because of cache failures."""
k = _key(key)
try:
raw = _client.get(k)
if raw is not None:
return json.loads(raw)
except redis.RedisError as e:
log.warning("cache get failed, falling through: %s", e)
value = compute()
try:
jittered = int(ttl * random.uniform(0.9, 1.1)) # avoid synchronized expiry stampedes
_client.set(k, json.dumps(value), ex=jittered)
except (redis.RedisError, TypeError, ValueError) as e:
log.warning("cache set failed: %s", e)
return value
def invalidate(key: str) -> None:
try:
_client.delete(_key(key))
except redis.RedisError as e:
log.warning("cache delete failed: %s", e)
def healthy() -> bool:
try:
return bool(_client.ping())
except redis.RedisError:
return False
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
docker compose up -d cache
echo "waiting for healthy..."
for _ in $(seq 1 30); do
[[ "$(docker inspect -f '{{.State.Health.Status}}' "$(docker compose ps -q cache)")" == healthy ]] && break
sleep 1
done
cli() { docker compose exec -T cache sh -c "valkey-cli --no-auth-warning --user app --pass \"\$(cat /run/secrets/cache_password)\" $*"; }
# 1. Functional: set/get with TTL
cli SET verify:key hello EX 30 >/dev/null
[[ "$(cli GET verify:key)" == "hello" ]] && echo "PASS set/get"
# 2. Eviction policy and memory cap applied (checked via INFO since CONFIG is blocked)
cli INFO memory | grep -q 'maxmemory_policy:allkeys-lru' && echo "PASS eviction policy"
cli INFO memory | grep -q 'maxmemory:268435456' && echo "PASS memory cap"
# 3. Unauthenticated access rejected
docker compose exec -T cache valkey-cli PING 2>&1 | grep -qiE 'NOAUTH|NOPERM|denied' \
&& echo "PASS anonymous access denied"
# 4. Dangerous commands blocked for app user
cli FLUSHALL 2>&1 | grep -qi 'NOPERM' && echo "PASS FLUSHALL blocked"
cli CONFIG GET maxmemory 2>&1 | grep -qi 'NOPERM' && echo "PASS CONFIG blocked"
# 5. Not reachable from the host
if timeout 2 bash -c '</dev/tcp/127.0.0.1/6379' 2>/dev/null; then
echo "FAIL port 6379 reachable from host"; exit 1
else
echo "PASS not exposed on host"
fi
cli DEL verify:key >/dev/null
echo "all checks passed"
chmod +x scripts/*.sh ./scripts/setup.sh ./scripts/verify.sh docker compose up -d # bring up the app too
answer 5 Valkey8,883 characters
counted from its code (a container image, a Terraform engine or an install line)
#!/usr/bin/env bash
# Generates random passwords and the hashed ACL file. Plaintext secrets never get logged.
set -euo pipefail
umask 077
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SECRETS="$ROOT/secrets"
mkdir -p "$SECRETS"
chmod 700 "$SECRETS"
gen() { openssl rand -hex 32; }
hash() { printf '%s' "$1" | sha256sum | cut -d' ' -f1; }
for u in app admin healthcheck; do
f="$SECRETS/cache_${u}_password"
[[ -s "$f" ]] || gen > "$f" # idempotent: don't rotate on re-run
done
APP=$(cat "$SECRETS/cache_app_password")
ADMIN=$(cat "$SECRETS/cache_admin_password")
HC=$(cat "$SECRETS/cache_healthcheck_password")
cat > "$SECRETS/users.acl" <<EOF
user default off resetkeys resetchannels -@all
user app on #$(hash "$APP") ~app:* resetchannels -@all +get +set +del +unlink +mget +exists +expire +pexpire +ttl +incr +incrby +ping +client|setinfo
user healthcheck on #$(hash "$HC") resetkeys resetchannels -@all +ping
user admin on #$(hash "$ADMIN") ~* &* +@all
EOF
chmod 600 "$SECRETS"/*
grep -qxF 'secrets/' "$ROOT/.gitignore" 2>/dev/null || echo 'secrets/' >> "$ROOT/.gitignore"
echo "Cache secrets ready in $SECRETS (not printed)."
# Network: reachable only inside the compose network (no host port published) bind 0.0.0.0 port 6379 protected-mode yes aclfile /run/secrets/cache_acl # Memory: behave like a cache maxmemory 512mb maxmemory-policy allkeys-lru maxmemory-samples 10 lazyfree-lazy-eviction yes lazyfree-lazy-expire yes # No persistence save "" appendonly no dir /data # Connections maxclients 1000 timeout 300 tcp-keepalive 60 # Observability slowlog-log-slower-than 10000 slowlog-max-len 256 loglevel notice
services:
cache:
image: valkey/valkey:8-alpine # pin to a digest (@sha256:...) once pulled
command: ["valkey-server", "/etc/valkey/valkey.conf"]
volumes:
- ./cache/valkey.conf:/etc/valkey/valkey.conf:ro
tmpfs:
- /data:size=16m
secrets:
- cache_acl
- cache_healthcheck_password
networks: [backend]
# NOTE: intentionally no "ports:" — not reachable from the host or internet
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
mem_limit: 700m # maxmemory + overhead for buffers/fragmentation
cpus: 1.0
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "VALKEYCLI_AUTH=$$(cat /run/secrets/cache_healthcheck_password) valkey-cli --no-auth-warning --user healthcheck ping | grep -q PONG"]
interval: 10s
timeout: 2s
retries: 3
start_period: 5s
app:
build: .
environment:
CACHE_HOST: cache
CACHE_PORT: "6379"
CACHE_USER: app
CACHE_PASSWORD_FILE: /run/secrets/cache_app_password
secrets: [cache_app_password]
networks: [backend]
depends_on:
cache:
condition: service_healthy
networks:
backend:
internal: false # set true if the app needs no outbound internet
secrets:
cache_acl:
file: ./secrets/users.acl
cache_app_password:
file: ./secrets/cache_app_password
cache_healthcheck_password:
file: ./secrets/cache_healthcheck_password
"""Cache-aside helper. The cache is an optimization, never a dependency for correctness."""
import json
import logging
import os
import random
import time
from typing import Any, Callable
import redis # redis-py; fully compatible with Valkey
log = logging.getLogger("cache")
PREFIX = "app:"
_last_error_log = 0.0
def _read_secret(path: str) -> str:
with open(path) as f:
return f.read().strip()
_pool = redis.ConnectionPool(
host=os.environ.get("CACHE_HOST", "cache"),
port=int(os.environ.get("CACHE_PORT", "6379")),
username=os.environ.get("CACHE_USER", "app"),
password=_read_secret(os.environ["CACHE_PASSWORD_FILE"]),
socket_connect_timeout=0.2, # fail fast; the source of truth is the fallback
socket_timeout=0.1,
health_check_interval=30,
max_connections=50,
decode_responses=True,
)
client = redis.Redis(connection_pool=_pool)
def _warn(e: Exception) -> None:
global _last_error_log
now = time.monotonic()
if now - _last_error_log > 30: # rate-limit log spam during an outage
log.warning("cache unavailable, falling through: %s", type(e).__name__)
_last_error_log = now
def get_or_compute(key: str, compute: Callable[[], Any], ttl: int = 300) -> Any:
full = PREFIX + key
try:
hit = client.get(full)
if hit is not None:
return json.loads(hit)
except redis.RedisError as e:
_warn(e)
return compute()
value = compute()
try:
# ±10% TTL jitter avoids synchronized expiry stampedes
client.set(full, json.dumps(value), ex=max(1, int(ttl * random.uniform(0.9, 1.1))))
except redis.RedisError as e:
_warn(e)
return value
def invalidate(key: str) -> None:
try:
client.unlink(PREFIX + key)
except redis.RedisError as e:
_warn(e)
#!/usr/bin/env bash
set -euo pipefail
./scripts/gen-cache-secrets.sh
docker compose up -d cache
docker compose exec -T cache true # wait for container
cli() { docker compose exec -T -e VALKEYCLI_AUTH="$2" cache valkey-cli --no-auth-warning --user "$1" "${@:3}"; }
APP=$(cat secrets/cache_app_password)
echo "1. App user can read/write its namespace"
cli app "$APP" SET app:probe ok EX 10 | grep -q OK
cli app "$APP" GET app:probe | grep -q ok
echo "2. App user denied outside its namespace"
cli app "$APP" SET other:key x 2>&1 | grep -qi noperm
echo "3. App user denied dangerous commands"
for c in FLUSHALL "CONFIG GET *" "KEYS *" DEBUG SHUTDOWN; do
cli app "$APP" $c 2>&1 | grep -qi noperm
done
echo "4. Unauthenticated access rejected"
docker compose exec -T cache valkey-cli PING 2>&1 | grep -qiE 'noauth|noperm|denied'
echo "5. Not reachable from host"
! (exec 3<>/dev/tcp/127.0.0.1/6379) 2>/dev/null
echo "6. Eviction policy and memory cap applied"
ADMIN=$(cat secrets/cache_admin_password)
cli admin "$ADMIN" CONFIG GET maxmemory-policy | grep -q allkeys-lru
echo "All cache checks passed."
GPT-6 Sol · Valkey in 5 of 5
answer 1 Valkey1,843 characters
counted from its code (a container image, a Terraform engine or an install line)
mkdir -p cache
printf 'cache/users.acl\ncache/cache.env\n' >> .gitignore
cat > cache/valkey.conf <<'EOF'
bind 0.0.0.0
protected-mode yes
aclfile /run/secrets/valkey_acl
maxmemory 512mb
maxmemory-policy allkeys-lru
save ""
appendonly no
EOF
(
umask 077
password="$(openssl rand -hex 32)"
printf 'user default on >%s ~* &* +@all\n' "$password" > cache/users.acl
printf 'VALKEY_PASSWORD=%s\n' "$password" > cache/cache.env
)
cat > compose.yaml <<'EOF'
services:
valkey:
image: valkey/valkey:8.0
command: ["valkey-server", "/etc/valkey/valkey.conf"]
volumes:
- ./cache/valkey.conf:/etc/valkey/valkey.conf:ro
secrets:
- valkey_acl
networks:
- backend
mem_limit: 768m
restart: unless-stopped
secrets:
valkey_acl:
file: ./cache/users.acl
networks:
backend:
EOF
docker compose up -d
set -a; . ./cache/cache.env; set +a export VALKEYCLI_AUTH="$VALKEY_PASSWORD" docker compose exec -T -e VALKEYCLI_AUTH valkey valkey-cli PING unset VALKEYCLI_AUTH VALKEY_PASSWORD
answer 2 Valkey1,332 characters
counted from its code (a container image, a Terraform engine or an install line)
# compose.yaml
services:
valkey:
image: valkey/valkey:8
restart: unless-stopped
command:
- valkey-server
- --save
- ""
- --appendonly
- "no"
- --maxmemory
- 512mb
- --maxmemory-policy
- allkeys-lfu
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
docker compose up -d valkey docker compose exec valkey valkey-cli ping # Expected: PONG
answer 3 Valkey1,712 characters
counted from its code (a container image, a Terraform engine or an install line)
mkdir -p secrets openssl rand -hex 32 > secrets/cache_password chmod 600 secrets/cache_password
services:
cache:
image: valkey/valkey:8-alpine
restart: unless-stopped
command:
- /bin/sh
- -ec
- 'exec valkey-server --save "" --appendonly no --maxmemory 512mb --maxmemory-policy allkeys-lru --requirepass "$$(cat /run/secrets/cache_password)"'
secrets:
- cache_password
networks:
- cache_net
healthcheck:
test: ["CMD-SHELL", 'VALKEYCLI_AUTH="$$(cat /run/secrets/cache_password)" valkey-cli ping | grep -qx PONG']
interval: 10s
timeout: 3s
retries: 5
secrets:
cache_password:
file: ./secrets/cache_password
networks:
cache_net:
name: cache_net
internal: true
docker compose up -d docker compose exec -T cache sh -c \ 'VALKEYCLI_AUTH="$(cat /run/secrets/cache_password)" valkey-cli ping' # PONG
answer 4 Valkey1,490 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
valkey:
image: valkey/valkey:8.1
restart: unless-stopped
secrets:
- valkey_password
entrypoint: ["/bin/sh", "-ec"]
command:
- |
exec valkey-server \
--save "" \
--appendonly no \
--maxmemory 512mb \
--maxmemory-policy allkeys-lru \
--requirepass "$$(cat /run/secrets/valkey_password)"
healthcheck:
test: ["CMD-SHELL", "VALKEYCLI_AUTH=\"$$(cat /run/secrets/valkey_password)\" valkey-cli ping | grep -qx PONG"]
interval: 10s
timeout: 3s
retries: 5
secrets:
valkey_password:
file: ./.secrets/valkey_password
mkdir -p .secrets chmod 700 .secrets openssl rand -hex 32 > .secrets/valkey_password chmod 600 .secrets/valkey_password printf '\n.secrets/\n' >> .gitignore docker compose up -d docker compose ps
answer 5 Valkey1,423 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
cache:
image: valkey/valkey:8-alpine
restart: unless-stopped
environment:
CACHE_PASSWORD: ${CACHE_PASSWORD:?Set CACHE_PASSWORD in .env}
command:
- sh
- -ec
- exec valkey-server --save '' --appendonly no --requirepass "$$CACHE_PASSWORD" --maxmemory 512mb --maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD-SHELL", "REDISCLI_AUTH=\"$$CACHE_PASSWORD\" valkey-cli ping | grep -qx PONG"]
interval: 10s
timeout: 3s
retries: 3
umask 077 printf 'CACHE_PASSWORD=%s\n' "$(openssl rand -hex 32)" > .env printf '.env\n' >> .gitignore docker compose up -d docker compose exec cache sh -c 'REDISCLI_AUTH="$CACHE_PASSWORD" valkey-cli ping'
DeepSeek V4.1 Flash · Valkey in 5 of 5
answer 1 Valkey3,127 characters
counted from its code (a container image, a Terraform engine or an install line)
#!/usr/bin/env bash
set -euo pipefail
mkdir -p cache
cd cache
# Generate a password once
if [ ! -f .env ]; then
echo "VALKEY_PASSWORD=$(openssl rand -hex 32)" > .env
fi
cat > docker-compose.yml <<'YAML'
services:
valkey:
image: valkey/valkey:8.0-alpine
container_name: valkey
restart: unless-stopped
environment:
VALKEY_PASSWORD: ${VALKEY_PASSWORD:?VALKEY_PASSWORD is required}
command: [
"sh", "-c",
"valkey-server \
--bind 0.0.0.0 \
--requirepass \"$${VALKEY_PASSWORD}\" \
--maxmemory 512mb \
--maxmemory-policy allkeys-lru \
--appendonly yes \
--appendfsync everysec \
--save 60 1000 \
--tcp-keepalive 60 \
--timeout 0 \
--protected-mode yes"
]
volumes:
- valkey-data:/data
networks:
- cache-net
healthcheck:
test: ["CMD-SHELL", "valkey-cli --no-auth-warning -a \"$${VALKEY_PASSWORD}\" ping | grep -q PONG"]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s
ulimits:
nofile:
soft: 65536
hard: 65536
mem_limit: 768m
cpus: 1.0
networks:
cache-net:
driver: bridge
volumes:
valkey-data:
YAML
docker compose up -d
docker compose ps
# Verify
docker compose exec valkey sh -c 'valkey-cli --no-auth-warning -a "$VALKEY_PASSWORD" ping'
chmod +x setup.sh ./setup.sh
import os
import redis
r = redis.Redis(
host="valkey",
port=6379,
password=os.environ["VALKEY_PASSWORD"],
decode_responses=True,
socket_timeout=2,
socket_connect_timeout=2,
retry_on_timeout=True,
health_check_interval=30,
)
r.set("user:42:name", "Ada", ex=300)
print(r.get("user:42:name")) # Ada
import Redis from "ioredis";
const redis = new Redis({
host: "valkey",
port: 6379,
password: process.env.VALKEY_PASSWORD,
maxRetriesPerRequest: 3,
enableOfflineQueue: false,
});
await redis.set("user:42:name", "Ada", "EX", 300);
console.log(await redis.get("user:42:name"));
ports: - "127.0.0.1:6379:6379"
answer 2 Valkey3,416 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
cache:
image: valkey/valkey:8-alpine
command: valkey-server /usr/local/etc/valkey/valkey.conf
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./valkey.conf:/usr/local/etc/valkey/valkey.conf:ro
- cache-data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
api:
build: .
environment:
CACHE_URL: redis://cache:6379/0
depends_on:
cache:
condition: service_healthy
ports:
- "8000:8000"
restart: unless-stopped
volumes:
cache-data:
maxmemory 512mb maxmemory-policy allkeys-lru save "" appendonly no
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
fastapi uvicorn[standard] redis
import os
import json
import hashlib
from functools import wraps
from fastapi import FastAPI
import redis.asyncio as redis
app = FastAPI()
CACHE_URL = os.getenv("CACHE_URL", "redis://127.0.0.1:6379/0")
cache = redis.from_url(CACHE_URL, decode_responses=True)
def cached(ttl=60, prefix="cache"):
def decorator(fn):
@wraps(fn)
async def wrapper(*args, **kwargs):
key_data = json.dumps([args, kwargs], sort_keys=True, default=str)
digest = hashlib.sha256(key_data.encode()).hexdigest()
key = f"{prefix}:{fn.__name__}:{digest}"
cached_value = await cache.get(key)
if cached_value is not None:
return json.loads(cached_value)
result = await fn(*args, **kwargs)
await cache.setex(key, ttl, json.dumps(result, default=str))
return result
return wrapper
return decorator
@app.get("/health")
async def health():
await cache.ping()
return {"status": "ok"}
@app.get("/items/{item_id}")
@cached(ttl=30, prefix="items")
async def get_item(item_id: int):
# Simulate expensive work
return {"item_id": item_id, "value": f"expensive-{item_id}"}
#!/usr/bin/env bash
set -euo pipefail
docker compose up -d --build
echo "Waiting for cache to be healthy..."
for i in {1..30}; do
status=$(docker inspect --format='{{.State.Health.Status}}' "$(docker compose ps -q cache)" 2>/dev/null || echo "starting")
if [ "$status" = "healthy" ]; then
echo "Cache is healthy."
exit 0
fi
sleep 1
done
echo "Cache failed to become healthy." >&2
exit 1
chmod +x setup.sh ./setup.sh
curl http://localhost:8000/items/42
answer 3 Valkey2,222 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
cache:
image: valkey/valkey:8-alpine
restart: unless-stopped
environment:
VALKEY_PASSWORD: ${CACHE_PASSWORD:?set CACHE_PASSWORD in .env}
command:
- valkey-server
- --requirepass
- ${CACHE_PASSWORD:?set CACHE_PASSWORD in .env}
- --maxmemory
- 512mb
- --maxmemory-policy
- allkeys-lru
- --appendonly
- "no"
- --save
- ""
- --tcp-keepalive
- "60"
- --protected-mode
- "yes"
healthcheck:
test: ["CMD-SHELL", "valkey-cli --no-auth-warning -a \"$$VALKEY_PASSWORD\" ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
mem_limit: 768m
# ports:
# - "127.0.0.1:6379:6379" # local debugging only; never expose publicly
echo "CACHE_PASSWORD=$(openssl rand -hex 32)" > .env
docker compose up -d docker compose ps docker compose exec cache sh -c 'valkey-cli --no-auth-warning -a "$VALKEY_PASSWORD" ping'
PONG
CACHE_URL=redis://:${CACHE_PASSWORD}@cache:6379/0
import Redis from "ioredis";
export const cache = new Redis(process.env.CACHE_URL, {
maxRetriesPerRequest: 2,
enableOfflineQueue: false,
connectTimeout: 500,
commandTimeout: 200,
});
import os
import redis
cache = redis.Redis.from_url(
os.environ["CACHE_URL"],
socket_connect_timeout=0.5,
socket_timeout=0.2,
health_check_interval=30,
)
answer 4 Valkey6,007 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
cache:
image: valkey/valkey:8.0-alpine
restart: unless-stopped
command:
- valkey-server
- --bind
- 0.0.0.0
- --protected-mode
- "yes"
- --port
- "6379"
- --requirepass
- ${CACHE_PASSWORD:?CACHE_PASSWORD is required}
- --maxmemory
- ${CACHE_MAXMEMORY:-512mb}
- --maxmemory-policy
- allkeys-lru
- --appendonly
- "no"
- --save
- ""
- --tcp-keepalive
- "300"
- --timeout
- "0"
environment:
REDISCLI_AUTH: ${CACHE_PASSWORD:?CACHE_PASSWORD is required}
healthcheck:
test: ["CMD-SHELL", "valkey-cli ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 10
start_period: 5s
mem_limit: 768m
ulimits:
nofile:
soft: 65536
hard: 65536
networks:
- app
# Local debugging only. Remove this in production.
ports:
- "127.0.0.1:6379:6379"
networks:
app:
driver: bridge
echo "CACHE_PASSWORD=$(openssl rand -hex 32)" > .env echo "CACHE_MAXMEMORY=512mb" >> .env
docker compose up -d cache docker compose ps docker compose exec cache valkey-cli ping # PONG
from __future__ import annotations
import asyncio
import json
import os
import uuid
from typing import Any, Awaitable, Callable, Optional, TypeVar
import redis.asyncio as redis
from redis.exceptions import RedisError
T = TypeVar("T")
CACHE_URL = os.getenv("CACHE_URL", "redis://localhost:6379/0")
DEFAULT_TTL = int(os.getenv("CACHE_DEFAULT_TTL", "300"))
LOCK_TTL_MS = int(os.getenv("CACHE_LOCK_TTL_MS", "10000"))
_RELEASE_LOCK = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
class Cache:
def __init__(self, url: str = CACHE_URL) -> None:
self._client = redis.from_url(
url,
decode_responses=True,
socket_timeout=0.25,
socket_connect_timeout=0.25,
health_check_interval=30,
max_connections=50,
)
async def close(self) -> None:
await self._client.aclose()
async def ping(self) -> bool:
try:
return bool(await self._client.ping())
except RedisError:
return False
async def get_json(self, key: str) -> Optional[Any]:
try:
raw = await self._client.get(key)
except RedisError:
return None
if raw is None:
return None
try:
return json.loads(raw)
except json.JSONDecodeError:
return None
async def set_json(self, key: str, value: Any, ttl: int = DEFAULT_TTL) -> None:
try:
await self._client.set(
key,
json.dumps(value, separators=(",", ":")),
ex=ttl,
)
except RedisError:
pass
async def delete(self, *keys: str) -> None:
if not keys:
return
try:
await self._client.delete(*keys)
except RedisError:
pass
async def get_or_set(
self,
key: str,
producer: Callable[[], Awaitable[T]],
ttl: int = DEFAULT_TTL,
lock_ttl_ms: int = LOCK_TTL_MS,
) -> T:
cached = await self.get_json(key)
if cached is not None:
return cached
lock_key = f"lock:{key}"
token = uuid.uuid4().hex
try:
got_lock = bool(
await self._client.set(lock_key, token, nx=True, px=lock_ttl_ms)
)
except RedisError:
# Cache unavailable: do not fail or slow the request path.
value = await producer()
await self.set_json(key, value, ttl)
return value
if not got_lock:
# Another worker is filling the cache. Wait briefly, then fall back.
for _ in range(20):
await asyncio.sleep(0.05)
cached = await self.get_json(key)
if cached is not None:
return cached
value = await producer()
await self.set_json(key, value, ttl)
return value
try:
value = await producer()
await self.set_json(key, value, ttl)
return value
finally:
try:
await self._client.eval(_RELEASE_LOCK, 1, lock_key, token)
except RedisError:
pass
pip install "redis>=5.0,<6"
environment:
CACHE_URL: redis://:${CACHE_PASSWORD}@cache:6379/0
CACHE_DEFAULT_TTL: "300"
depends_on:
cache:
condition: service_healthy
value = await cache.get_or_set(
f"items:v1:{item_id}",
lambda: load_item(item_id),
ttl=300,
)
docker compose exec cache valkey-cli info stats \ | grep -E 'keyspace_hits|keyspace_misses|evicted_keys'
answer 5 Valkey3,366 characters
counted from its code (a container image, a Terraform engine or an install line)
#!/usr/bin/env bash
set -euo pipefail
CACHE_DIR="${CACHE_DIR:-./cache}"
mkdir -p "$CACHE_DIR"
cd "$CACHE_DIR"
if [[ ! -f .env ]]; then
umask 077
PASSWORD="$(openssl rand -hex 32)"
printf 'VALKEY_PASSWORD=%s\n' "$PASSWORD" > .env
echo "Generated $CACHE_DIR/.env"
fi
cat > docker-compose.yml <<'YAML'
services:
valkey:
image: valkey/valkey:8-alpine
container_name: valkey-cache
restart: unless-stopped
command:
- valkey-server
- --save
- ""
- --appendonly
- "no"
- --maxmemory
- 512mb
- --maxmemory-policy
- allkeys-lru
- --requirepass
- ${VALKEY_PASSWORD:?set VALKEY_PASSWORD}
- --protected-mode
- "yes"
environment:
VALKEY_PASSWORD: ${VALKEY_PASSWORD:?set VALKEY_PASSWORD}
# Uncomment if you need host access:
# ports:
# - "127.0.0.1:6379:6379"
healthcheck:
test: ["CMD-SHELL", "valkey-cli -a \"$$VALKEY_PASSWORD\" --no-auth-warning ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
ulimits:
nofile:
soft: 65536
hard: 65536
networks:
- cache
networks:
cache:
driver: bridge
YAML
docker compose up -d
for _ in $(seq 1 30); do
status="$(docker inspect --format='{{.State.Health.Status}}' valkey-cache 2>/dev/null || echo starting)"
if [[ "$status" == "healthy" ]]; then
break
fi
sleep 2
done
if [[ "$(docker inspect --format='{{.State.Health.Status}}' valkey-cache 2>/dev/null || echo unhealthy)" != "healthy" ]]; then
echo "Valkey failed to become healthy" >&2
docker compose logs valkey >&2
exit 1
fi
set -a
# shellcheck disable=SC1091
. ./.env
set +a
docker compose exec -T valkey \
valkey-cli -a "$VALKEY_PASSWORD" --no-auth-warning set smoke:test ok EX 10 >/dev/null
value="$(docker compose exec -T valkey \
valkey-cli -a "$VALKEY_PASSWORD" --no-auth-warning get smoke:test | tr -d '\r')"
if [[ "$value" != "ok" ]]; then
echo "Smoke test failed: got '$value'" >&2
exit 1
fi
echo "Valkey is up and healthy."
echo "Internal URL: redis://:${VALKEY_PASSWORD}@valkey:6379/0"
chmod +x setup-cache.sh ./setup-cache.sh
VALKEY_URL=redis://:${VALKEY_PASSWORD}@valkey:6379/0
import { createClient } from 'redis';
const cache = createClient({ url: process.env.VALKEY_URL });
cache.on('error', (err) => console.error('cache error', err));
await cache.connect();
await cache.set('user:123', JSON.stringify(user), { EX: 300 });
const cached = await cache.get('user:123');
GLM-5.3-Flash · Valkey in 3 of 5
answer 1 Valkey7,039 characters
counted from its code (a container image, a Terraform engine or an install line)
services:
cache:
image: valkey/valkey:8-alpine # Redis swap: redis:8-alpine, redis-server/redis-cli
command:
- valkey-server
- --maxmemory 768mb # sized to hot working set + headroom
- --maxmemory-policy allkeys-lru # allkeys-lfu if access is heavily skewed
- --save "" # it's a cache: no RDB
- --appendonly no # it's a cache: no AOF
- --requirepass ${CACHE_PASSWORD}
- --lazyfree-lazy-eviction yes # don't block on eviction frees
- --lazyfree-lazy-expire yes
- --tcp-keepalive 60
environment:
REDISCLI_AUTH: ${CACHE_PASSWORD} # consumed by healthcheck's valkey-cli
healthcheck:
test: ["CMD-SHELL", "valkey-cli ping | grep -q PONG"]
interval: 5s
timeout: 2s
retries: 5
start_period: 5s
restart: unless-stopped
mem_limit: 1g # headroom above maxmemory for overhead/fragmentation
ulimits:
nofile: 65535
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
networks: [internal]
# deliberately NO ports: mapping — not reachable from outside
# app (your service) joins the cache's private network:
# app:
# build: .
# environment:
# CACHE_URL: redis://:${CACHE_PASSWORD}@cache:6379/0
# networks: [default, internal]
networks:
internal:
internal: true # no internet egress on this network
# svc/cache.py
import asyncio, json, logging
from redis import asyncio as aioredis
from redis.exceptions import RedisError
log = logging.getLogger("cache")
NEG = "__NUL__" # cached "not found"
class Cache:
"""Fail-open cache: if Redis is down/slow, load from source of truth.
Values must be JSON-serializable."""
def __init__(self, url: str, socket_timeout: float = 0.100):
self.r = aioredis.from_url(
url,
socket_timeout=socket_timeout, # never let the cache stall a request
socket_connect_timeout=socket_timeout,
max_connections=64,
health_check_interval=30,
decode_responses=True,
)
self._stripes = [asyncio.Lock() for _ in range(64)] # bounded singleflight
@staticmethod
def _k(ns: str, key: str) -> str:
return f"{ns}:v1:{key}" # bump v1 on schema changes
def _lock(self, key: str) -> asyncio.Lock:
return self._stripes[hash(key) % len(self._stripes)]
async def get_or_load(self, ns: str, key: str, loader, ttl: int = 300):
k = self._k(ns, key)
try:
v = await self.r.get(k)
except RedisError as e:
log.warning("cache GET %s failed: %r", k, e)
return await loader()
if v is not None:
return None if v == NEG else json.loads(v)
async with self._lock(k): # collapse concurrent misses
try:
v = await self.r.get(k)
if v is not None:
return None if v == NEG else json.loads(v)
except RedisError:
pass
value = await loader()
try:
if value is None:
await self.r.set(k, NEG, ex=30) # negative caching
else:
await self.r.set(k, json.dumps(value), ex=ttl)
except RedisError as e:
log.warning("cache SET %s failed: %r", k, e)
return value
async def invalidate(self, ns: str, key: str):
try:
await self.r.delete(self._k(ns, key))
except RedisError:
pass # TTL bounds staleness even if the delete fails
async def close(self):
await self.r.aclose()
#!/usr/bin/env sh set -eu . ./.env echo "PING: $(docker compose exec -T cache valkey-cli ping)" echo "ROUNDTRIP: $(docker compose exec -T cache sh -c \ 'valkey-cli set smoke:ok 1 EX 60 >/dev/null && valkey-cli get smoke:ok && valkey-cli del smoke:ok >/dev/null')" docker compose exec -T cache valkey-cli info memory | grep -E '^(used_memory_human|maxmemory_human|maxmemory_policy)' docker compose exec -T cache valkey-cli info stats | grep -E '^(keyspace_hits|keyspace_misses|evicted_keys)'
grep -q CACHE_PASSWORD .env 2>/dev/null || echo "CACHE_PASSWORD=$(openssl rand -hex 24)" >> .env docker compose up -d cache chmod +x verify.sh && ./verify.sh
answer 2 Valkey8,089 characters
counted from its code (a container image, a Terraform engine or an install line)
cache/ docker-compose.yml valkey.conf provision_cache.sh .env # generated on first run cache.py # client, copied into the service
services:
cache:
image: valkey/valkey:8.1-alpine
container_name: svc-cache
restart: unless-stopped
# Password via CLI arg: conf files don't interpolate env vars
command:
- valkey-server
- /usr/local/etc/valkey/valkey.conf
- --requirepass
- ${VALKEY_PASSWORD}
- --maxmemory
- ${CACHE_MAXMEMORY:-512mb}
volumes:
- ./valkey.conf:/usr/local/etc/valkey/valkey.conf:ro
environment:
- VALKEY_PASSWORD=${VALKEY_PASSWORD}
ports:
- "127.0.0.1:6379:6379" # loopback only — reachable by the service on this host, nothing else
mem_limit: 768m # ~1.5x maxmemory: headroom for fragmentation + client buffers
cpus: "2"
healthcheck:
test: ["CMD-SHELL", "valkey-cli --no-auth-warning -a \"$$VALKEY_PASSWORD\" ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
networks: [svcnet]
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
networks:
svcnet:
driver: bridge
# Pure cache: no persistence, evict under pressure. Nothing here is a system of record. bind 0.0.0.0 port 6379 protected-mode yes # No persistence — restart = cold cache; the DB is the source of truth save "" appendonly no # LFU tracks "actually hot" better than LRU under skewed traffic maxmemory-policy allkeys-lfu maxmemory-samples 10 # Lower tail latency: reclaim memory in the background, never block command threads lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes # Client pool manages keepalive; don't kill connections server-side timeout 0 tcp-keepalive 60 maxclients 512 # Observability slowlog-log-slower-than 1000 # log anything slower than 1ms slowlog-max-len 128
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
if [[ ! -s .env ]]; then
umask 077
echo "VALKEY_PASSWORD=$(openssl rand -hex 24)" > .env
echo "[provision] generated password -> .env (0600)"
fi
docker compose up -d
status=""
for _ in $(seq 1 30); do
status="$(docker inspect -f '{{.State.Health.Status}}' svc-cache 2>/dev/null || true)"
[[ "$status" == "healthy" ]] && break
sleep 1
done
if [[ "$status" != "healthy" ]]; then
echo "[provision] cache unhealthy after 30s:" >&2
docker logs --tail 50 svc-cache >&2
exit 1
fi
# Smoke test: prove auth + read/write path end to end
set -a; source .env; set +a
docker compose exec -T cache sh -c \
'valkey-cli --no-auth-warning -a "$VALKEY_PASSWORD" SET __smoke:boot 1 EX 60' >/dev/null
docker compose exec -T cache sh -c \
'valkey-cli --no-auth-warning -a "$VALKEY_PASSWORD" GET __smoke:boot' | grep -q 1
echo "[provision] healthy, smoke test passed"
echo "[provision] export CACHE_URL='redis://:${VALKEY_PASSWORD}@127.0.0.1:6379/0'"
$ ./provision_cache.sh [provision] generated password -> .env (0600) [provision] healthy, smoke test passed [provision] export CACHE_URL='redis://:<redacted>@127.0.0.1:6379/0'
"""Shared-cache client. JSON-serializable values. Every key gets a TTL, always."""
import json, os, random, time, uuid
import redis
_POOL = redis.ConnectionPool.from_url(
os.environ["CACHE_URL"],
max_connections=32,
socket_connect_timeout=0.2,
socket_timeout=0.2,
decode_responses=True,
)
_R = redis.Redis(connection_pool=_POOL)
DEFAULT_TTL = 300 # seconds
_RELEASE_LOCK = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end return 0"
def _jitter(ttl: int) -> int:
# ±10% so expirations don't thundering-herd on the origin
return max(1, int(ttl * random.uniform(0.9, 1.1)))
def get(key: str):
try:
raw = _R.get(key)
return json.loads(raw) if raw is not None else None
except redis.RedisError:
return None # fail open: caller sees a miss
def set(key: str, value, ttl: int = DEFAULT_TTL) -> bool:
try:
return bool(_R.set(key, json.dumps(value), ex=_jitter(ttl)))
except redis.RedisError:
return False
def delete(key: str) -> None:
try:
_R.delete(key)
except redis.RedisError:
pass
def get_or_set(key: str, producer, ttl: int = DEFAULT_TTL):
"""Cache-aside with stampede protection: producer() runs at most once per miss."""
val = get(key)
if val is not None:
return val
lock, token = f"__lock:{key}", uuid.uuid4().hex
deadline = time.monotonic() + 2.0
while time.monotonic() < deadline:
try:
if _R.set(lock, token, nx=True, ex=5):
try:
value = producer()
set(key, value, ttl)
return value
finally:
_R.eval(_RELEASE_LOCK, 1, lock, token)
time.sleep(0.05) # waiters poll briefly; upgrade to pub/sub notify at high concurrency
val = get(key)
if val is not None:
return val
except redis.RedisError:
break # cache unusable -> go to source of truth
value = producer() # contention timeout or cache down
set(key, value, ttl)
return value
def healthy() -> bool:
try:
return _R.ping()
except redis.RedisError:
return False
def get_order(order_id: str) -> Order:
return get_or_set(
f"orders:{ENV}:id:{order_id}", # namespace: service:env:entity:id
lambda: db.fetch_order(order_id),
ttl=600,
)
# after any committed write:
delete(f"orders:{ENV}:id:{order_id}") # write-through invalidation; TTL is the backstop
@app.get("/healthz")
def healthz():
return {"cache": cache.healthy()} # cache down => "degraded", not "failed"
answer 3 Redis7,064 characters
counted from its code (a container image, a Terraform engine or an install line)
cache/ docker-compose.yml redis.conf bootstrap.sh # idempotent provision + verify .env # generated on first run svc/cache.py # client module the service imports
name: svc-stack
services:
cache:
image: redis:7.2-alpine
restart: unless-stopping
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?generate .env first}
command: /bin/sh -c 'exec redis-server /usr/local/etc/redis/redis.conf --requirepass "$$REDIS_PASSWORD"'
volumes:
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
networks: [internal]
mem_limit: 768m # 512mb dataset cap + overhead headroom
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" --no-auth-warning ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
# Uncomment for host-side debugging; everything below uses the internal network.
# ports:
# - "127.0.0.1:6379:6379"
# Your service joins the same network and consumes the cache:
# api:
# build: ./svc
# environment:
# REDIS_URL: redis://:${REDIS_PASSWORD}@cache:6379/0
# networks: [internal]
# depends_on:
# cache:
# condition: service_healthy
networks:
internal:
driver: bridge
# Cache profile: speed over durability. bind 0.0.0.0 protected-mode yes # no-op here since requirepass is set # Memory & eviction maxmemory 512mb maxmemory-policy allkeys-lru # switch to allkeys-lfu if workload has periodic bulk scans maxmemory-samples 10 # Persistence OFF — source of truth is the primary DB; this is a cache. save "" appendonly no # Latency hygiene lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes tcp-keepalive 60 tcp-backlog 511 io-threads 2 # conservative; helps under many concurrent connections io-threads-do-reads no # Observability slowlog-log-slower-than 1000 # log ops slower than 1ms slowlog-max-len 256
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# 1. Generate secret on first run (hex → URL-safe, no escaping issues)
if [ ! -f .env ]; then
umask 177
echo "REDIS_PASSWORD=$(openssl rand -hex 24)" > .env
umask 022
fi
source .env
# 2. Provision; --wait blocks until the healthcheck passes
docker compose up -d --wait --wait-timeout 60
# 3. Smoke test
exec_in() { docker compose exec -T cache redis-cli -a "$REDIS_PASSWORD" --no-auth-warning "$@"; }
exec_in ping | grep -q PONG
exec_in set smoke:test '"ok"' EX 60 | grep -q OK
[ "$(exec_in get smoke:test)" = '"ok"' ] || { echo "SMOKE FAILED: readback mismatch"; exit 1; }
exec_in ttl smoke:test | grep -qE '^[0-9]+$'
exec_in del smoke:test > /dev/null
echo "smoke test: PASS"
# 4. Baseline throughput so we know what "normal" looks like
docker compose exec -T cache redis-benchmark -a "$REDIS_PASSWORD" --no-auth-warning -n 20000 -c 50 -q -t set,get
# svc/cache.py
import json, os, random, logging
import redis
log = logging.getLogger("cache")
class Cache:
def __init__(self, namespace="svc", default_ttl=300):
self.r = redis.Redis.from_url(
os.environ["REDIS_URL"], # redis://:<pw>@cache:6379/0
socket_connect_timeout=0.2, # fail fast — cache must never stall requests
socket_timeout=0.2,
health_check_interval=30,
decode_responses=True,
)
self.ns, self.ttl = namespace, default_ttl
def _k(self, key): return f"{self.ns}:{key}"
def get(self, key):
try:
raw = self.r.get(self._k(key))
return json.loads(raw) if raw is not None else None
except redis.RedisError:
log.warning("cache get failed; treating as miss", exc_info=True)
return None
def set(self, key, value, ttl=None):
try:
# TTL jitter prevents synchronized expiry (stampede) on bulk-loaded keys
self.r.set(self._k(key), json.dumps(value, separators=(",", ":")),
ex=(ttl or self.ttl) + random.randint(0, 30))
except redis.RedisError:
log.warning("cache set failed; skipping", exc_info=True)
def get_or_set(self, key, producer, ttl=None):
val = self.get(key)
if val is not None:
return val
val = producer()
if val is not None:
self.set(key, val, ttl)
return val
user = cache.get_or_set(f"user:{uid}", lambda: db.fetch_user(uid), ttl=300)
chmod +x bootstrap.sh && ./bootstrap.sh docker compose exec cache redis-cli -a "$(source .env; echo $REDIS_PASSWORD)" \ --no-auth-warning info stats | grep -E 'keyspace_hits|keyspace_misses|evicted_keys'
answer 4 Redis6,259 characters
counted from its code (a container image, a Terraform engine or an install line)
echo "REDIS_PASSWORD=$(openssl rand -hex 24)" > .env chmod 600 .env
services:
cache:
image: redis:8-alpine # or valkey/valkey:8-alpine, same protocol
command: >
sh -c 'exec redis-server /usr/local/etc/redis/redis.conf
--requirepass "$$REDIS_PASSWORD"'
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?set REDIS_PASSWORD in .env}
volumes:
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
ports:
- "127.0.0.1:6379:6379" # loopback only; service traffic stays on the compose network
mem_limit: 768m # headroom over maxmemory for fragmentation
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli --no-auth-warning -a $$REDIS_PASSWORD ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
# --- network --- bind 0.0.0.0 # container-internal; exposure is controlled by compose protected-mode yes port 6379 tcp-keepalive 300 # --- general --- daemonize no loglevel notice databases 1 # use key prefixes, not db sprawl # --- memory: the part that actually matters for a cache --- maxmemory 512mb maxmemory-policy allkeys-lru # default is noeviction -> writes start failing when full. Override. maxmemory-samples 10 # --- persistence: OFF. A cache is rebuildable; don't pay fork()/fsync latency tails --- save "" appendonly no # --- latency hygiene: free memory async, never block the event loop --- lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes # --- observability --- slowlog-log-slower-than 1000 # log anything over 1ms slowlog-max-len 256 latency-monitor-threshold 100
set -a; source .env; set +a docker compose up -d --wait # blocks until healthcheck passes docker compose exec cache redis-cli --no-auth-warning -a "$REDIS_PASSWORD" ping # PONG docker compose exec cache redis-cli --no-auth-warning -a "$REDIS_PASSWORD" CONFIG GET maxmemory-policy # allkeys-lru <- confirms eviction is on, not the noeviction default docker compose exec cache redis-cli --no-auth-warning -a "$REDIS_PASSWORD" SET smoke:test '"ok"' EX 60 docker compose exec cache redis-cli --no-auth-warning -a "$REDIS_PASSWORD" GET smoke:test # "ok" docker compose exec cache redis-benchmark -q -n 100000 -c 50 -P 16 -a "$REDIS_PASSWORD" # SET: ~400k rps, GET: ~500k rps pipelined — way past anything this service needs
# cache_client.py — pip install redis
import json, os, random
import redis
_pool = redis.ConnectionPool(
host=os.getenv("CACHE_HOST", "127.0.0.1"),
port=6379,
password=os.getenv("REDIS_PASSWORD", ""),
socket_timeout=0.25, # the cache must never become the bottleneck
socket_connect_timeout=0.25,
health_check_interval=30,
max_connections=64,
decode_responses=True,
)
_r = redis.Redis(connection_pool=_pool)
_NS = "svc:v1:" # versioned prefix: bump to invalidate a whole schema
def get(key: str):
try:
raw = _r.get(_NS + key)
return json.loads(raw) if raw is not None else None
except redis.RedisError:
return None # Redis down == cache miss, never a failed request
def put(key: str, value, ttl: int) -> None:
ttl = int(ttl * random.uniform(0.85, 1.15)) # jitter de-synchronizes expiry herds
try:
_r.set(_NS + key, json.dumps(value), ex=ttl)
except redis.RedisError:
pass # best-effort by design
def get_or_put(key: str, producer, ttl: int = 300):
hit = get(key)
if hit is not None:
return hit
value = producer() # fall through to source of truth
put(key, value, ttl)
return value
# usage: user = cache_client.get_or_put(f"user:{uid}", lambda: db.fetch_user(uid), ttl=600)
answer 5 Valkey4,895 characters
counted from its code (a container image, a Terraform engine or an install line)
┌──────────────┐
clients ───▶│ app :8000 │ N worker processes, each with a 2s L1
└──────┬───────┘
│ RESP, password auth, private network
│ 50ms timeout, fail-open on error
┌──────▼───────┐
│ valkey :6379 │ 512mb cap, allkeys-lfu, no persistence
└──────────────┘
.
├── docker-compose.yml
├── .env # generated, chmod 600, never committed
├── deploy/valkey.conf
└── service/
├── Dockerfile
├── requirements.txt
├── cache.py
└── app.py
name: svc
services:
cache:
image: valkey/valkey:8-alpine
restart: unless-stopped
# password comes from env; static tuning lives in the conf file
command: sh -c 'exec valkey-server /usr/local/etc/valkey/valkey.conf --requirepass "$$VALKEY_PASSWORD"'
environment:
VALKEY_PASSWORD: ${VALKEY_PASSWORD}
volumes:
- ./deploy/valkey.conf:/usr/local/etc/valkey/valkey.conf:ro
ports:
- "127.0.0.1:6379:6379" # loopback only — for my ops/bench tooling, not the app
healthcheck:
test: ["CMD-SHELL", "valkey-cli --no-auth-warning -a \"$$VALKEY_PASSWORD\" ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
mem_limit: 768m # > maxmemory so eviction wins over cgroup OOM-kill
logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }
app:
build: ./service
restart: unless-stopped
environment:
CACHE_HOST: cache
CACHE_PORT: "6379"
CACHE_PASSWORD: ${VALKEY_PASSWORD}
CACHE_PREFIX: svc:prod:v1
depends_on:
cache: { condition: service_healthy }
ports:
- "127.0.0.1:8000:8000"
# Cache-only node. Nothing here survives a restart — by design. bind 0.0.0.0 protected-mode yes port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 # --- durability is a bug for a cache; disable it --- save "" appendonly no # --- memory ceiling & eviction --- maxmemory 512mb maxmemory-policy allkeys-lfu # real workloads are skewed; LFU keeps the hot set maxmemory-samples 10 active-expire-effort 2 # --- latency hygiene --- lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes slowlog-log-slower-than 1000 # flag anything >1ms (units: µs) slowlog-max-len 256
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
fastapi>=0.115 uvicorn[standard]>=0.30 valkey>=6.0 cachetools>=5.3
"""Shared cache client. L2 = Valkey (shared), L1 = per-process TTL cache.
Invariants baked in:
- Fail open: cache errors never fail a request. Cache down != service down.
- Every key has a TTL, jittered so expiries don't align.
- Single-flight on miss so a cold/hot key never stampedes the origin.
- Negative caching: absent keys are cached briefly too.
"""
import json, os, random, time, uuid
from cachetools import TTLCache
from valkey import Valkey
from valkey.exceptions import ValkeyError
class SharedCache:
def __init__(self, host, port, password, prefix, l1_size=10_000, l1_ttl=2.0):
self.prefix = prefix
self.l1 = TTLCacheMiMo-V2.6-Flash · Valkey in 1 of 5
answer 1 Redis3,159 characters
counted from the line that states the choice
cat /etc/os-release 2>/dev/null | head -5 which redis-server 2>/dev/null which apt 2>/dev/null
# Debian/Ubuntu apt-get update && apt-get install -y redis-server # RHEL/CentOS/Rocky # dnf install -y redis && systemctl start redis
cat > /etc/redis/redis.conf << 'EOF' # --- Network --- bind 127.0.0.1 ::1 port 6379 protected-mode yes # --- Memory (cache-focused) --- maxmemory 2gb maxmemory-policy allkeys-lru # Evict least recently used when full # --- Performance --- tcp-keepalive 60 timeout 0 tcp-backlog 511 # --- Persistence (lightweight for cache) --- # Snapshot only — no AOF overhead for a pure cache save 900 1 save 300 10 save 60 10000 dbfilename dump.rdb dir /var/lib/redis # --- Logging --- loglevel notice logfile /var/log/redis/redis-server.log # --- Misc --- databases 16 lazyfree-lazy-eviction yes lazyfree-lazy-expire yes lazyfree-lazy-server-del yes EOF
systemctl daemon-reload systemctl enable redis-server systemctl start redis-server systemctl status redis-server
# Health check redis-cli ping # Expected: PONG # Set/get test redis-cli set "greeting" "hello-from-cache" EX 3600 redis-cli get "greeting" # Expected: "hello-from-cache" # Latency benchmark (6s: 100k SET + 100k GET) redis-benchmark -n 100000 -q -t set,get # Memory stats redis-cli info memory | grep -E 'used_memory_human|maxmemory_human|maxmemory_policy'
SET: 150000.00 requests per second GET: 150000.00 requests per second
redis-cli config get maxmemory-policy # maxmemory-policy # allkeys-lru redis-cli config get maxmemory # maxmemory # 2147483648 (2 GB) redis-cli info server | grep redis_version
answer 2 Redis4,525 characters
counted from its code (a container image, a Terraform engine or an install line)
# Ubuntu/Debian sudo apt-get update && sudo apt-get install -y redis-server # macOS brew install redis
cat > /etc/redis/redis.conf << 'EOF' # --- Network --- bind 127.0.0.1 ::1 port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 # --- Memory (the critical part) --- # Give Redis a hard ceiling so it never eats the host maxmemory 512mb # Eviction strategy: cache-friendly — drop least recently used keys first maxmemory-policy allkeys-lru # --- Persistence: DISABLED (it's a cache, not a database) --- # This removes fsync overhead and disk I/O entirely save "" appendonly no # --- Performance --- # No disk means these are mostly irrelevant, but keep them sane auto-aof-rewrite-percentage 0 activerehashing yes # --- Safety --- requirepass mycache-secret-password # --- Logging --- loglevel notice logfile /var/log/redis/redis-server.log EOF
# Create log directory sudo mkdir -p /var/log/redis sudo touch /var/log/redis/redis-server.log sudo chown redis:redis /var/log/redis/redis-server.log # Start sudo systemctl start redis-server sudo systemctl enable redis-server
# Health check redis-cli -a mycache-secret-password ping # Expected: PONG # Check memory config took effect redis-cli -a mycache-secret-password config get maxmemory redis-cli -a mycache-secret-password config get maxmemory-policy # Bench it (quick sanity check) redis-cli -a mycache-secret-password --bench -n 100000 -c 10 -q
REDIS_HOST="localhost" REDIS_PORT=6379 REDIS_PASS="mycache-secret-password"
# Cache a value for 5 minutes
redis-cli -a "$REDIS_PASS" set "user:1042:profile" '{"name":"Alice","role":"admin"}' EX 300
# Cache a computed result for 1 minute
redis-cli -a "$REDIS_PASS" set "api:report:daily" '{"revenue":48210}' EX 60
redis-cli -a "$REDIS_PASS" get "user:1042:profile" # (nil) if expired or evicted — caller falls back to source DB
# Read: try cache first
cached=$(redis-cli -a "$REDIS_PASS" get "report:daily")
if [ -z "$cached" ]; then
# Cache miss — compute or fetch from DB, then populate
result='{"revenue":48210}'
redis-cli -a "$REDIS_PASS" set "report:daily" "$result" EX 300
echo "$result"
else
echo "$cached"
fi
# After updating a user in the source DB, bust the cache redis-cli -a "$REDIS_PASS" del "user:1042:profile"
# HMSET for caching structured objects as a hash redis-cli -a "$REDIS_PASS" hset "session:abc123" user_id 1042 role admin expires_at 1700000000
# Current memory usage redis-cli -a "$REDIS_PASS" info memory | grep used_memory_human # Hit rate (the metric that matters for a cache) redis-cli -a "$REDIS_PASS" info stats | grep keyspace_hits redis-cli -a "$REDIS_PASS" info stats | grep keyspace_misses # Hit rate = hits / (hits + misses) — aim for >90% # Eviction count (if growing, you're underprovisioned) redis-cli -a "$REDIS_PASS" info stats | grep evicted_keys # Active keys redis-cli -a "$REDIS_PASS" dbsize
answer 3 Redis5,768 characters
counted from its code (a container image, a Terraform engine or an install line)
#!/usr/bin/env bash set -euo pipefail # ────────────────────────────────────────────── # 1. Install Redis # ────────────────────────────────────────────── sudo apt-get update -y sudo apt-get install -y redis-server redis-tools # ────────────────────────────────────────────── # 2. Performance-tuned configuration # ────────────────────────────────────────────── sudo tee /etc/redis/redis.conf > /dev/null <<'CONF' # ── Networking ── bind 127.0.0.1 protected-mode yes port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 # ── Memory (cache policy: evict LRU when full) ── maxmemory 256mb maxmemory-policy allkeys-lru # ── Persistence: disable for cache-only use (speed) ── save "" # disable RDB snapshots appendonly no # disable AOF # For durability later, flip to: # appendonly yes # appendfsync everysec # ── Performance ── hash-max-ziplist-entries 512 hash-max-ziplist-value 64 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 # ── Logging ── loglevel notice logfile /var/log/redis/redis-server.log # ── Security ── requirepass CHANGE_ME_SECURE_PASSWORD # set your own # ── Slow log (diagnostics) ── slowlog-log-slower-than 10000 slowlog-max-len 128 CONF # ────────────────────────────────────────────── # 3. Tune OS kernel for Redis # ────────────────────────────────────────────── sudo tee /etc/sysctl.d/99-redis.conf > /dev/null <<'SYSCTL' # overcommit memory vm.overcommit_memory = 1 # disable transparent hugepages vm.swappiness = 10 SYSCTL sudo sysctl -p /etc/sysctl.d/99-redis.conf echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled > /dev/null # ────────────────────────────────────────────── # 4. Start & enable Redis # ────────────────────────────────────────────── sudo systemctl daemon-reload sudo systemctl enable redis-server sudo systemctl restart redis-server # ────────────────────────────────────────────── # 5. Verify # ────────────────────────────────────────────── sleep 1 redis-cli -a CHANGE_ME_SECURE_PASSWORD ping # expect: PONG redis-cli -a CHANGE_ME_SECURE_PASSWORD info server | grep redis_version redis-cli -a CHANGE_ME_SECURE_PASSWORD config get maxmemory-policy redis-cli -a CHANGE_ME_SECURE_PASSWORD config get maxmemory echo "✅ Redis is running on localhost:6379"
redis-cli -a CHANGE_ME_SECURE_PASSWORD <<'TEST'
SET user:1001 '{"name":"Ada","role":"admin"}' EX 3600
GET user:1001
HSET session:abc:123 ip 10.0.0.1 last_seen 1690000000
HGETALL session:abc:123
LPUSH queue:jobs "render_pdf"
RPOP queue:jobs
INFO stats | grep keyspace_hits
QUIT
TEST
// npm install ioredis
const Redis = require('ioredis');
const cache = new Redis({
host: '127.0.0.1',
port: 6379,
password: 'CHANGE_ME_SECURE_PASSWORD',
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 50, 2000),
});
// ── Cache-aside pattern ──
async function getUser(id) {
const key = `user:${id}`;
// 1) Try cache
const hit = await cache.get(key);
if (hit) return JSON.parse(hit);
// 2) Cache miss → DB
const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);
// 3) Write back with TTL (jitter to avoid stampede)
const ttl = 300 + Math.floor(Math.random() * 60);
await cache.set(key, JSON.stringify(user), 'EX', ttl);
return user;
}
// ── Simple round-trip benchmark ──
async function bench(n = 10000) {
const start = process.hrtime.bigint();
for (let i = 0; i < n; i++) {
await cache.set(`bench:${i}`, 'x', 'EX', 60);
}
const ms = Number(process.hrtime.bigint() - start) / 1e6;
console.log(`${n} ops in ${ms.toFixed(0)}ms → ${(n / (ms / 1000)).toFixed(0)} ops/sec`);
}
bench(); // ~80,000+ ops/sec single connection
┌──────────────────────────────────────────────┐ │ Application │ │ ┌─────┐ ┌──────┐ ┌────────────────────┐ │ │ │ GET │ │ HSET │ │ pipeline / Lua │ │ │ └──┬──┘ └──┬───┘ └────────┬───────────┘ │ │ │ │ │ │ │─────┼────────┼───────────────┼───────────────│ │ ▼ ▼ ▼ │ │ ┌──────────────────────────────────┐ │ │ │ Redis (localhost:6379) │ │ │ │ • allkeys-LRU eviction │ │ │ │ • 256 MB max │ │ │ │ • 0.1ms read/write │ │ │ │ • ~100k ops/sec/connection │ │ │ │ • password-protected │ │ │ └──────────────┬───────────────────┘ │ │ │ cache miss │ │ ┌──────────────▼───────────────────┐ │ │ │ Primary Database │ │ │ └──────────────────────────────────┘ │ └──────────────────────────────────────────────┘
answer 4 Redis6,261 characters
counted from its code (a container image, a Terraform engine or an install line)
# Detect OS package manager and install
if command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y redis-server redis-tools
elif command -v brew &> /dev/null; then
brew install redis
elif command -v dnf &> /dev/null; then
sudo dnf install -y redis redis-tools
else
# Build from source as fallback
wget https://download.redis.io/releases/redis-7.2.4.tar.gz
tar xzf redis-7.2.4.tar.gz
cd redis-7.2.4
make && sudo make install
fi
sudo tee /etc/redis/redis-cache.conf << 'EOF'
# --- Bind to localhost only (private service mesh) ---
bind 127.0.0.1
port 6379
protected-mode yes
# --- Memory management: LRU eviction is critical for a cache ---
maxmemory 512mb
maxmemory-policy allkeys-lru
# --- Performance tuning ---
# Disable persistence entirely: this is a cache, data can be recomputed
save ""
appendonly no
appendfsync no
auto-aof-rewrite-percentage 0
# --- Networking ---
tcp-backlog 511
tcp-keepalive 60
timeout 0
# --- Latency optimizations ---
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# --- Slow log for observability (operations > 10ms) ---
slowlog-log-slower-than 10000
slowlog-max-len 128
# --- Logging ---
loglevel notice
logfile /var/log/redis/cache.log
# --- Security: generate and require a password ---
requirepass CHANGE_ME_GENERATE_RANDOM
EOF
# Generate a strong random password and inject it
PASS=$(openssl rand -base64 32)
sudo sed -i "s/^requirepass .*/requirepass ${PASS}/" /etc/redis/redis-cache.conf
echo "Redis password saved to /root/.redis_cache_pass"
echo "$PASS" | sudo tee /root/.redis_cache_pass > /dev/null
sudo chmod 600 /root/.redis_cache_pass
sudo tee /etc/systemd/system/redis-cache.service << 'EOF' [Unit] Description=Redis Cache Instance After=network.target [Service] Type=simple ExecStart=/usr/bin/redis-server /etc/redis/redis-cache.conf ExecStop=/usr/bin/redis-cli -a $(cat /root/.redis_cache_pass) shutdown Restart=always RestartSec=5 LimitNOFILE=65535 User=redis Group=redis # Hardening NoNewPrivileges=true ProtectSystem=full ProtectHome=true ReadWritePaths=/var/log/redis /tmp [Install] WantedBy=multi-user.target EOF # Ensure directories exist sudo mkdir -p /var/log/redis sudo chown redis:redis /var/log/redis 2>/dev/null || true # Reload and start sudo systemctl daemon-reload sudo systemctl enable redis-cache sudo systemctl start redis-cache
# Wait for startup sleep 2 # Check service status sudo systemctl status redis-cache --no-pager # Ping with password PASS=$(cat /root/.redis_cache_pass) redis-cli -a "$PASS" ping # Expected: PONG
# Quick latency test: 100k SET + 100k GET redis-benchmark -a "$PASS" -n 100000 -q -c 50 # Typical output for cache workload: # SET: ~120000 ops/sec (1000 bytes, 50 connections) # GET: ~140000 ops/sec # Sub-millisecond p99 latency
# Test TTL-based caching (core pattern)
redis-cli -a "$PASS" eval "
redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2])
return redis.call('GET', KEYS[1])
" 1 "session:user:abc123" '{"user_id":"abc123","name":"Alice"}' 300
# Test atomic cache-aside with Lua (prevent cache stampede)
redis-cli -a "$PASS" eval "
local cached = redis.call('GET', KEYS[1])
if cached then return cached end
redis.call('SETEX', KEYS[1], tonumber(ARGV[2]), ARGV[1])
return ARGV[1]
" 1 "cache:expensive:query" '{"result":"fresh"}' 60
# Output the connection details for the service code
echo "=== Redis Cache Connection Details ==="
echo "Host: 127.0.0.1"
echo "Port: 6379"
echo "Password: $(cat /root/.redis_cache_pass)"
echo "Config: /etc/redis/redis-cache.conf"
echo "Service: redis-cache"
echo "Max Memory: 512MB"
echo "Eviction: allkeys-lru"
echo "Persistence: disabled (cache-only)"
echo ""
echo "=== Example Connection (Python) ==="
cat << 'PYEOF'
import redis
r = redis.Redis(
host='127.0.0.1',
port=6379,
password=open('/root/.redis_cache_pass').read().strip(),
decode_responses=True,
socket_connect_timeout=2,
socket_timeout=2,
)
# Cache-aside pattern
def get_with_cache(key, fetch_fn, ttl=300):
cached = r.get(key)
if cached is not None:
return cached
fresh = fetch_fn()
r.setex(key, ttl, fresh)
return fresh
PYEOF
answer 5 Valkey22,413 characters
counted from its code (a container image, a Terraform engine or an install line)
url-shortener/ ├── docker-compose.yml ├── Dockerfile ├── requirements.txt ├── app/ │ ├── __init__.py │ ├── main.py │ ├── cache.py │ ├── store.py │ └── models.py └── README.md
version: "3.9"
services:
valkey:
image: valkey/valkey:8-alpine
container_name: url-shortener-cache
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- valkey-data:/data
command: >
valkey-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--appendonly yes
--save 900 1
--save 300 10
--save 60 10000
--tcp-keepalive 60
--timeout 0
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# Two API instances to demonstrate the SHARED cache
api-1:
build: .
container_name: url-shortener-api-1
restart: unless-stopped
ports:
- "8001:8000"
environment:
- VALKEY_HOST=valkey
- VALKEY_PORT=6379
- DB_PATH=/data/urls.db
- INSTANCE_ID=api-1
volumes:
- shared-db:/data
depends_on:
valkey:
condition: service_healthy
api-2:
build: .
container_name: url-shortener-api-2
restart: unless-stopped
ports:
- "8002:8000"
environment:
- VALKEY_HOST=valkey
- VALKEY_PORT=6379
- DB_PATH=/data/urls.db
- INSTANCE_ID=api-2
volumes:
- shared-db:/data
depends_on:
valkey:
condition: service_healthy
# Optional: a lightweight load balancer in front
nginx:
image: nginx:alpine
container_name: url-shortener-lb
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api-1
- api-2
volumes:
valkey-data:
shared-db:
events {
worker_connections 1024;
}
http {
upstream backend {
least_conn;
server api-1:8000;
server api-2:8000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
EXPOSE 8000
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
fastapi==0.115.6 uvicorn[standard]==0.34.0 valkey==5.2.1 pydantic==2.10.4 httpx==0.28.1
from pydantic import BaseModel, HttpUrl, Field
from typing import Optional
from datetime import datetime
class CreateURLRequest(BaseModel):
url: HttpUrl
custom_alias: Optional[str] = Field(None, min_length=3, max_length=50)
class CreateURLResponse(BaseModel):
short_url: str
original_url: str
created_at: datetime
class URLRedirect(BaseModel):
original_url: str
click_count: int
class CacheMetrics(BaseModel):
hits: int
misses: int
hit_rate: float
total_keys: int
memory_usage_bytes: int
class HealthResponse(BaseModel):
status: str
cache_connected: bool
instance_id: str
"""
Cache layer wrapping Valkey with cache-aside pattern and metrics.
This is the shared cache all API instances talk to.
"""
import os
import time
import threading
from typing import Optional, Any
import valkey
# ---- Configuration ----
VALKEY_HOST = os.environ.get("VALKEY_HOST", "localhost")
VALKEY_PORT = int(os.environ.get("VALKEY_PORT", "6379"))
VALKEY_DB = int(os.environ.get("VALKEY_DB", "0"))
CACHE_TTL_SHORT_TO_LONG = 3600 # 1 hour
CACHE_TTL_LONG_TO_SHORT = 86400 # 24 hours
CACHE_TTL_METRICS = 0 # no expiry on metrics
class ValkeyCache:
"""
Thread-safe shared cache using Valkey (Redis-compatible).
Implements cache-aside pattern with hit/miss tracking.
"""
def __init__(self):
self._client: Optional[valkey.Valkey] = None
self._lock = threading.Lock()
# In-process metrics (per-instance, aggregated in shared valkey)
self._local_hits = 0
self._local_misses = 0
self._connect()
def _connect(self) -> None:
"""Establish connection with retry logic."""
try:
self._client = valkey.Valkey(
host=VALKEY_HOST,
port=VALKEY_PORT,
db=VALKEY_DB,
decode_responses=True,
socket_timeout=2.0,
socket_connect_timeout=2.0,
socket_keepalive=True,
health_check_interval=30,
retry_on_timeout=True,
max_connections=20,
)
# Verify connectivity
self._client.ping()
except Exception as e:
print(f"[cache] WARNING: Could not connect to Valkey at "
f"{VALKEY_HOST}:{VALKEY_PORT} — {e}")
self._client = None
@property
def is_connected(self) -> bool:
if self._client is None:
self._connect()
if self._client is None:
return False
try:
self._client.ping()
return True
except Exception:
return False
def get(self, key: str) -> Optional[str]:
"""Get value from shared cache."""
if not self.is_connected:
return None
try:
value = self._client.get(key)
if value is not None:
with self._lock:
self._local_hits += 1
return value
else:
with self._lock:
self._local_misses += 1
return None
except Exception as e:
print(f"[cache] get error for key '{key}': {e}")
with self._lock:
self._local_misses += 1
return None
def set(self, key: str, value: str, ttl: int = 3600) -> None:
"""Set value in shared cache with TTL."""
if not self.is_connected:
return
try:
self._client.setex(key, ttl, value)
except Exception as e:
print(f"[cache] set error for key '{key}': {e}")
def delete(self, key: str) -> None:
"""Delete a key from the cache."""
if not self.is_connected:
return
try:
self._client.delete(key)
except Exception as e:
print(f"[cache] delete error for key '{key}': {e}")
def cache_miss(self) -> None:
"""Track an explicit cache miss (e.g., when store lookup happens)."""
with self._lock:
self._local_misses += 1
def cache_hit(self) -> None:
"""Track an explicit cache hit."""
with self._lock:
self._local_hits += 1
def get_metrics(self) -> dict:
"""Get cache metrics from the shared Valkey instance."""
metrics = {
"hits": self._local_hits,
"misses": self._local_misses,
"hit_rate": 0.0,
"total_keys": 0,
"memory_usage_bytes": 0,
}
total = self._local_hits + self._local_misses
if total > 0:
metrics["hit_rate"] = round(self._local_hits / total, 4)
if self.is_connected:
try:
info = self._client.info("memory")
metrics["memory_usage_bytes"] = info.get("used_memory", 0)
# Count our URL keys
keys = self._client.keys("url:*")
metrics["total_keys"] = len(keys)
except Exception:
pass
return metrics
def flush_pattern(self, pattern: str) -> int:
"""Flush keys matching a pattern."""
if not self.is_connected:
return 0
try:
keys = self._client.keys(pattern)
if keys:
return self._client.delete(*keys)
except Exception as e:
print(f"[cache] flush error for pattern '{pattern}': {e}")
return 0
# Singleton cache instance
cache = ValkeyCache()
"""
Persistent store (SQLite) that sits behind the shared cache.
Implements the cache-aside (lazy loading) pattern.
"""
import os
import sqlite3
import string
import random
import threading
from datetime import datetime, timezone
from typing import Optional, Tuple
DB_PATH = os.environ.get("DB_PATH", "/tmp/urls.db")
ALPHABET = string.ascii_letters + string.digits
SHORT_CODE_LENGTH = 7
_db_lock = threading.Lock()
def _get_connection() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH, timeout=5.0)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
return conn
def init_db() -> None:
"""Initialize the database schema."""
conn = _get_connection()
conn.execute("""
CREATE TABLE IF NOT EXISTS urls (
short_code TEXT PRIMARY KEY,
original_url TEXT NOT NULL,
created_at TEXT NOT NULL,
click_count INTEGER DEFAULT 0
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS url_reverse (
original_url TEXT PRIMARY KEY,
short_code TEXT NOT NULL,
FOREIGN KEY (short_code) REFERENCES urls(short_code)
)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_urls_original
ON urls(original_url)
""")
conn.commit()
conn.close()
def generate_short_code(length: int = SHORT_CODE_LENGTH) -> str:
"""Generate a random short code."""
return ''.join(random.choices(ALPHABET, k=length))
def create_url(original_url: str, custom_alias: Optional[str] = None) -> Optional[Tuple[str, str]]:
"""
Create a new short URL.
Returns (short_code, original_url) or None if custom alias taken.
"""
with _db_lock:
# Check if this URL already exists
conn = _get_connection()
existing = conn.execute(
"SELECT short_code FROM urls WHERE original_url = ?",
(original_url,)
).fetchone()
if existing:
conn.close()
return (existing["short_code"], original_url)
# Determine short code
short_code = custom_alias or generate_short_code()
# Check if short code is available
if custom_alias:
taken = conn.execute(
"SELECT 1 FROM urls WHERE short_code = ?",
(custom_alias,)
).fetchone()
if taken:
conn.close()
return None
# Ensure uniqueness for auto-generated codes
while not custom_alias:
taken = conn.execute(
"SELECT 1 FROM urls WHERE short_code = ?",
(short_code,)
).fetchone()
if not taken:
break
short_code = generate_short_code()
now = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT INTO urls (short_code, original_url, created_at, click_count) "
"VALUES (?, ?, ?, 0)",
(short_code, original_url, now)
)
conn.execute(
"INSERT INTO url_reverse (original_url, short_code) VALUES (?, ?)",
(original_url, short_code)
)
conn.commit()
conn.close()
return (short_code, original_url)
def get_url_by_short(short_code: str) -> Optional[dict]:
"""Look up a short code and return the URL data."""
conn = _get_connection()
row = conn.execute(
"SELECT short_code, original_url, created_at, click_count "
"FROM urls WHERE short_code = ?",
(short_code,)
).fetchone()
conn.close()
if row:
return dict(row)
return None
def get_short_by_url(original_url: str) -> Optional[str]:
"""Look up an original URL and return its short code."""
conn = _get_connection()
row = conn.execute(
"SELECT short_code FROM urls WHERE original_url = ?",
(original_url,)
).fetchone()
conn.close()
if row:
return row["short_code"]
return None
def increment_clicks(short_code: str) -> int:
"""Increment click count for a URL."""
with _db_lock:
conn = _get_connection()
conn.execute(
"UPDATE urls SET click_count = click_count + 1 WHERE short_code = ?",
(short_code,)
)
conn.commit()
row = conn.execute(
"SELECT click_count FROM urls WHERE short_code = ?",
(short_code,)
).fetchone()
conn.close()
return row["click_count"] if row else 0
# Initialize on import
init_db()
"""
FastAPI URL Shortener — demonstrates the cache-aside pattern
with Valkey as the shared cache across multiple instances.
"""
import os
import time
from datetime import datetime, timezone
from typing import Optional
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from app.cache import cache
from app import store
from app.models import (
CreateURLRequest,
CreateURLResponse,
URLRedirect,
CacheMetrics,
HealthResponse,
)
INSTANCE_ID = os.environ.get("INSTANCE_ID", "unknown")
app = FastAPI(
title="URL Shortener with Shared Cache",
description="Demonstrates Valkey as a shared cache across multiple API instances",
version="1.0.0",
)
# ---- Cache key builders ----
def _key_short_to_long(short_code: str) -> str:
return f"url:s2l:{short_code}"
def _key_long_to_short(original_url: str) -> str:
return f"url:l2s:{original_url}"
# ---- Endpoints ----
@app.get("/health", response_model=HealthResponse)
async def health_check():
return HealthResponse(
status="healthy",
cache_connected=cache.is_connected,
instance_id=INSTANCE_ID,
)
@app.post("/api/shorten", response_model=CreateURLResponse)
async def shorten_url(request: CreateURLRequest):
original_url = str(request.url)
# --- Step 1: Check cache first (cache-aside read) ---
cached_short = cache.get(_key_long_to_short(original_url))
if cached_short:
cache.cache_hit()
conn = store._get_connection()
row = conn.execute(
"SELECT short_code, original_url, created_at FROM urls WHERE short_code = ?",
(cached_short,)
).fetchone()
conn.close()
if row:
return CreateURLResponse(
short_url=f"/{row['short_code']}",
original_url=row["original_url"],
created_at=datetime.fromisoformat(row["created_at"]),
)
# --- Step 2: Cache miss — look up in store ---
cache.cache_miss()
result = store.create_url(original_url, request.custom_alias)
if result is None:
raise HTTPException(status_code=409, detail="Custom alias already taken")
short_code, orig_url = result
# --- Step 3: Populate both cache directions ---
cache.set(
_key_short_to_long(short_code),
orig_url,
ttl=3600,
)
cache.set(
_key_long_to_short(orig_url),
short_code,
ttl=86400,
)
# Get created_at from store
conn = store._get_connection()
row = conn.execute(
"SELECT created_at FROM urls WHERE short_code = ?",
(short_code,)
).fetchone()
conn.close()
return CreateURLResponse(
short_url=f"/{short_code}",
original_url=orig_url,
created_at=datetime.fromisoformat(row["created_at"]) if row else datetime.now(timezone.utc),
)
@app.get("/api/{short_code}", response_model=URLRedirect)
async def get_url_info(short_code: str):
# --- Check cache first ---
cached_url = cache.get(_key_short_to_long(short_code))
if cached_url:
cache.cache_hit()
conn = store._get_connection()
row = conn.execute(
"SELECT click_count FROM urls WHERE short_code = ?",
(short_code,)
).fetchone()
conn.close()
clicks = row["click_count"] if row else 0
return URLRedirect(original_url=cached_url, click_count=clicks)
# --- Cache miss: fetch from store ---
cache.cache_miss()
data = store.get_url_by_short(short_code)
if not data:
raise HTTPException(status_code=404, detail="Short URL not found")
# Populate cache
cache.set(
_key_short_to_long(short_code),
data["original_url"],
ttl=3600,
)
cache.set(
_key_long_to_short(data["original_url"]),
short_code,
ttl=86400,
)
return URLRedirect(
original_url=data["original_url"],
click_count=data["click_count"],
)
@app.get("/{short_code}")
async def redirect(short_code: str):
"""
Redirect endpoint — the high-throughput path.
Cache hit means zero database reads.
"""
start = time.perf_counter()
# --- Cache-aside lookup ---
cached_url = cache.get(_key_short_to_long(short_code))
if cached_url:
cache.cache_hit()
# We still need to increment clicks (fire-and-forget style)
# For cache hit, we still update the store async-ish
store.increment_clicks(short_code)
elapsed = (time.perf_counter() - start) * 1000
print(f"[{INSTANCE_ID}] cache HIT for '{short_code}' in {elapsed:.2f}ms")
return RedirectResponse(url=cached_url, status_code=302)
# --- Cache miss: store lookup ---
cache.cache_miss()
data = store.get_url_by_short(short_code)
if not data:
raise HTTPException(status_code=404, detail="Short URL not found")
# Populate cache
cache.set(_key_short_to_long(short_code), data["original_url"], ttl=3600)
store.increment_clicks(short_code)
elapsed = (time.perf_counter() - start) * 1000
print(f"[{INSTANCE_ID}] cache MISS for '{short_code}' in {elapsed:.2f}ms")
return RedirectResponse(url=data["original_url"], status_code=302)
@app.get("/api/metrics/cache", response_model=CacheMetrics)
async def cache_metrics():
"""Expose cache performance metrics."""
metrics = cache.get_metrics()
return CacheMetrics(**metrics)
@app.delete("/api/cache/flush")
async def flush_cache():
"""Flush all URL cache entries (admin operation)."""
count = cache.flush_pattern("url:*")
return {"flushed": count}
# URL Shortener with Shared Valkey Cache A production-style URL shortener that demonstrates the **cache-aside pattern** with **Valkey** as a shared cache across multiple API instances. ## Architecture
## Quick Start
## Verify the Shared Cache
## Why Valkey? | Feature | Value | |---------|-------| | Latency | <1ms P99 | | Memory | 256MB with LRU eviction | | Persistence | AOF + RDB snapshots | | License | BSD-3 (fully open source) | | Protocol | Redis-compatible | | Max connections | 20 per instance |
# 1. Create the project mkdir -p url-shortener/app cd url-shortener # 2. Create all the files above in their paths # 3. Build and launch docker compose up --build -d # 4. Verify curl http://localhost:8080/health
{
"status": "healthy",
"cache_connected": true,
"instance_id": "api-1"
}