Skip to content

Split View: 시스템 설계 기초 완전 가이드 — 확장성, 가용성, 캐싱, 메시지 큐

|

시스템 설계 기초 완전 가이드 — 확장성, 가용성, 캐싱, 메시지 큐

목차

  1. 시스템 설계란
  2. 확장성
  3. 로드 밸런싱
  4. 캐싱 전략
  5. CDN
  6. 데이터베이스
  7. CAP 정리
  8. 메시지 큐
  9. API 설계
  10. 모니터링과 로깅
  11. 실전 예제: URL 단축기 설계
  12. 실전 예제: 채팅 시스템 설계

시스템 설계란

시스템 설계(System Design)는 대규모 소프트웨어 시스템의 아키텍처를 구성하는 과정이다. 단순히 코드를 작성하는 것을 넘어, 수백만 사용자를 처리할 수 있는 확장 가능하고 안정적인 시스템을 만드는 방법을 다룬다.

왜 중요한가

  • 실무: 실제 서비스는 단일 서버로 운영할 수 없다. 트래픽 증가, 장애 대응, 데이터 관리 등 다양한 문제를 해결해야 한다.
  • 면접: FAANG을 비롯한 대부분의 테크 기업에서 시스템 설계 면접을 진행한다.
  • 커리어 성장: 시니어 엔지니어로 성장하려면 아키텍처 수준의 사고력이 필수다.

면접에서의 접근법

시스템 설계 면접에서는 정답이 없다. 중요한 것은 사고 과정이다.

  1. 요구사항 명확화: 기능적/비기능적 요구사항을 정리한다
  2. 규모 추정: 사용자 수, QPS, 저장 용량 등을 계산한다
  3. 고수준 설계: 핵심 컴포넌트를 배치한다
  4. 상세 설계: 병목 지점을 파악하고 해결한다
  5. 트레이드오프 논의: 모든 결정에는 장단점이 있다

확장성

확장성(Scalability)은 시스템이 증가하는 부하를 처리하는 능력이다.

수직 확장 (Scale Up)

하나의 서버에 더 강력한 CPU, 더 많은 RAM, 더 큰 디스크를 추가하는 방식이다.

장점:

  • 구현이 단순하다
  • 데이터 일관성 유지가 쉽다
  • 네트워크 호출이 없으므로 지연 시간이 적다

단점:

  • 하드웨어 한계가 있다 (단일 머신의 스펙 한계)
  • 단일 장애점(Single Point of Failure)이 된다
  • 비용이 기하급수적으로 증가한다

수평 확장 (Scale Out)

여러 대의 서버를 추가하여 부하를 분산하는 방식이다.

장점:

  • 이론적으로 무한한 확장이 가능하다
  • 내결함성(Fault Tolerance)을 확보할 수 있다
  • 비용 효율적이다

단점:

  • 분산 시스템의 복잡성이 증가한다
  • 데이터 일관성 유지가 어렵다
  • 로드 밸런싱이 필요하다

Stateless 설계

수평 확장의 핵심은 Stateless 설계다. 서버가 상태를 보유하지 않으면 어떤 서버든 동일한 요청을 처리할 수 있다.

Stateful 서버:
  사용자 A -> 서버 1 (세션 저장)
  사용자 A -> 서버 2 (세션 없음 -> 오류!)

Stateless 서버:
  사용자 A -> 서버 1 (외부 저장소에서 세션 조회)
  사용자 A -> 서버 2 (외부 저장소에서 세션 조회 -> 정상)

세션, 캐시 등의 상태는 Redis나 Memcached 같은 외부 저장소에 보관한다.


로드 밸런싱

로드 밸런서(Load Balancer)는 들어오는 트래픽을 여러 서버에 분배하는 역할을 한다.

L4 vs L7 로드 밸런싱

L4 (전송 계층):

  • TCP/UDP 수준에서 동작한다
  • IP와 포트 정보만으로 라우팅한다
  • 빠르고 효율적이다
  • 예: AWS NLB

L7 (애플리케이션 계층):

  • HTTP/HTTPS 수준에서 동작한다
  • URL 경로, 헤더, 쿠키 등을 기반으로 라우팅할 수 있다
  • 더 세밀한 제어가 가능하다
  • 예: AWS ALB, Nginx

로드 밸런싱 알고리즘

라운드 로빈 (Round Robin): 요청을 순서대로 각 서버에 배분한다. 구현이 단순하지만 서버 성능 차이를 고려하지 못한다.

가중치 기반 라운드 로빈 (Weighted Round Robin): 서버마다 가중치를 부여하여, 성능이 좋은 서버에 더 많은 요청을 배분한다.

IP 해시 (IP Hash): 클라이언트의 IP를 해싱하여 항상 같은 서버로 라우팅한다. 세션 유지가 필요한 경우에 유용하지만, 서버 추가/제거 시 재분배가 필요하다.

최소 연결 (Least Connections): 현재 연결이 가장 적은 서버에 요청을 보낸다. 요청 처리 시간이 다양할 때 효과적이다.

라운드 로빈 예시:

  요청 1 -> 서버 A
  요청 2 -> 서버 B
  요청 3 -> 서버 C
  요청 4 -> 서버 A  (순환)
  요청 5 -> 서버 B  (순환)

헬스 체크

로드 밸런서는 주기적으로 서버의 상태를 확인한다. 응답이 없거나 비정상 응답을 보내는 서버는 로드 밸런싱 대상에서 제외한다.

로드 밸런서:
  /health -> 서버 A: 200 OK (정상)
  /health -> 서버 B: 503 Error (비정상 -> 제외)
  /health -> 서버 C: 200 OK (정상)

  이후 트래픽은 서버 A와 C에만 분배

캐싱 전략

캐싱(Caching)은 자주 조회되는 데이터를 빠른 저장소에 임시로 보관하여 응답 시간을 줄이는 기법이다.

Cache Aside (Lazy Loading)

가장 널리 사용되는 패턴이다.

  1. 애플리케이션이 캐시를 먼저 확인한다
  2. 캐시에 있으면(Cache Hit) 반환한다
  3. 캐시에 없으면(Cache Miss) DB에서 조회 후 캐시에 저장한다
