DS DevShelfHub Projects · AI tools
Cheatsheets / Redis
Cheatsheet · Dev tooling

Redis: Strings, Hashes, Sorted Sets and Streams Reference Guide

By DevShelfHub

redis-cli, strings, hashes, lists, sets, sorted sets, streams, pub/sub, scripting, persistence, cluster — the full data-structure store.

122 items 8 min Types Streams Lua

Start hereQuick start · 6 you’ll reach for daily

String with TTLSET k v EX 60
Hash mappingHSET u:1 name Ana plan pro
CounterINCR pageviews
Top-N leaderboardZADD lb 1500 alice
Append to streamXADD events * k v
Pipeline (batch)r.pipeline(); ...; .execute()

Target versions · paceVersions

Targets: redis ≥ 7.2 redis-py ≥ 5.0 RESP3 protocol

Redis 7 added FUNCTION (replaces ad-hoc EVAL scripts), sharded pub/sub, ACL v2, and the new RESP3 wire protocol. Streams (5+), HyperLogLog (2.8+) and Geo (3.2+) are stable. License changed to SSPL/RSALv2 in 7.4 — if you need OSS-pure, valkey is the fork. This sheet targets Redis 7.2+.

Install · connectSetup

bash
# macOS — Homebrew
brew install redis
brew services start redis

# Linux — Debian/Ubuntu
sudo apt install redis-server
sudo systemctl enable --now redis-server

# Docker — disposable dev instance
docker run --name redis -p 6379:6379 -d redis:7

# Connect (CLI)
redis-cli                                   # localhost:6379
redis-cli -h prod.example -p 6380 -a $PW    # remote + auth
redis-cli -u "rediss://default:$PW@host:6380/0"   # URL form (TLS)

# Python — modern client (sync + async in one package)
pip install "redis>=5.0"

# Test the connection
redis-cli PING
# → PONG

The CLIredis-cli essentials

redis-cli -h host -p 6379 -a $PWConnect with auth.
redis-cli -u rediss://user:pw@h:6380/0URL form, TLS.
redis-cli --scan --pattern 'user:*'Preferred non-blocking key scan. Never use KEYS in prod.
redis-cli MONITORLive tail of every command. Heavy — debug only.
redis-cli --latency / --latency-historyNetwork + server latency probes.
redis-cli --bigkeysFind the biggest key per type. Hot spots.
redis-cli --memkeys --memkeys-samples 100Sampled memory profile by key.
redis-cli pingHealth check.
redis-cli -x SET kRead value from stdin (good for piping files).
redis-cli --rdb dump.rdbPull a snapshot off a remote.

Lifecycle · expiryKeys & expiration

EXISTS k1 if present.
DEL k [k ...]Synchronous delete.
UNLINK k [k ...]Async delete in background. Preferred for big keys.
TYPE kstring / hash / list / set / zset / stream.
EXPIRE k 60 / PEXPIRE k 60000TTL in seconds / ms.
EXPIREAT k <unix>Absolute expiry.
TTL k / PTTL k-2 if missing, -1 if no TTL.
PERSIST kRemove TTL.
RENAME k newkRename atomically.
SCAN 0 MATCH 'user:*' COUNT 1000Cursor-based scan. Safe under load.
OBJECT ENCODING kInternal encoding (embstr, listpack, ...). Useful for memory tuning.
DBSIZE / FLUSHDBCount keys / wipe current db. FLUSHDB is destructive — never in prod.

The default typeStrings & counters

SET k vSet or overwrite.
SET k v EX 60Set with TTL.
SET k v NX EX 60Set only if missing. Preferred over SETNX + EXPIRE (atomic).
SET k v XXSet only if exists.
SET k v GETReturn the previous value.
GET k / GETDEL k / GETEX k EX 60Read; read+delete; read+touch TTL.
MGET k1 k2 ... / MSET k1 v1 k2 v2Multi-key read / write in one round-trip.
INCR k / INCRBY k 5 / INCRBYFLOAT k 0.5Atomic counters. Initial 0.
DECR k / DECRBY k 5Decrement.
APPEND k suffixAppend; returns new length.
STRLEN k / GETRANGE k 0 7Length / substring.

Objects in one keyHashes

