Claude
Skills
Sign in
Back

redis-expert

Included with Lifetime
$97 forever

Expert-level Redis for caching, pub/sub, data structures, and high-performance applications

datarediscachepubsubinmemorykeyvaluenosql

What this skill does


# 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
```bash
# 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)
```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
```typescript
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: '[email protected]',
  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
```typescript
// 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
```typescript
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
```typescript
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
```typescript
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, lockVal
Files: 1
Size: 16.1 KB
Complexity: 23/100
Category: data

Related in data