Skip to content

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

mintaek edited this page Dec 11, 2024 · 9 revisions

좋은 쿼리 실행 환경이란 무엇인가

쿼리를 테스트해볼때 가장 중요하게 보는 지표는 당연 실행속도입니다.

이 실행속도가 쿼리외 다른 원인에 의해 느려진다면 이는 정확한 테스트를 할 수가 없게 됩니다.

따라서, 좋은 쿼리 실행 환경이란 실행 속도가 쿼리 외부의 다른 요인의 영향을 최소화하는 환경을 말합니다.

QLab은 nest와 독립적으로 실제 쿼리 실행 시간을 반환하는데 자세한 내용은 아래 링크에 있습니다.

쿼리 실행시간 측정 방법

제한된 조건(서버)

QLab은 유료 서비스가 아니기 때문에, 각 사용자에게 서버를 개별적으로 할당하는 것은 현실적으로 어렵습니다.

그러므로 최대한 제한된 서버안에서도 외부환경에 영향을 덜 받을 수 있는 실행환경을 제공해야합니다.

어느정도 이를 보장하는지 테스트해보기 위해 부하테스트를 진행해보았습니다.

부하테스트

부하테스트는 locust로 진행하였습니다.

부하테스트 목적

현 부하테스트 목적은 api응답속도가 아닌,쿼리 실행시간이 어느정도 보장되는가 입니다.

그래서 쿼리 실행시간을 파일에 기록하는 코드를 추가하였습니다.

if response2.status_code == 201:
    # 응답 데이터 파일에 저장
    data_to_write = response2.json().get('data', {}).get('text', '')
    User.log_to_file("locust_output.txt", data_to_write)

@staticmethod
def log_to_file(filename, data):
    with open(filename, "a") as file:
        file.write(data + "\n")

어떤 쿼리로 테스트해야 할까?

쿼리가 느려지는 이유는 크게 두 가지로 나눌 수 있습니다:

  1. 연산 처리가 많아서 CPU를 많이 사용하는 경우.
  2. I/O 작업이 많아서 대량 데이터를 처리하거나 디스크 접근이 빈번한 경우.

I/O 작업을 테스트하려면 테이블 생성이나 데이터 삽입 같은 사전 작업이 많이 필요합니다.
이러면 준비 과정도 복잡해지고, 테스트 결과를 명확히 파악하기도 어려울 수 있습니다.

그래서, 사전 작업 없이 CPU에 부하를 줄 수 있는 benchmark 쿼리를 활용해 테스트를 진행했습니다.

이 쿼리는 반복적인 연산을 수행하는 작업이라 별도의 데이터 준비 없이도 CPU 부하를 효과적으로 테스트할 수 있습니다.

어떤 부분을 개선하면서 부하테스트를 진행할 수 있을까?

nest서버 관점에서 DB 성능에 가장 직접적인 영향을 주는건 Connection입니다.

따라서 Connection 관리 방식을 달리하면서 테스트를 진행해보았습니다.

시나리오

  • 최대 동접자: 100명
  • 유저 증가 속도: 1초당 1명
  • 테스트 실행 시간: 테스트 실행 시간 2분
  • 쿼리 실행 주기: 한 유저는 5~10초마다 쿼리를 실행
  • 실행 쿼리: SELECT BENCHMARK(20000000, POW(2, 1))
    • 이 쿼리는 한 유저가 실행시 서버 기준 0.6초 정도 시간이 소요됩니다.

결과

표에 x축은 성공 응답된 요청수이고 y축은 쿼리 실행시간 입니다.

Query DB Connection을 세션으로 유지

이 방식에서는 각 사용자가 세션을 통해 동일한 DB 커넥션을 유지합니다.

단일 실행 시와 비교하여 약 33배의 차이가 발생했으며, 사용자가 많아질수록 성능 변동이 커지고 서비스 신뢰성이 저하될 가능성이 높습니다.

요청마다 Connection 생성

각 요청마다 새 DB 커넥션을 생성하고 사용 후 즉시 제거하는 방식입니다.

기대와 달리 성능 저하가 발생했으며, 이는 커넥션 생성과 제거에 따른 추가 비용이 부하를 크게 유발했기 때문입니다.

결과적으로 성능 개선 효과가 미미하며, 다수의 요청을 효율적으로 처리하기에는 한계가 있음을 확인했습니다.

요청마다 Connection 생성 + 최대 Connection 제한 적용

MySQL의 max_connections를 20으로 제한하고 요청마다 커넥션을 생성하는 방식입니다.

이 방식에서는 최대 실행 시간이 크게 줄어들었으며, 약 4초로 감소했습니다.

다만, 요청 수가 제한에 도달할 경우 일부 요청이 실패할 가능성이 있으며, 제한된 환경에서도 성공적인 요청 처리율(RPS)이 이전 방식보다 증가함을 확인했습니다.

요약

방식 최대 실행 시간 특징
세션으로 Connection 유지 20초 높은 변동성과 낮은 신뢰성
요청마다 Connection 생성 25초 커넥션 생성 비용으로 성능 저하
요청마다 Connection 생성 + 제한 적용 4초 성능 대폭 개선, 일부 요청 실패 가능

결론

요청마다 Connection 생성 + 제한 적용 방식을 택하였습니다.
이 방식은 제한된 자원에서도 쿼리 실행 시간을 줄이고 성공 요청 수를 최대화할 수 있는 방법입니다.

특히, 커넥션 생성에 실패하면 바로 에러를 반환해서 실행 완료를 무한히 기다리는 상황을 방지합니다.
덕분에 사용자도 빠르게 알림을 받을 수 있어 UX 측면에서도 더 나은 선택이라고 판단하였습니다.

Clone this wiki locally