Build leaderboards, time windows, delayed-work indexes, and time-ordered secondary paths while exposing cleanup and reliability boundaries.
ZINCRBY, Leaderboards, Sliding Windows, Delayed Work, and Time-Ordered Indexes
Choose hashes, Redis JSON, or many keys from structure, update/query patterns, expiration granularity, memory/cardinality, indexing, ACL boundaries, and migration evidence.
Learning outcomes
AtlasMart now wants four behaviors from the same underlying idea: increment a leaderboard, keep only requests from the last 60 seconds, find jobs whose due time has arrived, and query records in timestamp order. A sorted set can model each when the score is chosen deliberately, but none of these patterns is automatically a complete queue, scheduler, or rate limiter.
Use ZINCRBY while preserving the distinction between relative score mutation and authoritative counters.
Build a bounded sliding-window fixture with timestamp scores and explicit cleanup.
Model delayed work and time-ordered indexes with due/created timestamps.
Explain duplicate-score/timestamp collisions, cleanup cost, and atomicity gaps in multi-command patterns.
Identify hot-key, precision, persistence, retry, and Cluster risks before production use.
All Chapter 06 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from Docker
Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled only
because traffic stays on loopback, default ACL user disabled,
named ACL users atlasmart-app and
academy-admin, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data volume, and no explicit Redis
maxmemory limit or eviction policy. The primary
interface is the redis-cli shipped in the same
pinned image. Mandatory examples use only bounded synthetic
keys under atlasmart:ch06:*.
1. ZINCRBY is an atomic score update
ZINCRBY key increment member atomically adds a
floating increment to the member's score, creating the member if
absent. Atomic command execution prevents a lost update that
would occur with client-side ZSCORE → add →
ZADD.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch06:sales-rankdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZINCRBY atlasmart:ch06:sales-rank 5 seller:anadocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZINCRBY atlasmart:ch06:sales-rank 8 seller:bodocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZINCRBY atlasmart:ch06:sales-rank 4 seller:anadocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZRANGE atlasmart:ch06:sales-rank 0 -1 REV WITHSCORES
One command is atomic; the surrounding business workflow is not. If awarding points must be exactly coordinated with an order ledger, reconcile against authoritative order IDs rather than assuming a score increment is the transaction.
2. Sliding-window mental model: event time is the score
For an exact sliding window, each request can be a unique member and its timestamp the score. Before counting, remove scores older than the window boundary. Then add the current request and count the remaining members. This makes retention explicit instead of letting old entries accumulate forever.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch06:window:user42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZADD atlasmart:ch06:window:user42 100000 req:1 120000 req:2 155000 req:3 161000 req:4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZREMRANGEBYSCORE atlasmart:ch06:window:user42 -inf 101000docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZCOUNT atlasmart:ch06:window:user42 101001 161000docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZRANGE atlasmart:ch06:window:user42 0 -1 WITHSCORES
This deterministic fixture treats numbers as milliseconds. A live implementation must define clock source, boundary inclusivity, request identity, retry behavior, and the exact atomicity requirement. Chapter 23 revisits production rate limiting.
3. Cleanup is part of the algorithm, not housekeeping
Without ZREMRANGEBYSCORE, the window key grows with
lifetime traffic. Cleanup is O(log N + M) where M is the number
removed. That means bursty catch-up cleanup can itself become
expensive.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZCARD atlasmart:ch06:window:user42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZREMRANGEBYSCORE atlasmart:ch06:window:user42 -inf 150000docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZCARD atlasmart:ch06:window:user42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch06:window:user42
Measure both steady-state cardinality and worst cleanup bursts. A “sliding window” without a retention policy is an unbounded time index.
4. Delayed work: due time as score
A delayed-work index stores job IDs as members and due timestamps as scores. A worker queries the smallest due scores up to “now.” This discovers due work efficiently, but a plain read does not claim ownership and a pop/remove can lose work if processing fails.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch06:duedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZADD atlasmart:ch06:due 1700000000000 job:a 1700000005000 job:b 1700000010000 job:cdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZRANGE atlasmart:ch06:due -inf 1700000006000 BYSCORE LIMIT 0 10 WITHSCORESdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZCOUNT atlasmart:ch06:due -inf 1700000006000
The query identifies due members. It does not provide acknowledgment, retry history, consumer ownership, or exactly-once processing. Those are separate messaging/workflow concerns.
5. Wrong approach: ZPOPMIN as a complete reliable scheduler
ZPOPMIN atomically removes the lowest-score member,
which is useful when removal itself is the desired state
transition. But if a worker pops a due job and crashes before
completing the business action, Redis no longer contains that
job in the sorted set.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch06:unsafe-schedulerdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZADD atlasmart:ch06:unsafe-scheduler 1 job:lost-demodocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZPOPMIN atlasmart:ch06:unsafe-scheduler 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZCARD atlasmart:ch06:unsafe-scheduler
ZCARD = 0 proves removal, not completion. Repair
with an explicit processing/ownership state machine, idempotent
job handling, or a structure/system whose delivery semantics
match the requirement.
6. Time-ordered secondary access path
A sorted set can index an authoritative object by timestamp: member = object ID, score = creation/update time. The sorted set is then a secondary access path, not the record itself. Updates must keep the index and primary data synchronized according to an explicit consistency contract.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch06:orders-by-timedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZADD atlasmart:ch06:orders-by-time 1700001000000 order:7001 1700001002000 order:7002 1700001002000 order:7003docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZRANGE atlasmart:ch06:orders-by-time 1700001000000 1700001002000 BYSCORE WITHSCORESdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZREVRANK atlasmart:ch06:orders-by-time order:7003 WITHSCORE
Ties at the same millisecond are ordered by member bytes. If the source order changes or an object is deleted, the secondary index needs reconciliation or explicit transactional/function logic.
7. Timestamp precision and collision design
Milliseconds and contemporary microseconds fit inside the exact ±2^53 integer region of Redis scores; nanosecond epoch timestamps do not. Exact representability does not imply uniqueness: multiple events can share a millisecond or microsecond. Use unique members, a tie-break strategy, or a Stream when append/history semantics—not merely time ordering—are the real requirement.
8. Multi-command windows have race boundaries
A rate-limit sequence such as remove-old → add-current → count is three separate atomic commands, not one atomic algorithm. Concurrent clients can interleave. Later chapters cover MULTI/EXEC and Functions; Chapter 23 applies those tools to production rate-limit patterns. Here, the important lesson is to identify the race rather than silently calling the pattern atomic.
9. Hot-key and retention model
| Pattern | Growth driver | Required control | Typical risk |
|---|---|---|---|
| Leaderboard | number of ranked members | scope/shard/archive policy | global write hot key |
| Sliding window | events inside retention horizon | continuous score cleanup | burst cleanup + cardinality |
| Delayed work | scheduled outstanding jobs | claim/completion cleanup | loss/duplicate processing |
| Time index | indexed object population | delete/update reconciliation | stale secondary index |
10. Reproducible combined lab
Exercise score mutation, due-time reads, and explicit cleanup with small deterministic values.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch06:lab:rank atlasmart:ch06:lab:timedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZINCRBY atlasmart:ch06:lab:rank 10 user:adocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZINCRBY atlasmart:ch06:lab:rank 15 user:bdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZINCRBY atlasmart:ch06:lab:rank 8 user:adocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZRANGE atlasmart:ch06:lab:rank 0 1 REV WITHSCORESdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZADD atlasmart:ch06:lab:time 100 e:1 200 e:2 300 e:3 400 e:4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZREMRANGEBYSCORE atlasmart:ch06:lab:time -inf 150docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app ZRANGE atlasmart:ch06:lab:time 151 350 BYSCORE WITHSCORES
Verification: user:a reaches 18; user:b remains 15; event e:1 is removed; events e:2 and e:3 remain in the queried interval.
11. Production judgment
Use Sorted Sets when numeric ordering plus point membership is the core primitive. Rate limiting and scheduling require more than ordering: define atomicity, retry/idempotency, clock assumptions, ownership, retention, and failure recovery. Persisted/replicated sorted-set state still does not guarantee external work completion. Monitor cardinality, cleanup removals, hot-key commands/sec, range-result sizes, memory, and latency distributions.
12. Summary and next step
ZINCRBY supports atomic score mutation; timestamp scores support windows, due-work discovery, and time indexes; cleanup and reliability remain application responsibilities. Next, we derive rankings across multiple sorted sets with weighted union, intersection, and difference.
Check your understanding
- Why is ZINCRBY safer than client-side read/add/write for one score?
- What happens to a sliding-window key if old scores are never removed?
- Does ZRANGE due work claim ownership?
- Why can ZPOPMIN lose work?
- Are remove-old/add/count automatically one atomic rate-limit decision?
Review the answers
ZINCRBY performs the score increment atomically on the server.
It grows with lifetime events instead of the intended retention horizon.
No. It only reads matching members.
The member is removed before external processing succeeds, so a worker crash can make it disappear.
No. They are separate commands unless combined with an explicit atomic mechanism.
Authoritative references
- Redis sorted sets — data model, score/rank behavior, and common patterns
- ZADD — conditional updates, score precision, and return behavior
- ZRANGE — rank, BYSCORE, BYLEX, REV, LIMIT, and WITHSCORES
- ZINCRBY — relative score updates
- ZUNION — weighted unions and aggregation
- ZINTER — weighted intersections and complexity
- ZDIFF — sorted-set difference semantics
- Redis Cluster specification — slot locality for multi-key commands
- Redis Open Source 8.10 release notes — pinned course release family
- ZREMRANGEBYSCORE — window cleanup
- ZPOPMIN — destructive lowest-score pop