개요
- 단일 사용자에서 다수의 사용자를 커버할 수 있는 시스템 확장에 대해 알아본다.
단일 서버
- 단일 서버 사용 시 사용자의 요청 흐름
- 사용자는 도메인 주소를 이용해 웹사이트 접속
- DNS(Domain Name Service)를 이용해 도메인 주소 → IP 주소 변환
- DNS가 반환한 IP 주소로 HTTP 요청을 전달
- 웹 서버는 HTML 페이지 혹은 JSON 결과를 응답
- 사용자는 도메인 주소를 이용해 웹사이트 접속
데이터베이스
- 서버는 웹 계층(트래픽 처리를 위한 웹 서버)과 데이터 계층(데이터베이스)을 구분하는 것이 좋다.
- 계층을 분리함으로써 각각을 독립적으로 확장할 수 있다.
데이터베이스의 선택
- 데이터베이스는 크게 관계형 데이터베이스와 비-관계형 데이터베이스 사이에서 선택한다.
- 관계형 데이터베이스(RDB): MySQL, PostgreSQL 등
- 비-관계형 데이터베이스(NoSQL): CouchDB, DynamoDB 등
- NoSQL의 4가지 분류
- 키-밸류 저장소 (Key-Value Store)
- 그래프 저장소 (Graph Store)
- 컬럼 저장소 (Column Store)
- 문서 저장소 (Document Store)
- NoSQL 선택이 권장되는 상황
- Low-Latency가 필요한 경우
- 데이터가 비정형(unstructured)이라 관계형 데이터가 아닌 경우
- 데이터(JSON, YAML 등)를 직렬화/역직렬화할 수만 있으면 되는 경우
- 많은 양의 데이터를 저장할 필요가 있는 경우
수직적 규모 확장 vs 수평적 규모 확장
- 수직적 규모 확장(Vertical Scaling): 스케일-업(Scale Up)을 의미, 서버에 고사양 자원을 추가하는 것
- 수평적 규모 확장(Horizontal Scaling): 스케일-아웃(Scale Out)을 의미, 더 많은 서버를 추가해 성능을 개선하는 것
- 스케일 업의 한계로 인해 대규모 애플리케이션 지원에 있어서는 스케일 아웃이 더 적절하다.
- 서버 리소스를 무한정 늘리는 것은 불가능
- Fail Over 방안이나 다중화(redundancy) 방안을 제시하지 않음
로드밸런서
- 로드밸런서: 부하 분산 집합에 속한 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 하는 장치
- 사용자는 로드밸런서의 Public IP 주소로 접속하고, 로드밸런서는 Private IP를 사용해 내부 서버와 통신
Private IP는 같은 네트워크에 속한 서버 사이 통신에서만 사용 가능하며, 공인망(인터넷)을 통해 접근 불가능
- 로드밸런서를 사용함으로써 웹 서버를 추가하면 Fail Over 상황을 방지하고 웹 서버 가용성을 향상시킬 수 있다.
데이터베이스 다중화
- 데이터베이스 시스템은 Master/Slave 형태로 다중화를 지원
- Master에서만 데이터 쓰기 연산을 지원
- Slave에서는 데이터 읽기 연산만 지원
- Master에 쓰여진 데이터를 Slave에서 제공하기 위해 Master로부터 데이터 복제본을 전달받는다.
- 데이터베이스 다중화에 따른 이점
- DB 작업의 대부분은 읽기 연산이며, Slave에서 읽기 연산을 제공하기 때문에 쿼리 요청이 분산되어 성능이 향상된다.
- 자연재해와 같이 데이터 서버 일부분이 파괴되어도 지리적으로 떨어진 장소에 데이터를 다중화 시킬 수 있어 안정성이 높아진다.
- 서버 장애가 발생해도 데이터를 여러 지역에 복제했기 때문에 가용성이 높아진다.
- 데이터베이스 서버 장애 발생 시 동작
- Slave에 장애가 발생한 경우 Slave가 1대라면 Master에서 읽기 연산을 처리하며, Slave가 여러대인 경우 장애가 발생한 Slave를 다른 Slave가 대체
- Master에 장애가 발생한 경우 다른 Active Slave가 Master로 승격되어 Master 역할을 수행
💡 장애 발생 시 Master/Slave간 데이터가 동기화가 되지 않은 상황이 생길 수 있다.
(e.g Master 쓰기 연산 완료 후 데이터 복제본 Slave 전달 전 Master 장애 발생하는 경우)
- 복구 스크립트를 구성해 누락된 데이터를 추가하는 방안을 고려
- 다중 마스터(multi-masters), 원형 다중화(circular replication) 방안 고려
💡 Mutli-Master / Circular ReplicationMulti-Master
다중 마스터(multi-masters): 여러 마스터가 동시에 쓰기 작업을 처리하고 서로 복제하는 방식
- 고가용성: 하나의 마스터 장애시 다른 마스터가 쓰기 연산 처리 가능
- 로드 밸런싱: 쓰기 요청을 여러 마스터로 분산시켜서 수행 가능
- 지리적 분산: 마스터를 지리적으로 분산시켜 지역별 접근 속도 향상 가능
원형 다중화(circular replication): 데이터베이스 노드들이 원형으로 연결된 형태를 가지며, 이웃한 노드와 데이터를 복제하는 방식
- 고가용성: 한 노드가 장애가 발생해도 나머지 노드들이 데이터 복제 가능
- 로드 밸런싱: 이웃한 노드 간에만 데이터 복제가 이뤄지기 때문에 복제 과정에서 부하 분산
- 확장성: 새로운 노드를 추가하기 쉬움
두 방식 모두 데이터 일관성을 보장하기 어렵고, 각 노드에서 동시에 데이터 수정이 발생하는 경우 충돌 발생 가능성이 있이 이를 해결하는 것이 주요 도전과제이다.
캐시
- 캐시(Cache): 값비싼 연산 결과, 자주 참조되는 데이터를 메모리에 두고 요청을 빠르게 처리하기 위한 저장소
캐시 계층
- 데이터가 잠시 보관되는 곳으로 데이터베이스보다 빠르다.
- 캐시 전략은 다양하며 가장 일반적인 전략은 주도형 캐시 전략(read-through caching strategy)이다.
- 캐시 히트 시 클라이언트에 바로 반환
- 캐시 미스 시 원본 데이터베이스를 쿼리 후 캐시에 데이터를 저장하고 반환하는 전략
캐시할 데이터 종류, 크기, 액세스 패턴에 맞게 다른 캐시 전략을 사용할 수 있다.
캐시 사용 시 유의할 점
- 캐시 사용이 필요한 상황: 데이터 갱신이 자주 일어나지 않고, 데이터 참조는 자주 발생하는 경우
- 캐시에 적재할 데이터 종류: 캐시는 휘발성 메모리이기 때문에 영속적 데이터는 DB에 두는 것이 바람직하다.
- 캐싱된 데이터의 만료 기한: 캐싱할 데이터는 TTL을 걸고, TTL을 적절하게 지정해야 한다.
- TTL이 짧은 경우 DB 조회가 많아진다.
- TTL이 긴 경우 원본 데이터와 캐싱된 데이터 간 일관성이 떨어진다.
- 일관성(Consistency): 원본을 갱신하는 연산과 캐시를 갱신하는 연산이 단일 트랜잭션으로 처리되지 않는 경우 일관성이 깨질 수 있다.
- 장애 대응: 캐시 서버가 한대인 경우 단일 장애 지점(Single Point of Failure, SPOF)이 될 수 있으므로, 여러 지역에 걸쳐 캐시 서버를 분산시켜야 한다.
💡 단일 장애 지점 (Single Point of Failure, SPOF)
시스템 구성 요소에서 동작하지 않으면 전체 시스템이 중단되는 요소를 의미한다.
캐시 서버 장애 발생 시 SPOF가 되는 이유는 전체 시스템의 성능 및 가용성에 영향을 미치기 때문이다.
또한, 캐시 서버가 복구되었을 때 캐시 데이터를 로드하는 과정에서 데이터 일관성 문제가 발생할 수 있기 때문이다.
- 캐시 메모리 크기: 캐시 메모리 크기가 작은 경우 데이터가 자주 캐시에서 밀려나 캐시 성능이 떨어진다.
- 데이터 방출 정책: 캐시가 가득찼을 때 기존 데이터를 내보내는 정책이 필요하다.
- LRU(Least Recently Used): 마지막으로 사용된 시점이 가장 오래된 데이터 제거
- LFU(Least Frequently Used): 사용된 빈도가 가장 낮은 데이터를 제거
- FIFO(First In, First Out): 가장 먼저 캐시에 들어온 데이터 제거
콘텐츠 전송 네트워크(CDN)
- CDN은 정적 콘텐츠를 전송하는데 쓰이는 분산된 서버의 네트워크
- 요청한 사용자와 가장 가까운 CDN 서버로부터 정적 콘텐츠를 내려줘 콘텐츠 로딩에 필요한 지연을 줄인다.
- CDN의 동작
- 사용자가 CDN URL(CDN 사업자가 제공)로 이미지 접근
- CDN 서버에 이미지가 없는 경우 원본 서버에서 파일을 가져옴
- 원본 서버가 파일을 반환할 때 오는 HTTP Response 헤더에는 콘텐츠의 TTL 값을 갖고 있다.
- CDN은 파일을 TTL 기간동안 캐싱하며, 이후 동일한 콘텐츠 요청이 들어오는 경우 CDN에서 직접 반환
CDN 사용 시 고려사항
- CDN은 제3 사업자에 운영되기 때문에 요금이 발생한다.
- Time-Sensitive 콘텐츠의 경우 TTL시점을 잘 지정해줘야 한다
- CDN 자체에 장애가 발생했을 때 콘텐츠를 제공하기 위한 대응책을 고려해야 한다.
- TTL이 만료되지 않은 콘텐츠를 CDN에서 제거할 수 있어야 한다.
- API를 사용한 삭제, 콘텐츠 오브젝트의 버저닝
무상태(Stateless) 웹 계층
- 웹 계층을 수평적으로 확장하기 위해서는 상태 정보를 제거해야 한다.
- 상태 정보를 데이터 계층에 저장하고, 필요할 때 가져오는 방식으로 구축해야 한다.
상태 정보 의존적인 아키텍처
- 상태 정보 의존적인 아키텍처의 경우 상태를 유지해야 하기 때문에 해당 상태 정보를 갖고 있는 웹 서버에서만 요청을 처리할 수 있다.
- 로드밸런서는 Sticky Session을 통해 같은 클라이언트라면 항상 같은 서버로 요청을 라우팅할 수 있지만, 로드밸런서에 부담이 가기 때문에 바람직하지 않다.
무상태 아키텍처
- 무상태 아키텍처의 경우 상태 정보가 필요한 경우 공유 저장소(Shared Storage)에서 데이터를 가져온다.
- 상태 정보는 웹 계층(웹 서버)와 물리적으로 분리되어 있어 사용자의 요청이 어떤 웹 서버로와도 문제가 없다.
- 즉, 수평적 확장이 용이하며, 안정적이다.
- 공유 저장소는 RDB, NoSQL 어떤 것을 사용해도 문제 없으며, 일반적으로 트래픽, 규모 확장을 고려해 NoSQL을 사용한다.
데이터 센터
- 지리적 라우팅(geoDNS-routing): 장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 라우팅 된다.
geoDNS는 사용자의 위치에 따라 도메인 이름을 어떤 IP 주소로 변환할지 결정하는 것
- 장애가 발생하면 모든 트래픽은 장애가 없는 데이터 센터로 전송된다.
- 다중 데이터 센터를 구축 시 고려사항
- 트래픽 우회: 올바른 데이터 센터로 트래픽을 보내는 방법을 찾아야 한다. (GeoDNS)
- 데이터 동기화: 데이터를 여러 데이터 센터에 다중화해야 한다.
- 테스트와 배포: 여러 위치에서 테스트 해 각 데이터 센터가 정상적으로 동작하는지 확인해야 하며, 자동화된 배포 도구를 통해 모든 데이터 센터에 동일한 서비스가 구성되어야 한다.
메세지 큐
- 메세지 큐: 메세지의 무손실(durability)를 보장하는 비동기 통신을 지원하는 컴포넌트
Durability: 트랜잭션이 완료된 메세지(메세지 큐에 보관된 메세지)는 손실되지 않고 안전한 것을 의미
- 메세지 큐는 메세지 버퍼 역할을 하고, 비동기적으로 전송한다.
- 메세지 큐의 동작
- Publisher/Producer 는 메세지 큐에 메세지를 전송한다.
- Subscriber/Consumer는 메세지를 받아 로직을 처리한다.
- 메세지 큐를 사용하면 서비스/서버 간 결합이 느슨해지기 때문에 확장성이 보장된 애플리케이션에서 사용하기 좋다.
로그, 메트릭, 자동화
- 로그, 메트릭, 자동화를 통해 서비스의 생산성 및 품질을 향상시킬 수 있다.
- 에러 로그 모니터링을 통해 오류와 문제를 빠르고 쉽게 파악할 수 있다.
- 로그를 단일 서비스로 모아주는 도구를 활용하면 유용하다. (e.g ELK 등)
- 메트릭 수집을 통해 시스템의 현재 상태를 쉽게 파악할 수 있다.
- 유용한 시스템 메트릭
- 호스트 단위 메트릭 (CPU, 메모리, 디스크 I/O)
- 종합 메트릭 (데이터베이스 계층 성능, 캐시 계층 성능 등)
- 핵심 비즈니스 메트릭 (DAU, 수익 등)
- 유용한 시스템 메트릭
- CI/CD를 이용해 코드의 통합 및 배포 자동화를 통해 생산성을 향상시킬 수 있다.
데이터베이스의 규모 확장
- 데이터베이스의 규모를 확장하는 방법에는 수직적 확장과 수평적 확장이 있다.
수직적 확장
- 기존 서버에 더 많고 좋은 리소스(CPU, RAM, 디스크 등)를 추가하는 방법
- 수직적 확장의 한계
- 리소스를 무한히 증설시킬 수 없기 때문에 한계가 명확하다.
- SPOF로 인한 위험성이 크다.
- 비용이 많이 든다.
수평적 확장 (샤딩)
- 데이터베이스를 샤드(shard)라는 작은 단위로 분할해 확장시키는 방법
- 모든 샤드는 같은 스키마를 쓰지만, 샤드 별 보관되는 데이터 사이엔 중복이 없다.
- 샤딩 키(파티션 키)를 적절히 정해 각 샤드에 고르게 데이터가 분배되도록 구성하는 것이 중요하다
- 샤딩 사용 시 발생하는 문제
- 데이터의 재샤딩: 샤드 소진 현상이 발생할 때 샤드 키를 계산하는 방식을 변경해 데이터 재배치 필요
- 유명인사 문제(핫스팟 키 문제): 특정 샤드에 쿼리가 집중되어 특정 서버에 과부하가 걸리는 문제
- 조인과 비정규화: 샤딩을 사용하면 여러 샤드에 걸친 데이터를 조인하기 힘들다.
- 데이터베이스 비정규화를 통해 하나의 테이블에서 쿼리 수행을 통해 해결할 수 있다.
정리
- 시스템 설계 시 각 계층에 대해 다중화를 도입하자.
- 데이터 계층은 샤을 통해 규모를 확장하자.
- 각 계층은 독립적인 서비스로 분할하자.
- 웹 계층은 무상태 계층으로 구성하자.
- 가능한 많은 데이터를 캐시하고, 정적 콘텐츠는 CDN을 사용하자.
- 시스템을 지속적으로 모니터링하고 자동화 도구를 활용하자.
반응형