Redis 캐싱 with Claude Code: Cache-Aside, Write-Through 및 TTL 전략

발행: (2026년 3월 11일 PM 01:57 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

위에 제공된 Source 라인 외에 번역할 텍스트를 알려주시면, 해당 내용을 한국어로 번역해 드리겠습니다.

캐시 규칙

Redis 캐시 설계 규칙

패턴

  • Cache-Aside: 읽기 많고 쓰기 적은 데이터 (사용자 프로필, 제품 카탈로그)
  • Write-Through: 강한 일관성이 필요한 데이터 (잔액, 재고)
  • Pub/Sub: 캐시 무효화 전파 (분산 환경)

TTL (필수)

  • 모든 캐시는 TTL을 가져야 함 (영구 캐시 없음)
  • 마스터 데이터: TTL 1시간
  • 세션/프로필: TTL 15분
  • 집계: TTL 5분

캐시 키

  • 형식: {service}:{entity}:{id}
  • 예시: user:profile:123, product:detail:abc
  • 버전 관리: user:v2:profile:123 (스키마 변경 시 버전 증가)

모니터링

  • 캐시 적중률을 메트릭으로 추적
  • 콜드 스타트(예열되지 않은 캐시) 시나리오를 고려한 설계

캐시 어사이드 패턴

요구 사항

  • 캐시 키: user:profile:{userId}
  • TTL: 15 분
  • 캐시 미스 시: DB에서 가져와 캐시에 채움
  • 프로필 업데이트 시: 캐시 무효화
  • 캐시 히트/미스 로그 기록

Generated Service (src/services/userCacheService.ts)

// src/services/userCacheService.ts
import { createClient } from 'redis';
import { prisma } from '../prisma/client';
import { logger } from '../logger';

const redis = createClient({ url: process.env.REDIS_URL });
const CACHE_TTL = 15 * 60; // seconds
const KEY_PREFIX = 'user:profile';

function cacheKey(userId: string): string {
  return `${KEY_PREFIX}:${userId}`;
}

export async function getUserProfile(userId: string): Promise {
  const key = cacheKey(userId);
  const cached = await redis.get(key);

  if (cached) {
    logger.debug({ userId, cacheHit: true }, 'User profile cache hit');
    return JSON.parse(cached) as UserProfile;
  }

  logger.debug({ userId, cacheHit: false }, 'User profile cache miss');

  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: {
      id: true,
      name: true,
      email: true,
      avatarUrl: true,
      updatedAt: true,
    },
  });

  if (!user) return null;

  await redis.set(key, JSON.stringify(user), { EX: CACHE_TTL });
  return user;
}

export async function invalidateUserProfile(userId: string): Promise {
  await redis.del(cacheKey(userId));
  logger.info({ userId }, 'User profile cache invalidated');
}

Invalidate on Update

// src/services/userService.ts
export async function updateUserProfile(userId: string, data: UpdateProfileInput) {
  const updated = await prisma.user.update({ where: { id: userId }, data });
  await invalidateUserProfile(userId);
  return updated;
}

쓰루 패턴

// src/services/inventoryCacheService.ts
import { prisma } from '../prisma/client';
import { redis } from '../redis/client';
import { logger } from '../logger';

// Inventory: consistency‑critical → Write‑Through
export async function updateInventory(productId: string, quantity: number): Promise {
  await prisma.$transaction(async (tx) => {
    await tx.inventory.update({ where: { productId }, data: { quantity } });
  });

  // After DB success, update cache too (Write‑Through)
  await redis.set(
    `inventory:${productId}`,
    JSON.stringify({ quantity, updatedAt: new Date() }),
    { EX: 5 * 60 } // 5 minutes TTL
  );

  logger.debug({ productId, quantity }, 'Inventory cache updated (write‑through)');
}

분산 캐시 무효화 (Pub/Sub)

// src/cache/pubsub.ts
import { redisPublisher as publisher, redisSubscriber as subscriber } from '../redis/client';
import { logger } from '../logger';

// Publish cache invalidation to other servers
export async function publishCacheInvalidation(channel: string, key: string): Promise {
  await publisher.publish(channel, JSON.stringify({ key, timestamp: Date.now() }));
}

// Subscribe and act on invalidation messages
export async function subscribeCacheInvalidation(): Promise {
  await subscriber.subscribe('cache:invalidate', async (message) => {
    const { key } = JSON.parse(message);
    await redis.del(key);
    logger.info({ key }, 'Cache invalidated via pub/sub');
  });
}

요약

  • CLAUDE.md는 모든 캐시의 TTL을 적용하고, 표준 키 형식 및 패턴 선택 기준을 강제합니다.
  • Cache‑Aside는 읽기 부하를 감소시키며, 데이터를 신선하게 유지하기 위해 쓰기 시 무효화합니다.
  • Write‑Through는 강한 일관성을 위해 DB 쓰기와 동시에 캐시를 원자적으로 업데이트합니다.
  • Pub/Sub는 분산 서버 간에 캐시 무효화를 전파합니다.

보다 깊은 코드 리뷰(TTL 간격, 스탬프ede 위험, 일관성 검사 포함)를 원하시면 Code Review Pack을 확인하세요: prompt-works.jp

Claude 코드 엔지니어는 성능 및 캐싱에 중점을 두었습니다.

0 조회
Back to Blog

관련 글

더 보기 »