본문 바로가기

clone toy projects/node_express_sns

부하 테스트

이번 절에서는 다른 종류의 테스트를 진행해보겠습니다. 

바로 부하 테스트 (loadtest)입니다. 서버가 얼마만큼의 요청을 견딜 수 있는지 (또는 수용할 수 있는지) 테스트하는 방법입니다.

내 코드가 실제로 배포되었을 때 어떤 문법적, 논리적 문제가 있을지는 유닛 테스트와 통합 테스트를 통해 어느 정도 확인할 수 있습니다. 

그러나 내 서버가 몇 명의 동시 접속자나 일일 사용자를 수용할 수 있는지 예측하는 일은 매우 어렵습니다. 

특히 실제 서비스 중이 아니라 개발 중일 때는 예측하는 것이 더 어려워집니다.

코드에 문법적, 논리적 문제가 없더라도 서버의 하드웨어 제약 때문에 비즈니스가 중단될 수 있습니다. 

대표적인 예가 "OOM (Out of Memory)" 문제입니다. 

 

서버는 접속자들의 정보를 저장하기 위해 각각의 접속자마다 일정한 메모리를 할당합니다. 

이렇게 사용하는 메모리의 양이 증가하다가 서버의 메모리 용량을 넘어서게 되면 문제가 발생합니다. 

부하 테스트를 통해 이를 어느 정도 예측할 수 있습니다. artillery를 설치하고 서버를 실행합니다.

npm i -D artillery

 

npx artillery quick --count 100 -n 50 http://localhost:8001

이 명령어는 http://localhost:8001에 빠르게 부하 테스트를 하는 방법입니다.

 --count 옵션은 가상의 사용자 수를 의미하고, -n 옵션은 요청 횟수를 의미합니다. 

100명의 가상 사용자가 각각 50번씩 요청을 보내므로 총 5,000번의 요청이 서버로 전달됩니다. 

너무 많다고 생각할 수도 있지만, 실제 서비스를 할 때 5,000번의 요청은 그렇게 많은 양이 아닙니다. 

단지 절대적인 숫자가 중요한 것이 아니라, 하나의 요청이 얼마나 많은 작업을 하는지가 더 중요합니다.

 

콘솔을 보면 가상의 사용자 100명이 생성 (Scenarios launched)되고, 

그들의 요청이 완료 (Scenarios completed)되었으며, 

총 요청이 5,000번 수행되었음 (Request completed)을 알 수 있습니다. 

초당 474.38번의 요청이 처리 (RPS sent)되었습니다.

Request latency (응답 지연 속도)를 주목해서 보면 좋은데, 최소 (min) 9.1ms (밀리초), 최대 (max) 303.1ms가 걸렸습니다. 

중간값 (median)은 176.2ms이고, 95% (p95) 값은 252.2ms, 99% (p99) 값은 276.7ms입니다. 

이 수치를 해석하기 난해하지만, 보통 중간값과 p95의 차이가 크지 않으면 좋습니다. 

수치의 차이가 적을수록 대부분의 요청이 비슷한 속도로 처리되었다는 의미입니다.

Scenarios counts는 총 사용자의 수를 보여주며, Codes는 HTTP 상태 코드를 나타냅니다. 

5.00건의 요청 모두 200 (성공) 응답 코드를 받았습니다. 

혹시나 에러가 발생한다면 Errors 항목이 추가로 생성됩니다.

 

 

실제 서비스를 부하 테스트 할 때
지금은 http://localhost:8001 서버에 요청을 보내고 있습니다. 

이 서버는 개발용 서버이며 여러분의 컴퓨터이므로 무리한 요청으로 인해 서버가 지연될지라도 큰 문제가 없습니다. 

하지만 실제 서비스 중인 서버에 과도한 부하 테스트를 가하면 실제 서비스가 중단될 수 있습니다. 

또한, AWS나 GCP 같은 클라우드에서 종량제 요금을 선택한 경우 과도한 요금이 청구될 수 있습니다.
따라서 실제 서비스에 부하 테스트를 하는 대신에는 실제 서버와 같은 사양의 서버 

(일반적으로 스테이징 서버라고 부릅니다)를 만든 후, 그 서버에 부하 테스트를 진행하는 것이 좋습니다.

 

 

부하 테스트를 할 때 단순히 한 페이지에만 요청을 보내는 것이 아니라 실제 사용자의 행동을 모방하여 시나리오를 작성할 수 있습니다. 

이 때는 JSON 형식의 설정 파일을 작성해야 합니다.

config 객체에서 target을 현재 서버로 잡고, phases에서 30초 동안 (duration), 

매 초마다 20명의 사용자 (arrivalRate)를 생성하도록 했습니다. 

timeout은 30초로 , 요청이 30초 이내에 처리되지 않으면 실패로 간주됩니다.

이제 이 가상 사용자들이 어떠한 동작을 할지 scenarios 속성에 적습니다. 

첫 번째 flow로는 먼저 인 페이지(GET/)에 접속하고, 로그인 (POST /auth/login)을 한 후 

해시태그 검색 (GET /hashtag?hashtag=nodebird)을 합니다. 

로그인할 때 요청의 본문으로 email과 password를 JSON 형식으로 보냈습니다.

