서버에 트래픽이 많이진다는 상상을 해봤다...
지금 내가 개발 중인 서버는 모놀리틱 기반의 서버이다. 결제에 관련된 기능들과 앱 서비스를 제공하기 위한 수 많은 기능들이 하나의 모놀리틱 서버에서 운영 되고 있다. 만약 사용자가 많아진다면 지금 현 서비스의 구조를 어떻게 가져갈지 고민을 해 보았다.
먼저, 아래에 적어놓는 점들은 시스템 설계를 할 때 고려해야 할 점이라고 한다. 아래와 같은 부분을 시스템 설계 시 참고해야 나중에 다시 설계를 하는 등의 일이 발생하지 않을 것이다.
- 가용성 (오랜기간동안 지속적으로 이용 가능)
- 성능
- 확장성
- 안정성
- 관리성
- 비용
예를 들어 완전 안전한 시스템을 설계했더니 비용이 너무 많이 나왔다. 경영진에서 비용이 너무 많이 나온다며 비용을 줄여달라고 한다면 그 시스템은 안전할지라도 걷어내야 할 것이다. 그래서 비용이 낮게 나오도록 했더니 성능이 너무 나오지 않는다. 그것은 그거 나름대로 잘못 설계를 한 것이다. 그래서 이러한 부분들을 잘 생각해서 포기할건 포기하더라도 어느 정도 챙길 수 있도록 상황에 맞게 설계를 해야 한다.
다시 돌아와서 설계를 하려고 한다고 해보도록 하겠다. 위에서 서버에 트래픽이 많아졌다고 했으며 상황에 맞게 잘 설계를 해야 한다고 말했다. 가령 사용자가 많아져서 전체적으로 트래픽이 많아진건지, 초대박 이벤트를 자주 해서 특정 기능에 트래픽이 많이 몰리는 상황이 자주 발생한다던지를 미리 파악해놓고 어느 부분을 개선해야 사용자에게 문제 없는 서비스를 제공할 수 있는지 알고 설계를 해야 시스템 설계를 잘했다라고 할 수 있을 듯 싶다.
우선 나는 사용자가 매우 많아져서 서버 트래픽이 기하급수적으로 계속 증가하고 있다는 전제를 기반으로 하여 생각을 해보겠다.
첫 번째로 스케일 아웃, 스케일 업을 고려해볼 수 있다. 요즘은 aws 같은 클라우드 서비스를 이용하기 때문에 aws elastic beanstalk 를 이용해 HA 로 구성하여 오토 스케일링 기능을 이용할 수 있다. 이런 기능을 이용하면 좀 더 개발자가 편하게 비즈니스 로직만 집중할 수 있을 것이다. 오토 스케일링을 이용했는데 서버 인스턴스는 버티는데 사용자가 계속 많아져서 DB 에 데이터가 많아졌고 결국 write, select 속도가 느려졌다. 그러면 이제 두 번째 방법을 생각할 수 있다(여기서는 master, slave 를 설계를 이미 한 경우라고 하겠다).
두 번째는 DB 샤딩 또는 파티셔닝을 통해 해결할 수 있을 것이다. 찾아보니 샤딩, 파티셔닝은 어쨌든 같은 의미인 것 같다는 생각이 든다. 여기서 한 가지 다른 점이 있다면 인스턴스에 대한 부분이다. 나는 샤딩은 여러 대의 DB 서버를 가지고 분할하는 것이고 파티셔닝은 하나의 인스턴스에서 분할한다는 식으로 이해를 했다. 샤딩이 더 넓은 의미의 파티셔닝이랄까. 파티셔닝을 통해 수평분할을 한다면 하나의 인스턴스에서 파티션을 두겠지만 샤딩에서의 수평분할은 데이터를 인스턴스 별로 분리한다는 것이다.
어찌되었든 위에서 이야기 했듯이 시스템 설계 시 고려해야 할 점들과 각각의 장단점을 잘 생각해서 샤딩을 할 것인지 파티셔닝을 할 것인지 정해야 할 것이다. 만약 하나의 인스턴스에서 사용자 테이블에 대해 데이터가 너무 많아져 인덱스 파일도 커지고 read, write 에 대해서 오래걸려 샤딩을 통해 수평분할을 한다면 사용자 DB 인스턴스를 여러 대 두고 각각의 인스턴스에 데이터를 분할하여 가지고 있을 것이다. 이 때 데이터를 분리하는 기준에 여러가지가 존재한다. 여기선 모듈러와 레인지에 대해서만 얘기를 하겠다. 모듈러는 PK 를 특정 값으로 나누는 등 수식을 통해 나온 값을 가지고 어느 DB 인스턴스에 넣을지 결정하는 식이고, 레인지는 말 그대로 PK 범위를 통해 어느 DB 인스턴스에 넣을지 결정한다. 모듈러는 DB 인스턴스를 늘린다면 다시 데이터를 재정렬해서 넣어야한다는 단점이 있고 레인지는 특정 DB 인스턴스 트래픽이 쏠릴 수 있다는 단점이 있다. 예를 들어 레인지 방식으로 데이터 분할하였는데 특정 시점에 가입한 유저들만 서비스를 많이 이용한다면 당연히 특정 범위에 해당하는 DB 인스턴스에만 트래픽이 몰릴 것이다. 이러한 부분들을 미리 잘 파악해서 트레이드 오프를 결정해야 한다.
세 번째는 특정 기능에 트래픽이 몰렸을 경우를 전제로 해서 예를 들도록 하겠다. 예를 들어 결제 기능에 트래픽이 많이 몰린경우라고 하겠다. 서버의 자원은 한정적이기에 최대 처리할 수 있는 요청 수가 존재할 것이다. 초당 100건의 결제를 처리할 수 있는 서버에서 갑자기 10,000건의 결제를 처리해야 한다면 그 서버는 자원 부족으로 특정 요청들에 대해서 제대로 처리를 할 수가 없을 것이다. 또한, 서버가 죽었을 때도 생각해야 한다. 초당 100건의 결제를 처리하는 서버에서 결제 요청을 처리 도중 서버가 죽었다. 사용자는 계속 결제 요청을 할텐데 이 때 결제가 되지 않고 실패가 된다면 사용자는 안 좋은 경험을 얻게 될 것이다. 빠르게 다른 서버로 요청이 가도록 하더라도 초당 100건이 들어오는 서버에서는 이미 수백 건의 결제가 실패가 일어날 것이다. 그래서 적용하는 것이 카프카라고 생각한다(MQ 와는 아키텍처가 좀 다른데 다음 기회에 얘기를 해보겠다). 간단하게 얘기하면 카프카는 다른 MQ 와 다르게 publisher/subscriber 구조다. subscriber 에서 broker 에 몇 개의 메세지를 가져와 처리할지 결정한다. 만약 서버가 죽더라도 이미 broker 에서는 메세지를 가지고 있을 것이고, 부하 분산을 통해 다른 서버에서 알아서 처리될 것이다. 만약 처리되고 있는 중 서버가 죽더라도 카프카는 메세지를 디스크에 처리하고 메세지에 대한 수명이 있다고 알고 있다. 그래서 실패 시 해당 메세지에 대해서 다시 처리한다고 한다. 이런 식으로 카프카를 이용해서 안정적으로 할 수 있을 듯 하다.
다음 글은 MSA 로 구축된 상황에서 트래픽이 많아졌을 때 해결하는 방향으로 그려보도록 하겠다. 아직 서비스메쉬 같은 개념을 잘 몰라서 이 부분을 좀 공부하고 해야 할 듯 싶다.
꼭 명심해야 할 부분은 오버 엔지니어링은 때로는 차라리 아무것도 없는 것보다 못하다라는 것을 꼭 명심하자.