Episode 3 — NodeJS MongoDB Backend Architecture / 3.16 — Caching with Redis
3.16.b — Introduction to Redis
Redis (Remote Dictionary Server) is an open-source, in-memory data structure store used as a database, cache, message broker, and streaming engine, supporting rich data types like Strings, Lists, Sets, Sorted Sets, and Hashes.
<< Previous: 3.16.a — What is Caching | Next: 3.16.c — Redis with Node.js >>
1. What is Redis?
Redis stands for Remote Dictionary Server. Created by Salvatore Sanfilippo in 2009, it is an in-memory key-value store that has become the de facto standard for caching in modern applications.
Unlike traditional databases that store data on disk, Redis keeps the entire dataset in memory, making reads and writes extraordinarily fast -- typically sub-millisecond.
Traditional Database (Disk-based):
Write → Disk I/O → Acknowledge (~5-50ms)
Read → Disk I/O → Parse → Return (~5-50ms)
Redis (Memory-based):
Write → RAM → Acknowledge (~0.1-0.5ms)
Read → RAM → Return (~0.1-0.5ms)
Key Characteristics
| Characteristic | Description |
|---|---|
| In-memory | All data lives in RAM for sub-millisecond access |
| Single-threaded | One thread processes all commands sequentially (no race conditions) |
| Rich data types | Strings, Hashes, Lists, Sets, Sorted Sets, Streams, and more |
| Persistence | Optional disk persistence via RDB snapshots or AOF log |
| Atomic operations | All commands are atomic; transactions via MULTI/EXEC |
| Pub/Sub | Built-in publish/subscribe messaging |
| Replication | Master-replica architecture for high availability |
| Clustering | Horizontal scaling via Redis Cluster |
| Lua scripting | Server-side scripting for complex operations |
2. Installing Redis
macOS (Homebrew)
# Install Redis
brew install redis
# Start Redis server
brew services start redis
# Or start manually (foreground)
redis-server
# Verify it is running
redis-cli ping
# Output: PONG
Ubuntu / Debian
sudo apt update
sudo apt install redis-server
# Start the service
sudo systemctl start redis-server
sudo systemctl enable redis-server
# Verify
redis-cli ping
# Output: PONG
Docker (Recommended for Development)
# Pull and run Redis in Docker
docker run --name my-redis -p 6379:6379 -d redis
# Connect with redis-cli inside the container
docker exec -it my-redis redis-cli
# Stop/start
docker stop my-redis
docker start my-redis
Verify Installation
redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> SET hello "world"
OK
127.0.0.1:6379> GET hello
"world"
127.0.0.1:6379> quit
3. Redis Data Types
Redis is far more than a simple key-value store. It supports rich data structures, each with specialized commands.
3.1 Strings
The simplest data type. A String can hold text, numbers, or binary data (up to 512 MB).
# Set and get
SET name "Alice"
GET name # "Alice"
# Set with expiration (seconds)
SET session "abc123" EX 3600
# Set with expiration (milliseconds)
SET session "abc123" PX 60000
# Set only if key does NOT exist (useful for locks)
SETNX lock:order "processing"
# Increment/Decrement (atomic counters)
SET visitors 0
INCR visitors # 1
INCR visitors # 2
INCRBY visitors 10 # 12
DECR visitors # 11
DECRBY visitors 5 # 6
# Multiple set/get
MSET first "Alice" last "Smith" age "30"
MGET first last age # "Alice" "Smith" "30"
# String length
STRLEN name # 5
# Append
APPEND name " Johnson" # "Alice Johnson"
3.2 Lists
Ordered collections of strings. Implemented as linked lists, so pushing/popping from head or tail is O(1).
# Push to left (head) / right (tail)
LPUSH tasks "Task 1"
LPUSH tasks "Task 2"
RPUSH tasks "Task 3"
# List is now: ["Task 2", "Task 1", "Task 3"]
# Get range (0-based index, -1 = last element)
LRANGE tasks 0 -1 # All items
LRANGE tasks 0 1 # First 2 items
# Pop from left / right
LPOP tasks # "Task 2"
RPOP tasks # "Task 3"
# Get length
LLEN tasks # 1
# Get by index
LINDEX tasks 0 # "Task 1"
# Trim (keep only range)
RPUSH logs "log1" "log2" "log3" "log4" "log5"
LTRIM logs 0 2 # Keep only first 3: ["log1", "log2", "log3"]
# Blocking pop (waits for data, great for queues)
BLPOP queue 30 # Wait up to 30 seconds for an item
Use cases: Message queues, recent activity feeds, task queues, chat history.
3.3 Sets
Unordered collections of unique strings. No duplicates allowed.
# Add members
SADD tags "javascript" "nodejs" "redis"
SADD tags "javascript" # 0 (already exists, not added)
# Get all members
SMEMBERS tags # {"javascript", "nodejs", "redis"}
# Check membership
SISMEMBER tags "redis" # 1 (true)
SISMEMBER tags "python" # 0 (false)
# Count members
SCARD tags # 3
# Remove a member
SREM tags "redis"
# Set operations
SADD frontend "html" "css" "javascript" "react"
SADD backend "nodejs" "javascript" "python" "redis"
SINTER frontend backend # {"javascript"} (intersection)
SUNION frontend backend # All unique members (union)
SDIFF frontend backend # {"html", "css", "react"} (in frontend but not backend)
# Random member
SRANDMEMBER tags # Random element
SPOP tags # Remove and return random element
Use cases: Tags, unique visitors tracking, online users, common friends.
3.4 Sorted Sets (ZSets)
Like Sets, but each member has a score used for ordering. Members are unique; scores can repeat.
# Add with scores
ZADD leaderboard 100 "Alice"
ZADD leaderboard 85 "Bob"
ZADD leaderboard 92 "Charlie"
ZADD leaderboard 78 "Diana"
# Get range by rank (ascending)
ZRANGE leaderboard 0 -1 # Diana, Bob, Charlie, Alice
ZRANGE leaderboard 0 -1 WITHSCORES # With scores
# Get range by rank (descending)
ZREVRANGE leaderboard 0 2 # Top 3: Alice, Charlie, Bob
# Get rank of a member
ZRANK leaderboard "Alice" # 3 (0-based, ascending)
ZREVRANK leaderboard "Alice" # 0 (top of leaderboard)
# Get score
ZSCORE leaderboard "Alice" # 100
# Increment score
ZINCRBY leaderboard 15 "Bob" # Bob's score is now 100
# Range by score
ZRANGEBYSCORE leaderboard 80 100 # Members with score 80-100
ZCOUNT leaderboard 80 100 # Count in score range
# Remove
ZREM leaderboard "Diana"
# Count
ZCARD leaderboard # 3
Use cases: Leaderboards, rankings, priority queues, time-series data, rate limiting.
3.5 Hashes
Maps of field-value pairs. Perfect for representing objects.
# Set fields
HSET user:1001 name "Alice" email "alice@example.com" age "30" role "admin"
# Get a single field
HGET user:1001 name # "Alice"
# Get multiple fields
HMGET user:1001 name email # "Alice" "alice@example.com"
# Get all fields and values
HGETALL user:1001 # name, Alice, email, alice@example.com, ...
# Check if field exists
HEXISTS user:1001 name # 1
# Get all field names
HKEYS user:1001 # name, email, age, role
# Get all values
HVALS user:1001 # Alice, alice@example.com, 30, admin
# Increment a numeric field
HINCRBY user:1001 age 1 # 31
# Delete a field
HDEL user:1001 role
# Count fields
HLEN user:1001 # 3
Use cases: User profiles, product details, session data, configuration objects.
4. Redis Data Types Summary
| Type | Description | Key Commands | Common Use Case |
|---|---|---|---|
| String | Single value (text/number/binary) | SET, GET, INCR, MSET | Caching, counters, locks |
| List | Ordered collection | LPUSH, RPUSH, LRANGE, LPOP | Queues, feeds, logs |
| Set | Unordered unique collection | SADD, SMEMBERS, SINTER | Tags, unique tracking |
| Sorted Set | Scored unique collection | ZADD, ZRANGE, ZREVRANGE | Leaderboards, rankings |
| Hash | Field-value map | HSET, HGET, HGETALL | Objects, profiles, sessions |
5. Redis CLI Basics
Key Management Commands
# Check if key exists
EXISTS user:1001 # 1 (exists) or 0 (not found)
# Delete key(s)
DEL user:1001 # 1 (deleted) or 0 (not found)
DEL key1 key2 key3 # Delete multiple
# Set expiration (TTL)
EXPIRE key 3600 # Expire in 3600 seconds
PEXPIRE key 60000 # Expire in 60000 milliseconds
EXPIREAT key 1700000000 # Expire at Unix timestamp
# Check remaining TTL
TTL key # Seconds remaining (-1 = no expiry, -2 = not found)
PTTL key # Milliseconds remaining
# Remove expiration (make persistent)
PERSIST key
# Find keys by pattern (development only!)
KEYS user:* # All keys starting with "user:"
KEYS * # All keys (NEVER in production!)
# SCAN (production-safe alternative to KEYS)
SCAN 0 MATCH user:* COUNT 10
# Get key type
TYPE user:1001 # hash, string, list, set, zset
# Rename a key
RENAME old-key new-key
# Dump database info
INFO # Server info and stats
INFO memory # Memory usage details
DBSIZE # Number of keys in current database
Important CLI Tips
# Select database (0-15, default is 0)
SELECT 1 # Switch to database 1
# Flush current database
FLUSHDB # Delete all keys in current DB
# Flush ALL databases
FLUSHALL # Delete everything (DANGEROUS!)
# Monitor all commands in real time
MONITOR # See every command (debugging tool)
# Measure latency
redis-cli --latency
redis-cli --latency-history
6. Persistence: RDB vs AOF
By default, Redis stores everything in memory. If the process crashes, data is lost. Redis offers two persistence mechanisms to save data to disk.
6.1 RDB (Redis Database Snapshots)
Point-in-time snapshots of the dataset at specified intervals.
redis.conf:
save 900 1 # Snapshot if 1+ key changed in 900 seconds
save 300 10 # Snapshot if 10+ keys changed in 300 seconds
save 60 10000 # Snapshot if 10000+ keys changed in 60 seconds
How RDB works:
1. Redis forks a child process
2. Child writes entire dataset to a temporary .rdb file
3. When complete, replaces old .rdb file atomically
4. Main process continues serving requests during save
| Pros | Cons |
|---|---|
| Compact binary file, great for backups | Data loss between snapshots |
| Fast restarts (loading .rdb is quick) | Fork can be slow for large datasets |
| Minimal performance impact | Not suitable for zero-data-loss requirements |
6.2 AOF (Append Only File)
Logs every write operation. Can replay the log to reconstruct the dataset.
redis.conf:
appendonly yes
appendfsync always # Fsync every write (safest, slowest)
appendfsync everysec # Fsync every second (good compromise)
appendfsync no # Let OS decide when to fsync (fastest, least safe)
How AOF works:
1. Every write command is appended to the AOF file
2. On restart, Redis replays all commands to rebuild dataset
3. Redis can rewrite (compact) the AOF file in background
| Pros | Cons |
|---|---|
| Minimal data loss (at most 1 second with everysec) | Larger file size than RDB |
| Human-readable log format | Slower restarts (must replay all commands) |
| Automatic rewriting to compact the file | Slightly more I/O overhead |
6.3 Which to Use?
| Scenario | Recommendation |
|---|---|
| Cache only (data loss is acceptable) | No persistence, or RDB only |
| General purpose | RDB + AOF together |
| Maximum durability | AOF with appendfsync always |
| Fast backups and disaster recovery | RDB snapshots |
| Development | Default settings (RDB) |
COMBINED (recommended for production):
- RDB for fast backups and restarts
- AOF for durability between snapshots
- Redis will use AOF to rebuild on restart (more complete)
7. Redis vs Memcached
Both are in-memory caching systems, but they have significant differences.
| Feature | Redis | Memcached |
|---|---|---|
| Data types | Strings, Hashes, Lists, Sets, Sorted Sets, Streams | Strings only |
| Persistence | RDB + AOF | None (pure cache) |
| Replication | Built-in master-replica | None built-in |
| Pub/Sub | Built-in | Not available |
| Transactions | MULTI/EXEC | Not available |
| Lua scripting | Supported | Not available |
| Clustering | Redis Cluster | Client-side sharding |
| Max value size | 512 MB | 1 MB |
| Threading | Single-threaded (I/O threads in 6.0+) | Multi-threaded |
| Memory efficiency | Slightly higher overhead | More memory efficient for simple strings |
| Eviction policies | 8 policies (LRU, LFU, random, etc.) | LRU only |
When to Choose Redis
- You need data structures beyond simple strings
- You need persistence (data survival across restarts)
- You need Pub/Sub messaging
- You need transactions and atomic operations
- You want a single tool for caching, sessions, queues, and more
When to Choose Memcached
- You only need simple key-value string caching
- You want multi-threaded performance for very high throughput
- Your caching needs are simple and you want a minimal solution
- You are already running Memcached in your infrastructure
For most Node.js applications, Redis is the clear choice due to its versatility and rich ecosystem.
8. Redis Architecture
Single Server:
┌─────────────┐
│ Application │
└──────┬──────┘
│ TCP (port 6379)
┌──────▼──────┐
│ Redis │
│ Server │ ← All data in RAM
│ │ ← Optional: RDB/AOF on disk
└──────────────┘
Master-Replica (Read Scaling + High Availability):
┌──────────┐ ┌──────────┐ ┌──────────┐
│ App #1 │ │ App #2 │ │ App #3 │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ writes │ reads │ reads
┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
│ Master │────>│ Replica 1│ │ Replica 2│
└──────────┘ └──────────┘ └──────────┘
│ replication ▲ replication
└────────────────────┘
Redis Cluster (Horizontal Scaling):
┌──────────────────────────────────────────┐
│ Redis Cluster │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ 0-5460 │ │5461-10922│ │10923-16383│ │
│ └────────┘ └────────┘ └────────┘ │
│ Data is automatically sharded across │
│ nodes by hash slot (16384 slots total) │
└──────────────────────────────────────────┘
9. Memory Management and Eviction
When Redis reaches its memory limit, it uses eviction policies to decide what to remove.
redis.conf:
maxmemory 256mb
maxmemory-policy allkeys-lru
| Policy | Description |
|---|---|
noeviction | Return error on write when memory is full (default) |
allkeys-lru | Remove least recently used key from all keys |
allkeys-lfu | Remove least frequently used key from all keys |
allkeys-random | Remove a random key from all keys |
volatile-lru | Remove LRU key among keys with expiration set |
volatile-lfu | Remove LFU key among keys with expiration set |
volatile-random | Remove random key among keys with expiration set |
volatile-ttl | Remove key with shortest TTL |
Recommended for caching: allkeys-lru or allkeys-lfu -- Redis automatically evicts the least useful data when memory is full.
Key Takeaways
- Redis is an in-memory data structure store -- not just a key-value cache
- It supports five core data types: Strings, Lists, Sets, Sorted Sets, and Hashes
- redis-cli provides a REPL for interacting with Redis directly
- Redis offers persistence via RDB (snapshots) and AOF (write log) -- use both in production
- Redis vs Memcached: Redis wins for most use cases due to richer data types, persistence, and Pub/Sub
- Single-threaded design means no race conditions and atomic operations by default
- Eviction policies like LRU and LFU manage memory automatically when limits are reached
- Master-replica and cluster modes provide high availability and horizontal scaling
Explain-It Challenge
Scenario: You are designing a caching layer for a social media application. You need to store user profiles (name, bio, follower count), a timeline of recent posts (ordered by time), a set of unique hashtags for each post, and a real-time leaderboard of the most active users this week.
For each feature, decide which Redis data type you would use and explain why. Write the Redis CLI commands you would use to create, read, and update the data. How would you handle expiration for each feature? What persistence configuration would you choose?
<< Previous: 3.16.a — What is Caching | Next: 3.16.c — Redis with Node.js >>