서문
본 장에서는 사용자 수에 따른 서버 확장 방법에 대해서 설명한다. 이번 글에서는 책의 내용을 그대로 따라가기 보다는 독자의 이해를 돕기 위해서 실제 서비스 명을 사용해서 내 방식으로 글을 풀어보려고한다.
단일 서버
단일 서버는 모든 컴포넌트가 한 대의 서버에서 실행되는 시스템으로 웹 앱,데이터베이스, 캐시 등이 전부 한대의 서버에서 실행된다. 그럼 한 번 예시로 nginx + spring + mysql + redis로 묶인 서버가 있다고 가정하겠다.
아래의 사진은 실제 단일 서버 구조 예시를 보여준다. 이러한 단일 서버 구조에서 가장 먼저 책에서 설명하는 내용은 데이터베이스 서버를 분리하는 것이다. 이 말은 즉, 서버를 하나 더 추가해서 해당 서버에서 DB를 실행하겠다는 것을 의미한다.

데이터베이스
데이터 베이스가 분리된 서버

데이터 베이스 종류
- 관계형 데이터 베이스 : 데이터를 테이블과 열, 칼럼으로 표현해서 여러 테이블에 있는 데이터를 관계에 따라서 Join할 수 있다. 보통 대부분의 상황에서 관계형 데이터베이스는 최우선으로 DB 선택지로 고려된다. 하지만 종종 실제로 내가 구축하려는 서비스에서 관계형 데이터 베이스가 최선의 선택이 아닌 경우도 있다.
- 비관계형 데이터 베이스 : NoSQL DB라고도 부르는데 네 부류로 나뉘게 된다.
1. 키 값 저장소
2.그래프 저장소
3.칼럼 저장소
4.문서 저장소
이러한 NoSQL DB는 아래와 같은 경우에 바람직한 선택일 수 있다.
1. 아주 낮은 응답 지연시간 요구
2. 다루는 데이터가 비정형이라 관계형 데이터가 아님
3. 데이터를 직렬화하거나 역직렬화 할 수 있기만 하면 됨
4. 아주 많은 양의 데이터를 저장할 필요가 있음
수직적 규모 확장 vs 수평적 규모 확장
수직적 규모 확장(스케일 업)
수직적 규모 확장은 서버의 스펙을 단순히 늘리는 것을 의미한다.

스케일업은 아주 간단한 선택지 이지만 치명적인 단점이 있다.
1. 수직적으로 규모를 확장하는 데에는 한계가 있다. 우리가 무한으로 CPU나 RAM을 늘리는건 한계가 있다.
2. 하나의 서버가 다운되면 그대로 웹사이트/앱이 중단된다.
이러한 이유로 인해서 대규모 시스템에서는 수평적 규모 확장이 더 적절하다.
수평적 규모 확장(스케일 아웃)
수평적 규모 확장은 서버의 사양 자체를 올리기보다는 여러개의 서버를 둬서 요청을 분산처리 하는 방식이다.
먼저 로드밸런서를 통한 스케일 아웃에 대해서 예시를 보이겠다.
로드밸런서

이러한 방식을 사용하면 수직적 규모 확장에서 해결하지 못했던 문제점들을 해결할 수 있다.
1. 서버가 하나 다운되더라도 다른 서버가 작업을 받아서 수행할 수 있다.
2. 감당하기 어려운 트레픽이 들어오면 서버를 더 추가하기만 하면된다.
데이터 베이스 다중화
앞에서 살펴본 API 서버와 마찬가지로 DB 역시도 수평적 확장이 가능하다. 보통 DB에서 가장 많이 일어나는 연산은 READ 연산이다. 따라서 실제 DATA를 WRITE하는 것은 주 데이터베이스에서 진행하고 읽는 것은 부 데이터베이스에서 진행하면 데이터 베이스를 수평적으로 확장할 수 있다. 부 데이터 베이스는 주 데이터베이스로부터 그 사본을 전달 받아서 읽기 연산만을 지원한다. 하지만 여기서 보이는 확장은 DB전체의 완전한 수평적 확장보다는 읽기 성능의 수평 확장에 가깝다. 실제 DB의 수직적,수평적 확장은 이 장의 마지막에서 다룰 예정이다.

이러한 데이터 베이스 다중화는 아래와 같은 이익이 있다.
1. 더 나은 성능 : 병렬로 처리할 수 있는 질의 수가 늘어나서 성능이 좋아진다.
2. 안정성 : 데이터 베이스 일부가 파괴되어도 다른 데이터베이스가 살아있다면 데이터를 보존할 수 있다.
3. 가용성 : 데이터를 여러 지역에 복제해 둠으로써, 하나의 데이터베이스에 장애가 발생해도 계속 서비스가 가능하다.
지금까지 살펴본 내용을 기반으로 만든 서버구조

