iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Examples

A small gallery of Redis patterns you reach for in real apps: rate limiting, distributed lock, pub/sub, leaderboard, session store, and cached aggregate. Each is short enough to drop into a service today.

Six Redis patterns with working code

EXAMPLE
// npm i ioredis
import Redis from 'ioredis';
const r = new Redis(process.env.REDIS_URL);

// ===== 1) Rate limiting: fixed window, 60 req / 60s per IP =====
async function allow(ip: string): Promise<boolean> {
  const key   = \`rl:${ip}:${Math.floor(Date.now() / 60_000)}\`;
  const count = await r.incr(key);
  if (count === 1) await r.expire(key, 60);
  return count <= 60;
}

// ===== 2) Sliding-window rate limit (more accurate, slightly costlier) =====
async function allowSliding(userId: string, max = 30, windowMs = 60_000) {
  const now = Date.now();
  const key = \`slide:${userId}\`;
  const pipe = r.multi();
  pipe.zadd(key, now, \`${now}-${Math.random()}\`);
  pipe.zremrangebyscore(key, 0, now - windowMs);
  pipe.zcard(key);
  pipe.pexpire(key, windowMs);
  const res: any = await pipe.exec();
  return res[2][1] <= max;
}

// ===== 3) Distributed lock (single-instance Redis, not Redlock) =====
async function withLock<T>(key: string, ttlMs: number, fn: () => Promise<T>) {
  const token = crypto.randomUUID();
  // NX = set only if not exists; PX = TTL in ms
  const ok = await r.set('lock:' + key, token, 'PX', ttlMs, 'NX');
  if (!ok) throw new Error('lock busy');
  try {
    return await fn();
  } finally {
    // Release only if we still own it — Lua keeps it atomic
    await r.eval(
      "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
      1, 'lock:' + key, token,
    );
  }
}

// ===== 4) Pub/Sub for fan-out events =====
const pub = new Redis(process.env.REDIS_URL);
const sub = new Redis(process.env.REDIS_URL);
sub.subscribe('orders:created');
sub.on('message', (channel, msg) => console.log(channel, JSON.parse(msg)));
await pub.publish('orders:created', JSON.stringify({ id: 'o1', total: 49.95 }));

// ===== 5) Sorted-set leaderboard =====
await r.zincrby('leaderboard:weekly', 100, 'alice');
await r.zincrby('leaderboard:weekly', 250, 'bob');
const top = await r.zrevrange('leaderboard:weekly', 0, 9, 'WITHSCORES');

// ===== 6) Session store — a hash per session id, with sliding TTL =====
async function loadSession(sid: string) {
  const data = await r.hgetall('sess:' + sid);
  if (!Object.keys(data).length) return null;
  await r.expire('sess:' + sid, 60 * 60 * 24 * 14); // slide TTL on read
  return data;
}
async function saveSession(sid: string, patch: Record<string, string>) {
  await r.hmset('sess:' + sid, patch);
  await r.expire('sess:' + sid, 60 * 60 * 24 * 14);
}

// ===== 7) Cached aggregate with double-checked locking =====
async function dailyRevenue(date: string): Promise<number> {
  const key = \`rev:${date}\`;
  const cached = await r.get(key);
  if (cached) return Number(cached);

  return withLock(key, 5000, async () => {
    const recheck = await r.get(key);
    if (recheck) return Number(recheck);
    const rev = await heavyDbQuery(date);
    await r.set(key, rev, 'EX', 300);
    return rev;
  });
}

async function heavyDbQuery(_d: string) { return 12345; }

Why it matters

A single-instance Redis lock (SET NX PX + Lua release) is the right tool 95% of the time. Reach for Redlock only when a multi-master Redis topology actually exists in production — the added complexity is rarely worth it when the simpler primitive plus a sensible TTL covers your real failure modes.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Real-world: cache, queue, session, leaderboard, rate-limit, pubsub.
Try it Yourself »

Discussion

Loading…