사용자 수에 따른 규모 확장성
단일 서버
- 클라이언트가 도메인 이름으로 요청을 날릴 때 DNS를 통해 도메인 주소를 통해 웹 서버의 IP 주소를 얻을 수 있다.
- 클라이언트는 IP 주소로 HTTP 요청을 날린다.
- 웹 서버는 요청을 처리할 때 필요한 경우 데이터베이스에게 쿼리를 실행하도록 한다.
- 요청의 결과를 클라이언트에게 응답한다.
어떤 데이터베이스를 사용하나?
RDB(관계형 DB)와 NoSQL(비관계형 DB)로 나뉜다.
RDB는 테이블 단위로 데이터를 관리하고, 조인을 활용해서 다른 테이블의 자료를 같이 조회해올 수 있다.
NoSQL은 키-값 저장소, 그래프 저장소, 칼럼 저장소, 문서 저장소로 나뉘는데 조인 연산을 지원하지 않는다.
수직적 규모 확장 VS 수평적 규모 확장
스케일업은 서버를 더 고사양으로 되도록 램이나 더 좋은 CPU를 교체하는 행위이다. 반면 스케일아웃은 서버를 더많이 추가하여 서비스의 수행력을 높이는 행위다.
하지만 각 방법의 장단점이 있다.
- 수직적 확장은 한계가 있다. 아무리 돈을 많이 써도 하나의 서버의 성능을 무한대로 높일 수 있다.
- 수직적 확장은 적은 서버를 운영하게 된다. 장애에 대한 자동복구(Fail over)나 다중화가 되지않아 불리하다.
다중 서버
이번엔 웹 서버를 스케일아웃한 예시이다. DNS를 통해 로드밸런서의 IP 주소를 얻어낸다. 로드밸런서는 요청을 웹서버들에게 나눠줘서 처리하도록 한다. 다만 웹 서버의 IP는 private IP로 같은 네트워크에 속한 서버 사이 통신에만 사용된다. private IP는 외부 인터넷에서 접속할 수 없다. 이제 웹 서버 하나가 운영할 수 없게 되더라도 다른 웹 서버가 트래픽을 처리할 수 있다.
데이터베이스 다중화
데이터베이스 다중화는 주-부 관계(master-slave)를 통해 이뤄진다. 주 데이터베이스에는 원본을 저장하고 부 데이터베이스에는 사본을 저장한다.
쓰기 연산은 주 데이터베이스에서만 한다. 부 데이터베이스에서는 주 데이터베이스의 사본만 전달받으며, 읽기 연산만 한다.
데이터베이스 다중화는 다음과 같은 장점이 있다.
- 성능 향상 : 읽기 연산을 여러 데이터베이스에서 나눠서 진행해서 성능이 더 올라갈 수 있다.
- 안정성 : 데이터베이스 하나가 작동하지 않아도 다른 데이터베이스의 존재 때문에 데이터가 보존된다.
- 가용성 : 데이터베이스 하나가 작동하지 않아도 다른 데이터베이스가 트래픽을 소화할 수 있게 된다.
데이터베이스 다중화 환경의 가용성 시나리오
- 부 데이터베이스가 하나 뿐인데 다운된 경우 : 읽기 연산을 주 데이터베이스가 한시적으로 처리한다. 그 동안 새로운 부 데이터베이스가 생성되어 대체한다.
- 주 데이터베이스가 다운된 경우 : 부 데이터베이스 중 하나가 주 데이터베이스가 되고 부 데이터베이스를 새로 추가한다. 다만 주 데이터베이스 역할을 하게 될 부 데이터베이스의 데이터 상태가 최신 상태가 아닌 경우가 있을 수 있다. 이 경우 복구 스크립트를 돌려서 최신 상태로 만들거나, 다중 마스터(Multi-masters)나 원형 다중화 방식을 도입하면 이런 상황을 대처할 수 있다.
응답 시간(latency) 개선
캐시
비싼 연산 결과나 자주 참조되는 데이터를 메모리에 두고 뒤이은 요청에 빠르게 처리하는 저장소.
읽기 주도형 캐시 전략
요청을 받으면 일단 해당 데이터를 찾아 보고 있으면 반환한다. 없으면 데이터베이스에서 조회해와서 캐시에 저장하고 반환한다.
캐시 사용 시 유의할 점
- 데이터 갱신이 자주 일어나지 않지만 참조가 빈번하게 일어나는 경우 유리하다.
- 영속적으로 보관할 데이터를 캐시에 두지 말고 중요 데이터는 지속적 저장소(persistent data store)에 저장한다.
- 만료 정책을 정해둬야 한다. 너무 짧으면 데이터베이스 조회가 자주 일어난다. 너무 길면 캐시 데이터가 원본과 달라질 수 있다.
- 데이터 저장소 원본 갱신 연산과 캐시 갱신하는 연산이 단일 트랜잭션으로 처리 되지 않는 경우 일관성이 무너진다.
- 캐시 서버 한 대만 두면 SPOF가 된다. 캐시 서버는 여러개가 되어야 한다.
- 캐시 메모리 크기. 캐시 메모리가 너무 작으면 액세스 패턴에 따라 데이터가 캐시에서 밀려날 수 있다. 캐시 메모리를 과할당하면 데이터가 갑자기 늘어났을 때 메모리에서 밀려나는 상황을 막을 수 있다.
- 데이터 방출 정책. 캐시가 꽉 찬 경우 어떤 데이터를 내보내야 하는 지 정하는 방법이다. LRU(가장 마지막에 쓰인 데이터 빼기), LFU(가장 덜 빈번하게 쓰인 데이터 빼기), FIFO(가장 먼저 들어온 캐시 데이터를 내보내기)
콘텐츠 전송 네트워크 (CDN)
정적 콘텐츠를 전송하는 데 쓰이는 지리적으로 분산된 서버의 네트워크. 요청 경로, 질의 문자열, 쿠키, 요청 헤더 등 정보에 기반해서 정적 콘텐츠를 캐싱한다.
- 비용 : CDN을 통해 데이터가 들어나고 나가는 양에 따라 요금을 내게 된다.
- 적절한 만료 시한 설정 : 시의성 중요한 컨텐츠의 경우 만료 시점을 잘 정해야 한다.
- CDN 장애에 대한 대처 : CDN 자체가 죽었을 경우 대응을 해야 한다. 만약 클라이언트에서 CDN으로 접근이 불가능한 경우 원본 서버로 바로 접근하도록 구현할 수 있다.
무상태 웹 계층
웹 계층을 수평적으로 확장하기 위해서는 무상태로 유지해야 한다. 무상태는 서버에서 상태 정보를 저장하지 않는 모습을 의미한다.
상태 정보 의존적인 아키텍처
각 서버가 클라이언트의 세션 정보를 저장하는 경우는 상태 정보에 의존적인 아키텍처이다. 이 경우 클라이언트가 기존에 요청하던 서버가 아닌 다른 서버로 요청할 경우 해당 서버에는 클라이언트의 세션 정보가 없어서 요청 처리에 문제가 생길 수 있다. 상태 정보 의존적인 아키텍처는 클라이언트가 통신하던 서버와만 계속 통신해야 하는 문제가 있다. 이 문제를 로드밸런서가 원래 소통하던 서버로 요청을 중개하는 sticky session 기능을 해줄 수 있으나 로드밸런서에 부담을 주게 된다.
무상태 아키텍처
웹서버들이 공유해야 하는 세션 정보와 같은 내용을 공유 저장소로 모아두고 해당 데이터가 필요할 때마다 공유 저장서에서 데이터를 가져오도록 한다. 이렇게 각 서버들이 공유해야 할 정보를 공유 저장소로 모으면 서버들은 상태 데이터를 관리하지 않아도 되고, 클라이언트들도 어떤 서버에 요청하던지 상관 없게 된다.
데이터 센터 아키텍처
데이터 센터를 여러 지역마다 운영해서 사용자와 지리적으로 가까운 데이터 센터에 라우팅하는 방식의 아키텍처이다. 이 절차를 지리적 라우팅 (geoDNS-routing, geo-routing)이라고 한다.
이렇게 여러 데이터센터를 운영하면 다른 데이터 센터에 장애가 생겨도 다른 데이터 센터로 트래픽을 옮기면 장애에 대응할 수 있다.
다만 다중 트래픽센터 아키텍처는 다음과 같은 어려움이 있다.
- 트래픽 우회 : 올바른 데이터 센터로 트래픽을 보내는 효과적 방법을 찾아야 한다. GeoDNS는 사용자와 가장 가까운 데이터 센터로 트래픽을 보내도록 한다.
- 데이터 동기화 : 데이터 센터마다 별도의 데이터베이스를 사용하는 상황이면 다른 데이터센터의 데이터베이스에 기존의 데이터 센터의 데이터가 없을 수 있다. 데이터를 여러 데이터센터에 걸쳐 다중화하는 전략을 사용할 수 있다. (넷플릭스의 다중화 :https://netflixtechblog.com/active-active-for-multi-regional-resiliency-c47719f6685b)
- 테스트와 배포 : 여러 데이터 센터를 사용하도록 시스템이 구성된 상황이라면 여러 위치에서 테스트를 해야 한다.
메시지 큐
시스템이 대규모로 확장하기 위해서는 시스템의 컴포넌트를 분리하여 독립적으로 확장해야 한다. 분리된 컴포넌트들의 통신을 지원한다. 메시지 큐는 메시지의 무손실(큐에 넣어두면 꺼내기 전까지 안전하게 보관된다.)을 보장하고 비동기적 통신을 지원하는 컴포넌트다.
메시지 큐 기본 아키텍처는 간단하다. 생산자(발행자, producer, publisher) 역할하는 입력 서비스가 메시지를 만들어 메시지 큐에 발행(publish)한다. 큐에는 소비자(구독자, consumer, subscriber)라 불리는 서비스가 연결되어 있다. 큐에 연결된 소비자가 메시지를 받아 그에 맞는 동작을 수행한다.
메시지 큐를 사용하면 서비스 또는 서버 간 결합이 느슨해져서 규모 확장성을 안정적으로 확보할 수 있다. 생산자는 소비자 프로세스가 다운되어 있어도 메시지를 발행할 수 있다. 소비자는 생산자 서비스가 가용한 상태가 아니더라도 메시지를 수신할 수 있다.
즉 일반적인 HTTP 통신으로 서비스 간 통신을 한다면, 클라이언트와 서버 중 한 쪽이라도 가용하지 않다면 해당 통신 내용은 실패하고 사라지게 된다. 메시지 큐는 한 쪽이 가용하지 않더라도 메시지가 사라지지 않는다.
로그, 메트릭, 자동화
- 로그 : 여러 서버에서 로그를 만들 때 이를 단일 서비스로 모아주면 편리하게 관리할 수 있다.
- 메트릭 : 메트릭은 시스템의 현재 상태를 나타내는 몇가지 지표를 의미한다.
호스트 단위 메트릭 : CPU, 메모리, 디스크 IO
종합 메트릭 : 데이터베이스 계층 성능, 캐시 계층 성능
핵심 비즈니스 메트릭 : 일별 능동 사용자, 수익, 재방문 - 자동화 : 생산성을 높이기 위한 자동화 도구를 활용할 수 있다. 지속적 통합을 자동화하면 코드를 검증을 자동화할 수 있다.
데이터베이스 규모 확장
데이터 양이 많아지면 데이터베이스 부하도 증가한다. 데이터베이스를 증설할 방법에 대해 알아본다. 데이터베이스도 웹 서버 규모 확장과 동일하게, 수직적 확장(스케일 업)과 수평적 확장(스케일 아웃) 전략이 있다.
샤딩
샤딩은 데이터베이스를 작은 단위(샤드)로 분할하는 기술이다. 각 샤드는 같은 스키마를 쓰지만 보관되는 데이터 사이에 중복이 없다.
예를 들어 유저 테이블을 4개로 확장한다면, 유저 아이디를 4로 나눈 나머지 값에 따라 테이블에 나눠서 저장하면 중복 없이 샤딩할 수 있다.
다만 다음과 같은 부분을 고려해야 한다.
- 재샤딩(resharding) : 데이터가 기존 샤드로 버틸 수 없을 때, 일부 샤드에 데이터가 지나치게 몰려서 가득 찰 때 샤드 키 계산하는 함수를 수정하여 데이터를 재배치시킬 수 있다. (안정 해시 기법을 활용하면 문제를 해결할 수 있다.)
- 유명인사 문제(핫스팟 키) : 특정 샤드에 질의가 집중되어 과부하가 걸리는 문제. 이 경우에는 자주 질의되는 데이터들을 한 샤드에 몰리지 않도록 하거나 샤드를 더 잘게 나눠야 한다.
- 조인과 비정규화 : 샤딩된 테이블들과 조인하기가 힘들다. 비정규화를 통해 하나의 테이블에서 질의할 수 있도록 하는 방법을 고민해야 한다.
요약
- 웹 계층은 무상태 계층
- 모든 계층 다중화
- 가능한 많은 데이터 캐시
- 여러 데이터 센터 지원
- 정적 컨텐츠는 CDN을 통해 서비스
- 데이터 계층은 샤딩을 통해 규모 확장
- 각 계층은 독립적 서비스로 분할
- 시스템을 지속적으로 모니터링, 자동화 도구 활용