Redis Sorted Sets
Sorted Sets are sets where each member has an associated score, keeping elements ordered by score. They combine the uniqueness of sets with the ordering capabilities needed for leaderboards, time-series, and priority queues.
Key Characteristics
• Members are unique, scores can repeat • Ordered by score (ascending by default) • O(log N) add, remove, and rank operations • Range queries by score or rank • Lexicographical ordering when scores are equal
Basic Operations
# Add members with scores
ZADD leaderboard 100 "alice" 85 "bob" 92 "charlie"
# Get score
ZSCORE leaderboard "alice" # → 100
# Get rank (0-based, low to high)
ZRANK leaderboard "bob" # → 0 (lowest score)
ZREVRANK leaderboard "alice" # → 0 (highest score)
# Count members
ZCARD leaderboard # → 3Range Queries by Rank
# Get by rank (low to high)
ZRANGE leaderboard 0 -1 # All members
ZRANGE leaderboard 0 2 # First 3 (lowest scores)
ZRANGE leaderboard 0 2 WITHSCORES # With scores
# Get by rank (high to low)
ZREVRANGE leaderboard 0 2 # Top 3
ZREVRANGE leaderboard 0 9 WITHSCORES # Top 10 with scoresRange Queries by Score
# Get members with scores in range
ZRANGEBYSCORE leaderboard 80 100 # Scores 80-100
ZRANGEBYSCORE leaderboard (80 100 # Exclusive lower bound (>80)
ZRANGEBYSCORE leaderboard 80 (100 # Exclusive upper bound (<100)
ZRANGEBYSCORE leaderboard -inf +inf # All (useful with LIMIT)
# With pagination
ZRANGEBYSCORE leaderboard 0 100 LIMIT 0 10 # First 10
ZRANGEBYSCORE leaderboard 0 100 LIMIT 10 10 # Next 10
# Reverse (high to low)
ZREVRANGEBYSCORE leaderboard 100 80 # Note: max before minScore Updates
# Increment score
ZINCRBY leaderboard 10 "bob" # Add 10 to bob's score
# Conditional updates
ZADD leaderboard XX 110 "alice" # Update only if exists
ZADD leaderboard NX 75 "dave" # Add only if not exists
ZADD leaderboard GT 120 "alice" # Update only if new > current
ZADD leaderboard LT 90 "alice" # Update only if new < current
# Get updated score
ZADD leaderboard 150 "alice" INCR # Like ZINCRBYRemoval Operations
# Remove by member
ZREM leaderboard "charlie"
ZREM leaderboard "a" "b" "c" # Remove multiple
# Remove by score range
ZREMRANGEBYSCORE leaderboard 0 50 # Remove scores 0-50
ZREMRANGEBYSCORE leaderboard -inf 50 # Remove all ≤ 50
# Remove by rank range
ZREMRANGEBYRANK leaderboard 0 4 # Remove bottom 5
# Pop min/max
ZPOPMIN leaderboard # Remove lowest score
ZPOPMAX leaderboard # Remove highest score
ZPOPMIN leaderboard 3 # Remove 3 lowestSet Operations
# Union with weights
ZUNIONSTORE combined 2 scores:week1 scores:week2 WEIGHTS 1 2
# Member scores: (week1 * 1) + (week2 * 2)
# Union with aggregation
ZUNIONSTORE combined 2 key1 key2 AGGREGATE SUM # Default
ZUNIONSTORE combined 2 key1 key2 AGGREGATE MIN
ZUNIONSTORE combined 2 key1 key2 AGGREGATE MAX
# Intersection
ZINTERSTORE overlap 2 key1 key2 AGGREGATE MAXCounting
# Count by score range
ZCOUNT leaderboard 80 100 # Members with score 80-100
ZCOUNT leaderboard -inf +inf # All members (same as ZCARD)
# Count by lexicographical range (when scores are equal)
ZLEXCOUNT myset [a [z # Members from 'a' to 'z'Use Case: Real-time Leaderboard
def update_score(game_id, user_id, score):
key = f"leaderboard:{game_id}"
ZADD(key, score, user_id)
def get_top_players(game_id, count=10):
key = f"leaderboard:{game_id}"
return ZREVRANGE(key, 0, count - 1, WITHSCORES=True)
def get_user_rank(game_id, user_id):
key = f"leaderboard:{game_id}"
rank = ZREVRANK(key, user_id)
return rank + 1 if rank is not None else None # 1-based
def get_user_score(game_id, user_id):
key = f"leaderboard:{game_id}"
return ZSCORE(key, user_id)
def get_players_around(game_id, user_id, count=5):
"""Get players around a user's rank"""
key = f"leaderboard:{game_id}"
rank = ZREVRANK(key, user_id)
start = max(0, rank - count)
end = rank + count
return ZREVRANGE(key, start, end, WITHSCORES=True)Use Case: Sliding Window Rate Limiter
def is_rate_limited(user_id, limit=100, window_seconds=60):
key = f"ratelimit:{user_id}"
now = current_timestamp_ms()
window_start = now - (window_seconds * 1000)
# Atomic operations in pipeline
pipeline:
# Remove old entries
ZREMRANGEBYSCORE(key, 0, window_start)
# Add current request (score = timestamp)
ZADD(key, now, f"{now}-{random_id()}")
# Count requests in window
count = ZCARD(key)
# Set expiration for cleanup
EXPIRE(key, window_seconds)
return count > limitUse Case: Priority Queue
# Add tasks with priority (lower = higher priority)
ZADD tasks:priority 1 "urgent_task" # Priority 1 (highest)
ZADD tasks:priority 5 "normal_task" # Priority 5
ZADD tasks:priority 10 "low_task" # Priority 10
# Get highest priority task
ZPOPMIN tasks:priority # Returns and removes "urgent_task"
# Peek without removing
ZRANGE tasks:priority 0 0 # View highest priority
# Blocking pop (wait for tasks)
BZPOPMIN tasks:priority 30 # Block up to 30 secondsUse Case: Time-Series Data
# Store events with timestamp as score
def add_event(stream_id, event_data):
key = f"events:{stream_id}"
timestamp = current_timestamp_ms()
ZADD(key, timestamp, f"{timestamp}:{event_data}")
# Get events in time range
def get_events(stream_id, start_time, end_time):
key = f"events:{stream_id}"
return ZRANGEBYSCORE(key, start_time, end_time)
# Get last N events
def get_recent_events(stream_id, count=100):
key = f"events:{stream_id}"
return ZREVRANGE(key, 0, count - 1, WITHSCORES=True)
# Cleanup old events
def cleanup_old_events(stream_id, max_age_ms):
key = f"events:{stream_id}"
cutoff = current_timestamp_ms() - max_age_ms
ZREMRANGEBYSCORE(key, 0, cutoff)Knowledge is power.