OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 42 / 90
stack-analysis
CHAPTER 42 / 90
읽기 약 2
FUNCTION

Redis 패턴: 캐시/세션/큐/Pub-Sub


핵심 개념

5가지 사용 패턴·TTL 전략·Cache Aside·Write Through — 실전 활용.

본문

5가지 핵심 패턴

📋 코드 (5줄)
1. 캐시 (가장 흔함)
2. 세션 저장소
3. 작업 큐 (BullMQ)
4. Pub/Sub (실시간 알림)
5. Rate Limit 카운터

1. 캐시 — Cache Aside

TYPESCRIPT📋 코드 (27줄)
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);

async function getUser(id: string): Promise<User> {
  const cacheKey = `user:${id}`;

  // 1. 캐시 확인
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // 2. DB 조회
  const user = await db.user.findUnique({ where: { id } });
  if (!user) throw new NotFoundError();

  // 3. 캐시에 저장 (5분 TTL)
  await redis.setex(cacheKey, 300, JSON.stringify(user));

  return user;
}


async function updateUser(id: string, data: Partial<User>) {
  const user = await db.user.update({ where: { id }, data });
  // 캐시 무효화
  await redis.del(`user:${id}`);
  return user;
}

TTL 전략

TYPESCRIPT📋 코드 (15줄)
const TTL = {
  USER:           60 * 5,         // 5분 — 자주 변경
  PRODUCT:        60 * 60,        // 1시간 — 가끔 변경
  ARTICLE:        60 * 60 * 24,   // 1일
  STATIC_CONFIG:  60 * 60 * 24 * 7, // 1주
};


// 캐시 stampede 방지 — jitter
function withJitter(ttl: number) {
  return ttl + Math.floor(Math.random() * (ttl * 0.1));
  // ±10% 무작위 → 동시 만료 방지
}

await redis.setex(key, withJitter(TTL.USER), value);

2. 세션 저장소

TYPESCRIPT📋 코드 (19줄)
import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(session({
  store: new RedisStore({ client: redis, prefix: 'sess:' }),
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    maxAge: 1000 * 60 * 60 * 24 * 7,  // 7일
  },
}));


// 사용
req.session.userId = user.id;
const userId = req.session.userId;

3. Rate Limit (분산 카운터)

TYPESCRIPT📋 코드 (21줄)
async function checkRateLimit(userId: string, max: number, windowSec: number) {
  const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSec)}`;
  const count = await redis.incr(key);

  if (count === 1) {
    await redis.expire(key, windowSec);
  }

  return { allowed: count <= max, count, max };
}


// 사용
app.post('/api/messages', authenticate, async (req, res, next) => {
  const { allowed, count, max } = await checkRateLimit(req.user!.id, 60, 60);
  if (!allowed) {
    return res.status(429).json({ error: 'Too many', retryAfter: 60 });
  }
  res.setHeader('X-RateLimit-Remaining', max - count);
  next();
});

4. Pub/Sub — 실시간 알림

TYPESCRIPT📋 코드 (18줄)
const publisher = new Redis(process.env.REDIS_URL!);
const subscriber = new Redis(process.env.REDIS_URL!);

// 발행
async function notifyUser(userId: string, event: any) {
  await publisher.publish(`user:${userId}`, JSON.stringify(event));
}


// 구독 (각 서버 인스턴스에서)
subscriber.psubscribe('user:*');
subscriber.on('pmessage', (pattern, channel, message) => {
  const userId = channel.split(':')[1];
  const event = JSON.parse(message);

  // Socket.io로 클라이언트에 전달
  io.to(`user:${userId}`).emit('notification', event);
});

5. 분산 락

TYPESCRIPT📋 코드 (32줄)
async function acquireLock(key: string, ttlMs: number, retries = 5): Promise<string | null> {
  const lockId = crypto.randomUUID();

  for (let i = 0; i < retries; i++) {
    const ok = await redis.set(`lock:${key}`, lockId, 'PX', ttlMs, 'NX');
    if (ok) return lockId;
    await new Promise(r => setTimeout(r, 100));
  }
  return null;
}

async function releaseLock(key: string, lockId: string) {
  // Lua 스크립트로 원자적 해제
  await redis.eval(
    `if redis.call('GET', KEYS[1]) == ARGV[1] then
       return redis.call('DEL', KEYS[1])
     else return 0 end`,
    1, `lock:${key}`, lockId
  );
}


// 사용 — 동시 결제 방지
async function processPayment(orderId: string) {
  const lockId = await acquireLock(`order:${orderId}`, 30000);
  if (!lockId) throw new Error('Already processing');
  try {
    return await actualPayment(orderId);
  } finally {
    await releaseLock(`order:${orderId}`, lockId);
  }
}

자료구조 활용

TYPESCRIPT📋 코드 (15줄)
// Sorted Set — 리더보드
await redis.zincrby('leaderboard', 10, 'user:123');
const top10 = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');

// Hash — 객체
await redis.hset('user:123', 'name', 'Alice', 'email', 'a@x.com');
const user = await redis.hgetall('user:123');

// List — 큐 (간단한 케이스)
await redis.lpush('queue:emails', JSON.stringify(job));
const job = await redis.brpop('queue:emails', 0);

// Set — 중복 제거
await redis.sadd('online_users', userId);
const isOnline = await redis.sismember('online_users', userId);

다음 챕터

CH.43 "MongoDB Aggregation Pipeline 실전".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 코드의 Redis 패턴 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

Redis 패턴 관련 인기 라이브러리/패턴 5개를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 Redis 패턴
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 백엔드 시장의
Redis 패턴 트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
Redis 패턴: 캐시/세션/큐/Pub-Sub 이 3가지만 확실히 잡으세요
1.Cache Aside = 80% 케이스 — DB 조회 후 Redis 저장
2.TTL + jitter로 동시 만료 방지 — cache stampede 회피
3.분산 락은 SET NX EX + Lua 해제 — 결제·중복 작업 방지


공유하기
진행도 42 / 90