If your Redis experience is limited to SET and GET, you’re treating a Swiss Army knife like a single screwdriver. Redis has matured into a multi-model data structure server that can handle everything from rate limiting and leaderboards to pub/sub messaging and stream processing—often with simpler operational overhead than the alternatives.

The good news: you don’t need to rewrite your whole system to get value. You just need to start using the right Redis primitives for the job.

Redis as a multi-model data structure server

The fastest way to appreciate Redis is to stop thinking in terms of “cache” and start thinking in terms of “data structures with commands designed for specific workflows.”

Instead of storing one value per key, Redis gives you purpose-built tools:

  • Lists, sets, and sorted sets for different access patterns
  • Streams for event ingestion, retention, and consumer groups
  • Pub/sub for lightweight real-time messaging
  • Hashes for structured objects
  • Lua scripting for atomic, multi-step logic
  • Transactions and server-side primitives to coordinate distributed behavior

That means Redis can sit at the center of real application logic—not just performance tuning.

A practical mindset shift

Ask a better question: “What data structure does my problem resemble?”

  • Need ranking? Use sorted sets.
  • Need ordered events? Use streams.
  • Need real-time fan-out? Use pub/sub.
  • Need atomic updates across keys? Use Lua.

Pub/sub: real-time notifications without the overhead

Pub/sub is Redis’s simplest messaging model: publishers send messages to channels; subscribers receive them. It’s a great fit when you want quick, transient distribution and don’t need durable replay.

Example: live notifications

Suppose you have a web app where users get notifications when something changes:

  • When an event happens, publish to a channel like notify:user:123.
  • Subscribers listen and push to WebSocket connections.
PUBLISH notify:user:123 "Your report is ready"

In practice, pub/sub shines for:

  • Live UI updates
  • Server-to-server “nudge” messaging
  • Broadcasting lightweight state changes

The gotcha you should plan for

Pub/sub is not durable. If a subscriber is offline, it misses messages. That’s fine for “best effort” updates, but if you need reliability, you should pivot to Streams (more on that next).

Streams: a lightweight Kafka alternative

Redis Streams are designed for ordered event logs with consumer groups—meaning you can process events asynchronously, at scale, with less system complexity than a full Kafka deployment.

Example: background jobs from events

Imagine you receive events from an API (e.g., user actions) and need a worker to process them. With Streams:

  1. Your API appends events to a stream.
  2. Worker processes read from the stream using a consumer group.
  3. You can track progress with consumer offsets.

Writing to a stream looks like:

XADD events:activity * userId 42 action "clicked_button"

Workers then read using XREADGROUP, claim pending messages when needed, and continue.

Why this beats “roll-your-own” queues

Many teams start by pushing jobs into lists or using ad-hoc keys. Streams are better because they are:

  • Ordered: event sequencing is preserved.
  • Retainable: you can keep messages for retries and reprocessing.
  • Operationally cohesive: offsets and consumer groups help you avoid inventing your own coordination logic.

If Kafka is overkill for your needs—or you want to avoid running a separate distributed log system—Streams are often the pragmatic answer.

When streams aren’t enough

If you need strict, enterprise-grade event log governance, heavy multi-datacenter replication, or deep Kafka ecosystem integrations, Kafka still has advantages. But for many product teams, Streams hit the sweet spot: durable enough, simple enough.

Sorted sets: ranking, deduplication, and time windows made easy

Sorted sets (ZSET) are Redis’s most underused “business logic” primitive. They store members with scores and keep them ordered. That single property unlocks a ton of features that are otherwise annoyingly complex.

Example: real-time leaderboards

A classic ranking system: you award points and need to show the top users.

ZINCRBY leaderboard:points 10 user:42

To get the top 10:

ZREVRANGE leaderboard:points 0 9

What makes this elegant is that the sorted set is always ready to answer ranking queries—no separate database sorting step required.

Example: expiring leaderboards and time windows

For “leaderboards for the last 24 hours,” you can store timestamps as scores and periodically prune old entries. A common pattern:

  • Add score = timestamp
  • Trim entries older than your window
ZREMRANGEBYSCORE leaderboard:last24h 0 (currentTime - 86400)

This approach can work surprisingly well for time-bounded metrics without needing a heavyweight analytics pipeline.

Lua scripting: atomic multi-step operations you can actually trust

When distributed systems go wrong, it’s often because multi-step operations aren’t atomic. Redis Lua scripting lets you execute multiple commands server-side as a single atomic operation.

If you’re doing anything like:

  • check then set
  • decrement with guards
  • update multiple keys consistently

…Lua is your tool of choice.

Example: atomic rate limiting-ish logic

Suppose you want to increment a counter only if it doesn’t exceed a threshold, and you want the read/update decision to be consistent.

Lua runs the logic entirely on the Redis server, so you don’t get race conditions between separate client requests.

A typical pattern is:

  • Key holds a counter (or hash field)
  • Lua reads current value
  • If under threshold, it increments and sets expiry if needed
  • Otherwise, it returns a denial

Even if you don’t copy an exact template, the key point is this: move the “decision” into Redis.

Practical advice

Lua scripts are power tools—use them deliberately:

  • Keep scripts small and focused.
  • Use EVALSHA with cached script hashes for performance.
  • Return simple values (0/1, remaining tokens, current score) so your application logic stays readable.

Lua turns Redis from a storage layer into a coordination layer.

Putting it together: design patterns that actually work in production

Once you stop treating Redis as a cache, you can build clean distributed patterns. Here are a few that consistently improve outcomes.

Pattern 1: Streams for ingestion, Sets/Sorted sets for state

  • Ingest events into a stream
  • Update ranking or dedup state using sorted sets
  • Use Lua to ensure multi-key consistency during updates

This gives you both durability (stream) and fast queryability (sorted sets).

Pattern 2: Pub/sub for real-time UX, Streams for reliability

  • Pub/sub for “instant” UI nudges (best effort)
  • Streams for everything that must not be lost (auditing, retries, workflows)

This hybrid approach keeps your system responsive without sacrificing correctness where it matters.

Pattern 3: Rate limiting with Redis data structures + server-side logic

Instead of scattering rate-limit logic across the app:

  • Store counters in Redis
  • Use Lua to enforce thresholds atomically
  • Set expirations to keep memory usage bounded

Your API becomes simpler, and your guarantees become stronger.

Conclusion: Redis isn’t smaller infrastructure—it’s smarter architecture

Redis is not “just a cache.” It’s a multi-model data structure platform with messaging (pub/sub, streams), ranking (sorted sets), and atomic computation (Lua). If your current usage is SET and GET, you’re leaving reliability, speed, and architectural clarity on the table.

Pick one feature you currently handle awkwardly—rate limiting, leaderboards, event processing, coordination—and implement it with the Redis primitive that matches the shape of the problem. You’ll be surprised how quickly your system becomes simpler and more robust.