redis
Skill190 starsChanged 6 months agoMITView on GitHub
Use when choosing a Redis data structure for a use case, implementing caching or rate limiting, building pub/sub or Streams-based real-time messaging, or writing atomic operations like distributed locks.
We split this skill into the rules it gives an agent
2 rules. 0 are common, 1 is distinctive, and 1 is very specific to this project.
0
Common
Most of the 9 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 120
Use hashes for objects with many fields where you update individual fields often.
- Very specific: line 385
Never use KEYS * in production.
“Common” is measured: kept by at least 6 in 10 of the 9 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Redis Patterns
Redis data structures, caching, pub/sub, and streams for Python async apps.
When to Activate
- Choosing the right Redis data structure for a use case
- Implementing caching (cache-aside, write-through, TTL eviction)
- Using pub/sub or Redis Streams for real-time messaging / SSE
- Building a job queue with SKIP LOCKED semantics
- Writing atomic operations (counters, rate limits, distributed locks)
- Debugging slow Redis commands or memory bloat
Connection (async redis-py)
import redis.asyncio as redis
# Single connection
client = await redis.from_url("redis://localhost:6379", decode_responses=True)
# Connection pool (recommended for apps)
pool = redis.ConnectionPool.from_url(
"redis://localhost:6379",
decode_responses=True,
max_connections=20,
)
client = redis.Redis(connection_pool=pool)
# Close on shutdown
await client.aclose()decode_responses=True returns str instead of bytes — use it unless you store binary data.
Data Structure Decision Table
| Structure | Use For | Avoid When |
| String | Single values, JSON blobs, counters, distributed locks | Frequently updating one field of many |
| Hash | Objects with multiple fields; partial field reads/writes | >100 fields or deeply nested — use String+JSON instead |
| List | FIFO queues, activity feeds, bounded history | Random access by index — use Sorted Set |
| Set | Unique membership, tag intersections/unions, "online users" | Need ordering or score — use Sorted Set |
| Sorted Set | Leaderboards, priority queues, time-ordered events, rate limiting | Cardinality >10M — memory gets expensive |
| Stream | Durable pub/sub, consumer groups, event log | Simple fire-and-forget — use pub/sub |
| HyperLogLog | Approx unique count (±0.81% error, capped at 12 KB) | Exact count required |
| Bitmap | Per-user boolean flags, daily active user tracking | More than 512 MB of bits |
Data Structures
Strings — single values, counters, JSON blobs
# Set / get
await client.set("user:123:name", "Alice")
await client.get("user:123:name") # "Alice"
# With TTL (seconds)
await client.set("session:abc", token, ex=3600) # expires in 1 hour
await client.setex("session:abc", 3600, token) # same
# Only set if not exists (NX) — distributed lock primitive
acquired = await client.set("lock:job:42", "worker-1", nx=True, ex=30)
# Atomic counter
await client.incr("page:views")
await client.incrby("page:views", 5)
await client.decr("inventory:product:99")
# Get + set atomically (Lua or GETEX)
await client.getex("session:abc", ex=3600) # reset TTL on read
# Store JSON
import json
await client.set("user:123", json.dumps(user_dict))
user = json.loads(await client.get("user:123"))Hashes — objects / partial updates
# Set multiple fields at once
await client.hset("user:123", mapping={
"name": "Alice",
"email": "alice@example.com",
"role": "admin",
})
# Get all fields
user = await client.hgetall("user:123") # {"name": "Alice", ...}
# Get one field
name = await client.hget("user:123", "name")
# Update one field without overwriting others
await client.hset("user:123", "role", "user")
# Check existence
exists = await client.hexists("user:123", "email")
# Delete a field
await client.hdel("user:123", "temp_token")
# Get field names / values
fields = await client.hkeys("user:123")
values = await client.hvals("user:123")Use hashes for objects with many fields where you update individual fields often. Cheaper than JSON string for partial reads.
Lists — queues, activity feeds
# Push to right (tail) — enqueue
await client.rpush("queue:emails", "msg-1", "msg-2")
# Pop from left (head) — dequeue FIFO
job = await client.lpop("queue:emails")
# Blocking pop — wait up to 30s for an item
job = await client.blpop("queue:emails", timeout=30) # returns (key, value)
# Stack (LIFO): rpush + rpop
await client.rpush("stack", "item")
item = await client.rpop("stack")
# Peek without removing
items = await client.lrange("queue:emails", 0, -1) # all items
recent = await client.lrange("activity:user:1", 0, 9) # first 10
# Keep list bounded (trim to last 100)
await client.ltrim("activity:user:1", -100, -1)
# Length
length = await client.llen("queue:emails")Sets — unique membership, tags
await client.sadd("online_users", "user-1", "user-2")
await client.srem("online_users", "user-2")
is_online = await client.sismember("online_users", "user-1")
members = await client.smembers("online_users")
count = await client.scard("online_users")
# Set operations
common = await client.sinter("user:1:friends", "user:2:friends") # intersection
all_ = await client.sunion("tag:python", "tag:async") # union
diff = await client.sdiff("all_users", "banned_users") # differenceSorted Sets — leaderboards, priority queues, rate limiting
# Add with score (score determines order)
await client.zadd("leaderboard", {"alice": 1500, "bob": 1200, "carol": 1800})
# Get top 3 (highest score first)
top3 = await client.zrevrange("leaderboard", 0, 2, withscores=True)
# [("carol", 1800.0), ("alice", 1500.0), ("bob", 1200.0)]
# Rank (0-indexed, lowest score = rank 0)
rank = await client.zrevrank("leaderboard", "alice") # 1 (2nd place)
# Increment score atomically
await client.zincrby("leaderboard", 50, "bob")
# Range by score — get items between two scores
members = await client.zrangebyscore("leaderboard", 1400, 2000)
# Remove
await client.zrem("leaderboard", "bob")TTL and Expiration
# Set TTL on existing key
await client.expire("session:abc", 3600) # seconds
await client.expireat("session:abc", timestamp) # unix timestamp
await client.pexpire("key", 500) # milliseconds
# Check remaining TTL
ttl = await client.ttl("session:abc") # seconds remaining, -1 if no TTL, -2 if missing
pttl = await client.pttl("session:abc") # milliseconds
# Remove TTL (make persistent)
await client.persist("key")Pub/Sub
# Publisher
async def publish_event(client, channel: str, data: dict):
await client.publish(channel, json.dumps(data))
# Subscriber — runs indefinitely
async def subscribe_to_events(client, channel: str):
async with client.pubsub() as pubsub:
await pubsub.subscribe(channel)
async for message in pubsub.listen():
if message["type"] == "message":
data = json.loads(message["data"])
yield data
# Pattern subscribe
async with client.pubsub() as pubsub:
await pubsub.psubscribe("tasks:*") # matches tasks:created, tasks:done, etc.
async for message in pubsub.listen():
if message["type"] == "pmessage":
process(message["channel"], message["data"])Limitation: pub/sub messages are fire-and-forget. Subscribers that miss a message while offline don't receive it. Use Streams for durable delivery.
Redis Streams (durable pub/sub)
Streams persist messages — consumers can read from any position, including past messages.
# Produce — append message to stream
msg_id = await client.xadd(
"task:updates",
{"task_id": "t-123", "status": "running", "content": "Processing..."},
maxlen=10000, # trim to 10k entries (approximate)
)
# Consume from beginning
messages = await client.xread({"task:updates": "0-0"}, count=100)
# messages: [("task:updates", [(id, {fields...}), ...])]
# Consume only new messages (since last read)
last_id = "0-0"
messages = await client.xread({"task:updates": last_id}, count=10, block=5000)
for stream, entries in messages:
for msg_id, fields in entries:
process(fields)
last_id = msg_id
# Consumer groups — multiple workers compete for messages
await client.xgroup_create("task:updates", "workers", id="0", mkstream=True)
# Worker reads and claims a message
msgs = await client.xreadgroup("workers", "worker-1", {"task:updates": ">"}, count=1)
for stream, entries in msgs:
for msg_id, fields in entries:
process(fields)
await client.xack("task:updates", "workers", msg_id) # mark done
# Trim old entries
await client.xtrim("task:updates", maxlen=5000, approximate=True)SSE streaming pattern (used in Agentex frontend):
# Backend: push deltas to a stream per task
await client.xadd(f"task:{task_id}:stream", {"delta": chunk})
# Frontend SSE endpoint: read stream and forward to browser
async def stream_task(task_id: str):
last_id = "0-0"
while True:
messages = await client.xread({f"task:{task_id}:stream": last_id}, block=5000)
for _, entries in messages:
for msg_id, fields in entries:
yield f"data: {fields['delta']}\n\n"
last_id = msg_idCaching Patterns
Cache-aside (lazy loading)
async def get_user(user_id: str) -> User:
key = f"user:{user_id}"
cached = await client.get(key)
if cached:
return User(**json.loads(cached))
user = await db.fetch_user(user_id)
await client.set(key, user.model_dump_json(), ex=300) # cache 5 min
return user
async def invalidate_user(user_id: str):
await client.delete(f"user:{user_id}")Write-through
async def update_user(user_id: str, data: dict) -> User:
user = await db.update_user(user_id, data)
await client.set(f"user:{user_id}", user.model_dump_json(), ex=300)
return userAtomic Operations
Distributed lock
import uuid
async def with_lock(client, resource: str, ttl: int = 30):
lock_key = f"lock:{resource}"
lock_val = str(uuid.uuid4())
acquired = await client.set(lock_key, lock_val, nx=True, ex=ttl)
if not acquired:
raise RuntimeError(f"Could not acquire lock on {resource}")
try:
yield
finally:
# Only release if we still own it (Lua script for atomicity)
script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
await client.eval(script, 1, lock_key, lock_val)Rate limiting (sliding window)
async def is_rate_limited(client, user_id: str, limit: int = 100, window: int = 60) -> bool:
key = f"rate:{user_id}:{int(time.time()) // window}"
count = await client.incr(key)
if count == 1:
await client.expire(key, window)
return count > limitPipeline (batch commands — reduce round trips)
async with client.pipeline(transaction=False) as pipe:
pipe.hset("user:1", mapping=data)
pipe.expire("user:1", 3600)
pipe.zadd("leaderboard", {"user-1": score})
results = await pipe.execute() # sent as one network round trip
# Atomic pipeline (MULTI/EXEC)
async with client.pipeline(transaction=True) as pipe:
await pipe.watch("inventory:42")
quantity = int(await pipe.get("inventory:42"))
if quantity < 1:
raise Exception("Out of stock")
pipe.multi()
pipe.decr("inventory:42")
await pipe.execute()SCAN — Non-Blocking Key Iteration
Never use KEYS * in production. Use SCAN with a cursor instead:
# Python — iterate all keys matching a pattern without blocking
async def scan_keys(client, pattern: str) -> list[str]:
keys = []
cursor = 0
while True:
cursor, batch = await client.scan(cursor, match=pattern, count=100)
keys.extend(batch)
if cursor == 0:
break
return keys
# Scan hash fields
cursor = 0
while True:
cursor, fields = await client.hscan("user:123", cursor, count=50)
for field, value in fields.items():
process(field, value)
if cursor == 0:
break
# Scan sorted set members by score range (non-blocking alternative to ZRANGEBYSCORE on huge sets)
cursor = 0
while True:
cursor, members = await client.zscan("leaderboard", cursor, count=100)
for member, score in members:
process(member, score)
if cursor == 0:
breakEviction Policies
Set maxmemory and maxmemory-policy in redis.conf or via CONFIG SET:
redis-cli CONFIG SET maxmemory 2gb
redis-cli CONFIG SET maxmemory-policy allkeys-lru| Policy | Evicts | Use When |
noeviction | Nothing — returns error on write | Data must never be lost (primary store) |
allkeys-lru | Least-recently-used key (any key) | General cache — you can't control which keys have TTL |
volatile-lru | LRU among keys with TTL | Mix of persistent + cache keys in one instance |
allkeys-lfu | Least-frequently-used key (any key) | Hotspot skew — some keys accessed far more |
volatile-ttl | Key with shortest remaining TTL | Prefer expiring the soonest-to-expire keys |
allkeys-random | Random key | Uniform access patterns, lowest overhead |
Production default for caches: allkeys-lru
Never use noeviction for a cache — the first write after memory is full raises an error.
# Check current eviction policy
info = await client.config_get("maxmemory-policy")
# {'maxmemory-policy': 'allkeys-lru'}
# Monitor eviction rate
stats = await client.info("stats")
evicted = stats["evicted_keys"] # total evictions since startTypeScript Patterns (node-redis)
import { createClient } from "redis";
const client = createClient({
url: "redis://localhost:6379",
socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 2000) },
});
await client.connect();
// String / JSON
await client.set("user:123", JSON.stringify(user), { EX: 300 });
const raw = await client.get("user:123");
const user = raw ? JSON.parse(raw) : null;
// Hash
await client.hSet("user:123", { name: "Alice", role: "admin" });
const data = await client.hGetAll("user:123"); // Record<string, string>
// Sorted set
await client.zAdd("leaderboard", [{ score: 1500, value: "alice" }]);
const top = await client.zRangeWithScores("leaderboard", 0, 9, { REV: true });
// Pipeline
const pipeline = client.multi();
pipeline.set("a", "1");
pipeline.expire("a", 60);
pipeline.incr("counter");
const [, , count] = await pipeline.exec();
// Distributed lock
const acquired = await client.set("lock:job:42", workerId, { NX: true, EX: 30 });
if (!acquired) throw new Error("Lock unavailable");
// Pub/sub (separate subscriber client)
const sub = client.duplicate();
await sub.connect();
await sub.subscribe("events", (message) => {
const data = JSON.parse(message);
handle(data);
});Sentinel & Cluster Connections
# Sentinel (high availability — automatic failover)
from redis.sentinel import Sentinel
sentinel = Sentinel(
[("sentinel-1", 26379), ("sentinel-2", 26379), ("sentinel-3", 26379)],
socket_timeout=0.5,
)
# master for writes, replica for reads
master = sentinel.master_for("mymaster", decode_responses=True)
replica = sentinel.slave_for("mymaster", decode_responses=True)
# Cluster (horizontal scaling)
from redis.asyncio.cluster import RedisCluster
cluster = RedisCluster.from_url("redis://node-1:7000", decode_responses=True)
await cluster.set("key", "value") # routes to correct shard automaticallyRed Flags
- No TTL on cache or session keys — keys without expiry accumulate forever and evict randomly under memory pressure; set
ex=on everyset()call for cached data and sessions - Using pub/sub for reliable delivery — pub/sub is fire-and-forget; subscribers that are offline when a message is published never receive it; use Redis Streams with consumer groups for any message that must not be lost
- Single connection instead of a pool — a single
await redis.from_url(...)connection serializes all commands and blocks under concurrent load; useConnectionPoolwithmax_connectionssized to your concurrency KEYS *in production —KEYSis O(n) and blocks the Redis event loop while it scans every key; useSCANwith a cursor to iterate non-blocking, or redesign to avoid key enumeration entirely- Distributed lock without a unique value — a lock released by any caller using only the key (not the unique lock value) can accidentally release another owner's lock; always store a UUID as the value and use a Lua script to compare-then-delete atomically
- Unbounded stream growth —
xaddwithoutmaxlenlets the stream grow indefinitely; always setmaxlen=N(withapproximate=Truefor efficiency) or run periodicxtrim - Sending multiple independent commands one at a time — each
await client.set(...)is a network round trip; batch three or more independent commands in a pipeline (async with client.pipeline()) to cut round-trip overhead significantly
Checklist
- Connection pool used (not single connection) for async apps
decode_responses=Trueset unless storing binary- TTL set on all cache/session keys
- Pub/sub replaced with Streams where offline delivery matters
xackcalled after processing stream messages (consumer groups)- Pipeline used when sending ≥ 3 independent commands in sequence
- Distributed locks use NX + expiry to prevent deadlocks
- Sorted sets used for leaderboards / time-ordered data instead of sorted lists
maxlenset on streams to prevent unbounded growth
Other versions10 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 10
- G1Joshi/Agent-Skills★ 15same name
- derogab/agent-kit★ 4same name
- fworks-tech/agenthood★ 3same name
- dversoza/claude-skills★ 2same name
- ffsshhttiikk/opencode-agents-skills★ 2same name
- Sorcha-Platform/Sorcha★ 2same name
- Alteriom/ai-dev-skills★ 0same name
- muxammadmamajonov/dot-claude★ 0same name
- sudarshanpjadhav/finggu-skills★ 0same name
- weiweimhy/agent-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Used alongsideProjects with it also use mysql, postgres and 1 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
planetscaleSkillmysql
Plan and review MySQL/InnoDB schema, indexing, query tuning, transactions, and operations. Use when creating or modifying MySQL tables, indexes, or queries; diagnosing slow/locking behavior; planning migrations; or troubleshooting replication and connection issues. Load when using a MySQL database.
Also in 3 projects that use this
digitalocean-labsSkillpostgres
Configure DigitalOcean Managed Postgres with bindable variables or schema isolation. Use when setting up databases, creating users, managing permissions, configuring multi-tenant schemas, or troubleshooting database connectivity on App Platform.
Also in 3 projects that use this
GoogleSkillcode-review
Adversarial systems, concurrency, memory-model, and architecture code review protocol. Use this skill whenever asked to review code, PRs, diffs, or branches in Filament.
Also in 3 projects that use this
In this repository65 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- accessibilitySkill
- agentexSkill
- ai-engineerSkill
- angularSkill
- api-designSkill
- authSkill
- awsSkill
- azure-service-busSkill
- azureSkill
- cachingSkill
- ci-cdSkill
- claude-apiSkill
- claude-codeSkill
- coding-standardsSkill
- complex-doc-ragSkill
- containerizationSkill
- database-designSkill
- data-pipelinesSkill
- deployment-strategiesSkill
- development-workflowSkill
- dockerSkill
- error-handlingSkill
- event-drivenSkill
- fastapiSkill
- feature-flagsSkill
- frontendSkill
- general-temporalSkill
- github-issuesSkill
- goSkill
- graphqlSkill
- incident-responseSkill
- infrastructure-as-codeSkill
- integration-testingSkill
- langgraphSkill
- memory-mapSkill
- microservicesSkill
- mongodbSkill
- nextjsSkill
- observabilitySkill
- openai-agentsSkill
- performanceSkill
- performance-testingSkill
- postgresqlSkill
- promptbaseSkill
- pydanticSkill
- pythonSkill
- reactSkill
- requirements-planningSkill
- securitySkill
- smoke-testingSkill
- solution-testingSkill
- spellbook-setupSkill
- sqlalchemySkill
- system-designSkill
- tailwindSkill
- technical-documentationSkill
- temporalSkill
- test-strategySkill
- typescriptSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