아직 flow가 하나뿐이지만, 첫 번째 flow와는 다른 일련의 과정을 시뮬레이션하고 싶다면 두 번째 flow로 만들면 됩니다.

Note: YAML 형식으로 설정하기
Artillery 공식 문서에서는 JSON 형식 대신 YAML 형식으로 설정을 만들어 사용합니다. 

두 방법 모두 유효하므로 편한 것을 고르면 됩니다. 

YAML은 코드가 간결한 형식이고, JSON은 자바스크립트 개발자에게 익숙한 형식입니다. 

npx artillery convert 파일명 명령어로 설정 파일을 JSON과 YAML 간에 변환할 수 있습니다.

npx artillery run 파일명 명령어로 부하 테스트를 실행합니다. 

600명의 접속자가 각각 세 번의 요청을 보내서 (한 번의 redirect 포함) 총 1800번의 요청이 전송됩니다.

 각각의 요청이 모두 데이터베이스에 최소한 한 번씩 접근하므로 상당히 무리가 갑니다.

 

npx artillery run loadtest.json


중간 로그를 생략하긴 했지만, 시나리오를 짠 뒤부터는 중간 로그도 챙겨보는 것이 좋습니다.

 Request latency를 보면 문제가 심각하다는 것을 알게 됩니다. 

일단 median 값이 19초이고, p95, p99가 각각 45초, 49초입니다.

즉, 어떠한 시나리오는 네 개 요청을 처리하는 데 49초나 걸렸다는 뜻입니다.

또한 중간 로그부터 봤다면 테스트를 진행할수록 요청을 처리하는 속도가 점점 느려짐을 알 수 있습니다. 

이는 서버가 지금부하 테스트를 하는 정도의 요청을 감당하지 못한다는 뜻입니다. 

따라서 이 문제를 해결할 방법을 고민해봐야 합니다. 

 

서버의 사양을 업그레이드하거나, 서버를 여러 개 두거나, 코드를 더 효율적으로 개선하는 방법이 있습니다. 

지금 상황에서는 노드가 싱글 코어만 사용하고 있으므로 클러스터링 같은 기법을 통해서 

서버를 여러 개 실행하는 것을 우선적으로 시도해볼 만합니다.

일반적으로는 요청-응답 시 데이터베이스에 접근할 때 가장 많은 시간이 소요됩니다. 

서버는 여러 대로 늘리기 쉽지만, 데이터베이스는 늘리기 어려우므로 하나의 데이터베이스에 많은 요청이 몰리곤 합니다. 

 

따라서 최대한 데이터베이스에 접근하는 요청을 줄이면 좋습니다. 

반복적으로 가져오는 데이터는 캐싱을 하거나 데이터베이스에 접근하는 일을 줄이도록 합시다.

서버의 성능과 네트워크 상황에 따라 다르지만, arrivalRate를 줄이거나 늘려서 

자신의 서버가 어느 정도의 요청을 수용할 수 있는지 체크해보는 것이 좋습니다. 

또한, 한 번만 테스트하는 게 아니라 여러 번 같은 설정값으로 테스트하여 평균치를 내보는 게 좋습니다.

 

 

 

코드를 테스트할 때 어느 범위까지 테스트해야하는지 고민될 수 있습니다. 

보통 자신의 코드는 최대한 많이 테스트하는 것이 좋지만, 

npm을 통해 설치한 패키지나 다른 사람의 라이브러리 자체는 테스트하지 않습니다. 

그 패키지나 라이브러리를 테스트하는 것은 그들의 몫입니다. 

우리는 우리의 프로그램에서 그 패키지나 라이브러리를 사용하는 부분을 테스트하면 됩니다. 

테스트하기 어려운 패키지는 모킹하고, 테스트할 수 있는 패키지는 그대로 테스트합니다. 

단, 모킹할 때는 실제 상황에서는 에러가 발생할 수 있음을 염두에 두어야 합니다.

아직 소개하지 않은 테스트 기법들도 많습니다. 대표적으로 시스템 테스트와 인수 테스트가 있습니다. 

회사에서 QA들이 테스트 목록을 두고 체크해나가며 진행하는 테스트가 주로 시스템 테스트이고, 

알파 테스트나 베타 테스트처럼 특정 사용자 집단이 실제 서비스를 사용하는 것처럼 진행하는 테스트가 인수 테스트입니다. 

가능한 다양한 종류의 테스트를 주기적으로 수행해서 서비스를 안정적으로 유지하고 점검하는 것이 좋습니다.

 

 

 

 

 

 

핵심 정리:

테스트를 작성한다고 해서 에러가 발생하지 않는 것은 아닙니다. 하지만 자신의 코드에 대한 믿음을 가질 수는 있습니다.
테스트를 올바르게 작성하지 않으면 테스트를 하지 않는 것보다 못한 상황이 발생합니다.
테스트를 작성하면 나중에 코드 변경 사항이 생겼을 때 어떤 부분에 영향을 미치는지 쉽게 파악할 수 있습니다. 

이러한 긍정적인 영향과 테스트하는 데 필요한 공수를 함께 고려해서 테스트할 범위를 정해야 합니다.
실제 서비스에서는 모든 기능을 테스트하기가 어려우므로 우선순위를 정하여 우선순위가 높은 기능 위주로 테스트합니다.
테스트 커버리지가 100%라고 해서 에러가 발생하지 않는 것은 아닙니다