Solutions · Leaderboards & rate-limit

Sorted sets, atomic counters, and a Lua script for the tricky bits.

Realtime leaderboards with `ZADD` / `ZRANGE` and per-key rate-limits with `INCR` / `EXPIRE` — running on the same managed Redis that handles your sessions.

Two primitives, a lot of features

Sorted sets and counters cover most of what real-time applications need. A leaderboard is `ZADD scores user_id score` to update and `ZREVRANGE scores 0 9` to read the top 10 — both O(log N) on Redis. Rate-limiting is `INCR ratelimit:<key>` plus `EXPIRE` on first write. For atomic token-bucket logic we ship a Lua script you can run server-side.

Sorted sets

O(log N) updates, O(log N + M) range reads. Per-user, per-day, per-region leaderboards in a single command.

Atomic counters

`INCR` and `INCRBY` are atomic — no read-modify-write races. Pair with `EXPIRE` for sliding rolling counts.

Lua server-side

Run multi-step logic in a single round-trip with atomicity guarantees. Token buckets, GCRA rate-limiting, ranked-choice updates.

Streams for events

When you need history (not just current rank), Redis Streams give you durable, replayable event queues with consumer groups.

Code sample

lua
GCRA rate-limit as a single atomic script
-- KEYS[1]: the rate-limit key
-- ARGV[1]: emission interval in milliseconds (e.g. 200 = 5 req/s)
-- ARGV[2]: burst tolerance in milliseconds (e.g. 1000 = burst of 5)
-- Returns: 1 if allowed, 0 if rate-limited.

local now = redis.call('TIME')
local now_ms = tonumber(now[1]) * 1000 + math.floor(tonumber(now[2]) / 1000)
local emission_interval = tonumber(ARGV[1])
local burst = tonumber(ARGV[2])

local last = tonumber(redis.call('GET', KEYS[1])) or 0
local tat = math.max(last, now_ms)
local allow_at = tat - burst

if now_ms < allow_at then
  return 0
end

redis.call('SET', KEYS[1], tat + emission_interval, 'PX', burst + emission_interval)
return 1

Leaderboard sketch

For a daily-reset global leaderboard with millions of users: use one sorted set per day (`lb:2026-05-26`) so reads are bounded by that day's active users, not your whole user base. Schedule a daily job to drop yesterday's key. Compute the all-time top with a periodic job that merges day-sets via `ZUNIONSTORE`.

Examples

  • Game leaderboards — daily, weekly, all-time
  • Real-time auction bids — top-N bidders, atomic update on each bid
  • API rate-limiting — per-IP, per-API-key, per-tenant
  • Concurrent-job limiter — at most N background jobs running per tenant
  • Trending content — sorted set keyed by decayed score

Frequently asked questions

Is `INCR` really safe under concurrency?
Yes — Redis processes commands single-threaded, so `INCR` is atomic by definition. Same for `INCRBY`, `DECR`, sorted-set updates, and Lua scripts.
How do I avoid hot-key contention on rate-limits?
Most rate-limits are naturally sharded across IPs or API keys, so single-key contention is rare. If you need a single global limit, the Contact-us tier supports cluster mode with hash-tag-based key routing.
When should I switch to Streams instead of sorted sets?
Use Streams when you need event history with consumer groups (workers competing for events, replayable from a saved offset). Sorted sets are better when you only care about current rank or position.

Leaderboards in three commands, rate-limits in one Lua script.

256 MB free Redis handles tens of thousands of concurrent leaderboard entries.