Toolmingo
Guides20 min read

Redis Tutorial for Beginners (2025): Learn Redis Step by Step

Complete Redis tutorial for beginners. Learn Redis from scratch: data structures, commands, caching, pub/sub, persistence, and real-world patterns. Free guide with code examples.

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.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools