[System Design] Scale From Zero To Millions Of Users

Scale From Zero To Millions Of Users

Single server setup

웹 앱, 데이터베이스, 캐시 등 모든 것이 하나의 서버에서 실행되는 간단한 구조다.

요청 흐름과 트래픽 소스를 살펴보면 이 구조를 이해할 수 있다.

  1. 사용자는 api.mysite.com과 같은 도메인 이름을 통해 웹사이트에 엑세스한다. 일반적으로 DNS는 당서 서버에서 호스팅하지 않고 타사에서 제공하는 유료 서비스이다.
  2. IP 주소는 브라우저 또는 모바일 앱으로 반환된다.
  3. IP 주소가 확보되면 HTTP 요청이 웹 서버로 직접 전송된다.
  4. 웹 서버는 렌더링을 위해 HTML 페이지 또는 JSON 응답을 반환한다.

웹 서버로 유입되는 트래픽은 웹 애플리케이션과 모바일 애플레키에션 두 가지 소스에서 발생한다.

  • 웹 애플리케이션
    • 비즈니스 로직, 저장소 등을 처리하기 위해 서버측 언어와 프레젠테이션을 위한 클라이언트 측 언어의 조합을 사용한다.
  • 모바일 애플리케이션
    • HTTP 프로토콜은 모바일 앱과 웹 서버간의 통신 프로토콜이다. JSON은 단순성 때문에 데이터를 전송하는데 일반적으로 사용되는 API 응답 형식이다.

Database

사용자가 증가함에 따라 단일 서버로 충분하지 않고 웹/모바일 트래픽을 위한 서버와 데이터베이스를 위한 서버 등 목적에 따라 분리되어 필요하게 된다. 서버가 분리되면 각각 독립적으로 확장할 수 있다.

Which database to use?

관계형 데이터베이스와 비관계형 데이터베이스 중에서 선택할 수 있다.

관계형 데이터베이스는 테이블과 행으로 데이터를 저장한다. 서로 다른 테이블에서 SQL을 통해 조인 작업을 수행할 수 있다.

비관계형 데이터베이스는 키-값 저장소, 그래프 저장소, 컬럼 저장소, 문서 저장소의 네 가지 범주로 나뉜다. 비관계형 데이터베이스에서는 일반적으로 조인이 지원되지 않는다.

관계형 데이터베이스는 오랫동안 적극적으로 사용되어 왔기 때문에 최선의 선택일 수 있다. 하지만 특정 사용 사례에 따라 비관계형 데이터베이스가 더 좋은 선택이 될 수 있다.

  • 애플리케이션 지연시간
  • 데이터가 비정형이거나 관계가 없음
  • 데이터의 직렬화만 수행하면 됨
  • 방대한 양의 데이터 저장 필요

Vertical scaling vs Horizontal scaling

스케일 업은 서버에 더 많은 리소스를 추가하는 방식이다. 스케일 아웃은 리소스 풀에 더 많은 서버를 추가하여 확장한다. 트래픽이 적을때는 수평 확장이 가지는 단순성 때문에 좋은 옵션이 된다. 하지만 단일 서버에 CPU나 메모리를 무제한적으로 추가하는것은 불가능하며 failover와 이중화가 없기 때문에 서버가 single fault failure가 된다. 수직 확장의 한계로 수평 확장이 더 바람직하다.

Load balancer

이전 디자인에서는 사용자가 웹 서버에 직접 연결되고 웹 서버가 오프라인 상태라면 사용자는 웹사이트에 엑세스할 수 없다. 또한 많은 사용자가 동시에 웹 서버에 접속하여 부하가 가중되면 느린 응답을 경험하거나 서버에 연결할 수 없다. 이런 문제를 로드 밸런서는 효과적으로 처리한다.

로드 밸런서는 서버에 들어오는 부하를 균등하게 처리한다. 사용자는 로드 밸런서의 공인 IP에 접근하게 되고 클라이언트는 웹 서버가 아닌 로드 밸런서로 연결된다. 보안을 위해 로드 밸런서는 사설 IP를 통해 웹 서버와 통신한다.

로드 밸런서를 통해 서버1이 오프라인 상태가 되면 트래픽이 서버2로 라우팅 된다. 또한 트래픽이 증가하여 두 대의 서버로 부하를 처리하기 충분하기 않을때 서버 풀에 새로운 정상 웹 서버를 추가하여 부하를 분산시킨다.

