Skip to content

Split View: 시스템 디자인 면접 완전 가이드 2025: 대규모 시스템 설계, 확장성, 가용성 패턴

✨ Learn with Quiz
|

시스템 디자인 면접 완전 가이드 2025: 대규모 시스템 설계, 확장성, 가용성 패턴

서론

시스템 디자인 면접은 시니어 엔지니어 채용에서 가장 중요한 평가 항목입니다. 알고리즘 문제가 코딩 능력을 측정한다면, 시스템 디자인은 대규모 시스템을 설계하고 트레이드오프를 이해하는 종합적인 엔지니어링 역량을 평가합니다.

Google, Meta, Amazon, Netflix 등 빅테크 기업들은 4560분 동안 수백만수억 명의 사용자를 처리할 수 있는 시스템을 설계하도록 요구합니다. 이 면접에서 성공하려면 단순히 기술을 아는 것이 아니라, 요구사항을 분석하고 적절한 기술을 선택하며 트레이드오프를 명확히 설명할 수 있어야 합니다.

이 가이드에서는 시스템 디자인 면접의 4단계 프레임워크부터 확장성, 캐싱, 데이터베이스, 메시지 큐, 실전 설계 예제까지 체계적으로 다룹니다.


1. 시스템 디자인 면접 4단계 프레임워크

1.1 1단계: 요구사항 명확화 (5분)

면접에서 가장 중요한 첫 단계입니다. 바로 설계에 들어가는 것은 가장 흔한 실수입니다.

기능적 요구사항 (Functional Requirements):

  • 핵심 기능은 무엇인가?
  • 사용자가 수행할 주요 작업은?
  • 입력과 출력은 무엇인가?

비기능적 요구사항 (Non-Functional Requirements):

  • 예상 사용자 수 / DAU
  • 읽기 대 쓰기 비율
  • 지연 시간 요구사항
  • 가용성 요구 수준 (99.9%? 99.99%?)
  • 데이터 일관성 요구 수준
면접관: "URL 단축 서비스를 설계해 주세요."

좋은 질문 예시:
Q: "일간 활성 사용자(DAU)는 어느 정도인가요?"
A: "1억 명입니다."

Q: "URL 단축과 리디렉션 중 어느 것이 더 중요한가요?"
A: "리디렉션이 더 중요합니다. 읽기가 훨씬 많습니다."

Q: "단축 URL의 길이에 제한이 있나요?"
A: "가능한 한 짧게요."

Q: "커스텀 URL을 지원해야 하나요?"
A: "네, 선택적으로 지원해 주세요."

1.2 2단계: 상위 수준 설계 (15~20분)

요구사항을 바탕으로 전체 시스템의 큰 그림을 그립니다.

┌──────────┐     ┌──────────────┐     ┌──────────────┐
Client  │────▶│ Load Balancer│────▶│  Web Server└──────────┘     └──────────────┘     └──────┬───────┘
                    ┌────────────────────────┤
                    ▼                        ▼
             ┌──────────┐            ┌──────────────┐
Cache   │            │  Database             └──────────┘            └──────────────┘

이 단계에서 다뤄야 할 것:

  • API 설계 (RESTful 엔드포인트)
  • 데이터 모델 설계
  • 주요 컴포넌트와 상호 관계
  • 데이터 흐름

1.3 3단계: 상세 설계 (15~20분)

면접관이 관심 있는 특정 컴포넌트를 깊이 파고듭니다.

  • 데이터베이스 스키마와 인덱싱 전략
  • 캐싱 전략과 무효화
  • 확장성 병목 지점과 해결 방안
  • 장애 처리와 복구 방안

1.4 4단계: 마무리 (5분)

  • 설계의 병목 지점 식별
  • 향후 확장 방향 논의
  • 모니터링/알림 전략
  • 추가 기능 제안

2. 확장성 (Scalability)

2.1 수직 확장 (Vertical Scaling)

기존 서버의 하드웨어 사양을 올리는 방식입니다.

수직 확장 전:           수직 확장 후:
┌─────────────┐        ┌─────────────┐
4 CPU      │        │  32 CPU│  16GB RAM   │   ──▶  │  256GB RAM│  500GB SSD  │        │  4TB SSD└─────────────┘        └─────────────┘

장점: 구현이 간단, 데이터 일관성 유지 쉬움, 소프트웨어 변경 불필요

단점: 물리적 한계 존재 (단일 서버의 성능 상한), 단일 장애 지점(SPOF), 비용이 지수적으로 증가

2.2 수평 확장 (Horizontal Scaling)

서버 수를 늘려 부하를 분산하는 방식입니다.

수평 확장:
                    ┌─────────────┐
              ┌────▶│  Server 1              │     └─────────────┘
┌──────────┐  │     ┌─────────────┐
Load   │──┼────▶│  Server 2Balancer │  │     └─────────────┘
└──────────┘  │     ┌─────────────┐
              ├────▶│  Server 3              │     └─────────────┘
              │     ┌─────────────┐
              └────▶│  Server N                    └─────────────┘

장점: 이론적으로 무제한 확장 가능, 장애 격리, 점진적 확장

단점: 데이터 일관성 관리 복잡, 세션 관리 필요, 운영 복잡도 증가

2.3 Stateless 서비스 설계

수평 확장의 핵심은 서버를 Stateless로 만드는 것입니다.

Bad (Stateful):
┌──────────┐  session  ┌──────────┐
Client  │──────────▶│ Server A   (Server A가 죽으면 세션 유실)
└──────────┘           └──────────┘

Good (Stateless):
┌──────────┐     ┌──────────┐     ┌──────────┐
Client  │────▶│   Any    │────▶│  Redis└──────────┘     │  Server (Session)                 └──────────┘     └──────────┘

세션, 캐시, 상태 정보를 외부 저장소(Redis, Memcached)에 보관하면 어떤 서버든 동일하게 요청을 처리할 수 있습니다.

2.4 Auto-Scaling 전략

# AWS Auto Scaling 설정 예시
AutoScalingGroup:
  MinSize: 2
  MaxSize: 20
  DesiredCapacity: 4
  TargetTrackingScaling:
    TargetValue: 70.0          # CPU 70% 유지
    PredefinedMetricType: ASGAverageCPUUtilization

  # 스케줄 기반 스케일링 (예측 가능한 트래픽)
  ScheduledActions:
    - Schedule: "0 8 * * MON-FRI"   # 평일 오전 8시
      MinSize: 6
    - Schedule: "0 22 * * *"        # 매일 오후 10시
      MinSize: 2
전략기반적합한 상황
Target Tracking메트릭 목표값CPU, 요청 수 기반
Step Scaling임계값 단계급격한 트래픽 변화
Scheduled시간 기반예측 가능한 패턴
PredictiveML 기반반복적 패턴

3. 로드 밸런싱 (Load Balancing)

3.1 로드 밸런싱 알고리즘

1. Round Robin (순차 배분)
   Request 1Server A
   Request 2Server B
   Request 3Server C
   Request 4Server A  (다시 처음부터)

2. Weighted Round Robin (가중 순차)
   Server A (weight=5): 5개 요청 처리
   Server B (weight=3): 3개 요청 처리
   Server C (weight=2): 2개 요청 처리

3. Least Connections (최소 연결)
   Server A: 10 connections ← 새 요청은 여기로
   Server B: 25 connections
   Server C: 18 connections

4. IP Hash (IP 기반 분배)
   hash(client_ip) % server_count = target_server
   → 같은 클라이언트는 항상 같은 서버로

3.2 L4 vs L7 로드 밸런서

L4 (전송 계층):
┌────────┐    TCP/UDP     ┌──────────┐
Client │──────────────▶│ L4 LB    │── IP:Port 기반 분배
└────────┘               └──────────┘
  속도 빠름, 단순함, SSL 오프로드 불가

L7 (애플리케이션 계층):
┌────────┐    HTTP/HTTPS   ┌──────────┐
Client │──────────────▶│ L7 LB    │── URL, Header, Cookie 기반 분배
└────────┘               └──────────┘
  콘텐츠 기반 라우팅, SSL 종료, 더 유연함
특성L4L7
계층TCP/UDPHTTP/HTTPS
성능매우 빠름상대적으로 느림
라우팅IP/PortURL, Header, Cookie
SSL 종료불가가능
사용 예AWS NLBAWS ALB, Nginx