읽기 흐름:
  클라이언트 -> 캐시 확인
    히트 -> 캐시에서 반환
    미스 -> DB 조회 -> 캐시 저장 -> 반환

장점: 실제로 요청되는 데이터만 캐싱한다. 캐시 장애 시 DB에서 직접 조회 가능하다. 단점: 첫 번째 요청은 항상 Cache Miss가 발생한다. 데이터 불일치가 발생할 수 있다.

Write Through

데이터를 쓸 때 캐시와 DB에 동시에 기록하는 패턴이다.

쓰기 흐름:
  클라이언트 -> 캐시 업데이트 -> DB 업데이트 -> 응답

장점: 캐시와 DB의 일관성이 보장된다. 단점: 쓰기 지연이 증가한다. 사용되지 않는 데이터도 캐싱된다.

Write Behind (Write Back)

데이터를 캐시에 먼저 쓰고, 일정 시간 후 비동기적으로 DB에 반영하는 패턴이다.

쓰기 흐름:
  클라이언트 -> 캐시 업데이트 -> 응답 (즉시)
  백그라운드: 캐시 -> DB 동기화 (지연)

장점: 쓰기 성능이 매우 빠르다. DB 부하를 줄일 수 있다. 단점: 캐시 장애 시 데이터 유실 가능성이 있다.

TTL과 캐시 무효화

TTL (Time To Live): 캐시 항목의 만료 시간을 설정한다. 너무 짧으면 캐시 효율이 떨어지고, 너무 길면 데이터 불일치가 발생한다.

캐시 무효화 전략:

  • 시간 기반: TTL 만료 시 자동 삭제
  • 이벤트 기반: 데이터 변경 시 즉시 무효화
  • 버전 기반: 키에 버전 정보를 포함하여 새 버전 생성 시 자동으로 새 데이터를 캐싱
TTL 예시:
  SET user:123 "Kim" EX 3600   (1시간 후 만료)
  SET product:456 "Laptop" EX 86400  (24시간 후 만료)

캐시 스탬피드 (Thundering Herd)

인기 있는 캐시 항목이 만료될 때, 수많은 요청이 동시에 DB로 몰리는 현상이다.

해결 방법:

  • 잠금(Lock): 하나의 요청만 DB에 접근하고 나머지는 대기
  • 확률적 조기 갱신: TTL 만료 전 확률적으로 캐시를 갱신
  • 스태거 TTL: 캐시 항목마다 약간씩 다른 TTL을 설정

CDN

CDN(Content Delivery Network)은 전 세계에 분산된 엣지 서버를 통해 사용자에게 가장 가까운 위치에서 콘텐츠를 제공하는 시스템이다.

동작 원리

사용자(서울) -> 서울 엣지 서버 (캐시 히트) -> 즉시 응답
사용자(서울) -> 서울 엣지 서버 (캐시 미스) -> 오리진 서버 -> 엣지에 캐싱 -> 응답

정적 자산 제공

CDN은 주로 정적 콘텐츠를 제공하는 데 사용된다.

  • 이미지, 동영상, 오디오
  • CSS, JavaScript 파일
  • 폰트 파일
  • HTML 페이지 (정적 사이트)

캐시 히트율 최적화

캐시 히트율(Cache Hit Ratio)이 높을수록 CDN의 효율이 좋다.

최적화 방법:

  • 적절한 Cache-Control 헤더 설정
  • 파일 버저닝 (예: style.v2.css 또는 콘텐츠 해시 사용)
  • 자주 변경되지 않는 콘텐츠에 긴 TTL 설정
  • 쿼리 스트링 정규화

Pull CDN vs Push CDN

Pull CDN: 첫 번째 요청 시 오리진에서 콘텐츠를 가져와 캐싱한다. 트래픽이 많은 사이트에 적합하다.

Push CDN: 콘텐츠를 미리 CDN에 업로드한다. 콘텐츠 변경이 드문 경우에 적합하다.


데이터베이스

SQL vs NoSQL

SQL (관계형 데이터베이스):

  • 정형 데이터, 스키마 기반
  • ACID 트랜잭션 보장
  • JOIN을 통한 복잡한 쿼리 지원
  • 예: PostgreSQL, MySQL, Oracle

NoSQL (비관계형 데이터베이스):

  • 유연한 스키마
  • 수평 확장이 용이
  • 높은 쓰기/읽기 처리량
  • 예: MongoDB, Cassandra, DynamoDB, Redis

선택 기준:

기준SQLNoSQL
데이터 구조정형화됨유연함
확장 방식수직 확장 위주수평 확장 위주
일관성강한 일관성최종 일관성 (보통)
트랜잭션ACIDBASE
쿼리복잡한 JOIN 가능단순 키-값 조회 최적화

샤딩 (Sharding)

데이터를 여러 데이터베이스 인스턴스에 분산 저장하는 기법이다.

해시 기반 샤딩: 키를 해싱하여 샤드를 결정한다. 균등 분배가 가능하지만, 샤드 추가/제거 시 재분배 비용이 크다.

user_id % 4 = 0 -> 샤드 A
user_id % 4 = 1 -> 샤드 B
user_id % 4 = 2 -> 샤드 C
user_id % 4 = 3 -> 샤드 D

범위 기반 샤딩: 키의 범위에 따라 샤드를 결정한다. 범위 쿼리에 유리하지만, 핫스팟이 발생할 수 있다.

일관된 해싱 (Consistent Hashing): 해시 링을 사용하여 노드 추가/제거 시 최소한의 키만 재배치한다. 분산 캐시나 분산 DB에서 널리 사용된다.

레플리케이션 (Replication)

데이터를 여러 노드에 복제하여 가용성과 읽기 성능을 높이는 기법이다.

마스터-슬레이브 (Primary-Replica):

  • 쓰기는 마스터에서, 읽기는 슬레이브에서 처리한다
  • 마스터 장애 시 슬레이브 중 하나를 승격한다

마스터-마스터 (Multi-Primary):

  • 여러 노드에서 쓰기가 가능하다
  • 충돌 해결 전략이 필요하다
마스터-슬레이브 구조:

  쓰기 -> [마스터]
            |
     +------+------+
     |              |
  [슬레이브1]   [슬레이브2]
     |              |
  읽기 요청     읽기 요청

