Advanced

Claude Codeでキャッシュ戦略を設計・実装する方法

Claude Codeを使ってRedis・CDN・アプリケーションレベルのキャッシュ戦略を効率的に設計・実装する方法を解説します。

キャッシュ戦略の設計にClaude Codeを使うメリット

キャッシュは適切に設計しないと、古いデータの表示やメモリの浪費につながります。Claude Codeはアプリケーションのデータアクセスパターンを分析し、最適なキャッシュ戦略を提案・実装できます。

Redisキャッシュの実装

> Redisを使ったキャッシュレイヤーを実装して。
> キャッシュ無効化、TTL管理、キャッシュアサイドパターンで。
// src/lib/cache.ts
import Redis from 'ioredis';

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

interface CacheOptions {
  ttl?: number;      // 秒単位
  prefix?: string;
}

export class CacheService {
  private defaultTTL = 300; // 5分

  async get<T>(key: string, options?: CacheOptions): Promise<T | null> {
    const fullKey = this.buildKey(key, options?.prefix);
    const data = await redis.get(fullKey);
    if (!data) return null;

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

  async set<T>(key: string, value: T, options?: CacheOptions): Promise<void> {
    const fullKey = this.buildKey(key, options?.prefix);
    const ttl = options?.ttl ?? this.defaultTTL;

    await redis.setex(fullKey, ttl, JSON.stringify(value));
  }

  async getOrSet<T>(
    key: string,
    fetcher: () => Promise<T>,
    options?: CacheOptions
  ): Promise<T> {
    const cached = await this.get<T>(key, options);
    if (cached !== null) return cached;

    const data = await fetcher();
    await this.set(key, data, options);
    return data;
  }

  async invalidate(key: string, prefix?: string): Promise<void> {
    const fullKey = this.buildKey(key, prefix);
    await redis.del(fullKey);
  }

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

  private buildKey(key: string, prefix?: string): string {
    return prefix ? `${prefix}:${key}` : key;
  }
}

export const cache = new CacheService();

キャッシュアサイドパターンの実装

// src/services/product-service.ts
import { cache } from '@/lib/cache';
import { prisma } from '@/lib/db';

export class ProductService {
  async getProduct(id: string) {
    return cache.getOrSet(
      `product:${id}`,
      () => prisma.product.findUnique({
        where: { id },
        include: { category: true, reviews: { take: 10 } },
      }),
      { ttl: 600, prefix: 'products' }
    );
  }

  async getPopularProducts(limit = 20) {
    return cache.getOrSet(
      `popular:${limit}`,
      () => prisma.product.findMany({
        orderBy: { salesCount: 'desc' },
        take: limit,
        include: { category: true },
      }),
      { ttl: 300, prefix: 'products' }
    );
  }

  async updateProduct(id: string, data: UpdateProductInput) {
    const product = await prisma.product.update({
      where: { id },
      data,
    });

    // 関連キャッシュを無効化
    await cache.invalidate(`product:${id}`, 'products');
    await cache.invalidatePattern('products:popular:*');

    return product;
  }
}

HTTP キャッシュヘッダーの設定

// src/middleware.ts
import { NextResponse, NextRequest } from 'next/server';

export function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const path = req.nextUrl.pathname;

  // 静的アセット:長期キャッシュ
  if (path.match(/\.(js|css|png|jpg|svg|woff2)$/)) {
    res.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
  }

  // API:キャッシュなし
  if (path.startsWith('/api/')) {
    res.headers.set('Cache-Control', 'no-store');
  }

  // ページ:短期キャッシュ + ISR
  if (!path.startsWith('/api/') && !path.match(/\.[a-z]+$/)) {
    res.headers.set('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300');
  }

  return res;
}

メモリキャッシュ(アプリケーション内)

小規模なデータにはインメモリキャッシュが有効です。

// src/lib/memory-cache.ts
interface CacheEntry<T> {
  value: T;
  expiresAt: number;
}

export class MemoryCache {
  private store = new Map<string, CacheEntry<unknown>>();
  private maxSize: number;

  constructor(maxSize = 1000) {
    this.maxSize = maxSize;
  }

  get<T>(key: string): T | null {
    const entry = this.store.get(key);
    if (!entry) return null;

    if (Date.now() > entry.expiresAt) {
      this.store.delete(key);
      return null;
    }

    return entry.value as T;
  }

  set<T>(key: string, value: T, ttlMs: number): void {
    // サイズ制限チェック
    if (this.store.size >= this.maxSize) {
      const firstKey = this.store.keys().next().value;
      if (firstKey) this.store.delete(firstKey);
    }

    this.store.set(key, {
      value,
      expiresAt: Date.now() + ttlMs,
    });
  }

  clear(): void {
    this.store.clear();
  }
}

// 設定値などの頻繁にアクセスされるデータに使用
export const configCache = new MemoryCache(100);

キャッシュ戦略の選定ガイド

データの種類推奨キャッシュTTL目安
設定マスタメモリ + Redis1時間
ユーザープロフィールRedis10分
商品一覧Redis + CDN5分
セッション情報Redis24時間
静的アセットCDN1年
APIレスポンスHTTP Cache1分

キャッシュのモニタリング

// キャッシュヒット率の計測
export class CacheMetrics {
  private hits = 0;
  private misses = 0;

  recordHit() { this.hits++; }
  recordMiss() { this.misses++; }

  getHitRate(): number {
    const total = this.hits + this.misses;
    return total === 0 ? 0 : this.hits / total;
  }

  getStats() {
    return {
      hits: this.hits,
      misses: this.misses,
      hitRate: `${(this.getHitRate() * 100).toFixed(1)}%`,
    };
  }
}

まとめ

Claude Codeを使えば、Redisキャッシュ、HTTPキャッシュ、メモリキャッシュなど多層的なキャッシュ戦略を効率的に設計・実装できます。キャッシュの設計方針をCLAUDE.mdに記述しておくと、プロジェクト全体で一貫した実装が得られます。パフォーマンス改善の全体像は生産性を3倍にするTipsも参考にしてください。

Claude Codeの詳細はAnthropic公式ドキュメントをご覧ください。Redisの詳細はRedis公式ドキュメントを参照してください。

#Claude Code #キャッシュ #Redis #パフォーマンス #設計