3.3 Consistent Hashing

일반적인 해싱은 서버가 추가/제거될 때 거의 모든 키가 재배치됩니다. Consistent Hashing은 이 문제를 해결합니다.

일반 해싱:
hash(key) % 3 = 서버 번호
→ 서버 추가시: hash(key) % 4 = 대부분 다른 서버로!

Consistent Hashing (해시 링):

        Server A
        /      \
       /   0    \
      /          \
  Server D     Server B
      \          /
       \   key  /
        \  ↓  /
        Server C

→ 서버 추가시: 인접한 키만 이동 (전체의 약 1/N)

가상 노드(Virtual Nodes)로 균등 분배:

class ConsistentHash:
    def __init__(self, nodes, virtual_nodes=150):
        self.ring = SortedDict()
        self.virtual_nodes = virtual_nodes

        for node in nodes:
            for i in range(virtual_nodes):
                key = self._hash(f"node-vn-i")
                self.ring[key] = node

    def get_node(self, data_key):
        if not self.ring:
            return None
        hash_val = self._hash(data_key)
        # 해시 링에서 시계 방향으로 가장 가까운 노드
        idx = self.ring.bisect_right(hash_val)
        if idx == len(self.ring):
            idx = 0
        return self.ring.peekitem(idx)[1]

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

4. 캐싱 (Caching)

4.1 캐싱 전략

1. Cache-Aside (Lazy Loading):
   ┌────────┐  1.읽기  ┌───────┐
App   │────────▶│ Cache   └────┬───┘  miss   └───────┘
2.DB 읽기      ▲
3.캐시 저장
   ┌────────┐
DB   └────────┘

2. Write-Through:
   ┌────────┐  1.쓰기  ┌───────┐  2.동기 쓰기  ┌────────┐
App   │────────▶│ Cache │─────────────▶│   DB   └────────┘         └───────┘              └────────┘
   (항상 일관성 보장, 쓰기 지연 증가)

3. Write-Back (Write-Behind):
   ┌────────┐  1.쓰기  ┌───────┐  2.비동기 배치 쓰기  ┌────────┐
App   │────────▶│ Cache │ ═════════════════▶│   DB   └────────┘         └───────┘                   └────────┘
   (쓰기 성능 우수, 데이터 유실 위험)

4. Write-Around:
   ┌────────┐  1.쓰기  ┌────────┐
App   │────────▶│   DB   └────┬───┘         └────────┘
2.읽기시 캐시 로드
   ┌───────┐
Cache   └───────┘

4.2 Redis vs Memcached

# Redis - 다양한 데이터 구조 지원
import redis

r = redis.Redis(host='localhost', port=6379)

# String
r.set('user:1001:name', 'Alice', ex=3600)  # 1시간 TTL

# Hash
r.hset('user:1001', mapping={
    'name': 'Alice',
    'email': 'alice@example.com',
    'login_count': 42
})

# Sorted Set (리더보드)
r.zadd('leaderboard', {'player_a': 1500, 'player_b': 1800})
top_10 = r.zrevrange('leaderboard', 0, 9, withscores=True)

# Pub/Sub
r.publish('notifications', 'New message!')
특성RedisMemcached
데이터 구조String, Hash, List, Set, Sorted Set 등Key-Value만
지속성RDB/AOF 지원없음
복제Master-Replica없음
클러스터Redis Cluster클라이언트 측 샤딩
메모리 효율상대적으로 낮음높음 (단순 구조)
사용 사례세션, 리더보드, 큐, Pub/Sub단순 캐싱

4.3 CDN (Content Delivery Network)

CDN이 없는 경우:
사용자(서울) ──── 5000km ────▶ Origin Server(미국)
RTT: ~200ms

CDN이 있는 경우:
사용자(서울) ──── 50km ────▶ CDN Edge(서울) ── cache hit
RTT: ~5ms

Cache Miss시:
사용자(서울)CDN Edge(서울)Origin(미국)
                ↓ 캐시 저장
         다음 요청부터 Edge에서 응답

CDN 캐시 무효화 전략:

1. TTL 기반: Cache-Control: max-age=86400
2. 버전 관리: /static/app.v2.3.1.js
3. 수동 무효화: CloudFront Invalidation
4. 태그 기반: Surrogate-Key 헤더로 선택적 무효화

4.4 캐시 문제와 해결

1. Cache Stampede (캐시 쓰나미):
   TTL 만료 → 수천 요청이 동시에 DB   해결: 뮤텍스 락 + 사전 갱신

2. Cache Penetration (캐시 관통):
   존재하지 않는 키 반복 조회 → 매번 DB 조회
   해결: Bloom Filter + 빈 결과도 캐싱

3. Hot Key Problem:
   특정 키에 트래픽 집중 → 단일 캐시 노드 과부하
   해결: 로컬 캐시 +복제 (key_1, key_2, ...)

5. 데이터베이스 설계

5.1 SQL vs NoSQL

기준SQL (RDBMS)NoSQL
데이터 모델정규화된 테이블Document, Key-Value, Column, Graph
스키마고정 스키마유연한 스키마
확장 방식수직 확장 위주수평 확장 용이
트랜잭션ACID 완벽 지원제한적 (일부 지원)
조인효율적 지원제한적/불가
적합한 경우관계 복잡, 트랜잭션 중요대용량, 유연한 스키마, 고성능
대표 제품PostgreSQL, MySQLMongoDB, Cassandra, DynamoDB
SQL 선택 기준:
- 데이터 관계가 복잡하고 조인이 빈번
- ACID 트랜잭션이 필수 (금융, 결제)
- 데이터 무결성이 최우선
- 스키마가 안정적

NoSQL 선택 기준:
- 데이터 구조가 자주 변경
- 초대량 데이터 (TB 이상)
- 매우 낮은 지연 시간 필요
- 수평 확장이 필수
- 비정형 데이터 (로그, 소셜 미디어)

5.2 데이터베이스 복제 (Replication)

Master-Slave 복제:
┌──────────┐     동기/비동기 복제     ┌──────────┐
Master  │ ═══════════════════▶│  Slave 1 │  ← 읽기 전용
 (Write)  │ ═══════════════════▶│  Slave 2 │  ← 읽기 전용
└──────────┘                     └──────────┘

Multi-Master 복제:
┌──────────┐ ◀═══════════════▶ ┌──────────┐
Master 1 │    양방향 복제     │ Master 2 (R/W) (R/W)└──────────┘                   └──────────┘
(충돌 해결 필요)

5.3 데이터베이스 샤딩 (Sharding)

샤딩 전:
┌─────────────────────┐
Single Database│   10TB, 지연 증가    │
└─────────────────────┘

샤딩  (Hash 기반):
hash(user_id) % 4 = shard_number

┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
Shard 0 │ │ Shard 1 │ │ Shard 2 │ │ Shard 32.5TB   │ │ 2.5TB   │ │ 2.5TB   │ │ 2.5TB   │
└─────────┘ └─────────┘ └─────────┘ └─────────┘

샤딩 전략 비교:

전략장점단점
Range-based범위 쿼리 효율적핫스팟 발생 가능
Hash-based균등 분배범위 쿼리 비효율
Directory-based유연한 매핑디렉토리가 SPOF
Geographic지역성 활용지역 간 쿼리 어려움

샤딩 시 고려사항:

1. 조인 쿼리: 샤드 간 조인 불가 → 비정규화 또는 애플리케이션 레벨 조인
2. 리밸런싱: 데이터 증가시 샤드 재분배 → Consistent Hashing 활용
3. 핫스팟: 특정 샤드에 트래픽 집중 → 샤드 키 재설계
4. 트랜잭션: 분산 트랜잭션 → Saga 패턴 또는 2PC
5. 고유 ID: 글로벌 유니크 ID 필요 → Snowflake ID, UUID

5.4 비정규화 (Denormalization)

-- 정규화된 설계 (조인 필요):
SELECT u.name, o.total, p.name AS product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id;

-- 비정규화된 설계 (단일 테이블):
-- orders 테이블에 user_name, product_name 포함
SELECT user_name, total, product_name
FROM denormalized_orders
WHERE user_id = 1001;

비정규화는 읽기 성능을 향상시키지만, 데이터 중복과 쓰기 복잡성을 증가시킵니다. 읽기가 월등히 많은 경우에 적합합니다.


