/ElastiCache & Caching Strategies
Concept
Medium

ElastiCache & Caching Strategies

11 min read·ElastiCacheRedisCachingDVA-C02

A comprehensive deep dive into Amazon ElastiCache — Redis vs Memcached, all caching strategies, Redis data structures, cluster modes, replication, persistence, eviction policies, and common caching patterns for the DVA-C02 exam.


What is Amazon ElastiCache?

Amazon ElastiCache is a fully managed in-memory data store service that supports Redis and Memcached. It sits in front of your database to absorb read traffic, reduce latency from milliseconds to microseconds, and dramatically lower database load.

Core mental model: Your database is slow (disk I/O). Your cache is fast (RAM). Put frequently-read, rarely-changed data in the cache. The application checks cache first — on a hit, skip the database entirely.

When to use ElastiCache:

  • Read-heavy workloads with repetitive queries
  • Session storage across stateless application instances
  • Real-time leaderboards, rate limiting, pub/sub
  • Reducing RDS/DynamoDB read load and cost

Redis vs Memcached

FeatureRedisMemcached
Data structuresStrings, Lists, Sets, Sorted Sets, Hashes, Bitmaps, HyperLogLog, StreamsStrings only
Persistence✅ RDB snapshots + AOF log
Replication✅ Primary + up to 5 replicas
Multi-AZ failover✅ Automatic
Cluster mode (horizontal scale)✅ Up to 500 nodes✅ Multi-threaded sharding
Pub/Sub
Lua scripting
Transactions✅ MULTI/EXEC
Sorted sets / leaderboards
Multi-threaded❌ (single-threaded)
DVA-C02 answerAlmost alwaysOnly when multi-threading or pure simplicity is asked

Exam rule: Any question involving persistence, replication, Multi-AZ, pub/sub, sorted sets, or sessions → Redis. Simple pure key-value cache with multi-threading → Memcached.


Caching Strategies

1. Lazy Loading (Cache-Aside)

The most common pattern. Check cache first; only fetch from DB on a miss, then populate the cache.

Rendering diagram…
javascript
1import { createClient } from 'redis';
2const redis = createClient({ url: process.env.REDIS_URL });
3
4async function getUser(userId) {
5  const cacheKey = `user:${userId}`;
6
7  // 1. Check cache
8  const cached = await redis.get(cacheKey);
9  if (cached) {
10    return JSON.parse(cached);  // cache HIT — database not touched
11  }
12
13  // 2. Cache MISS — fetch from database
14  const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
15
16  // 3. Populate cache with TTL
17  await redis.setEx(cacheKey, 300, JSON.stringify(user)); // expires in 5 min
18
19  return user;
20}
ProsCons
Only caches data that is actually requestedFirst request after miss is slow (cache stampede risk)
Cache failure doesn't break the appStale data until TTL expires
Works well with any data storeRequires explicit cache invalidation on updates

Cache stampede (thundering herd): When many requests simultaneously get a cache miss and all hit the database. Fix with a short lock or probabilistic early expiration.

2. Write-Through

Write to cache and database simultaneously on every write. Cache is always up to date.

javascript
1async function updateUser(userId, data) {
2  // Write to DB first
3  await db.query('UPDATE users SET ... WHERE id = $1', [userId]);
4
5  // Immediately update cache
6  const cacheKey = `user:${userId}`;
7  await redis.setEx(cacheKey, 3600, JSON.stringify(data));
8}
ProsCons
Cache always consistent with DBWrite penalty — every write hits both DB and cache
No stale dataCache filled with data that may never be read
No explicit invalidation neededNode failure before DB write = data loss

3. Write-Behind (Write-Back)

Write to cache immediately; asynchronously flush to the database. Lowest write latency.

javascript
1async function updateUserWriteBehind(userId, data) {
2  const cacheKey = `user:${userId}`;
3
4  // Instant cache write — return immediately
5  await redis.setEx(cacheKey, 3600, JSON.stringify(data));
6
7  // Queue async DB write (processed by a background worker)
8  await redis.lPush('db:write:queue', JSON.stringify({ userId, data, ts: Date.now() }));
9}
ProsCons
Lowest write latencyRisk of data loss if cache crashes before DB write
Database load reducedComplex to implement correctly
Handles write bursts gracefullyEventual consistency — DB may lag behind

4. Read-Through

Cache sits in front of DB. On a miss, the cache itself fetches from the DB (not the application). Application only talks to the cache.

ProsCons
Simpler application codeCache must support read-through (not all do)
Consistent caching logicFirst-request latency on cold start

5. TTL (Time-To-Live)

Set an expiry on every cached key. After expiry, the key is deleted and the next request triggers a cache miss + DB fetch.

