api-database-redis について
Redis Patternsは、Node.jsアプリケーションでRedisを使う開発者向けの実践ガイドです。クライアント設定に加え、キャッシュ、セッション、メッセージング、レート制限、キューのパターンと、信頼性やアトミック操作を扱います。
主な機能
- 01ioredisとnode-redisの設定
- 02キャッシュアサイドとライトスルーキャッシュの適用
- 03Redisのデータ構造とストリームの利用
- 04Luaによるアトミック操作の実装
- 05Pub/Subメッセージングとレート制限の実装
- 06BullMQキューとワーカーの設定
活用シーン
- 01データベースクエリやAPIレスポンスのキャッシュ
- 02ExpressまたはHonoアプリでのセッション保存
- 03分散レート制限やPub/Subの追加
- 04BullMQによるバックグラウンドジョブ処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill api-database-redisインストール後、Claude Code で/api-database-redisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/caching.mdcore.mddata-structures.mdpub-sub.mdqueues.mdrate-limiting.mdsessions.md詳細
Redis パターン
クイックガイド: キャッシュ、セッション管理、レート制限、Pub/Sub メッセージング、ジョブキュー用のインメモリデータストアとして Redis を使用します。優れた TypeScript サポート、Cluster/Sentinel との統合、自動パイプライン化、Lua スクリプト機能を備えた ioredis(v5.x)を、主要なクライアントとして使用します。Redis Stack モジュール(JSON、Search、TimeSeries)が必要な場合に限り、node-redis(v5.x)を使用します。BullMQ ワーカーでは常に
maxRetriesPerRequest: nullを設定し、Pub/Sub のサブスクライバーには別の接続を使用して、アトミックな複数ステップ操作にはdefineCommandで Lua スクリプトを定義します。
<critical_requirements>
重要:このスキルを使用する前に
すべてのコードは CLAUDE.md のプロジェクト規約(kebab-case、名前付きエクスポート、インポート順序、
import type、名前付き定数)に従う必要があります。
(Pub/Sub のサブスクライバーには、必ず別の Redis 接続を使用してください。サブスクライブした接続は特別なモードになり、ほかのコマンドを実行できません)
(BullMQ に渡すすべての ioredis 接続には、必ず maxRetriesPerRequest: null を設定してください。BullMQ には無限回の再試行が必要で、設定されていないとエラーが発生します)
(複数の Redis コマンドにまたがるアトミック性が必要な操作には、必ず Lua スクリプト(defineCommand または eval)を使用してください。パイプライン内でも、個別のコマンドはアトミックではありません)
(すべての Redis クライアントインスタンスで、必ず error イベントを処理してください。処理されていないエラーは Node.js プロセスをクラッシュさせます)
</critical_requirements>
例
- 基本パターン -- ioredis/node-redis の接続、エラー処理、再接続、クラスター、Sentinel、パイプライン処理、トランザクション
- キャッシュパターン -- キャッシュアサイド、ライトスルー、無効化、スタンピード防止、複数キーのパイプライン
- データ構造 -- 型付きヘルパーを使用した文字列、ハッシュ、リスト、セット、ソート済みセット
- セッション -- Express の connect-redis(v9 以降には node-redis が必要)、Hono の手動ミドルウェア
- Pub/Sub -- パブリッシュ/サブスクライブ、イベントのブロードキャスト、パターンサブスクリプション
- レート制限 -- スライディングウィンドウ(Lua)、トークンバケット(Lua)、ミドルウェアとの統合
- キューとロック -- BullMQ ジョブキュー、コンシューマーグループを使用した Redis Streams、分散ロック
追加のリソース:
- reference.md -- コマンドの早見表、接続オプション、アンチパターン、本番環境チェックリスト
自動検出: Redis、ioredis、node-redis、createClient、RedisStore、BullMQ、Queue、Worker、Pub/Sub、MULTI、EXEC、パイプライン、Lua スクリプト、defineCommand、xadd、xread、キャッシュアサイド、レート制限、セッションストア、connect-redis、Redis.Cluster、Sentinel
使用する場面:
- データベースのクエリや API のレスポンスをキャッシュする場合(キャッシュアサイド、ライトスルー)
- Express/Hono/Fastify アプリケーションのセッションストレージ
- 分散レート制限(スライディングウィンドウ、トークンバケット)
- Pub/Sub によるリアルタイムメッセージング
- BullMQ キューによるバックグラウンドジョブ処理
- ソート済みセットを使ったリーダーボード、カウンター、リアルタイム分析
- Lua スクリプトによる分散ロックとアトミック操作
対象となる主なパターン:
- ioredis の接続設定、構成、エラー処理
- データ構造(文字列、ハッシュ、リスト、セット、ソート済みセット、ストリーム)
- TTL 管理を伴うキャッシュアサイドとライトスルーキャッシュ
- connect-redis によるセッションストレージ
- Lua スクリプトによるレート制限(スライディングウィンドウ、トークンバケット)
- 別々の接続を使用する Pub/Sub メッセージング
- 永続的なメッセージキュー向けの Redis Streams
- 再試行とスケジュール設定に対応したジョブキュー向けの BullMQ
- パイプライン処理とトランザクション(MULTI/EXEC)
- アトミック操作のための Lua スクリプト
- 高可用性のためのクラスター モードと Sentinel
使用しない場面:
- リレーショナルデータのプライマリデータベース(リレーショナルデータベースを使用してください)
- 複雑なクエリを伴うドキュメントストレージ(ドキュメントデータベースを使用してください)
- 大容量のバイナリファイルのストレージ(S3/オブジェクトストレージを使用してください)
- 永続化を設定せず、メモリの完全な消失後も保持する必要があるデータ
## 哲学
Redis は、キャッシュ、メッセージブローカー、ストリーミングエンジンとして使用されるインメモリデータストアです。中核となる原則は、Redis は高速、一時的、またはリアルタイムのデータに使い、プライマリデータベースとしては使わないことです。
中核となる原則:
- 保存するのではなく、キャッシュする -- Redis はプライマリデータベースを補完するものです。頻繁にアクセスされるデータをキャッシュしますが、真実の情報源は必ず別の場所に用意してください。
- アトミック操作 -- 複数のキーにまたがる操作には、Lua スクリプトまたは MULTI/EXEC を使用します。Redis の個々のコマンドはアトミックですが、コマンドの連続実行はアトミックではありません。
- 関心事を分離する -- キャッシュ、セッション、キューには異なる Redis データベース(またはキーのプレフィックス)を使用します。Pub/Sub には別の接続を使用します。
- すべてに TTL を設定する -- メモリには限りがあります。キャッシュされたキーはすべて有効期限を設定してください。SET コマンドでは
EX(秒)またはPX(ミリ秒)を使用します。 - 適切にフォールバックする -- Redis はキャッシュであり、データベースではありません。Redis が停止した場合、アプリケーションは適切に機能を縮退できるようにしてください(キャッシュを迂回して、データベースを直接使用するなど)。
中核パターン
パターン 1:ioredis の接続設定
適切なエラーハンドリングと再接続戦略を設定して、ioredis を構成します。node-redis やクラスター構成を含む詳しい例は、examples/core.md を参照してください。
// ✅ Good Example - Proper ioredis setup with error handling
import Redis from "ioredis";
const RETRY_DELAY_BASE_MS = 50;
const RETRY_DELAY_MAX_MS = 2000;
function createRedisClient(): Redis {
const url = process.env.REDIS_URL;
if (!url) {
throw new Error("REDIS_URL environment variable is required");
}
const client = new Redis(url, {
maxRetriesPerRequest: 3,
retryStrategy(times) {
return Math.min(times * RETRY_DELAY_BASE_MS, RETRY_DELAY_MAX_MS);
},
lazyConnect: true,
});
client.on("error", (err) => {
console.error("Redis connection error:", err.message);
});
return client;
}
export { createRedisClient };
良い点: 環境変数の検証、再試行間隔に名前付き定数を使用、lazyConnect により準備ができる前の接続を防止、エラーイベントハンドラーによりプロセスのクラッシュを防止
// ❌ Bad Example - No error handling, hardcoded config
import Redis from "ioredis";
const redis = new Redis("redis://localhost:6379");
// No error handler, no retry strategy, hardcoded URL
悪い点: エラーイベントハンドラーがないと、接続失敗時に Node.js プロセスがクラッシュします。URL がハードコードされているため、環境ごとの構成ができません。
パターン 2:キャッシュ・アサイド
まずキャッシュを確認し、キャッシュミス時にはデータベースにフォールバックして、キャッシュにデータを格納します。ライトスルー、無効化、スタンピード防止、複数キーのキャッシュパターンについては、examples/caching.md を参照してください。
// ✅ Good Example - Generic cache-aside helper
const CACHE_TTL_SECONDS = 300;
async function cacheAside<T>(
redis: Redis,
key: string,
fetcher: () => Promise<T>,
ttlSeconds: number = CACHE_TTL_SECONDS,
): Promise<T> {
const cached = await redis.get(key);
if (cached !== null) {
return JSON.parse(cached) as T;
}
const data = await fetcher();
// Fire-and-forget cache write
redis.set(key, JSON.stringify(data), "EX", ttlSeconds).catch((err) => {
console.error(`Cache write failed for ${key}:`, err.message);
});
return data;
}
良い点: 汎用の cacheAside<T> は任意のデータ型で動作し、キャッシュへの非同期書き込みによりキャッシュの障害がレスポンスを妨げず、TTL も設定可能です。
パターン 3:パイプライン処理とトランザクション
ネットワーク効率を高めるにはコマンドをまとめて送信し(パイプライン)、アトミック性を確保するには MULTI/EXEC を使用します。詳しい例は、examples/core.md を参照してください。
// ✅ Pipeline - batch for network efficiency (NOT atomic)
const pipeline = redis.pipeline();
pipeline.hset(key, { name: user.name, email: user.email });
pipeline.expire(key, TTL_SECONDS);
await pipeline.exec();
// ✅ Transaction - atomic execution with optimistic locking
await redis.watch(fromKey);
const results = await redis
.multi()
.decrby(fromKey, amount)
.incrby(toKey, amount)
.exec();
if (!results) {
/* WATCH detected change, retry */
}
良い点: パイプラインによりラウンドトリップが減り、MULTI/EXEC によりアトミック性が確保され、WATCH により楽観的ロックが可能になります。
// ❌ Bad Example - Non-atomic balance transfer
await redis.decrby("balance:user1", 100);
await redis.incrby("balance:user2", 100);
// Crash between commands causes data loss
悪い点: 2 つのコマンドを別々に実行してもアトミックではありません。その間にクラッシュすると、データの整合性が損なわれます。
パターン 4:アトミック性を確保する Lua スクリプト
Lua スクリプトは Redis サーバー上でアトミックに実行されます。Lua ベースのレートリミッターの詳しい例は、examples/rate-limiting.md を参照してください。
// ✅ Define custom atomic command
redis.defineCommand("rateLimit", {
numberOfKeys: 1,
lua: `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random(1000000))
redis.call('EXPIRE', key, window)
return 1
end
return 0
`,
});
良い点: defineCommand は内部で EVALSHA を使用してパフォーマンスを高めます。Lua スクリプトはアトミックであるため、競合状態が発生しません。
パターン 5:Pub/Sub メッセージング
サブスクライブとパブリッシュには、別々の接続が必要です。イベントブロードキャストシステムについては、examples/pub-sub.md を参照してください。
// ✅ Good Example - Separate connections
const publisher = new Redis(url);
const subscriber = new Redis(url); // MUST be separate
await subscriber.subscribe("notifications");
subscriber.on("message", (channel, message) => {
handleNotification(JSON.parse(message));
});
await publisher.publish("notifications", JSON.stringify(data));
良い点: パブリッシュ用とサブスクライブ用に別々の接続を使用します(Redis プロトコル上必須)。両方にエラーハンドラーを設定します。
// ❌ Bad Example - Same connection
const redis = new Redis();
await redis.subscribe("channel");
await redis.set("key", "value"); // THROWS: connection is in subscriber mode
悪い点: サブスクライブ中の接続では、Pub/Sub 以外のコマンドを実行できません。
パターン 6:BullMQ ジョブキュー
再試行、スケジューリング、優先度設定に対応した堅牢なジョブキューです。ワーカー、イベント、グレースフルシャットダウンを含む詳しい例は、examples/queues.md を参照してください。
// ✅ Good Example - BullMQ connection factory
function createBullMQConnection(): Redis {
return new Redis(process.env.REDIS_URL!, {
maxRetriesPerRequest: null, // REQUIRED for BullMQ
});
}
const emailQueue = new Queue<EmailJobData>(QUEUE_NAME, {
connection: createBullMQConnection(),
defaultJobOptions: {
attempts: MAX_RETRY_ATTEMPTS,
backoff: { type: "exponential", delay: BACKOFF_DELAY_MS },
},
});
良い点: BullMQ では maxRetriesPerRequest: null が必須です。Queue と Worker ごとに別の接続を使用します。
// ❌ Bad Example - Missing required config
const connection = new Redis(); // Missing maxRetriesPerRequest: null
const queue = new Queue("emails", { connection });
// BullMQ will throw: "maxRetriesPerRequest must be null"
悪い点: BullMQ では無制限の再試行が必要です。null を指定しないと、ioredis は所定の回数だけ試行した後に処理を諦めます。
パターン 7:Redis Streams
コンシューマーグループを使用した、永続的で順序が保持されるメッセージログです。保留中のメッセージの復旧を含む、プロデューサーとコンシューマーの完全な実装については、examples/queues.md を参照してください。
// Producer
const entryId = await redis.xadd(
STREAM_KEY,
"*",
"orderId",
id,
"action",
action,
);
// Consumer group setup (idempotent)
try {
await redis.xgroup("CREATE", STREAM_KEY, GROUP_NAME, "0", "MKSTREAM");
} catch (err) {
if (!(err instanceof Error) || !err.message.includes("BUSYGROUP")) throw err;
}
// Consumer read + acknowledge
const results = await redis.xreadgroup(
"GROUP",
GROUP_NAME,
consumerName,
"COUNT",
"10",
"BLOCK",
"5000",
"STREAMS",
STREAM_KEY,
">",
);
await redis.xack(STREAM_KEY, GROUP_NAME, messageId);
良い点: MKSTREAM によりストリームが存在しない場合に作成され、BUSYGROUP の処理によりセットアップの冪等性が確保され、XACK により処理の完了が確認されます。
## パフォーマンスの最適化
- 自動パイプライン処理 --
enableAutoPipelining: trueを有効にすると、同じイベントループのティック中に発行されたコマンドがまとめられます。WATCH/MULTI やブロッキングコマンドでは機能しません。設定については examples/core.md を参照してください。 - 手動パイプライン処理 --
redis.pipeline()を使って、コマンドを明示的にまとめます。examples/core.md を参照してください。 - キーの有効期限 -- すべてのキャッシュキーに TTL を設定します(
TTL_SHORT_SECONDS = 60、TTL_MEDIUM_SECONDS = 300など)。examples/core.md を参照してください。 - KEYS より SCAN -- 本番環境では
KEYSを決して使用しないでください(Redis をブロックします)。redis.scanStream({ match: "pattern", count: 100 })を使用してください。examples/core.md を参照してください。 - DEL より UNLINK -- 大きなキーには、ノンブロッキングで削除できる
unlinkを使用してください。
<decision_framework>
選定ガイド
Redis クライアントの選び方
Which Redis client should I use?
├─ Need Redis Stack modules (JSON, Search, TimeSeries)? -> node-redis (v5.x)
├─ Using BullMQ for job queues? -> ioredis (BullMQ requires it)
├─ Need Cluster or Sentinel support? -> ioredis (built-in, battle-tested)
├─ Need auto-pipelining? -> ioredis (enableAutoPipelining option)
└─ General caching/sessions/pub-sub? -> ioredis (recommended default)
キャッシュ戦略の選び方
How should I cache this data?
├─ Read-heavy, tolerates brief staleness? -> Cache-aside with TTL
├─ Needs strong consistency after writes? -> Write-through (update DB + invalidate cache)
├─ Write-heavy, can tolerate brief data loss? -> Write-behind (async cache update)
└─ Data changes rarely? -> Cache-aside with long TTL + manual invalidation
データ構造の選び方
What Redis data structure should I use?
├─ Simple key-value (cache, sessions)? -> Strings (GET/SET)
├─ Object with multiple fields? -> Hashes (HSET/HGET)
├─ Ordered ranking/leaderboard? -> Sorted Sets (ZADD/ZRANGE)
├─ Queue (FIFO/LIFO)? -> Lists (LPUSH/RPOP)
├─ Unique collection (tags, categories)? -> Sets (SADD/SMEMBERS)
├─ Persistent message log with consumers? -> Streams (XADD/XREAD)
└─ Rate limiting (sliding window)? -> Sorted Sets + Lua script
メッセージングパターンの選び方
How should I implement real-time messaging?
├─ Fire-and-forget broadcast? -> Pub/Sub (no persistence)
├─ Need message persistence and replay? -> Streams with consumer groups
├─ Need reliable job processing with retries? -> BullMQ (built on Redis)
└─ Need request-reply pattern? -> Pub/Sub with correlation IDs
アトミック性の判断
Do I need atomicity across multiple commands?
├─ YES -> Are the commands on the same key?
│ ├─ YES -> Use a single atomic command (INCR, SETNX, etc.)
│ └─ NO -> Use Lua script (defineCommand)
├─ NO, but I want batching -> Use pipeline (non-atomic, single round-trip)
└─ Need optimistic locking? -> Use WATCH + MULTI/EXEC
</decision_framework>
統合ガイド
一般的な統合パターン:
- データベースのキャッシュ -- キャッシュアサイドパターンを使ってクエリ結果をキャッシュし、書き込み時にキャッシュを無効化します
- HTTP フレームワークのセッション -- セッションストレージミドルウェア、レート制限ミドルウェア
- BullMQ ジョブキュー -- Redis 上に構築されています(
maxRetriesPerRequest: nullを指定した ioredis が必要です) - WebSocket のスケーリング -- Redis アダプターを使って、サーバー間で WebSocket 接続を分散します
置き換え/競合するもの:
- インメモリキャッシュ -- Redis は複数のアプリケーションインスタンス間で分散キャッシュを提供します
- データベースを使用したセッション -- Redis セッションはより高速で、データベースの負荷を軽減します
- シンプルなメッセージキュー -- Redis Streams と BullMQ はほとんどのキュー用途に対応します。複雑なルーティングや大規模なスループットには、専用のメッセージブローカーを使用してください
<red_flags>
要注意事項
優先度の高い問題:
- Pub/Sub のサブスクライブと通常のコマンドで同じ接続を使用すること。サブスクライブ中の接続では、Pub/Sub 以外のコマンドを実行できません
- BullMQ の接続で
maxRetriesPerRequest: nullを設定しないこと。この設定がないと、BullMQ は直ちにエラーをスローします - 本番環境で
KEYSコマンドを使用すること。すべてのキーをスキャンする間、Redis サーバー全体がブロックされます - Redis クライアントに
errorイベントハンドラーがないこと。未処理のエラーによって Node.js プロセスがクラッシュします - 大きなオブジェクト(> 1 MB)を Redis に保存すること。パフォーマンスが低下し、メモリを浪費します。参照を保存し、オブジェクトストレージから取得してください
優先度が中程度の問題:
- キャッシュされたキーに TTL が設定されていないこと。Redis のメモリが枯渇するまでメモリ使用量が際限なく増加します
- 多数のキーに対して
unlinkではなくdelを使用すること。delは Redis をブロックしますが、unlinkは非同期でメモリを解放します - バッチ処理でパイプラインを使用しないこと。各コマンドで個別にネットワーク往復が発生します
- 複雑なオブジェクトのシリアライズ/デシリアライズでエラー処理をしないこと。キャッシュ内の不正な JSON によってパース時にクラッシュします
- BullMQ の Queue と Worker で単一の Redis 接続を共有すること。それぞれに独自の接続が必要です
よくある間違い:
- パイプラインのコマンドがアトミックだと思い込むこと。パイプラインはネットワーク効率のためにコマンドをまとめますが、アトミック性は保証しません(MULTI/EXEC または Lua を使用してください)
hgetallは存在しないキーに対してnullではなく空のオブジェクト{}を返すことを忘れること。Object.keys(result).length === 0を確認してください- 条件付き更新に
WATCHを使わずにMULTI/EXECを使用すること。事前にキーを WATCH しない限り、トランザクションは無条件に実行されます GETがnullを返す場合を処理しないこと。キャッシュミスではundefinedではなくnullが返されます- 本番環境で TLS を使わずに Redis に接続すること。認証情報がネットワーク上で平文のまま送信されます
注意点とエッジケース:
- Redis の
HGETALLはすべての値を文字列として返します。HSETで保存した数値も文字列として返されるため、明示的なパースが必要です - キーを
SETで上書きするとEXPIREがリセットされます。すでに TTL が設定されているキーをSETすると、SETコマンドにEX/PXを含めない限り TTL は削除されます - Pub/Sub メッセージはファイア・アンド・フォーゲットです。メッセージの発行時にサブスクライバーがいなければ、そのメッセージは永久に失われます(永続化には Streams を使用してください)
- Redis Cluster は異なるハッシュスロットにまたがる複数キー操作をサポートしません。関連するキーを同じスロットに割り当てるには
{hash-tag}プレフィックスを使用してください WATCHは接続単位です。同じ接続を共有する同時リクエストは、互いの WATCH 状態に干渉します- ioredis の自動パイプライン化は、
WATCH/MULTIやブロッキングコマンド(BRPOP、BLPOP、XREAD BLOCK)では機能しません
</red_flags>
<critical_reminders>
重要な注意事項
すべてのコードは CLAUDE.md のプロジェクト規約に従う必要があります(ケバブケース、名前付きエクスポート、インポート順序、
import type、名前付き定数)
(Pub/Sub のサブスクライバーには必ず別の Redis 接続を使用してください。サブスクライブ中の接続は特殊なモードに入り、他のコマンドを実行できません)
(BullMQ に渡す ioredis 接続には、必ず maxRetriesPerRequest: null を設定してください。BullMQ は無限回の再試行を必要とし、この設定がないとエラーをスローします)
(複数の Redis コマンドにまたがるアトミック性が必要な操作には、必ず Lua スクリプト(defineCommand または eval)を使用してください。パイプライン内であっても、個別のコマンドはアトミックではありません)
(すべての Redis クライアントインスタンスで、必ず error イベントを処理してください。未処理のエラーによって Node.js プロセスがクラッシュします)
これらのルールに従わないと、Pub/Sub の障害、BullMQ の接続エラー、競合状態、アプリケーションのクラッシュを引き起こします。
</critical_reminders>