agentleFS
AlteriomAlteriom/ai-dev-skills/skills/redis-store/SKILL.md

redis-store

Skill0 starsChanged 3 months agoMITView on GitHub

Master Redis for caching, session storage, rate limiting, pub/sub, and distributed data structures with proper expiration and persistence

  • Installs packages

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 4 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when:

    line 26
  2. Distinctive:

    Don't use: when you need:

    line 37
  3. Very specific:

    Karpathy Principle: Simplicity First: - Start with basic SET/GET.

    line 86
  4. Very specific:

    Karpathy Principle: Goal-Driven Execution: - After storing data, verify with redis-cli: GET key, HGETALL user:1, ZRANGE leaderboard 0 -1 …

    line 221
  5. Distinctive:

    Before deploying Redis in production:

    line 500
  6. Distinctive:

    Karpathy Principle: Simplicity First: - Start with simple key-value caching.

    line 585

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Redis - In-Memory Data Store

When to Use

Use this skill when:

  • Caching database queries or API responses
  • Session storage for web applications
  • Rate limiting and throttling
  • Real-time analytics and counters
  • Pub/sub messaging between services
  • Job queues (with Redis Streams)
  • Leaderboards and rankings (sorted sets)
  • Distributed locks and coordination

Don't use when you need:

  • Complex queries with JOINs (use SQL database)
  • Long-term persistent storage (Redis is primarily in-memory)
  • ACID transactions across multiple keys (limited support)
  • Multi-gigabyte datasets on small memory machines

Karpathy Principle: Think Before Coding - Redis is RAM-based. Every key costs memory. Plan your data structures, expiration policies, and eviction strategies before storing data. A memory leak in Redis crashes your application.

Prerequisites

Required Knowledge

  • Key-value store concepts
  • Data structure basics (lists, sets, hashes, sorted sets)
  • TTL and expiration strategies
  • Pub/sub patterns

Required Tools

# Install Redis (Ubuntu/Debian)
sudo apt update
sudo apt install redis-server

# Install Redis (macOS)
brew install redis

# Start Redis
sudo systemctl start redis
# or
redis-server

# Test connection
redis-cli ping  # Should return PONG

# Monitor commands in real-time
redis-cli monitor

Client Libraries

# Node.js
npm install redis ioredis

# Python
pip install redis

# Go
go get github.com/go-redis/redis/v8

Karpathy Principle: Simplicity First - Start with basic SET/GET. Add complexity (sorted sets, streams, Lua scripts) only when needed. Most caching needs are solved with strings and hashes.

Core Workflows

1. Basic Operations (Strings)

Node.js (ioredis):

import Redis from 'ioredis'

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  maxRetriesPerRequest: 3,
})

// Set with expiration
await redis.setex('session:123', 3600, JSON.stringify({ user: 'alice' }))

// Get
const session = await redis.get('session:123')
const data = JSON.parse(session)

// Set with options (Redis 6.2+)
await redis.set('key', 'value', 'EX', 3600, 'NX')  // Only if not exists

// Atomic increment
await redis.incr('page:views')
await redis.incrby('user:score', 10)

// Delete
await redis.del('session:123')

// Check existence
const exists = await redis.exists('key')  // Returns 1 if exists, 0 if not

Python:

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Set with expiration
r.setex('session:123', 3600, json.dumps({'user': 'alice'}))

# Get
session = r.get('session:123')
data = json.loads(session) if session else None

# Increment
r.incr('page:views')

# Delete
r.delete('session:123')

2. Data Structures

Hashes (for objects):

// Set multiple fields
await redis.hset('user:1', {
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
})

// Get single field
const name = await redis.hget('user:1', 'name')

// Get all fields
const user = await redis.hgetall('user:1')
// { name: 'Alice', email: 'alice@example.com', age: '30' }

// Increment field
await redis.hincrby('user:1', 'age', 1)

// Delete field
await redis.hdel('user:1', 'email')

Lists (queues, recent items):

// Push to queue
await redis.lpush('queue:jobs', JSON.stringify({ id: 1, task: 'send email' }))

// Pop from queue (blocking)
const job = await redis.brpop('queue:jobs', 5)  // Wait 5 seconds max
if (job) {
  const [key, value] = job
  const data = JSON.parse(value)
}

// Recent items
await redis.lpush('recent:posts', 'post:123')
await redis.ltrim('recent:posts', 0, 99)  // Keep only 100 most recent

Sets (unique items):

// Add to set
await redis.sadd('tags:typescript', 'post:1', 'post:2', 'post:3')

// Check membership
const isMember = await redis.sismember('tags:typescript', 'post:1')

