API 성능 측정 테스트를 위해 쓸만한 툴들을 리서치하던 중 측정 결과를 모니터링하고 간편하게 시각화해주는 Clinic.js 라는 툴을 발견하게 되었다.
테스트를 위한 API Shooting 은 autocannon 을 지원하여 CLI 로 간편하게 요청을 전송할 수 있었고, 이외에 K6, artillery 와 같은 다양한 API Testing 툴을 함께 사용하여 병렬적인 요청 혹은 램프업(Ramp-up), 램프다운(Ramp-down) 등의 다양한 상황을 재현하여 테스트할 수도 있었다.
뿐만 아니라, 부하나 병목이 발생하는 지점에 대해 Code Level 의 메트릭까지 그래프로 시각화하여 제공되기 때문에 문제 발생의 원인을 빠르고 정확하게 파악할 수 있다는 점도 큰 장점이었다.
따라서 Node 환경에서 간편하게 성능 테스트를 수행하고 자세한 결과를 받아보고 싶다면 Clinic.js 가 좋은 선택지가 될 수 있을 것이다.
오늘은 Clinic.js 의 공식문서를 읽어보며 짧게 정리한 글을 포스팅하고, 추후에 이를 참고하여 테스트를 수행, 병목 발생 지점을 실제로 개선하는 과정까지를 포스팅할 예정이다.
Clinic.js 는 Node.js 애플리케이션의 다양한 성능 관련 지표를 그래프로 시각화하여 이슈를 진단하고, 병목지점을 손쉽게 찾아낼 수 있도록 아래 네 가지의 서비스를 제공하고 있다.
아래에서는 위 서비스들의 개념에 대해 정리하고, 간단한 데모 시연을 준비했다.
메트릭에 대한 자세한 설명과 이를 통한 인사이트는 공식문서에 친절하게 설명되어 있다.
Node.js 애플리케이션의 성능 이슈를 진단하는 서비스
이 때 CPU 사용량은 멀티코어의 경우 100% 를 넘게 될 수 있으며, 특정 시점에 급격히 떨어진 CPU 사용량은 특정 I/O 작업에 의해 CPU 작업이 Block 되었을 가능성이 있다.
Memory Usage 그래프에서 RSS 는 해당 프로세스에서 할당된 모든 메모리 공간을 나타낸다. 따라서 RSS 는 항상 가장 큰 값을 가져야 하며, RSS 와 THA 의 사이는 Stack 과 같은 Non-heap memory 가 될 것이다.
Heap Used 는 할당된 Heap memory 중에서 해당 시점에 실제로 사용 중인 Heap 공간을 나타내는 지표다.
Event Loop Delay 는 특정 자바스크립트 동기 코드에 의해 Event Loop 가 Block 된 시간을 나타낸다. 이를 통해, 비효율적인 동기 코드가 애플리케이션에 존재하는지 확인할 수 있다.
마지막으로 Active Handles 는 현재 시점에 활성화된 I/O 작업의 수를 나타낸다. libuv 에 의해 수행되어 아직 결과를 기다리고 있는 비동기 작업만을 포함한다.
가장 상단의 Alert Bar 를 통해 애플리케이션의 주요 이슈를 요약하여 보여준다.
Recommendations Panel 을 통해 애플리케이션 진단 결과와 Next Steps, Reference 를 보여준다.
여기서 Next Steps 는 사용자가 취해야할 적절한 Action 을 의미한다.
Reference 를 통해 포착된 이슈에 대한 정보를 얻을 수 있으며 개선을 위한 새로운 인사이트를 찾아낼 수도 있다.
Code Level 에서 애플리케이션의 Bottlenecks 혹은 Hot Function 을 찾을 수 있도록 시각화를 제공하는 서비스
호출되는 함수 (Stack)
CPU 에 의해 각 함수가 관찰된 시간 (Block Width)
CPU 점유 시간이 긴 함수일수록 각 Block 의 넓이는 넓어진다.
스택의 최상단에서 각 함수가 관찰된 총 시간 (Brightness)
스택의 최상단에서 실행되는 시간이 길수록, Block 의 밝기는 밝아진다.
가장 오래 실행되는 함수는 첫번째, 가장 왼쪽에 표시되며, 이는 Node.js Event Loop 를 오랜 시간 Block 하고 있다는 것을 의미한다.
위 세 가지 메트릭을 통해 애플리케이션에서 서로를 호출하는 함수 간 관계, 각 함수의 CPU 점유 시간, 스택의 최상단에서 실행 중인 함수 등의 정보를 알아낼 수 있다.
Node.js 애플리케이션의 비동기 Flow 를 시각화하여, 비동기 프로세스 실행 시 Delay 를 측정하는 서비스
비동기 Operations 를 각각의 버블로 그룹화하여 시각화한다.
버블의 크기는 비동기 Operations 의 수행 시간을 나타낸다.
더 많은 시간이 소요될수록 버블의 크기는 크게 표현된다.
버블 사이를 잇는 라인 길이는 두 Operation 간 Latency 를 나타낸다.
Latency 가 길수록 두 버블 간 라인의 길이는 길게 표현된다.
버블 혹은 라인의 색상은 비동기 Operation 의 타입을 나타낸다.
버블 내부의 비동기 Operation 의 Stack Trace 도 제공한다.
Search Bar 와 비동기 Operation 의 타임라인을 제공한다.
Search Bar 에서는 앞선 서비스들과 동일하게, 원하는 파일 혹은 함수명으로 메트릭 결과를 필터링하여 확인할 수 있다.
Profiled Time 동안의 메모리 할당 정보를 Flamegraph 로 시각화하여 제공하는 서비스
npm install -g clinic
# 설치 확인
clinic doctor --help
Clinic.js 가 애플리케이션을 모니터링하는 동안, 서버에 API 요청을 수행할 툴을 설치한다.
공식문서에서는 autocannon 과 함께 사용하는 방법을 소개하고 있으므로, 동일하게 autocannon 을 설치해주었다.
npm install -g autocannon
# 설치 확인
autocannon --version
실행 명령은 다음과 같다.
clinic doctor --on-port 'autocannon localhost:$PORT/api/url/users' -- node dist/main.js
clinic doctor
→ doctor 서비스 실행 명령--on-port
→ 서버가 Port 를 Listening 한 뒤에, 이후 스크립트를 실행$PORT
→ 서버가 Listening 하는 첫 번째 Port-- node dist/main.js
→ 프로파일링 대상 서버를 실행하는 명령다시 말해 위 명령은 총 세 가지의 프로세스를 실행하는 명령이다.
TypeScript 환경에서 Clinic.js 를 사용하기 위해서는 Transpiling 된 JS 파일을 타겟하여 실행하여야 한다. 따라서 dist 디렉토리 내 main.js 파일을 실행 대상 파일로 지정하였다.
명령을 실행하면, 서버가 시작되고 아래와 같은 autocannon 메트릭이 출력된다.
각 테이블은 다음과 같다.
이후, 서버는 자동으로 종료되고 Clinic.js Doctor 의 진단 결과가 담긴 HTML 페이지가 로드된다.
로드된 HTML 페이지의 최상단, Issue Summary 를 확인해보면, CPU Usage 가 비정상적으로 낮은 것과 관련하여 I/O Operation 이슈가 탐지되었다는 진단 결과를 확인할 수 있다.
아래 Recommendations Panel 에서 더욱 자세한 진단 결과를 살펴볼 수 있으며, 이에 따른 Next Step 은 물론, 관련된 Reference 까지 제공하고 있다.
Clinic.js Doctor 의 진단 결과는 다음과 같다.
clinic bubbleprof
명령을 통해 비동기 Delay 가 발생하고 있는지 확인이 필요하다.이제 Doctor 의 진단 결과를 참고하여 Next Step 을 따라 문제를 파악하고, 해당 이슈를 해결하여 성능 개선을 시도할 수 있게 되었다.
Bubbleprof 나 Flame 과 같은 다른 서비스들 또한 doctor 와 동일한 명령으로 수행할 수 있다.
clinic bubbleprof --on-port 'autocannon localhost:$PORT/api/url/users' -- node dist/main.js
지금까지 Clinic.js 가 제공하는 서비스들에 대해 간단하게 정리해보았다.
각각의 메트릭으로부터 인사이트를 추출하는 과정과 이슈가 발생한 지점을 개선하는 과정은 실제로 큰 차이가 있기 때문에 별도의 포스트로 작성하기로 했다.
Clinic.js 에서 제공하는 각 메트릭들을 학습하여 그래프를 읽어내고, 문제가 되는 지점을 찾아 실제로 개선하는 연습은 그 자체로 값진 경험이고 좋은 학습 방향이 될 것 같다.