Redis beyond caching: rate limits, locks, deduplication and queues

Redis is usually introduced as a cache, but its data structures solve many coordination problems. Practical patterns for rate limiting, distributed locks, request deduplication, queues and leaderboards, with their limits.

Abstract diagram of fast in-memory nodes coordinating many application requests

Redis is an in-memory data store with rich data structures — strings, hashes, lists, sets, sorted sets and streams — and atomic operations on all of them. That combination makes it far more than a cache. Many coordination problems in web platforms, from rate limiting to deduplicating expensive supplier calls, have simple and fast Redis solutions.

Caching, done properly

The baseline use still deserves care:

  • Namespaced keys with versions (search:v3:<hash>) so cache formats can change safely.
  • TTLs on everything, chosen per data type.
  • Stampede protection — when a hot key expires, let one request rebuild it while others wait briefly or receive the stale value.

Layered, volatility-aware caching for travel search is covered in caching flight search.

Rate limiting

A fixed-window limiter is two commands: increment a counter keyed by client and window, and set an expiry on first use. If the counter exceeds the limit, reject the request.

For smoother behaviour, a sliding window uses a sorted set of request timestamps per client, removing entries older than the window and counting the rest. A token bucket implemented in a Lua script allows short bursts while enforcing an average rate. Lua scripts execute atomically, which avoids race conditions between reading and updating.

Rate limits protect both your platform and your suppliers — especially important when contracts limit look-to-book ratios — and enforce fairness between tenants in multi-tenant SaaS.

Request deduplication

When identical expensive requests arrive at the same moment, only one should reach the supplier:

  1. Normalise the request and hash it into a key.
  2. Try SET key <token> NX PX <ms>.
  3. If it succeeds, perform the call and store the result under a result key.
  4. If it fails, poll briefly for the result key or subscribe for completion.

This “single flight” pattern can cut supplier traffic substantially during traffic spikes.

Distributed locks

SET lock:<resource> <unique-token> NX PX <ttl> acquires a lock that expires automatically. Release it with a Lua script that deletes the key only if the token matches, so a process never releases a lock it no longer holds.

Be clear about what such a lock guarantees. With expiry, a slow process can lose its lock while still working. For efficiency (avoid doing the same job twice) that is acceptable. For correctness (never let two writers update a record) add a fencing token checked by the database, or rely on database constraints and transactions. Debates around multi-node algorithms such as Redlock centre on exactly this distinction.

Queues and streams

  • Lists with blocking pops make simple job queues.
  • Streams add consumer groups, acknowledgements and pending-entry tracking, so a crashed worker’s jobs can be reclaimed.

Streams work well for moderate-volume background processing. For durable, high-volume event pipelines, or where events must never be lost, a dedicated broker or a database outbox may be a better fit — see reliable webhooks.

Counters, leaderboards and sessions

  • Counters — atomic increments for views, quotas and usage metering.
  • Sorted sets — rankings by score, such as popular routes or top-selling hotels.
  • Hashes — compact storage for session and user state.

Operating Redis

  • Decide what may be lost. If Redis holds only caches, a restart is harmless; if it holds queues or sessions, configure persistence (AOF or snapshots) and replication.
  • Set maxmemory and an eviction policy that matches the data — eviction is fine for caches, disastrous for queues. Consider separate instances for each.
  • Avoid slow commands on large keys in production (KEYS, large SMEMBERS); use SCAN.
  • Monitor memory, evictions, latency and connected clients.

The takeaway

Redis turns many hard coordination problems into a few atomic operations. Use it confidently for caches, rate limits, deduplication and lightweight queues, and be explicit about durability and lock guarantees so it never becomes the silent single point of failure.

Frequently asked questions

What is Redis used for besides caching?

Redis is widely used for rate limiting, distributed locks, request deduplication, session storage, job queues with lists or streams, real-time counters, leaderboards with sorted sets, and pub/sub messaging.

Are Redis locks safe?

A single-instance lock using SET with NX and an expiry, released only by the holder, is suitable for efficiency, such as avoiding duplicate work. For correctness-critical mutual exclusion, combine it with fencing tokens or use the database’s own locking and constraints.

How do you implement rate limiting with Redis?

A fixed-window limiter increments a counter keyed by client and time window and sets an expiry. Sliding-window and token-bucket limiters use sorted sets or Lua scripts to smooth bursts and keep the operation atomic.