티스토리 뷰

반응형

벨로그에 우리 서비스를 홍보했던 글을 보고 많은 분들이 기대 이상으로 사용해 주시고 응원해 주셨다

여러 플랫폼에 서비스를 소개했지만 운 좋게도 벨로그에서 추천을 굉장히 많이 받아 글이 주간 트랜딩 3위까지 올라갔고 현재는 월간 트렌드로 넘어간 상태다.

 

 

덕분에 무난하게 초기 사용자 목표를 달성할 수 있었다.

 

사실 이보다 더 기대했던 것은 실 사용 데이터였고,

의미 있는 수준의 데이터가 쌓여 이를 바탕으로 우리의 서버가 얼마나 안정적으로 사용자를 수용할 수 있을지 테스트가 가능해졌다.

 

마침 새로 추가되는 기능으로 인해 API 호출 수준이 많이 늘어날 것으로 예상되었고,

테스트 결과를 바탕으로 목표한 수준의 로드에서도 병목이 발생한다면 어느 부분이 문제인지 인지하고 튜닝을 진행할 것을 기대했다.

 


 

 

테스트 목표: EC2 T4g micro에 떠 있는 우리의 EC2는 어느 정도의 동시 사용자까지 100ms~300ms 수준의 응답 속도를 보일 수 있는가?

 

 

수치를 기반으로 한 테스트 계획 수립

 

피크 시간 사용자 계산

 

GA를 통해 얻은 시간대 별 사용자 수

 

Google Analytics를 넣어둔 덕분에 가장 기대했던 데이터인 시간대 별 사용자 데이터를 얻을 수 있었다.

 

 

 

실 사용자 수를 살펴보니 14시에 피크를 찍고 밤시간까지 비슷한 수준을 유지하다가 새벽에 급격하게 감소하였다.

가장 중요한 건 피크 1시간에 전체 24시간 중의 약 6.62% 정도의 사용자가 발생한다는 것이다.

 

 

목표 트래픽과 VUSER 선정

 

MAU

 

현재 수준의 트래픽은 그렇게 크지 않아 테스트를 하여도 서버가 너무 잘 대응한다.

목표는 지금 당장이 아닌 트래픽이 늘었을 때의 서버 상태를 보는 것이므로 적절한 규모의 트래픽을 상정해야 한다.

아래의 사이트에서 원하는 사이트를 검색해 MAU를 쉽게 확인할 수 있다.

https://www.similarweb.com/

 

나의 경우 교육과 관련된 사이트를 선택하였으며 MAU는 1M이었다.

 

 

평균 잔류 시간

 

Google Analytics를 통해 사용자들의 평균 잔류 시간을 알 수 있었다.

대부분의 사용이 Chrome을 통해 일어났으며 평균적으로 약 6.5분 정도 잔류했다. (1.71분 * 231초)

 

 

예상보다 높은 수치였는데 아마 서비스 특성상 켜둔 채로 개인 작업을 하다 보니 그렇지 않을까 싶다.

또한 찾아보니 GA의 세션 참여 시간 측정 방식의 특성 상 데이터가 완벽히 사용자 평균 잔류 시간과 일치한다 보긴 어렵지만 (참여한 마지막 페이지에 대한 측정값이 불확실하거나 장시간 무응답 후 재요청 시 새로운 세션으로 고려 등...) 현재 가진 데이터 중 가장 신뢰할 만한 값이었다.

 

VUSER

 

목표 MAU와 평균 잔류 시간을 알았으므로 이제 VUSER를 얼마로 가정해야 할지 계산할 수 있다.

VUSER는 가상의 유저를 뜻하며 측정 도구마다 다르지만 일반적으로 VUSER 수치를 실시간으로 서비스를 동시 사용 중인 사용자의 수라 가정한다. 부하테스트 도구마다 가상 사용자에 대한 의미가 다르므로 도구에 맞춰 산정 방식을 다르게 가져가야 한다.

 

 

http://www.webperformance.com/library/tutorials/CalculateNumberOfLoadtestUsers/

 

적절하다고 생각하는 VUSER는 이 사이트를 통해 쉽게 구할 수 있었다.

 

 