지금까지는 로드밸런서 + 수평적 확장 서버 + 데이터 베이스 다중화를 사용한 서버구조에 대해서 알아보았다.
이제 다음으로 캐시에 대해서 알아보려고한다.
캐시
캐시 계층
캐시 - 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고 더 빨리 데이터에 접근할 수 있게 해주는 저장소이다. 캐시 계층은 데이터베이스보다 훨씬 빠르지만 실제 DB보다는 가지고 있는 데이터가 한정적이므로 여러가지 상황을 고려해서 설계해야한다. 그리고 보통 캐시 우선 읽기 전략을 사용하지만 다양한 캐시 사용 정책이 있는데 이는 6장에서 설명할 예정이다.
캐시를 사용할 때에 유의점은 아래와 같다.
1. 캐시는 어떤 상황에 바람직한가? -> 갱신보다는 참조가 빈번하게 일어나는 경우 바람직하다.
2. 어떤 데이터를 캐시에 두어야하는가? -> 캐시는 휘발성 메모리에 데이터를 저장하는 것이다. 데이터의 영속성이 필요한 경우 바람직한 선택이 아닐 수 있다.
3. 캐시에 보관된 데이터는 어떻게 만료되는가? -> 만료기간이 너무 길지도 짧지도 않게 설정해야한다.
4. 일관성은 어떻게 유지되는가? -> 데이터 저장소의 원본과 캐시 내의 사본이 일관성을 가지고 있는지 확인해야한다. 이에 대한 자세한 내용은 페이스북에서 내놓은 논문 <Scaling Memcache at Facebook>(https://research.facebook.com/publications/scaling-memcache-at-facebook/)을 참고하면 좋다.
5. 장애에는 어떻게 대처할 것인가? -> 캐시 서버를 한대만 두는 경우 해당 서버는 단일 장애 지점이 될 수 있는 가능성이 있다. -> 결국 캐시도 데이터베이서처럼 다중화를 해야할까?
6. 캐시 메모리는 얼마나 크게 잡을 것인가? -> 너무 작으면 데이터가 너무 자주 밀려나서 성능이 떨어질 것이다. 그렇다면 과할당을 하면되는데 과할당의 문제는 없을까?
7. 데이터 방출 정책은 무엇인가? -> LFU, FIFO,LRU등 어떤 정책을 사용하는게 좋을까?
콘텐츠 전송 네트워크(CDN)
CDN은 정적 콘텐츠를 전송하는데 쓰이는 지리적으로 분산된 서버의 네트워크이다.

CDN을 사용할 시에 고려해야할 사항
1. 비용 : 보통 CDN은 제3 사업자(위 그림에서는 AWS)에 의해 운영되어 데이터 트레픽에 따라서 요금을 내야하기 때문에 자주 사용되지 않는 컨텐츠는 CDN에서 제거하자
2. 적절한 만료 시한 설정 : 시의성이 중요한 콘텐츠의 경우 만료 시점을 잘 정해야한다. 너무 짧지도 길지도 않아야한다.
3. CDN 장애에 대한 대처 방안 : CDN 자체가 죽은 경우 웹사이트/어플리케이션이 어떻게 동작해야하는지 고려해야한다. \
4. 콘텐츠 무효화 방법 : 아직 만료되지 않은 컨텐츠라도 만료 시킬 수 있다 -> CDN 서비스 사업자 API 사용, 오브젝트 버저닝 이용
무상태 웹 계층
무상태 아키텍처
무상태 웹 계층의 이미지는 이미 앞선 "수평적 규모 확장 예시"에서 보인 바 있다. 상태 정보를 웹 계층에서 제거하고 지속성 저장소에 보관하여 필요할 때 가저오는 구조를 만드는 것이다. 위의 구조처럼 redis와 MySQL에 영속성이 보장되어야 하는 정보를 저장하면 서버는 stateless하기 때문에 수평적으로 확장할 수 있다.
상태 정보 의존적인 아키텍처
만약 상태 의존적인 웹 계층을 구성했다면 로드 밸런서의 고정 세션이라는 기능을 사용해야하지만 이는 로드밸랜서에 부담을주고 장애처리도 어렵다.
데이터 센터
- 지리적 라우팅 : geoDNS에 요청을 보내서 가장 가까운 데이터 센터로 요청을 라우팅한다.

이러한 다중 데이터센터 아키텍처를 만들려면 몇 가지 기술적 난제를 해결해야한다.
1. 트레픽 우회 : 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 한다. -> geoDNS의 역할
2. 데이터 동기화 : 모든 데이터 베이스들의 데이터가 동기화 되어야하는데 이는 쉬운 일이 아니다 넥플릭스의 <Active-Active for Multi-Regional Resiliency>참고 -> https://netflixtechblog.com/active-active-for-multi-regional-resiliency-c47719f6685b?gi=6a105a07b091
Active-Active for Multi-Regional Resiliency
With large scale and velocity there is increased chance of failure.
netflixtechblog.com
3. 테스트와 배포 : 여러 위치에서 테스트 그리고 모든 데이터 센터에 동일한 서비스가 설치되는 되에 자동화된 배포 도구가 중요하다.
메시지 큐
메세지 큐 : 소비자가 메세지를 꺼낼 때 까지 데이터의 무손실을 보장하는 비동기 통신 컴포넌트로 메세지 큐를 이용하면 서비스 간 결합이 느슨해져서, 규모 확장성이 보장되어야 하는 안정적 애플리케이션을 구성하기 좋다.

로그,메트릭 그리고 자동화
위 키워드들은 대규모 서비스를 만드는 데에 필수적인 요소들이다.
로그 : 로그를 통해서 시스템의 오류와 문제들을 보다 쉽게 찾아낼 수 있다. 에러 로그는 서버 단위로 모니터링 할 수도 있지만 로그를 단일 서비스로 모아주는 도구를 활용하면 더 편리하게 검색하고 조회할 수 있다.
메트릭 : 메트릭을 잘 수집하면 시스템을 파악하기 위한 유용한 정보를 얻을 수 있다. 메트릭 가운데 유용한 메트릭들을 정리하면 다음과 같다.
1. 호스트 단위 메트릭 : CPU, 메모리, 디스크I/O관련 메트릭
2. 종합 메트릭 : 데이터베이스 계층의 성능, 캐시 계층의 성능 같은 것
3. 핵심 비즈니스 메트릭 : 일별 능동 사용자, 수익, 재방문
자동화 : 시스템이 복잡해지면 생산성을 위해서 자동화 도구의 활용이 필요하다. CI 도구가 있다.
데이터 베이스의 규모 확장
앞에서 서버의 규모 확장에 대해서 알아보았으니 이제 데이터 베이스의 규모 확장에 대해서도 말해보려고한다. 언뜻보면 다중화된 데이터베이스에서 이미 다룬 내용 같지만 실제로는 차이가 존재하니 이를 유의하면 좋을 것 같다.
수직적 확장
수직정 확장은 앞에서 설명한 것과 마찬가지로 기존 서버에 더 고성능의 자원을 증설하여 해결하는 방법이다. 하지만 이는 몇가지 치명적인 문제점이 있다.
1. 무한히 증가시키는 것은 불가능하다.
2.SPOF로 인한 위험성이 크다.
3. 비용이 많이든다. (성능과 비용의 그래프는 성능이 올라갈 수록 기하급수적으로 증가한다)
수평적 확장
데이터 베이스의 수평적 확장은 웹 서버의 수평적 확장과는 약간의 차이점을 가지고 있다.
샤딩 : 대규모 데이터베이스를 샤드라고 부르는 작은 단위로 분할하는 기술을 일컫는다. 이 때 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다.

샤딩 전략을 고려해야할 때 가장 중요한 것은 샤딩 키를 어떻게 정하느냐 이다. 그리고 이 샤딩키를 정할 때 가장 중요한 것은 어떻게 데이터를 고르게 분할 할 수 있는가?이다. 샤딩은 데이터베이스 규모 확장을 실현하는 훌륭한 기술이지만 완벽하진 않다. 이를 도입하면 시스템이 복잡해지고 아래와 같이 풀어야할 새로운 문제도 생길 수 있다.
샤딩에서 발생할 수 있는 문제점
1. 데이터의 재 샤딩: 데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때, 샤드 간 데이터 분포가 균등하지 못하여 어떤 샤드에 할당된 공간 소모가 다른 샤드에 비해 빨리 진행될 때 -> 샤드 소진 -> 5장에서 다룰 안정 해시 기법을 활용하면 이 문제를 해결할 수 있다.
2. 유명인사 문제 : 핫스팟 키 문제라고도 불리는 이 문제는 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제다. 예를들어 블랙핑크,BTS, 메시 등의 유명인사들의 정보가 하나의 샤드에 저장되는 데이터베이스로 SNS로 구축한다면 과도한 read연산 때문에 과부하가 걸릴 수 있다.
3. 조인과 비정규화 : 일단 하나의 데이터베이스를 여러 샤드 서버로 쪼개면, 여러 샤드에 걸친 데이터를 조인하기 힘들어진다. -> 데이터베이스를 비정규화하여 하나의 테이블에서 질의를 하도록 하는 방식으로 해결할 수 있다.
정리
- 웹 계층은 무상태 계층으로
- 모든 계층에 다중화 도입
- 가능한 한 많은 데이터를 캐시할 것
- 여러 데이터 센터를 지원할 것
- 정적 콘텐츠는 CDN을 통해 서비스 할 것
- 데이터 계층은 샤딩을 통해 그 규모를 확장할 것
- 각 계층은 독립적으로 서비스로 분할할 것
- 시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용할 것