Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
personamanagmentlayer avatar

Redis Expert

  • 256 installs
  • 41 repo stars
  • Updated March 30, 2026
  • personamanagmentlayer/pcl

redis-expert is a Claude Code skill at version 1.0.0 that implements Redis caching, pub/sub, session stores, rate limiting, and queue patterns as backend integration layers for developers building high-performance in-mem

About

redis-expert is a version 1.0.0 agent skill from personamanagmentlayer/pcl providing expert guidance on Redis as an in-memory data structure store serving cache, message broker, and database roles with microsecond latency. The skill covers eight core data structures—strings up to 512MB, lists, sets, sorted sets, hashes, streams, bitmaps, and HyperLogLog—plus pub/sub, session storage, rate limiting, and queue patterns for production backends. Allowed tools include Read, Write, Edit, and Bash scoped to redis-cli and docker commands for hands-on validation. Developers reach for redis-expert when designing eviction policies, choosing structures for leaderboard or presence features, or wiring pub/sub and rate-limit middleware—not when a simple key-value wrapper without operational semantics is sufficient.

  • Cache key design and TTL strategies
  • Pub/sub, streams, and session storage
  • Rate limiting and distributed lock patterns
  • Persistence, eviction, and cluster configuration

Redis Expert by the numbers

  • 256 all-time installs (skills.sh)
  • +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #196 of 911 Databases skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/personamanagmentlayer/pcl --skill redis-expert

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs256
repo stars41
Last updatedMarch 30, 2026
Repositorypersonamanagmentlayer/pcl

How do you implement Redis rate limiting in APIs?

Implement Redis caching, pub/sub, session stores, rate limiting, and queue patterns as backend integration layers.

Who is it for?

Backend engineers integrating Redis for caching, pub/sub, sessions, rate limiting, or queues who need structure and pattern guidance.

Skip if: Teams needing managed Redis cluster provisioning docs only or applications with no in-memory caching or messaging requirements.

When should I use this skill?

A backend task involves Redis caching, pub/sub, sessions, rate limiting, queues, or choosing among Redis data structures.

What you get

Redis cache layers, pub/sub channels, session stores, rate limiters, and queue configurations with chosen data structures.

  • Cache configurations
  • Pub/sub channel design
  • Rate limiter and queue implementations

By the numbers

  • Version 1.0.0 skill covering 8 Redis data structures
  • Documents Redis strings supporting values up to 512MB

Files

SKILL.mdMarkdownGitHub ↗

Redis Expert

Expert guidance for Redis - the in-memory data structure store used as cache, message broker, and database with microsecond latency.

Core Concepts

Data Structures

  • Strings (binary-safe, up to 512MB)
  • Lists (linked lists)
  • Sets (unordered unique strings)
  • Sorted Sets (sets ordered by score)
  • Hashes (field-value pairs)
  • Streams (append-only log)
  • Bitmaps and HyperLogLog
  • Geospatial indexes

Key Features

  • In-memory storage with persistence
  • Pub/Sub messaging
  • Transactions
  • Lua scripting
  • Pipelining
  • Master-Replica replication
  • Redis Sentinel (high availability)
  • Redis Cluster (horizontal scaling)

Use Cases

  • Caching layer
  • Session storage
  • Real-time analytics
  • Message queues
  • Rate limiting
  • Leaderboards
  • Geospatial queries

Installation and Configuration

Docker Setup

# Development
docker run --name redis -p 6379:6379 -d redis:7-alpine

# Production with persistence
docker run --name redis \
  -p 6379:6379 \
  -v redis-data:/data \
  -d redis:7-alpine \
  redis-server --appendonly yes --requirepass strongpassword

# Redis with config file
docker run --name redis \
  -p 6379:6379 \
  -v ./redis.conf:/usr/local/etc/redis/redis.conf \
  -d redis:7-alpine \
  redis-server /usr/local/etc/redis/redis.conf

Configuration (redis.conf)

# Network
bind 0.0.0.0
port 6379
protected-mode yes

# Security
requirepass strongpassword

# Memory
maxmemory 2gb
maxmemory-policy allkeys-lru

# Persistence
save 900 1      # Save after 900s if 1 key changed
save 300 10     # Save after 300s if 10 keys changed
save 60 10000   # Save after 60s if 10000 keys changed

appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

# Replication
replica-read-only yes
repl-diskless-sync yes

# Performance
tcp-backlog 511
timeout 0
tcp-keepalive 300

Node.js Client (ioredis)

Basic Operations

import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  password: 'strongpassword',
  db: 0,
  retryStrategy: (times) => {
    const delay = Math.min(times * 50, 2000);
    return delay;
  },
});

// Strings
await redis.set('user:1000:name', 'Alice');
await redis.set('counter', 42);
await redis.get('user:1000:name'); // 'Alice'