피크 시간대에 분당 38명 정도가 새로운 사용자로 추가되고 떠나며 한 명당 대략 6.5분 정도를 잔류하므로 동시에 잔류 중인 사용자는 대략 240명 정도라 가정할 수 있다.

 

 

테스트 도구 선정

 

여러 테스트 도구가 있으나 자료를 찾아보기 비교적 수월한 NGrinder와 Locust 중에 고민, Locust를 선택했다.

더 큰 규모의 테스트 인프라를 구축해야 한다면 다시 고민했겠지만

Locust는 비교적 간단하게 환경 세팅이 가능했으며 여러 데코레이터를 통해 시나리오 기반의 테스트를 쉽게 구성할 수 있기 때문이었다.

 

NGrinder

 

https://github.com/naver/ngrinder/wiki/Virtual-User

 

NGrinder에서 사용하는 VUSER의 개념은 사용자의 think time(api 호출 간의 간격)을 포함하지 않는다.

N명의 VUSER를 가정했다면 Process * Thread만큼의 VUSER를 할당하여 각각이 같은 요청을 일관되게 보낸다.

따라서 대규모의 테스트를 하려면 여러 Process와 Thread를 할당할 수 있는 좋은 사양의 Agent들을 오케스트레이션해야 한다. (시스템이 어느 정도의 VUSER를 운용할 수 있는지 경험적인 데이터가 wiki에 약간 기재되어 있다.)

 

NGrinder wiki에서도 툴 자체가 서버의 한계 측정에 초점을 둠을 명시하며 그럼에도 시나리오 기반의 테스트를 하고자 하면 직접 grinder.sleep()을 통해 요청 시점을 조절해주어야 한다고 적혀있다.

 

물론 익숙하지 않아 그랬겠지만 원하는 Groovy Script 작성을 해보려니 생각보다 원하는 라이브러리의 추가나 랜덤한 요청 조절 등에서 불편함이 있었다. 또 다양한 부하 테스트 방식을 편리하게 구성할 수 있는지도 아직 알아내지 못했다.

 

 

Locust

 

파이썬 기반의 스크립트가 필요하다.

웹 환경에서 아래와 같이 굉장히 간단하게 VUSER와 ramp up 수준을 지정하고 바로 테스트를 실행시킬 수 있다.

 

 

 

 

공식문서도 굉장히 잘 정리되어 있다.

라이브러리를 편하게 가져다 쓸 수 있으며 데코레이터를 사용해서(@task) 원하는 사용자 행동의 비율을 확률적으로 조절하거나 API 호출 간 사용자의 think time을 between(1, 5) (1초에서 5초 사이의 랜덤 선택)와 같이 매우 간편하게 지정할 수 있다.

 

분산 클러스터 환경에서의 테스트도 가능하지만 Java 기반의 NGrinder와 달리 하나의 스레드에서 async 하게 요청을 보내는 방식을 사용, 단일 Agent에서도 충분한 테스트가 가능하다.

 


수행 결과

 

시나리오 기반의 스크립트 작성의 편의로 Locust를 선택,

당장 API별 호출 비율에 대한 데이터는 없어 Google Analytics로 많이 조회된 페이지를 참조하여

현재 서비스에서 요청이 가장 많을 것으로 예상되는 API를 몇 가지 선정하였다. (GA에는 사용자당 페이지 전환 이벤트 발생 수는 존재하나 특정 페이지에서 호출되는 API의 횟수는 제각각이므로 별도로 통계를 내지 않는 한 추정치를 사용할 수밖에 없다.)

 

다음 버전에서 업데이트될 새로운 기능으로 폴링 관련 API가 존재하는데 정상 사용자 플로우에서 필히 이를 사용하게 되며 적절히 DB 조회도 포함하며 호출 주기가 3초이다.

이것이 전체 요청의 꽤나 높은 비율을 차지할 것으로 예상되어 API 간의 think time은 이것을 따르는 평균 3초로 가정하였다.

 

 

목표치인 VUSER 239보다 약간 높은 250 정도로 30분 정도 테스트를 진행하고 결과를 확인해 보았다.

앞의 유사한 작업을 미리 시켜 충분한 예열을 거친 뒤 테스트하였다.

 

 

TPS는 83 정도의 값을 보여주었는데 이는 VUSER 250명이 3초에 1번 요청을 보내므로 평균적으로 초당 80 정도의 요청을 송신하는 것으로 해석할 수 있다. Failure는 없었다.

 

 

