시스템 설계 기초 — 캐싱·큐·샤딩·CAP
트래픽이 커질 때 꺼내는 4대 도구. 'Twitter를 설계하세요' 면접의 언어.
"유저 1000만 되면 어떻게?"라는 물음에 "Redis 캐시·Kafka 큐·DB 샤딩·읽기 복제본"으로 답할 수 있는 것이 바이브코더의 다음 단계. 규모 확장 사고의 출발점.
규모 확장은 단순 법칙. 느리면 캐시, 동시 처리량 부족하면 큐, 한 DB 감당 못 하면 샤딩/복제, 중단 없어야 하면 다중 AZ/리전.
| 병목 | 해결 | 대표 기술 |
|---|---|---|
| 읽기 반복 | 캐시 | Redis, CDN, 메모리 |
| 쓰기 폭주 | 큐 + 워커 비동기 처리 | Redis streams, Kafka, SQS |
| DB 읽기 부하 | 읽기 복제본(Read Replica) | Postgres streaming replication |
| DB 용량·쓰기 한계 | 샤딩(Sharding) | 수평 분할 (user_id 해시) |
| 단일 장애점 | 다중 AZ/리전 | AWS Multi-AZ, 글로벌 DB |
| 스파이크 | Rate Limiting + 큐 | Redis token bucket |
// ━━━ 1) 캐시 — cache-aside 패턴 (가장 흔함) ━━━
async function getUser(id: string) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached); // 히트
const user = await db.users.find(id); // 미스
await redis.set(`user:${id}`, JSON.stringify(user), "EX", 300); // 5분
return user;
}
// 무효화: 업데이트 시 `redis.del(\`user:\${id}\`)`
// ━━━ 2) 큐 — 이메일·이미지 처리 분리 ━━━
// 웹 핸들러에서는 job만 던지고 즉시 응답
await queue.add("sendWelcomeEmail", { userId: 42 });
return res.json({ ok: true }); // 사용자는 10ms 안에 응답 받음
// 별도 워커 프로세스가 큐를 읽어 실제 이메일 발송
// ━━━ 3) 샤딩 (개념) ━━━
// 유저 ID로 DB 선택
const dbIndex = hash(userId) % 8; // 8개 샤드
const db = shards[dbIndex];
// 샤드를 넘어가는 JOIN은 어려워짐 → 신중한 분할 키 선택 필요cache-aside가 바이브코더 1번 패턴. "DB 조회 → 없으면 가져와서 Redis에 저장". "업데이트 시 del" 잊으면 stale 데이터.
캐시 무효화가 가장 어렵다 (Phil Karlton 유명 명언: "컴퓨터 과학에서 어려운 건 네이밍·캐시 무효화·off-by-one"). 해법 3가지: ① TTL(시간 경과 자동 만료, 게으름) ② 이벤트 기반(DB 변경 시 del) ③ 버전 키(`user:v3:42`).
CAP 정리 다시 보기(ch22-4 연결). 분산 시스템은 Consistency · Availability · Partition 내성 중 둘만 선택. 샤딩·복제를 시작하면 CAP 선택이 현실이 된다. 대부분의 AP 시스템은 결국 일관(eventual consistency) — "지금은 다를 수 있지만 나중엔 같아진다".
Rate Limiting(속도 제한) — API 남용·DDoS 방어 필수. 알고리즘 4가지: Token Bucket(가장 흔함) · Leaky Bucket · Fixed Window · Sliding Window Log. Redis 한 줄이면 구현 가능.
// Rate Limit — Redis INCR + EXPIRE (Fixed Window)
async function rateLimit(key: string, limit: number, windowSec: number) {
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, windowSec);
if (count > limit) throw new Error("Too Many Requests");
}
// 사용
app.use(async (req, res, next) => {
try {
await rateLimit(`rl:${req.ip}`, 100, 60); // IP당 분당 100회
next();
} catch {
res.status(429).json({ error: "rate limited" });
}
});
// 정교한 슬라이딩 윈도우는 @upstash/ratelimit 등 라이브러리 사용"로그인 Rate Limit"(ch24-4 OWASP) 실전 구현. Upstash는 서버리스 환경용 Redis.
면접형 시스템 설계 5단계. ① 요구사항 명확화(QPS·데이터 크기·SLA) ② 대략 크기 계산(스토리지·대역폭) ③ 컴포넌트 다이어그램(LB·App·Cache·DB·Queue) ④ 병목 식별 → 캐시/샤딩/큐로 해결 ⑤ 실패 모드·관측성. 이 절차가 있으면 AI와의 설계 대화도 체계적이 된다.
"DB 조회 → 없으면 가져와 Redis에 저장"하는 가장 흔한 캐시 패턴의 이름은?
DB 용량·쓰기 한계를 넘기 위해 데이터를 여러 DB로 수평 분할하는 기법은?
API 남용을 막기 위해 IP당 요청 수를 제한하는 기법의 일반 용어는?