stack-analysis
CHAPTER 42 / 90
읽기 약 2분
FUNCTION
Redis 패턴: 캐시/세션/큐/Pub-Sub
핵심 개념
5가지 사용 패턴·TTL 전략·Cache Aside·Write Through — 실전 활용.
본문
5가지 핵심 패턴
1. 캐시 (가장 흔함)
2. 세션 저장소
3. 작업 큐 (BullMQ)
4. Pub/Sub (실시간 알림)
5. Rate Limit 카운터1. 캐시 — Cache Aside
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 전략
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. 세션 저장소
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 (분산 카운터)
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 — 실시간 알림
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. 분산 락
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);
}
}자료구조 활용
// 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