10만명의 유저가 될때까지 백엔드 인프라 확장하기

이 글은 원문 Scaling to 100k Users(https://alexpareto.com/scalability/systems/2020/02/03/scaling-100k.html) 원 저자인 Alex Pareto 의 동의 하에 번역하였음을 알려드립니다.

많은 스타트업이 있습니다. 수많은 신규 사용자가 매일 계정에 쉴세없이 등록하는 것처럼 느껴지고 엔지니어링 팀은 인프라가 지속적으로 유지하기 위해 분투합니다.

행복한 고민이지만 0 에서 수십만 명의 사용자를 받는 웹앱을 구현하는 정보가 부족할 수 있습니다.

일반적으로 솔루션은 갑자기 발생한 집중된 트래픽이나 병목현상을 찾아내는 것에서(종종 두가지 모두) 얻게 됩니다.

그렇긴 하지만 저는 확장성이 높은 사이드 프로젝트를 수행하면서 주요 패턴 중 상당수가 비교적 정형회되어 있다는 것을 알게 되었습니다.

이 글은 그 공식의 기본을 글로 만들기 위한 시도입니다. 이제 우리는 새로운 사진 공유 웹 사이트인 Graminsta를 1명에서 10만명의 사용자로 만들 것입니다.

1 사용자: 1개 머신

거의 모든 에플리케이션(즉 웹 사이트 또는 모바일 응용 프로그램)은 API, 데이터베이스, 클라이언트(일반적으로 응용 프로그램 또는 웹 사이트)의 세 가지 주요 구성 요소가 있습니다.

최신 에플리케이션 개발에서 클라이언트를 API 와 완전히 별도의 개체로 생각하면 응용 프로그램 확장에 대한 추론이 훨씬 쉬워진다는 것2을 알았습니다.

처음으로 에플리케이션을 빌드 할 때 이 3가지가 모두 하나의 서버에서 실행되는 것이 좋습니다. 어떤면에서 이것은 개발 환경과 비슷합니다. 한 엔지니어가 데이터베이스, API 및 클라이언트를 모두 같은 컴퓨터에서 실행합니다.

이론적으로는 아래와 같이 단일 DigitalOcean Droplet 또는 AWS EC2 인스턴스의 클라우드에 이를 배포 할 수 있습니다:

1명의 사용자

따라서 Graminsta를 한 사람 이상이 사용할 것으로 예상되는 경우 거의 항상 데이터베이스 계층을 분리하는 것이 좋습니다.

10 명의 사용자 : 데이터베이스 계층 분리

데이터베이스를 Amazon RDS 또는 Digital Ocean의 Managed Database와 같은 관리형 서비스로 분리하면 오랫동안 우리에게 도움이 될 것입니다.

단일머신 또는 EC2 인스턴스에서 자체 호스팅보다 약간 비싸지만 이러한 서비스를 사용하면 즉시 사용 가능한 많은 추가 기능을 즉시 사용할 수 있습니다.

  • 멀티 리전 중복성, 읽기 전용 복제본, 자동 백업 등

Graminsta 시스템의 모습은 다음과 같습니다:

10명의 사용자

100 명의 사용자 : 클라이언트 분리

운 좋게도 처음 몇 명의 사용자는 Graminsta를 좋아합니다. 이제 트래픽이 더욱 안정적으로 시작되었으므로 이제 고객을 분리해야합니다. 한 가지 주목할 점은 엔티티를 분리하는 것이 확장 가능한 응용 프로그램을 빌드하는 데 있어 핵심적인 요소라는 것입니다. 시스템의 한 부분이 더 많은 트래픽을 얻으면 자체 트래픽 패턴을 기반으로 서비스 확장을 처리 할 수 있도록 분할 할 수 있습니다.

이것이 제가 클라이언트를 API와 분리 된 것으로 생각하는 이유입니다. 웹, 모바일 웹, iOS, Android, 데스크톱 앱, 타사 서비스 등 여러 플랫폼을 구축하는 데 있어 추론하기가 매우 쉽습니다2. 모두 동일한 API를 사용하는 클라이언트 일 뿐입니다.

같은 맥락에서 사용자로부터 얻는 가장 큰 피드백은 Graminsta를 휴대 전화에서 원한다는 것입니다. 따라서 모바일 앱을 사용하는 동안 모바일 앱을 시작하겠습니다.

시스템의 모습은 다음과 같습니다:

100명의 사용자

1,000 명의 사용자 : 로드 밸런서 추가

Graminsta의 상황이 호전되고 있습니다. 사용자가 사진을 어디서든지 업로드하고 있습니다. 더 많은 가입을 시작하고 있습니다. 외로운 API 인스턴스가 모든 트래픽을 유지하는 데 문제가 있습니다. 더 많은 컴퓨팅 파워가 필요합니다!

로드 밸런서는 매우 강력합니다. 핵심 아이디어는 API 앞에로드 밸런서를 배치하고 트래픽을 해당 서비스의 인스턴스로 라우팅한다는 것입니다. 이를 통해 수평적 확장이 가능합니다 (동일한 코드를 실행하는 서버를 더 추가하여 처리 할 수있는 요청량 증가).

우리는 웹 클라이언트와 API 앞에 별도의 로드 밸런서를 배치 할 것입니다. 즉, API 및 웹 클라이언트 코드를 실행하는 인스턴스가 여러 개 있을 수 있습니다. 로드 밸런서는 트래픽이 가장 적은 인스턴스로 요청을 라우팅합니다.

이것으로부터 우리가 얻는 것은 이중화입니다. 한 인스턴스가 다운되면 (오버로드 또는 충돌이 발생하더라도) 전체 시스템이 중단되는 대신 들어오는 요청에 응답 할 수있는 다른 인스턴스가 있습니다.

로드 밸런서는 또한 자동 확장을 가능하게 합니다. 모든 사용자가 온라인 상태인 슈퍼볼 기간 동안 인스턴스 수를 늘리고 모든 사용자가 잠들 때 인스턴스 수를 줄이도록 로드 밸런서를 설정할 수 있습니다.

로드 밸런서를 사용하면 API 계층이 실질적으로 무한대로 확장 될 수 있으므로 더 많은 요청을 받을 때 인스턴스를 계속 추가 할 수 있습니다.

1000명의 사용자

참고 사항 : 지금까지 우리가 보유한 것은 Heroku 또는 AWS의 Elastic Beanstalk과 같은 PaaS 회사가 기본적으로 제공하는 것과 매우 유사합니다(인기가 높은 이유). Heroku는 데이터베이스를 별도의 호스트에 배치하고 자동 확장으로 로드 밸런서를 관리하며 API와 별도로 웹 클라이언트를 호스팅 할 수 있습니다. 이것은 프로젝트 또는 초기 단계의 스타트업에 Heroku와 같은 서비스를 사용하는 좋은 이유입니다. 필요한 모든 기본 사항이 기본적으로 제공됩니다.

10,000 사용자 : CDN

처음부터 이렇게 했어야 했는데, 그러나 Graminsta에서 빠르게 이동하고 있습니다. 이 모든 이미지를 제공하고 업로드하면 서버에 너무 많은 부하가 걸리기 시작합니다.

이 시점에서 이미지, 비디오 등을 생각하는(AWS의 S3 또는 Digital Ocean’s Spaces) 정적 콘텐츠를 호스팅하기 위해 클라우드 스토리지 서비스를 사용해야 합니다. 일반적으로 API는 이미지 및 이미지 업로드와 같은 작업을 처리하지 않아야 합니다.

우리가 클라우드 스토리지 서비스에서 얻는 또 다른 것은 CDN입니다(AWS에서는 이것이 Cloudfront라는 추가 기능이지만 많은 클라우드 스토리지 서비스가 즉시 제공합니다). CDN은 전 세계의 다른 데이터 센터에 이미지를 자동으로 캐시합니다.

주요 데이터 센터가 오하이오에서 호스팅 될 수 있지만 누군가 일본에서 이미지를 요청하면 클라우드 제공 업체가 사본을 만들어 일본의 데이터 센터에 저장합니다. 다음으로 일본에서 이미지를 요청한 사람은 이미지를 훨씬 더 빨리받습니다. 이는 전 세계에 적제 및 전송하는 데 오랜 시간이 걸리는 이미지 또는 비디오와 같은 더 큰 파일 크기를 제공해야 할 때 중요합니다.

10,000명의 사용자

100,000 명의 사용자 : 데이터 계층 확장

CDN은 우리에게 많은 도움을 주었습니다. Graminsta에서 많은 일들이 일어나고 있습니다. YouTube 유명인사인 Mavid Mobrick이 방금 가입하여 이야기를 게시했습니다. 환경에 10개의 API 인스턴스를 추가하는 로드 밸런서 덕분에 API CPU 및 메모리 사용량이 전반적으로 낮습니다. 그러나 요청에 대해 많은 시간 초과가 발생하기 시작했습니다. 왜 모든 요청이 오래 걸리는걸까요?

좀 더 자세히 조사해보면 데이터베이스 CPU가 80~90% 로 차 있습니다. 우리는 최대 한도를 초과했습니다.

데이터 계층의 스케일링은 아마도 방정식의 가장 까다로운 부분 일 것입니다. 상태 비 저장 요청을 처리하는 API 서버의 경우 인스턴스를 더 추가 할 수 있지만 대부분의 데이터베이스 시스템에서는 동일하지 않습니다. 이 경우 널리 사용되는 관계형 데이터베이스 시스템 (PostgreSQL, MySQL 등)을 살펴 보겠습니다.

캐싱

데이터베이스를 최대한 활용하는 가장 쉬운 방법 중 하나는 시스템에 새로운 구성 요소 인 캐시 계층을 도입하는 것입니다. 캐시를 구현하는 가장 일반적인 방법은 Redis 또는 Memcached와 같은 메모리 내 키 값 저장소를 사용하는 것입니다. 대부분의 클라우드에는 AWS의 Elasticache 및 Google Cloud의 Memorystore와 같은 관리 서비스 버전이 있습니다.

서비스가 동일한 정보에 대해 데이터베이스를 반복적으로 많이 호출 할 때 캐시가 유용합니다. 기본적으로 데이터베이스를 한 번 누르고 캐시에 정보를 저장하며 데이터베이스를 다시 만질 필요가 없습니다.

예를 들어 Graminsta에서 누군가가 Mavid Mobrick의 프로필 페이지를 방문 할 때마다 API 계층은 데이터베이스에서 Mavid Mobrick의 프로필 정보를 요청합니다. 이것은 계속해서 또 다시 일어나고 있습니다. Mavid Mobrick의 프로필 정보는 모든 요청에 ​​따라 변경되지 않으므로 해당 정보는 캐시에 적합합니다.

Redis의 데이터베이스 결과를 키 user : id 아래에 만료 시간 30 초로 캐시합니다. 이제 누군가 Mavid Mobrick의 프로필을 방문하면 Redis를 먼저 확인하고 Redis에서 데이터를 제공합니다. Mavid Mobrick이 사이트에서 가장 인기가 있지만 프로파일을 요청하면 데이터베이스에 거의 부하가 걸리지 않습니다.

대부분의 캐시 서비스의 또 다른 장점은 데이터베이스보다 쉽게 ​​확장 할 수 있다는 것입니다. Redis에는 로드 밸런서1와 유사한 방식으로 Redis 클러스터 모드가 내장되어있어 Redis 캐시를 여러 머신 (수천 대의 경우 수천 대)에 분산시킬 수 있습니다.

거의 모든 고도로 확장 된 응용 프로그램은 캐싱을 충분히 활용하므로 빠른 API를 만드는 데 절대적으로 필요한 부분입니다. 더 나은 쿼리와 성능이 좋은 코드는 모두 방정식의 일부이지만 캐시가 없으면 수백만 명의 사용자로 확장하기에 충분하지 않습니다.

읽기 전용 복제본

우리 데이터베이스가 꽤 많은 타격을 받기 시작한 지금 우리가 할 수있는 또 다른 일은 데이터베이스 관리 시스템을 사용하여 읽기 복제본을 추가하는 것입니다. 위의 관리 서비스를 사용하면 한 번의 클릭으로 수행 할 수 있습니다. 읽기 전용 복제본은 마스터 DB를 최신 상태로 유지하며 SELECT 문에 사용될 수 있습니다.

시스템은 다음과 같습니다:

100,000명의 사용자

그 이후에

앱이 계속 확장됨에 따라 독립적으로 확장 할 수있는 서비스를 분할하는 데 집중하려고 합니다. 예를 들어, 웹 소켓을 사용하기 시작하면 웹 소켓 처리 코드를 꺼내는 것이 좋습니다. 우리는 HTTP 로드 수에 관계없이 개방 또는 폐쇄 된 웹 소켓 연결 수에 따라 확장 및 축소 할 수 있는 자체 로드밸런서 뒤에 새로운 안정성을 제공 할 수 있습니다.

또한 데이터 계층의 한계에 부딪 치려고 계속 노력할 것입니다. 데이터베이스를 분할하고 샤딩하기 시작하려고 할 때입니다. 이 두 가지 모두 더 많은 오버 헤드가 필요하지만 효과적으로 데이터 계층을 무한대로 확장 할 수 있습니다.

New Relic 또는 Datadog 와 같은 서비스를 사용하여 모니터링을 설치했는지 확인하려고 합니다. 이를 통해 요청이 느리고 개선이 필요한 부분을 이해할 수 있습니다. 규모를 확장 할 때 종종 이전 섹션의 일부 아이디어를 활용하여 병목 현상을 찾아 수정하는 데 집중하려고 합니다.

이 시점에서 우리는 팀에 도움을 줄 다른 사람들도 있습니다!

참고 문헌 :

이 게시물은 High Scalability에 대해 제가 가장 좋아하는 게시물 중 하나에서 영감을 받았습니다. 나는 위의 게시물에 초기 단계의 내용을 조금 더 풍부하게 하고 클라우드에 구애받지 않기를 원했습니다. 이런 것들에 관심이 있다면 꼭 확인하십시오.

각주


  1. 여러 인스턴스에 로드를 분산시킬 수 있다는 점에서 비슷하지만 Redis Cluster의 기본 구현은 로드 밸런서와 크게 다릅니다.

  2. easy to reason about 프로그래밍의 관용적 표현


pilhwan kim
Written by@pilhwan kim
광운대학교 컴퓨터소프트웨어학과 03학번 졸업. 현재 하나님 나라의 기름부음 받은 소프트웨어 개발자로 살고자 함.

GitHubFacebook