redis-patterns について
Redisを使う開発者向けのリファレンススキルです。一般的なデータパターン、実装例、運用上の注意点を扱い、キャッシュやメッセージング、イベント処理の設計に役立ちます。
主な機能
- 01キャッシュ戦略とTTL
- 02スライディングウィンドウ方式のレート制限
- 03Pub/SubとRedis Streams
- 04Luaによるアトミック操作
- 05Redis利用チェックリスト
活用シーン
- 01キャッシュ動作の設計
- 02リクエストのレート制限実装
- 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 を含めている
コメント
Markdown 対応