Caching Strategies & In-Memory Optimization Questions
Designing cache layers to cut redundant work and speed up reads, and the correctness costs that come with them. Covers cache placement (client/CDN/application/in-memory store), eviction policies, TTLs, write-through vs write-back, warming, and invalidation. Emphasizes hit-rate reasoning and the staleness/consistency trade-offs caching introduces.
Design a multi-layer caching architecture for an e-commerce product page specifying layers such as CDN edge, CDN regional caches, application-level caches (Redis), and database caching. Describe cache key strategies, TTL choices for different content (images, product details, inventory, price), and how to handle personalization and A/B testing.
Sample Answer
Requirements (clarify): low-latency global product pages, high read volume, frequently-changing inventory/price, support personalization & A/B testing, strong availability and eventual consistency.
High-level architecture:
CDN Edge (global POPs) → CDN Regional/Origin Cache (regional POPs) → App-layer cache (Redis cluster per region) → Database (primary + read replicas) with DB-level caching (materialized views / memcache for heavy joins)
Cache responsibilities:
- CDN Edge: static assets (images, CSS, JS), whole HTML where not personalized. Max proximity, lowest latency.
- CDN Regional: regionally cached HTML fragments, API responses for non-personalized data.
- App Redis: fine-grained objects (product_details, inventory_snapshot, price_bucket, recommendations token), support TTLs, ETags, versioning.
- DB: canonical source, write-through or cache-aside patterns; use change-data-capture to invalidate/refresh caches.
Cache key strategy:
- Use stable prefixes and versioning: product:details:v2:{product_id}, product:price:{product_id}:{price_version}, product:inventory:{product_id}:{warehouse_id}
- Include region where relevant: product:details:us-east:{id}
- For personalization: separate keys for base content and personalized overlays. E.g., product:details:{id} (public) + personalization:{user_id}:{session_id}:{id} (small overlay).
- For A/B: include experiment id/version in cache key for content impacted by test: page:product:{id}:exp:{exp_id}:var:{var_id}
TTL recommendations:
- Images/CSS/JS: long-lived (1 week–1 year) with cache-busting via content hash.
- Product details (description, specs): medium (1h–6h); invalidate on catalog updates.
- Price: short (30s–5min) depending on volatility and risk; include price_version to force immediate updates on change.
- Inventory: very short (5–30s) for hot items, or use inventory reservations; show “available” with eventual consistency and provide real-time check at checkout.
- Personalized fragments: very short (seconds–minutes) to reflect user state.
- A/B content: TTL equal to experiment lifespan; include experiment metadata to avoid cross-contamination.
Personalization strategy:
- Cache base page widely; apply personalization as client-side JS overlays or server-side edge-side includes (ESI) from regional/app cache. Keep personalized data small and cached per user/session with short TTLs or signed tokens.
- Use tokenized responses to allow CDN caching of non-user-specific parts while fetching personalization from origin/Redis.
A/B testing:
- Treat variants as separate cache keys (include exp and variant). Pre-warm caches for expected traffic. Use sticky experiment assignment (cookie) so subsequent requests hit same key. Collect metrics with logs and edge analytics; ensure experiments can quickly be killed by switching experiment config and invalidating related keys.
Invalidation & consistency:
- Use cache-aside with write-through for critical updates; employ pub/sub (Redis) and CDC events to push invalidation to CDN via API (Purge API) and to regional caches. Use versioned keys to avoid mass purges.
- For consistency-sensitive flows (checkout), bypass caches or perform realtime DB checks.
Operational considerations:
- Monitor hit/miss ratios per layer, latency, error rates; auto-scale Redis clusters; rate-limit origin to prevent cache stampedes (use locks or request coalescing).
- Security: sign CDN content, rate-limit purge APIs, encrypt sensitive personalized payloads.
Trade-offs:
- Longer TTLs improve latency but increase staleness risk. Versioned keys + short personalized overlays balance performance and freshness.
Design a caching strategy for a faceted product search system (search queries with filters and sorts). Consider full-text search engine caching, query result caching, and caching of facet counts when product index updates are frequent. Describe how to maintain near-real-time relevance while keeping cache effective.
Sample Answer
Requirements & constraints:
- Low-latency faceted product search with filters/sorts.
- Product index updates are frequent (minutes/seconds).
- Need near-real-time relevance (seconds) but effective caching to reduce load.
- Support arbitrary combinations of filters/sorts, but some queries are hot.
High-level approach:
- Use hybrid caching: (1) full-text engine + query-result caching for hot queries, (2) separate facet-count caches (aggregations) with incremental/upsert updates, (3) schema-level index versioning & event-driven invalidation to keep caches near-real-time.
Architecture & components:
- Search engine: Elasticsearch/Opensearch for full-text, indexing, and aggregation primitives. Index configured with tuned refresh interval (e.g., 1s–5s) to trade throughput vs latency.
- Query router / API: normalizes queries, canonicalizes parameters (sort, filters, pagination) and computes cache keys.
- Result cache: Redis or distributed in-memory cache (clustered Redis or Memcached) that stores rendered query results (IDs + relevance scores + precomputed metadata) for hot queries. Keys include index-version token.
- Facet-count cache: stores counts per dimension for base filter states and for common combinations. Backed by Redis; supports increment/decrement updates using events.
- Event bus: Kafka or Kinesis capturing index changes (create/update/delete) and emitting lightweight events containing product id, changed attributes, and previous/new filter-relevant fields.
- Background updater: consumer services update facet caches incrementally (delta updates) and invalidate/refresh affected result-cache entries by index-version or explicit key invalidation (for truly affected hot queries). Also performs async re-aggregation for expensive combos.
- Cache-warming & popularity tracking: track query frequency and precompute caches for top-N patterns; warm caches after deploys or index rebuilds.
Key strategies to maintain near-real-time relevance:
- Index-versioning: include an index-version or sequence-number in cache keys. When a bulk refresh or schema change occurs, bump version to avoid serving inconsistent caches.
- Short TTLs for result cache (e.g., 5–30s) for general queries; longer TTLs (minutes) for highly stable pages. Use "stale-while-revalidate": serve slightly stale cached response immediately while asynchronously refreshing cache.
- Incremental facet updates: on each product event, determine which facet buckets change (e.g., category, brand, price-bucket) and apply atomic INCR/DECR operations to facet caches instead of recomputing full aggregation. For complex dependent filters, maintain precomputed combinations for top queries and mark others for lazy recompute.
- Selective invalidation: instead of broad cache flushes, use a mapping of product -> affected cache keys (maintain a reverse index for hot queries) for targeted invalidation; for cold queries fall back to engine.
- Use approximate counts for highly dynamic facets where exactness is less critical (e.g., "results ~ 1200") using counters or probabilistic sketches; mark exact-count requests to trigger on-demand recompute.
Query normalization & cache key design:
- Canonicalize filters order, normalize ranges, bucketize numeric filters for facet-level caches.
- Cache key = hash(index-name, index-version, normalized-filters, sort, page-size, user-segmentation).
- For result cache store: list of product IDs + cursor/token for paging; subsequent page fetches prefer precomputed next-page caches or use cursor to fetch from engine.
Performance & scalability:
- Shard facet caches by dimension and hash to scale Redis cluster.
- Offload heavy ad-hoc aggregations to worker pool with rate limiting.
- Use CDN for static rendered pages or category landing pages.
- Monitor cache hit rate, staleness windows, queue lag for event bus; auto-adjust TTLs & refresh intervals.
Trade-offs:
- Strong consistency = high invalidation cost; we prefer bounded staleness (seconds) and selective exactness.
- Incremental updates reduce recompute but add complexity and eventual consistency edge cases (race conditions); solve with idempotent events and sequence numbers.
- Materializing many filter combinations increases memory usage; limit to popular patterns and use on-demand for long-tail.
Example flow (update -> facet counts):
- Product P updated: event published with changed fields.
- Background consumer reads event, computes delta for facets (e.g., brand unchanged, price bucket changed).
- Apply atomic INCR/DECR to Redis facet buckets and update reverse mapping (product -> affected keys) for hot queries.
- For result caches referencing P, either invalidate keys via mapping or bump index-version if broad change occurred.
- If a hot query's cache is invalidated, trigger async refresh and serve stale result with stale-while-revalidate if allowed.
Observability & safety:
- Track cache hit ratio, staleness age, event lag, frequency of full re-aggregations, and false-count rate for approximations.
- Provide an admin tool to force refresh specific facets/queries and to adjust TTLs per segment.
This design balances near-real-time freshness (index refresh tuning + short TTLs + incremental facet updates) with cache effectiveness (hot-query materialization, selective invalidation, stale-while-revalidate).
Hot keys in a cache can become a performance bottleneck. Propose architectural and operational strategies to detect, mitigate, and prevent hot key issues in Redis or Memcached environments serving millions of requests per minute. Include both short-term mitigations and long-term design changes.
Sample Answer
Situation: Serving millions RPS with Redis/Memcached, hot keys (single keys receiving disproportionate traffic) cause CPU, network, and eviction pressure. Below is a structured set of detection, short-term mitigations, and long-term design changes suitable for solution-architect level advice.
Detection / Observability
- Instrumentation: emit per-key metrics (top-k), command stats and latency from Redis INFO, slowlog, and LATENCY HISTOGRAMS. Use keyspace notifications, sampling, and client-side telemetry to surface hot keys into Prometheus + Grafana.
- Automated alerts: thresholds on ops/sec per key, CPU/spike, increased miss rate or eviction rate, and tail latency.
Immediate (short-term) mitigations
- Rate-limit and backpressure: apply token-bucket at API gateway or client library to throttle requests for identified hot keys.
- Request coalescing (dedupe): implement “single flight” / request coalescing so concurrent identical misses result in one backend fetch.
- Local caching: add per-app in-process cache (LRU) for hottest keys to reduce trips to Redis.
- Temporary replication: promote a read-only replica or warm a dedicated Redis instance with just the hot key, updating via pub/sub or CDC.
- Increase resource isolation: pin hot-key shard to a larger node (vertical scale) or temporary faster instance class.
Long-term architectural changes
- Sharding and partitioning: ensure app-level or client-side consistent hashing with enough slots; migrate to Redis Cluster with hash tags to control collocation and avoid single-slot overload.
- Key redesign: split hot keys (fan-out) into multiple sub-keys (time-bucketed, user-sharded) and aggregate at read if needed; use pre-aggregation for counters.
- Adaptive caching patterns: use write-through for high-write keys, probabilistic caches (Bloom filters) to prevent cache stampedes, and TTL jitter to avoid synchronized expiries.
- CQRS / materialized views: move high-read patterns into dedicated read stores (CDN, edge caches, or specialized in-memory stores) or maintain precomputed views.
- Capacity planning & autoscaling: implement horizontal autoscaling of cache nodes and connection pools; ensure client libraries support ASK/MOVED and retry logic.
- Circuit breakers & graceful degradation: fall back to degraded responses or stale-read mode when a hot key overwhelms cache.
Operational playbook
- Runbook: detect → isolate (route traffic to dedicated shard) → mitigate (throttle/coalesce/local cache) → fix (schema change or shard rebalancing) → revert temporary measures.
- Testing: chaos testing on hot-key scenarios, load tests with synthetic hot-key patterns, and rehearsed failover.
Trade-offs & considerations
- Splitting keys increases read complexity and potential consistency concerns—choose eventual vs strong consistency per use case.
- Local caches add cache-coherency complexity; use short TTLs or invalidation pub/sub.
- Client-side sharding requires coordinated rollout and support in client libraries.
This approach balances quick operational measures to stop outages with sustainable design changes—sharding, key redesign, and edge caching—to prevent recurrence while documenting runbooks and metrics for proactive detection.
Explain how to establish performance budgets for API latency and page load (e.g., target TTFB and p95 response time). Describe how caching decisions and instrumentation map to these budgets and propose a short roadmap of caching interventions to meet a 200ms p95 TTFB SLO.
Sample Answer
Start by defining the budget and measurement surface
- SLO: p95 TTFB ≤ 200ms for user-facing API calls/pages.
- SLI: measured TTFB at the CDN/edge (time from request arrival at edge to first byte delivered to client).
- Instrumentation points: client synthetic checks, CDN edge logs, API gateway logs, app server metrics, DB metrics. Correlate traces using distributed tracing (trace id) to attribute latency to network, CDN, origin, app, and DB.
How caching decisions map to the budget
- CDN/edge cache reduces network + origin RTT; cache hit should bring TTFB down to ~20–40ms.
- Edge compute (e.g., Cloudflare Workers) can serve dynamic but cacheable parts reducing origin calls.
- Application in-memory caches (Redis/local LRU) cut origin compute/DB time for cacheable responses.
- DB read replicas / materialized views reduce DB tail latency for uncached requests.
- Cache miss path defines worst-case origin cost — this must be optimized to keep p95 under budget.
Instrumentation you must have
- Real user monitoring (RUM) for client TTFB.
- Synthetic p95 probes from target regions.
- CDN hit/miss ratios, cache TTLs, stale-while-revalidate stats.
- Traces spanning CDN → API gateway → app → DB with spans and timings.
- Alerting on p95 TTFB, cache-hit-rate, and origin p95.
Short roadmap to meet 200ms p95 TTFB (phased, measurable)
- Baseline & targets (week 0–1)
- Measure current p50/p95 TTFB, per-region and per-endpoint.
- Create SLA budget breakdown (e.g., network 40ms, CDN overhead 10ms, origin p95 150ms).
- Quick wins: CDN & caching headers (week 1–2)
- Put static assets and cacheable API responses behind CDN.
- Add correct Cache-Control, Vary, ETag and short TTLs with stale-while-revalidate for safety.
- Expected impact: large decrease in global p95 for cacheable endpoints.
- Edge caching & compute (week 2–4)
- Implement edge caching for full responses or assembled fragments; use edge workers for personalization fallbacks.
- Add cache key rules (device, auth state) to maximize hit-rate without leaking data.
- App-layer caches (week 3–6)
- Add Redis/LRU for hot DB query results; use consistent cache keys, TTLs aligned to staleness requirements.
- Implement cache warming for predictable high-traffic endpoints.
- Origin optimization (week 4–8)
- Optimize app critical paths: async background work, query tuning, indexes, DB read replicas.
- Add timeouts and circuit breakers to prevent tail amplification.
- Measure, iterate, and harden (ongoing)
- Track p95, cache-hit-rate, and origin p95; run traffic experiments (A/B) as you change TTLs.
- If p95 still >200ms, consider sharding, precomputed responses, or regional origins.
Trade-offs and guardrails
- Aggressive TTLs improve latency but increase staleness; use stale-while-revalidate and short TTLs for acceptable freshness.
- Edge caching increases complexity of cache invalidation—use versioned keys for easy invalidation.
- Instrumentation must be low overhead; sampling for traces but full metrics for p95.
This plan gives a measurable path: raise cache-hit-rate at the edge first to reduce user-observed TTFB, then shrink the origin cost for misses so the p95 budget holds even on cache misses.
Design an observability plan for your caching stack spanning CDN, application caches, and Redis clusters. Specify SLI/SLOs, the dashboards and heatmaps you would build, and automated alerts that indicate cache degradation, memory pressure, hot keys, or increasing miss rates.
Sample Answer
High-level goals: ensure cache correctness, low latency, capacity health and predictable hit-rates across CDN → app-layer caches (in-memory LRU) → Redis clusters. Map to SLIs/SLOs, dashboards, heatmaps and automated alerts with runbooks.
SLIs / SLOs (example):
- End-to-end cache hit ratio (requests served from any cache): SLI = hits/(hits+misses); SLO = 99.5% over 30d.
- Redis local hit ratio (keyspace_hits/(hits+misses)): SLO = 98% (7d rolling).
- 95th p99 read latency: CDN < 50ms, app-cache < 5ms, Redis p95 < 10ms; SLO: p95 < target 99% of time.
- Error rate (failed cache ops): < 0.1% per hour.
- Eviction rate: < 0.5% of keys/hour.
Dashboards & heatmaps:
- Global overview: end-to-end hit ratio, aggregate latency percentiles, traffic volume, errors.
- Layered panels: CDN (edge hit ratio, origin fetch rate, 95/99 latency), App-cache (local hit ratio, ops/sec, p50/p95/p99 latency, memory used, eviction rate), Redis clusters (commands/sec, connections, keyspace_hits/misses, memory_used, memory_fragmentation_ratio, evicted_keys, persistence lag, CPU, network IO).
- Heatmaps:
- Key access heatmap (keyspace access frequency over time) to spot hot keys and traffic spikes.
- Miss-rate heatmap by endpoint/service to detect regressions.
- Latency heatmap (time vs percentile) for Redis nodes to find noisy neighbors.
- Per-cluster / per-node drilldowns with shard maps and cross-region replication lag.
Automated alerts (severity, window, runbook):
- Cache degradation (P1):
- Trigger: End-to-end hit ratio drops >3 percentage points vs 1h baseline AND sustained for 5m.
- Action: Pager; runbook: verify upstream deploy, traffic shift, purge storms; rollback deploy; enable emergency cache-warm.
- Increasing miss rate (P1/P2):
- Trigger: Redis miss rate increase >30% relative to 1h baseline AND absolute miss rate >5% for 10m.
- Action: Investigate key TTL/regeneration logic, recent code pushes that changed cache keys or invalidation.
- Memory pressure / approaching max memory (P1):
- Trigger: memory_used > 85% of maxmemory on any Redis node for 5m OR eviction_rate > threshold.
- Action: Autoscale cluster (add shards) or increase maxmemory, trigger controlled eviction policy switch; runbook to add nodes and rebalance.
- Hot keys (P1/P2):
- Trigger: single key responsible for > X% of ops (e.g., >20% of commands/sec) OR key access rate > threshold and corresponding latency increase.
- Action: Auto-flag key, create dedicated counter/TTL, suggest sharding or request-side batching, enable local cache fallback.
- Latency spikes / p99 increase (P1):
- Trigger: p99 Redis command latency > 100ms sustained 5m or node CPU > 80% for 5m.
- Action: Failover to healthy replica, scale read replicas, rebalance slots.
- Eviction storms (P1):
- Trigger: sudden spike in evicted_keys > baseline x10 and increased miss rate.
- Action: Throttle writers, add capacity, temporary rate-limiting.
- CDN-origin fetch spike (P2):
- Trigger: origin_fetch_rate > baseline * 3 for 10m — indicates cache TTLs wrong or purge storm.
- Action: Investigate purges, adjust TTLs, enable origin shields.
Alerting best practices:
- Use composite alerts combining miss rate + latency + memory to reduce noise.
- Multi-window logic: short window for detecting spikes, longer window to confirm trend before paging.
- Annotate alerts with recent deploys, config changes, traffic anomalies.
- Tag metrics with service, env, region, shard for scoped alerts.
Automated mitigations (where safe):
- Auto-scale Redis (add replicas/shards) with cooldowns.
- Automatic replica failover when node latency/health fails heartbeat.
- Controlled cache warming: when miss storm detected, run background precompute for top N keys.
- Rate-limit cache-set operations from noisy clients.
Instrumentation & sampling:
- Emit per-key access counters (sampling for high-cardinality), aggregated by key-prefix.
- Trace requests end-to-end (CDN -> app -> cache -> Redis) capturing cache hit/miss and latencies.
- Export Redis INFO metrics, slowlog, and commandstats to observability backend.
Runbooks & postmortem:
- For each alert include clear steps: scope impact, throttle traffic, scale, rollback, redeploy fix, verify.
- Post-incident: compare heatmaps and traces; add synthetic checks for regressions (e.g., synthetic reads to verify hit-ratio and latency).
This plan balances SLO-driven monitoring, actionable dashboards, targeted heatmaps for hot-key/traffic patterns, and automated alerts + mitigations to keep the caching stack performant and predictable.
Unlock Full Question Bank
Get access to all 39 Caching Strategies & In-Memory Optimization interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.