6. CAP 정리와 PACELC

6.1 CAP Theorem

       Consistency (일관성)
         /          \
        /            \
       /     CA      \
      /  (single DC)  \
     /________________\
    |                  |
 CP |    분산 시스템    | AP
    |  P는 피할 수 없음  |
    |__________________|
         Availability
         (가용성)

네트워크 파티션 발생시:
CP 시스템 → 일관성 유지, 일부 요청 거부
AP 시스템 → 가용성 유지, 일시적 불일치 허용

실제 시스템의 CAP 선택:

시스템선택이유
은행 계좌CP잔액 불일치 불가
DNSAP약간 오래된 레코드 허용
MongoDBCP강한 일관성 기본
CassandraAP결국 일관성 (Eventual)
ZooKeeperCP분산 코디네이션

6.2 PACELC 이론

CAP을 확장한 PACELC은 파티션이 없을 때의 트레이드오프도 설명합니다.

if (Partition) then
    Choose between Availability and Consistency
else
    Choose between Latency and Consistency

예시:
- DynamoDB: PA/EL (가용성 + 낮은 지연)
- MongoDB:  PC/EC (일관성 우선)
- Cassandra: PA/EL (가용성 + 낮은 지연)
- PostgreSQL(single): PC/EC (일관성 + 일관성)

7. 메시지 큐 (Message Queue)

7.1 비동기 처리의 필요성

동기 처리:
ClientAPI → 이메일 전송(3s) → 이미지 리사이즈(2s) → 로그 저장(1s)Response
6초 대기

비동기 처리:
ClientAPIMessage QueueResponse (즉시)
             ┌──────┴──────┐
             ▼             ▼
       Email Worker   Image Worker
         (3s)           (2s)
0.1초 대기

7.2 Kafka vs RabbitMQ vs SQS

Apache Kafka:
┌──────────┐     ┌─────────────────────────┐
Producer │────▶│  Topic: orders          │
└──────────┘     │  ┌─────┬─────┬─────┐   │
                 │  │ P0P1P2  │   │
                 │  └──┬──┴──┬──┴──┬──┘   │
                 └─────┼─────┼─────┼──────┘
                       ▼     ▼     ▼
                 ┌──────────────────────┐
Consumer Group A                   (각 파티션 1 소비자)                 └──────────────────────┘
특성KafkaRabbitMQSQS
모델로그 기반 스트리밍AMQP 메시지 브로커관리형 큐
처리량초당 수백만 메시지초당 수만 메시지자동 확장
메시지 보존설정 기간 보존소비 후 삭제14일
순서 보장파티션 내 보장큐 단위 보장FIFO 큐 사용시
소비 모델Pull (Consumer Poll)Push + PullPull (Long Polling)
사용 사례이벤트 스트리밍, 로그작업 큐, RPC서버리스, 디커플링

7.3 메시지 전달 보장

1. At-Most-Once (최대 1):
   ProducerBroker (ACK 안 기다림)
   빠르지만 메시지 유실 가능
   사용: 로그, 메트릭 (유실 허용)

2. At-Least-Once (최소 1):
   ProducerBrokerACK
   ACK 실패시 재전송 → 중복 가능
   사용: 대부분의 시스템 (멱등 처리 필요)

3. Exactly-Once (정확히 1):
   트랜잭션 기반 또는 멱등 키 사용
   성능 오버헤드 있음
   사용: 금융, 결제 (정확성 필수)

8. 레이트 리미팅 (Rate Limiting)

8.1 Token Bucket 알고리즘

import time
import threading

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity        # 버킷 최대 토큰 수
        self.tokens = capacity           # 현재 토큰 수
        self.refill_rate = refill_rate   # 초당 추가 토큰
        self.last_refill = time.time()
        self.lock = threading.Lock()

    def allow_request(self):
        with self.lock:
            now = time.time()
            # 토큰 보충
            elapsed = now - self.last_refill
            self.tokens = min(
                self.capacity,
                self.tokens + elapsed * self.refill_rate
            )
            self.last_refill = now

            # 요청 허용 여부
            if self.tokens >= 1:
                self.tokens -= 1
                return True
            return False

# 초당 10개 요청, 버스트 최대 20개
limiter = TokenBucket(capacity=20, refill_rate=10)

8.2 Sliding Window Log 알고리즘

import time
from collections import deque

class SlidingWindowLog:
    def __init__(self, window_size, max_requests):
        self.window_size = window_size   # 윈도우 크기 (초)
        self.max_requests = max_requests
        self.requests = deque()

    def allow_request(self):
        now = time.time()
        # 윈도우 밖의 오래된 요청 제거
        while self.requests and self.requests[0] < now - self.window_size:
            self.requests.popleft()

        if len(self.requests) < self.max_requests:
            self.requests.append(now)
            return True
        return False

# 1분에 최대 100개 요청
limiter = SlidingWindowLog(window_size=60, max_requests=100)

8.3 Sliding Window Counter

Sliding Window Counter (메모리 효율적):

이전 윈도우 카운트: 84
현재 윈도우 카운트: 36
윈도우 크기: 60현재 위치: 윈도우의 25% 지점

가중 카운트 = 84 * (1 - 0.25) + 36 = 84 * 0.75 + 36 = 99

max_requests = 100허용 (99 < 100)
알고리즘메모리정확도구현 복잡도
Token BucketO(1)높음낮음
Leaky BucketO(1)높음낮음
Sliding Window LogO(N)매우 높음중간
Sliding Window CounterO(1)근사치중간
Fixed WindowO(1)낮음매우 낮음

9. 실전 설계 예제

9.1 URL Shortener 설계

요구사항:

  • 1억 DAU, 읽기:쓰기 = 100:1
  • 단축 URL은 가능한 짧게 (7자)
  • 커스텀 URL 지원
  • 리디렉션 지연 시간 100ms 이하

Back-of-Envelope 계산:

쓰기: 1DAU * 평균 0.1 URL/day = 1000/day
QPS(쓰기) = 10,000,000 / 86,400 = ~116 QPS
QPS(읽기) = 116 * 100 = ~11,600 QPS
피크 QPS = 11,600 * 3 = ~35,000 QPS

저장 용량 (5):
10,000,000 * 365 * 5 = 182.5억 레코드
각 레코드 ~500 bytes → 182.5* 500B = ~9.1TB

시스템 아키텍처:

┌────────┐     ┌──────┐     ┌─────────────┐
Client │────▶│  LB  │────▶│  API Server└────────┘     └──────┘     └──────┬──────┘
                    ┌──────────────┼──────────────┐
                    ▼              ▼              ▼
              ┌──────────┐  ┌──────────┐  ┌──────────┐
Redis   │  │ MySQL    │  │ Analytics│
Cache (Sharded)  (Kafka)              └──────────┘  └──────────┘  └──────────┘

URL 인코딩 전략:

import hashlib
import base64

def generate_short_url(long_url, counter):
    # 방법 1: Base62 인코딩 (auto-increment ID)
    chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    short = ""
    n = counter
    while n > 0:
        short = chars[n % 62] + short
        n //= 62
    return short  # 예: "dK4x9Z"

    # 방법 2: MD5 해시 + Base62 (처음 7자)
    hash_val = hashlib.md5(long_url.encode()).hexdigest()
    num = int(hash_val[:12], 16)
    short = ""
    for _ in range(7):
        short = chars[num % 62] + short
        num //= 62
    return short

# 62^7 = 약 3.5조 개의 고유 URL 가능

9.2 Twitter/X 피드 설계

핵심 문제: Fan-out

Fan-out on Write (Push 모델):
사용자가 트윗 작성시 모든 팔로워의 타임라인에 미리 저장

User A 트윗 → Fan-out Service
                ├─▶ Follower 1의 타임라인 캐시에 추가
                ├─▶ Follower 2의 타임라인 캐시에 추가
                └─▶ Follower N의 타임라인 캐시에 추가

장점: 읽기가 매우 빠름 (미리 계산됨)
단점: 팔로워가 많으면 쓰기 부하 엄청남

Fan-out on Read (Pull 모델):
타임라인 요청시 팔로잉 중인 사용자들의 트윗을 실시간 조합

