HomeBlogBackend
Backend

Redis Beyond Caching: Pub/Sub, Streams, and Lua Scripts

Most developers only use Redis as a key-value store. Here's how to use its Pub/Sub, Streams, and scripting features to solve hard distributed systems problems.

Nov 2, 2024 13 min read 11.9k views
Redis Pub/Sub Distributed Systems Backend

Redis is the most misunderstood tool in the backend ecosystem. Most teams use 10% of what it can do — GET/SET caching and maybe a session store. But Redis Streams, Pub/Sub, sorted sets, and atomic Lua scripts solve problems that would otherwise require Kafka, RabbitMQ, or a distributed lock manager.

Pub/Sub for Real-Time Features

Redis Pub/Sub is a fire-and-forget messaging system — publishers don't know if anyone is listening, and messages are not persisted. This makes it perfect for real-time UI updates (notifications, live counts, presence indicators) where losing a message is acceptable.

javascript
// Publisher
const pub = createClient();
await pub.publish('notifications:user:123', JSON.stringify({
  type: 'order_shipped',
  orderId: 'ord_abc',
  timestamp: Date.now()
}));

// Subscriber
const sub = createClient();
await sub.subscribe('notifications:user:123', (message) => {
  const event = JSON.parse(message);
  broadcastToWebSocket(event);
});
⚠️ Warning

Pub/Sub messages are NOT persisted. If the subscriber is offline when a message is published, the message is lost forever. For guaranteed delivery, use Redis Streams instead.

Redis Streams for Durable Event Logs

Redis Streams are an append-only log — think of a lightweight Kafka. Messages are persisted, consumers can read from any position, and consumer groups enable competing consumers with acknowledgement. For internal event buses in a single service, Streams are often all you need.

Atomic Operations with Lua Scripts

Lua scripts run atomically in Redis — no other commands execute while your script is running. This lets you implement race-condition-free operations like rate limiters, inventory reservations, and distributed locks without multi-step WATCH/MULTI/EXEC transactions.

lua
-- Atomic rate limiter: increment counter, set TTL only on first request
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

local current = redis.call('INCR', key)
if current == 1 then
  redis.call('EXPIRE', key, window)
end

if current > limit then
  return 0  -- rate limited
end
return 1  -- allowed

Found this useful?

Share it with your network