Toolmingo
Guides11 min read

Redis Cheat Sheet: Commands, Data Structures & Patterns

A complete Redis cheat sheet — strings, lists, hashes, sets, sorted sets, pub/sub, streams, Lua scripting, persistence, and production patterns. Copy-ready commands.

Redis is a fast in-memory data store used for caching, sessions, queues, pub/sub, leaderboards, and rate limiting. This reference covers every data structure and the patterns that matter in production.

Quick reference

The 25 commands that cover 90% of daily Redis work.

Command What it does
SET key value Store a string value
GET key Retrieve a string value
SET key value EX 60 Store with 60-second TTL
DEL key Delete a key
EXISTS key Check if key exists (returns 0/1)
EXPIRE key 300 Set TTL in seconds
TTL key Remaining seconds (-1=no TTL, -2=gone)
KEYS pattern Find keys matching pattern (avoid in prod)
SCAN 0 MATCH user:* COUNT 100 Safe key iteration
HSET hash field value Set hash field
HGET hash field Get hash field
HGETALL hash Get all fields and values
LPUSH list value Prepend to list
RPOP list Remove and return last element
SADD set member Add to set
SMEMBERS set Get all set members
ZADD zset score member Add to sorted set
ZRANGE zset 0 -1 WITHSCORES Get sorted set range
INCR counter Atomic increment by 1
INCRBY counter 5 Atomic increment by N
MSET k1 v1 k2 v2 Set multiple keys at once
MGET k1 k2 Get multiple keys at once
PUBLISH channel msg Publish to channel
SUBSCRIBE channel Subscribe to channel
FLUSHDB Delete all keys in current DB (⚠ irreversible)

Strings

Strings are the foundation — any binary data up to 512 MB.

# Basic set / get
SET user:1:name "Alice"
GET user:1:name           # "Alice"

# With TTL (seconds)
SET session:abc token123 EX 3600
TTL session:abc           # 3599

# NX — only set if key does NOT exist (distributed lock pattern)
SET lock:resource unique_token NX EX 30
# returns OK if acquired, nil if already locked

# XX — only set if key DOES exist
SET user:1:name "Bob" XX

# Integers — atomic operations
SET page:views 0
INCR page:views           # 1
INCRBY page:views 5       # 6
DECR page:views           # 5
DECRBY page:views 2       # 3

# Floats
SET price 19.99
INCRBYFLOAT price 0.01    # 20.00

# Append
APPEND log "2026-07-13 boot\n"

# Get length
STRLEN user:1:name        # 3

Hashes

Hashes store field-value maps — ideal for objects.

# Set fields
HSET user:1 name "Alice" email "alice@example.com" age 30

# Get a single field
HGET user:1 name          # "Alice"

# Get all fields + values
HGETALL user:1
# 1) "name"
# 2) "Alice"
# 3) "email"
# 4) "alice@example.com"
# 5) "age"
# 6) "30"

# Get multiple fields
HMGET user:1 name email

# Check if field exists
HEXISTS user:1 email      # 1

# Delete a field
HDEL user:1 age

# Count fields
HLEN user:1               # 2

# Increment a numeric field
HINCRBY user:1 score 10
HINCRBYFLOAT user:1 balance 5.50

# Iterate fields safely
HSCAN user:1 0 COUNT 10

Tip: Use hashes instead of storing JSON strings when you need to update individual fields without re-serialising the whole object.


Lists

Lists are doubly-linked — O(1) push/pop at both ends. Use for queues, recent activity, message log.

# Push
LPUSH tasks "task-1" "task-2"   # prepend (task-2 is now head)
RPUSH tasks "task-3"            # append

# Pop
LPOP tasks                      # remove + return head
RPOP tasks                      # remove + return tail

# Blocking pop (waits up to 5s)
BLPOP queue 5

# Peek without removing
LINDEX tasks 0                  # first element
LINDEX tasks -1                 # last element

# Range
LRANGE tasks 0 -1               # all elements
LRANGE tasks 0 4                # first 5

# Length
LLEN tasks

# Trim to last 1000 items
LTRIM log 0 999

# Remove elements
LREM tasks 0 "task-1"           # remove all occurrences

