1. 3-Tier Architecture 설계

작은 이커머스 서비스를 만든다고 가정하고 시작했다. 기능은 단순했다. 로그인, 상품 조회, 상품 주문, 회원 정보 저장, 주문 정보 저장. 이 정도면 그냥 서버 한 대에 다 넣어도 되지 않을까 싶었다.

시작: 서버 한 대에 다 몰아넣기

가장 쉬운 방법부터 시도해봤다. 서버 한 대에 웹 서버, 애플리케이션, DB를 전부 설치하는 것. 개발 초기에는 이게 제일 편하다. 배포도 한번만 하면 되고 서버 관리도 하나만 신경 쓰면 된다.

image.png

그런데 이 구성으로 사용자가 늘어난다고 가정해보면, 문제는 거기서부터 시작된다. 서버가 눈에 띄게 느려지는데, 정작 원인을 못 찾겠는 상황이 되어버린다. 웹 요청 처리가 느린 건지, 주문 로직이 느린 건지, DB 조회가 느린 건지 구분할 방법이 없다. CPU와 메모리 지표를 봐도 세 가지가 뒤섞여서 하나로 튀는 것처럼 보였다. 더 답답했던 건, 뭐 하나 고치겠다고 재시작하면 나머지 두 개까지 같이 멈춰버린다는 점이었다.

→ 여기서 그럼 그냥 더 좋은 서버로 바꾸면 되지 않나 하는 생각이 들었다. 그런데 조금만 더 파보니 이건 임시방편일 뿐이었다. 진짜 필요한 건 역할별로 서버를 나누는 것이었다. 웹 처리, 비즈니스 로직 처리, 데이터 저장은 애초에 리소스를 쓰는 패턴이 다르다. 웹은 I/O 위주고, 애플리케이션은 CPU 연산 위주고, DB는 디스크와 메모리 위주다. 이걸 한 서버에 몰아넣으니 어디가 병목인지 구분이 안됐던 거고 장애가 나면 셋 다 같이 죽는 구조였던 거다. 이게 흔히 말하는 3-Tier Architecture(Presentation - Application - Data)의 시작점이라는 걸 여기서 이해했다.

Web / App / DB를 나눴더니 생긴 새로운 문제

서버를 세 대로 나눴다. Web, Application, DB. 이제 원인 분리는 됐다. 그런데 사용자가 100명에서 10,000명으로 늘어나니 이번엔 Application 서버만 CPU 100%를 찍었다. 처음엔 단순하게 생각해서 더 큰 서버로 교체했다. 잠깐은 버텼는데 결국 또 한계가 왔고, 무엇보다 교체할 때마다 서비스를 내려야 한다는 게 계속 걸렸다.

여기서 Scale-up(수직 확장)과 Scale-out(수평 확장)의 차이를 찾아봤다. Scale-up은 서버 한 대의 사양을 올리는 거라 간단하지만 물리적 한계가 있고 교체할 때 다운타임이 생긴다. 반면 Scale-out은 서버를 여러 대로 늘리는 방식이라 이론상 한계가 거의 없고 한 대가 죽어도 나머지가 버텨준다. 다만 여러 대를 하나처럼 다루기 위한 장치가 추가로 필요하다는 게 숙제로 남았다. (= 로드밸런서)

App 서버를 두 대로 늘렸는데 트래픽이 안 감

Application 서버를 두 대로 늘렸다. 그런데 사용자는 여전히 서버 한 대의 주소로만 접속하고 있었다. 두 번째 서버는 사실상 놀고 있는 셈이었다. 당연한 결과였는데 서버를 늘리는 것과 트래픽을 나누는 건 완전히 다른 문제라는 것을 알게되었다.

image.png

→ 여기서 필요한 게 로드밸런서였다.

로드밸런서를 붙였더니 로그인이 자꾸 풀림

앞단에 분산 장치를 두고 나니 다음 문제가 등장했다. 로그인한 사용자가 페이지를 넘길 때마다 로그아웃되는 현상이었다. 원인을 찾아보니 단순했다. 요청이 App 1로 갔다가 다음 요청이 App 2로 가면, App 2 입장에서는 이 사용자가 로그인했다는 사실 자체를 모른다. 세션 정보가 각 서버 메모리 안에만 있었기 때문이다.

→ 이건 세션을 서버 밖으로 빼야 한다는 걸 알려주는 문제였다. 서버가 이 사용자를 기억하는 상태를 자기 안에 들고 있으면 서버를 늘릴수록 이런 문제가 계속 생긴다. 그래서 Redis 같은 외부 세션 스토어에 세션을 저장하고 어떤 App 서버가 요청을 받든 그 외부 저장소를 조회해서 로그인 상태를 확인하는 구조로 가야 한다는 걸 정리했다. 이렇게 서버 자체가 상태를 갖지 않게 만드는 걸 Stateless 설계라고 부른다는 것을 알게되었다.