// Expiration (TTL)
await redis.setex('session:abc123', 3600, JSON.stringify({ userId: 1000 }));
await redis.expire('user:1000:name', 300); // 5 minutes
await redis.ttl('user:1000:name'); // Returns remaining seconds

// Atomic operations
await redis.incr('page:views'); // 1
await redis.incr('page:views'); // 2
await redis.incrby('score', 10); // Increment by 10
await redis.decr('inventory:item123');

// Hashes (objects)
await redis.hset('user:1000', {
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
});

await redis.hget('user:1000', 'name'); // 'Alice'
await redis.hgetall('user:1000'); // { name: 'Alice', email: '...', age: '30' }
await redis.hincrby('user:1000', 'loginCount', 1);

// Lists (queues, stacks)
await redis.lpush('queue:jobs', 'job1', 'job2', 'job3'); // Push to left
await redis.rpush('queue:jobs', 'job4'); // Push to right
await redis.lpop('queue:jobs'); // Pop from left (FIFO)
await redis.rpop('queue:jobs'); // Pop from right (LIFO)
await redis.lrange('queue:jobs', 0, -1); // Get all items

// Sets (unique values)
await redis.sadd('tags:post:1', 'javascript', 'nodejs', 'redis');
await redis.smembers('tags:post:1'); // ['javascript', 'nodejs', 'redis']
await redis.sismember('tags:post:1', 'nodejs'); // 1 (true)
await redis.scard('tags:post:1'); // 3 (count)

// Set operations
await redis.sadd('tags:post:2', 'nodejs', 'typescript', 'docker');
await redis.sinter('tags:post:1', 'tags:post:2'); // ['nodejs'] (intersection)
await redis.sunion('tags:post:1', 'tags:post:2'); // All unique tags
await redis.sdiff('tags:post:1', 'tags:post:2'); // ['javascript', 'redis']

// Sorted Sets (leaderboards)
await redis.zadd('leaderboard', 1000, 'player1', 1500, 'player2', 800, 'player3');
await redis.zrange('leaderboard', 0, -1, 'WITHSCORES'); // Ascending
await redis.zrevrange('leaderboard', 0, 9); // Top 10 (descending)
await redis.zincrby('leaderboard', 50, 'player1'); // Add to score
await redis.zrank('leaderboard', 'player1'); // Get rank (0-indexed)
await redis.zscore('leaderboard', 'player1'); // Get score

Advanced Patterns

Caching with JSON
// Cache helper
class CacheService {
  constructor(private redis: Redis) {}

  async get<T>(key: string): Promise<T | null> {
    const data = await this.redis.get(key);
    return data ? JSON.parse(data) : null;
  }

  async set(key: string, value: any, ttl: number = 3600): Promise<void> {
    await this.redis.setex(key, ttl, JSON.stringify(value));
  }

  async delete(key: string): Promise<void> {
    await this.redis.del(key);
  }

  async getOrSet<T>(
    key: string,
    factory: () => Promise<T>,
    ttl: number = 3600
  ): Promise<T> {
    const cached = await this.get<T>(key);
    if (cached) return cached;

    const fresh = await factory();
    await this.set(key, fresh, ttl);
    return fresh;
  }
}

// Usage
const cache = new CacheService(redis);

const user = await cache.getOrSet(
  'user:1000',
  async () => await db.user.findById(1000),
  3600
);
Rate Limiting
class RateLimiter {
  constructor(private redis: Redis) {}

  async checkRateLimit(
    key: string,
    limit: number,
    window: number
  ): Promise<{ allowed: boolean; remaining: number }> {
    const current = await this.redis.incr(key);

    if (current === 1) {
      await this.redis.expire(key, window);
    }

    return {
      allowed: current <= limit,
      remaining: Math.max(0, limit - current),
    };
  }
}

// Usage: 100 requests per hour per IP
const limiter = new RateLimiter(redis);
const result = await limiter.checkRateLimit(`ratelimit:${ip}`, 100, 3600);

if (!result.allowed) {
  return res.status(429).json({ error: 'Too many requests' });
}
Sliding Window Rate Limiting
async function slidingWindowRateLimit(
  redis: Redis,
  key: string,
  limit: number,
  window: number
): Promise<boolean> {
  const now = Date.now();
  const windowStart = now - window * 1000;

  // Remove old entries
  await redis.zremrangebyscore(key, 0, windowStart);

  // Count requests in window
  const count = await redis.zcard(key);

  if (count < limit) {
    // Add current request
    await redis.zadd(key, now, `${now}-${Math.random()}`);
    await redis.expire(key, window);
    return true;
  }

  return false;
}
Distributed Locking
class RedisLock {
  constructor(private redis: Redis) {}