User B 타임라인 요청 → 팔로잉 사용자 조회
                        ├─▶ User X의 최신 트윗
                        ├─▶ User Y의 최신 트윗
                        └─▶ User Z의 최신 트윗
                        → 병합 + 정렬 → 반환

장점: 쓰기가 단순
단점: 읽기 시 여러 DB 조회 필요

Celebrity Problem 해결 (하이브리드):

일반 사용자 (팔로워 < 10,000): Fan-out on Write
셀러브리티 (팔로워 > 10,000): Fan-out on Read

타임라인 조회:
1. 캐시에서 미리 팬아웃된 트윗 가져오기
2. 팔로잉 중인 셀러브리티의 최신 트윗 조회
3. 두 결과를 병합 + 시간순 정렬

9.3 채팅 시스템 설계

WebSocket 기반 실시간 통신:

┌────────┐  WebSocket  ┌─────────────┐
User A │◀══════════▶│  Chat└────────┘             │  ServerCluster┌────────┐  WebSocket  │            │
User B │◀══════════▶│            │
└────────┘             └─────┬──────┘
                    ┌────────┼────────┐
                    ▼        ▼        ▼
              ┌────────┐ ┌──────┐ ┌────────┐
Redis  │ │Kafka │ │Message │
              │Presence│ │      │ │   DB              └────────┘ └──────┘ └────────┘

메시지 순서 보장:

방법 1: 서버 타임스탬프 (단순하지만 시계 동기화 문제)
방법 2: Snowflake ID (시간 + 서버 + 시퀀스)

Snowflake ID 구조 (64비트):
┌─────────────┬────────┬──────────┬──────────────┐
Timestamp  │Datactr │ MachineSequence41 bits   │ 5 bits │  5 bits  │   12 bits    │
└─────────────┴────────┴──────────┴──────────────┘
→ 시간순 정렬 가능 + 전역 고유
→ 초당 4096 * 1024 = ~400ID 생성 가능

온라인 상태 (Presence) 관리:

Heartbeat 방식:
- 클라이언트가 5초마다 heartbeat 전송
- 30초 동안 heartbeat 없으면 offline 처리
- Redis에 마지막 heartbeat 시간 저장

상태 변경 알림:
- 변경시 해당 사용자의 친구 목록에 알림
- 친구가 많으면 Fan-out 부하 → 접속 중인 친구에게만 알림

9.4 Netflix/비디오 스트리밍 설계

비디오 업로드 파이프라인:

┌──────────┐    ┌──────────┐    ┌─────────────────┐
Uploader │───▶│  Object  │───▶│  Transcoding│          │    │  Store   │    │  Pipeline└──────────┘      (S3)    │    │                 │
                └──────────┘    │  ┌───────────┐  │
                                │  │ 240p      │  │
                                │  │ 480p      │  │
                                │  │ 720p      │  │
                                │  │ 1080p     │  │
                                │  │ 4K        │  │
                                │  └───────────┘  │
                                └────────┬────────┘
                                ┌─────────────────┐
CDN에 배포    │
                                 (전 세계 Edge)                                └─────────────────┘

Adaptive Bitrate Streaming (ABR):

사용자 네트워크 상태에 따라 화질 자동 조정:

대역폭 10Mbps → 1080p 세그먼트 요청
대역폭 3Mbps  → 480p 세그먼트로 전환
대역폭 회복   → 점진적으로 화질 올림

HLS (HTTP Live Streaming) 방식:
1. 비디오를 2~10초 세그먼트로 분할
2. 각 세그먼트를 여러 화질로 인코딩
3. m3u8 매니페스트 파일로 세그먼트 목록 제공
4. 클라이언트가 대역폭에 맞는 세그먼트 선택

10. Back-of-Envelope 추정

10.1 자주 사용하는 숫자

지연 시간:
- L1 캐시 참조: ~1ns
- L2 캐시 참조: ~4ns
- 메인 메모리 참조: ~100ns
- SSD 랜덤 읽기: ~150us
- HDD Seek: ~10ms
- 동일 DC 왕복: ~0.5ms
- 미국 ↔ 유럽 왕복: ~75ms

용량:
- 1 char = 1 byte (ASCII) / 2~4 bytes (UTF-8)
- 1 UUID = 36 bytes (문자열) / 16 bytes (바이너리)
- 1 이미지 (중간) = ~200KB
- 1 비디오 (1, 720p) = ~50MB

일간 환산:
- 1= 86,400= ~100,000 (간단 계산)
- 1백만 요청/= ~12 QPS
- 1억 요청/= ~1,200 QPS

10.2 QPS 계산 예시

Twitter 규모 추정:
- DAU: 3억 명
- 평균 트윗 조회: 100/- 읽기 QPS = 3* 100 / 86,400 = ~347,000 QPS
- 피크 QPS = 347,000 * 3 = ~1,000,000 QPS

- 평균 트윗 작성: 2/- 쓰기 QPS = 3* 2 / 86,400 = ~6,944 QPS

저장소:
- 트윗 크기: ~300 bytes (텍스트 + 메타데이터)
- 하루: 6억 트윗 * 300B = ~180GB/- 5: 180GB * 365 * 5 = ~328TB
- 미디어 포함: ~5= ~1.6PB

11. 흔한 실수와 팁

11.1 피해야 할 실수

1. 요구사항 확인 없이 바로 설계 시작
   → 반드시 5분은 질문에 투자

2. 하나의 컴포넌트에만 집중
   → 전체 그림을 먼저, 면접관이 요청한 부분을 깊이

3. 완벽한 설계를 추구
   → 트레이드오프를 설명하는 것이 더 중요

4. 숫자 없는 설계
QPS, 저장량, 대역폭을 반드시 계산

5. 일방적으로 말하기
   → 면접관과 대화하며 피드백 수용

6. 과도한 기술 도입
   → 문제에 맞는 적절한 기술 선택

11.2 면접 팁

1. 구조화된 접근:
   - 4단계 프레임워크를 항상 따르기
   - 각 단계에서 시간 관리

2. 트레이드오프 설명:
   - "이 방법의 장점은 A이고, 단점은 B입니다."
   - "대안으로 C가 있지만, 우리 상황에서는 D가 더 적합합니다."

3. 실제 사례 언급:
   - "Netflix는 이 문제를 X 방식으로 해결했습니다."
   - "이전 프로젝트에서 비슷한 문제를 경험했습니다."

4. 화이트보드 활용:
   - 깔끔한 다이어그램
   - 데이터 흐름 화살표
   - 숫자/메트릭 표시

5. 면접관 힌트 수용:
   - 면접관의 질문은 곧 힌트
   - "좋은 지적입니다, 그 부분을 개선하면..."

12. 퀴즈

Q1. 확장성

수평 확장과 수직 확장의 주요 차이점 3가지를 설명하세요.

답변:

  1. 비용 구조: 수직 확장은 고사양 장비로 비용이 급증하지만, 수평 확장은 저사양 장비를 추가하므로 선형적 비용 증가
  2. 장애 격리: 수직 확장은 단일 장애 지점(SPOF)이지만, 수평 확장은 한 서버 장애가 전체에 영향 없음
  3. 확장 한계: 수직 확장은 물리적 한계가 있지만, 수평 확장은 이론적으로 무제한

수평 확장의 단점은 데이터 일관성 관리, 세션 관리, 운영 복잡도가 증가한다는 것입니다.

Q2. CAP 정리

은행 시스템은 왜 CP를 선택하나요? AP를 선택하면 어떤 문제가 발생하나요?

답변: 은행 시스템이 CP를 선택하는 이유는 잔액 일관성이 절대적으로 중요하기 때문입니다. AP를 선택하면 네트워크 파티션 발생 시 두 노드에서 동시에 출금이 가능해져, 실제 잔액보다 더 많은 금액이 출금되는 이중 지출(Double Spending) 문제가 발생할 수 있습니다. 이는 금융 손실로 직결되므로, 가용성을 일부 포기하더라도 일관성을 보장해야 합니다.

Q3. 캐싱

Cache Stampede(캐시 쓰나미)란 무엇이며, 어떻게 방지하나요?

답변: Cache Stampede는 인기 있는 캐시 항목의 TTL이 만료될 때, 수많은 요청이 동시에 DB에 쏟아지는 현상입니다.