파티셔닝

데이터를 논리적으로 분할하는 기법이다.

수평 파티셔닝: 행 단위로 분할. 샤딩과 유사하다. 수직 파티셔닝: 열 단위로 분할. 자주 조회되는 열과 그렇지 않은 열을 분리한다.


CAP 정리

분산 시스템에서 다음 세 가지 속성을 동시에 모두 만족시킬 수 없다는 정리다.

세 가지 속성

Consistency (일관성): 모든 노드가 동일한 데이터를 보유한다. 읽기 요청은 항상 가장 최신 데이터를 반환한다.

Availability (가용성): 모든 요청에 대해 응답을 반환한다. 일부 노드가 장애 상태더라도 시스템은 정상 응답한다.

Partition Tolerance (분할 허용성): 네트워크 분할이 발생해도 시스템이 계속 동작한다.

실제 선택

네트워크 분할(Partition)은 분산 시스템에서 불가피하므로, 실질적으로는 CP 또는 AP 중 하나를 선택해야 한다.

CP 시스템 (일관성 + 분할 허용성):

  • 네트워크 분할 시 일관성을 위해 일부 요청을 거부할 수 있다
  • 예: HBase, MongoDB(기본 설정), Zookeeper

AP 시스템 (가용성 + 분할 허용성):

  • 네트워크 분할 시 가용성을 위해 오래된 데이터를 반환할 수 있다
  • 예: Cassandra, DynamoDB, CouchDB
CP 시스템 (분할 발생 시):
  노드 A: 최신 데이터 = "v2"
  노드 B: 오래된 데이터 = "v1" -> 요청 거부 (일관성 우선)

AP 시스템 (분할 발생 시):
  노드 A: 최신 데이터 = "v2"
  노드 B: 오래된 데이터 = "v1" -> "v1" 반환 (가용성 우선)

PACELC

CAP 정리의 확장으로, 분할이 없을 때(E: Else)의 지연 시간(L: Latency)과 일관성(C: Consistency) 간의 트레이드오프도 고려한다.

  • PA/EL: 분할 시 가용성, 평상시 낮은 지연 (예: DynamoDB)
  • PC/EC: 분할 시 일관성, 평상시도 일관성 (예: HBase)
  • PA/EC: 분할 시 가용성, 평상시 일관성 (예: MongoDB)

메시지 큐

메시지 큐(Message Queue)는 비동기 통신을 가능하게 하는 미들웨어다. 생산자(Producer)와 소비자(Consumer)를 분리하여 시스템의 결합도를 낮추고 확장성을 높인다.

왜 필요한가

동기 통신의 문제점:

  • 서비스 A가 서비스 B를 직접 호출하면, B가 다운될 때 A도 영향을 받는다
  • 요청 급증 시 하위 서비스가 과부하된다
  • 처리 시간이 긴 작업이 전체 응답을 지연시킨다
동기 (문제):
  주문 API -> 결제 서비스 -> 재고 서비스 -> 알림 서비스 -> 응답
  (전체 지연 = 각 서비스 지연의 합)

비동기 (해결):
  주문 API -> 메시지 큐에 이벤트 발행 -> 즉시 응답
  결제 서비스 <- 큐에서 소비
  재고 서비스 <- 큐에서 소비
  알림 서비스 <- 큐에서 소비
  (각 서비스가 독립적으로 처리)

Apache Kafka

분산 이벤트 스트리밍 플랫폼이다.

핵심 특징:

  • 높은 처리량 (초당 수백만 메시지)
  • 메시지 영속성 (디스크에 저장)
  • 컨슈머 그룹을 통한 병렬 처리
  • 토픽 기반 파티셔닝
Kafka 구조:
  Producer -> Topic (Partition 0, 1, 2) -> Consumer Group
                                            Consumer A (P0)
                                            Consumer B (P1)
                                            Consumer C (P2)

사용 사례: 로그 수집, 이벤트 소싱, 실시간 스트리밍 처리, 마이크로서비스 간 통신

RabbitMQ

전통적인 메시지 브로커로, AMQP 프로토콜을 사용한다.

핵심 특징:

  • 다양한 라우팅 패턴 (Direct, Topic, Fanout, Headers)
  • 메시지 확인(Acknowledge) 메커니즘
  • 우선순위 큐 지원
  • 유연한 라우팅

사용 사례: 작업 큐, RPC 패턴, 복잡한 라우팅이 필요한 경우

Amazon SQS

AWS의 완전 관리형 메시지 큐 서비스다.

핵심 특징:

  • 서버리스, 완전 관리형
  • 자동 확장
  • 표준 큐(최소 1회 전달)와 FIFO 큐(정확히 1회 전달) 지원
  • Dead Letter Queue 지원

Kafka vs RabbitMQ vs SQS 비교

특성KafkaRabbitMQSQS
처리량매우 높음중간높음
메시지 순서파티션 내 보장보장 가능FIFO만 보장
메시지 보존설정 기간 동안 보존소비 후 삭제최대 14일
운영 복잡도높음중간낮음 (관리형)
적합한 용도이벤트 스트리밍작업 큐서버리스 워크로드

이벤트 드리븐 아키텍처

메시지 큐를 활용한 이벤트 드리븐 아키텍처는 마이크로서비스 환경에서 핵심 패턴이다.

이벤트 소싱: 상태 변경을 이벤트로 기록한다. 현재 상태는 이벤트를 재생하여 재구성한다.

CQRS: 명령(Command)과 조회(Query)를 분리하여 각각 최적화한다.

이벤트 소싱 예시:
  이벤트 1: 주문 생성 (OrderCreated)
  이벤트 2: 결제 완료 (PaymentCompleted)
  이벤트 3: 배송 시작 (ShippingStarted)
  이벤트 4: 배송 완료 (DeliveryCompleted)

  현재 상태 = 이벤트 1 + 2 + 3 + 4 적용 결과

API 설계

REST vs GraphQL vs gRPC

REST:

  • HTTP 메서드(GET, POST, PUT, DELETE) 기반
  • 리소스 중심 설계
  • 가장 널리 사용됨
  • 오버페칭/언더페칭 문제
GET /api/users/123
POST /api/orders
PUT /api/users/123
DELETE /api/orders/456