Database Replication

데이터베이스 계층도 웹 계층과 마찬가지로 이중화와 장애조치가 필요하다. 데이터베이스 복제는 일반적으로 master - slave 구조로 이루어진다. 마스터는 일반적으로 쓰기 작업만 지원한다. 슬레이브는 마스터에서 데이터의 복사본을 가져오고 읽기 작업만 지원한다. 데이터를 수정하는 요청은 모두 마스터로 보내야하고 대부분의 애플리케이션은 쓰기보다 읽기 비율이 높기 때문에 일반적으로 슬레이브의 수가 마스터보다 많다.

Advantages of database replication

  • 성능 향상
    • master - slave 모델에서는 데이터를 수정하는 요청이 마스터에서 이루어지지만 읽기는 슬레이브 노드에 분산된다.
  • 안정성
    • 여러 IDC에 걸쳐 데이터베이스를 복제하므로 IDC failure와 같은 장애에도 대응 가능하다.
  • 고가용성
    • 여러 위치에 데이터를 복제하면 다른 데이터베이스 서버에 저장된 데이터에 엑세스할 수 있으므로 데이터베이스가 오프라인 상태에서도 서비스가 운영된다.

실제로 데이터베이스가 오프라인이 되면 발생하는 상황을 살펴보면 도움이 된다.

슬레이브가 다운되었을 경우 읽기 작업이 마스터로 이동한다. 문제가 발견되면 즉시 새 슬레이브가 이전 슬레이브를 대체한다. 여러 개의 슬레이브를 사용 중인 경우 읽기 작업은 다른 정상 슬레이브로 리디렉션 된다.

마스터가 다운되면 슬레이브중 하나가 마스터로 승격된다. 이 과정에서 지연 복제로 인한 데이터 불일치는 복구되어야 하며 어떤 슬레이브를 승격시킬지에 대한 합의 문제도 필요하다. multi mastercircular replication과 같은 다른 복제 방식도 도움이 될 수 있다.

Cache

별도의 캐시 계층을 통해 시스템 성능 향상, 데이터베이스 부하 감소, 캐시 계층을 독립적으로 확장할 수 있는 기능 등의 이점이 있다.

요청을 받은 웹 서버는 먼저 캐시에 사용 가능한 응답이 있는지 확인한다. 응답이 있으면 클라이언트로 데이터를 보내고 그렇지 않다면 데이터베이스에 질의해 응답을 캐시에 저장한 후 클라이언트로 다시 보낸다. 이런 캐싱 전략을 읽기 캐시라고 하며 데이터 유형, 크기, 액세스 패턴에 따라 다른 전략을 사용할 수 있다.

Considerations for using cache

  • 캐시 적용 대상
    • 자주 읽지만 수정 빈도가 낮은 경우 캐시 사용을 고려한다. 캐시 데이터 메모리에 저장되기에 휘발성 특성이 있어 영속적인 저장이 필요하다면 영구 데이터 저장소에 저장해야 한다.
  • 만료 정책
    • 캐시된 데이터가 만료되면 캐시에서 제거된다. 만료 정책이 없는 경우 캐시된 데이터는 메모리에 영구적으로 저장된다. 만료일이 너무 짧다면 시스템이 데이터베이스에서 데이터를 너무 자주 로드할 수 있으므로 좋지 않다. 만료일이 너무 길다면 원본 데이터와 불일치가 발생할 수 있다.
  • 일관성
    • 데이터 저장소와 캐시를 동기화 상태로 유지하는 것은 어렵다. 이종 저장소 간에 수정 작업이 단일 트랜잭션에 포함되지 않기 때문에 불일치가 발생할 수 있다. 여러 지역으로 확장할 때 데이터 저장소와 캐시 간의 일관성을 유지하는 일은 쉽지 않다.
  • 장애 완화
    • 단일 캐시 서버는 단일 장애 지점(SPOF)이 된다. SPOF를 방지하기 위해선 여러 데이터센터에 걸쳐 캐시를 배치해 두는것이 좋다. 또 다른 권장하는 접근 방식은 필요한 메모리를 특정 비율만큼 오버프로비저닝 해두어 메모리 사용량의 증가에 따라 버퍼가 제공되도록 하는 방법이다.
  • 퇴거 정책
    • 캐시가 가득 차면 캐시에 항목을 추가하려는 요청으로 인해 기존 항목이 제거될 수 있다. LRU는 가장 많이 사용되는 정책이다. 그외에도 LFU나 FIFO같은 다양한 방식을 고려할 수 있다.

