SJ blog
database
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

Redisをキャッシュだけに使うのはもったいない:高度な使い方

Redisのデータ構造(List・Set・Sorted Set・Hash・Stream)を活用した高度なユースケースを解説。セッション管理・レート制限・リアルタイムランキング・メッセージキューなどの実装例を紹介します。

一言結論

RedisはSorted Setでリアルタイムランキング、Streamでメッセージキュー、LUAスクリプトでアトミックなレート制限を実装できる汎用データ構造サーバーであり、「キャッシュ専用」と割り切るのはその能力の半分以下しか使っていない。

Redis のデータ構造

Redis は「高速なデータ構造サーバー」です。キャッシュ以外にも多くのユースケースがあります。

データ構造コマンド例使いどころ
StringSETGETINCRキャッシュ・カウンター・フラグ
ListLPUSHRPOPLRANGEキュー・スタック
SetSADDSMEMBERSSINTERタグ・ユニーク集合・集合演算
Sorted SetZADDZRANGEZRANKランキング・スコアボード
HashHSETHGETHMGETオブジェクト・ユーザーセッション
StreamXADDXREADイベントログ・メッセージキュー

ユースケース1: セッション管理

import { createClient } from "redis";

const redis = createClient();

// セッションの保存(30分有効)
async function saveSession(sessionId: string, userId: string, data: object) {
  await redis.hSet(`session:${sessionId}`, {
    userId,
    ...data,
    createdAt: Date.now().toString(),
  });
  await redis.expire(`session:${sessionId}`, 1800); // 30分
}

// セッションの取得
async function getSession(sessionId: string) {
  const session = await redis.hGetAll(`session:${sessionId}`);
  if (!session.userId) return null;

  // アクセスのたびに有効期限を延長(スライディングセッション)
  await redis.expire(`session:${sessionId}`, 1800);
  return session;
}

ユースケース2: レート制限(固定ウィンドウ)

async function rateLimit(
  ip: string,
  limit: number = 100,
  windowSecs: number = 60
): Promise<{ allowed: boolean; remaining: number }> {
  const key = `rate:${ip}:${Math.floor(Date.now() / 1000 / windowSecs)}`;

  const count = await redis.incr(key);
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

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

// ミドルウェア
app.use(async (req, res, next) => {
  const { allowed, remaining } = await rateLimit(req.ip);
  res.set("X-RateLimit-Remaining", remaining.toString());

  if (!allowed) return res.status(429).json({ error: "Too Many Requests" });
  next();
});

ユースケース3: リアルタイムランキング(Sorted Set)

// スコアを追加・更新
await redis.zAdd("leaderboard", { score: 1500, value: "user:alice" });
await redis.zAdd("leaderboard", { score: 1200, value: "user:bob" });
await redis.zAdd("leaderboard", { score: 1800, value: "user:charlie" });

// 上位10名を取得(高スコア順)
const top10 = await redis.zRangeWithScores("leaderboard", 0, 9, { REV: true });
// → [{ value: "user:charlie", score: 1800 }, ...]

// ユーザーのランクを取得(0始まり)
const rank = await redis.zRevRank("leaderboard", "user:alice");
// → 1(2位)

// スコアをインクリメント
await redis.zIncrBy("leaderboard", 100, "user:alice"); // 1600点に

ユースケース4: シンプルなジョブキュー(List)

// プロデューサー: タスクをエンキュー
async function enqueue(queueName: string, task: object) {
  await redis.lPush(queueName, JSON.stringify(task));
}

// コンシューマー: タスクを処理(ブロッキング)
async function processQueue(queueName: string) {
  while (true) {
    // 最大10秒待って取得(ブロッキング)
    const result = await redis.brPop(queueName, 10);
    if (!result) continue;

    const task = JSON.parse(result.element);
    await processTask(task);
  }
}

// 使用例
await enqueue("email_queue", { to: "user@example.com", subject: "Welcome!" });

ユースケース5: Pub/Sub でリアルタイム通知

// パブリッシャー
await redis.publish("notifications", JSON.stringify({
  type: "new_message",
  userId: "123",
  content: "こんにちは!",
}));

// サブスクライバー
const subscriber = redis.duplicate();
await subscriber.connect();

await subscriber.subscribe("notifications", (message) => {
  const data = JSON.parse(message);
  // WebSocket でクライアントに転送
  wsClients.get(data.userId)?.send(message);
});

ユースケース6: 分散ロック(Redlock)

import Redlock from "redlock";

const redlock = new Redlock([redis], {
  retryCount: 3,
  retryDelay: 200,
});

async function processPayment(orderId: string) {
  // 同じ注文を同時に処理しないようにロック
  const lock = await redlock.acquire([`lock:order:${orderId}`], 5000); // 5秒

  try {
    await processOrderPayment(orderId);
  } finally {
    await lock.release();
  }
}

TTL の設計原則

// TTL を設定しないと Redis がメモリ不足になる
// すべての一時データには必ず TTL を設定する

await redis.set("cache:user:123", JSON.stringify(user), { EX: 300 }); // 5分

// maxmemory-policy の設定(redis.conf)
// allkeys-lru: メモリ不足時に最近使われていないキーを削除(キャッシュ用途)
// noeviction: メモリ不足でもエラーを返す(永続データ用途)

まとめ

ユースケースデータ構造コマンド
キャッシュStringSETGETEX
セッションHashHSETHGETALL
レート制限StringINCREXPIRE
ランキングSorted SetZADDZRANGE
キューListLPUSHBRPOP
通知Pub/SubPUBLISHSUBSCRIBE
分散ロックStringRedlock ライブラリ

参考: Redis 公式ドキュメント / Redis University