Advanced

Redis with Claude Code

Learn about redis using Claude Code. Practical tips and code examples included.

Redis キャッシュの重要性

Redisはインメモリデータストアとして、キャッシュ、セッション管理、リアルタイム処理に広く使われています。Claude Codeを使えば、適切なキャッシュ戦略を設計し効率的に実装できます。

基本的なキャッシュ層

import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

class CacheService {
  private redis: Redis;
  private defaultTTL: number;

  constructor(redis: Redis, defaultTTL = 300) {
    this.redis = redis;
    this.defaultTTL = defaultTTL;
  }

  async get<T>(key: string): Promise<T | null> {
    const data = await this.redis.get(key);
    if (!data) return null;

    try {
      return JSON.parse(data) as T;
    } catch {
      return null;
    }
  }

  async set<T>(key: string, value: T, ttl?: number): Promise<void> {
    const serialized = JSON.stringify(value);
    await this.redis.set(key, serialized, "EX", ttl ?? this.defaultTTL);
  }

  async delete(key: string): Promise<void> {
    await this.redis.del(key);
  }

  async deletePattern(pattern: string): Promise<void> {
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) {
      await this.redis.del(...keys);
    }
  }
}

const cache = new CacheService(redis);

Cache-Aside パターン

async function getPostById(id: string): Promise<Post | null> {
  const cacheKey = `post:${id}`;

  // 1. キャッシュから取得
  const cached = await cache.get<Post>(cacheKey);
  if (cached) {
    return cached;
  }

  // 2. DBから取得
  const post = await prisma.post.findUnique({
    where: { id },
    include: { author: true, categories: true },
  });

  if (!post) return null;

  // 3. キャッシュに保存(5分)
  await cache.set(cacheKey, post, 300);

  return post;
}

// 更新時のキャッシュ無効化
async function updatePost(id: string, data: Partial<Post>) {
  const updated = await prisma.post.update({
    where: { id },
    data,
  });

  // 関連するキャッシュを無効化
  await cache.delete(`post:${id}`);
  await cache.deletePattern("posts:list:*");

  return updated;
}

リスト結果のキャッシュ

async function getPostsList(params: {
  page: number;
  category?: string;
}): Promise<PaginatedResult<Post>> {
  const cacheKey = `posts:list:${params.page}:${params.category || "all"}`;

  const cached = await cache.get<PaginatedResult<Post>>(cacheKey);
  if (cached) return cached;

  const result = await fetchPostsFromDB(params);

  // リスト結果は短めのTTL(1分)
  await cache.set(cacheKey, result, 60);

  return result;
}

キャッシュデコレータ

function Cacheable(ttl: number = 300) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const cacheKey = `${target.constructor.name}:${propertyKey}:${JSON.stringify(args)}`;

      const cached = await cache.get(cacheKey);
      if (cached) return cached;

      const result = await originalMethod.apply(this, args);
      await cache.set(cacheKey, result, ttl);

      return result;
    };

    return descriptor;
  };
}

class PostService {
  @Cacheable(300)
  async getById(id: string) {
    return prisma.post.findUnique({ where: { id } });
  }

  @Cacheable(60)
  async getPopular(limit: number = 10) {
    return prisma.post.findMany({
      where: { published: true },
      orderBy: { viewCount: "desc" },
      take: limit,
    });
  }
}

セッション管理

import session from "express-session";
import RedisStore from "connect-redis";

const redisStore = new RedisStore({
  client: redis,
  prefix: "sess:",
  ttl: 86400, // 24時間
});

app.use(
  session({
    store: redisStore,
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: process.env.NODE_ENV === "production",
      httpOnly: true,
      maxAge: 86400 * 1000,
      sameSite: "strict",
    },
  })
);

レート制限

async function rateLimiter(
  key: string,
  maxRequests: number,
  windowSeconds: number
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
  const now = Math.floor(Date.now() / 1000);
  const windowKey = `ratelimit:${key}:${Math.floor(now / windowSeconds)}`;

  const current = await redis.incr(windowKey);

  if (current === 1) {
    await redis.expire(windowKey, windowSeconds);
  }

  const remaining = Math.max(0, maxRequests - current);
  const resetAt = (Math.floor(now / windowSeconds) + 1) * windowSeconds;

  return {
    allowed: current <= maxRequests,
    remaining,
    resetAt,
  };
}

// ミドルウェアとして使用
async function rateLimitMiddleware(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const key = req.ip || "unknown";
  const result = await rateLimiter(key, 100, 60);

  res.set("X-RateLimit-Remaining", String(result.remaining));
  res.set("X-RateLimit-Reset", String(result.resetAt));

  if (!result.allowed) {
    return res.status(429).json({ error: "Too many requests" });
  }

  next();
}

Claude Codeでの活用

Redisキャッシュの実装をClaude Codeに依頼する例です。エッジでのキャッシュについてはエッジコンピューティング、非同期処理はジョブキュー・非同期処理も参照してください。

Redisキャッシュ層を設計して。
- Cache-Asideパターンでの読み取りキャッシュ
- 更新時のキャッシュ無効化戦略
- APIレート制限
- セッション管理
- キャッシュのヒット率モニタリング

Redisの詳細はRedis公式ドキュメントを参照してください。Claude Codeの使い方は公式ドキュメントで確認できます。

Summary

Redisキャッシュはアプリケーションのパフォーマンスを劇的に向上させます。Claude Codeを使えば、キャッシュ戦略の設計から無効化パターンの実装まで、一貫したキャッシュ層を構築できます。

#Claude Code #Redis #caching #performance #backend