Alex Pareto의 Scaling to 100k Users를 번역한 글이다.
This article is a translation of Scaling to 100k Users by Alex Pareto
유저 수에 따른 서버 확장.
“Graminsta를 만든다고 가정하자.”
1 User: 1개의 머신.
대부분의 어플리케이션은 3가지 구성요소를 가지고 있다: API, 데이터베이스, 클라이언트(보통 앱이나 웹사이트). 데이터베이스는 영구적인 데이터를 저장한다. API는 요청을 받고 데이터를 전달해준다. 클라이언트는 API에서 받은 데이터를 보여준다.
우리가 처음 어플리케이션을 만들때, 3가지 구성요소를 하나의 머신에서 돌리는 것은 괜찮다.
그러나 우리가 만든 Graninsta가 1명이상의 유저가 사용하길 원한다면 데이터베이스를 분리시키는건 너무나 당연한 것이다.
10 Users: 데이터베이스를 분리한다.
데이터베이스를 아마존 RDS와 같은 관리형 서비스로 바꾸는 것은 오랫동안 우리에게 도움이 될수 있다. EC2 머신 하나를 DB용으로 만들어 직접 데이터베이스를 관리하는 것보다는 조금더 비쌀수 있다. 그러나 이런 관리형 서비스를 이용하면 백업, 레플리케이션등 여러가지 추가기능들을 몇번의 클릭만으로 구현할수 있기 때문에 무조건 써야한다.
Graminsta의 구성은 아래로 바뀌었다.
100 Users: 클라이언트를 분리한다.
“운이 좋게도 Graminsta의 초기 유저들의 반응이 좋다. 트레픽이 조금씩 올라가고 있는 상황이다.”
이제는 클라이언트를 분리할 시간이다. 하나 체크하고 갈 것이 있다면, 요소들을 분리하는 것은 확장가능한 어플리케이션을 만드는 것의 키포인트다. 시스템의 하나의 요소에서 트레픽이 늘어난다면 그 요소를 따로 떼어내어 구성해야한다. 하나의 요소요소가 받는 트레픽의 패턴이 다르므로 트레픽 패턴에 맞춰 확장해야만 한다. 이것이 API에서 클라이언트를 때어낸 이유다.
많은 유저들이 Graminsta를 Phone에서도 이용하고 싶다 피드백을 주었다. 이 피드백에 따라서 모바일 클라이언트를 추가하면 아래 그림처럼 분리되어 구성 된다.
1,000 Users: 로드 밸런서를 추가한다.
Graminsta의 상황이 나아지고 있다. 유저들이 많은 사진을 올리고 있고, 새 회원들이 많이 생겨난다. 우리의 외로운 API 인스턴스는 모든 트레픽을 받느라 힘들어하고 있다. 더많은 컴퓨터 파워가 필요할 때다.
로드밸런서는 아주 강력하다. 핵심 아이디어는 API 앞에로드 밸런서를 배치하고 트래픽을 해당 서비스의 인스턴스로 전달하는 것이다. (수평확장)
웹 클라이언트와 API앞에 각각 로드밸런서를 배치할 것이다. 이말은 클라이언트와 API모두 같은 코드를 실행하는 여러 인스터스를 가질수 있다는 말이다. 로드밸런서는 트래픽이 가장 적게 들어오는 인스턴스로 요청을 전달해주어 한군데에 트레픽이 몰리지 않게 해준다.
이로써 Graminsta는 중복성(redundancy)를 얻게 되었다. 하나의 API혹은 클라이언트가 다운되더라도 다른 인스턴스들이 살아있으니 유저의 요청을 계속 받을수 있게 된다.
로드밸런서는 자동 확장(Auto Scaling)이 가능하다. 슈퍼볼 기간에 인스턴스 갯수를 늘려서 많은 트레픽을 대비할수 있게 된다. 그리고 유저들이 자는 시간에는 인스턴스를 줄여 비용을 줄일수 있게 된다.
로드 밸런서를 사용하면 API 계층을 거의 무한대로 확장 할 수 있으며 더 많은 요청이 수신되면 인스턴스를 계속 추가 할수 있게 된다.
Side note: 지금까지의 인프라가 AWS Beanstalk, Heroku와 같은 보통 PasS 회사들이 제공해주는 서비스들이다. Heroku는 데이터베이스를 개별의 호스트로 두고 오토스케일링을 로드밸런서로 관리한다. 또한 클라이언트를 API와 분리하여 관리한다. 이러한 형태로 필요한 기능을 바로바로 제공해주는데. 이것이 Heroku가 초기 스타트업이 이용하기 아주 좋은 이유다.
10,000 Users: CDN
“처음부터 차근차근 Graminsta를 확장해왔지만 Graminsta가 빠르게 성장하고 있습니다. 이미지를 업로드하고 유저에게 제공해주는데에 너무나 큰 부하가 생겨나기 시작했습니다.”
이제 우리는 클라우드 스토리지 서비스를 이용하여 정적 콘텐츠를 제공해야합니다. 일반적으로 API는 이미지 제공과 이미지 업로드에 관여하면 안된다.
클라우드 스토리지 서비스에서 얻을 수있는 또 다른 것은 CDN이다. CDN은 전 세계 여러 데이터 센터에서 이미지를 자동으로 캐시한다.
우리의 핵심 데이터센터가 오하이오이기때문에 한국의 어떤 유저가 이미지를 요청한다면, 한국에 있는 CDN에서 데이터를 카피하여 가지고 있게 된다. 다음부터는 캐시된 데이터를 CDN으로부터 가져오기 때문에 한국 사용자는 요청에 대한 응답이 빨라진다. 용량이 큰 파일들을 제공할때 아주 중요한 포인트이다.
100,000 Users: 데이터 계층을 확장한다.
“CDN이 아주 많은 도움이 되었고 Graminsta는 아주 호황이다.”
유튜브 유명인사 Mavid Mobrick이 회원가입하였고 사진들을 많이 올리고 있다. 유저가 늘었음에도 API의 CPU와 메모리 사용률은 낮게 유지되고 있다. 로드밸런서가 알아서 10개의 API인스턴스를 늘려준덕분이다. 그러나 문제가 하나 있는데, 모든 요청에 걸리는 시간이 꽤 길어졌다. 왜이렇게 오래걸릴까?
좀더 찾아보니 문제는 데이터베이스 CPU가 80-90%를 유지하고 있는 것이다.
API를 확장하는 것은 갯수를 늘리는 것으로 쉽게 가능하지만 Database는 그리 간단하지 않다. 이상황에서 어떠한 방식으로 DB의 부하를 줄일수 있는지 관계형 데이터베이스과 함께 알아보자.
Caching
데이터를 최대한 활용하는 방법중에 하나는 **Cache 계층**
을 이용하는 것이다. 캐시를 구현하는 가장 일반적인 방법은 인메모리(in-memory) 저장소를 이용하는 것이다. 가장 유명한 것은 Redis
와 Memcached
가 있다. 클라우드 형태로도 비슷한 일을 할수 있는 것들이 있다. (AWS의 Elasticach, Google의 memorystore)
같은 데이터를 반환하는 요청이 자주 발생할때 Cache를 이용하게 된다면, 그 요청들을 단 한번의 데이터베이스 접근으로 모두 처리할 수 있게 된다. Graminsta에 Mavid Mobrick의 프로필 페이지 요청이 많이 들어오기 때문에 Redis에서 그 프로필 데이터를 저장하고 있기만하면 데이터베이스에 접근하는 일이 단 한번 뿐이게 된다. API를 거쳐 DB에 접근하는 것과 API의 앞부분에 배치되는 Redis(cache)에만 접근하는 하고 같은 데이터를 반환하는 것은 시간과 비용측면에서 아주아주 큰 성능 향상과 절약을 가져다 준다.
그리고 대부분의 캐시 서비스의 또다른 장점은 데이터베이스보다 훨씬 쉽게 확장가능하다는 것이다. Redis는 로드밸런서와 유사한 방식으로 Redis 캐시를 여러 머신에 배포할수 있는 내장 도구(cluster)를 가지고 있다.
거의 모든 고도록 확장된 어플리케이션은 캐싱을 충분히 활용하며 캐시는 빠른 API를 만드는데에 아주 절대적인 요소이다. 더 나은 쿼리를 짜는 것, 더 나은 코드를 작성하는 것도 중요하다지만, 캐시없이는 충분한 유저확장이 불가능하다.
Read Replicas (읽기용 복제 DB)
데이터베이스가 아주 많은 요청을 받고 있는 지금 상황에서 우리가 할 수 있는 것은 **읽기용 데이터베이스**
를 만드는 것이다. 관리형 데이터베이스 서비스를 이용하면 몇번의 클릭으로 알아서 만들어 준다. 읽기용 디비는 알아서 최신버전으로 관리된다.
Beyond
앱이 계속해서 성장하고 있기 때문에, 개별적으로 확장될수 있는 것들은 최대한 분리해나가야 한다. 예를 들어, 웹소켓을 이용하기 시작한다면, 웹소켓 처리 코드는 따로 떼어놓고 운영해야한다. 웹소켓 코드를 새로운 인스턴스에 넣어놓고 웹소켓의 필요량만큼 확장가능하도록 웹소켓만을 위한 로드밸런서를 구축해야만 한다.
서비스가 더 커지면 데이터베이스 계층의 한계에 부딛힐텐데, 그때는 데이터베이스 **파티셔닝**
과 **샤딩**
을 해야할때다. 둘 다 더 많은 오버 헤드가 필요하지만 효과적으로 데이터 계층을 무한대로 확장 할수 있게 해준다.
모니터링 도구도 필요해질 것이다. New Relic
, Datadog
가 유명하다. 이 모니터링 도구들은 어떤 요청이 느린지, 어떤 곳에서 개선이 필요한지 알려준다. 확장할때는 어디에서 병목현상이 발생하는지 찾아 그것부터 수정해야한다.
References
이 글은 “A beginner’s Guild to Scaling To 11 Million+ Users On Amazon’s AWS”에 영감을 받았다.