방지 방법:

  1. 뮤텍스/분산 락: 첫 번째 요청만 DB에서 가져오고, 나머지는 대기
  2. 사전 갱신: TTL 만료 전에 백그라운드에서 갱신 (TTL의 80% 시점)
  3. TTL 랜덤화: 동일 시간에 만료되지 않도록 약간의 랜덤값 추가
  4. 외부 갱신: 전용 워커가 주기적으로 캐시 갱신

Q4. 메시지 큐

Kafka와 RabbitMQ를 각각 어떤 상황에서 선택해야 하나요?

답변: Kafka 선택:

  • 이벤트 스트리밍 (로그 수집, 실시간 분석)
  • 높은 처리량 필요 (초당 수백만 메시지)
  • 메시지 재처리 필요 (소비 후에도 보존)
  • 이벤트 소싱 패턴

RabbitMQ 선택:

  • 전통적인 작업 큐 (이메일 전송, 이미지 처리)
  • 복잡한 라우팅 필요 (Exchange, Routing Key)
  • 메시지 우선순위 필요
  • RPC 패턴
  • 메시지 단위 ACK 필요

핵심 차이: Kafka는 "이벤트 로그"이고, RabbitMQ는 "메시지 브로커"입니다.

Q5. 실전 설계

URL Shortener에서 Base62 인코딩을 사용하는 이유와 해시 충돌을 어떻게 처리하나요?

답변: Base62 사용 이유:

  • URL-safe 문자만 사용 (0-9, a-z, A-Z)
  • 62^7 = 약 3.5조 개의 고유 URL 생성 가능
  • 짧은 문자열로 큰 숫자 표현 가능

해시 충돌 처리:

  1. Auto-increment ID + Base62: 충돌 없음 (추천)
  2. MD5/SHA256 + 앞 7자: 충돌 시 뒤에 문자 추가
  3. 충돌 감지 + 재시도: DB에서 중복 검사 후 다른 해시 시도
  4. Bloom Filter: 존재 여부 빠르게 확인, 충돌 시 재생성

실무에서는 방법 1 (분산 환경에서 Snowflake ID 사용)이 가장 신뢰성 있습니다.


참고 자료

  1. Alex Xu, "System Design Interview - An Insider's Guide" (2020)
  2. Alex Xu, "System Design Interview Volume 2" (2022)
  3. Martin Kleppmann, "Designing Data-Intensive Applications" (2017)
  4. Google SRE Book - https://sre.google/sre-book/table-of-contents/
  5. AWS Well-Architected Framework - https://aws.amazon.com/architecture/well-architected/
  6. Meta Engineering Blog - https://engineering.fb.com/
  7. Netflix Tech Blog - https://netflixtechblog.com/
  8. Uber Engineering Blog - https://eng.uber.com/
  9. Cassandra vs MongoDB vs DynamoDB - https://db-engines.com/en/system/Amazon+DynamoDB%3BCassandra%3BMongoDB
  10. Kafka Documentation - https://kafka.apache.org/documentation/
  11. Redis Documentation - https://redis.io/documentation
  12. Consistent Hashing Paper - Karger et al. (1997)
  13. Dynamo Paper - Amazon (2007)
  14. MapReduce Paper - Google (2004)

System Design Interview Complete Guide 2025: Scalable Architecture, Availability, and Design Patterns

Introduction

System design interviews are the most critical evaluation component in senior engineering hiring. While algorithm questions measure coding ability, system design evaluates comprehensive engineering capability -- the ability to architect large-scale systems and understand tradeoffs.

Companies like Google, Meta, Amazon, and Netflix ask candidates to design systems handling millions to hundreds of millions of users in 45-60 minutes. To succeed, you need more than technical knowledge -- you must analyze requirements, choose appropriate technologies, and clearly explain tradeoffs.

This guide systematically covers the 4-step framework, scalability, caching, databases, message queues, and real-world design examples.


1. The 4-Step System Design Interview Framework

1.1 Step 1: Clarify Requirements (5 minutes)

The most important first step. Jumping straight into design is the most common mistake.

Functional Requirements:

  • What are the core features?
  • What main actions will users perform?
  • What are the inputs and outputs?

Non-Functional Requirements:

  • Expected number of users / DAU
  • Read-to-write ratio
  • Latency requirements
  • Availability level (99.9%? 99.99%?)
  • Data consistency requirements
Interviewer: "Design a URL shortening service."

Good questions:
Q: "What's the expected DAU?"
A: "100 million."

Q: "Which is more important, URL shortening or redirection?"
A: "Redirection is more important. Reads are much more frequent."

Q: "Is there a length limit for shortened URLs?"
A: "As short as possible."

Q: "Should we support custom URLs?"
A: "Yes, optionally."

1.2 Step 2: High-Level Design (15-20 minutes)

Draw the big picture based on requirements.

+-----------+     +---------------+     +---------------+
|  Client   |---->| Load Balancer |---->|  Web Server   |
+-----------+     +---------------+     +------+--------+
                                               |
                    +--------------------------+
                    v                          v
             +-----------+            +---------------+
             |   Cache   |            |   Database    |
             +-----------+            +---------------+

What to cover at this stage:

  • API design (RESTful endpoints)
  • Data model design
  • Key components and their relationships
  • Data flow

1.3 Step 3: Deep Dive (15-20 minutes)

Dig deep into specific components the interviewer is interested in.

  • Database schema and indexing strategies
  • Caching strategies and invalidation
  • Scalability bottlenecks and solutions
  • Failure handling and recovery

1.4 Step 4: Wrap Up (5 minutes)

  • Identify design bottlenecks
  • Discuss future scaling directions
  • Monitoring/alerting strategy
  • Suggest additional features

2. Scalability

2.1 Vertical Scaling

Upgrading hardware specifications of existing servers.

Before:                 After:
+---------------+      +---------------+
|  4 CPU        |      |  32 CPU       |
|  16GB RAM     | ---> |  256GB RAM    |
|  500GB SSD    |      |  4TB SSD      |
+---------------+      +---------------+

Pros: Simple implementation, easy data consistency, no software changes needed

Cons: Physical limits exist (single server ceiling), single point of failure (SPOF), costs increase exponentially

2.2 Horizontal Scaling

Adding more servers to distribute load.

                    +---------------+
              +---->|  Server 1     |
              |     +---------------+
+-----------+ |     +---------------+
|   Load    |-+---->|  Server 2     |
| Balancer  | |     +---------------+
+-----------+ |     +---------------+
              +---->|  Server 3     |
              |     +---------------+
              |     +---------------+
              +---->|  Server N     |
                    +---------------+

Pros: Theoretically unlimited scaling, fault isolation, incremental expansion

Cons: Data consistency management complexity, session management needed, operational complexity increase

2.3 Stateless Service Design

The key to horizontal scaling is making servers stateless.

Bad (Stateful):
+-----------+  session  +-----------+
|  Client   |---------->| Server A  |  (Session lost if Server A dies)
+-----------+           +-----------+

Good (Stateless):
+-----------+     +-----------+     +-----------+
|  Client   |---->|   Any     |---->|   Redis   |
+-----------+     |  Server   |     | (Session) |
                  +-----------+     +-----------+

By storing sessions, cache, and state in external stores (Redis, Memcached), any server can handle any request identically.

2.4 Auto-Scaling Strategies

# AWS Auto Scaling configuration example
AutoScalingGroup:
  MinSize: 2
  MaxSize: 20
  DesiredCapacity: 4
  TargetTrackingScaling:
    TargetValue: 70.0          # Maintain 70% CPU
    PredefinedMetricType: ASGAverageCPUUtilization

  # Schedule-based scaling (predictable traffic)
  ScheduledActions:
    - Schedule: "0 8 * * MON-FRI"   # Weekdays 8 AM
      MinSize: 6
    - Schedule: "0 22 * * *"        # Daily 10 PM
      MinSize: 2
StrategyBased OnBest For
Target TrackingMetric target valueCPU, request count
Step ScalingThreshold stepsSudden traffic spikes
ScheduledTime-basedPredictable patterns
PredictiveML-basedRecurring patterns

3. Load Balancing

3.1 Load Balancing Algorithms

1. Round Robin (Sequential Distribution)
   Request 1 -> Server A
   Request 2 -> Server B
   Request 3 -> Server C
   Request 4 -> Server A  (back to start)

