대규모 트래픽 환경에서 안정적인 서비스를 운영하기 위해 신규 API에 대한 부하테스트는 필수적이라고 할 수 있습니다.
현재 진행 중인 프로젝트에 부하 테스트를 진행하기 위해 Naver에서 개발한 APM 도구인 Pinpoint와 부하테스트 오픈소스 nGrinder를 활용하였습니다.
이 글을 통해 테스트 환경 세팅을 하면서 습득한 지식과 경험에 대해 작성해보았습니다.
위와 같이 구성되어 있습니다.
nGrinder의 구조는 위와 같지만 이해하기 쉽게 추상화 해보면 아래와 같은 구조와 같습니다.
nGrinder는 성능 측정 목적으로 개발된 오픈 소스 프로젝트로
등의 기능을 제공합니다.
성능 측정 도구로 nGrinder를 채택한 이유는 groovy 스크립트로 테스트 시나리오를 작성할 수 있다는 것 입니다.
Pinpoint는 대규모 분산 시스템의 성능을 분석하고 문제를 진단, 처리하는 플랫폼입니다.
PinPoint 는 현재 인터넷 서비스가 발전하면서 대규모 사용자를 지원하면서 많은 기능과 서비스 간에 유기적인 연동이 필요하게 되었고, SOA나 MSA 형식의 아키텍처와 같은 시스템 복잡도가 높은 문제를 해결하게 위해 n계층 아키텍처를 효과적으로 추적할 수 있도록 개발되었습니다.
Pinpoint 는 다음과 같은 특징을 가지고 있습니다.
Pinpoint는 Google Dapper 스타일의 추적 방식을 사용해, 분산된 요청을 하나의 트랜잭션으로 추적합니다.
Google Dapper의 분산 트랜잭션 추적 방법
분산 트랜잭션 추적의 핵심은 위의 그림처럼 Node 1에서 Node 2로 메시지를 전송했을 때, Node 1과 Node 2가 처리한 메시지의 관계를 찾아내는 것입니다.
Google Dapper의 방법으로 메시지 전송 시 메시지를 엮을 수 있는 태그를 메세지에 추가하여 Node 1과 Node 2 메시지 간의 관계를 엮었습니다.
자세한 내용은 기술 블로그에서 확인할 수 있습니다.
https://d2.naver.com/helloworld/1194202
테스트에 들어가기 전에 테스트 계획을 세웠습니다.
그리고 LoadBalancer를 통한 분산 트래픽의 성능을 확인해보겠습니다.
Pod 1개를 실행시키고, 주문에 대한 재고 1천만건을 가정합니다.
TPS 160.2, 평균 테스트 시간 1,838 ms 초당 160번의 API 요청을 처리하는걸 확인할 수 있습니다.
응답시간이 평균 1.24초, 최대 4.38초 인것을 확인할 수 있습니다. API 성능이 좋지 않다는걸 확인할 수 있습니다.
Heap 메모리 사용량은 임계 영역을 넘을 때가 많고 CPU 사용량도 80프로 가까이 사용되는 걸 확인할 수 있습니다.
팟을 4개로 늘렸습니다.
TPS 171.2, 평균 테스트 시간 2,476 ms 초당 170번의 API 요청을 처리하는걸 확인할 수 있습니다. TPS는 10회정도 늘어났습니다.
응답시간이 평균 2.36초, 최대 22.64초 인것을 확인할 수 있습니다. API 성능이 더 안좋아졌습니다.
Heap 메모리 사용량은 임계 영역을 넘는 횟수가 줄었고 CPU 사용량도 50프로로 안정화된 것을 확인할 수 있습니다.
서버에 자원 사용률에 대한 부분은 절반 가까이 개선이 되었는데 왜 API 응답속도가 늦어졌을까요?
Apdex (Application Performance Index) 애플리케이션 성능이 느려진 부분을 확인해보겠습니다.
느려진 부분을 보니 Connection 을 맺는 부분에서 12초가 걸렸습니다.
오류난 API를 확인해보면 18초 걸렸는데 Lock을 획득하는데 실패하여 Exception이 발생했습니다.
예측 가는 부분은 재고 감소 로직에 단일 Redis 서버에 대한 분산락 로직이 있다는 것이 생각났습니다.
4개의 서버가 1개의 Redis 단일 서버에 접근하면서 아래와 같은 문제점들이 발생하는 걸 예상할 수 있습니다.
부하 테스트를 해보고 다중 서버에 대한 문제점을 알 수 있었습니다. redis를 이용해 분산 락을 구현해서 데이터 정합성을 유지하려고 했지만 락 획득에 대한 병목 현상 때문에 API 성능에 대한 이슈가 생겼다는 걸 확인했습니다.
이를 해결하기 위해서는 메세지 큐(Kafka, RabbitMQ) 을 도입하거나 락 획득 및 해제 시간을 최적화하여 락 경합에 대한 문제를 해결할 수 있을거 같습니다.
또 알아낸 방법은 재고 감소에 대한 부분을 DB에서 수행하는 것이 아닌 Redis에서 수행하는 것입니다.
위와 같은 방법들로 개선을 해봐야겠습니다.
참고
https://azelhhh.tistory.com/m/122
https://velog.io/@max9106/nGrinderPinpoint-test1#%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%AA%A9%ED%91%9C
https://hamryt.tistory.com/11
https://blog.imqa.io/pinpoint/