GraphQL:

  • 클라이언트가 필요한 데이터를 정확히 요청
  • 단일 엔드포인트
  • 스키마 기반 타입 시스템
  • 오버페칭/언더페칭 해결
query {
  user(id: "123") {
    name
    email
    orders {
      id
      total
    }
  }
}

gRPC:

  • Protocol Buffers 기반 직렬화
  • HTTP/2 사용 (양방향 스트리밍)
  • 높은 성능
  • 마이크로서비스 간 통신에 적합
service UserService {
  rpc GetUser (GetUserRequest) returns (UserResponse);
  rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}

API 비교

특성RESTGraphQLgRPC
프로토콜HTTP/1.1HTTP/1.1HTTP/2
데이터 형식JSONJSONProtocol Buffers
타입 시스템없음 (OpenAPI로 보완)내장내장
스트리밍제한적Subscription양방향 스트리밍
적합한 용도공개 API프론트엔드 BFF마이크로서비스

API 버저닝

API 변경 시 하위 호환성을 유지하는 전략이다.

  • URL 경로 버저닝: /api/v1/users, /api/v2/users
  • 헤더 버저닝: Accept: application/vnd.api.v2+json
  • 쿼리 파라미터: /api/users?version=2

Rate Limiting

API 남용을 방지하고 시스템을 보호하기 위해 요청 횟수를 제한한다.

알고리즘:

  • 토큰 버킷 (Token Bucket): 일정 속도로 토큰을 채우고, 요청 시 토큰을 소비한다. 버스트 트래픽을 허용한다.
  • 슬라이딩 윈도우: 시간 창 내에서 요청 수를 카운팅한다. 경계 문제가 적다.
  • 고정 윈도우: 고정된 시간 간격으로 카운터를 초기화한다. 구현이 단순하다.
토큰 버킷:
  버킷 용량: 10
  충전 속도: 1개/초

  시간 0: 10개 토큰 (요청 5개 -> 5개 남음)
  시간 5: 10개 토큰 (5개 충전, 상한 10)

모니터링과 로깅

메트릭

시스템의 건강 상태를 수치로 파악한다.

핵심 메트릭:

  • 지연 시간 (Latency): p50, p95, p99 백분위 응답 시간
  • 처리량 (Throughput): QPS (Queries Per Second)
  • 오류율 (Error Rate): 4xx, 5xx 응답 비율
  • 자원 사용률: CPU, 메모리, 디스크, 네트워크

도구: Prometheus, Grafana, Datadog, CloudWatch

알림 (Alerting)

메트릭이 임계값을 초과하면 담당자에게 알림을 보낸다.

좋은 알림의 조건:

  • 실행 가능한 알림이어야 한다 (받은 사람이 행동할 수 있어야 한다)
  • 오탐(False Positive)을 최소화해야 한다
  • 심각도를 구분해야 한다 (Critical, Warning, Info)

분산 트레이싱

마이크로서비스 환경에서 요청의 전체 경로를 추적한다.

사용자 요청 (trace-id: abc123)
  -> API Gateway (span-1, 5ms)
    -> 인증 서비스 (span-2, 10ms)
    -> 주문 서비스 (span-3, 50ms)
      -> 결제 서비스 (span-4, 200ms)
      -> 재고 서비스 (span-5, 30ms)
    -> 알림 서비스 (span-6, 15ms)
  총 소요 시간: 310ms

도구: Jaeger, Zipkin, AWS X-Ray, OpenTelemetry

로깅 전략

구조화된 로그: JSON 형식으로 로그를 기록하면 검색과 분석이 용이하다.

{
  "timestamp": "2026-04-12T10:30:00Z",
  "level": "ERROR",
  "service": "order-service",
  "trace_id": "abc123",
  "message": "Payment failed",
  "user_id": "user-789",
  "order_id": "order-456",
  "error_code": "INSUFFICIENT_FUNDS"
}

ELK 스택 (Elasticsearch + Logstash + Kibana) 또는 Loki + Grafana 조합으로 로그를 수집, 저장, 시각화한다.


실전 예제: URL 단축기 설계

면접에서 자주 출제되는 시스템 설계 문제다.

요구사항

기능적 요구사항:

  • 긴 URL을 짧은 URL로 변환
  • 짧은 URL로 접근 시 원래 URL로 리다이렉트
  • 사용자 정의 짧은 URL 지원
  • URL 만료 기능

비기능적 요구사항:

  • 높은 가용성
  • 낮은 지연 시간 (리다이렉트는 빨라야 한다)
  • 짧은 URL은 예측 불가능해야 한다

규모 추정

  • 쓰기: 하루 1억 건, QPS 약 1160
  • 읽기: 쓰기의 10배 = 하루 10억 건, QPS 약 11600
  • 저장: 5년간 약 1800억 건, 레코드당 500B = 약 90TB

고수준 설계

클라이언트 -> 로드밸런서 -> API 서버 -> DB
                              |
                            캐시 (Redis)

단축 키 생성

방법 1: 해시 + 자르기

  • MD5나 SHA-256으로 해시 생성 후 앞 7자를 사용한다
  • Base62 인코딩 (a-z, A-Z, 0-9) = 62의 7승 = 약 3.5조 개 조합

방법 2: 카운터 기반

  • 자동 증가 ID를 Base62로 인코딩한다
  • 분산 환경에서는 Zookeeper나 Snowflake ID를 활용한다

방법 3: 사전 생성

  • 미리 키를 생성해두고 DB에 저장한다
  • 키 생성 서비스(Key Generation Service, KGS)가 키를 관리한다

상세 설계

리다이렉트 흐름:

1. 클라이언트가 short.url/abc123 요청
2. 로드밸런서가 API 서버로 전달
3. API 서버가 Redis 캐시 확인
4. 캐시 히트 -> 301 Redirect 응답
5. 캐시 미스 -> DB 조회 -> 캐시 저장 -> 301 Redirect

301 vs 302 리다이렉트:

  • 301 (Permanent): 브라우저가 캐싱한다. 서버 부하 감소. 분석 데이터 수집 어려움.
  • 302 (Temporary): 매번 서버를 거친다. 클릭 분석 가능. 서버 부하 증가.