// Get all members
const posts = await redis.smembers('tags:typescript')

// Set operations
await redis.sinter('tags:typescript', 'tags:nodejs')  // Intersection
await redis.sunion('tags:typescript', 'tags:python')  // Union

Sorted Sets (leaderboards, rankings):

// Add with score
await redis.zadd('leaderboard', 100, 'alice', 85, 'bob', 92, 'charlie')

// Get top N
const top3 = await redis.zrevrange('leaderboard', 0, 2, 'WITHSCORES')
// ['alice', '100', 'charlie', '92', 'bob', '85']

// Get rank (0-based)
const rank = await redis.zrevrank('leaderboard', 'alice')  // 0 (first place)

// Increment score
await redis.zincrby('leaderboard', 5, 'bob')  // bob: 85 → 90

// Get score
const score = await redis.zscore('leaderboard', 'alice')  // '100'

Karpathy Principle: Goal-Driven Execution - After storing data, verify with redis-cli: GET key, HGETALL user:1, ZRANGE leaderboard 0 -1 WITHSCORES. Don't trust your code until you see the data.

3. Caching Patterns

Cache-Aside:

async function getUser(userId) {
  const cacheKey = `user:${userId}`
  
  // Try cache first
  const cached = await redis.get(cacheKey)
  if (cached) return JSON.parse(cached)
  
  // Cache miss - fetch from DB
  const user = await db.user.findUnique({ where: { id: userId } })
  
  // Store in cache (1 hour)
  await redis.setex(cacheKey, 3600, JSON.stringify(user))
  
  return user
}

Write-Through:

async function updateUser(userId, data) {
  // Update DB
  const user = await db.user.update({
    where: { id: userId },
    data,
  })
  
  // Update cache
  await redis.setex(`user:${userId}`, 3600, JSON.stringify(user))
  
  return user
}

Cache Invalidation:

async function deleteUser(userId) {
  // Delete from DB
  await db.user.delete({ where: { id: userId } })
  
  // Delete from cache
  await redis.del(`user:${userId}`)
}

4. Rate Limiting

Fixed Window:

async function checkRateLimit(userId, limit = 100) {
  const key = `rate:${userId}:${Math.floor(Date.now() / 60000)}`  // Per minute
  
  const current = await redis.incr(key)
  
  if (current === 1) {
    await redis.expire(key, 60)  // Set TTL on first request
  }
  
  return current <= limit
}

Sliding Window (more accurate):

async function checkRateLimitSliding(userId, limit = 100, windowSeconds = 60) {
  const key = `rate:${userId}`
  const now = Date.now()
  const windowStart = now - windowSeconds * 1000
  
  // Remove old entries
  await redis.zremrangebyscore(key, 0, windowStart)
  
  // Count requests in window
  const count = await redis.zcard(key)
  
  if (count < limit) {
    await redis.zadd(key, now, `${now}:${Math.random()}`)
    await redis.expire(key, windowSeconds)
    return true
  }
  
  return false
}

Common Patterns

1. Session Storage

// Create session
const sessionId = crypto.randomUUID()
await redis.setex(`session:${sessionId}`, 86400, JSON.stringify({
  userId: '123',
  role: 'admin',
  createdAt: Date.now(),
}))

// Get session
const session = await redis.get(`session:${sessionId}`)

// Extend session
await redis.expire(`session:${sessionId}`, 86400)

// Delete session (logout)
await redis.del(`session:${sessionId}`)

2. Distributed Lock

async function acquireLock(resource, token, ttl = 30) {
  const key = `lock:${resource}`
  const result = await redis.set(key, token, 'NX', 'EX', ttl)
  return result === 'OK'
}

async function releaseLock(resource, token) {
  const script = `
    if redis.call("get", KEYS[1]) == ARGV[1] then
      return redis.call("del", KEYS[1])
    else
      return 0
    end
  `
  return await redis.eval(script, 1, `lock:${resource}`, token)
}

// Usage
const token = crypto.randomUUID()
if (await acquireLock('payment:123', token)) {
  try {
    // Do work
  } finally {
    await releaseLock('payment:123', token)
  }
}

3. Pub/Sub Messaging

// Publisher
await redis.publish('notifications', JSON.stringify({
  type: 'new_message',
  userId: '123',
}))

// Subscriber
const subscriber = new Redis()
await subscriber.subscribe('notifications')

subscriber.on('message', (channel, message) => {
  const data = JSON.parse(message)
  console.log(`Received on ${channel}:`, data)
})

4. Job Queue (with Streams)

// Producer
await redis.xadd('queue:emails', '*', 'to', 'alice@example.com', 'subject', 'Hello')