javascript
1// TTL strategies
2await redis.setEx('product:123', 60,    JSON.stringify(product)); // 1 min — frequently changing
3await redis.setEx('config:flags', 3600, JSON.stringify(flags));   // 1 hour — rarely changing
4await redis.setEx('user:session', 86400, JSON.stringify(session)); // 1 day — session
5
6// Always combine TTL with Lazy Loading
7// TTL prevents stale data from living forever

Cache Invalidation

The hardest problem in computer science. When data changes in the DB, the cache entry must be removed or updated.

javascript
1async function deleteProduct(productId) {
2  await db.query('DELETE FROM products WHERE id = $1', [productId]);
3
4  // Explicitly invalidate all related cache keys
5  await redis.del(`product:${productId}`);
6  await redis.del('products:list:all');
7  await redis.del('products:list:featured');
8}

Strategies:

  • Delete on write — remove the key; next read fetches from DB (lazy loading)
  • Update on write — overwrite with new value (write-through)
  • TTL expiry — let it expire naturally (acceptable for low-freshness requirements)

Redis Data Structures

Redis is not just a key-value store — it has rich data types, each optimised for specific use cases.

Strings

javascript
1await redis.set('counter', 0);
2await redis.incr('counter');               // atomic increment → 1
3await redis.incrBy('counter', 5);          // → 6
4await redis.setEx('session:abc', 3600, token); // with TTL

Hashes — Object Storage

javascript
1// Store a user object field-by-field (memory efficient)
2await redis.hSet('user:123', {
3  name: 'Alice',
4  email: 'alice@example.com',
5  tier: 'premium',
6});
7
8const name = await redis.hGet('user:123', 'name');
9const user = await redis.hGetAll('user:123');  // returns full object

Lists — Queues and Activity Feeds

javascript
1// Activity feed (newest first)
2await redis.lPush('feed:usr_01', JSON.stringify({ action: 'liked', ts: Date.now() }));
3await redis.lTrim('feed:usr_01', 0, 99); // keep only 100 most recent items
4
5const feed = await redis.lRange('feed:usr_01', 0, 19); // get first 20
6
7// Simple queue (FIFO)
8await redis.rPush('job:queue', JSON.stringify(job));       // enqueue
9const job = await redis.lPop('job:queue');                  // dequeue

Sorted Sets — Leaderboards and Rankings

The most powerful Redis data structure. Each member has a score; the set is always sorted by score.

javascript
1// Game leaderboard
2await redis.zAdd('leaderboard:global', [
3  { score: 9850, value: 'player:alice' },
4  { score: 8200, value: 'player:bob' },
5  { score: 7500, value: 'player:carol' },
6]);
7
8// Update a player's score
9await redis.zIncrBy('leaderboard:global', 150, 'player:bob'); // bob → 8350
10
11// Get top 10 (highest scores first)
12const top10 = await redis.zRangeWithScores('leaderboard:global', 0, 9, { REV: true });
13
14// Get a player's rank (0-indexed, 0 = top)
15const rank = await redis.zRevRank('leaderboard:global', 'player:alice');

Sets — Unique Collections and Relationships

javascript
1// Track unique page visitors (no duplicates)
2await redis.sAdd(`visitors:${today}`, userId);
3const uniqueCount = await redis.sCard(`visitors:${today}`);
4
5// Common friends between two users
6const commonFriends = await redis.sInter('friends:alice', 'friends:bob');
7
8// Check membership O(1)
9const isFollowing = await redis.sIsMember('follows:alice', 'bob');

Session Store Pattern

The most common ElastiCache use case in the DVA-C02 exam. Store sessions in Redis so any instance in your fleet can serve any user — enabling horizontal scaling.

Rendering diagram…
javascript
1// Store session
2const sessionId = crypto.randomUUID();
3await redis.setEx(
4  `session:${sessionId}`,
5  86400,  // 24 hour TTL
6  JSON.stringify({ userId, email, tier, loginAt: Date.now() })
7);
8
9// Read session (any instance)
10const session = JSON.parse(await redis.get(`session:${sessionId}`) ?? 'null');
11if (!session) throw new Error('Session expired');
12
13// Extend session on activity (sliding expiry)
14await redis.expire(`session:${sessionId}`, 86400);

Rate Limiting Pattern

Redis atomic operations make it ideal for distributed rate limiting.

javascript
1async function checkRateLimit(userId, limitPerMinute = 100) {
2  const key = `rate:${userId}:${Math.floor(Date.now() / 60000)}`; // per-minute window
3
4  const count = await redis.incr(key);
5  if (count === 1) {
6    await redis.expire(key, 60); // set TTL on first request
7  }
8
9  if (count > limitPerMinute) {
10    throw new Error('Rate limit exceeded'); // return HTTP 429
11  }
12
13  return { remaining: limitPerMinute - count };
14}