실전 예제: 채팅 시스템 설계

요구사항

기능적 요구사항:

  • 1:1 채팅과 그룹 채팅
  • 메시지 전송 및 수신
  • 온라인/오프라인 상태 표시
  • 읽음 확인
  • 파일/이미지 전송
  • 메시지 검색

비기능적 요구사항:

  • 실시간 메시지 전달 (낮은 지연)
  • 메시지 순서 보장
  • 메시지 영속성 (유실 없음)
  • 높은 가용성

통신 프로토콜

HTTP 폴링: 클라이언트가 주기적으로 서버에 새 메시지를 확인한다. 구현이 단순하지만 비효율적이다.

롱 폴링: 서버가 새 메시지가 있을 때까지 연결을 유지한다. 폴링보다 효율적이지만, 타임아웃 관리가 필요하다.

WebSocket: 양방향 실시간 통신이 가능하다. 초기 HTTP 핸드셰이크 후 지속적 연결을 유지한다. 실시간 채팅에 가장 적합하다.

HTTP 폴링:
  클라이언트 -> 서버: 새 메시지? (매 1초)
  서버 -> 클라이언트: 없음
  클라이언트 -> 서버: 새 메시지? (매 1초)
  서버 -> 클라이언트: 있음! (메시지 전달)

WebSocket:
  클라이언트 <-> 서버: 상시 연결 유지
  서버 -> 클라이언트: 새 메시지 즉시 push

고수준 설계

발신자 -> WebSocket 서버 -> 메시지 큐 -> WebSocket 서버 -> 수신자
                |                           |
              메시지 DB              온라인 상태 서비스
                |
            메시지 검색 (Elasticsearch)

메시지 저장소 선택

채팅 메시지의 특성:

  • 순차적 쓰기 (append only)
  • 최근 메시지 읽기가 대부분
  • 랜덤 읽기는 검색 시에만 발생
  • 데이터량이 매우 많음

적합한 DB:

  • Cassandra: 높은 쓰기 처리량, 시간 기반 파티셔닝에 적합
  • HBase: 대규모 순차 쓰기에 최적화

메시지 ID 설계

메시지의 순서를 보장하기 위해 ID가 시간 순으로 정렬 가능해야 한다.

Snowflake ID 방식:

| 타임스탬프 (41비트) | 데이터센터 (5비트) | 머신 (5비트) | 시퀀스 (12비트) |
  • 시간 순 정렬 가능
  • 분산 환경에서 충돌 없음
  • 초당 4096개 ID 생성 가능 (머신당)

온라인 상태 관리

하트비트 방식: 클라이언트가 주기적으로(예: 5초 간격) 서버에 하트비트를 전송한다. 일정 시간 동안 하트비트가 없으면 오프라인으로 판정한다.

사용자 A: 하트비트 전송 (5초 간격)
  서버: 마지막 하트비트 시간 기록
  30초간 하트비트 없음 -> 상태를 "오프라인"으로 변경

그룹 채팅

그룹 채팅에서는 메시지를 그룹 내 모든 멤버에게 전달해야 한다.

소규모 그룹 (수십 명):

  • 각 멤버의 메시지 큐에 직접 복사한다

대규모 그룹 (수천 명):

  • 팬아웃 방식은 비효율적이다
  • 수신자가 직접 메시지를 풀(Pull)하는 방식이 더 적합하다

마무리

시스템 설계의 핵심은 트레이드오프를 이해하는 것이다. 완벽한 시스템은 없으며, 항상 요구사항에 맞는 최적의 선택을 해야 한다.

핵심 원칙 정리:

  • 단순하게 시작하고, 필요할 때 복잡성을 추가한다
  • 확장성은 처음부터 고려하되, 과도한 엔지니어링은 피한다
  • 장애는 반드시 발생한다. 내결함성을 설계에 포함한다
  • 모니터링 없이는 시스템을 운영할 수 없다
  • 데이터는 가장 중요한 자산이다. 백업과 복제를 반드시 구현한다

이 글에서 다룬 개념들은 시스템 설계의 기초에 해당한다. 실제 프로젝트에서 이 개념들을 적용하고, 다양한 시스템 설계 사례를 학습하면서 실력을 키워나가길 바란다.

System Design Fundamentals — Scalability, Availability, Caching, and Message Queues

Table of Contents

  1. What Is System Design
  2. Scalability
  3. Load Balancing
  4. Caching Strategies
  5. CDN
  6. Databases
  7. CAP Theorem
  8. Message Queues
  9. API Design
  10. Monitoring and Logging
  11. Practical Example: URL Shortener
  12. Practical Example: Chat System

What Is System Design

System design is the process of defining the architecture of large-scale software systems. It goes beyond writing code, dealing with how to build scalable and reliable systems that serve millions of users.

Why It Matters

  • In practice: Real services cannot run on a single server. You need to handle traffic growth, failures, and data management.
  • In interviews: Most major tech companies, including FAANG, include system design rounds.
  • Career growth: Architectural thinking is essential for advancing to senior engineering roles.

Interview Approach

There is no single correct answer in a system design interview. What matters is your thought process.

  1. Clarify requirements: Identify functional and non-functional requirements
  2. Estimate scale: Calculate user count, QPS, and storage needs
  3. High-level design: Place the core components
  4. Detailed design: Identify and address bottlenecks
  5. Discuss tradeoffs: Every decision has pros and cons

Scalability

Scalability is a system's ability to handle increasing load.

Vertical Scaling (Scale Up)

Adding more powerful CPUs, more RAM, or larger disks to a single server.

Pros:

  • Simple to implement
  • Easy to maintain data consistency
  • No network calls, so lower latency

Cons:

  • Hardware has physical limits
  • Creates a single point of failure
  • Cost grows exponentially

Horizontal Scaling (Scale Out)

Adding more servers to distribute the load.

Pros:

  • Theoretically unlimited scaling
  • Achieves fault tolerance
  • Cost-effective

Cons:

  • Increases distributed system complexity
  • Harder to maintain data consistency
  • Requires load balancing

Stateless Design

The key to horizontal scaling is stateless design. When servers hold no state, any server can handle any request.

Stateful server:
  User A -> Server 1 (session stored)
  User A -> Server 2 (no session -> error!)

