Skip to content

✍️ 필사 모드: 시스템 설계 기초 완전 가이드 — 확장성, 가용성, 캐싱, 메시지 큐

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

목차

  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)하는 방식이 더 적합하다

마무리

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

핵심 원칙 정리:

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

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

현재 단락 (1/436)

1. [시스템 설계란](#시스템-설계란)

작성 글자: 0원문 글자: 10,652작성 단락: 0/436