0. Overview
해당 포스팅은 "가상 면접 사례로 배우는 대규모 시스템 설계 기초"라는 도서를 읽고 이를 정리하고자 작성하는 포스팅입니다.
이번 챕터에서는 한 명의 사용자를 지원하는 시스템에서 시작하여, 최종적으로 수백만 사용자를 지원하는 시스템을 설계하고 규모 확장성에 대한 이해를 높여보자.
1. 단일 서버
- 모든 컴포넌트가 단 한대의 서버에서 실행되는 시스템
- 웹 앱, 데이터베이스, 캐시 등이 전부 서버 한대에서 실행되는 구조
1. 사용자가 도메인 이름(api.mysite.com)을 이용해 웹 사이트 접속
2. DNS(Domain Name Service)를 통해 도메인 이름을 IP 주소로 변환되는 요청 발생
3. DNS 결과로 IP 주소 반환
4. 해당 IP 주소로 HTTP 요청 전달
5. 요청 받은 웹 서버는 HTML 페이지나 JSON 형태의 응답 반환
그렇다면, 단일 서버로는 부족한 이유가 뭘까 ? 당연히 사용자가 늘어남에 따라 단일 서버로는 트래픽을 감당하기 어렵기 때문이다.
2. 수직적 규모 확장 (Scale-up) vs 수평적 규모 확장(Scale-out)
- 수직적 규모 확장 : Scale Up - 고사양 자원을 추가하는 행위 (고사양 CPU, 많은 RAM) = "질"
- 수평적 규모 확장 : Scale Out - 더 많은 서버를 추가하여 성능을 개선하는 행위 = "양"
서버로 유입되는 트래픽의 양이 많으면, 수직적 확장이 좋지만 많은 단점이 존재하는데...
📌 한 대의 서버에 CPU나 메모리를 무한대로 증설할 수 없음
📌 자동 복구(failover)방안이나 다중화(redundancy)방안을 제시할 수 없다는 점
따라서, 대규모 애플리케이션을 지원하는 데는 수평적 규모 확장법이 보다 적절하다.
3. 웹 계층과 데이터 계층의 수평적 확장
🔍 로드밸런서
웹 서버가 다운되거나 트래픽이 많아질 경우 응답속도가 느려져 서버 접속이 불가능해질 수 있는데, 이때 로드 밸런서(load balancer)을 도입하는 것이 좋다. 로드 밸런서는 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 한다.
1. 사용자는 로드 밸런서의 공개 IP 주소(public IP address)로 접속
2. 로드 밸런서는 웹 서버와 통신하기 위해 사설 IP 주소(private IP address)를 이용
- 서버1이 다운되면 모든 트래픽이 서버2로 전송되어 웹 사이트 전체의 다운을 막는다.
- 웹 서버 계층에 더 많은 서버를 추가하여 트래픽 분산을 효율적으로 할 수 있다.
🔍 데이터베이스 다중화
로드 밸런서를 통해 웹 계층 서버를 다중화 했다면, 이번엔 데이터 계층의 DB 서버 또한 장애의 자동복구를 위해 다중화할 필요가 있다. DB 다중화는 DB 서버 사이에 주(master)-부(slave) 관계를 설정하고 원본은 master 서버에, 사본은 slave 서버에 저장하는 방식이다. 쓰기 연산(write, insert, update, delete 등)은 master에만 지원하며 slave는 사본을 전달 받아 읽기 연산(read)만 지원하게 된다. 대부분 어플리케이션의 비중은 읽기 연산 > 쓰기 연산이므로 slave 서버의 수가 master 수보다 많다.
- 데이터 변경 연산은 master로, 읽기 연산은 slave로 분산되어 병렬 처리할 수 있는 질의의 수가 늘어나 성능 향상
- DB 서버 일부가 파괴되어도 데이터가 보존 (지역적, 물리적으로 떨어진 장소에 다중화할 수 있기 때문)
- 데이터를 여러 곳에 복제해두어, 하나의 DB 서버에 장애가 발생해도 다른 서버의 데이터를 통해 서비스 지속
그렇다면, 만약 DB 서버 하나가 다운되면 어떤 일이 생길까 ?
master가 다운된 경우 : slave 서버가 새로운 master 서버의 역할을 하며 모든 db연산은 일시적으로 새로운 master 서버에서 수행
4. 응답 시간 개선
다중화를 이해한 후에는 응답시간을 개선하는 방안을 고려해볼 수 있다. 응답 시간은 캐시(Cache)를 붙이고 정적 콘텐츠를 CDN로 옮김으로써 개선할 수 있다.
🔍 캐시
데이터가 잠시 보관되는 곳으로 DB에 접근하는 것보다 훨씬 빠르다.
1. 요청을 받은 웹 서버는 먼저 캐시에 찾고자 하는 응답이 저장되어 있는지 확인한다.
2. 저장되어 있으면 해당 데이터를 반환한다.
3. 저장되어 있지 않다면 DB 질의를 통해 데이터를 찾아 캐시에 저장 후 클라이언트에 반환한다.
[ 캐시 사용 시 고려할 사항 ]
- 언제 사용할 지 : 데이터 갱신 횟수 <<<<<<<< 참조 횟수일 때
- 어떤 데이터를 저장할 지 : 캐시는 휘발성이므로 영속적으로 보관할 데이터는 캐시에 두지 말자.
- 데이터 만료 정책 : 만료 기한 너무 짧다(DB를 자주 읽게 됨), 만료 기한 길다(원본과 차이가 날 가능성 존재)
- 일관성 유지 방법 : 데이터 저장소의 원본과 캐시 내의 사본이 같게 만들어야 한다.
- 장애 대처 방법 : 캐시 서버가 1대만 있으면 SPOF가 될 가능성이 있다.
❓ SPOF
Single Point Failure로 해당 지점에서의 장애가 전체 시스템의 동작을 중단시켜버릴 수 있는 지점을 뜻한다.
🔍 콘텐츠 전송 네트워크 (CDN)
CDN은 이미지, 비디오, CSS, JS 파일 등 정적 콘텐츠를 전송하고 캐싱하는데 쓰이는 지리적으로 분산된 서버의 네트워크이다. 정적 콘텐츠를 캐싱한다는 것은, 요청 경로(request path) , 질의 문자열(query string), 쿠키(cookie), 요청 헤더(request header)등의 정보에 기반하여 HTML 페이지를 캐시하는 것이다.
1. 사용자가 이미지 URL을 이용해 image.png에 접근한다.
2. CDN 서버 캐시에 해당 이미지 없으면 서버는 원본 서버에 요청해 파일을 가져온다.
3. 원본 서버가 파일을 CDN 서버에 반환하고 응답 HTTP 헤더에는 TTL(Time-to-live)값이 들어있다.
4. CDN 서버는 파일을 캐시하고 사용자 A에게 반환한다.
5. 다른 사용자 B가 같은 이미지에 대한 요청을 CDN 서버에 전송한다.
6. TTL이 만료되지 않았다면 이미지 요청은 캐시를 통해 처리된다.
5. 웹 계층의 수평적 확장, 무상태(stateless) 웹 계층
웹 계층의 수평적 확장을 위해 사용자 세션 데이터, 상태 정보의 경우 웹 계층에서 제거하고 해당 정보를 RDB나 NoSQL 같은 지속성 저장소에 보관 후 필요할 때 가져오도록 하는 것을 무상태 웹 계층이라고 부른다.
[ 상태 정보에 의존적라면.. ]
- 각각의 서버가 각각의 사용자 정보를 가지고 있으므로 http 요청을 각각 서버에 전송해야 인증절차를 거칠 수 있음
- 같은 클라이언트로부터 요청이 항상 같은 서버로만 전송되어야 함
[ 무상태 아키텍처라면.. ]
사용자로부터 HTTP 요청은 어떻나 웹 서버로도 전달이 가능하며 웹 서버는 상태 정보를 공유 저장소로부터 가져온다.
결론은 !!! 웹 계층을 stateless 하게 만들자..
6. 여러가지 추가 컴포넌트
🔍 데이터 센터
더 나아가, 전 세계 사용자의 이목을 받는 시스템으로 확장되었다고 해보자. 가용성을 높이고 어떤 지역에서든 쾌적한 사용을 보장하도록 설계하기 위해서는 여러 데이터 센터를 지원하는 것이 필수적이다.
- geoDNS : 사용자의 위치에 따라 도메인 이름을 어떤 IP 주소로 변환할 지 결정하게 해주는 DNS 서비스
- 장애 발생시 대응하기 위한 고려사항
- 트래픽 우회 : 살아있는 데이터 센터로 트래픽 보내는 가장 효과적인 방법 (geoDNS가 어느정도 해결)
- 데이터 동기화 : 각 데이터센터마다 들고 있는 정보가 다른 상황에서 장애가 난 경우
- 테스트와 배포 : 이를 위해 자동화 된 배포 도구가 중요하다.
🔍 메시지 큐
시스템의 더 큰 규모로 확장 -> 시스템 컴포넌트를 분리해 독립적으로 확장될 수 있게 해야함
메시지 큐는 많은 실제 분산 시스템이 위 문제를 해결하기 위해 채용하는 핵심적 전략 기술이다. publish/subscribe 구조로 비동기 통신을 지원하는 메시지 버퍼 역할을 한다.
장점 : 서비스/서버 간 결합이 느슨해져 규모 확장성을 보장하고 안정적으로 애플리케이션을 구성할 수 있다.
--> 무슨 말이냐면, 생산자는 소비자가 죽어도 메시지를 발행할 수 있고 소비자는 생산자가 불가용한 상태라도 메시지를 수신할 수 있다.
ex) 사진 보정 애플리케이션에서 사진 편집 작업은 시간이 오래 걸리기 때문에 비동기적으로 수행하면 좋다.
🔍 로그, 메트릭, 자동화
웹 사이트의 규모가 커지면 로그나 메트릭, 자동화 도구를 필수적으로 포함시켜야 한다.
로그 : 시스템 오류, 문제를 쉽게 찾아낼 수 있게 하는 에러 로그를 서버 단위, 또는 단일 서비스로 모아주는 도구를 활용해 더 편리하게 검색하고 조회 가능
메트릭(metric) : 메트릭 수집을 통해 사업 현황에 관한 유용한 정보를 얻고, 시스템 현재 상태를 손쉽게 파악 가능
- 호스트 단위 메트릭 : CPU, 메모리, 디스크 I/O 관련 메트릭
- 종합 메트릭 : DB 계층의 성능, 캐시 계층의 성능
- 핵심 비즈니스 메트릭 : 수익 , 재방문 기록
자동화(automation) : 지속적 통합, 배포(CI/CD) 도구를 사용하는 경우 개발 생산성을 크게 향상시킬 수 있다.
7. 데이터베이스 수평적 확장 (scale-out)
데이터베이스의 수평적 확장은 샤딩(sharding)이라고 부른다. scale-out이란 더 많은 db 서버를 추가하여 성능을 향상시키는 방법인데, 샤딩은 대규모 DB를 샤드(Shard)라고 부르는 작은 단위로 분할하는 기술이다. 모든 샤드는 같은 스키마를 쓰지만, 샤드에 보관되는 데이터 사이에는 중복이 없다. 위의 경우 user_id 값에 따라 샤드에 데이터를 넣는 기준을 정하였다.
[ 샤딩 전략 구현 시 고려해야할 사항 ]
샤딩 키를 어떻게 정하느냐가 매우 중요하므로 샤딩 키를 정할 때 데이터를 고르 분할 할 수 있도록 하는게 가장 중요하다.
[ 샤딩 도입 시 풀어야 할 문제들 ]
- 데이터 재샤딩 : 데이터가 너무 많아져 하나의 샤드로는 더 이상 감당하기 어려워 새로운 샤드를 추가해야 하는 경우.. 등
- 유명인사 문제 : 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제 , 예를 들어 많이 검색하는 단어를 전부 같은 샤드에 저장하여 애플리케이션을 구축하면 해당 샤드는 read 연산으로 인해 과부하가 걸린다.
- 조인과 비정규화 : 하나의 DB를 여러 샤드로 분할한 경우 여러 샤드에 걸친 데이터를 조인하기 힘들어진다. 이를 위해 DB를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 하는 방법이 있다.
--> 최종 설계안
8. 정리
- 웹 계층은 무상태(stateless) 계층으로 설계하자.
- 모든 계층에 다중화를 도입하자 (데이터베이스, 서버, 캐시 서버)
- 가능한 한 많은 데이터를 캐시
- 여러 데이터 센터 지원
- 정적 콘텐츠는 CDN을 통해 서비스
- 데이터 계층은 샤딩을 통한 규모 확장
- 각 계층은 독립적 서비스로 분할
- 시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용