Stateless server:
  User A -> Server 1 (looks up session in external store)
  User A -> Server 2 (looks up session in external store -> works)

State such as sessions and caches is stored in external systems like Redis or Memcached.


Load Balancing

A load balancer distributes incoming traffic across multiple servers.

L4 vs L7 Load Balancing

L4 (Transport Layer):

  • Operates at the TCP/UDP level
  • Routes based on IP and port information only
  • Fast and efficient
  • Example: AWS NLB

L7 (Application Layer):

  • Operates at the HTTP/HTTPS level
  • Can route based on URL paths, headers, cookies, etc.
  • More granular control
  • Example: AWS ALB, Nginx

Load Balancing Algorithms

Round Robin: Distributes requests sequentially to each server. Simple but does not account for differences in server capacity.

Weighted Round Robin: Assigns weights to servers, sending more requests to higher-capacity machines.

IP Hash: Hashes the client IP to always route to the same server. Useful when session persistence is needed, but requires redistribution when servers are added or removed.

Least Connections: Sends requests to the server with the fewest active connections. Effective when request processing times vary.

Round Robin example:

  Request 1 -> Server A
  Request 2 -> Server B
  Request 3 -> Server C
  Request 4 -> Server A  (cycles back)
  Request 5 -> Server B  (cycles back)

Health Checks

The load balancer periodically checks server health. Servers that fail to respond or return errors are removed from the rotation.

Load Balancer:
  /health -> Server A: 200 OK (healthy)
  /health -> Server B: 503 Error (unhealthy -> removed)
  /health -> Server C: 200 OK (healthy)

  Traffic goes to Server A and C only

Caching Strategies

Caching stores frequently accessed data in a fast storage layer to reduce response times.

Cache Aside (Lazy Loading)

The most widely used pattern.

  1. The application checks the cache first
  2. On a cache hit, it returns the cached data
  3. On a cache miss, it queries the database, stores the result in the cache, and returns it
Read flow:
  Client -> Check cache
    Hit -> Return from cache
    Miss -> Query DB -> Store in cache -> Return

Pros: Only caches data that is actually requested. Falls back to the database if the cache fails. Cons: First request always incurs a cache miss. Stale data is possible.

Write Through

Writes data to both the cache and the database simultaneously.

Write flow:
  Client -> Update cache -> Update DB -> Response

Pros: Cache and database stay consistent. Cons: Increased write latency. Caches data that may never be read.

Write Behind (Write Back)

Writes to the cache first and asynchronously persists to the database later.

Write flow:
  Client -> Update cache -> Response (immediate)
  Background: Cache -> DB sync (delayed)

Pros: Very fast writes. Reduces database load. Cons: Risk of data loss if the cache fails.

TTL and Cache Invalidation

TTL (Time To Live): Sets an expiration time on cache entries. Too short reduces cache efficiency; too long causes stale data.

Invalidation strategies:

  • Time-based: Automatic deletion when TTL expires
  • Event-based: Immediate invalidation when data changes
  • Version-based: Include a version in the cache key so new versions automatically create fresh entries
TTL examples:
  SET user:123 "Kim" EX 3600        (expires in 1 hour)
  SET product:456 "Laptop" EX 86400 (expires in 24 hours)

Cache Stampede (Thundering Herd)

When a popular cache entry expires, a flood of requests simultaneously hits the database.

Solutions:

  • Locking: Only one request queries the database; others wait
  • Probabilistic early refresh: Randomly refresh the cache before TTL expires
  • Staggered TTL: Set slightly different TTLs for different cache entries

CDN

A CDN (Content Delivery Network) serves content from edge servers distributed around the world, delivering data from the location closest to the user.

How It Works

User (Seoul) -> Seoul edge server (cache hit) -> Instant response
User (Seoul) -> Seoul edge server (cache miss) -> Origin server -> Cache at edge -> Response

Static Asset Delivery

CDNs are primarily used for serving static content:

  • Images, video, and audio
  • CSS and JavaScript files
  • Font files
  • HTML pages (static sites)

Optimizing Cache Hit Ratio

A higher cache hit ratio means better CDN efficiency.

Optimization techniques:

  • Set appropriate Cache-Control headers
  • Use file versioning (e.g., style.v2.css or content hashing)
  • Use long TTLs for infrequently changing content
  • Normalize query strings

Pull CDN vs Push CDN

Pull CDN: Fetches content from the origin on the first request and caches it. Suitable for high-traffic sites.

Push CDN: Content is pre-uploaded to the CDN. Suitable when content rarely changes.


Databases

SQL vs NoSQL

SQL (Relational Databases):

  • Structured data with strict schemas
  • ACID transaction guarantees
  • Complex queries with JOINs
  • Examples: PostgreSQL, MySQL, Oracle

NoSQL (Non-Relational Databases):

  • Flexible schemas
  • Easy horizontal scaling
  • High read/write throughput
  • Examples: MongoDB, Cassandra, DynamoDB, Redis

Selection criteria:

CriterionSQLNoSQL
Data structureStructuredFlexible
ScalingPrimarily verticalPrimarily horizontal
ConsistencyStrong consistencyEventual consistency (typically)
TransactionsACIDBASE
QueriesComplex JOINs possibleOptimized for simple key-value lookups

Sharding

Distributing data across multiple database instances.

Hash-based sharding: Determines the shard by hashing the key. Achieves even distribution but requires expensive redistribution when shards are added or removed.

user_id % 4 = 0 -> Shard A
user_id % 4 = 1 -> Shard B
user_id % 4 = 2 -> Shard C
user_id % 4 = 3 -> Shard D

Range-based sharding: Assigns shards based on key ranges. Good for range queries but can create hotspots.

Consistent Hashing: Uses a hash ring to minimize key redistribution when nodes are added or removed. Widely used in distributed caches and databases.

Replication

Copying data to multiple nodes to improve availability and read performance.

Primary-Replica (Master-Slave):

  • Writes go to the primary; reads go to replicas
  • On primary failure, a replica is promoted

Multi-Primary (Master-Master):

  • Multiple nodes accept writes
  • Requires conflict resolution strategies
Primary-Replica architecture:

  Writes -> [Primary]
               |
        +------+------+
        |              |
    [Replica 1]    [Replica 2]
        |              |
    Read requests   Read requests

