Sets, Sorted Sets, and Streams
Set Operations
Sets store unique, unordered members with O(1) add/remove/membership test.
SADD followers:user:1001 2001 2002 2003
SADD followers:user:1002 2002 2004
SCARD followers:user:1001
SMEMBERS followers:user:1001
SISMEMBER followers:user:1001 2002
# Set algebra — powerful for social and tagging features
SINTER followers:user:1001 followers:user:1002 # mutual followers
SUNION followers:user:1001 followers:user:1002 # all followers
SDIFF followers:user:1001 followers:user:1002 # unique to 1001
# Random sampling
SRANDMEMBER tags:article:42 3
SPOP tags:article:42 1
Iterating Large Sets
Never use SMEMBERS on sets with millions of members in production:
SSCAN followers:user:1001 0 COUNT 100
SSCAN followers:user:1001 1287 COUNT 100 # use returned cursor
Sorted Set Rankings
Sorted sets (ZSET) combine unique members with a numeric score for ordering.
ZADD game:scores 9500 "player_a" 12000 "player_b" 8700 "player_c"
ZREVRANGE game:scores 0 2 WITHSCORES # top 3 by score
ZRANGE game:scores 0 -1 WITHSCORES # ascending
ZRANK game:scores "player_a" # ascending rank (0-based)
ZREVRANK game:scores "player_a" # descending rank
ZSCORE game:scores "player_b"
# Range by score
ZRANGEBYSCORE game:scores 9000 11000 WITHSCORES
ZCOUNT game:scores 8000 10000
ZREMRANGEBYSCORE game:scores 0 5000 # remove low scores
Leaderboard Patterns
# Increment score atomically
ZINCRBY game:scores 500 "player_a"
# Keep only top 100 players
ZREMRANGEBYRANK game:scores 0 -101
# Leaderboard with timestamps as tiebreaker
ZADD events:timeline 1718200000 "event-uuid-1"
ZADD events:timeline 1718203600 "event-uuid-2"
Time-Series with ZSET
Use Unix timestamps as scores for time-ordered data:
ZADD sensor:temp:room-a 1718200000 "22.5"
ZADD sensor:temp:room-a 1718200060 "22.7"
ZRANGEBYSCORE sensor:temp:room-a 1718199000 1718201000 WITHSCORES
Trim old entries periodically with ZREMRANGEBYSCORE.
Streams — Producing Events
Streams are append-only logs with unique IDs and field-value pairs.
XADD sensor:readings * temp 22.5 humidity 65 location "room-a"
XADD sensor:readings * temp 23.1 humidity 62 location "room-a"
XLEN sensor:readings
XRANGE sensor:readings - + COUNT 10
XREVRANGE sensor:readings + - COUNT 5
The * auto-generates IDs: {milliseconds}-{sequence}.
Streams — Consumer Groups
Consumer groups distribute messages across workers with at-least-once delivery.
# Create group (once)
XGROUP CREATE sensor:readings processors $ MKSTREAM
# Worker reads new messages
XREADGROUP GROUP processors consumer-1 COUNT 10 STREAMS sensor:readings >
# Acknowledge processing
XACK sensor:readings processors 1718200000000-0
# Inspect pending (unacknowledged) messages
XPENDING sensor:readings processors
XCLAIM sensor:readings processors consumer-2 60000 1718200000000-0
Pending Message Recovery
Messages remain in the Pending Entry List (PEL) until acknowledged. Use XPENDING and XCLAIM to reassign stale messages from crashed consumers.
# Trim stream to approximate max length
XTRIM sensor:readings MAXLEN ~ 100000
HyperLogLog (Cardinality Estimation)
Estimate unique counts with ~0.81% error using only 12 KB per key.
PFADD unique:visitors:2024-06-01 user:1001 user:1002 user:1001
PFCOUNT unique:visitors:2024-06-01
# (integer) 2
PFMERGE unique:visitors:week visitors:mon visitors:tue visitors:wed
PFCOUNT unique:visitors:week
Use when: UV counts, unique IP tracking — not when exact counts are required.
Bitmap Operations
Treat strings as bit arrays — extremely memory-efficient for boolean flags across millions of IDs.
SETBIT user:logins:2024-06 15 1 # day 15 login (offset = day number)
GETBIT user:logins:2024-06 15
BITCOUNT user:logins:2024-06
# Daily active users across user IDs
SETBIT dau:2024-06-13 1001 1
SETBIT dau:2024-06-13 1002 1
BITCOUNT dau:2024-06-13
Bitwise Operations
BITOP AND result dau:2024-06-12 dau:2024-06-13 # users active both days
BITOP OR weekly dau:mon dau:tue dau:wed
Best Practices
- Use SSCAN/ZSCAN instead of full member dumps on large collections
- Trim leaderboards with
ZREMRANGEBYRANKto cap memory - Always XACK stream messages after successful processing
- Monitor PEL size — growing pending lists indicate slow or crashed consumers
- Use HyperLogLog for approximate counts; use sets when exact membership matters
Common Mistakes
| Mistake | Impact |
|---|---|
SMEMBERS on million-member sets |
Blocks server, latency spike |
| Duplicate stream consumers without unique names | Message delivery confusion |
| Forgetting XACK | Messages stuck in PEL, reprocessed forever |
| ZSET member string collisions | Same member, one score — usually intended |
| HyperLogLog for billing counts | Approximate — wrong for financial data |
Troubleshooting
Stream consumer lag:
XINFO GROUPS sensor:readings
XPENDING sensor:readings processors - + 10
# High pending count → scale consumers or fix slow handlers
Leaderboard rank wrong after tie scores:
# Redis orders by score, then lexicographically by member name
ZADD lb 100 "alice" 100 "bob"
ZRANGE lb 0 -1 WITHSCORES
Set intersection timeout:
# SINTER on huge sets is O(N×M) — consider alternative data models
SINTERSTORE temp:mutual k1 k2
SCARD temp:mutual
EXPIRE temp:mutual 60
Performance Tips
ZREVRANGEtop-N is O(log N + M) — efficient for dashboards- Batch
ZADDvia pipelining during bulk score imports - Use
XREAD BLOCK 5000for efficient stream polling without tight loops - Pre-aggregate with
SINTERSTORE/SUNIONSTOREfor repeated queries, with TTL on result keys
Production Scenario
A gaming platform maintained global and regional leaderboards using sorted sets. ZREVRANGE served top-100 queries in under 1ms. Stream consumer groups processed 50K match events/minute across 8 workers. HyperLogLog tracked daily unique players (12M DAU) using 144 KB total instead of gigabytes for a full set. Pending message alerts fired when PEL exceeded 10K, catching a deployment bug that stopped acknowledgments.
Sets, sorted sets, and streams unlock social, ranking, and event-driven architectures — the building blocks for real-time applications at scale.