DDIA 1장 — 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션
책: 데이터 중심 애플리케이션 설계 (Designing Data-Intensive Applications), Martin Kleppmann, O'Reilly 챕터: 1장 — Reliable, Scalable, and Maintainable Applications
이 문서는 1장을 학습하며 대화한 내용을 정리한 노트입니다. 책의 세 축인 신뢰성·확장성·유지보수성을 중심으로, 논의한 개념과 추가로 학습한 내용(SLA/SLO/SLI, 에러 버짓 등)을 함께 담았습니다.
목차
- 신뢰성 (Reliability)
- 확장성 (Scalability)
- 2-1. 부하 기술하기
- 2-2. 성능 기술하기
- 2-3. SLA / SLO / SLI
- 2-4. 부하 대응하기
- 유지보수성 (Maintainability)
- 핵심 요약
1. 신뢰성 (Reliability)
정의
"무언가 잘못되더라도 시스템이 올바르게 동작하는 것"
= **결함(fault)**이 **장애(failure)**로 이어지지 않도록 만드는 능력
결함(Fault) vs 장애(Failure) — 꼭 구분
| 구분 | 의미 | 예시 |
|---|---|---|
| Fault (결함) | 구성 요소 하나가 사양에서 벗어난 상태 | 디스크 한 개가 고장남 |
| Failure (장애) | 시스템 전체가 사용자에게 서비스를 못 하는 상태 | 서비스가 다운되어 접속 불가 |
신뢰성 있는 시스템의 목표는 "결함을 없애는 것"이 아니라 "결함이 장애가 되지 않게 막는 것".
그래서 Netflix의 Chaos Monkey처럼 의도적으로 결함을 일으켜 내결함성(fault tolerance)을 훈련합니다. 결함은 피할 수 없으므로, 결함에 내성을 갖게 설계하는 쪽이 현실적입니다.
결함의 세 가지 분류
1) 하드웨어 결함
- 디스크 고장, RAM 오류, 정전, 네트워크 케이블 절단
- 특징: 랜덤하고 독립적으로 발생 → 중복(redundancy)으로 대응
- 과거엔 이중화(RAID, UPS, 이중 전원)로 해결했으나, 대규모 시스템에서는 소프트웨어 레벨의 내결함성이 더 중요해짐
2) 소프트웨어 결함
- 특정 입력에서 터지는 버그, 메모리 누수, 폭주 프로세스, 의존 서비스 지연
- 특징: 체계적(systematic) — 같은 조건이면 모든 노드에서 동시에 터짐
- 중복으로 막을 수 없기에 하드웨어 결함보다 훨씬 치명적일 수 있음
3) 인적 결함 (Human Error)
- 설정 실수, 잘못된 배포, 운영 중 실수 — 실제 가장 큰 장애 원인
- 대응 방법:
- 실수하기 어려운 인터페이스 설계
- 샌드박스·스테이징 환경에서 먼저 실험
- 철저한 테스트 (단위 / 통합 / 수동)
- 빠른 롤백이 가능한 배포
- 상세한 모니터링·텔레메트리
- 좋은 교육과 운영 절차
100% 신뢰성은 불가능하다
- 완벽한 신뢰성은 존재하지 않으므로, SLO/SLA로 수준을 정량화
- 신뢰성은 비싸다 → 필요한 수준만 확보하고 비용·개발 속도와 균형
2. 확장성 (Scalability)
"증가하는 부하에 대처하는 시스템의 능력"
자원을 추가하거나 구조를 바꿔서 성능을 유지할 수 있는가?
확장성은 단일 개념이 아니라 세 단계의 사고 과정으로 이해해야 합니다.
[ 부하 기술 ] → [ 성능 기술 ] → [ 부하 대응 ]
무엇이 힘든가? 어떻게 측정하나? 어떻게 대처하나?
2-1. 부하 기술하기 (Describing Load)
부하 매개변수(load parameters) = 시스템의 현재 부하를 숫자로 기술하기 위해 선택하는 지표. 시스템 아키텍처에 따라 달라지며, **"무엇이 이 시스템을 힘들게 하는가"**를 가장 잘 나타내는 값을 고르는 것이 핵심.
| 시스템 | 부하 매개변수 예시 |
|---|---|
| 웹 서버 | 초당 요청 수 |
| 데이터베이스 | 읽기/쓰기 비율 |
| 채팅방 | 동시 접속자 수 |
| 캐시 | 적중률(hit rate) |
실제 예시
1) 트위터 (책의 대표 예시) 초당 트윗 작성량(평균 4.6k, 피크 12k)보다 **"한 사용자가 평균 몇 명에게 트윗을 전달해야 하는가(팬아웃)"**가 진짜 부하 매개변수였다. 팔로워 수 분포가 극단적이라(셀럽은 3천만+), 단순 요청 수가 아니라 이 분포가 타임라인 설계를 좌우함.
2) 이커머스 (예: 쿠팡/아마존) "초당 주문 수"보다 "상품 조회 대비 주문 비율", 그리고 **"블랙프라이데이 같은 순간의 피크 부하"**가 더 중요한 매개변수. 평소 부하가 아니라 피크 시점의 쓰기 집중도가 시스템 설계를 결정.
핵심: "뻔한" 지표가 진짜 병목을 가리키지 않을 수 있다. 시스템 내부에서 무엇이 증폭되는지를 보는 것이 중요.
2-2. 성능 기술하기 (Describing Performance)
애플리케이션의 성능은 보통 **응답 시간(response time)**으로 기술한다.
평균이 아니라 백분위를 써야 하는 이유
- 산술 평균은 극단값에 휘둘려 전형적인 사용자 경험을 나타내지 못함
- 중앙값(p50) = "절반의 사용자가 이보다 빠르게 응답받는다" → 체감 성능에 가까움
- 상위 백분위(p95, p99, p999) = 꼬리 지연(tail latency)
꼬리 지연(tail latency)이 중요한 이유
느린 요청을 겪는 사용자가 오히려 VIP 아마존 사례: 계정에 데이터가 많이 쌓인 사용자(= 구매 이력이 많은 우량 고객)가 느린 응답을 경험하기 쉬움.
꼬리 지연 증폭 (tail latency amplification) 사용자 요청 1건이 백엔드에서 여러 병렬 호출로 쪼개지면, 그중 하나만 느려도 전체가 느려짐. 호출이 많을수록 p99의 느린 요청을 마주칠 확률 ↑.
Head-of-line blocking 서버의 병렬 처리 능력이 제한적이므로, 앞선 느린 요청이 뒤의 빠른 요청까지 대기시킴. 큐잉 지연이 꼬리를 만드는 주범.
백분위 선택 기준
- 아마존은 p999까지 관찰 (1000명 중 1명)
- 그 이상은 개선 비용 대비 효과가 낮아 대부분 포기
2-3. SLA / SLO / SLI (성능을 계약으로 만들기)
세 개념은 세트로 이해해야 한다.
한 줄 정의
| 용어 | 풀이 | 역할 |
|---|---|---|
| SLA (Service Level Agreement) | 제공자와 고객 간 계약 | 외부용, 위반 시 패널티 |
| SLO (Service Level Objective) | SLA를 지키기 위한 내부 목표 | 팀이 달성해야 할 기준 |
| SLI (Service Level Indicator) | 실제로 측정되는 지표 값 | 현재 상태의 수치 |
관계 한 문장
SLI(측정값) ≥ SLO(내부 목표) 를 유지해야, SLA(대외 약속)를 위반하지 않는다.
구체 예시 — 데이터베이스 API
| 구분 | 내용 |
|---|---|
| SLA | 월 99.9% 가동률 보장, 미달 시 요금 10% 환불 |
| SLO | 월 99.95% 가동률 유지 (SLA보다 엄격) |
| SLI | 실제 측정값 — 예: 이번 달 99.97% |
에러 버짓(Error Budget)
SLA와 SLO 사이의 여유분 = "망가질 수 있는 허용치"
- 예: SLA 99.9% / SLO 99.95% → 그 0.05% 차이 동안 배포 실험, 장애 대응, 계획 다운타임 감수 가능
- 에러 버짓이 남아 있으면 → 새 기능을 과감히 출시
- 다 쓰면 → 안정화에 집중
- 개발 속도 ↔ 안정성 사이의 의사결정 도구
응답 시간과의 연결
꼬리 지연이 곧 SLO의 측정 기준이 되는 경우가 많다:
- SLA: "응답 시간 300ms 이하"
- SLO: "p99 응답 시간 200ms 이하를 월 99.9% 시간 동안 유지"
- SLI: 지난 주 측정된 p99 값 — 예: 185ms
즉, "어떤 백분위로, 얼마 동안, 얼마나 자주 지켜야 하는가"를 정의하는 것이 SLO.
2-4. 부하 대응하기 (Coping with Load)
부하에 대응하는 방식은 크게 두 축이 있다.
A. 자원 추가형 확장 — 스케일업 / 스케일아웃
| 구분 | 스케일업 (Scale-Up) | 스케일아웃 (Scale-Out) |
|---|---|---|
| 방식 | 한 대의 성능을 올림 | 여러 대로 분산 (shared-nothing) |
| 장점 | 구조 단순, 코드 수정 최소 | 저렴한 장비로 무한 확장, 장애 격리 |
| 단점 | 하드웨어 상한, 고사양은 비선형적 비용, SPOF | 분산 시스템 복잡도 폭발(일관성, 조정 등) |
실무 감각
- Stateless 서비스는 스케일아웃이 쉬움
- **Stateful(특히 DB)**은 스케일업이 쉬움
- 대부분 혼합: 업으로 버티다가 한계가 오면 아웃(샤딩/복제)으로 전환
B. 구조/로직 변경형 확장 — 아키텍처 재설계
부하 특성이 바뀌면 구조 자체를 바꿔야 할 때가 있다.
- 트위터: 팬아웃 문제 해결을 위해 **"쓰기 시점에 타임라인을 미리 만들어두는 구조"**로 전환
- 읽기 과다 → 캐시/읽기 복제본 추가
- 쓰기 과다 → 샤딩·이벤트 큐 도입
책의 중요한 메시지 "한 번에 모든 규모에 맞는 확장 아키텍처는 없다." 부하가 10배 늘면, 기존 아키텍처로는 안 되고 구조 자체를 다시 짜야 하는 경우가 많다. 그래서 초기부터 과도한 확장 설계를 하지 않는 것이 합리적.
3. 유지보수성 (Maintainability)
"시간이 지나도 여러 사람이 시스템을 생산적으로 이어받아 작업할 수 있는가"
소프트웨어 비용의 대부분은 초기 개발이 아니라 이후의 유지보수에서 발생한다. 버그 수정, 운영, 장애 대응, 신규 기능, 기술 부채, 온보딩 — 모두 유지보수 비용.
책은 이를 세 가지 설계 원칙으로 정리한다.
운용성 (Operability) — "운영팀을 편하게 하라"
좋은 운영은 나쁜 소프트웨어의 한계를 보완할 수 있지만, 좋은 소프트웨어라도 운영이 엉망이면 신뢰성 있게 동작할 수 없다.
운영팀이 하는 일:
- 시스템 모니터링, 문제 발생 시 빠른 복구
- 장애·성능 저하 원인 추적
- 소프트웨어·플랫폼 최신 상태 유지 (보안 패치)
- 향후 문제 예측 및 예방 (디스크 용량 등)
- 배포·설정 변경 등 운영 작업 수행
- 조직 지식의 유지
좋은 운용성의 조건: 반복 작업을 자동화하기 쉽고, 관찰 가능하며(observable), 동작이 예측 가능하고, 장애 시 빠르게 복구 가능.
단순성 (Simplicity) — "복잡도를 제거하라"
프로젝트가 커지면서 생기는 복잡도의 증상:
- 상태 공간 폭발, 강한 결합, 꼬인 의존성
- 일관성 없는 네이밍, 성능을 위한 해킹
- "여기 건드리면 저기가 터진다"는 공포
본질적 복잡도 vs 우발적 복잡도
- 본질적(essential): 문제 자체의 어려움 (예: 분산 트랜잭션의 근본적 난해함)
- 우발적(accidental): 구현 방식에서 우리가 만들어낸 복잡도 → 없앨 수 있음
최고의 무기 = 추상화(abstraction) 좋은 추상화는 복잡한 내부를 깔끔한 인터페이스 뒤에 숨기고 재사용 가능하게 만든다.
- 예: 고수준 언어가 CPU·메모리·syscall을 숨김
- 예: SQL이 디스크·인덱스 구조를 숨김
발전성 (Evolvability) — "변화를 쉽게 만들라"
요구사항은 반드시 바뀐다:
- 새 기능, 비즈니스 우선순위 변화, 사용자 요구 변화
- 새로운 플랫폼, 법/규제 변화, 부하 패턴 변화
이 변화를 유연하게 흡수하려면, 단순함과 좋은 운용성이 전제되어야 한다. 단순하고 이해하기 쉬운 시스템이 결국 바꾸기도 쉬운 시스템이다.
세 원칙의 관계
운용성
│
↓ 떠받치고
단순성 ──→ 발전성
↑ ↑
복잡도 제거 변화 수용
- 단순성 → 빠른 이해, 적은 버그 → 운용성 ↑
- 단순성 + 운용성 → 과감한 변경 가능 → 발전성
4. 핵심 요약
신뢰성
결함(fault) ≠ 장애(failure). 목표는 결함 제거가 아니라 결함이 장애로 번지지 않게 하는 내결함성. 하드웨어(독립적) / 소프트웨어(체계적) / 인적(최다 원인) 세 종류. 100% 신뢰성은 불가능하므로 SLO로 허용 수준을 정의.
확장성
세 단계로 이해: ① 부하를 기술(부하 매개변수) → ② 성능을 기술(백분위, 꼬리 지연, SLO) → ③ 부하에 대응(스케일업/아웃 + 구조 재설계). 부하 특성이 바뀌면 아키텍처도 바뀌어야 하므로, 만능 설계는 없다.
유지보수성
운용성(운영하기 쉬움) + 단순성(우발적 복잡도 제거) + 발전성(변화 수용). 소프트웨어 비용의 대부분은 유지보수에서 발생. 좋은 추상화가 단순성의 핵심 무기이며, 단순성과 운용성이 전제되어야 발전성이 확보됨.
부록. 핵심 용어 정리
| 용어 | 의미 |
|---|---|
| Fault (결함) | 시스템 구성 요소 하나가 사양에서 벗어난 상태 |
| Failure (장애) | 시스템 전체가 사용자에게 서비스를 제공하지 못하는 상태 |
| Fault Tolerance (내결함성) | 결함이 있어도 장애로 번지지 않게 하는 성질 |
| Load Parameter (부하 매개변수) | 시스템의 부하를 기술하는 지표 (예: 팬아웃) |
| Percentile (백분위, p50/p95/p99/p999) | 응답 시간 분포를 측정하는 지표 |
| Tail Latency (꼬리 지연) | 상위 백분위(p95~p999) 응답 시간 |
| SLA (서비스 수준 협약) | 제공자와 고객 사이의 외부 계약 |
| SLO (서비스 수준 목표) | SLA를 지키기 위한 내부 목표 |
| SLI (서비스 수준 지표) | 실제로 측정되는 값 |
| Error Budget (에러 버짓, 오류 예산) | SLA와 SLO의 여유분. 개발 속도와 안정성의 균형추 |
| Scale-Up (스케일업, 수직 확장) | 한 대의 성능을 올려서 부하를 감당 |
| Scale-Out (스케일아웃, 수평 확장) | 여러 대에 부하를 분산 |
| Shared-Nothing (비공유 아키텍처) | 각 노드가 자원을 공유하지 않는 스케일아웃 구조 |
| Stateless (무상태) / Stateful (상태 보존) | 상태 없음(확장 쉬움) / 상태 가짐(확장 어려움) |
| Accidental Complexity (우발적 복잡도) | 문제 본질이 아닌, 구현 방식에서 생긴 불필요한 복잡도 |
| Essential Complexity (본질적 복잡도) | 문제 자체의 어려움에서 비롯된, 피할 수 없는 복잡도 |
| Operability (운용성) | 시스템을 운영하기 쉽게 만드는 성질 |
| Simplicity (단순성) | 우발적 복잡도를 제거하여 이해하기 쉽게 만드는 성질 |
| Evolvability (발전성) | 변화하는 요구사항에 쉽게 적응하는 성질 |
본 문서는 O'Reilly 『데이터 중심 애플리케이션 설계』 1장 학습 대화 내용을 기반으로 정리되었습니다.