The fastest way to learn Redis is to type commands, watch the replies, and break things on purpose.
Getting started
Open a terminal, run redis-cli, and confirm the server answers. Use FLUSHDB between sections for a clean slate, but only on a practice instance.
PING # PONG
INFO server
INFO memory
INFO keyspace
SELECT 1 # switch to database 1
DBSIZE
CONFIG GET maxmemory
Two shell flags are worth learning on day one: redis-cli -n 2 PING targets a specific database, and redis-cli --raw GET key prints values without quoting or escaping.
Core data types
Most real Redis usage is built from six structures. Run every command, and note each return value: OK, (integer), or nil.
Strings
SET user:1:name "Musa"
GET user:1:name
SET counter 10
INCR counter # 11
INCRBY counter 5 # 16
APPEND user:1:name " Abu"
MSET a 1 b 2 c 3
MGET a b c
SET lock:x 1 NX EX 30 # only if absent, 30s TTL
SET k v XX # only if present
GETEX k EX 60
Keys and expiry
EXISTS user:1:name
TYPE user:1:name
EXPIRE user:1:name 60
TTL user:1:name
PERSIST user:1:name
UNLINK k # asynchronous delete
SCAN 0 MATCH user:* COUNT 100
OBJECT ENCODING counter # int, embstr or raw
Compare KEYS * with SCAN, and never use KEYS in production: it blocks the server while it walks the whole keyspace. Then set a 5-second TTL and run TTL repeatedly to watch the key expire.
Hashes
HSET user:1 name Musa age 30 city Dhaka
HGET user:1 name
HMGET user:1 name city
HGETALL user:1
HINCRBY user:1 age 1
HEXISTS user:1 city
HDEL user:1 city
HSCAN user:1 0
Lists
RPUSH queue job1 job2 job3
LPUSH queue job0
LRANGE queue 0 -1
LPOP queue
LTRIM queue 0 99
BLPOP queue 10 # blocking pop; try from a second terminal
LMOVE queue processing LEFT RIGHT
For a reliable queue, move each job with LMOVE queue processing, process it, then remove it with LREM processing 1 job.
Sets
SADD tags:post1 redis db cache
SADD tags:post2 redis golang
SISMEMBER tags:post1 redis
SINTER tags:post1 tags:post2
SUNION tags:post1 tags:post2
SDIFF tags:post1 tags:post2
SPOP tags:post1
Sorted sets
ZADD leaderboard 100 alice 250 bob 175 carol
ZRANGE leaderboard 0 -1 WITHSCORES
ZRANGE leaderboard 0 2 REV WITHSCORES # top 3 (Redis 6.2+)
ZRANK leaderboard alice
ZINCRBY leaderboard 50 alice
ZRANGEBYSCORE leaderboard 100 200
ZREM leaderboard carol
Sorted sets are the most versatile structure on this list: leaderboards, priority queues, time-ordered indexes and rate limiters all reduce to them.
Beyond the basics
These structures trade generality for extreme efficiency, and each solves one problem very well.
Bitmaps, HyperLogLog and geo
Bitmaps track yes/no flags per user ID in a few bytes each. HyperLogLog counts unique items approximately in about 12 KB, however many there are. Geo commands store coordinates and answer distance and radius queries.
SETBIT login:2026-10-12 1001 1
GETBIT login:2026-10-12 1001
BITCOUNT login:2026-10-12
BITOP AND active:both login:day1 login:day2
PFADD visitors:today u1 u2 u3 u1
PFCOUNT visitors:today # 3
PFMERGE visitors:week visitors:d1 visitors:d2
GEOADD shops 90.4125 23.8103 "dhaka-a" 91.8123 22.3569 "ctg-a"
GEODIST shops dhaka-a ctg-a km
GEOSEARCH shops FROMLONLAT 90.4 23.8 BYRADIUS 50 km ASC
Streams
Streams are an append-only log with consumer groups, which makes them the durable alternative to Pub/Sub and lists for event processing.
XADD orders * user 1 amount 500
XLEN orders
XRANGE orders - +
XREAD BLOCK 5000 STREAMS orders $
XGROUP CREATE orders workers $ MKSTREAM
XREADGROUP GROUP workers w1 COUNT 1 STREAMS orders >
XACK orders workers <id>
XPENDING orders workers
XCLAIM orders workers w2 60000 <id>
XTRIM orders MAXLEN ~ 1000
Use two terminals: one producer, one consumer-group worker. Kill the worker before it runs XACK, then recover the unacknowledged message with XPENDING and XCLAIM. That exercise teaches more about delivery guarantees than any documentation page.
Messaging and atomicity
Redis is single-threaded for command execution, so a single command is always atomic. The tools below extend that guarantee to groups of commands.
Pub/Sub
In terminal A, subscribe. In terminal B, publish.
# Terminal A
SUBSCRIBE news
PSUBSCRIBE news:*
# Terminal B
PUBLISH news "hello"
PUBSUB CHANNELS
PUBSUB NUMSUB news
Pub/Sub is fire-and-forget: a subscriber that connects late misses earlier messages. Confirm this yourself, then contrast it with Streams, which keep history.
Transactions
MULTI
SET balance 100
DECRBY balance 30
EXEC
WATCH balance # optimistic locking
MULTI
DECRBY balance 10
EXEC # nil if another client changed balance
Trigger a WATCH failure by modifying balance from a second terminal before EXEC. Also note that Redis transactions have no rollback: if one command fails at runtime, the others still run.
Lua scripting
A Lua script runs as one atomic unit, which is the right tool for read-modify-write logic that WATCH makes clumsy.
EVAL "return redis.call('GET', KEYS[1])" 1 user:1:name
# Safe unlock: delete only if the token matches
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock:x token123
SCRIPT LOAD "return 'hi'"
EVALSHA <sha> 0
Running Redis
Knowing the commands is half the job. The other half is knowing how Redis behaves when memory fills, a process restarts, or a client misbehaves.
Persistence
CONFIG GET save
BGSAVE
LASTSAVE
CONFIG SET appendonly yes
BGREWRITEAOF
INFO persistence
Set a key, run SHUTDOWN SAVE, restart with sudo systemctl restart redis-server, and check that the key survived. Then repeat with persistence disabled and watch the data disappear.
Memory and eviction
CONFIG SET maxmemory 10mb
CONFIG SET maxmemory-policy allkeys-lru
MEMORY USAGE user:1
INFO stats # evicted_keys, keyspace_hits, keyspace_misses
redis-cli --bigkeys
redis-cli --memkeys
Fill memory with a loop and watch evicted_keys climb. Try noeviction, volatile-lru and allkeys-lfu to see how each policy changes which keys survive.
Monitoring and debugging
MONITOR # live command stream; development only
SLOWLOG GET 10
CONFIG SET slowlog-log-slower-than 1000
CLIENT LIST
LATENCY DOCTOR
redis-cli --latency
redis-cli --stat
redis-benchmark -q -n 100000 -c 50
Security and ACLs
CONFIG SET requirepass strongpass
AUTH strongpass
ACL SETUSER reader on >readpass ~cache:* +get +mget
ACL LIST
AUTH reader readpass
Log in as reader and confirm that SET fails with NOPERM. Least-privilege users are one of the cheapest hardening steps Redis offers.
Replication on one machine
Copy the config, change the port, data directory and pidfile, then start a replica:
sudo cp /etc/redis/redis.conf /etc/redis/redis-6380.conf
redis-server /etc/redis/redis-6380.conf --replicaof 127.0.0.1 6379
INFO replication
WAIT 1 100
REPLICAOF NO ONE # promote the replica
Write on the primary and read on the replica. Then try writing to the replica to see the read-only error.
Real-world patterns
Once the commands feel natural, combine them into the patterns production systems actually use.
| Pattern | Commands |
|---|---|
| Cache-aside |
GET product:5; on a miss, SET product:5 <json> EX 300
|
| Fixed-window rate limiter |
INCR rate:user1:202610121530, then EXPIRE it to 60 seconds; reject above the limit |
| Distributed lock |
SET lock:order:9 <token> NX PX 10000; release with the Lua compare-and-delete script |
| Leaderboard |
ZINCRBY to score, ZRANGE ... REV to read the top N |
| Session store | Hash session:<id>, with EXPIRE refreshed on each request |
| Unique visitors |
PFADD and PFCOUNT
|
| Daily active users | One bitmap per date, combined with BITOP
|
| Job queue | List with BLPOP, or Streams with consumer groups |
| Idempotency key | SET idem:<key> 1 NX EX 86400 |
The distributed lock deserves special care. Always store a unique token as the value and release with the Lua script, so one client can never delete a lock another client now holds.
Challenges
Try these without looking anything up. Each one forces you to combine commands you have already met.
- Build a sliding-window rate limiter with a sorted set, using
ZADD,ZREMRANGEBYSCOREandZCARD. - Keep a "recently viewed, last 10 items" list per user with
LPUSHandLTRIM. - Implement a delayed job queue with a sorted set scored by run timestamp.
- Build autocomplete with
ZRANGEBYLEX. - Write a Lua script that decrements stock atomically and refuses to go below zero.
- Compare the memory used by 10,000 small keys against one hash with 10,000 fields, using
MEMORY USAGE.
The last exercise tends to surprise people: small hashes use a compact encoding, so the single hash is often far cheaper.
Conclusion
Redis rewards hands-on practice more than reading. Work through the sections in order, break things deliberately (kill a worker mid-stream, fill memory, fail a WATCH), and finish with the challenges.
Top comments (0)