Master Redis Hashes and Sorted Sets - object storage, field operations, leaderboards, rankings, and scoring systems
Manages Redis Hashes for object storage and field operations, and Sorted Sets for ranked collections like leaderboards and time-based feeds. Use when storing/retrieving structured objects, maintaining score-based rankings, or implementing rate limiting with sliding windows.
/plugin marketplace add pluginagentmarketplace/custom-plugin-redis/plugin install pluginagentmarketplace-developer-roadmap-interactive@pluginagentmarketplace/custom-plugin-redisThis skill inherits all available tools. When active, it can use any tool Claude has access to.
assets/config.yamlassets/leaderboard.luareferences/GUIDE.mdreferences/HASH_ZSET_PATTERNS.mdscripts/hash-migration.pyscripts/helper.pyRedis Hashes are maps between string fields and string values - perfect for object storage.
# Basic operations
HSET key field value [field value ...] # O(N)
HGET key field # O(1)
HGETALL key # O(N)
HDEL key field [field ...] # O(N)
HEXISTS key field # O(1)
# Bulk operations
HMSET key field value [field value ...] # Deprecated, use HSET
HMGET key field [field ...] # O(N)
# Numeric operations
HINCRBY key field increment # O(1)
HINCRBYFLOAT key field increment # O(1)
# Metadata
HLEN key # O(1)
HKEYS key # O(N)
HVALS key # O(N)
HSCAN key cursor [MATCH pattern] [COUNT count]
# Advanced (Redis 7.4+)
HSETEX key seconds field value # Set with TTL
HGETEX key field EX seconds # Get with TTL refresh
Sorted Sets combine Sets with scores, enabling ranked collections.
# Add with scores
ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
# Range queries by index
ZRANGE key start stop [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES] # Deprecated, use ZRANGE REV
# Range by score
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] # Deprecated
ZRANGE key min max BYSCORE [WITHSCORES] [LIMIT offset count]
# Ranking
ZRANK key member # 0-based rank (lowest first)
ZREVRANK key member # 0-based rank (highest first)
ZSCORE key member # Get score
# Modifications
ZINCRBY key increment member # Increment score - O(log N)
ZREM key member [member ...] # Remove - O(M*log N)
# Cardinality
ZCARD key # Total count - O(1)
ZCOUNT key min max # Count in range - O(log N)
# Set operations
ZINTER numkeys key [key ...] # Intersection
ZUNION numkeys key [key ...] # Union
ZDIFF numkeys key [key ...] # Difference
# Store user as hash
HSET user:123 name "John" email "john@example.com" age "30" status "active"
# Get specific fields (efficient)
HMGET user:123 name email
# Update single field
HSET user:123 status "premium"
# Atomic counter in hash
HINCRBY user:123 login_count 1
HINCRBY user:123 points 100
# Check field exists
HEXISTS user:123 email
# Add/Update scores
ZADD leaderboard 1000 "player:1"
ZADD leaderboard 1500 "player:2"
ZADD leaderboard 800 "player:3"
# Increment score (game points)
ZINCRBY leaderboard 50 "player:1"
# Get top 10 (highest scores)
ZRANGE leaderboard 0 9 REV WITHSCORES
# Get player rank (0-indexed)
ZREVRANK leaderboard "player:1"
# Get player score
ZSCORE leaderboard "player:1"
# Get players around a rank
ZRANGE leaderboard 5 15 REV WITHSCORES
# Count players above threshold
ZCOUNT leaderboard 1000 +inf
# Add items with timestamp as score
ZADD feed:user:123 1704067200 "post:456"
ZADD feed:user:123 1704153600 "post:789"
# Get recent items
ZRANGE feed:user:123 0 19 REV
# Get items from time range
ZRANGE feed:user:123 1704000000 1704200000 BYSCORE
# Remove old items
ZREMRANGEBYSCORE feed:user:123 -inf 1703980800
# Sliding window rate limiter
-- Add request with timestamp
ZADD ratelimit:user:123 1704067200.123 "req:uuid1"
-- Remove old entries (outside window)
ZREMRANGEBYSCORE ratelimit:user:123 -inf 1704067140.123
-- Count requests in window
ZCARD ratelimit:user:123
-- Check if under limit
-- If ZCARD < 100, allow request
# Store product
HSET product:sku:ABC123 \
name "Redis T-Shirt" \
price "29.99" \
stock "100" \
category "apparel"
# Decrement stock atomically
HINCRBY product:sku:ABC123 stock -1
# Get product info
HGETALL product:sku:ABC123
# Update price
HSET product:sku:ABC123 price "24.99"
# Store session data
HSET session:abc123 \
user_id "123" \
created "1704067200" \
ip "192.168.1.1" \
user_agent "Mozilla/5.0"
# Set session TTL
EXPIRE session:abc123 3600
# Update last access
HSET session:abc123 last_access "1704070800"
# Get specific session data
HMGET session:abc123 user_id created
| Command | Complexity | Notes |
|---|---|---|
| HSET/HGET | O(1) | Single field |
| HMSET/HMGET | O(N) | N = fields |
| HGETALL | O(N) | Returns all |
| HINCRBY | O(1) | Atomic |
| ZADD | O(log N) | Per member |
| ZRANGE | O(log N + M) | M = returned |
| ZRANK | O(log N) | Binary search |
| ZINCRBY | O(log N) | Update + reorder |
| ZCARD | O(1) | Stored metadata |
# redis.conf - Use ziplist for small hashes
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
leaderboard.lua - Atomic leaderboard operationshash-ttl.lua - Per-field TTL simulationhash-migration.py - Hash data migration scriptHASH_ZSET_PATTERNS.md - Pattern guide# Can block for seconds
HGETALL huge:hash # 100K+ fields
Fix:
# Use HSCAN instead
HSCAN huge:hash 0 COUNT 1000
WRONGTYPE Operation against a key holding the wrong kind of value
Diagnosis:
TYPE mykey
Prevention: Consistent key naming (hash:*, zset:*)
ZADD scores 1.7976931348623157e+308 "item" # Max float
ZADD scores 0.1 "a"
ZADD scores 0.2 "b"
# 0.1 + 0.1 may not equal 0.2 exactly
Fix: Use integer scores when possible (multiply by 100 for cents)
Problem: Same score = arbitrary order
Fix: Use composite score
# score = points * 1000000 + (MAX_TIME - timestamp)
ZADD leaderboard 1000000000 "player:1" # 1000 pts at time 0
ZADD leaderboard 1000999999 "player:2" # 1000 pts later (lower rank)
Cause: Large field values or too many fields
Diagnosis:
DEBUG OBJECT user:123
MEMORY USAGE user:123
Fix:
□ Key exists? (EXISTS key)
□ Correct type? (TYPE key)
□ Hash field exists? (HEXISTS key field)
□ Score within valid range?
□ Encoding efficient? (DEBUG OBJECT key)
□ Memory usage acceptable? (MEMORY USAGE key)
| Scenario | Issue | Solution |
|---|---|---|
| HGETALL large hash | Blocking | Use HSCAN |
| ZRANGE 0 -1 large set | High memory | Paginate |
| Many ZINCRBY | Hot key | Shard by key |
| String values in hash | Memory waste | Consider MessagePack |
| Code | Name | Description | Recovery |
|---|---|---|---|
| HZ001 | WRONGTYPE | Type mismatch | Check TYPE |
| HZ002 | FIELD_NOT_FOUND | Hash field missing | Check HEXISTS |
| HZ003 | NAN_SCORE | Invalid score value | Validate number |
| HZ004 | MEMBER_NOT_FOUND | ZSet member missing | Check membership |
| HZ005 | OVERFLOW | Integer overflow | Use float or reset |
# test_redis_hashes_zsets.py
import redis
import pytest
@pytest.fixture
def r():
return redis.Redis(decode_responses=True)
class TestHashes:
def test_user_profile(self, r):
r.delete("test:user:1")
r.hset("test:user:1", mapping={
"name": "John",
"email": "john@example.com",
"age": "30"
})
assert r.hget("test:user:1", "name") == "John"
assert r.hlen("test:user:1") == 3
r.delete("test:user:1")
def test_hincrby(self, r):
r.delete("test:counter")
r.hset("test:counter", "visits", "10")
r.hincrby("test:counter", "visits", 5)
assert r.hget("test:counter", "visits") == "15"
r.delete("test:counter")
def test_hmget(self, r):
r.delete("test:hash")
r.hset("test:hash", mapping={"a": "1", "b": "2", "c": "3"})
result = r.hmget("test:hash", "a", "c")
assert result == ["1", "3"]
r.delete("test:hash")
class TestSortedSets:
def test_leaderboard(self, r):
r.delete("test:leaderboard")
r.zadd("test:leaderboard", {"player:1": 100, "player:2": 200})
# Top player
top = r.zrange("test:leaderboard", 0, 0, desc=True)
assert top == ["player:2"]
# Rank
rank = r.zrevrank("test:leaderboard", "player:1")
assert rank == 1 # 0-indexed, second place
r.delete("test:leaderboard")
def test_zincrby(self, r):
r.delete("test:scores")
r.zadd("test:scores", {"player:1": 100})
r.zincrby("test:scores", 50, "player:1")
assert r.zscore("test:scores", "player:1") == 150
r.delete("test:scores")
def test_zrangebyscore(self, r):
r.delete("test:zset")
r.zadd("test:zset", {"a": 1, "b": 2, "c": 3, "d": 4})
result = r.zrangebyscore("test:zset", 2, 3)
assert result == ["b", "c"]
r.delete("test:zset")
def test_zcount(self, r):
r.delete("test:zset")
r.zadd("test:zset", {"a": 1, "b": 2, "c": 3})
assert r.zcount("test:zset", 2, "+inf") == 2
r.delete("test:zset")
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.