Partitioning

Logically dividing data.

Horizontal partitioning: Splits by rows. Similar to sharding. Vertical partitioning: Splits by columns. Separates frequently accessed columns from rarely accessed ones.


CAP Theorem

A distributed system cannot simultaneously satisfy all three of the following properties.

The Three Properties

Consistency: Every node has the same data. Reads always return the most recent write.

Availability: Every request receives a response. The system responds even if some nodes are down.

Partition Tolerance: The system continues to operate despite network partitions.

Real-World Choices

Network partitions are unavoidable in distributed systems, so the practical choice is between CP and AP.

CP systems (Consistency + Partition Tolerance):

  • May reject some requests during a partition to maintain consistency
  • Examples: HBase, MongoDB (default config), Zookeeper

AP systems (Availability + Partition Tolerance):

  • May return stale data during a partition to maintain availability
  • Examples: Cassandra, DynamoDB, CouchDB
CP system (during partition):
  Node A: latest data = "v2"
  Node B: stale data = "v1" -> Request rejected (consistency first)

AP system (during partition):
  Node A: latest data = "v2"
  Node B: stale data = "v1" -> Returns "v1" (availability first)

PACELC

An extension of CAP that also considers the tradeoff between Latency (L) and Consistency (C) when there is no partition (Else).

  • PA/EL: Availability during partition, low latency otherwise (e.g., DynamoDB)
  • PC/EC: Consistency during partition, consistency otherwise (e.g., HBase)
  • PA/EC: Availability during partition, consistency otherwise (e.g., MongoDB)

Message Queues

A message queue is middleware that enables asynchronous communication. It decouples producers from consumers, reducing coupling and improving scalability.

Why They Are Needed

Problems with synchronous communication:

  • When Service A calls Service B directly, a failure in B affects A as well
  • Traffic spikes can overload downstream services
  • Long-running tasks delay the entire response