평균적으로는 30ms, 하위 5%의 사용자에서 130ms정도의 응답 시간이 발생했다.

 

이번 테스트에서는 CloudFlare가 외부 도메인인 점, 서버의 수평적 확장이 일어났을 때를 대비해 NGINX를 거치지 않은 WAS만의 처리 성능을 알아두어야 한다는 점에서 WAS 앞단의 CloudFlare, NGINX는 제외한 채 외부 네트워크에서 WAS로 바로 요청을 보냈다.

 

실험적으로 USER -> CloudFlare -> NGINX -> WAS로의 플로우가 추가되었을 때 응답 시간의 지연이 약 150ms 전후로 추가되는 것을 확인하였는데(CloudFlare 서버의 리전 자체가 NGINX, WAS가 존재하는 리전과 차이가 꽤 커 지연이 크게 발생하는 것으로 예상) 이를 감안하면 하위 5% 사용자 또한 목표치인 300ms 안쪽의 응답을 받을 것이라 예상된다. (고부하 상황에서 CloudFlare 혹은 NGINX의 지연 변화는 측정해보지 못해 완벽한 추정은 아니다)

 

실제 요청 플로우
테스트 플로우(같은 사양의 개발용 EC2에서 진행)

 

예상보다 트래픽을 잘 받아내 VUSER를 점차 증가시켜 테스트를 진행하였고 VUSER가 1000까진 300ms 내의 요청이 거의 확실시되었고 아래처럼 VUSER 1500 수준의 요청(TPS 500)을 보냈을 때 요청의 처리는 실패 없이 일어나지만 하위 5%에서 200ms 전후에서 요동치는 응답 시간을 보여주었다.

 

CPU 사용률이 100%가 아님에도 VUSER 증가에 따라 응답 시간이 늦어지는 것은 다양한 요인이 있겠지만 JVM이 사용하는 OS의 Native Thread가 늘어나며 CPU 점유가 어려워지는 것이 가장 큰 이유로 예상된다.

 

VUSER 1500, TPS 500, 응답 속도가 느려짐이 눈에 띈다.
75% 이상의 CPU Usage

모니터링을 함께 진행했을 때 대략 70% 후반의 CPU 사용률을 보여주었다.

풀로드(CPU 99%) 상황에서 테스트를 진행했을 때 실제로 처리가 실패하는 요청이 생기기 시작하였는데 거의 그 지점에 가까워지는 로드인 듯하다. 목표했던 한계 수준의 응답 속도를 확인하여 당장 더 높은 부하까지 테스트할 필요는 없었다.

(풀로드에서는 서버 메트릭 모니터링 관련 프로세스조차 원활히 동작하지 않아 분석 또한 다른 방법이 필요하다)

 


결론

 

목표했던 응답 속도는 대략 300~500TPS 선에서는 나와주는 것을 확인하였다.

EC2 Instance가 정확히 어느 정도의 퍼포먼스를 보여주는지 제대로 알 기회가 없었는데 하나의 t4g small을 사용한 WAS에 요청을 보냈을 때 가정한 시나리오에서 월 몇백만 정도의 이용자 수준에서도 목표한 성능을 보여준다는 사실이 꽤나 놀라웠다. 아마 서버 로직 상 I/O Bound인 작업 위주여서 그렇지 않을까 싶다.

 

스왑 메모리를 설정하고도 스왑을 사용하는 시점의 요청 처리가 지나치게 느려지지 않을까 우려했는데 걱정을 조금 덜었다. 적절한 통계 없이 Scale Up 또는 Out을 해야 하는 시점은 언제인지 계속 궁금했었는데 당장 수준에선 고민할 필요가 없겠다는 생각도 들었다.

 

가장 간단히 응답 속도를 올리는 방법은 다른 물리적 리전에 위치한 cloudflare를 제거하는 것이고 PinPoint 같은 툴을 설치해서 실제 병목을 탐지해 보는 것도 좋을 것이다. 하지만 당장 테스트했던 수준의 트래픽이 발생하는 것도 아니고 불편함을 느끼는 실사용자도 없어 사양과 가장 큰 병목을 인지하는 수준에서 마무리하고 다른 일을 하기로 했다.

 

 

반응형