HSET k f1 v1 f2 v2Set fields. Returns # of new fields.
HGET k f / HMGET k f1 f2Read fields.
HGETALL kAll fields + values. Avoid on huge hashes.
HEXISTS k f1 if field present.
HDEL k f [f ...]Drop fields.
HINCRBY k f 1Atomic per-field counter.
HKEYS k / HVALS k / HLEN kInspect shape.
HSCAN k 0 MATCH 'pref:*'Cursor scan within a big hash.
HEXPIRE k 60 FIELDS 1 fPer-field TTL (7.4+).

Queues · stacksLists

LPUSH k v [v ...] / RPUSH k vPush to head / tail. LPUSH+RPOP = FIFO queue.
LPOP k / RPOP kPop from head / tail.
LMPOP 2 q1 q2 LEFT COUNT 10Multi-key pop with count (7+).
BLPOP k 5Blocking pop — classic worker pattern.
LRANGE k 0 -1Slice. -1 = last.
LLEN kLength.
LINDEX k 0 / LSET k 0 vRandom access (O(N) past head/tail).
LREM k 1 vRemove first match.
LMOVE src dst LEFT RIGHTAtomic move. Preferred over deprecated RPOPLPUSH.
LTRIM k 0 99Cap a list to first N (e.g. rolling activity feed).

Unique membersSets

SADD k m [m ...]Add members. Duplicates ignored.
SISMEMBER k m / SMISMEMBER k m1 m2Membership check (single / multi).
SREM k mRemove.
SMEMBERS kAll members. Avoid on huge sets.
SCARD kCardinality.
SINTER a b / SUNION a b / SDIFF a bIntersect / union / difference.
SINTERSTORE dest a bStore the result. Useful for materialized cohorts.
SRANDMEMBER k 3 / SPOP kRandom sample / atomic random remove.
SSCAN k 0Cursor scan.

Leaderboards · priority queuesSorted sets (ZSET)

ZADD k score member [score member ...]Add / update with score.
ZADD k NX 100 m / ZADD k XX 100 mOnly-if-missing / only-if-exists variants.
ZADD k GT 100 mUpdate only if new score > old.
ZINCRBY k 1 mAtomic score bump.
ZRANGE k 0 9 WITHSCORESIndex-based range.
ZRANGE k 0 9 REV WITHSCORESTop-N. Preferred over deprecated ZREVRANGE.
ZRANGEBYSCORE k 100 200 LIMIT 0 10Score window with pagination.
ZRANK k m / ZREVRANK k m0-based rank.
ZSCORE k m / ZMSCORE k m1 m2Read score(s).
ZREM k m / ZREMRANGEBYSCORE k -inf 100Remove by member or score range.
ZPOPMIN k / ZPOPMAX kPriority-queue pop.
BZPOPMIN k 5Blocking pop with timeout.

Append-only log · consumer groupsStreams

XADD k * field val [field val ...]Append entry. * = auto id.
XADD k MAXLEN ~ 10000 * ...Approximate trim — cheap, keeps newest ~10k.
XLEN kEntry count.
XRANGE k - +All entries (replay).
XREAD COUNT 10 BLOCK 5000 STREAMS k $Fan-out read. $ = only new.
XGROUP CREATE k group $ MKSTREAMCreate consumer group at tail.
XREADGROUP GROUP g c-1 COUNT 10 BLOCK 5000 STREAMS k >Worker reads its share. > = new only.
XACK k group idMark entry as processed.
XPENDING k groupClaimed-but-unacked entries (PEL).
XAUTOCLAIM k group new-c 60000 0Reassign entries idle for 60s.
XDEL k id / XTRIM k MAXLEN 1000Delete entry / trim length.
bash
# Append an event ("*" → server-generated id)
XADD orders * sku abc qty 3
# → "1715944800123-0"

# Create a consumer group starting from the current end ("$")
XGROUP CREATE orders billing '$' MKSTREAM

# A worker reads new events (blocks up to 5s)
XREADGROUP GROUP billing worker-1 COUNT 10 BLOCK 5000 STREAMS orders '>'

# Acknowledge after processing
XACK orders billing 1715944800123-0

# See still-pending (claimed but un-ACKed) entries
XPENDING orders billing
XAUTOCLAIM orders billing worker-2 60000 0    # steal stuck messages

# Cap stream length (lossy trim — keeps newest ~10k)
XADD orders MAXLEN '~' 10000 * sku abc qty 3

