Redis is an in-memory data store used as a cache, message broker, and database. It's fast (sub-millisecond latency), simple to use, and powers features like caching, session storage, rate limiting, leaderboards, and real-time messaging in millions of applications.
What you'll learn
| Topic | What you'll be able to do |
|---|---|
| Installation | Run Redis locally and in Docker |
| Strings | Store and retrieve values, counters, expiry |
| Lists | Queue, stack, and timeline patterns |
| Hashes | Store objects with multiple fields |
| Sets | Unique member collections and set operations |
| Sorted Sets | Leaderboards and ranked data |
| Pub/Sub | Real-time messaging between clients |
| Transactions | Atomic operations with MULTI/EXEC |
| Persistence | RDB snapshots and AOF logging |
| Real projects | Cache layer, rate limiter, session store |
Why Redis?
| Use case | Description |
|---|---|
| Caching | Sub-millisecond data retrieval, reduces DB load |
| Session storage | Store user sessions across servers |
| Rate limiting | Count requests per IP/user per time window |
| Leaderboards | Real-time ranked lists with sorted sets |
| Pub/Sub messaging | Fan-out events to multiple subscribers |
| Job queues | Producer/consumer task queues |
| Real-time analytics | Counters, HyperLogLog, time-series |
| Distributed locks | Cross-server mutex with SET NX EX |
Redis vs other solutions
| Redis | Memcached | Database | Local cache | |
|---|---|---|---|---|
| Speed | Sub-ms | Sub-ms | 1–100ms | Fastest |
| Data structures | Rich (10+ types) | Strings only | Rich | Language types |
| Persistence | Optional | None | Yes | None |
| Replication | Yes | No | Yes | No |
| Pub/Sub | Yes | No | No | No |
| Best for | Caching + features | Pure caching | Source of truth | Single server |
1. Installation
Install Redis locally
macOS (Homebrew):
brew install redis
brew services start redis
Ubuntu/Debian:
sudo apt update
sudo apt install redis-server
sudo systemctl start redis
Windows: Use WSL2 (Ubuntu) or Docker (recommended).
Docker (any platform):
docker run -d --name redis -p 6379:6379 redis:7-alpine
Verify installation
redis-cli ping
# PONG
Redis CLI
The Redis CLI is your primary tool for learning and debugging:
redis-cli # connect to localhost:6379
redis-cli -h <host> -p <port> -a <password> # remote
2. Strings — the Foundation
Strings are the most basic Redis type. They hold text, numbers, serialized JSON, or binary data (up to 512 MB).
Basic commands
# Set and get
SET name "Alice"
GET name # "Alice"
# Set with expiry (seconds)
SET session:abc "user123" EX 3600
TTL session:abc # 3599 (seconds remaining)
PERSIST session:abc # remove expiry
# Check existence
EXISTS name # 1 (exists) or 0 (not found)
# Delete
DEL name # 1 (deleted)
# Get and set atomically
GETSET name "Bob" # returns old value, sets new
# Set only if not exists
SETNX lock "1" # 1 (set) or 0 (already exists)
# Set multiple keys
MSET k1 "v1" k2 "v2" k3 "v3"
MGET k1 k2 k3 # ["v1", "v2", "v3"]
Counters
Redis string operations on numeric values are atomic — perfect for counters:
SET visits 0
INCR visits # 1
INCRBY visits 5 # 6
DECR visits # 5
DECRBY visits 2 # 3
# Float counters
SET price 9.99
INCRBYFLOAT price 0.01 # 10.00
String key patterns
Use colons as namespace separators (convention, not enforced):
SET user:1001:name "Alice"
SET user:1001:email "alice@example.com"
SET product:42:stock 150
SET cache:article:99 "<html>...</html>"
String data type summary
| Command | Description |
|---|---|
SET k v [EX s] [NX] |
Set value, optionally with expiry or only-if-not-exists |
GET k |
Get value |
MSET / MGET |
Set/get multiple keys |
INCR / INCRBY |
Increment integer |
DECR / DECRBY |
Decrement integer |
APPEND k v |
Append to string value |
STRLEN k |
Length of string value |
TTL k |
Seconds until expiry (-1 = no expiry, -2 = gone) |
EXPIRE k s |
Set expiry on existing key |
DEL k |
Delete key |
3. Lists
Redis lists are ordered sequences of strings. Push/pop from either end in O(1).
Common list operations
# Push to left (head)
LPUSH tasks "task3" "task2" "task1"
# List: [task1, task2, task3]
# Push to right (tail)
RPUSH tasks "task4"
# List: [task1, task2, task3, task4]
# Pop from left (FIFO queue)
LPOP tasks # "task1"
# Pop from right (stack)
RPOP tasks # "task4"
# Length
LLEN tasks # 2
# Range (0-indexed, -1 = last element)
LRANGE tasks 0 -1 # all elements
LRANGE tasks 0 1 # first 2 elements
# Get by index
LINDEX tasks 0 # "task2"
# Blocking pop (waits up to 30s for element)
BLPOP tasks 30
Queue pattern (FIFO)
# Producer
RPUSH job:queue "job:1" "job:2" "job:3"
# Consumer
LPOP job:queue # "job:1" — first in, first out
Stack pattern (LIFO)
# Push
RPUSH history "page:a" "page:b" "page:c"
# Pop most recent
RPOP history # "page:c"
Capped list (keep last N items)
RPUSH recent:events "event1"
RPUSH recent:events "event2"
RPUSH recent:events "event3"
LTRIM recent:events -100 -1 # keep last 100
List command summary
| Command | Description |
|---|---|
LPUSH / RPUSH |
Push to left/right |
LPOP / RPOP |
Pop from left/right |
BLPOP / BRPOP |
Blocking pop with timeout |
LLEN |
List length |
LRANGE k 0 -1 |
Get all elements |
LINDEX k n |
Get element at index |
LTRIM k start stop |
Keep only elements in range |
LINSERT |
Insert before/after element |
4. Hashes
Hashes store field-value pairs under one key — perfect for objects.
Basic hash commands
# Set fields
HSET user:1001 name "Alice" email "alice@example.com" age 30
# Get one field
HGET user:1001 name # "Alice"
# Get all fields
HGETALL user:1001
# 1) "name"
# 2) "Alice"
# 3) "email"
# 4) "alice@example.com"
# 5) "age"
# 6) "30"
# Get multiple fields
HMGET user:1001 name email # ["Alice", "alice@example.com"]
# Check field exists
HEXISTS user:1001 phone # 0 (no)
# Delete a field
HDEL user:1001 age
# Number of fields
HLEN user:1001 # 2
# Increment numeric field
HINCRBY user:1001 login_count 1
# Get all field names / values
HKEYS user:1001
HVALS user:1001
When to use hashes vs strings
| Pattern | Use |
|---|---|
| Store one value | String: SET user:1001:name "Alice" |
| Store object with many fields | Hash: HSET user:1001 name "Alice" age 30 |
| Partial updates | Hash (update one field without deserializing full object) |
| Atomic counter on object | Hash HINCRBY |
Hash command summary
| Command | Description |
|---|---|
HSET k f v [f v ...] |
Set one or more fields |
HGET k f |
Get field value |
HMGET k f1 f2 |
Get multiple fields |
HGETALL k |
Get all fields and values |
HDEL k f |
Delete field |
HEXISTS k f |
Check if field exists |
HLEN k |
Number of fields |
HKEYS / HVALS |
All field names / values |
HINCRBY k f n |
Increment numeric field |
5. Sets
Sets hold unique, unordered string members. Great for tags, unique visitors, and set math.
Basic set commands
# Add members
SADD tags:post:1 "redis" "cache" "tutorial"
SADD tags:post:1 "redis" # 0 — already exists, ignored
# Check membership
SISMEMBER tags:post:1 "redis" # 1 (yes)
SISMEMBER tags:post:1 "go" # 0 (no)
# Count members
SCARD tags:post:1 # 3
# Get all members
SMEMBERS tags:post:1 # {"redis", "cache", "tutorial"}
# Remove member
SREM tags:post:1 "tutorial"
# Random member (without removing)
SRANDMEMBER tags:post:1
# Pop random member
SPOP tags:post:1
Set operations
SADD teamA "alice" "bob" "charlie"
SADD teamB "bob" "charlie" "diana"
# Intersection (members in both)
SINTER teamA teamB # {"bob", "charlie"}
# Union (all members)
SUNION teamA teamB # {"alice", "bob", "charlie", "diana"}
# Difference (in A but not B)
SDIFF teamA teamB # {"alice"}
# Store result in new key
SINTERSTORE result:both teamA teamB
Unique visitor tracking
# Track unique visitors per day
SADD visitors:2025-07-16 "user:1" "user:2" "user:1"
SCARD visitors:2025-07-16 # 2 (unique count)
Set command summary
| Command | Description |
|---|---|
SADD k m1 m2 |
Add members |
SREM k m |
Remove member |
SISMEMBER k m |
Check membership |
SMEMBERS k |
All members (use SSCAN in production) |
SCARD k |
Member count |
SINTER / SUNION / SDIFF |
Set operations |
SRANDMEMBER k [n] |
Random members |
SPOP k [n] |
Remove and return random members |
6. Sorted Sets
Sorted sets are like sets but each member has a numeric score. Members are ordered by score.
Basic sorted set commands
# Add members with scores
ZADD leaderboard 1500 "alice"
ZADD leaderboard 2200 "bob"
ZADD leaderboard 1800 "charlie"
# Get rank (0-indexed, lowest score = rank 0)
ZRANK leaderboard "alice" # 0
ZRANK leaderboard "charlie" # 1
ZRANK leaderboard "bob" # 2
# Get score
ZSCORE leaderboard "bob" # "2200"
# Top N (highest scores) — ZREVRANGE
ZREVRANGE leaderboard 0 2 # ["bob", "charlie", "alice"]
ZREVRANGE leaderboard 0 2 WITHSCORES
# Count members
ZCARD leaderboard # 3
# Increment score
ZINCRBY leaderboard 100 "alice" # alice score = 1600
# Remove
ZREM leaderboard "alice"
# Range by score
ZRANGEBYSCORE leaderboard 1500 2500 # members with score 1500-2500
Leaderboard pattern
# Add/update player score
ZADD game:scores 9500 "player:42"
ZINCRBY game:scores 200 "player:42"
# Top 10 players
ZREVRANGE game:scores 0 9 WITHSCORES
# Player rank (1-indexed for display)
rank=$(redis-cli ZREVRANK game:scores "player:42")
echo "Rank: $((rank + 1))"
Rate limiting with sorted set
# Sliding window rate limit: max 100 requests per minute
key="rate:user:1001"
now=$(date +%s%3N) # milliseconds
window=$((now - 60000))
ZADD $key $now $now # add current timestamp
ZREMRANGEBYSCORE $key 0 $window # remove old entries
count=$(ZCARD $key)
EXPIRE $key 61
if [ $count -le 100 ]; then
echo "OK"
else
echo "Rate limited"
fi
Sorted set command summary
| Command | Description |
|---|---|
ZADD k score member |
Add/update member |
ZSCORE k m |
Get score |
ZRANK k m |
Rank (low to high, 0-indexed) |
ZREVRANK k m |
Rank (high to low, 0-indexed) |
ZINCRBY k n m |
Increment score |
ZRANGE k 0 -1 |
All members (low to high) |
ZREVRANGE k 0 -1 |
All members (high to low) |
ZRANGEBYSCORE k min max |
Members by score range |
ZCARD k |
Member count |
ZREM k m |
Remove member |
7. Key Expiry and TTL
Expiry is one of Redis's most powerful features — it automatically removes keys after a set time.
# Set with expiry at creation
SET token "abc123" EX 3600 # seconds
SET token "abc123" PX 3600000 # milliseconds
SET token "abc123" EXAT 1784000000 # Unix timestamp
# Add expiry to existing key
EXPIRE key 3600 # seconds
PEXPIRE key 3600000 # milliseconds
EXPIREAT key 1784000000 # Unix timestamp
# Check expiry
TTL key # seconds remaining (-1 = no expiry, -2 = key gone)
PTTL key # milliseconds remaining
# Remove expiry (make key permanent)
PERSIST key
Common expiry patterns
| Pattern | TTL |
|---|---|
| Session token | 30 minutes to 24 hours |
| OTP / verification code | 5–10 minutes |
| Cache entry | 5 minutes to 1 hour |
| Rate limit window | 60 seconds |
| Refresh token | 7–30 days |
8. Pub/Sub Messaging
Redis Pub/Sub lets publishers broadcast messages to channels, and subscribers receive them in real time.
Basic pub/sub
Terminal 1 — subscriber:
redis-cli SUBSCRIBE news sports
# Waiting for messages...
Terminal 2 — publisher:
redis-cli PUBLISH news "Breaking: Redis 8 released"
redis-cli PUBLISH sports "Score: 2-1"
Terminal 1 receives:
1) "message"
2) "news"
3) "Breaking: Redis 8 released"
1) "message"
2) "sports"
3) "Score: 2-1"
Pattern subscribe
Subscribe to multiple channels using glob patterns:
redis-cli PSUBSCRIBE "news.*"
# Matches: news.tech, news.sports, news.world
Pub/Sub limitations
| Limitation | Solution |
|---|---|
| Messages not persisted | Use Redis Streams instead |
| Subscriber must be online | Use Streams with consumer groups |
| No delivery guarantee | Use Streams or a message queue |
| No message history | Use Streams |
Redis Streams (XADD, XREAD, XGROUP) are the modern alternative when you need persistence and guaranteed delivery.
Pub/Sub command summary
| Command | Description |
|---|---|
SUBSCRIBE ch1 ch2 |
Subscribe to channels |
UNSUBSCRIBE |
Unsubscribe from channels |
PUBLISH ch msg |
Publish message to channel |
PSUBSCRIBE pattern |
Subscribe with glob pattern |
PUNSUBSCRIBE |
Unsubscribe from patterns |
PUBSUB CHANNELS |
List active channels |
PUBSUB NUMSUB ch |
Subscriber count per channel |
9. Transactions
Redis transactions execute a block of commands atomically — all or nothing, no interleaving.
MULTI # start transaction
SET balance 100
DECRBY balance 30
INCRBY savings 30
EXEC # execute all atomically
Result:
1) OK
2) 70
3) 30
To cancel:
MULTI
SET key "value"
DISCARD # abort transaction
Optimistic locking with WATCH
WATCH monitors keys and aborts the transaction if any watched key changes before EXEC:
WATCH balance
current=$(redis-cli GET balance)
new=$((current - 30))
MULTI
SET balance $new
result=$(EXEC)
if [ -z "$result" ]; then
echo "Transaction failed — balance changed, retry"
fi
Transaction caveats
| Behavior | Detail |
|---|---|
| Atomicity | All commands run or none (if DISCARD / WATCH abort) |
| Errors at queue time | Whole transaction aborted |
| Errors at exec time | Other commands still run (no rollback!) |
| Not true ACID | No isolation from other clients during MULTI/EXEC |
10. Persistence
Redis is in-memory but can persist data to disk.
RDB (Redis Database) — snapshots
Creates point-in-time snapshots:
# redis.conf
save 900 1 # snapshot if 1 key changed in 900s
save 300 10 # snapshot if 10 keys changed in 300s
save 60 10000 # snapshot if 10000 keys changed in 60s
dbfilename dump.rdb
dir /var/lib/redis/
Trigger manual snapshot:
BGSAVE # background save
SAVE # synchronous (blocks Redis!)
AOF (Append-Only File) — write log
Logs every write operation:
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
# Fsync policy
appendfsync always # safest, slowest
appendfsync everysec # 1 second data loss risk (recommended)
appendfsync no # OS decides, fastest
RDB vs AOF comparison
| RDB | AOF | |
|---|---|---|
| Data loss risk | Up to minutes (snapshot interval) | Up to 1 second |
| Restart speed | Fast (compact binary) | Slower (replay log) |
| File size | Small | Larger (grows over time) |
| Best for | Backups, disaster recovery | Minimal data loss |
| Performance impact | Low (background) | Low with everysec |
Recommendation: Enable both RDB and AOF for production.
11. Using Redis with Node.js
Install the official client:
npm install ioredis
Connect and basic operations
import Redis from 'ioredis';
const redis = new Redis({
host: 'localhost',
port: 6379,
// password: 'yourpassword',
// db: 0,
});
// String
await redis.set('name', 'Alice');
const name = await redis.get('name');
console.log(name); // "Alice"
// With expiry
await redis.set('token', 'abc123', 'EX', 3600);
// Hash
await redis.hset('user:1001', { name: 'Alice', email: 'alice@example.com' });
const user = await redis.hgetall('user:1001');
console.log(user); // { name: 'Alice', email: 'alice@example.com' }
// List
await redis.rpush('tasks', 'task1', 'task2');
const task = await redis.lpop('tasks');
// Increment counter
await redis.incr('page:views');
const views = await redis.get('page:views');
await redis.quit();
Cache-aside pattern
async function getUser(userId) {
const cacheKey = `user:${userId}`;
// Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Cache miss — fetch from database
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
// Store in cache for 5 minutes
await redis.set(cacheKey, JSON.stringify(user), 'EX', 300);
return user;
}
Rate limiter
async function rateLimit(userId, maxRequests = 100, windowSeconds = 60) {
const key = `rate:${userId}`;
const count = await redis.incr(key);
if (count === 1) {
// First request — set expiry
await redis.expire(key, windowSeconds);
}
if (count > maxRequests) {
throw new Error('Rate limit exceeded');
}
return { count, remaining: maxRequests - count };
}
Session store
// Save session
async function saveSession(sessionId, userData, ttlSeconds = 86400) {
await redis.setex(
`session:${sessionId}`,
ttlSeconds,
JSON.stringify(userData)
);
}
// Get session
async function getSession(sessionId) {
const data = await redis.get(`session:${sessionId}`);
return data ? JSON.parse(data) : null;
}
// Delete session (logout)
async function deleteSession(sessionId) {
await redis.del(`session:${sessionId}`);
}
12. Using Redis with Python
Install:
pip install redis
Basic usage
import redis
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# String
r.set('name', 'Alice')
print(r.get('name')) # Alice
# With expiry
r.setex('token', 3600, 'abc123')
# Hash
r.hset('user:1001', mapping={'name': 'Alice', 'email': 'alice@example.com'})
user = r.hgetall('user:1001')
print(user) # {'name': 'Alice', 'email': 'alice@example.com'}
# List
r.rpush('tasks', 'task1', 'task2')
task = r.lpop('tasks')
print(task) # task1
# Increment
r.incr('page:views')
Cache-aside in Python
def get_user(user_id: int):
cache_key = f"user:{user_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
# Fetch from database
user = db.fetch_user(user_id)
# Cache for 5 minutes
r.setex(cache_key, 300, json.dumps(user))
return user
13. Real-World Patterns
Distributed lock
Prevent multiple servers from running the same task simultaneously:
# Acquire lock (SET with NX = only if not exists, EX = auto-expire)
SET lock:job:42 "server-1" NX EX 30
# Returns OK if acquired, nil if already locked
# Release lock (only if we own it — use Lua for atomicity)
if [ "$(redis-cli GET lock:job:42)" = "server-1" ]; then
redis-cli DEL lock:job:42
fi
// Node.js distributed lock
async function acquireLock(lockKey, value, ttlSeconds) {
const result = await redis.set(lockKey, value, 'NX', 'EX', ttlSeconds);
return result === 'OK';
}
async function releaseLock(lockKey, value) {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
return redis.eval(script, 1, lockKey, value);
}
Leaderboard
// Update score
await redis.zadd('leaderboard', score, `player:${playerId}`);
// Top 10
const top10 = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
// Player rank (1-indexed)
const rank = await redis.zrevrank('leaderboard', `player:${playerId}`);
const displayRank = rank !== null ? rank + 1 : null;
Autocomplete
// Index words as sorted set with score 0
async function indexWord(word) {
for (let i = 1; i <= word.length; i++) {
await redis.zadd('autocomplete', 0, word.substring(0, i));
}
await redis.zadd('autocomplete', 0, word + '*'); // mark complete words
}
// Search
async function autocomplete(prefix, limit = 10) {
const start = await redis.zrank('autocomplete', prefix);
if (start === null) return [];
const results = await redis.zrange('autocomplete', start, start + 200);
return results
.filter(w => w.endsWith('*') && w.startsWith(prefix))
.map(w => w.slice(0, -1))
.slice(0, limit);
}
Common patterns reference
| Pattern | Redis type | Key commands |
|---|---|---|
| Cache | String | SET key val EX ttl, GET |
| Session store | String or Hash | SETEX, GET, DEL |
| Rate limit (fixed window) | String | INCR, EXPIRE |
| Rate limit (sliding window) | Sorted Set | ZADD, ZREMRANGEBYSCORE, ZCARD |
| Distributed lock | String | SET NX EX, Lua eval |
| Job queue | List | RPUSH, BLPOP |
| Pub/Sub event | Pub/Sub | PUBLISH, SUBSCRIBE |
| Leaderboard | Sorted Set | ZADD, ZREVRANGE, ZRANK |
| Unique visitors | Set | SADD, SCARD |
| Object store | Hash | HSET, HGETALL, HINCRBY |
| Autocomplete | Sorted Set | ZADD score 0, ZRANGE |
14. Production Tips
Connection pooling
Don't create a new connection per request. Use a singleton:
// redis.js — create once, import everywhere
import Redis from 'ioredis';
export const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 50, 2000),
});
Key naming conventions
| Convention | Example |
|---|---|
type:id:field |
user:1001:name |
type:id (hash) |
user:1001 (with HSET) |
namespace:resource:id |
cache:article:99 |
feature:identifier |
rate:ip:192.168.1.1 |
event:date |
visitors:2025-07-16 |
Memory management
# Set max memory limit
# redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru # evict least recently used keys
# Eviction policies
# allkeys-lru — LRU eviction from all keys (recommended for cache)
# volatile-lru — LRU only from keys with expiry
# allkeys-lfu — LFU (least frequently used)
# volatile-ttl — evict keys closest to expiry
# noeviction — return error when memory full (default)
Monitoring commands
# Server info
INFO server
INFO memory
INFO stats
INFO clients
# Real-time commands per second
redis-cli --stat
# Slow query log
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET
# Active connections
CLIENT LIST
# Memory usage of a key
MEMORY USAGE key
# Scan keys (never use KEYS * in production)
SCAN 0 MATCH "user:*" COUNT 100
Security checklist
| Item | Action |
|---|---|
| Password | Set requirepass in redis.conf |
| Bind address | bind 127.0.0.1 (not 0.0.0.0) unless needed |
| Rename commands | Rename FLUSHALL, DEBUG, CONFIG |
| TLS | Enable for remote connections |
| Firewall | Block port 6379 from public internet |
| Disable commands | rename-command FLUSHALL "" |
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Using KEYS * in production |
Blocks Redis (O(N) scan) | Use SCAN with cursor |
| No key expiry on cache | Memory grows unboundedly | Always set EX on cache keys |
| Storing large objects | Slows serialization, uses memory | Break into fields (Hash) or use compression |
| One connection per request | Connection overhead | Use connection pool / singleton |
No maxmemory config |
Redis uses all RAM | Set maxmemory + eviction policy |
Forgetting MULTI/EXEC atomicity limits |
Partial failures not rolled back | Use Lua scripts for true atomicity |
| Using Pub/Sub for reliable delivery | Messages lost if subscriber offline | Use Redis Streams |
| Storing sensitive data unencrypted | Security risk | Encrypt before storing |
Redis vs related technologies
| Redis | Memcached | PostgreSQL | Kafka | |
|---|---|---|---|---|
| Type | In-memory store | In-memory cache | Relational DB | Message broker |
| Persistence | Optional | No | Yes | Yes |
| Data structures | 10+ types | Strings only | Tables, JSON | Messages/topics |
| Use for cache | Yes | Yes | No | No |
| Use for queuing | Yes (Lists/Streams) | No | No | Yes |
| Throughput | 1M+ ops/sec | 1M+ ops/sec | ~10k queries/sec | 1M+ msg/sec |
| Horizontal scale | Redis Cluster | Yes | Partitioning | Yes |
Learning Path
| Stage | Topics | Milestone |
|---|---|---|
| 1 — Basics | Install, Strings, CLI, TTL | Store and retrieve data with expiry |
| 2 — Data structures | Lists, Hashes, Sets, Sorted Sets | Build a leaderboard and queue |
| 3 — Patterns | Cache-aside, rate limit, session | Add caching to a Node.js/Python app |
| 4 — Reliability | Transactions, Pub/Sub, Streams | Build a reliable job queue |
| 5 — Production | Persistence, monitoring, security, clustering | Deploy Redis in production |
6 Frequently Asked Questions
Q: Is Redis a database or a cache?
Both. Redis works as a cache (ephemeral, fast) or as a primary database (with persistence enabled). Most teams use it as a cache alongside PostgreSQL/MySQL.
Q: Does Redis lose data when it restarts?
Without persistence: yes. Enable RDB snapshots, AOF, or both to survive restarts. With AOF + appendfsync everysec, you lose at most 1 second of data.
Q: When should I use Redis Streams instead of Pub/Sub?
Use Streams when you need: message persistence, ability to replay history, consumer groups (multiple workers processing each message once), or guaranteed delivery. Pub/Sub is fire-and-forget — if no one is listening, the message is lost.
Q: How does Redis handle concurrency?
Redis is single-threaded for command processing (I/O is async). This means commands are serialized — no race conditions from simultaneous reads/writes. Transactions (MULTI/EXEC) and Lua scripts provide additional atomicity.
Q: What's the difference between Redis and Redis Stack?
Redis Stack bundles Redis with modules: RedisSearch (full-text search), RedisJSON (native JSON), RedisTimeSeries, and RedisBloom (probabilistic structures). Redis alone handles the core types (String, List, Hash, Set, Sorted Set, Streams).
Q: Can Redis replace my primary database?
For some use cases: yes. Redis works as a primary database for data that fits in RAM, doesn't require complex joins, and can tolerate the persistence tradeoffs. Most teams use Redis alongside a disk-based database (PostgreSQL/MySQL) rather than replacing it.