Queue pattern (producer → consumer):

# Producer
RPUSH jobs:email '{"to":"alice@example.com","subject":"Hi"}'

# Consumer (blocking)
BRPOPLPUSH jobs:email jobs:processing 0
# atomically moves item to processing list
# replay-safe — item stays until you explicitly delete it

Sets

Sets are unordered collections of unique strings. Use for tags, unique visitors, friend lists.

# Add members
SADD tags:post:1 "redis" "caching" "backend"

# Check membership
SISMEMBER tags:post:1 "redis"    # 1 (true)
SMISMEMBER tags:post:1 "redis" "nosql"  # 1, 0

# Count
SCARD tags:post:1               # 3

# Get all
SMEMBERS tags:post:1

# Remove
SREM tags:post:1 "caching"

# Pop a random member
SPOP tags:post:1

# Set operations
SUNION set1 set2                # union
SINTER set1 set2                # intersection
SDIFF set1 set2                 # difference (in set1, not set2)

# Store result
SUNIONSTORE dest set1 set2
SINTERSTORE dest set1 set2

Sorted Sets (ZSet)

Members with a float score — automatically ordered. Use for leaderboards, rate limiting by time, priority queues.

# Add with score
ZADD leaderboard 1500 "alice"
ZADD leaderboard 2200 "bob"
ZADD leaderboard 1800 "carol"

# Get by rank (0-indexed, lowest to highest)
ZRANGE leaderboard 0 -1 WITHSCORES

# Get by rank (highest to lowest)
ZREVRANGE leaderboard 0 2 WITHSCORES

# Get rank of a member
ZRANK leaderboard "alice"        # 0 (lowest)
ZREVRANK leaderboard "alice"     # 2 (highest)

# Get score
ZSCORE leaderboard "alice"       # 1500.0

# Count members
ZCARD leaderboard

# Range by score
ZRANGEBYSCORE leaderboard 1000 2000 WITHSCORES
ZRANGEBYSCORE leaderboard -inf +inf LIMIT 0 10   # paginated

# Increment score
ZINCRBY leaderboard 100 "alice"   # 1600

# Remove
ZREM leaderboard "bob"

# Remove by rank or score
ZREMRANGEBYRANK leaderboard 0 1
ZREMRANGEBYSCORE leaderboard -inf 1000

# Sliding window rate limit (member = request ID, score = timestamp)
ZADD rate:user:1 1721000000 "req-abc"
ZREMRANGEBYSCORE rate:user:1 -inf 1720996400   # remove older than 1h
ZCARD rate:user:1                               # count in window

Key management

# Check type
TYPE user:1             # hash
TYPE tasks              # list

# Rename
RENAME old_key new_key
RENAMENX old_key new_key    # only if new_key doesn't exist

# Copy (Redis 6.2+)
COPY src dst

# Persist (remove TTL)
PERSIST session:abc

# Object encoding (internal representation)
OBJECT ENCODING mykey

# Dump / restore
DUMP user:1             # serialized
RESTORE user:99 0 <dump>

# Safe key scanning (use instead of KEYS in production)
SCAN 0 MATCH user:* COUNT 100
# Returns cursor + results; repeat until cursor == 0

Expiry patterns

# Set TTL on existing key
EXPIRE key 3600          # seconds
PEXPIRE key 3600000      # milliseconds

# Set absolute expiry
EXPIREAT key 1721000000  # Unix timestamp (seconds)
PEXPIREAT key 1721000000000  # milliseconds

# Check TTL
TTL key     # seconds remaining (-1 = no TTL, -2 = key missing)
PTTL key    # milliseconds remaining

# Remove TTL
PERSIST key

Pub / Sub

Redis pub/sub is fire-and-forget — no message persistence. Use for real-time notifications; use Streams for durable queues.

# Subscribe (blocks, waiting for messages)
SUBSCRIBE news sports

# Subscribe with pattern
PSUBSCRIBE news.*

# Publish (from another connection)
PUBLISH news "Breaking: Redis 8 released"

# List active channels
PUBSUB CHANNELS news.*
PUBSUB NUMSUB news         # subscriber count

Node.js example:

import { createClient } from 'redis';