Content delivery network (CDN)

CDN은 정적 콘텐츠를 전송하는데 사용되는 지리적으로 분산된 서버다. CDN은 이미지, 동영상, CSS, JavaScript 파일 등과 같은 정적 콘텐츠를 캐시한다. 사용자가 웹사이트를 방문하면 사용자와 가장 가까운 CDN 서버가 정적 콘텐츠를 전송하는 것이 CDN의 기본적인 작동 방식이다. 사용자가 CDN 서버에서 멀어질수록 웹사이트 로딩 속도가 느려진다.

  1. 사용자 A가 이미지 URL을 사용해 image.png를 가져오려 한다. URL의 도메인은 CDN 제공 업체에서 제공한다.
  2. CDN 서버의 캐시에 image.png가 없는 경우 CDN 서버는 원본에서 파일을 요청한다.
  3. 오리진은 이미지가 캐시되는 시간을 설명하는 선택적 HTTP 혜더 TTL이 포함된 image.png를 CDN 서버로 반환한다.
  4. CDN이 이미지를 캐싱하여 사용자 A에게 반환한다. 이미지는 TTL이 만료될 때까지 CDN에 캐싱된 상태로 유지된다.
  5. 사용자 B가 동일한 이미지 가져오기 요청을 보낸다.
  6. TTL이 만료되지 않는 한 이미지가 캐시에서 반환된다.

Considerations of using a CDN

  • 비용
    • CDN은 타사 제공 업체에서 운영하며 CDN을 오가는 데이터 전송에 대한 요금이 부과된다. 자주 사용하지 않는 에셋을 캐싱하는 것은 큰 이점을 제공하지 않으므로 CDN 외부로 옮기는 것을 고려해야 한다.
  • 적절한 캐시 만료 설정
    • 시간에 민감한 콘텐츠의 경우 캐시 만료 시간을 설정하는 것이 중요하다. 캐시 만료 시간은 너무 길지도 짧지도 않아야 한다. 너무 길면 콘텐츠가 더 이상 최신 상태가 아닐 수 있다. 너무 짧으면 원본 서버에서 CDN으로 콘텐츠가 반복적으로 다시 로드될 수 있다.
  • CDN fallback
    • 웹 사이트 / 애플리케이션이 CDN 장애에 어떻게 대처하는지 고려해야 한다. 일시적인 CDN 중단이 발생하는 경우 클라이언트가 문제를 감지하고 오리진에 리소스를 요청할 수 있어야 한다.
  • 파일 무효화
    • 만료되기 전에 CDN에서 파일을 제거할 수 있어야 한다.
      • CDN 공급 업체에서 제공하는 API를 사용해 CDN 개체 무효화
      • 오브젝트 버전 관리를 사용하여 다른 버전의 오브젝트 제공 ex) image.png?v=2

Stateless Webtier

웹 계층의 수평적 확장을 고려해야 한다. 이를 위해서는 상태(사용자 세션 등..)을 웹 계층 밖으로 이동해야 한다. 세션 데이터를 관계형 데이터베이스나 NoSQL과 같은 영구 저장소에 저장하는 것이 좋다. 클러스터의 각 웹 서버는 데이터베이스에서 상태 데이터에 엑세스할 수 있다. 이를 stateless web tier라고 한다.

Stateful architecture

Stateful server와 stateless server에는 몇 가지 주요 차이점이 있다. Stateful server는 한 요청에서 다음 요청까지 클라이언트 데이터를 기억한다. Stateless 서버는 저장하지 않는다.

사용자 A의 정보는 서버 1에 저장되어 있다. 사용자 A를 인증하려면 HTTP 요청이 서버 1로 라우팅 되어야 한다. 요청이 서버 2와 같은 다른 서버로 전송되면 사용자 A의 세션 데이터가 포함되어 있지 않기 때문에 인증이 실패한다. 이 방식의 문제는 동일한 클라이언트로부터의 모든 요청이 동일한 서버로 라우팅되어야 한다는 것이다. 이는 대부분의 로드 밸런서의 sticky session을 통해 수행할 수 있지만 오버헤드가 추가된다. 이 아키텍쳐에서는 서버를 추가하거나 제거하는것이 훨씬 어렵고 서버 장애를 처리하는 것도 어렵다.

