Skip to content

쿼리 실행 기능의 성능을 1000% 증가시킨 이야기

Hoons97 edited this page Feb 4, 2025 · 9 revisions

문제상황

유저에게 더 좋은 쿼리 실행 환경을 제공할 수 있도록 with 부하테스트

쿼리 실행기능에 대해 부하테스트를 진행하였고,
동시 처리량을 제한함으로써 해결하였었습니다.

서비스의 핵심 기능인 쿼리 실행 기능에 대해서 부하테스트를 진행하였습니다. 하나의 인스턴스에서 MySQL 서버 하나만을 활용하고 있었기에, 많은 요청을 감당하기 어려웠습니다.

이에 DB 서버에서 감당할 수 있을정도로 쿼리 실행 기능동시처리량을 제한함으로써 사용자 경험을 개선하였습니다.

그러나 이는 기능 개선이라 보기 어려웠고, 쿼리 실행 기능 의 처리량을 어떻게 개선할 수 있을지 고민하였습니다. 단일 DB 서버를 사용하였기에 모든 사용자 쿼리가 하나의 서버에서 처리되고 있었고, DB 서버의 부하가 매우 컸습니다. 따라서 DB 처리량을 확장하는 방법을 중점적으로 개선목표를 잡았습니다.

개선 방법 학습

DB 확장 수단

데이터베이스의 처리량을 증가시킬 방법들을 학습하고, 정리하였습니다.

수직 확장

데이터베이스의 처리량을 증가시킬 방법으로 수직확장이 있습니다. 단순히 현재 사용하고 있는 DB 서버의 리소스를 업그레이드 하는 방법입니다.

  • 더 성능이 좋은 NCloud 인스턴스 활용
  • NCloud 의 Cloud DB Service 활용 (RDS)

기존의 아키텍처에서 데이터베이스 서버를 수직 확장 할 방법은 크게 두가지가 있었습니다.
그러나 서버를 수직 확장하는 것은 단순히 더 큰 비용을 쓰고 더 좋은 성능을 얻는 방법입니다. 또한 기술적으로 큰 도전이라고 할 만한 부분이 많지 않았습니다. 이에 최소한의 비용으로, 보다 많은 기술적 도전을 경험하고자 수직확장 방법을 택하지 않았습니다.

수평 확장

DB 의 수평확장은 여러개의 데이터베이스 서버를 두고 관리하는 방법입니다. 크게 레플리카샤딩 두가지 방법이 있습니다.

  • 레플리카
    여러개의 데이터베이스 인스턴스를 두고 데이터를 복제해서 관리하며 동기화하는 기술입니다. 마스터-슬레이브 구조를 가지며 읽기 작업에 대한 성능을 효과적으로 향상시킬 수 있습니다.

  • 샤딩
    여러개의 데이터베이스 인스턴스를 두고, 각 데이터를 분산저장하는 기술입니다. 데이터를 어떻게 분산해서 저장할 지 전략을 택하고 데이터를 관리해야 합니다.

서비스에서 개선하고자 하는 핵심 기능은 쿼리 실행 기능 입니다. 이는 사용자가 입력한 쿼리를 실행하고 결과를 반환하는 기능입니다. 따라서 사용자가 입력한 쿼리가 읽기 작업이 많을 지, 쓰기 작업이 많을 지 예측하기 어렵습니다.

또한 사용자 별로 독립된 데이터베이스 환경을 제공합니다. 한 사용자의 데이터와 다른 사용자의 데이터는 독립적입니다. 한 사용자의 쿼리는 다른 사용자의 DB 에 영향받지 않습니다. 따라서 각 사용자의 DB 와 데이터를 독립적으로 저장하고 관리해야 합니다.

종합적으로 비교한 결과, 읽기 작업을 중점적으로 개선할 수 있는 레플리카 방법은 적합하지 않다고 판단하였습니다. 각 사용자의 DB 를 독립적으로 관리하고, 사용자의 쿼리를 따로 실행하기 위해선 샤딩이 더 적합하다고 판단하였고, 각 사용자의 DB 분산 저장을 직접 구현하기로 하였습니다.

구현

다음과 같은 목표를 기준으로 계획을 구체화하였습니다.

비용 고려

단순히 고성능의 서버를 도입하는 대신, 최소한의 서버 비용 증가로 성능을 개선해 더 나은 사용자 경험을 제공하고자 했습니다. 기존 구조는 2개의 인스턴스를 활용하고 있었고, 리팩토링 후 같은 성능의 4개 인스턴스를 활용하여 비용 증가를 최소화하였습니다.

아키텍처 개선

기존 아키텍처 개선 아키텍처

Query DB 서버의 인스턴스를 4개로 늘리고 로드 밸런싱을 통해 Query DB 서버의 부하를 효과적으로 분산시키는 구조를 설계하여 쿼리 응답 시간의 병목 현상을 해결하려 하였습니다. 또한 쿠버네티스를 도입하여 어플리케이션 서버와 DB 서버, 각 인스턴스를 관리하였습니다.

로드밸런싱 구현

유저 분산을 위한 로드밸런서 도입기

유저 수 증가에 맞춰 데이터베이스를 유연하게 확장할 수 있도록,
로드밸런싱를 구현하였습니다.

각 사용자의 DB를 분산 저장하기 위해 사용자를 어느 DB 에 할당할 지 정하는 로드밸런싱을 구현하였습니다. 각 DB 서버 별 실시간 사용자 수 를 로드밸런싱 기준으로 잡아, 부하 분산을 저희 서비스에 최적화하였습니다.

오토스케일링 고려

오토스케일링을 적용하여 유저 수가 증가함에 따라 DB 서버 인스턴스도 증가시키고자 하였습니다. 또한 의도치 않게 DB 서버 인스턴스가 제거되고 새로 생성되는 경우를 고려하였습니다. 따라서 k8s api Server 에서 DB 서버 인스턴스 생성 이벤트를 구독하여 가용 DB 목록을 관리하였습니다.

개선 결과

비용 비교

개선 전과 비교하여 유지비용이 218% 증가하였습니다.

  • 리팩토링 전

      타입 개수 비용(월) total
    서버 s2-g2-g100 2 88,000 176,000
    Public IP - 2 4032 8,064
    NAT Gateway - 1 40,320 40,320
    합계       224,384
  • 리팩토링 후

      타입 개수 비용(월) total
    서버 s2-g3 4 82,240 328,960
    Public IP - 3 4,032 12,096
    NAT Gateway - 1 40,320 40,320
    네트워크 프록시 로드밸런서 small 2 18,720 37,440
    NKS standard 1 72,000 72,000
    합계       490,816

쿼리 실행 기능 비교

이전 시나리오와 동일하게 쿼리 실행 기능 에 대해서 부하테스트를 진행하였습니다.

기존에는 처리량이 요쳥량을 따라가지 못해 20초 넘게 걸리던 쿼리 실행 시간을,
100명의 동시 사용자 환경에서 안정적으로 쿼리 실행 요청을 감당하며 대부분의 요청을 1초 내외로 처리할 수 있었습니다.
개선 결과, 평균 쿼리 반환 시간의 개선율이 1000% 증가하였습니다.

  개선 전 개선 후 개선율 비용 효율성
평균 쿼리 반환 시간 17.6 초 1.6 초 1000 % 458 %
최악 반환 시간 25 초 7 초 257 % 117 %
1초 이내 응답 비율 0.06 0.45 650 % 298 %

응답 반환 시간

개선 전 개선 후
Clone this wiki locally