# Range scan (useful for replay)
XRANGE orders - + COUNT 100

Fire-and-forget messagingPub/Sub

SUBSCRIBE channel [c ...]Listen on channels. No persistence — messages lost if no subscribers.
UNSUBSCRIBE [c ...]Stop listening.
PSUBSCRIBE 'news.*'Pattern subscription.
PUBLISH channel "hello"Send. Returns # of subscribers reached.
SPUBLISH / SSUBSCRIBESharded pub/sub (7+) — cluster-safe.
PUBSUB CHANNELS [pattern]Active channels.
PUBSUB NUMSUB chSubscriber count per channel.
Pub/Sub has no delivery guarantees and no replay. For at-least-once with backpressure, use Streams + consumer groups.

MULTI · WATCH · LuaTransactions & scripting

MULTI ... EXECQueue + atomically execute. No mid-tx logic.
DISCARDAbort a queued transaction.
WATCH kOptimistic CAS — EXEC aborts if k changed.
EVAL "script" 1 k1 arg1Run Lua server-side. Atomic.
EVALSHA sha 1 k1 arg1Cached script by hash. Preferred for hot paths.
SCRIPT LOAD "..."Preload + get SHA.
FUNCTION LOAD "..."Replaces EVAL with named, versioned, replicated functions (7+).
FCALL fn 1 k1 arg1Call a loaded function.
sql
-- Safe lock release: only delete if the value still matches our token.
-- Run via EVAL — KEYS[1] = lock key, ARGV[1] = token.
if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
else
  return 0
end

RDB · AOFPersistence & durability

SAVESynchronous snapshot. Blocks the server — rarely used.
BGSAVEFork + snapshot in background (RDB).
LASTSAVEUnix ts of last successful save.
BGREWRITEAOFCompact the AOF log.
CONFIG SET appendonly yesEnable AOF. Pair with appendfsync everysec.
save 3600 1 300 100 60 10000RDB triggers (changes in window). redis.conf default.
aof-use-rdb-preamble yesHybrid AOF — faster restart, smaller log. Default since 4.0.
DEBUG RELOADRound-trip through disk — tests durability.
SHUTDOWN NOSAVE / SAVEStop the server; NOSAVE skips final snapshot.

Replication, slots, hash tagsCluster & replication

REPLICAOF host port / REPLICAOF NO ONEAttach / detach as replica. Preferred over SLAVEOF.
INFO replicationRole, lag, connected replicas.
WAIT numreplicas timeout-msBlock until N replicas ACK. Best-effort durability.
CLUSTER INFO / CLUSTER NODESTopology + slot ownership.
CLUSTER SLOTS / CLUSTER SHARDSSlot → node map.
CLUSTER KEYSLOT kWhich slot a key hashes into.
{tag}suffix / user:{42}:nameHash-tag braces force keys to the same slot (multi-key ops).
CLUSTER FAILOVER [FORCE]Trigger replica promotion.
redis-cli --cluster reshard host:portMove slots between shards.

maxmemory · eviction policiesMemory & eviction

CONFIG SET maxmemory 2gbCap memory usage.
maxmemory-policy noevictionDefault. New writes error when full.
allkeys-lru / allkeys-lfuEvict by recency / frequency across all keys. Cache mode.
volatile-lru / volatile-lfu / volatile-ttlOnly evict keys that have a TTL.
allkeys-random / volatile-randomRandom eviction.
MEMORY USAGE kBytes for a single key.
MEMORY STATSGlobal memory breakdown.
INFO memoryused_memory_human, mem_fragmentation_ratio.
DEBUG OBJECT kInternals + encoding for a key.

Sync · async · pipelinesPython with redis-py

python
from redis import Redis
from redis.asyncio import Redis as AsyncRedis

# Sync — connection pool is created for you
r = Redis(host="localhost", port=6379, db=0, decode_responses=True)

r.set("user:42:name", "Ana", ex=3600)        # TTL 1h
r.get("user:42:name")                         # "Ana"

# Pipeline — batch commands, one round-trip
with r.pipeline(transaction=False) as pipe:
    pipe.hset("user:42", mapping={"name": "Ana", "plan": "pro"})
    pipe.expire("user:42", 86400)
    pipe.zadd("leaderboard", {"42": 1500})
    pipe.execute()

