Redis is the in-memory data structure server that became "the swiss army knife" of backend engineering — caches, queues, rate limits, pub/sub, leaderboards, geo. The data structures and the speed (sub-millisecond, 100k+ ops/sec on a single node) make it tempting to use for everything.
It is not a database. It is not durable in the way Postgres is durable. It is not a substitute for a real message queue. Knowing where the lines are is the difference between Redis being a delight and being a footgun.
The 95% use case for Redis. Application checks cache; on miss, fetches from the source-of-truth (DB), populates cache.
def get_user(id):
cached = redis.get(f"user:{id}")
if cached: return json.loads(cached)
user = db.query(...)
redis.setex(f"user:{id}", 3600, json.dumps(user)) # 1 hour TTL
return user
Considerations:
DELETE the cache key.See CachingStrategies for cache-aside vs write-through vs other patterns.
Token bucket is the typical pattern. Use a Lua script for atomicity:
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local data = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(data[1]) or limit
local last_refill = tonumber(data[2]) or now
local elapsed = now - last_refill
tokens = math.min(limit, tokens + elapsed * refill_rate)
if tokens < 1 then
redis.call('HSET', key, 'tokens', tokens, 'last_refill', now)
return 0 -- rejected
end
tokens = tokens - 1
redis.call('HSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600)
return 1 -- allowed
Atomic, fast, correct. See ApiRateLimitingAlgorithms for the full pattern.
The naive pattern:
def acquire_lock(key, timeout):
return redis.set(key, value, nx=True, ex=timeout)
SET NX EX is atomic. If it returns OK, you have the lock; release with a script that checks the value to prevent another caller from accidentally releasing your lock.
The catch: this is not safe under all failure conditions. Martin Kleppmann's analysis (2017) and counter-arguments are worth reading. The short version:
For locks where correctness matters (financial transactions, irreversible operations), use a real consensus system (etcd, ZooKeeper, Consul). For locks where best-effort suffices (rate-limiting periodic jobs, cron singleton enforcement), Redis-based locks are fine.
Redlock (Redis's recommended distributed lock) tries to address the failover concern by acquiring on a majority of independent Redis nodes. It improves things but doesn't fully solve the GC-pause / fencing-token problem.
Rule of thumb: Redis lock for "we'd prefer not to run two of these at once but it's not catastrophic if we do." etcd / ZooKeeper for "two of these running at once would be catastrophic."
Redis can be a queue. Two patterns:
LPUSH queue "job1"
BRPOP queue 30 # blocking pop, 30s timeout
Simple, fast. Good for fire-and-forget jobs where loss is tolerable.
Limitations:
XADD / XREAD / XACK — Redis 5+. Has consumer groups, message acks, pending entries lists, retention. Closer to Kafka than to a list.
XADD orders * order_id 42 status pending
XREADGROUP GROUP processors worker1 COUNT 10 STREAMS orders >
XACK orders processors <message-id>
Production-ready for moderate workloads. Above ~10k messages/sec sustained, Kafka starts winning on durability and operational maturity.
When to use Redis for queueing:
When not:
PUBLISH / SUBSCRIBE:
SUBSCRIBE notifications:user:42
PUBLISH notifications:user:42 "{"type":"new_message","id":12345}"
Fire-and-forget. Subscribers receive messages only while connected. Disconnected subscriber misses everything that was published while disconnected.
Use for:
Don't use for:
Atomic counters at speed:
INCR pageview:home:2026-04-25
INCRBYFLOAT revenue:total 19.99
HINCRBY user:42:counters orders 1
Hyperloglog for cardinality estimates (unique-visitor counts):
PFADD daily_visitors "user_42" "user_43" ...
PFCOUNT daily_visitors -- approximate; uses 12KB regardless of count
Sorted sets for leaderboards:
ZADD leaderboard 9870 "alice" 8420 "bob"
ZREVRANGE leaderboard 0 9 WITHSCORES -- top 10
ZREVRANK leaderboard "alice" -- their rank
This is where Redis is almost magically efficient. Real-time analytics with millions of events / second, no specialized analytics database.
Cache user session data with a TTL keyed by session ID. Simple, fast, scales. Most web frameworks have built-in Redis session store backends.
Caveat: if Redis is your only session store and Redis goes down, all users are logged out. Persist sessions to a durable store too if this matters.
GEOADD, GEORADIUS, GEOSEARCH. Stores lat/lng for keys; queries within radius or rectangle.
Useful for "find users near me," store locator, ride-sharing matches. Doesn't replace PostGIS for serious GIS but covers a lot of ground.
Single Redis node: ~100k ops/sec sustained. Adequate for most applications.
Redis Cluster: shards data across nodes. Adds:
{user42}:orders to colocate).Most teams under 50k ops/sec with < 100GB working set don't need cluster. Promote when you do.
Redis offers two modes:
fsync=always (rarely used; usually everysec).For cache-only Redis: persistence off (the cache is recoverable). For Redis-as-database: AOF with everysec and replicas.
If you're using Redis as the source of truth for important data, reconsider. Redis is designed as a cache; durability is a backstop, not the focus.
By 2026, the Redis ecosystem has split. The original Redis Inc. moved to a non-OSS license; the community forked into:
For a new deployment in 2026, Valkey is the most common pick. Functionally equivalent to Redis for the patterns above.
KEYS in production. O(N) blocks the entire server. Use SCAN.