const sub = createClient();
const pub = createClient();
await sub.connect();
await pub.connect();

await sub.subscribe('alerts', (message) => {
  console.log('Received:', message);
});

await pub.publish('alerts', JSON.stringify({ level: 'warn', msg: 'High CPU' }));

Streams (Redis 5+)

Streams are durable, append-only logs with consumer groups — like Kafka, built into Redis.

# Append entry (auto-ID = timestamp-sequence)
XADD events * type "click" user "alice"
# Returns ID like "1721000000000-0"

# Read latest 10 entries
XRANGE events - + COUNT 10

# Read from ID onward
XRANGE events 1721000000000-0 +

# Read in reverse
XREVRANGE events + - COUNT 5

# Consumer group
XGROUP CREATE events mygroup $ MKSTREAM

# Read as consumer (never-delivered messages)
XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS events >

# Acknowledge processed
XACK events mygroup 1721000000000-0

# Pending messages (unacked)
XPENDING events mygroup - + 10

# Stream length
XLEN events

# Trim to last 1000
XTRIM events MAXLEN ~ 1000

Transactions

Redis transactions execute atomically but do not roll back on errors.

# MULTI / EXEC block
MULTI
SET balance 100
DECRBY balance 30
EXEC
# All commands run, or none (if DISCARD called)

# DISCARD cancels the queued transaction
MULTI
SET balance 100
DISCARD

# WATCH — optimistic locking
WATCH balance
MULTI
DECRBY balance 30
EXEC
# Returns nil if 'balance' changed between WATCH and EXEC

Node.js transaction:

const result = await client
  .multi()
  .set('balance', 100)
  .decrBy('balance', 30)
  .get('balance')
  .exec();
// result = [null, 70, '70']

Lua scripting

Lua scripts run atomically — replace MULTI/EXEC when you need conditional logic.

# Run inline script (KEYS[1] = key, ARGV[1] = value)
EVAL "return redis.call('GET', KEYS[1])" 1 mykey

# Atomic get-and-delete
EVAL "
  local val = redis.call('GET', KEYS[1])
  redis.call('DEL', KEYS[1])
  return val
" 1 session:abc

# Load script, get SHA
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# Returns SHA: abc123...

# Run cached script
EVALSHA abc123 1 mykey

Common patterns

Cache-aside (most common)

async function getUser(id) {
  const cached = await redis.get(`user:${id}`);
  if (cached) return JSON.parse(cached);

  const user = await db.findUser(id);
  await redis.setEx(`user:${id}`, 3600, JSON.stringify(user));
  return user;
}

Distributed lock

const token = crypto.randomUUID();
const acquired = await redis.set('lock:resource', token, {
  NX: true,   // only if not exists
  EX: 30,     // 30 second TTL
});
if (!acquired) throw new Error('Could not acquire lock');

try {
  // ... do work ...
} finally {
  // Release only if we own the lock (Lua for atomicity)
  await redis.eval(`
    if redis.call("GET", KEYS[1]) == ARGV[1] then
      return redis.call("DEL", KEYS[1])
    end
    return 0
  `, 1, 'lock:resource', token);
}

Rate limiter (sliding window)

async function isAllowed(userId, limitPerMinute) {
  const now = Date.now();
  const window = 60_000;
  const key = `rate:${userId}`;

  const pipe = redis.multi();
  pipe.zRemRangeByScore(key, 0, now - window);
  pipe.zCard(key);
  pipe.zAdd(key, { score: now, value: `${now}-${Math.random()}` });
  pipe.expire(key, 60);
  const results = await pipe.exec();
  const count = results[1];
  return count < limitPerMinute;
}

Session store

// Save session
await redis.setEx(`session:${sessionId}`, 86400, JSON.stringify(sessionData));

// Get session
const data = await redis.get(`session:${sessionId}`);
if (data) await redis.expire(`session:${sessionId}`, 86400); // rolling TTL

// Destroy session
await redis.del(`session:${sessionId}`);

Persistence

