어느덧 초기 기획 단계에서 계획 했던 그라운드 플립의 모든 기능을 다 구현했다. 이제 어느 정도 유저들도 모여서 안정적으로 서비스가 운영되고 있다. 그러던 중 문뜩 내가 만든 서버는 어느 정도의 부하를 버틸 수 있을지 궁금했다. 그래서 서버의 인스턴스가 죽을 때 까지 부하를 걸어 보려한다!
우선 부하 테스트를 실행하기 전에 여러가지 준비를 해야했다. 부하 테스트 툴, 테스트 시나리오, 모니터링, 테스트 환경 등 준비해야할 부분이 많았다.
부하 테스트 툴로는 Jmeter 를 선택했다. Jmeter 외에도 여러가지 툴이 있긴 하지만 Jmeter 를 선택한 이유는 다음과 같다.
물론 위 장점만 있는 것은 아니다. 메모리를 많이 사용한다는 단점도 있긴 했다. 하지만 로컬 컴퓨터의 컴퓨팅 파워가 부족하면 aws 에서 사양이 좋은 인스턴스를 빌릴 예정이었기 때문에 괜찮다고 판단했다.
나는 Jmeter 를 선택했지만 이 글을 보는 분들은 각자의 프로젝트에 맞는 툴을 비교해보며 고르는 것이 좋겠다.
다음으로는 테스트를 진행하기 위한 시나리오를 선택해야한다. 나의 목적은 실제와 비슷한 부하 상황에서 어느 정도까지 서버가 버티는지 테스트 하는 것이다.
서비스 운영 초기 였다면 시나리오를 짜는 것이 어려웠겠지만 지금은 3개월이 넘게 운영되면서 사용자들의 요청 로그가 충분히 쌓여있다. 이 로그를 분석하면 각 api 마다 어떤 패턴으로 요청되는지 확인 할 수 있다.
하루중 가장 부하가 심한 시간대의 로그를 분석해보았다. 분석 결과는 다음과 같다.
API Path | Request Count | 비율(%) |
---|---|---|
GET 소유하고 있는 땅의 개수 조회 | 25,380 | 59.98% |
GET 개인전 지도에서 땅 조회 | 10,333 | 24.43% |
GET 개인 기록 지도에서 땅 조회 | 1,488 | 3.51% |
POST 땅 점령 요청 | 397 | 0.94% |
GET 유저 랭킹 조회 | 361 | 0.85% |
GET 유저 정보 조회 | 360 | 0.85% |
위 6개의 요청이 가장 많은 비율을 차지하고 있었다. 6개보다 적은 요청은 요청 비율이 적었기 때문에 시나리오에서 제외하고 위 6개의 요청을 사용해서 시나리오를 구성했다. 각 요청 별로 동일한 요청을 보내면 캐시의 영향을 받을 수 있으니 전부 랜덤한 인자를 넣어 요청을 보내려한다.
위 비율을 토대로 동시접속자 수를 늘려가면서 테스트를 할 예정이다. 우리 서비스는 DAU 약 300명 일때 최대 동시접속자가 30명 정도 나오는 것을 확인 할 수 있었다. 따라서 DAU 와 동시접속자 비율은 10:1 로 가정하고 결과를 산정 하려한다.
테스트 시나리오를 요약하자면
테스트를 진행하면 결과를 확인 할 수 있어야한다. 크게 2가지 부분은 확인 할 수 있어야 했다.
인프라 자원 사용률을 모니터링 하기 위해 CloudWatch 를 사용한다. aws 의 자원을 빌려서 사용중이기에 CloudWatch 를 사용하여 쉽게 CPU 사용량이나 메모리 사용량을 모니터링 할 수 있었다.
응답 시간은 Jmeter 에서 제공하는 지표를 사용한다. Jmeter 에서는 테스트 결과를 그래프와 표로 변환해주는 기능을 제공한다.
이 표에서 각 요청 별 지표, 전체 요청 지표를 쉽게 확인 할 수 있다. 또한 평균 응답 시간, 처리량, 에러율 등을 확인할 수 있다.
이렇게 표로 확인하는 방법은 아래에서 소개하도록 하겠다!
위는 우리 서비스의 시스템 아키텍처이다. 운영 서버와 동일한 환경에서 테스트를 진행하기 위해 개발용 서버를 사용했다. 실제 운영중인 서버를 사용하면 장애 상황이 발생한다. 😢
우리는 EC2 2개, RDS 1개, ElastiCache 1개를 사용중이다. EC2 1개는 Batch Server 라 이번 부하 테스트에서는 사용되지 않는다. 각 인스턴스의 스펙도 소개하겠다.
로드 밸런서를 사용하여 오토 스케일링이 적용되어있지만 부하테스트를 진행할 때는 오토 스케일링 기능을 끄고 순수하게 한대의 서버가 어느 정도의 부하를 버틸 수 있는지 테스트 해보려한다.
일단 처음에는 로컬에서 부하를 발생시키려한다. 나의 맥북에서 Jmeter 를 설치해 부하를 발생시킨다. 나의 맥북의 스펙은 다음과 같다.
부하 테스트를 위한 사전 준비는 끝났다. 이제 실제 부하 테스트를 실행하기 위해 Jmeter 에 시나리오를 작성해야한다. 이 글에서는 Jmeter 에 대한 자세한 설명을 다루기 보다는 내가 어떻게 사용했는지 위주로 다루도록 하겠다. Jmeter 에 대해 잘 모르시는 분들은 구글에서 Jmeter 를 찾아보면 좋을 것 같다.
동시에 다른 비율로 여러 요청을 보내야 하기 때문에 여러 스레드 그룹을 만들었다.
총 4개의 스레드 그룹을 만들었다. 하위 3개의 요청은 비율이 거의 비슷하여 하나의 스레드 그룹으로 묶었다.
위 사진 처럼 하나의 스레드 그룹에 3개의 요청을 보내도록 설정해두었다.
스레드 그룹을 생성 하였으니, 각 그룹 마다 얼마나 많은 요청을 보낼지 설정해야한다.
를 설정해야한다.
나의 경우 위와 같이 설정하였다. Ramp-up period 는 600초, 10분 으로 고정하였고 Loop Count 는 테스트 시나리오의 표를 기준으로 1명의 접속자가 10분동안 보내는 요청의 수로 설정하였다.
Number of Threads 는 점진적인 테스트를 위해 가변적으로 설정하였다. 테스트에 따라 스레드 개수를 지정할 수 있고 스레드의 개수가 증가할수록 시나리오와 같은 비율로 증가하는 구조이다.
스레드 그룹 설정까지 완료하였으니 각 스레드 그룹에서 전송할 HTTP 요청을 정의해두어야한다.
매 요청마다 같은 값으로 요청하면 실제 환경과 비슷하게 테스트할 수 없다. 따라서 요청의 파라미터들을 랜덤으로 할당하도록 설정해두었다.
위와 같이 Jmeter 안에서 사용할 수 있는 Random Variable 을 정의해두고 사용하였다.
위와 같은 방식들로 상위 6개 요청의 시나리오를 전부 만들어뒀다. 위 사진은 다 만든 예시이다. 모든 스레드 그룹을 펼치기엔 너무 길어서 대표로 하나만 펼쳤다.
모든 준비가 끝났으니 부하 테스트를 진행해보았다. 동시접속자는 30, 60, 100, 130, 160, 190, 200, 230 순으로 늘려가며 테스트 해보았다.
자세한 그래프 사진과 표는 글이 너무 길어져 외부 링크에 첨부한다. 아래 링크에 들어가면 자세 확인 할 수 있다.
자세한 결과
동시접속자수 | 평균 응답 속도 | CPU 최대 사용량 | DB 최대 사용량 | Redis 최대 사용량 | 에러 비율 |
---|---|---|---|---|---|
30명 | 16.96 ms | 18.6 % | 11 % | 3.72 % | 0 % |
60명 | 16.12 ms | 32.5 % | 6.83 % | 4.73 % | 0 % |
100명 | 18.12 ms | 46.6 % | 9.37 % | 5.91 % | 0 % |
130명 | 18.92 ms | 66.2 % | 10.5 % | 6.7 % | 0 % |
160명 | 18.81 ms | 72.3 % | 12.6 % | 7.31 % | 0 % |
190명 | 23.15 ms | 83.9 % | 12.9 % | 8.13 % | 0 % |
200명 | 29.5 ms | 89.7 % | 13.2 % | 8.33 % | 0 % |
230명 | 119.61 ms | 95.5 % | 14.1 % | 8.5 % | 0 % |
250명 | 310.10 ms | 95.9 % | 17.4 % | 8.62 % | 0.69 % |
테스트 결과를 확인해보면 동시접속자 200명 까지는 평균 응답속도가 29.5 ms 이내로 안정적인 응답속도가 나오는 것을 확인 할 수 있다.
하지만 200명을 넘긴 순간부터 응답 속도가 급격하게 느려진다. 심지어 250명에서는 0.69 % 의 요청이 실패하는 것을 확인 할 수 있었다. 에러 코드는 502 / Bad Gateway 로 서버에서 요청을 처리 못하는 것을 확인 할 수 있다.
서버 CPU 사용량을 분석하면 평균 응답속도가 동시접속자 230명에서 느려지는 이유를 확인 할 수 있었다. 사용량을 확인해보면 동시접속자가 늘어날수록 사용량이 증가하는 모습을 확인 할 수 있다.
CPU 사용량이 90% 이하인 200명 까지는 서버에서 요청을 잘 처리하여 평균 응답속도가 약 18ms 로 균일하게 응답되는 것을 확인할 수 있다. 하지만 90%가 넘어가면 응답속도가 급격히 느려진다. 이는 서버의 CPU 자원이 이미 포화 상태에 도달 했음에도 불구하고 추가적인 요청이 들어오면서, 나중에 들어온 요청이 앞선 요청이 처리될 때까지 대기해야 하기 때문으로 생각된다.
이보다 더 많은 요청이 들어오면 서버에서는 아예 처리를 하지 못하고 502 에러를 응답하는 모습을 볼 수 있다. 안정적으로 서버를 운영하기 위해서는 적어도 CPU 사용량이 80% 이상이 되면 스케일 업을 하든, 스케일 아웃을 하든 트래픽을 분산 시키기 위한 조치가 필요할 것 같다.
DB와 Redis 의 CPU 사용률은 서버와 다르게 여유롭다. 물론 요청이 올라갈 수록 사용량이 증가하지만 두 CPU 거의 15%를 넘지 않는다. 따라서 서버를 증설해도 DB와 Redis 는 여유가 있을 것으로 보인다.
스토리지 용량을 주의 해야한다!
부하 테스트를 진행하게 되면 많은 양의 요청을 서버가 처리하게 된다. 따라서 많이 처리하는 만큼 로그도 많이 쌓이게 된다. 이때 스토리지 용량이 다 차게 되면 서버의 CPU 사용률이 올라가며 성능이 떨어진다.
나의 경우에도 200명 테스트 할 쯤에 스토리지가 꽉차서 성능이 떨어지는 현상이 발생하여 스토리지를 비우고 다시 테스트를 했다. 따라서 부하 테스트를 할 때는 스토리지 용량을 잘 체크해야한다.
어플리케이션에서 남기는 로그를 비우는 것도 중요하지만 시스템에서 자동으로 남기는 로그도 잘 체크 해야된다. nginx 로그, docker container 로그 등 우리가 모르는 로그들이 계속 쌓이기 때문에 잘 관리해야한다.
df -h
위 명령어로 확인 할 수 있다.
du -h
위 명령어를 사용하면 현재 위치해있는 폴더안에서 용량을 확인 할 수 있다. 어떤 폴더의 용량이 많은지 확인 할 때 용이하다.
스토리지 용량을 지우는 방법은 다음 글에서 자세히 소개하겠다.
이번 부하테스트를 통해 사용자가 많아졌을 때의 가장 큰 병목은 서버인것을 확인 할 수 있었다. DB와 Redis 의 CPU 사용률은 안정적이기 때문에 서버단에서 요청을 안정적으로 처리 할 수 있도록 개선이 필요하다. 서버를 터뜨리려면 맥북으로는 부족할 수도 있겠다고 생각했는데 맥북 한대만으로 충분히 마비 시킬 수 있었다 ㅋㅋㅋ
부하테스트를 진행하면서 우리 서버의 한계를 확실히 알 수 있어 의미있는 작업이었던 것 같다. 이전 까지는 서버가 어느 정도까지 버틸지 확신이 없었는데 이제는 확실히 트래픽이 많아진다면 어느 정도 수준에서 자애가 날지 파악이 되었기 때문에 더 안정적으로 운영할 수 있을 것 같다.
부하 테스트를 진행하면서 예상치 못한 버그도 발견되었다. 이는 분산 락에서 발생한 문제였다. 요청이 증가하며 처리 속도가 느려지자, 한 스레드가 락을 사용하는 동안 설정된 시간(leaseTime)이 초과되어 락이 자동으로 해제되었다. 문제는 락이 이미 해제된 상태에서 다시 락을 해제하려고 시도하면서 에러가 발생한 것이다.
이 문제는 락을 해제하기 전에 락이 여전히 유효한지 확인하는 코드를 추가하여 해결하였다. 자세한 해결 과정과 코드 변경 내용은 다음 글에서 다루어 보겠다.
대부분의 API가 50ms 이내로 처리되는 것을 확인했지만, 땅을 차지하는 로직의 경우 다른 API에 비해 평균 응답 속도가 느린 점이 눈에 띄었다. 해당 API가 다른 API보다 작업이 무겁다는 점은 이미 인지하고 있었지만, 실제 성능에 영향을 미친다는 것을 확인하면서 로직을 개선해야겠다는 필요성을 느꼈다.
이 로직을 개선할 여지가 많아 보이며, 이를 통해 성능을 더 향상시킬 수 있을 것으로 기대된다. 개선 과정과 결과는 추후 정리하여 포스팅으로 공유하도록 하겠다!
끝으로 다음글에서는 서버에서 많은 요청을 안정적으로 처리 할 수 있도록 스케일 업과 스케일 아웃을 해보며 처리량을 확인해보려 한다. 다음글에서는 DB 까지 터뜨려보며 현 DB 스펙에서 어느정도 까지 버틸지 확인 할 예정이니 많은 기대 부탁드린다.
또한 JMeter로 부하테스트를 진행하며 시행착오와 알아두면 좋은 팁 같은 것들이 많이 생겨, 이 부분도 따로 정리해보려한다.
이번 테스트를 통해 더 해볼 수 있는 재미있는 작업이 많이 생겨 기분이 좋고 기대된다. 해볼 수 있는 컨텐츠가 3개 이상은 되어 너무 좋다 😀😀 그 동안 프로젝트를 진행하면서 부하 테스트를 해봐야지 생각만하고 방법과 시나리오 짜는 법을 잘 몰라서 진행을 못했는데 이번에는 실제로 진행 할 수 있어 너무 좋은 경험이었다.
이 글을 읽는 분들도 꼭 프로젝트를 완성했다면 부하테스트를 해보는 것을 추천한다. 생각보다 더 많은 것을 얻어 갈 수 있을 것이다!
진짜 끝!
좋은 글이네요!