Prompt
I'm building a product with feature flags and need a fast cache layer
Latest observation
A fast cache layer for feature flags usually needs to optimize for:
- Low latency reads on every request
- Fast propagation of flag changes
- High availability even if the config service is down
- Safe defaults when cache is stale or missing
- Targeting support if flags vary by user/segment/region
Recommended architecture
1) Use a layered cache
Best pattern:
- L1: in-process memory cache in each app instance
- L2: distributed cache like Redis
- Source of truth: your feature-flag service or config DB
This gives you:
- microsecond reads from memory
- shared fallback across instances via Redis
- durability and correctness from the source system
2) Cache at the evaluation result level
Instead of caching raw flag definitions only, cache:
- flag config
- compiled evaluation rules
- per-user evaluation results for high-traffic repeated lookups
If your flag rules are deterministic and stable, caching the final boolean/variant result can be a big win.
3) Use short TTLs + push invalidation
For feature flags, stale data is often worse than slightly slower data.
Good approach:
- TTL: 30s–5m depending on tolerance
- plus event-based invalidation when a flag changes
Examples:
- publish a message on Kafka / SNS / Redis PubSub / webhook
- app nodes receive invalidation and refresh cache immediately
4) Include versioning
Store a version or updated-at timestamp with cached entries.
Example cache key:
flag:{flag_name}:v{version}
This helps prevent serving old values after updates.
5) Add a stale-while-revalidate strategy
When cache is expired:
- serve the stale value if acceptable
- refresh asynchronously in the background
This keeps request latency stable.
6) Protect against thundering herd
If many requests miss the cache at once:
- use a per-key lock / single-flight mechanism
- let only one request repopulate the cache
- others wait briefly or serve stale data
A practical implementation sketch
Cache entry
{
"value": true,
"version": 42,
"expires_at": "2026-10-07T12:00:00Z"
}
Read flow
- Check in-memory cache
- If miss, check Redis
- If miss, fetch from flag service
- Store in Redis and memory
- Return result
Update flow
- Flag changes in control plane
- Increment version
- Publish invalidation event
- Workers evict local cache and refresh Redis
Important design choices
If your flags are simple
If flags are mostly global booleans:
- cache the whole flag map in memory
- refresh periodically or via invalidation
- this is often enough
If your flags are highly targeted
If rules depend on user attributes:
- cache compiled rules
- cache cohort/segment membership
- optionally cache per-user results with short TTL
Suggested default settings
For most products:
- L1 memory TTL: 10–30 seconds
- L2 Redis TTL: 1–5 minutes
- invalidation: immediate via pub/sub or queue
- fallback: use last known good value
Common pitfalls
- Caching per-request with too many unique keys
- No invalidation strategy
- Serving stale values for kill switches
- Overly long TTLs for experiment flags
- Not distinguishing critical flags from normal rollout flags
Special case: kill switches
For emergency flags, do not rely only on TTL. Use:
- push invalidation
- very short cache TTL
- possibly bypass cache for critical flags
If you want, I can also give you:
- a Redis + in-memory cache design,
- a Go/Node/Python implementation, or
- a feature-flag caching strategy for experiments vs kill switches.