Tuần 10 — Redis Mastery
“Redis không phải ‘cache nhanh’. Redis là một in-memory data structure server. Coi nó như cache = bạn dùng 5% năng lực. Master nó = bạn unlock leaderboard, rate limiter, pub/sub, geo, stream, lock — tất cả trong 1 tool.”
Tags: database redis valkey cache data-structures operations Thời lượng: 7 ngày (4-6h/ngày) Prerequisites: Tuan-01-DB-Internals-Refresh (in-memory concepts) Liên quan: Tuan-06-Cache-Strategy (SD course) · Tuan-13-Search-Engines-ES
1. Context & Why
1.1 Redis trong 2024-2026: lịch sử + thực trạng
timeline title Redis evolution 2009 : Redis 1.0 by Salvatore Sanfilippo 2013 : Redis Cluster 2015 : Redis 3 - sentinel HA 2018 : Redis 5 - Streams 2020 : Redis 6 - SSL, multi-threaded I/O 2022 : Redis 7 - Functions, Sharded Pub/Sub 2024 : Redis 7.4 license change to RSALv2/SSPL 2024 : Valkey fork (BSD-3-Clause, hosted by Linux Foundation, governed by TSC) 2025 : Valkey 8 GA - many Redis 7+ users migrate
License change (March 2024): Redis Inc moved Redis to RSALv2+SSPL — not OSS by OSI definition. Linux Foundation forked → Valkey (BSD-3). Most cloud providers (AWS, Google, Oracle) adopted Valkey.
Pattern 2024-2026: Code 99% compatible. Choose Valkey for OSS purity, Redis for Redis Stack features (RediSearch, RedisJSON).
1.2 Vai trò trong stack
graph LR Client[Client] --> LB[Load Balancer] LB --> App[App Server] App -->|read-through| R[(Redis<br/>cache)] App -->|cache miss| PG[(Postgres)] App -->|rate limit| R App -->|session| R App -->|queue| R App -->|leaderboard| R App -->|pub/sub| R R -.eviction.-> Trash[/dev/null]
Redis use cases:
- Cache — query result, page fragment, API response
- Session store — user session, JWT denylist
- Rate limiter — token bucket, sliding window
- Leaderboard — sorted set
- Queue — list (legacy) hoặc Stream (modern)
- Pub/Sub — real-time notifications
- Distributed lock — Redlock pattern
- Counter — atomic INCR
- Geospatial — nearby search
- Real-time analytics — HyperLogLog, Bitmap, TimeSeries
1.3 Mục tiêu tuần
- Master 10 data structures, biết encoding internal
- Persistence: RDB vs AOF — pick đúng cho từng use case
- HA: Sentinel vs Cluster
- Cache patterns: aside, through, behind, refresh-ahead
- Atomic patterns: MULTI/EXEC, Lua, WATCH
- Anti-patterns: KEYS, FLUSHALL trong production, không TTL
- Streams (Redis 5+) cho event processing
- Redis Cluster topology + resharding
- Tools: redis-cli, redis-benchmark, redis-stat
1.4 Tham chiếu
- Redis Documentation — https://redis.io/docs/
- Valkey Documentation — https://valkey.io/
- Redis in Action — Josiah L. Carlson (older but solid)
- The Little Redis Book — Karl Seguin (free)
- Redis Cluster Spec — https://redis.io/docs/reference/cluster-spec/
- Aphyr — Jepsen on Redis — distributed safety analysis
2. Data Structures Deep Dive
2.1 String — most basic
SET user:42:name "Alice"
GET user:42:name
SET counter 0
INCR counter # atomic +1
INCRBY counter 10
DECR counter
APPEND log:today "new entry\n"
GETRANGE user:42:name 0 4
SETEX session:abc 3600 "data" # SET with TTL 1h
SETNX lock:resource "owner" # SET if not exists
MSET k1 v1 k2 v2 k3 v3 # multi-set atomic
MGET k1 k2 k3Internal encoding (Redis 7):
- Short strings (<44 chars):
embstr(embedded with header) - Long strings:
raw(separate alloc) - Integers:
int(8-byte signed)
Atomic ops: INCR, DECR, INCRBY, GETSET, INCRBYFLOAT.
2.2 Hash — Field-value map
HSET user:42 name "Alice" email "[email protected]" age 30
HGET user:42 name
HGETALL user:42
HMGET user:42 name email
HINCRBY user:42 age 1
HDEL user:42 age
HEXISTS user:42 phone
HKEYS user:42
HLEN user:42Encoding:
- Small (
<128fields, values<64bytes):listpack(Redis 7+, replaces ziplist) - Large: hashtable
Use case: user profile, config, structured cache. Like row in DB.
Pattern: instead of SET user:42:name, SET user:42:email, etc. → HSET user:42 name ... email .... One key, less memory, atomic per-user ops.
2.3 List — Doubly linked list (or listpack)
LPUSH queue "job1"
LPUSH queue "job2"
RPUSH queue "job3"
LRANGE queue 0 -1 # all
LPOP queue # FIFO out from RPUSH
RPOP queue
BLPOP queue 0 # blocking pop
LLEN queue
LINSERT queue BEFORE "job2" "newjob"Encoding:
- Small: listpack
- Large: quicklist (linked list of listpacks)
Use cases:
- Queue (legacy — Redis Stream is modern)
- Recent items (LPUSH + LTRIM 0 N)
- Activity feed
# Recent 100 events
LPUSH events:user:42 "event_data"
LTRIM events:user:42 0 992.4 Set — Unique unordered
SADD tags:post:1 "rust" "database" "performance"
SISMEMBER tags:post:1 "rust" # exists?
SMEMBERS tags:post:1
SCARD tags:post:1 # size
SREM tags:post:1 "performance"
SUNION tags:post:1 tags:post:2 # union
SINTER tags:post:1 tags:post:2 # intersect
SDIFF tags:post:1 tags:post:2 # diff
SRANDMEMBER tags:post:1 3 # random 3
SPOP tags:post:1 # pop randomEncoding:
- Small int-only:
intset - Small mixed:
listpack(Redis 7+) - Large:
hashtable
Use cases:
- Tags, categories
- Unique visitors
- Friend lists
- Allowlist/denylist
2.5 Sorted Set (ZSet) — Score-ordered set
ZADD leaderboard 1500 "alice"
ZADD leaderboard 1800 "bob"
ZADD leaderboard 1200 "carol"
ZRANGE leaderboard 0 -1 WITHSCORES # asc
ZREVRANGE leaderboard 0 9 WITHSCORES # top 10
ZRANGEBYSCORE leaderboard 1000 2000 # range
ZRANK leaderboard "alice" # position
ZSCORE leaderboard "alice"
ZINCRBY leaderboard 100 "alice" # alice +100
ZREM leaderboard "carol"
ZCOUNT leaderboard 1500 2000
ZADD leaderboard XX GT 1900 "alice" # only if > currentEncoding:
- Small: listpack
- Large: skiplist + hashtable
Use cases:
- Leaderboard (game score, view count)
- Time-series queue (score = timestamp)
- Priority queue (score = priority)
- Rate limiter (score = timestamp, window)
- Pagination (score = sort key, member = entity)
2.6 Stream — Append-only log
XADD events * action "login" user "alice" # * = auto ID
XADD events 1700000000000-0 action "logout" user "alice"
XLEN events
XRANGE events - +
XREAD COUNT 100 STREAMS events 0 # read from start
# Consumer groups (multi-consumer at-least-once)
XGROUP CREATE events workers $ MKSTREAM
XREADGROUP GROUP workers worker1 COUNT 10 STREAMS events >
XACK events workers <id> # confirm processed
XPENDING events workers # see unacked
XCLAIM events workers worker2 60000 <id> # claim from dead workerUse cases:
- Event sourcing
- At-least-once message delivery
- Activity feed
- Real-time analytics ingestion
Streams replaced Lists as Redis-native queue mechanism (Redis 5+).
2.7 HyperLogLog — Cardinality estimation
PFADD visitors:2026-05-16 "ip1" "ip2" "ip3" "ip1"
PFCOUNT visitors:2026-05-16 # ~3 (probabilistic)
PFMERGE visitors:total visitors:2026-05-16 visitors:2026-05-15
PFCOUNT visitors:total12KB fixed memory regardless of cardinality. Error ~0.81%.
Use cases:
- Unique visitors per day
- Distinct events
- Unique search terms
2.8 Bitmap — Bit manipulation
SETBIT user:42:active:2026-05 16 1 # day 16 = active
GETBIT user:42:active:2026-05 16
BITCOUNT user:42:active:2026-05 # active days
BITOP AND result user:42:active:2026-05 user:43:active:2026-05Use cases:
- User activity tracking
- Feature flag per user
- A/B test bucketing
- Permission bitmap
2.9 Geospatial — Built on ZSet
GEOADD locations -122.4194 37.7749 "san_francisco"
GEOADD locations -73.9857 40.7484 "new_york"
GEOSEARCH locations FROMLONLAT -122.4 37.7 BYRADIUS 100 km ASC COUNT 10
GEODIST locations "san_francisco" "new_york" km
GEOPOS locations "san_francisco"Use cases:
- Nearby search
- Driver matching (Uber-style)
- Store locator
Note: full-blown geo workload → PostGIS more powerful. Redis Geo for simple within-radius.
2.10 Time series (Redis Stack)
RedisTimeSeries module:
TS.CREATE temperature RETENTION 86400000 LABELS sensor 1
TS.ADD temperature * 22.5
TS.RANGE temperature - + AGGREGATION avg 60000 # avg per minute
TS.MRANGE - + AGGREGATION avg 60000 FILTER sensor=1Niche but useful for sensor data, metrics.
3. Memory Model
3.1 Encoding optimization
Redis automatically picks encoding based on data. Settings:
CONFIG SET hash-max-listpack-entries 128
CONFIG SET hash-max-listpack-value 64
CONFIG SET zset-max-listpack-entries 128
CONFIG SET set-max-listpack-entries 128Smaller listpack thresholds → faster lookup but more memory. Defaults reasonable.
3.2 Per-key overhead
Approximate:
- Each key adds ~50 bytes overhead (hash table entry)
- Plus TTL: ~50 bytes
- Plus encoding-specific
→ 1M small keys × 100 bytes overhead = 100MB just for keys.
Tip: use Hash to group related fields under one key:
SET user:42:name,SET user:42:email, … → 1MB overhead for 10K users with 10 fieldsHSET user:42 name ... email ...→ 50KB overhead
3.3 Memory limit & eviction
CONFIG SET maxmemory 8gb
CONFIG SET maxmemory-policy allkeys-lruPolicies:
noeviction(default) — return error on write when fullallkeys-lru— evict LRU across all keysvolatile-lru— evict LRU among TTL keysallkeys-lfu— least frequently used (Redis 4+)volatile-lfuallkeys-randomvolatile-randomvolatile-ttl— shortest TTL first
Pattern 2024:
- Cache use case:
allkeys-lfu - Mixed (cache + durable data with TTL):
volatile-lru - Database use case:
noeviction(you’d rather error than lose data)
3.4 Monitor memory
INFO memory
# used_memory_human, used_memory_peak_human, mem_fragmentation_ratioFragmentation ratio:
- 1.0-1.5 OK
-
1.5 → defragment via
CONFIG SET activedefrag yes - <1.0 → swap! Bad. Add RAM or reduce maxmemory.
3.5 redis-cli —bigkeys, —memkeys
redis-cli --bigkeys # find biggest keys per data type
redis-cli --memkeys --memkeys-samples 1000 # memory by sampling
redis-cli memory usage key:name # bytes used by specific key4. Persistence
4.1 RDB — Point-in-time snapshot
save 3600 1 # every 1h if >=1 change
save 300 100 # every 5min if >=100 changes
save 60 10000 # every 1min if >=10000 changesMechanism:
- Postgres-style
fork()→ child process - Child writes snapshot to
dump.rdb - Parent continues serving (copy-on-write)
Pros:
- Compact file
- Fast recovery (single file load)
- Periodic backup
Cons:
- Lose data since last snapshot (minutes)
fork()heavy on large datasets
4.2 AOF — Append-Only File
Every write → append to log. Replay on restart.
appendonly yes
appendfsync always # safest, slowest
appendfsync everysec # default, good balance
appendfsync no # OS decides (~30s window)Pros:
- Durable (<1s data loss with
everysec) - Easier to recover partial
- Human-readable (mostly)
Cons:
- Larger file
- Slower restart (replay log)
- Rewrite (compaction) needed periodically
4.3 Combination
Best practice 2024: both enabled.
- RDB for backup, fast recovery baseline
- AOF for durability
Redis 7+ multi-part AOF: combines RDB base + AOF tail → fast recovery + low data loss.
4.4 Replication != Backup
Replication propagates corruption. If app deletes wrong data, replica deletes too. → Take RDB/AOF snapshot, store offsite, test restore.
5. High Availability
5.1 Sentinel — automatic failover
graph TB Sentinel1[Sentinel 1] Sentinel2[Sentinel 2] Sentinel3[Sentinel 3] Master[(Redis Master)] Replica1[(Replica 1)] Replica2[(Replica 2)] Sentinel1 -.monitor.-> Master Sentinel2 -.monitor.-> Master Sentinel3 -.monitor.-> Master Sentinel1 -.monitor.-> Replica1 Sentinel1 -.monitor.-> Replica2 Master --> Replica1 Master --> Replica2 Client[App Client] -.discover via Sentinel.-> Sentinel1 Client --> Master
Setup:
- 1 master + N replicas (
replicaof master_host 6379) - 3+ Sentinel nodes (odd, for quorum)
- Client uses Sentinel SDK to discover master
Sentinel detects master down → promotes a replica → updates clients. RTO ~30s typical.
5.2 Cluster — sharded HA
graph TB subgraph "Shard 1: hash slots 0-5461" M1[Master 1] R1a[Replica 1a] R1b[Replica 1b] M1 --> R1a M1 --> R1b end subgraph "Shard 2: hash slots 5462-10922" M2[Master 2] R2a[Replica 2a] M2 --> R2a end subgraph "Shard 3: hash slots 10923-16383" M3[Master 3] R3a[Replica 3a] M3 --> R3a end Client --> M1 Client --> M2 Client --> M3
16384 hash slots. Each master owns range. Key → CRC16 % 16384 → slot → master.
Setup:
redis-cli --cluster create node1:6379 node2:6379 node3:6379 \
node4:6379 node5:6379 node6:6379 --cluster-replicas 15.3 Cluster operations
# Check cluster status
redis-cli -c -h node1 CLUSTER NODES
redis-cli -c -h node1 CLUSTER INFO
# Reshard (move slots)
redis-cli --cluster reshard node1:6379
# Add node
redis-cli --cluster add-node new:6379 existing:6379
# Remove node
redis-cli --cluster del-node existing:6379 <node_id>5.4 Multi-key operations + hashtags
Cluster forbids multi-key ops across shards. Solution: hashtags.
# Without hashtag: keys may go different shards
SET user:42 ... ; SET user:43 ...
# With hashtag {tag}: same hash slot
SET {user}:42 ... ; SET {user}:43 ...
MGET {user}:42 {user}:43 # works - same slotUse sparingly — hashtag = manual sharding, can lead to hot shard.
5.5 Cluster vs Sentinel decision
| Sentinel | Cluster | |
|---|---|---|
| HA | ✓ | ✓ |
| Sharding | ✗ | ✓ |
| Setup complexity | Lower | Higher |
| Client support | Universal | Most modern clients |
| Multi-key ops | All | Only same hashtag |
| Throughput limit | Single master | Scales horizontally |
| Use case | Small-medium, single-shard | Large, write-heavy |
6. Caching Patterns
6.1 Cache-aside (Lazy loading)
def get_user(id):
cached = redis.get(f"user:{id}")
if cached:
return json.loads(cached)
user = db.query("SELECT * FROM users WHERE id = %s", id)
redis.setex(f"user:{id}", 300, json.dumps(user))
return user- Pros: only cache what’s needed
- Cons: cache miss → 2 roundtrips; stale data possible
6.2 Write-through
def update_user(id, data):
db.execute("UPDATE users SET ... WHERE id = %s", data, id)
redis.setex(f"user:{id}", 300, json.dumps(data))- Pros: cache always fresh
- Cons: write latency higher; cache stores unused data
6.3 Write-behind (Write-back)
def update_user(id, data):
redis.setex(f"user:{id}", 300, json.dumps(data))
queue.publish("user_updated", {"id": id, "data": data})
# Worker processes queue, writes to DB async- Pros: fast writes
- Cons: data loss risk if Redis crashes; complexity
6.4 Cache invalidation
3 strategies:
- TTL — set expiry, accept staleness
- Explicit DELETE on update — risk dual-write inconsistency
- Listen to CDC — Postgres changes → invalidate cache via Debezium
6.5 Cache stampede prevention
Many users request same expired key → 1000s hit DB simultaneously.
Locking pattern:
def get_user(id):
cached = redis.get(f"user:{id}")
if cached:
return json.loads(cached)
# Try acquire lock
lock_acquired = redis.set(f"lock:user:{id}", "1", ex=10, nx=True)
if lock_acquired:
try:
user = db.query(...)
redis.setex(f"user:{id}", 300, json.dumps(user))
return user
finally:
redis.delete(f"lock:user:{id}")
else:
# Wait for other process to populate
time.sleep(0.05)
return get_user(id) # retryAlternative: stale-while-revalidate, probabilistic early expiration.
6.6 Hot key problem
Single key receiving massive traffic (e.g., trending product viewed millions/sec) → bottleneck on shard hosting that key.
Solutions:
- Replicate to multiple keys:
product:42:shard:0,product:42:shard:1, … randomize on read - Local cache (multi-tier): app-level cache → Redis → DB
- Pre-aggregate / read replicas
7. Atomic Patterns
7.1 INCR for counters
INCR page:views
INCR page:views:2026-05-16
HINCRBY user:42 visits 1Atomic, lock-free. Single-shard fast.
7.2 SETNX for distributed lock
SET lock:resource owner123 NX PX 30000
# NX = only set if not exists
# PX = expire in 30s
# Release - safe (only own lock)
EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 lock:resource owner123Redlock: multi-node lock algorithm. Acquire from majority of N independent Redis nodes. Aphyr’s Jepsen has criticized — not 100% safe under all assumptions. For most apps OK.
7.3 MULTI/EXEC transactions
MULTI
SET key1 val1
INCR counter
LPUSH queue item
EXECAll queued, then executed atomically. Compare and swap with WATCH:
while True:
redis.watch("balance:42")
balance = int(redis.get("balance:42"))
if balance < amount:
redis.unwatch()
raise InsufficientFunds
pipe = redis.pipeline()
pipe.multi()
pipe.decrby("balance:42", amount)
pipe.lpush("transactions:42", str(amount))
try:
pipe.execute()
break # success
except WatchError:
continue # someone modified, retry7.4 Lua scripting
EVAL "
local current = tonumber(redis.call('GET', KEYS[1]) or '0')
if current < tonumber(ARGV[1]) then
return -1
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return current - tonumber(ARGV[1])
" 1 balance:42 100Lua script atomic (no other commands interleave). Useful for:
- Complex atomic operations
- Reducing roundtrips
- Conditional logic
Redis 7+ added Functions (server-stored Lua) for organized scripts.
7.5 Pipelining
Not atomic, just batched (reduces roundtrips):
pipe = redis.pipeline()
for i in range(1000):
pipe.set(f"key:{i}", i)
pipe.execute()
# 1 roundtrip vs 1000Vs MULTI/EXEC:
- Pipeline: just batch commands, no atomicity
- MULTI/EXEC: atomic transaction (no interleave)
8. Patterns by Use Case
8.1 Rate Limiter — token bucket via Lua
EVAL "
local key = KEYS[1]
local now = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local max_tokens = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])
local data = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(data[1] or max_tokens)
local last_refill = tonumber(data[2] or now)
local elapsed = now - last_refill
tokens = math.min(max_tokens, tokens + elapsed * refill_rate)
if tokens < cost then
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600)
return 0
end
tokens = tokens - cost
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600)
return 1
" 1 ratelimit:user:42 1700000000 10 100 1
-- Refill 10/sec, max 100, cost 18.2 Sliding window rate limiter
ZADD requests:user:42 <timestamp> <unique_id>
ZREMRANGEBYSCORE requests:user:42 0 <timestamp_minus_window>
ZCARD requests:user:42
# Compare with limit, allow/deny8.3 Leaderboard
ZADD leaderboard:global 1500 alice
ZADD leaderboard:weekly:2026-w20 100 alice
# User rank
ZREVRANK leaderboard:global alice
# Top 10
ZREVRANGE leaderboard:global 0 9 WITHSCORES
# Around user (10 above + 10 below)
ZREVRANGEBYSCORE leaderboard:global +inf -inf LIMIT (rank-10) 218.4 Queue with priorities
ZADD jobs <priority_score> <job_id>
ZPOPMIN jobs # pop lowest score (highest priority if score = priority)Or use Streams for at-least-once delivery.
8.5 Session store
HMSET session:abc123 user_id 42 created 1700000000
EXPIRE session:abc123 3600
HGET session:abc123 user_id # validate8.6 Caching aggregate (Top N items)
# Top 10 viewed products
ZINCRBY top_products 1 "product:42"
ZREVRANGE top_products 0 9 WITHSCORES8.7 Distributed lock fencing token
INCR lock:resource:counter # fencing token
# Use token to verify ordering at downstream8.8 Job deduplication
SADD jobs:processed_today <job_id>
SISMEMBER jobs:processed_today <job_id>
EXPIRE jobs:processed_today 864009. Streams Deep Dive
9.1 Why Streams over List
Lists:
- LPUSH/RPOP — simple FIFO
- BLPOP blocking
- ❌ Once popped, gone
- ❌ No consumer groups
- ❌ No replay
Streams:
- Persistent log (like Kafka)
- Consumer groups (workers split load)
- At-least-once delivery
- Replay any time
- Better for event-driven architecture
9.2 Consumer group
XGROUP CREATE events workers $ MKSTREAM
# Worker loop
while True:
XREADGROUP GROUP workers worker1 COUNT 10 STREAMS events >
# Process each message
XACK events workers <id>
# Dead worker recovery - claim pending
XPENDING events workers IDLE 60000 # idle >60s
XCLAIM events workers worker2 60000 <id>9.3 Trim stream
XTRIM events MAXLEN 1000 # keep latest 1000
XTRIM events MINID 1700000000000 # delete older than timestamp
XADD events MAXLEN 1000 * data hello # auto-trim on add9.4 Use cases
- Activity feed with rewind
- Event sourcing light
- Worker job distribution with at-least-once
- Real-time analytics ingest
For full Kafka semantics → use Kafka, not Streams. Streams good for single-cluster, lower throughput.
10. Operations & Tuning
10.1 Persistence config trade-offs
| Workload | Persistence config |
|---|---|
| Pure cache (data recoverable from DB) | No AOF, optional RDB hourly |
| Session store | AOF everysec, RDB daily |
| Counter / atomic state | AOF everysec, RDB hourly |
| Queue (durable) | AOF always or everysec |
| Database role | AOF + RDB combined |
10.2 Slow log
CONFIG SET slowlog-log-slower-than 10000 # 10ms
SLOWLOG GET 10
SLOWLOG RESETFind slow commands. KEYS scan, large MGET, etc.
10.3 Latency monitor
CONFIG SET latency-monitor-threshold 100 # ms
LATENCY LATEST
LATENCY HISTORY eventReasons latency spike: fork (RDB save), AOF rewrite, expire bursts, network.
10.4 Common config tune
maxmemory 16gb
maxmemory-policy allkeys-lfu
tcp-keepalive 60
timeout 0 # disable idle timeout
# Persistence
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite yes # don't fsync during AOF rewrite (latency safety)
# Snapshotting
save 3600 1
save 300 100
# Slow log
slowlog-log-slower-than 10000
slowlog-max-len 128
# Latency monitor
latency-monitor-threshold 100
# Active defrag (Redis 4+)
activedefrag yes
active-defrag-threshold-lower 10
active-defrag-threshold-upper 10010.5 Backup
# Force RDB save
redis-cli BGSAVE
# Copy out
cp /var/lib/redis/dump.rdb /backup/
# Or stream from running
redis-cli --rdb /backup/dump.rdbFor Cluster: per-node, ensure all shards captured.
10.6 Monitor with INFO
redis-cli INFO server # version, uptime
redis-cli INFO clients # connections
redis-cli INFO memory # memory stats
redis-cli INFO stats # commands processed, hit/miss
redis-cli INFO replication # master/replica status
redis-cli INFO persistence # last RDB, AOF status
redis-cli INFO commandstats # per-command stats10.7 Prometheus exporter
oliver006/redis_exporter:
docker run -d -p 9121:9121 \
-e REDIS_ADDR=redis:6379 \
oliver006/redis_exporterScrape to Prometheus. Grafana community dashboards available.
11. Anti-patterns
| Pattern | Why bad | Fix |
|---|---|---|
KEYS * in production | O(N), blocks Redis | SCAN (incremental) |
FLUSHALL accidentally | Wipe all data | Rename command in config, or remove access |
| No TTL on cache keys | Memory grows forever | Always SETEX or expire policy |
| Large value (1MB+) | Slow op, network burst | Split / store in DB |
| LRANGE 0 -1 on huge list | Slow + memory | LRANGE with limit |
| Many small keys vs Hash | Wasted overhead | HSET grouping |
| Pub/Sub for critical messages | No durability, no replay | Use Streams |
| Synchronous client in hot path | Blocks app thread | Pipelining or async client |
| Run on default port no auth | Public internet attack | requirepass, bind localhost, network isolation |
| Cache without invalidation strategy | Stale data | TTL + explicit invalidation |
| Cache stampede | DB hit by 1000s simultaneously | Lock-based fetch |
| Hot key | Single shard bottleneck | Sharded keys, multi-tier cache |
| Use Redis as primary DB | Memory cost, no joins | Use proper DB |
12. Redis Stack + Modules
12.1 RediSearch
Full-text search inside Redis.
FT.CREATE idx:products SCHEMA name TEXT price NUMERIC tags TAG SORTABLE
FT.SEARCH idx:products "iPhone" LIMIT 0 10
FT.SEARCH idx:products "@price:[100 500] @tags:{rust}"Faster than Elasticsearch for in-memory data. Use for products catalog, autocomplete.
12.2 RedisJSON
Native JSON with path operations.
JSON.SET user:42 $ '{"name":"Alice","age":30}'
JSON.GET user:42 $.name
JSON.SET user:42 $.age 31
JSON.NUMINCRBY user:42 $.age 112.3 RedisBloom
Bloom filter + Cuckoo filter + Top-K + Count-Min Sketch.
BF.RESERVE seen 0.01 1000000 # 1% error, 1M items
BF.ADD seen "user42"
BF.EXISTS seen "user42" # probably yes12.4 RedisGraph (deprecated 2024)
Cypher-style graph. Discontinued by Redis Inc.
12.5 RedisTimeSeries
Already covered section 2.10.
13. Valkey — fork status 2024-2026
Code-compatible with Redis 7.2. Maintained by Linux Foundation, AWS, Google, Oracle, Snap, etc.
Use cases:
- AWS ElastiCache → Valkey option
- Google Memorystore → Valkey
- Self-host: 99% compat, drop-in
- Redis Stack modules NOT available on Valkey
Decision 2024-2026:
- Need Redis modules (Search, JSON, etc) → stick Redis or buy Redis Cloud
- Pure OSS preference → Valkey
- Existing Redis < 7.4 → no urgent migrate, but plan
14. Lab
14.1 Day 1: Data structures
docker run -d -p 6379:6379 redis:7
redis-cli
# Practice each data structure: String, Hash, List, Set, ZSet, Stream, HLL, Bitmap, Geo
# Make notes on common commands14.2 Day 2: Memory
# Generate 1M keys with String
for i in {1..1000000}; do redis-cli SET "key:$i" "value$i" > /dev/null; done
redis-cli INFO memory | grep used_memory_human
# Same data in Hash
redis-cli FLUSHALL
for i in {1..1000000}; do redis-cli HSET "users" "user:$i" "value$i" > /dev/null; done
# Compare memory14.3 Day 3: Persistence experiment
Configure AOF + RDB. Generate writes. Force kill Redis. Restart. Compare data loss.
14.4 Day 4: Cluster setup
Use docker-compose with 6 nodes (3 masters + 3 replicas). redis-cli --cluster create. Test resharding.
14.5 Day 5: Patterns
Implement 3 patterns:
- Rate limiter (Lua script)
- Leaderboard with daily reset
- Distributed lock with fencing
14.6 Day 6: Cache patterns
Build a cache-aside + stampede protection layer in Python/Node. Load test with 1000 concurrent users.
14.7 Day 7: Streams worker
Multi-consumer worker pool processing stream messages. Implement worker failure recovery (XCLAIM).
15. Self-check
- 10 data structures Redis + 1 use case mỗi cái?
- RDB vs AOF — pick khi nào? Combine?
- Sentinel vs Cluster — khác nhau, pick khi nào?
- Hashtag là gì? Trade-off?
- Cache stampede — pattern phòng ngừa?
- Hot key problem — 3 solutions?
- Streams vs List vs Pub/Sub — pick khi nào?
- INCR atomic — vì sao đặc biệt với Redis?
- Lua script vs MULTI/EXEC vs Pipeline?
- Valkey vs Redis 7.4 — chọn khi nào?
16. Tiếp theo
Bài tiếp: Tuan-11-DynamoDB-Cassandra — wide-column NoSQL với access pattern thinking.
Tuần 10 hoàn thành. Redis is a data structure server, not a cache. Cập nhật: 2026-05-16