Redis Cluster Modes

Cluster Mode Disabled (Single Shard)

One primary node + up to 5 read replicas. All data fits in one shard. Multi-AZ with automatic failover.

Rendering diagram…
  • Failover: If primary fails, a replica is promoted automatically (Multi-AZ must be enabled)
  • Scaling reads: Add replicas (up to 5)
  • Scaling writes: Increase node size (scale up) — no horizontal write scaling
  • Max data: Limited to single node memory

Cluster Mode Enabled (Multiple Shards)

Data is partitioned across multiple shards using consistent hashing. Each shard has its own primary + replicas.

Rendering diagram…
  • 16,384 hash slots distributed across shards
  • Scale writes by adding shards
  • Up to 500 nodes (250 shards × 2 nodes each, or 90 shards × 6 nodes each)
  • Online resharding — add/remove shards without downtime
FeatureCluster DisabledCluster Enabled
Shards1Up to 500
Scale writes❌ (scale up only)✅ (add shards)
Scale reads✅ (add replicas)
Max nodes6 (1+5)500
Multi-key ops❌ (keys must be on same shard)
Failover✅ per shard

Redis Persistence

RDB (Redis Database Backup)

Point-in-time snapshots saved to disk at configurable intervals.

text
1save 900 1      # snapshot if 1 key changed in 900s
2save 300 10     # snapshot if 10 keys changed in 300s
3save 60 10000   # snapshot if 10,000 keys changed in 60s
  • Pros: Compact file, fast restart, minimal performance impact
  • Cons: Data loss between snapshots (up to the last save interval)

AOF (Append-Only File)

Logs every write operation. On restart, Redis replays the log to reconstruct state.

AOF fsync policyDurabilityPerformance
alwaysEvery write persistedSlowest
everysecMax 1 second of data lossGood balance (recommended)
noOS decides when to flushFastest, least durable
  • Pros: Up to 1 second of data loss with everysec
  • Cons: Larger file than RDB, slower restart

Best practice: Enable both RDB + AOF. Use RDB for fast restarts; AOF for durability. ElastiCache Redis supports AOF with appendonly yes.


Cache Eviction Policies

When the cache is full and a new key must be written, Redis evicts an existing key based on the configured policy:

PolicyBehaviourUse case
noevictionReturn error on write when fullNever use for cache
allkeys-lruEvict least recently used key (any key)Most common for cache
volatile-lruEvict LRU key with TTL setMixed cache + persistent data
allkeys-lfuEvict least frequently used keySkewed access patterns
volatile-lfuEvict LFU key with TTL setMixed with TTL keys
allkeys-randomEvict random keyUniform access pattern
volatile-randomEvict random key with TTLRarely useful
volatile-ttlEvict key with shortest TTLExpire-prioritised eviction

Security

bash
1# Redis AUTH token (password)
2aws elasticache create-replication-group \
3  --replication-group-id my-redis \
4  --auth-token "MyStrongPassword123!" \
5  --transit-encryption-enabled   # TLS in transit
6  --at-rest-encryption-enabled   # encrypted at rest (KMS)
7
8# IAM auth (Redis 7.0+) — use AWS credentials instead of password
9aws elasticache create-user \
10  --user-id app-user \
11  --user-name app-user \
12  --engine redis \
13  --authentication-type iam

ElastiCache is deployed inside a VPC. Access is controlled via Security Groups — no public internet access by default.


DVA-C02 Quick Reference

TopicKey Fact
Best caching strategyLazy Loading + TTL (most flexible)
Always fresh cacheWrite-Through
Lowest write latencyWrite-Behind
Persistence supportRedis only (not Memcached)
Replication + Multi-AZRedis only
Pub/Sub supportRedis only
Sorted sets / leaderboardsRedis only
Multi-threadedMemcached
Session storageRedis (shared across fleet)
Cluster disabled max replicas5 read replicas
Cluster enabled max nodes500
Hash slots16,384
Scale writesCluster Mode Enabled (add shards)
RDB vs AOFRDB = snapshots; AOF = operation log
Most common eviction policyallkeys-lru
Rate limiting toolRedis INCR + EXPIRE
Cache always inVPC — no public access

Practice Questions3

medium

Q1. A developer is choosing between ElastiCache for Redis and ElastiCache for Memcached for a session store that must persist data across node restarts. Which should be chosen and why?


Select one answer before revealing.

easy

Q2. A developer implements a caching layer using ElastiCache for Redis. When a cache miss occurs, the application queries the database and writes the result to the cache. What caching strategy is this?


Select one answer before revealing.

medium

Q3. A developer uses ElastiCache for Redis to cache database query results. After deploying a new version of the application that uses different query logic, stale data is returned. Which approach correctly invalidates affected cache entries?


Select one answer before revealing.