Redis Sorted Sets and Client-Side Pagination: ZRANGE vs ZSCAN
0 reputation · 29 Jul 2023, 05:51 UTC
0 reputation · 29 Jul 2023, 05:51 UTC
Implementing pagination for large datasets in Redis often involves a choice between rank-based indexing using ZRANGE and cursor-based iteration via ZSCAN. While ZRANGE allows for precise offset-based bounding, the underlying skip list traversal can lead to increased latency as the offset grows deeper into the set.
Conversely, ZSCAN mitigates server-side blocking and performance degradation associated with high offsets, but it does not guarantee a strict sorted order across multiple cursor calls, which is often a requirement for user-facing paginated interfaces.
Given these architectural trade-offs in Redis 7.0+, what is the recommended approach for maintaining strict sort order while avoiding the O(log(N)+M) performance penalty of deep offsets? Is there a documented method to combine cursor-based stability with rank-based precision?
29775 reputation · 29 Jul 2023, 10:58 UTC
For Redis 7.0+ with a requirement for strict score order in user-facing pages, do not use ZSCAN for pagination. Use ZRANGE with LIMIT for shallow pages and keyset seek pagination with score+member as a cursor for deep pages. There is no documented way to make ZSCAN return score-ordered results, so cursor stability from ZSCAN cannot be combined with rank-based precision.
ZSCAN is intended for full key iteration or maintenance tasks, not for ordered page views. Client-side re-sorting of ZSCAN output defeats the purpose of a sorted set and increases client memory.
For stable, ordered pagination without deep offset cost, use a seek cursor based on last seen score and member:
In practice this is ZRANGEBYSCORE with an exclusive lower bound on score, and a tie-breaker on member within the same score. The pattern avoids offset traversal and remains stable under inserts between requests, at the cost of losing random jump to page N.
# first page
ZRANGE myzset 0 99 WITHSCORES
# seek next page after last_score, last_member
ZRANGEBYSCORE myzset (last_score +inf LIMIT 0 100
If ties on score are possible, use the member as a secondary cursor: score = last_score and member > last_member. This requires application logic to build the min bound.
This recommendation assumes read-mostly access and that users can accept next/prev navigation instead of arbitrary page numbers. Write churn during a session can still cause items to appear on two pages or be skipped if scores change. Redis does not provide a built-in transactional snapshot for pagination.
Verification you can run in a test key:
One missing diagnostic that changes the recommendation: what is the maximum page offset you must support and the write rate to the sorted set during a user session. If you need random access to page 10,000+ with high write churn, a different external index is required.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.