2. Weighted Round Robin
   Server A (weight=5): handles 5 requests
   Server B (weight=3): handles 3 requests
   Server C (weight=2): handles 2 requests

3. Least Connections
   Server A: 10 connections  <-- new request goes here
   Server B: 25 connections
   Server C: 18 connections

4. IP Hash
   hash(client_ip) % server_count = target_server
   -> Same client always goes to same server

3.2 L4 vs L7 Load Balancers

L4 (Transport Layer):
+----------+    TCP/UDP     +-----------+
| Client   |--------------->| L4 LB     |-- routes by IP:Port
+----------+                +-----------+
  Fast, simple, no SSL offload

L7 (Application Layer):
+----------+    HTTP/HTTPS  +-----------+
| Client   |--------------->| L7 LB     |-- routes by URL, Header, Cookie
+----------+                +-----------+
  Content-based routing, SSL termination, more flexible
FeatureL4L7
LayerTCP/UDPHTTP/HTTPS
PerformanceVery fastRelatively slower
RoutingIP/PortURL, Header, Cookie
SSL TerminationNoYes
ExamplesAWS NLBAWS ALB, Nginx

3.3 Consistent Hashing

Regular hashing redistributes almost all keys when servers are added or removed. Consistent hashing solves this problem.

Regular Hashing:
hash(key) % 3 = server number
-> Adding a server: hash(key) % 4 = most keys move!

Consistent Hashing (Hash Ring):

        Server A
        /      \
       /   0    \
      /          \
  Server D     Server B
      \          /
       \   key  /
        \  |  /
        Server C

-> Adding a server: only adjacent keys move (~1/N of total)

Virtual Nodes for even distribution:

class ConsistentHash:
    def __init__(self, nodes, virtual_nodes=150):
        self.ring = SortedDict()
        self.virtual_nodes = virtual_nodes

        for node in nodes:
            for i in range(virtual_nodes):
                key = self._hash(f"node-vn-i")
                self.ring[key] = node

    def get_node(self, data_key):
        if not self.ring:
            return None
        hash_val = self._hash(data_key)
        # Find nearest node clockwise on the hash ring
        idx = self.ring.bisect_right(hash_val)
        if idx == len(self.ring):
            idx = 0
        return self.ring.peekitem(idx)[1]

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

4. Caching

4.1 Caching Strategies

1. Cache-Aside (Lazy Loading):
   +--------+  1.read   +---------+
   |  App   |---------->|  Cache  |
   +---+----+  miss     +---------+
       | 2.read DB         ^
       v             3.store in cache
   +--------+
   |   DB   |
   +--------+

2. Write-Through:
   +--------+  1.write  +---------+  2.sync write  +--------+
   |  App   |---------->|  Cache  |--------------->|   DB   |
   +--------+           +---------+                +--------+
   (Always consistent, higher write latency)

3. Write-Back (Write-Behind):
   +--------+  1.write  +---------+  2.async batch write  +--------+
   |  App   |---------->|  Cache  |======================>|   DB   |
   +--------+           +---------+                       +--------+
   (Great write performance, data loss risk)

4. Write-Around:
   +--------+  1.write  +--------+
   |  App   |---------->|   DB   |
   +---+----+           +--------+
       | 2.load to cache on read
       v
   +---------+
   |  Cache  |
   +---------+

4.2 Redis vs Memcached

# Redis - supports various data structures
import redis

r = redis.Redis(host='localhost', port=6379)

# String
r.set('user:1001:name', 'Alice', ex=3600)  # 1-hour TTL

# Hash
r.hset('user:1001', mapping={
    'name': 'Alice',
    'email': 'alice@example.com',
    'login_count': 42
})

# Sorted Set (leaderboard)
r.zadd('leaderboard', {'player_a': 1500, 'player_b': 1800})
top_10 = r.zrevrange('leaderboard', 0, 9, withscores=True)

# Pub/Sub
r.publish('notifications', 'New message!')
FeatureRedisMemcached
Data StructuresString, Hash, List, Set, Sorted Set, etc.Key-Value only
PersistenceRDB/AOF supportedNone
ReplicationMaster-ReplicaNone
ClusteringRedis ClusterClient-side sharding
Memory EfficiencyRelatively lowerHigher (simple structure)
Use CasesSessions, leaderboards, queues, Pub/SubSimple caching

4.3 CDN (Content Delivery Network)

Without CDN:
User(Seoul) ---- 5000km ----> Origin Server(US)
RTT: ~200ms

With CDN:
User(Seoul) ---- 50km ----> CDN Edge(Seoul) -- cache hit
RTT: ~5ms

On Cache Miss:
User(Seoul) -> CDN Edge(Seoul) -> Origin(US)
                | store in cache
         Next request served from Edge

CDN Cache Invalidation Strategies:

1. TTL-based: Cache-Control: max-age=86400
2. Versioning: /static/app.v2.3.1.js
3. Manual invalidation: CloudFront Invalidation
4. Tag-based: Surrogate-Key header for selective purging

4.4 Cache Problems and Solutions

1. Cache Stampede (Thundering Herd):
   TTL expires -> thousands of requests hit DB simultaneously
   Solution: Mutex lock + pre-refresh

2. Cache Penetration:
   Repeated queries for non-existent keys -> every request hits DB
   Solution: Bloom Filter + cache empty results

3. Hot Key Problem:
   Traffic concentrated on specific key -> single cache node overloaded
   Solution: Local cache + key replication (key_1, key_2, ...)

5. Database Design

5.1 SQL vs NoSQL

CriteriaSQL (RDBMS)NoSQL
Data ModelNormalized tablesDocument, Key-Value, Column, Graph
SchemaFixed schemaFlexible schema
ScalingPrimarily verticalEasy horizontal scaling
TransactionsFull ACID supportLimited (some support)
JoinsEfficient supportLimited/impossible
Best ForComplex relationships, transactions criticalLarge volume, flexible schema, high performance
ProductsPostgreSQL, MySQLMongoDB, Cassandra, DynamoDB
SQL Selection Criteria:
- Complex data relationships with frequent joins
- ACID transactions essential (finance, payments)
- Data integrity is top priority
- Stable schema

NoSQL Selection Criteria:
- Data structure changes frequently
- Massive data volume (multiple TB+)
- Very low latency required
- Horizontal scaling essential
- Unstructured data (logs, social media)

5.2 Database Replication

Master-Slave Replication:
+-----------+     sync/async replication     +-----------+
|  Master   | =============================> |  Slave 1  |  <- Read-only
|  (Write)  | =============================> |  Slave 2  |  <- Read-only
+-----------+                                +-----------+

Multi-Master Replication:
+-----------+ <=============================> +-----------+
| Master 1  |    bidirectional replication     | Master 2  |
| (R/W)     |                                 | (R/W)     |
+-----------+                                 +-----------+
(Conflict resolution required)

5.3 Database Sharding

Before Sharding:
+-----------------------+
|   Single Database     |
|   10TB, latency grows |
+-----------------------+

After Sharding (Hash-based):
hash(user_id) % 4 = shard_number

+-----------+ +-----------+ +-----------+ +-----------+
|  Shard 0  | |  Shard 1  | |  Shard 2  | |  Shard 3  |
|  2.5TB    | |  2.5TB    | |  2.5TB    | |  2.5TB    |
+-----------+ +-----------+ +-----------+ +-----------+

Sharding Strategy Comparison:

StrategyProsCons
Range-basedEfficient range queriesHotspot potential
Hash-basedEven distributionInefficient range queries
Directory-basedFlexible mappingDirectory is SPOF
GeographicLocality advantageCross-region queries difficult

Sharding Considerations:

1. Join queries: Cross-shard joins impossible -> denormalization or application-level joins
2. Rebalancing: Shard redistribution on growth -> use Consistent Hashing
3. Hotspots: Traffic concentrated on specific shard -> redesign shard key
4. Transactions: Distributed transactions -> Saga pattern or 2PC
5. Unique IDs: Need globally unique IDs -> Snowflake ID, UUID

5.4 Denormalization

-- Normalized design (joins needed):
SELECT u.name, o.total, p.name AS product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id;

-- Denormalized design (single table):
-- orders table includes user_name, product_name
SELECT user_name, total, product_name
FROM denormalized_orders
WHERE user_id = 1001;

