우선 이 글을 정리하는 이유는 지금까지 공부하면서 혹은 일하면서 경험해 본 인프라 구조를 다시 한번 정리하기 위해 작성한다.
시스템의 아키텍쳐는 회사의 선택에 따라 얼마든지 달라질 수 있으므로 이 내용으로 100% 설계되어있다 라고 생각하면 안된다.
글의 순서는 시스템을 오픈하고 사용자가 늘어감에 따라 인프라 구조가 어떻게 변해가는지 따라가본다.
간단한 서비스를 위해 WAS 서버를 세팅하고 서비스를 오픈했다.
아주 간단한 시스템 구조는 아래와 같다.
사용자가 증가하면서 WAS
가 점점 처리가 느려지는 현상이 발생했다. 그래서 WAS
서버를 scale-up
했다.
트래픽이 계속해서 늘어나다 보면 수직적 확장은 한계가 온다.
장비의 성능만을 업그레이드해서는 업그레이드의 한계가있고, 무엇보다 비용이 아주 비싸다.
그래서 어느 정도 scale-up
했다면 수평적 확장을 위한 scale-out
될 수 있도록 구성한다.
그 구조는 다음과 같다.
이 전 단계와의 차이는 WAS 가 하나 더 생겼고 두 개의 WAS는 한 그룹으로 묶여있다.
그리고 그 앞에 LoadBalancer
가 추가되어 여러 대의 WAS에 골고루 트래픽을 분산 처리해 준다.
이제 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의 인스턴스 개수가 많아지면 그만큼 복사에 지연이 생긴다.
서비스를 운영하다 보면 정적 파일을 제공하는 경우가 많다. 프론트엔드 코드 파일이나 이미지 파일 등 정적파일이 이에 해당한다.
정적 파일은 반드시 WAS에서 제공해야 하는 것은 아니기 때문에, 서버 트래픽의 부담을 줄어주기 위해 정적 파일은 AWS의 cloudfront등을 이용해 CDN
을 통해 서비스할 수 있다.
그러면 그림과 같이 구성할 수 있는데, 예를 들어 AWS의 CloudFront 등의 서비스에 프론트엔드 코드를 올리고 CDN을 통해 제공하면
사용자 입장에서는 더 빠르게 서비스를 이용할 수 있고, 운영 측면에서는 백엔드 코드와 프론트엔드 코드를 분리할 수 있는 장점이 있다.
이렇게 구조를 변경할 때 해야 할 작업으로는 기존에 WAS에서 제공할 때 서버가 프론트엔드 코드를 작성해서 render 해주었다면 (SSR) CDN으로 분리되고 나면 프론트엔드 코드에서 서버 API를 호출하도록 (CSR) 변경해야 한다.
여전히 이 시스템 구조에서 가장 많은 부하를 받는 곳은 데이터베이스일 것이다.
이를 해결하기 위해 WAS 와 Database 사이에 Memory DB를 둘 수 있다. 예를 들면 Redis가 있다.
그러면 대략 그림이 위와 같이 그려진다. 메모리를 사용해 Database 데이터를 캐시 하게 되면 엄청 빠른 속도로 조회되는 것을 경험할 수 있다.
(물론 그만큼 비용은 증가한다)
주의해야 할 점은 데이터베이스의 모든 데이터를 cache 하면 효율이 떨어진다.
최대한 hits 가 많이 생길 수 있도록 자주 조회되는 데이터를 cache 하는 것이 효율적이다.
이쯤 왔는데도 여전히 트래픽 처리가다면 각 요청에 대해 서비스하는 WAS 자체를 분리할 부담스럽 수 있다. 개선할 수 있는 아키텍처로는 MSA 가 있다.
도메인별로 혹은 하위 도메인으로 서버를 분리하고 각 서버는 본인의 DB만을 사용한다.
예를 들어 한 서비스에 10개의 도메인이 있다고 가정해 본다면, MSA로 전환하고 나면 한 개의 서버가 처리하는 양은 기존의 1/10 정도가 될 수 있다.
(도메인의 중요도에 따라 꼭 1/10은 아니겠지만)
단순히 MSA로의 전환이라고 작성해두었지만, 고려해야 할 점은 많다. 더 자세한 내용은 관련 글을 참고 바란다. 바로 가기
MSA로 시스템 아키텍처를 변경했다면 필연적으로 CQRS
를 고민하게 된다.
CQRS
는 간단히 말하면 프론트에서 어떤 페이지에 데이터를 보여주어야 할 때, 서버 입장에서는 이 데이터들은 많은 여러 도메인들에게 데이터를 취합해서 제공해야 한다.
그런데 MSA에서 서로의 서버를 호출하는 방법은 여러 가지가 있겠지만 많이 사용하는 HTTP 호출을 한다고 했을 때, 4~5번의 호출이 필요하다. (4~5개 도메인에 데이터를 요청한다고 했을 때)
그러면 이 데이터를 취합하는 데만도 시간이 많이 필요하다. 그래서 특정 페이지에 보여주는 데이터를 미리 취합해서 가지고 있다가 프론트의 요청이 있을 때 즉시 제공할 수 있는 상태를 만들어둔다.
(샤딩 부분은 실무에서 경험해 본 적은 없는 구조라 공부한 내용으로 작성한다)
이제 확장할 수 있는 대부분은 확장했다. 그런데 시스템이 더욱 커지면서 데이터베이스의 부하가 계속된다면 데이터베이스를 더 확장할 수 있다.
(데이터베이스를 확장하는 방식에는 크게 위에서 처리했던 레플리케이션 그리고 파티셔닝, 샤딩 3가지가 있다.)
도메인을 더 작게 나누어 데이터를 더 분산시키는 방법도 있겠지만, 데이터베이스에 데이터를 저장할 때 저장하는 방법에 대한 구조를 변경하는 방법인 샤딩이 있다.
샤딩과 비슷해서 헷갈리는 개념으로는 파티셔닝이 있다.
쉽게 이해하면 파티셔닝은 하나의 테이블 데이터를 여러 개의 테이블로 나누어 저장한다고 생각하면 된다. MySQL 을 예로 들면 내부 데이터는 여러 개의 테이블에 나눠서 저장되지만, 운영 측면에서는 하나의 테이블에서 쿼리를 하는 것이기 때문에 개발에 차이점은 생기지 않는다.
그와 다르게 샤딩은 큰 테이블의 데이터들을 여러 작은 테이블들로 나눠 분리하는 것이 아니라 디비 인스턴스 자체를 분리하는 것이다.
그러면 같은 스키마의 다른 인스턴스로 데이터가 분리된다고 이해할 수 있다.
장점으로는 샤딩 개수만큼 데이터가 나눠서 저장되니 부하가 줄어든다는 점이 있지만,
단점으로는 데이터가 분산되어 저장되어 있다 보니 join에 영향을 받고 샤딩 방법에 따라 특정 디비 인스턴스에 트래픽이 더 많이 몰리는 현상도 발생할 수 있다.