Synchronous (problem):
  Order API -> Payment -> Inventory -> Notification -> Response
  (Total latency = sum of each service's latency)

Asynchronous (solution):
  Order API -> Publish event to queue -> Respond immediately
  Payment Service    <- Consumes from queue
  Inventory Service  <- Consumes from queue
  Notification Service <- Consumes from queue
  (Each service processes independently)

Apache Kafka

A distributed event streaming platform.

Key features:

  • High throughput (millions of messages per second)
  • Message durability (persisted to disk)
  • Parallel processing via consumer groups
  • Topic-based partitioning
Kafka architecture:
  Producer -> Topic (Partition 0, 1, 2) -> Consumer Group
                                            Consumer A (P0)
                                            Consumer B (P1)
                                            Consumer C (P2)

Use cases: Log aggregation, event sourcing, real-time stream processing, microservice communication

RabbitMQ

A traditional message broker using the AMQP protocol.

Key features:

  • Multiple routing patterns (Direct, Topic, Fanout, Headers)
  • Message acknowledgment mechanism
  • Priority queues
  • Flexible routing

Use cases: Task queues, RPC patterns, scenarios requiring complex routing

Amazon SQS

A fully managed message queue service from AWS.

Key features:

  • Serverless and fully managed
  • Auto-scaling
  • Standard queues (at-least-once delivery) and FIFO queues (exactly-once delivery)
  • Dead Letter Queue support

Kafka vs RabbitMQ vs SQS Comparison

FeatureKafkaRabbitMQSQS
ThroughputVery highMediumHigh
Message orderingGuaranteed within partitionCan be guaranteedFIFO only
Message retentionConfigurable retention periodDeleted after consumptionUp to 14 days
Operational complexityHighMediumLow (managed)
Best forEvent streamingTask queuesServerless workloads

Event-Driven Architecture

Event-driven architecture built on message queues is a core pattern in microservice environments.

Event Sourcing: Records state changes as events. Current state is reconstructed by replaying events.

CQRS: Separates commands (writes) and queries (reads), optimizing each independently.

Event Sourcing example:
  Event 1: Order Created (OrderCreated)
  Event 2: Payment Completed (PaymentCompleted)
  Event 3: Shipping Started (ShippingStarted)
  Event 4: Delivery Completed (DeliveryCompleted)

  Current state = result of applying Events 1 + 2 + 3 + 4

API Design

REST vs GraphQL vs gRPC

REST:

  • Based on HTTP methods (GET, POST, PUT, DELETE)
  • Resource-oriented design
  • Most widely adopted
  • Over-fetching and under-fetching problems
GET /api/users/123
POST /api/orders
PUT /api/users/123
DELETE /api/orders/456

GraphQL:

  • Clients request exactly the data they need
  • Single endpoint
  • Schema-based type system
  • Solves over-fetching and under-fetching
query {
  user(id: "123") {
    name
    email
    orders {
      id
      total
    }
  }
}

gRPC:

  • Protocol Buffers serialization
  • Uses HTTP/2 (bidirectional streaming)
  • High performance
  • Ideal for inter-service communication
service UserService {
  rpc GetUser (GetUserRequest) returns (UserResponse);
  rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}

API Comparison

FeatureRESTGraphQLgRPC
ProtocolHTTP/1.1HTTP/1.1HTTP/2
Data formatJSONJSONProtocol Buffers
Type systemNone (OpenAPI for docs)Built-inBuilt-in
StreamingLimitedSubscriptionsBidirectional streaming
Best forPublic APIsFrontend BFFMicroservices

API Versioning

Strategies for maintaining backward compatibility when APIs change.

  • URL path versioning: /api/v1/users, /api/v2/users
  • Header versioning: Accept: application/vnd.api.v2+json
  • Query parameter: /api/users?version=2

Rate Limiting

Limits request frequency to prevent API abuse and protect the system.

Algorithms:

  • Token Bucket: Tokens are added at a steady rate; each request consumes a token. Allows burst traffic.
  • Sliding Window: Counts requests within a moving time window. Fewer boundary issues.
  • Fixed Window: Resets the counter at fixed intervals. Simple to implement.
Token Bucket:
  Bucket capacity: 10
  Refill rate: 1/second

  Time 0: 10 tokens (5 requests -> 5 remaining)
  Time 5: 10 tokens (5 refilled, capped at 10)

Monitoring and Logging

Metrics

Quantify the health of the system.

Key metrics:

  • Latency: p50, p95, p99 percentile response times
  • Throughput: QPS (Queries Per Second)
  • Error rate: Ratio of 4xx and 5xx responses
  • Resource utilization: CPU, memory, disk, network

Tools: Prometheus, Grafana, Datadog, CloudWatch

Alerting

Notifies on-call engineers when metrics exceed thresholds.

Good alerting criteria:

  • Alerts must be actionable (the recipient can take action)
  • Minimize false positives
  • Differentiate severity levels (Critical, Warning, Info)

Distributed Tracing

Tracks the complete path of a request across microservices.

User request (trace-id: abc123)
  -> API Gateway (span-1, 5ms)
    -> Auth Service (span-2, 10ms)
    -> Order Service (span-3, 50ms)
      -> Payment Service (span-4, 200ms)
      -> Inventory Service (span-5, 30ms)
    -> Notification Service (span-6, 15ms)
  Total duration: 310ms

Tools: Jaeger, Zipkin, AWS X-Ray, OpenTelemetry

Logging Strategy

Structured logs: Recording logs in JSON format makes them easier to search and analyze.

{
  "timestamp": "2026-04-12T10:30:00Z",
  "level": "ERROR",
  "service": "order-service",
  "trace_id": "abc123",
  "message": "Payment failed",
  "user_id": "user-789",
  "order_id": "order-456",
  "error_code": "INSUFFICIENT_FUNDS"
}

Use the ELK stack (Elasticsearch + Logstash + Kibana) or a Loki + Grafana combination to collect, store, and visualize logs.


Practical Example: URL Shortener

A frequently asked system design interview question.

Requirements

Functional requirements:

  • Convert long URLs to short URLs
  • Redirect short URLs to the original URL
  • Support custom short URLs
  • URL expiration

Non-functional requirements:

  • High availability
  • Low latency (redirects must be fast)
  • Short URLs must not be predictable

Scale Estimation

  • Writes: 100 million per day, approximately 1,160 QPS
  • Reads: 10x writes = 1 billion per day, approximately 11,600 QPS
  • Storage: About 180 billion records over 5 years, 500 bytes per record = approximately 90 TB

High-Level Design

Client -> Load Balancer -> API Server -> DB
                              |
                           Cache (Redis)

Short Key Generation

Method 1: Hash and truncate

  • Generate a hash with MD5 or SHA-256 and use the first 7 characters
  • Base62 encoding (a-z, A-Z, 0-9) = 62 to the 7th power = approximately 3.5 trillion combinations

Method 2: Counter-based

  • Encode an auto-incrementing ID in Base62
  • In distributed environments, use Zookeeper or Snowflake IDs

Method 3: Pre-generation

  • Pre-generate keys and store them in a database
  • A Key Generation Service (KGS) manages the key pool

Detailed Design

Redirect flow:

1. Client requests short.url/abc123
2. Load balancer forwards to API server
3. API server checks Redis cache
4. Cache hit -> 301 Redirect response
5. Cache miss -> Query DB -> Store in cache -> 301 Redirect

301 vs 302 redirect:

  • 301 (Permanent): Browser caches the redirect. Reduces server load. Harder to collect analytics.
  • 302 (Temporary): Every request hits the server. Enables click analytics. Higher server load.

Practical Example: Chat System

Requirements

Functional requirements:

  • One-to-one and group chat
  • Send and receive messages
  • Online/offline status indicators
  • Read receipts
  • File and image sharing
  • Message search

Non-functional requirements:

  • Real-time message delivery (low latency)
  • Message ordering guarantees
  • Message durability (no data loss)
  • High availability

Communication Protocols

HTTP Polling: The client periodically asks the server for new messages. Simple but inefficient.

Long Polling: The server holds the connection until a new message arrives. More efficient than polling but requires timeout management.

WebSocket: Enables real-time bidirectional communication. After an initial HTTP handshake, a persistent connection is maintained. The best fit for real-time chat.

HTTP Polling:
  Client -> Server: New messages? (every 1 second)
  Server -> Client: None
  Client -> Server: New messages? (every 1 second)
  Server -> Client: Yes! (delivers message)

WebSocket:
  Client <-> Server: Persistent connection
  Server -> Client: New message pushed immediately

High-Level Design

Sender -> WebSocket Server -> Message Queue -> WebSocket Server -> Receiver
               |                                     |
           Message DB                        Presence Service
               |
         Message Search (Elasticsearch)

Message Store Selection

Characteristics of chat messages:

  • Sequential writes (append only)
  • Most reads are for recent messages
  • Random reads occur only during search
  • Very large data volumes

Suitable databases:

  • Cassandra: High write throughput, well-suited for time-based partitioning
  • HBase: Optimized for large-scale sequential writes

Message ID Design

Message IDs must be sortable by time to guarantee ordering.

Snowflake ID approach:

| Timestamp (41 bits) | Datacenter (5 bits) | Machine (5 bits) | Sequence (12 bits) |
  • Sortable by time
  • No collisions in distributed environments
  • Generates up to 4,096 IDs per second per machine

Presence Management

Heartbeat approach: Clients send a periodic heartbeat (e.g., every 5 seconds). If no heartbeat is received for a set period, the user is marked offline.

User A: Sends heartbeat (every 5 seconds)
  Server: Records last heartbeat time
  No heartbeat for 30 seconds -> Status changed to "offline"

Group Chat

In group chat, messages must be delivered to all group members.

Small groups (tens of members):

  • Copy the message directly into each member's message queue

Large groups (thousands of members):

  • Fan-out becomes inefficient
  • A pull-based approach where recipients fetch messages is more suitable

Conclusion

The essence of system design is understanding tradeoffs. No system is perfect, and the goal is always to make the best choices for the given requirements.

Key principles:

  • Start simple and add complexity only when needed
  • Consider scalability from the start, but avoid over-engineering
  • Failures will happen. Build fault tolerance into the design
  • A system cannot be operated without monitoring
  • Data is the most valuable asset. Always implement backups and replication

The concepts covered here form the foundation of system design. Apply them in real projects, study diverse system design case studies, and continue building your skills.