Mode Config Durability Performance
No persistence save "" None (pure cache) Fastest
RDB snapshots save 900 1 Last snapshot Fast
AOF (append-only) appendonly yes Near zero loss Slightly slower
RDB + AOF Both enabled Best Moderate
# redis.conf — recommended production settings
appendonly yes
appendfsync everysec     # fsync every 1 second (good balance)
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# Manual save
BGSAVE       # background RDB snapshot
BGREWRITEAOF # background AOF rewrite
LASTSAVE     # Unix timestamp of last successful save

Server & monitoring

# Server info (sections: server/clients/memory/stats/replication)
INFO
INFO memory
INFO replication

# Real-time command monitoring (dev only — high overhead)
MONITOR

# Slow log
SLOWLOG GET 10             # last 10 slow commands
SLOWLOG RESET

# Client list
CLIENT LIST

# Kill a client
CLIENT KILL ID 42

# Config at runtime
CONFIG GET maxmemory
CONFIG SET maxmemory 512mb
CONFIG SET maxmemory-policy allkeys-lru

# Database selection (0–15 by default)
SELECT 1
MOVE key 0                 # move key to DB 0

# Flush
FLUSHDB                    # current DB
FLUSHALL                   # all DBs  ⚠ DANGER in production

Memory policies

When Redis reaches maxmemory, the eviction policy controls what happens:

Policy Behaviour
noeviction Return error on write (default)
allkeys-lru Evict least-recently-used from all keys
volatile-lru Evict LRU from keys with TTL
allkeys-lfu Evict least-frequently-used from all keys
volatile-lfu Evict LFU from keys with TTL
allkeys-random Evict random key
volatile-random Evict random key with TTL
volatile-ttl Evict key closest to expiry

Cache recommendation: Use allkeys-lru. Every key is evictable; no wasted memory from forgotten non-expiring keys.


redis-cli tips

# Connect
redis-cli
redis-cli -h 127.0.0.1 -p 6379 -a password
redis-cli -u redis://user:pass@host:6379/0

# Pipe commands from file
redis-cli < commands.txt

# Inline execution
redis-cli GET user:1

# Latency test
redis-cli --latency
redis-cli --latency-history

# Memory usage of a key
redis-cli MEMORY USAGE user:1

# Find big keys
redis-cli --bigkeys

# Scan keys interactively
redis-cli --scan --pattern "user:*"

Common mistakes

Mistake Why it hurts Fix
KEYS * in production Blocks server for seconds on large datasets Use SCAN with COUNT
No TTL on cached data Memory fills up; stale data forever Always set EX/EXPIREAT
Storing JSON blobs when fields differ Re-read + re-write entire blob for one field update Use Hash for mutable objects
MULTI/EXEC for conditional logic No rollback; race conditions possible Use Lua scripting
SELECT to separate concerns Shares memory and connection pool; hard to monitor Use separate Redis instances
Large lists as queues No consumer groups; lost on crash Use Streams for durable queues
Synchronous ops in hot path Latency spikes Use pipelines; batch operations

FAQ

What's the difference between Redis and Memcached?
Both cache in memory. Redis adds persistence, Lua scripting, pub/sub, streams, sorted sets, and cluster support. Memcached is simpler and slightly faster for pure string caching.

Can Redis lose data?
Yes, if persistence is off and the server restarts. With AOF + appendfsync everysec you lose at most 1 second of writes. With always you lose nothing but writes are slower.

When should I use Streams vs Pub/Sub?
Pub/Sub: fire-and-forget real-time events (online users, live notifications). Streams: durable message queue where consumers can replay, acknowledge, and process at their own pace.

How do I handle a Redis cluster?
Use client-side cluster support (ioredis, redis-py, go-redis all support it). Keys are sharded across 16,384 hash slots. Multi-key operations require keys in the same slot — use hash tags {user:1}:name and {user:1}:email to colocate.

What is pipelining?
Sending multiple commands at once without waiting for each reply. Reduces round-trip overhead:

const pipe = redis.pipeline();
pipe.set('a', 1);
pipe.set('b', 2);
pipe.get('a');
const results = await pipe.exec();

How do I debug slow commands?
Set slowlog-log-slower-than 10000 (10ms in microseconds) in redis.conf. Then SLOWLOG GET 25 shows the worst offenders with their arguments and execution time.

Related tools

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