대용량 트래픽 처리를 위한 대규모 시스템 설계하기
규모의 성장에 따라 시스템 확장하며 대용량 트래픽 처리하기
  • Backend

시작하며

우선 이 글을 정리하는 이유는 지금까지 공부하면서 혹은 일하면서 경험해 본 인프라 구조를 다시 한번 정리하기 위해 작성한다.
시스템의 아키텍쳐는 회사의 선택에 따라 얼마든지 달라질 수 있으므로 이 내용으로 100% 설계되어있다 라고 생각하면 안된다.

글의 순서는 시스템을 오픈하고 사용자가 늘어감에 따라 인프라 구조가 어떻게 변해가는지 따라가본다.

대용량 트래픽 처리를 위한 대용량 시스템 설계

시스템의 오픈

간단한 서비스를 위해 WAS 서버를 세팅하고 서비스를 오픈했다.
아주 간단한 시스템 구조는 아래와 같다.

image

사용자의 증가

image

사용자가 증가하면서 WAS가 점점 처리가 느려지는 현상이 발생했다. 그래서 WAS 서버를 scale-up 했다.

수직적 확장의 한계

트래픽이 계속해서 늘어나다 보면 수직적 확장은 한계가 온다.
장비의 성능만을 업그레이드해서는 업그레이드의 한계가있고, 무엇보다 비용이 아주 비싸다.
그래서 어느 정도 scale-up 했다면 수평적 확장을 위한 scale-out 될 수 있도록 구성한다.

그 구조는 다음과 같다.

image

이 전 단계와의 차이는 WAS 가 하나 더 생겼고 두 개의 WAS는 한 그룹으로 묶여있다.
그리고 그 앞에 LoadBalancer가 추가되어 여러 대의 WAS에 골고루 트래픽을 분산 처리해 준다.

Database 이중화

image

이제 WAS가 여러 개가 되면서 성능이 많이 향상되었다. 이제부터는 시스템에서 부하를 가장 많이 받는 곳은 I/O 가 발생하는 Database 지점이 된다.
Database은 확장 방법 중 하나인 Replication을 활용하여 Main 과 Replica로 나누고, CUD는 Main db로, R 은 Sub로 한다. (그 외 확장 방법에 대해서는 아래에서 한 번 더 설명한다.)
(master, slave 대신에 main, replica로 용어를 사용한다.)

이렇게 서비스하면 만약 두 대중 한 대의 DB 인스턴스가 장애가 났을 때 나머지 한 대로도 서비스가 가능하다.
만약 main db가 장애가 난다면 Replica 중에 하나를 Main DB로 전환된다.

주의할 점은 Main 1대에 Replica 여러 대를 설정할 수 있는데, main으로 들어온 CUD에 대한 데이터가 Replica로 복사될 때 Replica의 인스턴스 개수가 많아지면 그만큼 복사에 지연이 생긴다.

static 파일의 분리

서비스를 운영하다 보면 정적 파일을 제공하는 경우가 많다. 프론트엔드 코드 파일이나 이미지 파일 등 정적파일이 이에 해당한다.
정적 파일은 반드시 WAS에서 제공해야 하는 것은 아니기 때문에, 서버 트래픽의 부담을 줄어주기 위해 정적 파일은 AWS의 cloudfront등을 이용해 CDN을 통해 서비스할 수 있다.

image

그러면 그림과 같이 구성할 수 있는데, 예를 들어 AWS의 CloudFront 등의 서비스에 프론트엔드 코드를 올리고 CDN을 통해 제공하면
사용자 입장에서는 더 빠르게 서비스를 이용할 수 있고, 운영 측면에서는 백엔드 코드와 프론트엔드 코드를 분리할 수 있는 장점이 있다.

이렇게 구조를 변경할 때 해야 할 작업으로는 기존에 WAS에서 제공할 때 서버가 프론트엔드 코드를 작성해서 render 해주었다면 (SSR) CDN으로 분리되고 나면 프론트엔드 코드에서 서버 API를 호출하도록 (CSR) 변경해야 한다.

Memory DB

image

여전히 이 시스템 구조에서 가장 많은 부하를 받는 곳은 데이터베이스일 것이다.

이를 해결하기 위해 WAS 와 Database 사이에 Memory DB를 둘 수 있다. 예를 들면 Redis가 있다.
그러면 대략 그림이 위와 같이 그려진다. 메모리를 사용해 Database 데이터를 캐시 하게 되면 엄청 빠른 속도로 조회되는 것을 경험할 수 있다.
(물론 그만큼 비용은 증가한다)

주의해야 할 점은 데이터베이스의 모든 데이터를 cache 하면 효율이 떨어진다.
최대한 hits 가 많이 생길 수 있도록 자주 조회되는 데이터를 cache 하는 것이 효율적이다.