Stateless architecture

Stateless 아키텍쳐에서는 사용자의 HTTP 요청을 모든 웹 서버로 전송할 수 있으며 이 서버는 공유 데이터 저장소에서 상태 데이터를 가져온다. 상태 데이터는 공유 데이터 저장소에 저장되며 웹 서버에 저장되지 않는다. Stateless 시스템은 더 간단하고 안정적이며 확장성이 뛰어나다.

Data centers

가용성을 개선하고 더 넓은 지역에서 더 나은 사용자 경험을 제공하려면 여러 데이터 센터를 지원하는 것이 중요하다. 아래는 두 데이터 센터가 있는 설정의 예를 보여준다. 정상 작동 시 사용자는 지리적 라우팅이라고 하는 geoDNS 라우팅을 통해 가장 가까운 데이터 센터로 라우팅되며 트래픽은 미국 동부에서는 x% 미국 서부에서는 (100-x)%로 분할된다. geoDNS는 사용자의 위치를 기반으로 도메인 이름을 IP 주소로 확인할 수 있는 DNS 서비스다.

데이터센터에 심각한 장애가 발생하면 모든 트래픽을 정상 데이터센터로 라우팅한다.

다중 데이터 센터를 구축하려면 몇 가지 기술적 과제를 해결해야 한다.

  • 트래픽 리디렉션
    • 트래픽을 올바른 데이터 센터로 리디렉션 하려면 효과적인 도구가 필요하다. GeoDNS는 사용자의 위치에 따라 가장 가까운 데이터 센터로 트래픽을 리디렉션 하는데 사용할 수 있다.
  • 데이터 동기화
    • 서로 다른 지역의 사용자는 서로 다른 로컬 데이터베이스 또는 캐시를 사용할 수 있다. 장애 조치의 경우 데이터를 사용할 수 없는 데이터 센터로 트래픽이 라우팅될 수 있다. 일반적인 전략은 여러 데이터 센터에 데이터를 복제하는 것이다.
  • 테스트 및 배포
    • 다중 데이터 센터에서는 여러 위치에서 웹사이트 / 애플리케이션을 테스트하는 것이 중요하다. 자동화된 배포 도구는 모든 데이터 센터에서 서비스의 일관성을 유지하는데 필수적이다.

Message Queue

메세지 큐는 내구성있는 컴포넌트로 비동기 통신을 지원한다. 버퍼의 역할처럼 비동기 요청을 제공한다. 메세지 큐의 아키텍쳐는 Producer Consumer 모델을 따른다. Producer가 메세지를 큐에 게시하면 소비자는 큐에서 메세지를 받아와 처리한다.

메세지 큐를 통한 디커플링은 애플리케이션을 확장 가능하고 안정적인 아키텍쳐로 만든다. 메세지 큐를 사용하면 소비자가 메세지를 처리할 수 없을 때 생산자가 큐에 메세지를 게시할 수 있다. 소비자는 생산자가 사용할 수 없을 때에서 큐에서 메세지를 읽을 수 있다. 애플리케이션이 이미지나 동영상에 대한 변환과 같은 무거운 작업을 한다고 가정하자. 이런 작업은 메세지 큐에서 작업을 선택하여 비동기적으로 작업을 수행한다. Consumer와 Producer는 독립적으로 확장할 수 있다. 대기열의 크기가 커지면 처리 시간을 줄이기 위해 더 많은 워커 노드들이 추가된다. 그러나 대기열에 대부분이 유휴상태라면 워커 노드들은 줄어들 수 있다.

Logging, metrics, automation