# Transaction — WATCH for optimistic concurrency
def increment_if_below(key, cap):
    with r.pipeline() as pipe:
        while True:
            try:
                pipe.watch(key)
                cur = int(pipe.get(key) or 0)
                if cur >= cap:
                    pipe.unwatch(); return cur
                pipe.multi()
                pipe.incr(key)
                pipe.execute()
                return cur + 1
            except r.WatchError:
                continue   # somebody else wrote — retry

# Async
async def main():
    a = AsyncRedis.from_url("redis://localhost", decode_responses=True)
    await a.set("k", "v"); print(await a.get("k"))
    await a.aclose()

Rate-limit · leaderboard · cacheEnd-to-end · three patterns

Three minimal patterns developers reach for daily — all on stock Redis 7. Adapt the keys; the logic stays.

python
# Tiny rate-limiter: N requests per minute per user.
# Uses INCR + EXPIRE — both run atomically in one pipeline.
from redis import Redis

r = Redis(decode_responses=True)

def allow(user_id: str, limit: int = 60, window_sec: int = 60) -> bool:
    key = f"rl:{user_id}:{int(time.time()) // window_sec}"
    with r.pipeline() as pipe:
        pipe.incr(key)
        pipe.expire(key, window_sec)
        count, _ = pipe.execute()
    return count <= limit

# Leaderboard with sorted set
r.zadd("leaderboard", {"alice": 1500, "bob": 1820, "carol": 1730})
r.zrevrange("leaderboard", 0, 2, withscores=True)
# → [('bob', 1820.0), ('carol', 1730.0), ('alice', 1500.0)]

# Cache-aside read with TTL
def get_user(user_id):
    key = f"user:{user_id}"
    cached = r.get(key)
    if cached: return json.loads(cached)
    row = db.fetch_user(user_id)             # cache miss → source of truth
    r.set(key, json.dumps(row), ex=300)      # cache 5 min
    return row

Best practiceGood to know

Pipelines are not transactions. A pipeline batches commands into one round-trip — nothing more. For atomicity, wrap in MULTI/EXEC or use a Lua script.
Use SET k v NX EX 60, never SETNX + EXPIRE. The two-command form races: a crash between commands leaves a key without TTL. The single-command form is atomic.
Cluster needs hash tags for multi-key commands. MSET user:1:name x user:2:name y errors in cluster mode — the keys hash to different slots. Use {group} braces to pin related keys to the same slot.

Common trapsWatch out for

Never run KEYS in production. KEYS * is O(N) and blocks the single-threaded event loop. Use SCAN, which is cursor-based and incremental.
Pub/Sub drops messages with no listeners. Subscribers receive only what’s published while they’re online — no buffering, no replay. Use Streams when you need durability.
A big key blocks every other client. Redis runs one command at a time. A single DEL of a 1M-element hash freezes the whole server. Use UNLINK or trim in chunks; spot them with redis-cli --bigkeys.

Go deeperSee also

Redis FAQ

What is Redis used for?

Redis is an in-memory data structure store used as a cache, message broker, and primary database. Common use cases include session storage, rate limiting, leaderboards (sorted sets), pub/sub event streaming, distributed locks, and task queues via Redis Streams.

What is the difference between Redis Strings and Hashes?

Strings store a single value per key — raw text, a JSON blob, or a counter you can INCR. Hashes store a map of field-value pairs under one key, like a lightweight database row. Use Hashes when you want to read or update individual fields without fetching the entire value.

What are Redis Sorted Sets used for?

Sorted sets store members with a floating-point score, kept in sorted order automatically. They are ideal for leaderboards (ZADD, ZRANK, ZRANGE), time-ordered events, rate limiting sliding windows, and priority queues. Lookups by rank and by score are both O(log N).

Does Redis persist data to disk?

Yes. Redis offers two persistence modes: RDB (point-in-time snapshots on a configurable schedule) and AOF (Append-Only File that logs every write). You can use both together — AOF for durability, RDB for fast restarts. If you need no persistence at all, both can be disabled for pure cache mode.

Is Redis free to use?

Redis Community Edition (up to 7.2) was open source under BSD. Since version 7.4, Redis uses a dual SSPL/RSALv2 license. The Valkey fork maintains the BSD license for OSS-pure environments. Redis Cloud offers a free hosted tier. The redis-py Python client remains MIT-licensed.