  async acquire(
    resource: string,
    ttl: number = 10000,
    retryDelay: number = 50,
    retryCount: number = 100
  ): Promise<string | null> {
    const lockKey = `lock:${resource}`;
    const lockValue = crypto.randomUUID();

    for (let i = 0; i < retryCount; i++) {
      const acquired = await this.redis.set(
        lockKey,
        lockValue,
        'PX',
        ttl,
        'NX'
      );

      if (acquired === 'OK') {
        return lockValue;
      }

      await new Promise((resolve) => setTimeout(resolve, retryDelay));
    }

    return null;
  }

  async release(resource: string, lockValue: string): Promise<boolean> {
    const lockKey = `lock:${resource}`;

    // Use Lua script to ensure atomicity
    const script = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;

    const result = await this.redis.eval(script, 1, lockKey, lockValue);
    return result === 1;
  }

  async withLock<T>(
    resource: string,
    fn: () => Promise<T>,
    ttl: number = 10000
  ): Promise<T> {
    const lockValue = await this.acquire(resource, ttl);
    if (!lockValue) {
      throw new Error('Failed to acquire lock');
    }

    try {
      return await fn();
    } finally {
      await this.release(resource, lockValue);
    }
  }
}

// Usage
const lock = new RedisLock(redis);

await lock.withLock('resource:123', async () => {
  // Critical section - only one process can execute this
  const data = await fetchData();
  await processData(data);
});

Pub/Sub

// Publisher
const publisher = new Redis();

await publisher.publish('notifications', JSON.stringify({
  type: 'new_message',
  userId: 1000,
  message: 'Hello!',
}));

// Subscriber
const subscriber = new Redis();

subscriber.subscribe('notifications', (err, count) => {
  console.log(`Subscribed to ${count} channels`);
});

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

// Pattern subscription
subscriber.psubscribe('user:*:notifications', (err, count) => {
  console.log(`Subscribed to ${count} patterns`);
});

subscriber.on('pmessage', (pattern, channel, message) => {
  console.log(`Pattern ${pattern} matched ${channel}:`, message);
});

// Unsubscribe
await subscriber.unsubscribe('notifications');
await subscriber.punsubscribe('user:*:notifications');

Redis Streams

// Add to stream
await redis.xadd(
  'events',
  '*', // Auto-generate ID
  'type', 'user_registered',
  'userId', '1000',
  'email', 'alice@example.com'
);

// Read from stream
const messages = await redis.xread('COUNT', 10, 'STREAMS', 'events', '0');
/*
[
  ['events', [
    ['1609459200000-0', ['type', 'user_registered', 'userId', '1000']],
    ['1609459201000-0', ['type', 'order_placed', 'orderId', '500']]
  ]]
]
*/

// Consumer Groups
await redis.xgroup('CREATE', 'events', 'worker-group', '0', 'MKSTREAM');

// Read as consumer
const messages = await redis.xreadgroup(
  'GROUP', 'worker-group', 'consumer-1',
  'COUNT', 10,
  'STREAMS', 'events', '>'
);

// Acknowledge message
await redis.xack('events', 'worker-group', '1609459200000-0');

// Pending messages
const pending = await redis.xpending('events', 'worker-group');

Transactions

// Multi/Exec (transaction)
const pipeline = redis.multi();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.incr('counter');
const results = await pipeline.exec();

// Watch (optimistic locking)
await redis.watch('balance:1000');
const balance = parseInt(await redis.get('balance:1000') || '0');

if (balance >= amount) {
  const multi = redis.multi();
  multi.decrby('balance:1000', amount);
  multi.incrby('balance:2000', amount);
  await multi.exec(); // Executes only if balance:1000 wasn't modified
} else {
  await redis.unwatch();
}

Pipelining

// Pipeline multiple commands
const pipeline = redis.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.get('key1');
pipeline.get('key2');
const results = await pipeline.exec();
// [[null, 'OK'], [null, 'OK'], [null, 'value1'], [null, 'value2']]

// Batch operations
async function batchSet(items: Record<string, string>) {
  const pipeline = redis.pipeline();
  for (const [key, value] of Object.entries(items)) {
    pipeline.set(key, value);
  }
  await pipeline.exec();
}

Lua Scripts

// Atomic increment with max
const script = `
  local current = redis.call('GET', KEYS[1])
  local max = tonumber(ARGV[1])

  if current and tonumber(current) >= max then
    return tonumber(current)
  else
    return redis.call('INCR', KEYS[1])
  end
`;

const result = await redis.eval(script, 1, 'counter', 100);

// Load script once, execute many times
const sha = await redis.script('LOAD', script);
const result = await redis.evalsha(sha, 1, 'counter', 100);

Redis Cluster

Setup

