mcpField
R
redis-patterns

redis-patterns

github.com/rohitg00データ・データベース2.8k196 回インストール25 週間前に更新
リポジトリ

redis-patterns について

Redisを使う開発者向けのリファレンススキルです。一般的なデータパターン、実装例、運用上の注意点を扱い、キャッシュやメッセージング、イベント処理の設計に役立ちます。

主な機能

  1. 01キャッシュ戦略とTTL
  2. 02スライディングウィンドウ方式のレート制限
  3. 03Pub/SubとRedis Streams
  4. 04Luaによるアトミック操作
  5. 05Redis利用チェックリスト

活用シーン

  1. 01キャッシュ動作の設計
  2. 02リクエストのレート制限実装
  3. 03永続的なイベント処理方式の選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rohitg00/awesome-claude-code-toolkit --skill redis-patterns

インストール後、Claude Code で/redis-patternsを入力して読み込まれたか確認します。

詳細

Redis パターン

キャッシュ戦略

async function getUser(userId: string): Promise<User> {
  const cacheKey = `user:${userId}`;
  const cached = await redis.get(cacheKey);

  if (cached) {
    return JSON.parse(cached);
  }

  const user = await db.user.findUnique({ where: { id: userId } });
  if (user) {
    await redis.set(cacheKey, JSON.stringify(user), "EX", 3600);
  }

  return user;
}

async function invalidateUser(userId: string): Promise<void> {
  await redis.del(`user:${userId}`);
  await redis.del(`user:${userId}:orders`);
}

async function cacheAside<T>(
  key: string,
  ttlSeconds: number,
  fetcher: () => Promise<T>
): Promise<T> {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const value = await fetcher();
  await redis.set(key, JSON.stringify(value), "EX", ttlSeconds);
  return value;
}

スライディングウィンドウによるレート制限

async function isRateLimited(
  clientId: string,
  limit: number,
  windowSeconds: number
): Promise<boolean> {
  const key = `ratelimit:${clientId}`;
  const now = Date.now();
  const windowStart = now - windowSeconds * 1000;

  const pipe = redis.multi();
  pipe.zremrangebyscore(key, 0, windowStart);
  pipe.zadd(key, now, `${now}:${crypto.randomUUID()}`);
  pipe.zcard(key);
  pipe.expire(key, windowSeconds);

  const results = await pipe.exec();
  const count = results[2][1] as number;
  return count > limit;
}

Pub/Sub

const subscriber = redis.duplicate();
await subscriber.subscribe("notifications", "orders");

subscriber.on("message", (channel, message) => {
  const event = JSON.parse(message);
  switch (channel) {
    case "notifications":
      handleNotification(event);
      break;
    case "orders":
      handleOrderEvent(event);
      break;
  }
});

async function publishEvent(channel: string, event: object): Promise<void> {
  await redis.publish(channel, JSON.stringify(event));
}

イベント処理のための Streams

async function produceEvent(stream: string, event: Record<string, string>) {
  await redis.xadd(stream, "*", ...Object.entries(event).flat());
}

async function consumeEvents(
  stream: string,
  group: string,
  consumer: string
) {
  try {
    await redis.xgroup("CREATE", stream, group, "0", "MKSTREAM");
  } catch {
    // group already exists
  }

  while (true) {
    const results = await redis.xreadgroup(
      "GROUP", group, consumer,
      "COUNT", 10,
      "BLOCK", 5000,
      "STREAMS", stream, ">"
    );

    if (!results) continue;

    for (const [, messages] of results) {
      for (const [id, fields] of messages) {
        await processMessage(fields);
        await redis.xack(stream, group, id);
      }
    }
  }
}

Streams は、確認応答と再生に対応した、永続性のあるコンシューマーグループベースのイベント処理を提供します。

アトミック操作のための Lua スクリプト

const acquireLock = `
  local key = KEYS[1]
  local token = ARGV[1]
  local ttl = ARGV[2]
  if redis.call("SET", key, token, "NX", "EX", ttl) then
    return 1
  end
  return 0
`;

const releaseLock = `
  local key = KEYS[1]
  local token = ARGV[1]
  if redis.call("GET", key) == token then
    return redis.call("DEL", key)
  end
  return 0
`;

async function withLock<T>(
  resource: string,
  ttl: number,
  fn: () => Promise<T>
): Promise<T> {
  const token = crypto.randomUUID();
  const acquired = await redis.eval(acquireLock, 1, `lock:${resource}`, token, ttl);
  if (!acquired) throw new Error("Failed to acquire lock");
  try {
    return await fn();
  } finally {
    await redis.eval(releaseLock, 1, `lock:${resource}`, token);
  }
}

アンチパターン

  • 圧縮せずに大きなオブジェクト(>100KB)を Redis に保存する
  • 本番環境で KEYS * を使用する(サーバーをブロックします。代わりに SCAN を使用してください)
  • キャッシュエントリに TTL を設定しない(メモリが無制限に増加します)
  • 永続性が必要なメッセージングに pub/sub を使用する(サブスクライバーが接続されていない場合、メッセージは失われます)
  • 永続化戦略なしで Redis を唯一のデータストアとして頼る
  • 複数の連続したコマンドにパイプラインを使用しない

チェックリスト

  • キャッシュキーに一貫した命名規則(entity:id:field)を使用している
  • メモリリークを防ぐため、すべてのキャッシュエントリに TTL を設定している
  • 本番環境でパターンマッチングに KEYS ではなく SCAN を使用している
  • アトミック性が必要な操作に Lua スクリプトを使用している
  • 永続性が必要な場合、pub/sub ではなく Streams を使用している
  • 高スループットのアプリケーション向けにコネクションプーリングを設定している
  • レート制限にソート済みセットを使ったスライディングウィンドウを使用している
  • 分散ロックにフェンシングトークンと TTL を含めている

コメント