1장. 사용자 수에 따른 규모 확장성
규모 확장성과 관계된 설계 문제 푸는 데에 유용한 지식들을 배워보자!
1. 단일 서버
단일 서버란?
- 모든 컴포넌트가 단 한 대의 서버에서 실행되는 시스템
- 웹, 앱, 데이터베이스, 캐시 등이 모두 서버 한 대에서 실행
사용자 요청 처리 흐름
- 사용자 : 도메인 이름을 통해 웹사이트 접속
- DNS(third party 유료 서비스) 조회 결과로 IP 주소 반환
- 해당 IP주소로 HTTP 요청 전달
- 요청받은 웹 서버는 HTML 페이지나 JSON 형태의 응답 반환
단말의 종류
- 웹 애플리케이션
- 모바일 앱
2. 데이터베이스
이중화 서버
- 웹/모바일 트래픽 처리 서버
- 데이터베이스 서버
데이터 베이스 종류
1. 관계형 데이터베이스 (relational database)
- RDBMS (Relational Data-Base Management System)
- ex. MySQL, 오라클 데이터베이스, PostgreSQL
- 테이블, 열, 칼럼으로 표현
- 여러 테이블에 있는 데이터를 관계에 따라 join 가능
2. 비-관계형 데이터베이스 (irrelational database)
- NoSQL
- ex. CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB
- NoSQL : join 연산X
- 키-값 저장소 key-value store
- 그래프 저장소 graph store
- 칼럼 저장소 column store
- 문서 저장소 document store
- 비-관계형 DB 선택이 바람직한 경우
- 아주 낮은 응답 지연 시간(latency) 요구
- 다루는 데이터가 비정형
- 데이터의 직렬화, 역직렬화만 필요
- 아주 많은 양의 데이터 저장
3. 수직적 규모 확장 vs 수평적 규모 확장
수직적 규모 확장 프로세스
Scale Up
- 서버에 고사양 자원(더 좋은 CPU, 더 많은 RAM)을 추가하는 행위
- 서버로 유입되는 트래픽의 양이 적을 때 Good
장점
단점
- 한계가 존재 : 한 대의 서버에 CPU, 메모리 무한대 증설할 수 X
- 장애에 대한 자동복구나 다중화방안 존재 X
→ 서버에 장애 발생 시 웹 사이트/앱은 완전히 중단
수평적 규모 확장 프로세스란?
Scale Out
- 더 많은 서버를 추가하여 성능을 개선하는 행위
- 대규모 어플리케이션에서 Good
서버에 장애가 발생한다면?
- 서버가 다운되거나 너무 많은 사용자가 접속하게 될 경우
- 부하 분산기 or 로드밸런서 도입하여 해결
로드밸런서
- 역할 : 부하 분산 집합에 속한 웹 서버들에게 트래픽 부하를 고르게 분산
- 과정
- 사용자가 로드밸런서의 공개 IP 주소로 접속 ( = 웹 서버는 클라이언트의 접속을 직접 처리X)
- 사설 IP 주소로 로드밸런서와 웹 서버가 통신 (사설 IP 주소 : 같은 네트워크에 속한 서버 사이의 통신에만 쓰일 수 있는 IP주소, 인터넷을 통해서는 접속 불가)
- 부하를 나누기 위해 새로운 서버 추가 가능
- 서버 1 다운 → 모든 트래픽은 서버 2로 전송
- 웹 사이트 전체가 다운되는 일 방지
- 트래픽 가파르게 증가할 경우
- 두 대의 서버로 트래픽을 감당할 수 없는 시점에서 로드밸런서가 대처
- 웹 서버 계층에 더 많은 서버 추가
- 로드 밸런서가 자동적으로 트래픽 분산
- 한계
- 장애의 자동복구나 다중화를 지원 X
→ 데이터베이스 다중화가 필요
데이터베이스 다중화
데이터베이스 다중화
- 대부분의 DB가 다중화 지원
- 서버 간 주(master)-부(slave) 관계 설정
- 원본 → 주서버, write + read
- 사본 → 부서버, read only
장점
- 더 나은 성능
- 분산 처리 : 변경 연산은 주서버에, 읽기 연산은 부서버에
- 병렬 처리 : 처리될 수 있는 질의 수 증가
- 안정성(reliability) : 여러 장소에 서버 다중화
- 가용성(availability) : 장애 대처 용이
데이터베이스 서버 가운데 하나가 다운되면?
- 부 서버가 한 대 뿐인데 다운된 경우
- 읽기 연산은 한시적으로 모두 주 데이터베이스로 전달
- 즉시 새로운 부 데이터베이스 서버가 장애 서버를 대체
- 부 서버가 여러 대인데 다운된 경우
- 읽기 연산은 나머지 부 데이터베이스 서버들로 분산
- 새로운 부 데이터베이스 서버가 장애 서버를 대체
- 주 데이터베이스 서버가 다운된 경우
- 한 대의 부 서버가 주 서버를 대체
- 새로운 부 서버 추가
→ 부 서버에 보관된 데이터가 최신 상태가 아닐 경우,
- 없는 데이터는 복구 스크립트를 돌려서 추가
- 다중 마스터(multi-masters), 원형 다중화(circular replication) 도입
동작 과정
- 사용자는 DNS로부터 로드밸런서의 공개 IP 주소를 받는다.
- 사용자는 해당 IP 주소를 사용해 로드밸런서에 접속한다.
- HTTP 요청은 서버 1이나 서버 2로 전달된다.
- 웹 서버는 사용자의 데이터를 부 데이터베이스 서버에서 읽는다.
- 웹 서버는 데이터 변경 연산(데이터 추가, 삭제, 갱신 연산)을 주 데이터베이스로 전달한다.
4. 캐시
캐시란?
- 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고
- 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소
응답 시간(latency)
- 캐시를 붙이고 정적 콘텐츠를 전송 네트워크(CDN)으로 옮기면 개선 가능
- 성능 ← DB를 얼마나 자주 호출하느냐 (캐시가 완화)
캐시 계층
장점
캐시 사용 시 유의할 점
- 갱신 ⬇️, 참조 ⬆️ 인 상황에 바람직
- 휘발성 메모리 → 영속적 보관 X
- 만료 정책 필수!
- 너무 짧으면 → DB 자주 접근
- 너무 길면 → 원본과 차이
- 일관성(consistency) 유지 중요
→ 원본, 캐시 갱신하는 연산이 단일 트랜잭션으로 처리되지 않는 경우 위험
→ but, 어려움
- 장애 대응
- 단일 장애 지점: 어떤 특정 지점에서의 장애가 전체 시스템의 동작을 중단시켜버릴 수 있는 지점, SPOF
- 캐시 서버를 한 대만 두는 경우 단일 장애 지점 발생 → 캐시 서버 분산 필요
- 캐시 메모리의 적절한 크기
- 너무 작으면 → 너무 자주 밀려나(eviction) 캐시 성능 ⬇️
- 캐시 메모리 과할당(overprovision) 하면 문제 해결
→ 캐시에 보관될 데이터가 갑자기 늘어났을 때 생긴 문제도 방지
- 데이터 방출 정책 (eviction)
- 캐시가 꽉 찬 후에 추가로 데이터를 넣어야 할 경우, 어떤 기존 데이터를 방출하지?
- ex. LRU(Least Recently Used) : 마지막으로 사용된 시점이 가장 오래된 데이터
LFU(Least Frequently Used) : 사용된 빈도가 가장 낮은 데이터
FIFO(First In First Out) : 가장 먼저 캐시에 들어온 데이터
5. 콘텐츠 전송 네트워크(CDN)
CDN
- 정적 콘텐츠를 전송하는 데 쓰이는 지리적으로 분산된 서버의 네트워크
- 이미지, 비디오, CSS, JavaScript 파일 등을 캐시
- 웹사이트 방문 시, 그 사용자에게 가장 가까운 CDN 서버가 정적 콘텐츠를 전달
- 사용자가 CDN 서버로부터 멀면 멀수록 웹사이트 로드 속도 ⬇️
- 동작 과정
- 사용자 A가 이미지 URL을 이용해 image.png에 접근
→ URL의 도메인은 CDN 서비스 사업자가 제공
- CDN 서버의 캐시에 해당 이미지가 없는 경우, 서버는 원본 서버에 요청하여 파일을 가져옴.
- 원본 서버가 파일을 CDN 서버에 반환
→ 응답의 HTTP 헤더에는 해당 파일이 얼마나 오래 캐시될 수 있는지를 설명하는 TTL 값이 들어있다.
- CDN 서버는 파일을 캐시하고 사용자A에게 반환
→ 이미지는 TTL에 명시된 시간이 끝날 때까지 캐시
- 사용자 B가 같은 이미지에 대한 요청을 CDN 서버에 전송
- 만료되지 않은 이미지에 대한 요청은 캐시를 통해 처리
CDN 사용시 고려해야 할 사항
- 비용
- 보통 제3 사업자(third-party providers)에 의해 운영됨
- 사용자는 CDN으로 들어가고 나가는 데이터 전송 양에 따라 요금 지불
- 자주 사용하지 않는 콘텐츠를 캐싱하는 것은 이득 X, CDN에서 빼는 것을 고려!
- 적절한 만료 기한 설정
- 시의성이 중요한 콘텐츠의 경우, 만료 시점 설정 필요
- 너무 길면 → 콘텐츠의 신선도 하락
- 너무 짧으면 → 원본 서버에 빈번히 접속
- CDN 장애에 대한 대처 방안
- CDN 자체가 죽었을 경우 웹사이트/앱이 어떻게 동작해야하는지 고려
- 해당 문제를 감지하여 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트를 구성
- 콘텐츠 무효화 방법
- 아직 만료되지 않은 콘텐츠여도 CDN에서 제거하는 방법
- CDN 서비스 사업자가 제공하는 API를 이용한 콘텐츠 무효화
- 콘텐츠의 다른 버전을 서비스하도록 오브젝트 버저닝(object versioning) 이용
→ 콘텐츠의 새로운 버전을 지정하기 위해서는 URL 마지막에 버전 번호를 인자로 주면 된다. (ex. image.png?v=2)
장점
- 정적 콘텐츠는 더 이상 웹 서버를 통해 서비스 X, CDN을 통해 제공 → 더 나은 성능 보장
- 캐시가 데이터베이스 부하를 줄여준다.
6. 무상태(stateless) 웹 계층
무상태 웹 계층
- 웹 계층을 수평적으로 확장하는 방법
- 상태 정보(ex. 사용자 세션 테이터)를 웹 계층에서 제거
- 상태정보를 지속성 저장소에 보관하고 필요할 때 가져오도록 하는 것이 바람직
상태 정보 의존적인 아키텍처
- 상태 정보를 보관하는 서버
- 클라이언트 정보(상태)를 유지하여 요청들 사이에 공유
- 문제점
- 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송되어야 함.
- 로드밸런서의 고정 세션 sticky session 활용 → 로드밸런서에 부담
- 무상태 서버에는 이런 장치 필요 X
무상태 아키텍처
- 웹 서버
- 사용자로부터의 HTTP 요청은 어떤 웹 서버로도 전달 가능
- 상태 정보가 필요할 경우 공유 저장소로부터 데이터 받아옴
- 상태 정보가 웹 서버로부터 물리적으로 분리
- 공유 저장소
- ex. 캐시 시스템(Memcached/Redis), NoSQL
- 데이터 센터
- 웹서버 + 데이터베이스 + 캐시
- 가용성을 높이기 위해 필수
7. 데이터 센터
다중 데이터센터 아키텍처를 만들기 위해 해결해야하는 기술적 난제
- 트래픽 우회
- 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법 고안
- ex.geoDNS(지리적 라우팅): 사용자에게서 가장 가까운 데이터센터로
- 데이터 동기화 synchronization
- 테스트&배포
- 자동화된 배포 도구: 모든 데이터 센터에 동일한 서비스가 설치되도록-
시스템을 더 큰 규모로 확장하려면
- 시스템의 컴포넌트 분리해서 각기 독립적으로 확장될 수 있어야 함
- 핵심적 전략: ex. 메시지 큐
8. 메시지 큐
- 메시지의 무손실을 보장하는 비동기 통신을 지원하는 컴포넌트
- 무손실(durability): 메시지 큐에 보관된 메시지는 소비자가 꺼낼 때까지 안전히 보관
- 메시지의 버퍼 역할 (중간 다리)
- 비동기적으로 전송
메시지 큐의 기본 아키텍처
- 생산자(발생자, producer/publisher, 입력 서비스)가 메시지를 생성
- 메시지큐에 발행(publish)
- 소비자(구독자, consumer/subscriber)가 메시지 소비
- 생산자, 소비자 서로를 직접 의존하지 않고 큐만 의존하게 됨
장점
- 서비스 또는 서버 간 결합 느슨 → 규모 확장성 보장에 유용
- 생산자는 소비자 프로세스가 다운돼도 메시지 발행 가능
- 소비자는 생산자 서비스가 가용한 상태가 아니어도 메시지 수신 가능
9. 로그, 메트릭 그리고 자동화
로그
- 에러 로그를 모니터링 하는 것
- 로그를 단일 서비스로 모아주는 도구를 활용하면 더 편리 (ex. CloudWatch, Datadog, Papertrail)
매트릭
- 사업 현황에 관한 유용한 정보, 시스템의 현재 상태 파악
- 호스트 단위 메트릭: CPU, 메모리, 디스크 I/O
- 종합(aggregated) 메트릭: DB, 캐시 계층의 성능
- 핵심 비즈니스 메트릭: 일별 능동 사용자, 수익, 재방문
자동화
- 지속적 통합을 도와주는 도구 활용 → 개발자가 만드는 코드가 자동으로 검증 절차를 거치도록
- 빌드, 테스트, 배포 등의 절차 자동화 가능
10. 데이터베이스의 규모 확장
수직적 확장 (scale Up)
- 기존 서버에 고성능의 자원(ex. CPU, RAM, 디스크)을 증설하는 방법
- 약점
- DB 서버 하드웨어에는 한계가 존재: CPU, RAM 등을 무한 증설 X
- SPOF(Single Point Of Failure) 위험성
- 비용 ⬆️
수평적 확장 (sharding)
- 더 많은 서버를 추가하는 방법
- 샤드
- 대규모 데이터베이스의 작은 단위
- 모든 샤드는 같은 스키마 사용
- 샤드에 보관되는 데이터 사이에 중복 X
- 샤딩
- 대규모 데이터베이스를 샤드로 분할하는 기술
- ex.
user_id%4 를 해시함수(샤딩 키)로 사용해서 4개의 샤드로 분리
- 샤딩 키(파티션 키)
- 데이터가 어떻게 분산될 지 정하는 하나 이상의 칼럼으로 구성
- 데이터를 고르게 분할할 수 있도록 하는 것이 중요
샤딩 도입 시 풀어야 할 문제
- 데이터의 재 샤딩(resharding)이 필요할 때
- 데이터가 너무 많아져서 하나의 샤드로는 감당이 어려울 때
- 샤드 간 데이터 분포가 균등하지 못해 어떤 샤드에 할당된 공간 소모가 빨리 진행될 때 (샤드 소진, shard exhaustion)
→ 샤드 키 계산하는 함수 변경, 데이터 재배치 필요 (안정 해시 기법 활용)
- 유명인사(celebrity) 문제 (핫스팟 키 문제)
- 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제
- 조인과 비정규화
- 여러 샤드에 걸친 데이터를 조인하기 어려움
→ 해결: DB 정규화하여 하나의 테이블에서 질의가 수행될 수 있도록
11. 백만 사용자, 그리고 그 이상
- 웹 계층은 무상태 계층으로
- 모든 계층에 다중화 도입
- 가능한 한 많은 데이터를 캐시
- 여러 데이터 센터를 지원
- 정적 콘텐츠는 CDN을 통해 서비스
- 데이터 계층은 샤딩을 통해 규모 확장
- 각 계층은 독립적 서비스로 분할
- 시스템을 지속적으로 모니터링, 자동화 도구들 활용