# Create 6 nodes (3 masters, 3 replicas)
for port in {7000..7005}; do
  mkdir -p cluster/${port}
  cat > cluster/${port}/redis.conf <<EOF
port ${port}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
  redis-server cluster/${port}/redis.conf &
done

# Create cluster
redis-cli --cluster create \
  127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
  127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
  --cluster-replicas 1

Cluster Client

import Redis from 'ioredis';

const cluster = new Redis.Cluster([
  { host: '127.0.0.1', port: 7000 },
  { host: '127.0.0.1', port: 7001 },
  { host: '127.0.0.1', port: 7002 },
]);

// Operations work transparently
await cluster.set('key', 'value');
await cluster.get('key');

Best Practices

Memory Management

  • Set maxmemory limit
  • Choose appropriate eviction policy:
  • allkeys-lru: Remove least recently used keys
  • allkeys-lfu: Remove least frequently used keys
  • volatile-lru: Remove LRU keys with expire set
  • volatile-ttl: Remove keys with shortest TTL
  • Monitor memory usage: INFO memory
  • Use memory-efficient data structures

Key Naming

// Good: hierarchical, descriptive
'user:1000:profile'
'session:abc123'
'cache:api:users:page:1'
'ratelimit:ip:192.168.1.1:2024-01-19'

// Use consistent separators
const key = ['user', userId, 'profile'].join(':');

Expiration

  • Always set TTL for cache keys
  • Use appropriate TTL based on data freshness
  • Monitor keys without expiration: redis-cli --bigkeys

Persistence

  • Use AOF for durability (appendonly yes)
  • Use RDB for backups (save snapshots)
  • Test restore procedures

Monitoring

# Monitor commands in real-time
redis-cli MONITOR

# Stats
redis-cli INFO

# Slow queries
redis-cli SLOWLOG GET 10

# Memory analysis
redis-cli --bigkeys

# Latency
redis-cli --latency

Performance Optimization

Connection Pooling

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

Avoid KEYS Command

// ❌ Bad: Blocks entire server
const keys = await redis.keys('user:*');

// ✅ Good: Use SCAN for large datasets
async function* scanKeys(pattern: string) {
  let cursor = '0';
  do {
    const [newCursor, keys] = await redis.scan(
      cursor,
      'MATCH',
      pattern,
      'COUNT',
      100
    );
    cursor = newCursor;
    yield* keys;
  } while (cursor !== '0');
}

for await (const key of scanKeys('user:*')) {
  console.log(key);
}

Optimize Data Structures

// Use hashes for objects instead of multiple keys
// ❌ Bad: 3 keys
await redis.set('user:1000:name', 'Alice');
await redis.set('user:1000:email', 'alice@example.com');
await redis.set('user:1000:age', '30');

// ✅ Good: 1 key
await redis.hset('user:1000', {
  name: 'Alice',
  email: 'alice@example.com',
  age: '30',
});

Anti-Patterns to Avoid

Using Redis as primary database: Use for caching/sessions ❌ Not setting TTL on cache keys: Causes memory bloat ❌ Using KEYS in production: Use SCAN instead ❌ Large values in keys: Keep values small (<1MB) ❌ No monitoring: Track memory, latency, hit rate ❌ Synchronous blocking operations: Use async operations ❌ Not handling connection failures: Implement retry logic ❌ Storing large collections in single key: Split into multiple keys

Common Use Cases

Session Store (Express)

import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(
  session({
    store: new RedisStore({ client: redis }),
    secret: 'secret',
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: true,
      httpOnly: true,
      maxAge: 1000 * 60 * 60 * 24, // 24 hours
    },
  })
);

Job Queue (BullMQ)

import { Queue, Worker } from 'bullmq';

const queue = new Queue('emails', { connection: redis });

// Add job
await queue.add('send-email', {
  to: 'user@example.com',
  subject: 'Welcome',
  body: 'Hello!',
});

// Process jobs
const worker = new Worker('emails', async (job) => {
  await sendEmail(job.data);
}, { connection: redis });

Resources

  • Redis Documentation: https://redis.io/docs/
  • ioredis: https://github.com/redis/ioredis
  • Redis University: https://university.redis.com/
  • BullMQ: https://docs.bullmq.io/

Related skills

How it compares

Pick redis-expert over generic caching advice when Redis-specific structures, pub/sub semantics, or redis-cli validation are required.

FAQ

Which Redis data structures does redis-expert cover?

redis-expert covers strings up to 512MB, lists, sets, sorted sets, hashes, streams, bitmaps, and HyperLogLog. The skill guides pattern selection for caching, pub/sub, sessions, rate limiting, and queues.

What tools does redis-expert allow?

redis-expert allows Read, Write, Edit, and Bash commands scoped to redis-cli and docker. Developers validate configurations against a live or containerized Redis instance during implementation.

Databasesdatabasespipelines

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.