MSA 로의 전환

image

이쯤 왔는데도 여전히 트래픽 처리가다면 각 요청에 대해 서비스하는 WAS 자체를 분리할 부담스럽 수 있다. 개선할 수 있는 아키텍처로는 MSA 가 있다.
도메인별로 혹은 하위 도메인으로 서버를 분리하고 각 서버는 본인의 DB만을 사용한다.

예를 들어 한 서비스에 10개의 도메인이 있다고 가정해 본다면, MSA로 전환하고 나면 한 개의 서버가 처리하는 양은 기존의 1/10 정도가 될 수 있다.
(도메인의 중요도에 따라 꼭 1/10은 아니겠지만)

단순히 MSA로의 전환이라고 작성해두었지만, 고려해야 할 점은 많다. 더 자세한 내용은 관련 글을 참고 바란다. 바로 가기

CQRS

image

MSA로 시스템 아키텍처를 변경했다면 필연적으로 CQRS를 고민하게 된다.

CQRS는 간단히 말하면 프론트에서 어떤 페이지에 데이터를 보여주어야 할 때, 서버 입장에서는 이 데이터들은 많은 여러 도메인들에게 데이터를 취합해서 제공해야 한다.
그런데 MSA에서 서로의 서버를 호출하는 방법은 여러 가지가 있겠지만 많이 사용하는 HTTP 호출을 한다고 했을 때, 4~5번의 호출이 필요하다. (4~5개 도메인에 데이터를 요청한다고 했을 때)

그러면 이 데이터를 취합하는 데만도 시간이 많이 필요하다. 그래서 특정 페이지에 보여주는 데이터를 미리 취합해서 가지고 있다가 프론트의 요청이 있을 때 즉시 제공할 수 있는 상태를 만들어둔다.

Sharding

image

(샤딩 부분은 실무에서 경험해 본 적은 없는 구조라 공부한 내용으로 작성한다)
이제 확장할 수 있는 대부분은 확장했다. 그런데 시스템이 더욱 커지면서 데이터베이스의 부하가 계속된다면 데이터베이스를 더 확장할 수 있다.
(데이터베이스를 확장하는 방식에는 크게 위에서 처리했던 레플리케이션 그리고 파티셔닝, 샤딩 3가지가 있다.)

도메인을 더 작게 나누어 데이터를 더 분산시키는 방법도 있겠지만, 데이터베이스에 데이터를 저장할 때 저장하는 방법에 대한 구조를 변경하는 방법인 샤딩이 있다.

샤딩과 비슷해서 헷갈리는 개념으로는 파티셔닝이 있다.
쉽게 이해하면 파티셔닝은 하나의 테이블 데이터를 여러 개의 테이블로 나누어 저장한다고 생각하면 된다. MySQL 을 예로 들면 내부 데이터는 여러 개의 테이블에 나눠서 저장되지만, 운영 측면에서는 하나의 테이블에서 쿼리를 하는 것이기 때문에 개발에 차이점은 생기지 않는다.

그와 다르게 샤딩은 큰 테이블의 데이터들을 여러 작은 테이블들로 나눠 분리하는 것이 아니라 디비 인스턴스 자체를 분리하는 것이다.
그러면 같은 스키마의 다른 인스턴스로 데이터가 분리된다고 이해할 수 있다.

장점으로는 샤딩 개수만큼 데이터가 나눠서 저장되니 부하가 줄어든다는 점이 있지만,
단점으로는 데이터가 분산되어 저장되어 있다 보니 join에 영향을 받고 샤딩 방법에 따라 특정 디비 인스턴스에 트래픽이 더 많이 몰리는 현상도 발생할 수 있다.

Sharding 종류

image

  • Hash Sharding: 간단하게 auto increase 되는 pk값을 인스턴스 개수만큼 나누기해서 나머지 값을 이용해 저장한다.
    장점으로는 알고리즘이 매우 간단하다는 점이 있는 반면 단점은 인스턴스가 늘어나거나 줄어들었을 때 전체 데이터의 재정리가 필요하다.

image

  • Range Sharding: auto increase 되는 pk값을 범위에 따라 나눈다고 생각하면 된다.
    장점으로는 DB 인스턴스가 늘어나도 큰 리소스가 들지 않는다. 데이터가 급격히 늘어나는 성격에 사용하면 좋다.
    반면 이렇게 DB를 분산시켜놨음에도 특정 디비에 트래픽이 몰리는 현상이 발생할 수 있다. 예를 들어 최근 게시글을 보여준다면 Range 중에 가장 마지막 디비에 접근이 많을 것이다.