Denormalization improves read performance but increases data duplication and write complexity. Best suited when reads significantly outnumber writes.


6. CAP Theorem and PACELC

6.1 CAP Theorem

       Consistency
         /          \
        /            \
       /     CA       \
      /  (single DC)   \
     /------------------\
    |                    |
 CP |  Distributed Sys   | AP
    |  P is unavoidable  |
    |--------------------|
         Availability

During network partition:
CP system -> maintains consistency, rejects some requests
AP system -> maintains availability, allows temporary inconsistency

Real-World CAP Choices:

SystemChoiceReason
Bank AccountsCPBalance inconsistency unacceptable
DNSAPSlightly stale records acceptable
MongoDBCPStrong consistency by default
CassandraAPEventual consistency
ZooKeeperCPDistributed coordination

6.2 PACELC Theory

PACELC extends CAP by describing tradeoffs when there is no partition.

if (Partition) then
    Choose between Availability and Consistency
else
    Choose between Latency and Consistency

Examples:
- DynamoDB:   PA/EL (availability + low latency)
- MongoDB:    PC/EC (consistency-first)
- Cassandra:  PA/EL (availability + low latency)
- PostgreSQL(single): PC/EC (consistency + consistency)

7. Message Queues

7.1 The Need for Asynchronous Processing

Synchronous Processing:
Client -> API -> Send Email(3s) -> Resize Image(2s) -> Log(1s) -> Response
Total: 6 seconds waiting

Asynchronous Processing:
Client -> API -> Message Queue -> Response (immediately)
                    |
             +------+------+
             v             v
       Email Worker   Image Worker
         (3s)           (2s)
Total: 0.1 seconds waiting

7.2 Kafka vs RabbitMQ vs SQS

Apache Kafka:
+-----------+     +---------------------------+
| Producer  |---->|  Topic: orders            |
+-----------+     |  +-------+-------+------+ |
                  |  |  P0   |  P1   |  P2  | |
                  |  +---+---+---+---+--+---+ |
                  +------+-------+------+-----+
                         v       v      v
                  +------------------------+
                  |  Consumer Group A       |
                  |  (one consumer per      |
                  |   partition)            |
                  +------------------------+
FeatureKafkaRabbitMQSQS
ModelLog-based streamingAMQP message brokerManaged queue
ThroughputMillions/secTens of thousands/secAuto-scaling
Message RetentionConfigurable retentionDeleted after consumption14 days
OrderingGuaranteed within partitionPer-queue guaranteeWith FIFO queue
Consumption ModelPull (Consumer Poll)Push + PullPull (Long Polling)
Use CasesEvent streaming, logsTask queues, RPCServerless, decoupling

7.3 Message Delivery Guarantees

1. At-Most-Once:
   Producer -> Broker (no ACK wait)
   Fast but messages can be lost
   Use: Logs, metrics (loss acceptable)

2. At-Least-Once:
   Producer -> Broker -> ACK
   Retry on ACK failure -> duplicates possible
   Use: Most systems (idempotent processing needed)

3. Exactly-Once:
   Transaction-based or idempotency keys
   Performance overhead
   Use: Finance, payments (accuracy critical)

8. Rate Limiting

8.1 Token Bucket Algorithm

import time
import threading

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity        # Max tokens in bucket
        self.tokens = capacity           # Current tokens
        self.refill_rate = refill_rate   # Tokens added per second
        self.last_refill = time.time()
        self.lock = threading.Lock()

    def allow_request(self):
        with self.lock:
            now = time.time()
            # Refill tokens
            elapsed = now - self.last_refill
            self.tokens = min(
                self.capacity,
                self.tokens + elapsed * self.refill_rate
            )
            self.last_refill = now

            # Check if request allowed
            if self.tokens >= 1:
                self.tokens -= 1
                return True
            return False

# 10 requests/sec, burst up to 20
limiter = TokenBucket(capacity=20, refill_rate=10)

8.2 Sliding Window Log Algorithm

import time
from collections import deque

class SlidingWindowLog:
    def __init__(self, window_size, max_requests):
        self.window_size = window_size   # Window size (seconds)
        self.max_requests = max_requests
        self.requests = deque()

    def allow_request(self):
        now = time.time()
        # Remove old requests outside window
        while self.requests and self.requests[0] < now - self.window_size:
            self.requests.popleft()

        if len(self.requests) < self.max_requests:
            self.requests.append(now)
            return True
        return False

# Max 100 requests per minute
limiter = SlidingWindowLog(window_size=60, max_requests=100)

8.3 Sliding Window Counter

Sliding Window Counter (memory efficient):

Previous window count: 84
Current window count: 36
Window size: 60 seconds
Current position: 25% into window

Weighted count = 84 * (1 - 0.25) + 36 = 84 * 0.75 + 36 = 99

max_requests = 100 -> allowed (99 < 100)
AlgorithmMemoryAccuracyImplementation
Token BucketO(1)HighLow
Leaky BucketO(1)HighLow
Sliding Window LogO(N)Very HighMedium
Sliding Window CounterO(1)ApproximateMedium
Fixed WindowO(1)LowVery Low

9. Real-World Design Examples

9.1 URL Shortener Design

Requirements:

  • 100M DAU, read:write = 100:1
  • Shortened URLs as short as possible (7 chars)
  • Custom URL support
  • Redirection latency under 100ms

Back-of-Envelope Estimation:

Writes: 100M DAU * 0.1 URLs/day = 10M/day
Write QPS = 10,000,000 / 86,400 = ~116 QPS
Read QPS = 116 * 100 = ~11,600 QPS
Peak QPS = 11,600 * 3 = ~35,000 QPS

Storage (5 years):
10,000,000 * 365 * 5 = 18.25 billion records
Each record ~500 bytes -> 18.25B * 500B = ~9.1TB

System Architecture:

+----------+     +------+     +---------------+
|  Client  |---->|  LB  |---->|  API Server   |
+----------+     +------+     +------+--------+
                                     |
                    +----------------+----------------+
                    v                v                v
              +-----------+  +-----------+  +-----------+
              |   Redis   |  |  MySQL    |  | Analytics |
              |   Cache   |  | (Sharded) |  |  (Kafka)  |
              +-----------+  +-----------+  +-----------+

URL Encoding Strategy:

import hashlib

def generate_short_url(long_url, counter):
    # Method 1: Base62 encoding (auto-increment ID)
    chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    short = ""
    n = counter
    while n > 0:
        short = chars[n % 62] + short
        n //= 62
    return short  # e.g., "dK4x9Z"

    # Method 2: MD5 hash + Base62 (first 7 chars)
    hash_val = hashlib.md5(long_url.encode()).hexdigest()
    num = int(hash_val[:12], 16)
    short = ""
    for _ in range(7):
        short = chars[num % 62] + short
        num //= 62
    return short

# 62^7 = ~3.5 trillion unique URLs possible

9.2 Twitter/X Feed Design

Core Problem: Fan-out

Fan-out on Write (Push Model):
When user tweets -> pre-write to all followers' timelines

User A tweets -> Fan-out Service
                +--> Follower 1's timeline cache
                +--> Follower 2's timeline cache
                +--> Follower N's timeline cache

Pros: Reads are very fast (pre-computed)
Cons: Enormous write load for users with many followers

Fan-out on Read (Pull Model):
When timeline requested -> assemble tweets in real-time

User B requests timeline -> Query followed users
                           +--> User X's latest tweets
                           +--> User Y's latest tweets
                           +--> User Z's latest tweets
                           -> merge + sort -> return

Pros: Simple writes
Cons: Multiple DB queries on every read

Celebrity Problem Solution (Hybrid):

Regular users (followers < 10,000): Fan-out on Write
Celebrities (followers > 10,000): Fan-out on Read

Timeline fetch:
1. Get pre-fanned-out tweets from cache
2. Query latest tweets from followed celebrities
3. Merge both results + sort by time

9.3 Chat System Design

WebSocket-based Real-time Communication:

+----------+  WebSocket  +---------------+
| User A   |<==========>|  Chat         |
+----------+             |  Server      |
                         |  Cluster     |
+----------+  WebSocket  |              |
| User B   |<==========>|              |
+----------+             +------+-------+
                                |
                    +-----------+-----------+
                    v           v           v
              +----------+ +--------+ +----------+
              |  Redis   | | Kafka  | | Message  |
              | Presence | |        | |    DB    |
              +----------+ +--------+ +----------+

