시스템 설계 방법론
들어가며
시스템 설계는 머릿속에서 짜낸 아키텍처 다이어그램이 아니라, 체계적인 방법론입니다. 면접의 시스템 설계 문제든 실무의 아키텍처 설계든, 비슷한 사고 프레임워크를 따릅니다: 먼저 문제를 명확히 이해하고, 규모를 추정한 후, 설계方案을 제시하고, 마지막으로 심화 최적화합니다.
이 글에서 무엇을 배우게 되나요?
이 장을 마치면 다음을 얻게 됩니다:
- 설계 프로세스: 시스템 설계의 4단계 프레임워크 파악
- 용량 추정: '봉투 뒷면 추정' 기법 습득
- 일반적 패턴: 캐시, 샤딩, 메시지 큐 등 핵심 패턴 숙지
- 트레이드오프 사고: 아키텍처 설계에서의 trade-off 사고 이해
- 실전 사례: 단축 URL 서비스, 피드 스트림 등의 사례로 설계 과정 이해
| 장 | 내용 | 핵심 개념 |
|---|---|---|
| 1장 | 설계 4단계법 | 요구사항 명확화, 용량 추정, 아키텍처 설계, 심화 최적화 |
| 2장 | 용량 추정 | QPS, 저장소, 대역폭, 봉투 뒷면 추정 |
| 3장 | 핵심 설계 패턴 | 캐시, 샤딩, 메시지 큐, CDN |
| 4장 | 트레이드오프 사고 | 일관성 vs 가용성, 성능 vs 비용 |
| 5장 | 고전 사례 | 단축 URL 서비스, 피드 스트림, 플래시 세일 시스템 |
1. 시스템 설계 4단계법
시스템 설계는 처음부터 아키텍처 다이어그램을 그리는 것이 아닙니다. 면접이든 실전이든 구조화된 프로세스를 따라야 합니다.
왜 먼저 요구사항을 명확히 해야 하나요?
많은 사람이 문제를 받자마자 다이어그램을 그리기 시작하고, '정확하지만 면접관이 원하지 않는' 시스템을 설계하게 됩니다. 5분 동안 요구사항을 명확히 물어보면, 이후 30분의 재작업을 피할 수 있습니다.
일반적인 명확화 질문:
- 시스템의 핵심 기능은 무엇인가요? (모든 기능을 설계하지 마세요)
- 사용자 규모는 어느 정도인가요? (분산 필요 여부를 결정)
- 읽기/쓰기 비율은? (캐시 전략을 결정)
- 데이터를 얼마나 오래 보관해야 하나요? (저장 方案을 결정)
2. 용량 추정: 봉투 뒷면의 예술
'봉투 뒷면 추정'(Back-of-envelope estimation)은 시스템 설계의 핵심 기술입니다. 정확한 계산이 필요한 것이 아니라, 규모만 파악하면 됩니다.
자주 쓰는 환산 치트 시트
| 규모 | 환산 | 기억 팁 |
|---|---|---|
| 1일 | 86,400초 | 약 10만 초 |
| 1일 1억 건 요청 | 약 1,200 QPS | 10만으로 나눔 |
| 1KB x 1억 | 약 100GB | 1억 건의 작은 레코드 |
| 1MB x 100만 | 약 1TB | 100만 장의 이미지 |
2-8 법칙의 추정 적용
대부분의 시스템은 80/20 법칙을 따릅니다: 20%의 데이터가 80%의 요청을 담당합니다. 이는 다음을 의미합니다:
- 캐시 크기 ≈ 전체 데이터량 x 20%
- 핫 키 QPS ≈ 전체 QPS의 80%가 20%의 키에 집중
- 캐시 적중률 목표 ≈ 80% 이상(이보다 낮으면 캐시 전략에 문제가 있음)
3. 핵심 설계 패턴
시스템 설계에서 반복적으로 등장하는 패턴입니다. 이를 마스터하면 대부분의 시나리오에 대응할 수 있습니다.
3.1 캐시 패턴
| 패턴 | 읽기 경로 | 쓰기 경로 | 적용 시나리오 |
|---|---|---|---|
| Cache-Aside | 먼저 캐시 조회, miss면 DB 조회 후 캐시에 채움 | 먼저 DB에 쓰고, 캐시 삭제 | 범용 시나리오, 가장 널리 사용 |
| Read-Through | 캐시 계층이 자동으로 DB에서 로드 | Cache-Aside와 동일 | 캐시 프레임워크 지원 필요 |
| Write-Behind | Cache-Aside와 동일 | 먼저 캐시에 쓰고, 비동기로 DB에 씀 | 쓰기 집약형, 데이터 손실 감당 가능 |
왜 '캐시 업데이트'가 아니라 '캐시 삭제'인가요?
캐시 업데이트는 동시성 환경에서 데이터 불일치가 발생하기 쉽습니다: 스레드 A와 B가 동시에 업데이트할 때, A가 먼저 DB에 쓰지만 B가 먼저 캐시를 업데이트하면, 캐시에는 B의 이전 값이 남게 됩니다. 반면 캐시 삭제는 다음 읽기 요청이 DB에서 다시 로드하게 만들어, 자연스럽게 이 문제를 피할 수 있습니다.
3.2 샤딩
단일 테이블의 데이터가 천만 건을 넘거나, 단일 DB의 QPS가 병목에 도달하면 샤딩을 고려해야 합니다.
| 전략 | 방법 | 장점 | 단점 |
|---|---|---|---|
| 수직 분할 | 비즈니스 도메인별로 데이터베이스 분할 | 비즈니스 결합 해제, 독립 확장 | 크로스 DB JOIN이 어려움 |
| 수평 분할 | 같은 테이블을 규칙에 따라 여러 테이블로 분할 | 단일 테이블 데이터량 제어 가능 | 샤딩 키 선택이 핵심 |
| 수직 테이블 분할 | 큰 필드를 독립 테이블로 분리 | IO 감소, 쿼리 효율 향상 | 추가 JOIN 필요 |
샤딩 키 선택 원칙:
- 가장 자주 조회되는 필드 선택(예: user_id)
- 데이터 분포가 균등해야 하며, 핫스팟을 피해야 함
- 같은 사용자의 데이터가 같은 샤드에 있도록(크로스 샤드 쿼리 감소)
3.3 메시지 큐
메시지 큐는 분산 시스템의 '완충기'로, 핵심 역할은 결합 해제, 비동기, 피크 평탄화입니다.
| 시나리오 | 큐 미사용 | 큐 사용 |
|---|---|---|
| 주문 후 알림 발송 | 주문 API가 알림 서비스를 동기 호출, 알림 실패로 주문도 실패 | 주문 성공 후 메시지 발송, 알림 서비스가 비동기 소비 |
| 플래시 세일 | 순간 트래픽이 데이터베이스를 마비 | 요청이 먼저 큐에 들어가고, 백엔드가 처리 능력에 따라 소비 |
| 데이터 동기화 | 서비스 A가 서비스 B의 API를 직접 호출 | 서비스 A가 이벤트를 발행하고, 서비스 B가 구독하여 처리 |
4. 트레이드오프 사고: 은탄환은 없다
아키텍처 설계의 본질은 트레이드오프(Trade-off)입니다. 모든 결정에는 대가가 따르며, 핵심은 그 대가를 이해하고 현재 단계에 적합한 선택을 하는 것입니다.
| 트레이드오프 차원 | 선택 A | 선택 B | 결정 기준 |
|---|---|---|---|
| 일관성 vs 가용성 | 강 일관성(CP) | 고가용성(AP) | 비즈니스가 일시적 불일치를 감당할 수 있는가? |
| 성능 vs 비용 | 전체 캐시 | 필요 시 캐시 | 데이터량과 예산 |
| 단순함 vs 유연성 | 모놀리식 아키텍처 | 마이크로서비스 | 팀 규모와 비즈니스 복잡도 |
| 실시간 vs 배치 | 스트림 처리 | 배치 처리 | 데이터 시효성 요구 |
| 자체 구축 vs 관리형 | 직접 MySQL 구축 | 클라우드 데이터베이스 RDS 사용 | 운영 역량과 비용 |
아키텍처 결정 기록(ADR)
모든 중요한 아키텍처 결정은 기록해야 합니다: 배경이 무엇인지, 어떤 方案을 고려했는지, 왜 이것을 선택했는지, 어떤 대가가 있는지. 책임을 전가하기 위한 것이 아니라, 나중에 오는 사람들이 '왜 그렇게 설계했는지' 이해할 수 있도록 하기 위함입니다.
형식은 간단합니다:
- 제목: YYY 대신 XXX 사용
- 배경: 우리가 직면한 문제
- 결정: 선택한 方案
- 이유: 왜 이것을 선택했는지
- 대가: 이 결정의 단점과 리스크
흔한 잘못된 트레이드오프
| 오류 | 표현 | 올바른 접근 |
|---|---|---|
| 조기 최적화 | DAU 1,000인데 샤딩 도입 | 먼저 단일 DB로 시작하고, 병목에 도달하면 분할 |
| 기술 주도 | "Kafka를 쓰고 싶다"가 아니라 "비동기가 필요하다" | 기술이 아니라 문제에서 출발 |
| 운영 비용 무시 | 최적의 方案을 선택했지만 팀이 유지보수할 수 없음 | 方案은 팀의 역량과 일치해야 함 |
| 완벽한 일관성 추구 | 모든 시나리오에 분산 트랜잭션 사용 | 대부분의 시나리오에서 최종 일관성이면 충분 |
5. 고전 사례
세 가지 고전 사례를 통해 앞서 배운 방법론을 연결해 보겠습니다.
5.1 단축 URL 서비스(TinyURL)
단축 URL 서비스는 시스템 설계 면접의 고전적인 문제로, 작지만 모든 것을 갖추고 있습니다.
요구사항 명확화:
- 핵심 기능: 긴 URL → 짧은 URL(쓰기), 짧은 URL → 리다이렉트(읽기)
- 읽기/쓰기 비율: 약 100:1(읽기가 쓰기보다 훨씬 많음)
- 일일 리다이렉트: 1억 건
- 단축 URL은 만료되지 않음
용량 추정:
| 지표 | 계산 | 결과 |
|---|---|---|
| 쓰기 QPS | 1억 / 100 / 86400 | 약 12 QPS |
| 읽기 QPS | 1억 / 86400 | 약 1,200 QPS |
| 피크 읽기 QPS | 1,200 x 3 | 약 3,600 QPS |
| 5년 저장소 | 100만/일 x 365 x 5 x 100B | 약 18GB |
| 캐시(20%) | 18GB x 20% | 약 3.6GB |
아키텍처 설계:
쓰기 경로: 클라이언트 → API Server → ID 생성기 → Base62 인코딩 → MySQL + Redis에 기록
읽기 경로: 클라이언트 → CDN → API Server → Redis 조회 → 302 리다이렉트
↓ (cache miss)
MySQL 조회 → Redis에 채움핵심 설계 결정:
- 단축 코드 생성: Snowflake 분산 ID + Base62 인코딩, 해시 충돌 방지
- 캐시 전략: Cache-Aside, 핫 단축 URL은 CDN으로 가속
- 데이터베이스: 단일 테이블로 충분(18GB는 작음), 단축 코드로 인덱스
5.2 피드 스트림 시스템
소셜 플랫폼의 피드 스트림(모멘트, 마이크로블로그 홈)은 또 다른 고전적인 문제입니다.
핵심 과제: 사용자가 게시물을 올리면, 모든 팔로워가 볼 수 있도록 하려면?
| 方案 | 방법 | 장점 | 단점 |
|---|---|---|---|
| 풀 모델(Pull) | 읽기 시 팔로워의 게시물을 실시간으로 집계 | 쓰기가 단순, 저장 공간 적음 | 읽기가 느리고, 팔로우가 많으면 지연 증가 |
| 푸시 모델(Push) | 발행 시 모든 팔로워의 받은편지함에 기록 | 읽기가 매우 빠름 | 대형 계정이 게시하면 쓰기 확산이 심각 |
| 푸시-풀 결합 | 일반 사용자는 푸시, 대형 계정은 풀 | 읽기/쓰기 성능의 균형 | 구현이 복잡 |
푸시-풀 결합 方案:
- 팔로워 수 < 1만: 발행 시 모든 팔로워의 피드 캐시에 푸시(푸시 모델)
- 팔로워 수 > 1만: 푸시하지 않고, 팔로워가 읽을 때 실시간으로 풀(풀 모델)
- 사용자가 피드를 열 때: 푸시된 내용 + 대형 계정의 실시간 풀 내용을 병합하여 시간순 정렬
5.3 플래시 세일 시스템
플래시 세일의 핵심 과제: 순간 초고동시성 + 재고 초과 판매 방지.
트래픽 특징:
- 이벤트 시작 전: 대량의 사용자가 페이지를 새로고침하며 대기
- 이벤트 시작 순간: QPS가 평소의 100배 이상일 수 있음
- 이벤트 종료 후: 트래픽이 빠르게 감소
다층 피크 평탄화 전략:
사용자 요청 → CDN(정적 페이지) → 게이트웨이(속도 제한) → 메시지 큐(피크 평탄화) → 재고 서비스(차감)| 계층 | 전략 | 효과 |
|---|---|---|
| 프론트엔드 | 버튼 비활성화 + 무작위 지연 + CAPTCHA | 봇 필터링, 요청 분산 |
| CDN | 정적 리소스 캐시 | 페이지 요청의 90% 감소 |
| 게이트웨이 | 토큰 버킷 속도 제한 | 시스템이 감당할 수 있는 트래픽만 통과 |
| 메시지 큐 | 요청을 큐에 넣고 비동기 처리 | 피크 평탄화, 데이터베이스 보호 |
| 재고 서비스 | Redis 사전 차감 + Lua 원자적 연산 | 초과 판매 방지, 밀리초 응답 |
플래시 세일의 핵심 원칙
- 상류에서 최대한 차단: CDN에서 막을 수 있는 것은 애플리케이션 계층까지 가지 않게 합니다
- 읽기/쓰기 분리: 상품 상세 페이지는 캐시를 사용하고, 주문만 데이터베이스를 사용합니다
- 비동기 처리: 사용자가 '구매하기'를 클릭한 후 즉시 '대기 중'을 반환하고, 백그라운드에서 비동기 처리합니다
- 폴백 方案: 속도 제한, 서킷 브레이커, 저하 — 어느 계층에 문제가 생겨도 Plan B가 있습니다
요약
시스템 설계는 실천적인 기술로, 핵심은 구조화된 사고와 트레이드오프에 있습니다.
이 장의 핵심 요점을 되돌아보겠습니다:
- 4단계법 프레임워크: 요구사항 명확화 → 용량 추정 → 아키텍처 설계 → 심화 최적화, 각 단계를 건너뛸 수 없습니다
- 봉투 뒷면 추정: 정확할 필요는 없고, 규모만 파악하면 되며, 아키텍처 결정을 안내하는 데 사용합니다
- 핵심 패턴: 캐시, 샤딩, 메시지 큐, CDN, 속도 제한/서킷 브레이커 — 이것들이 시스템 설계의 '블록'입니다
- 트레이드오프 사고: 완벽한 方案은 없고, 현재 단계에 적합한 方案만 있으며, 각 결정의 이유와 대가를 기록하세요
- 고전 사례: 단축 URL 서비스로 기초 연습, 피드 스트림으로 푸시/풀 모델 연습, 플래시 세일로 고동시성 연습 — 이 세 가지를 마스터하면 응용력이 생깁니다
더 읽어보기
- System Design Interview - Alex Xu의 시스템 설계 면접 고전
- Designing Data-Intensive Applications - Martin Kleppmann의 데이터 집약적 애플리케이션 설계
- The System Design Primer - GitHub에서 가장 포괄적인 시스템 설계 학습 자료
- ByteByteGo - Alex Xu의 시스템 설계 시각화 블로그