// Consumer
while (true) {
  const messages = await redis.xreadgroup(
    'GROUP', 'workers', 'worker-1',
    'BLOCK', 5000,
    'STREAMS', 'queue:emails', '>'
  )
  
  if (messages) {
    for (const [stream, entries] of messages) {
      for (const [id, fields] of entries) {
        // Process job
        const job = Object.fromEntries(
          fields.reduce((acc, val, i, arr) => {
            if (i % 2 === 0) acc.push([val, arr[i + 1]])
            return acc
          }, [])
        )
        
        // Acknowledge
        await redis.xack('queue:emails', 'workers', id)
      }
    }
  }
}

Common Pitfalls

1. No TTL (Memory Leak)

❌ Bad (no expiration):

await redis.set('cache:data', data)  // Lives forever!

✅ Good (with TTL):

await redis.setex('cache:data', 3600, data)  // Expires in 1 hour

2. Large Keys

❌ Bad (1MB+ values):

await redis.set('user:sessions', JSON.stringify(allSessions))  // Huge value

✅ Good (split into small keys):

for (const session of sessions) {
  await redis.setex(`session:${session.id}`, 3600, JSON.stringify(session))
}

3. Blocking Operations

❌ Bad (blocks Redis):

const keys = await redis.keys('user:*')  // Scans entire keyspace!

✅ Good (non-blocking):

let cursor = '0'
const keys = []
do {
  const [nextCursor, batch] = await redis.scan(cursor, 'MATCH', 'user:*', 'COUNT', 100)
  keys.push(...batch)
  cursor = nextCursor
} while (cursor !== '0')

4. No maxmemory Limit

❌ Bad (no memory limit):

# redis.conf
# (no maxmemory set)

✅ Good (with eviction policy):

# redis.conf
maxmemory 1gb
maxmemory-policy allkeys-lru  # Evict least recently used

5. Forgetting Connection Pooling

❌ Bad (new connection per request):

app.get('/api/user', async (req, res) => {
  const redis = new Redis()  // New connection!
  const user = await redis.get('user:1')
  await redis.quit()
})

✅ Good (reuse connection):

const redis = new Redis()  // Outside handler

app.get('/api/user', async (req, res) => {
  const user = await redis.get('user:1')
})

Verification Checklist

Before deploying Redis in production:

Configuration

  • maxmemory set (e.g., 1gb, 2gb)
  • maxmemory-policy configured (allkeys-lru for cache)
  • Persistence enabled (RDB or AOF or both)
  • bind address restricted (not 0.0.0.0 for public)
  • Password set (requirepass)

Operations

  • All cache keys have TTL
  • No blocking commands in production (KEYS, FLUSHALL)
  • Connection pooling enabled
  • Monitoring set up (memory, CPU, evictions)

Performance

  • Pipeline batch operations
  • Use hashes for small objects (memory efficient)
  • Avoid storing large values (>1MB)
  • Index patterns for SCAN operations

Integration with Other Skills

With Node.js / Express

  • Session middleware with redis-store
  • Rate limiting middleware
  • Cache API responses

With Next.js

  • Cache server-side rendered pages
  • Session storage
  • Real-time updates via pub/sub

With Docker

  • Redis container in docker-compose
  • Volume for persistence
  • Health checks

With Kubernetes

  • Redis Sentinel for HA
  • Redis Cluster for sharding
  • Persistent volume claims

References

Official Docs

Guides

Tools

Meta: Skill Quality

Completeness: ✅ Comprehensive (9/9 sections)
Karpathy Principles: ✅ 5 mentions
Code Examples: ✅ 25+ working examples
Production Tested: ✅ Used in NorthRelay, MemoryRelay
Last Updated: 2026-04-13

Coverage:

  • ✅ Basic operations (strings, hashes, lists, sets, sorted sets)
  • ✅ Caching patterns (cache-aside, write-through)
  • ✅ Rate limiting (fixed window, sliding window)
  • ✅ Session storage
  • ✅ Distributed locks
  • ✅ Pub/sub messaging
  • ✅ Job queues (Streams)
  • ✅ Memory management
  • ✅ Common pitfalls

Skill Level: Intermediate
Time to Learn: 3-4 hours
Prerequisites Met: Key-value concepts, basic programming

Known Gaps: None - production-ready

Karpathy Principle: Cache Invalidation is Hard - The two hardest problems in CS: naming things, cache invalidation, off-by-one errors. Always have explicit invalidation strategies.

Karpathy Principle: Simplicity First - Start with simple key-value caching. Add complex patterns (pub/sub, streams) only when simple patterns fail.


Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

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.