Message Ordering:

Method 1: Server timestamps (simple but clock sync issues)
Method 2: Snowflake ID (time + server + sequence)

Snowflake ID Structure (64-bit):
+---------------+----------+----------+--------------+
|  Timestamp    | Datactr  | Machine  |   Sequence   |
|   41 bits     |  5 bits  |  5 bits  |   12 bits    |
+---------------+----------+----------+--------------+
-> Time-sortable + globally unique
-> Can generate ~4 million IDs/sec

Presence Management:

Heartbeat approach:
- Client sends heartbeat every 5 seconds
- Marked offline after 30 seconds without heartbeat
- Last heartbeat time stored in Redis

Status change notifications:
- Notify user's friend list on change
- For users with many friends: only notify currently online friends

9.4 Netflix / Video Streaming Design

Video Upload Pipeline:

+-----------+    +-----------+    +-------------------+
| Uploader  |--->|  Object   |--->|  Transcoding      |
|           |    |  Store    |    |  Pipeline          |
+-----------+    |  (S3)     |    |                    |
                 +-----------+    |  +---------------+ |
                                  |  | 240p          | |
                                  |  | 480p          | |
                                  |  | 720p          | |
                                  |  | 1080p         | |
                                  |  | 4K            | |
                                  |  +---------------+ |
                                  +---------+----------+
                                            v
                                  +-------------------+
                                  |   Deploy to CDN   |
                                  | (Global Edges)    |
                                  +-------------------+

Adaptive Bitrate Streaming (ABR):

Automatically adjusts quality based on user network:

Bandwidth 10Mbps -> request 1080p segments
Bandwidth 3Mbps  -> switch to 480p segments
Bandwidth recovers -> gradually increase quality

HLS (HTTP Live Streaming) approach:
1. Split video into 2-10 second segments
2. Encode each segment at multiple quality levels
3. Provide m3u8 manifest file listing segments
4. Client selects segments matching bandwidth

10. Back-of-Envelope Estimation

10.1 Commonly Used Numbers

Latency:
- L1 cache reference: ~1ns
- L2 cache reference: ~4ns
- Main memory reference: ~100ns
- SSD random read: ~150us
- HDD seek: ~10ms
- Same DC round trip: ~0.5ms
- US <-> Europe round trip: ~75ms

Capacity:
- 1 char = 1 byte (ASCII) / 2-4 bytes (UTF-8)
- 1 UUID = 36 bytes (string) / 16 bytes (binary)
- 1 image (medium) = ~200KB
- 1 video (1 min, 720p) = ~50MB

Daily conversion:
- 1 day = 86,400 sec = ~100,000 sec (simplified)
- 1 million requests/day = ~12 QPS
- 100 million requests/day = ~1,200 QPS

10.2 QPS Calculation Example

Twitter-scale estimation:
- DAU: 300 million
- Avg tweet views: 100/day
- Read QPS = 300M * 100 / 86,400 = ~347,000 QPS
- Peak QPS = 347,000 * 3 = ~1,000,000 QPS

- Avg tweets written: 2/day
- Write QPS = 300M * 2 / 86,400 = ~6,944 QPS

Storage:
- Tweet size: ~300 bytes (text + metadata)
- Daily: 600M tweets * 300B = ~180GB/day
- 5 years: 180GB * 365 * 5 = ~328TB
- With media: ~5x = ~1.6PB

11. Common Mistakes and Tips

11.1 Mistakes to Avoid

1. Jumping into design without clarifying requirements
   -> Always invest 5 minutes in questions

2. Focusing on a single component
   -> Big picture first, then deep dive where interviewer asks

3. Pursuing a perfect design
   -> Explaining tradeoffs is more important

4. Design without numbers
   -> Always calculate QPS, storage, bandwidth

5. Talking without interacting
   -> Converse with interviewer, accept feedback

6. Over-engineering
   -> Choose appropriate technology for the problem

11.2 Interview Tips

1. Structured Approach:
   - Always follow the 4-step framework
   - Time management in each step

2. Explain Tradeoffs:
   - "The advantage of this approach is A, the drawback is B."
   - "Alternative C exists, but D is more suitable for our situation."

3. Reference Real Examples:
   - "Netflix solved this problem using X approach."
   - "I experienced a similar issue in a previous project."

4. Whiteboard Usage:
   - Clean diagrams
   - Data flow arrows
   - Numbers/metrics displayed

5. Accept Interviewer Hints:
   - Interviewer questions are hints
   - "Great point, if we improve that part..."

12. Quiz

Q1. Scalability

Explain 3 key differences between horizontal and vertical scaling.

Answer:

  1. Cost Structure: Vertical scaling costs increase exponentially with high-spec hardware, while horizontal scaling costs grow linearly with commodity machines.
  2. Fault Isolation: Vertical scaling is a single point of failure (SPOF), while horizontal scaling means one server failure does not affect the whole system.
  3. Scaling Limits: Vertical scaling has physical limits, while horizontal scaling is theoretically unlimited.

The tradeoff for horizontal scaling is increased complexity in data consistency management, session handling, and operations.

Q2. CAP Theorem

Why do banking systems choose CP? What problems arise if they choose AP?

Answer: Banking systems choose CP because balance consistency is absolutely critical. If AP is chosen, during a network partition, withdrawals could happen simultaneously on two nodes, allowing more money to be withdrawn than the actual balance -- a double spending problem. This directly leads to financial loss, so consistency must be guaranteed even at the cost of some availability.

Q3. Caching

What is a Cache Stampede and how do you prevent it?

Answer: A Cache Stampede occurs when the TTL of a popular cache entry expires and thousands of requests simultaneously flood the database.

Prevention methods:

  1. Mutex/Distributed Lock: Only the first request fetches from DB, others wait
  2. Pre-refresh: Refresh in background before TTL expiration (at 80% of TTL)
  3. TTL Randomization: Add small random values to prevent simultaneous expiration
  4. External refresh: Dedicated worker periodically refreshes the cache

Q4. Message Queues

When should you choose Kafka vs RabbitMQ?

Answer: Choose Kafka:

  • Event streaming (log collection, real-time analytics)
  • High throughput needed (millions of messages per second)
  • Message replay needed (retained after consumption)
  • Event sourcing patterns

Choose RabbitMQ:

  • Traditional task queues (email sending, image processing)
  • Complex routing needed (Exchange, Routing Key)
  • Message priority needed
  • RPC patterns
  • Per-message ACK needed

Key difference: Kafka is an "event log", RabbitMQ is a "message broker".

Q5. Real Design

Why use Base62 encoding in a URL Shortener and how do you handle hash collisions?

Answer: Why Base62:

  • Uses only URL-safe characters (0-9, a-z, A-Z)
  • 62^7 = ~3.5 trillion unique URLs possible
  • Short strings represent large numbers

Collision Handling:

  1. Auto-increment ID + Base62: No collisions (recommended)
  2. MD5/SHA256 + first 7 chars: Append characters on collision
  3. Collision detection + retry: Check DB for duplicates, retry with different hash
  4. Bloom Filter: Quick existence check, regenerate on collision

In practice, Method 1 (using Snowflake ID in distributed environments) is most reliable.


References

  1. Alex Xu, "System Design Interview - An Insider's Guide" (2020)
  2. Alex Xu, "System Design Interview Volume 2" (2022)
  3. Martin Kleppmann, "Designing Data-Intensive Applications" (2017)
  4. Google SRE Book - https://sre.google/sre-book/table-of-contents/
  5. AWS Well-Architected Framework - https://aws.amazon.com/architecture/well-architected/
  6. Meta Engineering Blog - https://engineering.fb.com/
  7. Netflix Tech Blog - https://netflixtechblog.com/
  8. Uber Engineering Blog - https://eng.uber.com/
  9. Cassandra vs MongoDB vs DynamoDB - https://db-engines.com/en/system/Amazon+DynamoDB%3BCassandra%3BMongoDB
  10. Kafka Documentation - https://kafka.apache.org/documentation/
  11. Redis Documentation - https://redis.io/documentation
  12. Consistent Hashing Paper - Karger et al. (1997)
  13. Dynamo Paper - Amazon (2007)
  14. MapReduce Paper - Google (2004)