작은 규모의 서버에서는 로깅, 메트릭 및 자동화가 필수는 아니다. 하지만 대규모 서버에서는 이야기가 다르다.

  • 로깅
    • 오류 로그를 모니터링하는 것은 시스템의 오류와 문제 파악에 도움이 된다. 서버 수준에서 오류 로그를 모니터링 하거나 도구를 통해 오류 로그를 중앙 집중식 서비스로 집계하여 볼 수 있다.
  • 메트릭
    • 다양한 유형의 메트릭을 수집하면 비즈니스 인사이트와 시스템의 상태를 이해하는데 도움이 된다.
    • Host level metrics: CPU, Memory, Disk I/O, etc.
    • Aggregated level metrics: performance of the entire database tier, cache tier, etc.
    • Key business metrics: daily active users, retention, revenue, etc.
  • 자동화
    • 시스템이 크고 복잡해지면 생산성 향상을 위한 자동화 도구를 구축하거나 활용해야 한다. CI는 자동화를 통해 각 코드를 체크인해 문제를 조기에 발견하도록 돕는다. 또한 빌드, 테스트, 배포 프로세스 등을 자동화하면 생산성을 향상시킬 수 있다.

Database scaling

데이터베이스 확장에는 두 가지 접근 방법이 있다.

Vertical scaling

스케일 업이라고 하는 수직 확장은 기존 시스템에 더 많은 성능(CPU, RAM, 디스크 등)을 추가하여 확장하는 방식이다. Amazon 관계형 데이터베이스 서비스는 24TB의 RAM을 갖춘 데이터베이스 서버를 얻을 수 있다. 이런 종유릐 강력한 데이터베이스 서버는 많은 데이터를 저장하고 처리할 수 있다. 그러나 수직 확장에는 몇 가지 심각한 단점이 있다.

  • 데이터베이스에 리소스를 추가할 수 있지만 하드웨어 제한이 있음
  • 단일 장애 지점의 위험
  • 수직 확장의 전체 비용이 높음

Horizontal scaling

샤딩이라고 하는 수평 확장은 서버를 더 추가하는 방식이다. 샤딩은 대규모 데이터베이스를 샤드라고 하는 더 작고 관리하기 쉬운 부분으로 분리한다. 각 샤드는 동일한 스키마를 고유하지만 샤드의 실제 데이터는 샤드에 고유하다.


사용자 데이터는 사용자 ID를 기반으로 데이터베이스 서버에 할당된다. 데이터에 엑세스할 때마다 해시 함수를 사용해 해당 샤드를 찾는다. 위 예제에서는 user_id % 4가 해시 함수로 사용된다. 샤딩을 구현할 때 고려해야할 가장 중요한 요소는 샤딩 키의 선택이다. 샤딩 키는 데이터 분산 방식을 결정하는 하나 이상의 열로 구성된다. 샤딩 키를 사용하면 데이터베이스 쿼리를 올바른 데이터베이스로 라우팅하여 데이터를 효율적으로 검색하고 수정할 수 있다. 샤딩 키를 선택할 때 가장 중요한 기준 중 하나는 데이터를 고르게 분산할 수 있는 키를 선택하는 것이다. 샤딩은 데이터베이스를 확장하는 훌륭한 방법이지만 완벽한 방법과는 거리가 멀다. 샤딩은 시스템에 복잡성과 새로운 문제를 가져온다.

  • Resharing data
    • 빠른 성장으로 인해 단일 샤드가 더 이상 많은 데이터를 저장할 수 없는 경우에 필요
    • 특정 샤드는 고르지 않은 데이터 분포로 인해 다른 샤드보다 더 빨리 샤드 소진을 경험할 수 잇음
    • 샤드 소진이 발생하면 샤딩 기능을 업데이트하고 데이터를 이동해야 함
    • Consistent hashing은 이 문제를 해결하기 위한 일반적으로 사용되는 기술
  • Celebrity problem
    • 핫스팟 키 문제로도 불리는 이 문제는 특정 샤드에 과도한 엑세스가 서버에 과부하를 유발하는 문제이다. 유명인들의 데이터가 모두 같은 샤드에 있다고 가정하자. SNS의 경우 해당 샤드는 읽기 작업으로 인해 과부하가 걸릴 것이다. 이 문제를 해결하려면 각 유명인사별로 샤드를 할당해야 할 수 있다.
  • Join and de-normalization
    • 데이터베이스가 여러 서버에 걸쳐 사딩된 후에는 데이터베이스 샤드 간에 조인 작업을 수행하기 어렵다. 일반적인 해결 방법은 단일 테이블에서 쿼리를 수행할 수 있도록 정규화를 해제하는 것이다.

Author: Song Hayoung
Link: https://songhayoung.github.io/2023/03/06/System%20Design/ByteByteGo/book/scale-from-zero-to-millions-of-users/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.