topic난이도 · 약 25

시스템 설계 기초 — 캐싱·큐·샤딩·CAP

트래픽이 커질 때 꺼내는 4대 도구. 'Twitter를 설계하세요' 면접의 언어.

#캐시##샤딩#CAP#Rate Limit#시스템설계
왜 배우는가

"유저 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
typescript
// ━━━ 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 한 줄이면 구현 가능.

typescript
// 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와의 설계 대화도 체계적이 된다.

실기 드릴 3문항
edit실기 드릴 · 단답형

"DB 조회 → 없으면 가져와 Redis에 저장"하는 가장 흔한 캐시 패턴의 이름은?

edit실기 드릴 · 단답형

DB 용량·쓰기 한계를 넘기 위해 데이터를 여러 DB로 수평 분할하는 기법은?

edit실기 드릴 · 단답형

API 남용을 막기 위해 IP당 요청 수를 제한하는 기법의 일반 용어는?