Your team is debating whether to add Kafka to your stack for event streaming — or whether Redis Streams can handle it. Both process millions of messages per second, but they solve different problems. Here's the architectural breakdown to make the right call for your workload.
- Redis Streams: best for sub-millisecond latency, simple consumer groups, and teams already on Redis
- Kafka: best for TB-scale retention, exactly-once semantics, and cross-team event backbone
- Redis Streams messages expire or overflow; Kafka retains indefinitely by design
- Kafka requires Zookeeper/KRaft overhead; Redis Streams add zero new infrastructure
How Redis Streams Work
Redis Streams (introduced in Redis 5.0) is an append-only log data structure with consumer group support. Each message gets an auto-generated ID based on the millisecond timestamp plus a sequence number. Consumer groups allow multiple consumers to process the same stream with at-least-once delivery guarantees.
# Produce a message
XADD orders * user_id 12345 product_id 999 quantity 2
# Create consumer group
XGROUP CREATE orders processing-group $ MKSTREAM
# Consume messages (read up to 10, block 2s if empty)
XREADGROUP GROUP processing-group worker-1 COUNT 10 BLOCK 2000 STREAMS orders >
# Acknowledge processed messages
XACK orders processing-group 1722000000000-0
# Check pending (unacknowledged) messages
XPENDING orders processing-group - + 10Redis Streams Consumer Group Pattern
import redis
r = redis.Redis(host='redis.internal', port=6379)
# Create consumer group
try:
r.xgroup_create('orders', 'processors', id='$', mkstream=True)
except redis.ResponseError:
pass # group already exists
while True:
messages = r.xreadgroup(
'processors', 'worker-1',
{'orders': '>'},
count=10, block=2000
)
for stream, msgs in (messages or []):
for msg_id, data in msgs:
process_order(data)
r.xack('orders', 'processors', msg_id)How Apache Kafka Works
Kafka stores messages in immutable, ordered partitions within topics. Each partition is a durable log replicated across brokers. Consumers track their position (offset) independently, enabling replay, time-travel queries, and multiple independent consumer groups reading the same topic at different rates.
from confluent_kafka import Producer, Consumer
# Producer
p = Producer({'bootstrap.servers': 'kafka.internal:9092'})
p.produce('orders', key='12345', value=json.dumps({'user_id': 12345, 'product_id': 999}))
p.flush()
# Consumer with exactly-once processing
c = Consumer({
'bootstrap.servers': 'kafka.internal:9092',
'group.id': 'order-processors',
'auto.offset.reset': 'earliest',
'enable.auto.commit': False # manual commit for exactly-once
})
c.subscribe(['orders'])
while True:
msg = c.poll(timeout=1.0)
if msg and not msg.error():
process_order(json.loads(msg.value()))
c.commit(asynchronous=False) # commit after processingRedis Streams vs Kafka: Side-by-Side
| Dimension | Redis Streams | Apache Kafka |
|---|---|---|
| Latency | Sub-millisecond | 2–10ms typical |
| Throughput (single node) | ~1M msgs/s | ~1M msgs/s per partition |
| Message retention | Memory-limited (MAXLEN) | Configurable (days/weeks/forever) |
| Delivery guarantee | At-least-once | At-least-once; exactly-once with transactions |
| Consumer replay | Yes (while in memory) | Yes (full history) |
| Cross-service fan-out | Limited (one topic per stream) | Excellent (consumer groups per topic) |
| Infrastructure overhead | Zero (already have Redis) | Brokers + ZooKeeper or KRaft |
| Schema registry | No | Confluent Schema Registry |
Redis Streams are bounded by memory. Set XADD orders MAXLEN ~ 100000 * ... to cap stream length. Without MAXLEN, a busy stream will exhaust Redis memory and trigger OOM kills.
When to Use Redis Streams
- Low-latency job queues (task processing, webhooks, real-time notifications)
- Short-lived event pipelines where you don't need weeks of history
- Teams already running Redis who want to avoid Kafka's operational complexity
- Event volumes under ~100M messages/day where memory retention is feasible
When to Use Kafka
- Event sourcing and audit logs requiring indefinite retention
- Cross-team event backbone shared by 10+ services
- Exactly-once semantics for financial transactions
- Stream processing with Flink, Spark Streaming, or Kafka Streams
- Redis Streams wins on latency (sub-ms) and operational simplicity — zero new infrastructure if you're already on Redis.
- Kafka wins on retention, exactly-once semantics, and cross-team fan-out at TB scale.
- Always set MAXLEN on Redis Streams in production — unbounded streams exhaust memory silently.
- For most applications under 100M events/day, Redis Streams avoids Kafka's operational overhead without sacrificing correctness.
Working with JusDB on Redis and Event Streaming
JusDB architects event streaming solutions using Redis Streams and Kafka for engineering teams at any scale. We tune Redis memory allocation, configure consumer groups for reliable processing, and design Kafka topologies for cross-service event backbones.
Explore JusDB Redis Services → | Talk to a DBA
Related reading: