`wrk`를 사용한 정확한 성능 테스트
API7.ai
November 25, 2022
이 글에서는 성능 테스트에 대해 이야기하겠습니다. 이 부분은 OpenResty에만 국한되지 않고 다른 백엔드 서비스에도 적용됩니다.
성능 테스트는 매우 보편적이며, 제품을 출시할 때는 항상 QPS, TPS, 지연 시간, 동시에 지원되는 사용자 수 등과 같은 성능 지표가 함께 제공됩니다. 오픈소스 프로젝트의 경우에도 버전을 출시하기 전에 성능 테스트를 수행하여 이전 버전과 비교하여 성능이 크게 저하되었는지 확인합니다. 또한 중립적인 웹사이트에서 유사 제품의 비교 성능 데이터를 발표하기도 합니다. 성능 테스트는 우리와 매우 가깝다고 할 수 있습니다.
그렇다면 과학적이고 엄격한 성능 테스트는 어떻게 진행할까요?
성능 테스트 도구
일을 잘하려면 먼저 좋은 도구를 사용해야 합니다. 좋은 성능 테스트 도구를 선택하는 것이 성공의 절반입니다.
여러분이 익숙할 ab
라고도 불리는 Apache Benchmark 도구는 가장 쉬운 성능 테스트 도구이지만, 아쉽게도 그다지 유용하지 않습니다. 이는 현재 서버 측이 동시성과 비동기 I/O를 기반으로 개발되어 성능이 나쁘지 않기 때문입니다. ab
는 머신의 멀티코어를 활용하지 못하며 생성된 요청이 충분한 부하를 주지 못합니다. 이 경우 ab
테스트로 얻은 결과는 실제와 다를 수 있습니다.
따라서 우리는 부하 테스트 도구에 대한 기준을 선택할 수 있습니다: 도구 자체가 견고한 성능을 가지고 있어 서버 측 프로그램에 충분한 부하를 줄 수 있어야 합니다.
물론, 더 많은 비용을 들여 여러 부하 테스트 클라이언트를 시작하고 이를 분산 부하 테스트 시스템으로 만들 수도 있습니다. 하지만 이 경우 복잡성도 증가한다는 점을 잊지 마세요.
OpenResty 실습으로 돌아가서, 우리가 추천하는 성능 테스트 도구는 wrk
입니다. 먼저, 왜 이를 선택했는지 알아보겠습니다.
첫째, wrk
는 도구 선택 기준을 충족합니다. wrk
가 단일 머신에서 생성하는 부하는 NGINX가 100% CPU 사용률에 도달하기 쉽게 만들며, 다른 서버 측 애플리케이션은 말할 것도 없습니다.
둘째, wrk
는 OpenResty와 많은 공통점을 가지고 있습니다. wrk
는 처음부터 작성된 오픈소스 프로젝트가 아니라 LuaJIT와 Redis의 위에 서 있으며 시스템의 멀티코어 리소스를 활용하여 요청을 생성합니다. 또한, wrk
는 Lua API를 노출시켜 사용자가 Lua 스크립트를 임베드하여 요청 헤더와 내용을 사용자 정의할 수 있게 하여 매우 유연합니다.
그렇다면 wrk
를 어떻게 사용해야 할까요? 다음 코드 조각을 보면 간단합니다.
wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
이는 wrk
가 12개의 스레드를 사용하여 400개의 장기 연결을 30초 동안 유지하며 지정된 API 인터페이스로 HTTP 요청을 보낸다는 의미입니다. 물론, 매개변수를 지정하지 않으면 wrk
는 기본적으로 두 개의 스레드와 열 개의 장기 연결을 시작합니다.
테스트 환경
테스트 도구를 찾은 후에는 바로 부하 테스트를 시작할 수 없습니다. 테스트 환경을 한 번 확인해야 합니다. 테스트 환경에서 확인해야 할 주요 항목은 네 가지이며, 이를 자세히 설명하겠습니다.
1. SELinux 끄기
CentOS/RedHat 운영 체제를 사용 중이라면 SELinux를 끄는 것을 권장합니다. 그렇지 않으면 많은 이상한 권한 문제가 발생할 수 있습니다.
다음 명령어로 SELinux가 켜져 있는지 확인해 보겠습니다.
$ sestatus
SELinux status: disabled
만약 켜져 있다면(enforcing), $ setenforce 0
명령어로 일시적으로 끌 수 있습니다. 또한, /etc/selinux/config
파일을 수정하여 SELINUX=enforcing
를 SELINUX=disabled
로 변경하여 영구적으로 끌 수 있습니다.
2. 열린 파일의 최대 수
그런 다음, 현재 시스템의 전체 열린 파일의 최대 수를 다음 명령어로 확인해야 합니다.
$ cat /proc/sys/fs/file-nr
3984 0 3255296
여기서 마지막 숫자 3255296
이 열린 파일의 최대 수입니다. 이 숫자가 작다면 /etc/sysctl.conf
파일을 수정하여 이를 증가시켜야 합니다.
fs.file-max = 1020000
net.ipv4.ip_conntrack_max = 1020000
net.ipv4.netfilter.ip_conntrack_max = 1020000
수정 후에는 시스템 서비스를 재시작하여 적용해야 합니다.
sudo sysctl -p /etc/sysctl.conf
3. 프로세스 제한
시스템 전체의 열린 파일 수 외에도 프로세스가 열 수 있는 파일 수에도 제한이 있습니다. 이는 ulimit
명령어로 확인할 수 있습니다.
$ ulimit -n
1024
이 값이 기본적으로 1024로 설정되어 있는 것을 알 수 있습니다. 이는 작은 값입니다. 각 사용자 요청은 파일 핸들에 해당하며, 부하 테스트는 많은 요청을 생성하므로 이 값을 증가시켜야 합니다. 이를 수백만으로 변경하려면 다음 명령어를 사용하여 일시적으로 변경할 수 있습니다.
ulimit -n 1024000
또는 /etc/security/limits.conf
파일을 수정하여 영구적으로 변경할 수 있습니다.
* hard nofile 1024000
* soft nofile 1024000
4. NGINX 구성
마지막으로, NGINX 구성을 약간 수정해야 합니다. 다음 세 줄의 코드입니다.
events {
worker_connections 10240;
}
이를 통해 각 Worker의 연결 수를 증가시킬 수 있습니다. 기본값은 512로, 고부하 테스트에는 충분하지 않습니다.
부하 테스트 전 확인
이제 테스트 환경이 준비되었습니다. wrk
로 테스트를 시작하기 전에 마지막으로 한 번 더 확인해야 합니다. 사람은 실수를 할 수 있으므로 교차 테스트가 중요합니다.
이 마지막 테스트는 두 단계로 나눌 수 있습니다.
1. 자동화 도구 c1000k
사용
c1000k는 SSDB
의 저자가 만든 도구입니다. 이름에서 알 수 있듯이, 이 도구의 목적은 환경이 10^6 동시 연결 요구 사항을 충족할 수 있는지 확인하는 것입니다.
이 도구의 사용법도 매우 간단합니다. server
와 client
를 시작하여, server
프로그램이 포트 7000
에서 수신 대기하고 client
프로그램이 실제 환경에서의 부하 테스트를 시뮬레이션합니다:
. /server 7000
. /client 127.0.0.1 7000
이어서 client
가 server
에 요청을 보내 현재 시스템 환경이 백만 동시 연결을 지원할 수 있는지 확인합니다. 직접 실행해 보고 결과를 확인할 수 있습니다.
2. 서버 프로그램이 정상적으로 실행 중인지 확인
서버 측 프로그램이 제대로 작동하지 않으면 부하 테스트가 오류 로그 새로 고침 테스트나 404
응답 테스트가 될 수 있습니다.
따라서 테스트 환경 테스트의 마지막이자 가장 중요한 단계는 서버 측 단위 테스트 세트를 실행하거나 주요 인터페이스를 수동으로 호출하여 wrk
테스트의 모든 인터페이스, 반환 및 HTTP 응답 코드가 정상인지 확인하고 logs/error.log
에 오류 수준의 메시지가 없는지 확인하는 것입니다.
요청 보내기
이제 모든 준비가 끝났습니다. wrk
로 부하 테스트를 시작해 보겠습니다!
$ wrk -d 30 http://127.0.0.2:9080/hello
Running 30s test @ http://127.0.0.2:9080/hello
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 595.39us 178.51us 22.24ms 90.63%
Req/Sec 8.33k 642.91 9.46k 59.80%
499149 requests in 30.10s, 124.22MB read
Requests/sec: 16582.76
Transfer/sec: 4.13MB
여기서 매개변수를 지정하지 않았으므로 wrk
는 기본적으로 2개의 스레드와 10개의 장기 연결을 시작합니다. wrk
의 스레드와 연결 수를 매우 크게 조정할 필요는 없습니다. 대상 프로그램이 100% CPU 사용률에 도달할 수 있다면 충분합니다.
하지만 부하 테스트 시간은 너무 짧아서는 안 됩니다. 몇 초 동안의 부하 테스트는 의미가 없습니다. 그렇지 않으면 서버 프로그램이 핫 리로딩을 완료하기 전에 부하 테스트가 끝날 가능성이 높습니다. 동시에 top
이나 htop
과 같은 모니터링 도구를 사용하여 부하 테스트 중 서버의 대상 프로그램이 100% CPU 사용률로 실행 중인지 확인해야 합니다.
현상적으로, CPU가 완전히 로드되고 테스트가 중지된 후 CPU와 메모리 사용량이 빠르게 감소한다면, 축하합니다. 테스트가 성공적으로 완료된 것입니다. 그러나 다음과 같은 예외가 발생한다면 서버 측 개발자로서 주의를 기울여야 합니다.
- CPU가 완전히 로드되지 않습니다. 이는
wrk
의 문제가 아니라 네트워크 제한이나 코드 내의 블로킹 작업 때문일 수 있습니다. 코드를 검토하거나off CPU
플레임 그래프를 사용하여 이를 확인할 수 있습니다. - CPU가 항상 완전히 로드되어 있으며, 부하가 중지된 후에도 그대로입니다. 이는 정규식이나 LuaJIT 버그로 인한 무한 루프를 나타낼 수 있으며, 실제 환경에서 이를 경험한 적이 있습니다. 이때는 CPU 플레임 그래프를 사용하여 이를 확인해야 합니다.
마지막으로, wrk
통계를 살펴보겠습니다. 이 결과에 대해 일반적으로 두 가지 값에 주목합니다.
첫 번째는 QPS, 즉 Requests/sec: 16582.76
입니다. 이는 서버 측에서 초당 처리되는 요청 수를 나타내는 정확한 수치입니다.
두 번째는 지연 시간: Latency 595.39us 178.51us 22.24ms 90.63%
입니다. 이는 QPS만큼 중요하며 시스템의 응답 속도를 반영합니다. 예를 들어, 게이트웨이 애플리케이션의 경우 지연 시간을 1ms 이내로 유지하려고 합니다.
또한, wrk
는 latency
매개변수를 제공하여 지연 시간의 백분율 분포를 상세히 출력합니다. 예를 들어:
Latency Distribution
50% 134.00us
75% 180.00us
90% 247.00us
99% 552.00us
그러나 wrk
의 지연 시간 분포 데이터는 정확하지 않습니다. 이는 네트워크와 도구의 교란을 인위적으로 추가하여 지연 시간을 증폭시키기 때문입니다. 이 점에 특별히 주의해야 합니다.
요약
성능 테스트는 기술적인 작업입니다. 많은 사람들이 이를 올바르고 잘 수행할 수 없습니다. 이 글이 성능 테스트에 대해 더 포괄적으로 이해하는 데 도움이 되길 바랍니다.
마지막으로, wrk
는 사용자 정의 Lua 스크립트를 지원하여 부하 테스트를 수행할 수 있습니다. 따라서 문서를 기반으로 간단한 Lua 스크립트를 작성할 수 있을까요? 약간 어려울 수 있지만, 완료하면 wrk
가 노출한 인터페이스의 의도를 이해하게 될 것입니다.
이 글을 더 많은 사람들과 공유하여 함께 발전해 나가길 바랍니다.