회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
최근 Non-Blocking I/O 모델을 채택한 Node.js의 사용이 많다. Node.js는 사용자가 직접 스레드를 제어하지 못하는 한계로 인해 클라이언트로부터 요청이 증가할수록 비례하여 처리 속도가 느려지는 한계가 있다. 이번 글에서는 PM2와 Docker를 이용해 클러스터링을 구현, 멀티코어를 구현한 후 부하테스트를 진행하여 HTTP Request의 처리량 증가, 대기시간 감소, 초당 데이터 송수신량 등의 개선을 확인했다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
최근 Non-Blocking I/O 모델을 채택한 Node.js의 사용이 많다. Node.js는 사용자가 직접 스레드를 제어하지 못하는 한계로 인해 클라이언트로부터 요청이 증가할수록 비례하여 처리 속도가 느려지는 한계가 있다. 이번 글에서는 PM2와 Docker를 이용해 클러스터링을 구현, 멀티코어를 구현한 후 부하테스트를 진행하여 HTTP Request의 처리량 증가, 대기시간 감소, 초당 데이터 송수신량 등의 개선을 확인했다.
소스는 링크를 통해 확인 가능하다.
Node.js는 Javascript의 웹 브라우저에서의 사용을 위해 C++로 작성된 EMCA Script 3rd Edition 규격의 응용프로그램인 V8 Engine을 기반으로 동작한다.
Nodejs는 메인 스레드를 중심으로 작업이 이벤트 큐에 쌓이고, 이벤트 루프를 통해 수행된다. 연산 작업이 오래 걸리는 작업은 Worker Thread로 별도 분리하여 동작한다. 처리가 완료된 작업은 다시 이벤트 큐에 순차적으로 쌓이게 되고, 이후에 이벤트 루프를 통해 수행된다.
Node.js의 구조는 여러 스레드를 이용하도록 설계되어 있으나, 사용자가 직접 스레드를 조작하는 API와 라이브러리는 지원하지 않는 한계가 있다.
사용자가 직접 스레드를 제어하지 못하는 환경에서는 클라이언트로부터 요청되는 이벤트의 수가 많아지거나, 암호화 및 복호화 등 하나의 작업처리에 시간 소요가 높은 작업을 처리할 때 속도가 점차 느려지는 한계점이 있다.
PM2는 로드 밸런스가 내장된 Node.js 애플리케이션용 프로덕션 프로세스 관리를 지원하는 프레임워크다. 애플리케이션을 영구적으로 유지하고 다운타임 없이 다시 로드하며 일반적인 시스템 관리 작업을 용이하게 할 수 있다.
도커(Docker)는 리눅스의 응용 시스템들을 프로세스 격리 기술을 사용해 컨테이너로 실행하고 관리하는 오픈소스다. 하나의 서버나 가상 머신으로 여러 컨테이너를 동시에 구동할 수 있다. 컨테이너를 사용하여 리소스를 격리하고, 서비스를 제한하며, 프로세스를 예비할 수 있다. 프로세스 ID 공간, 파일 시스템 구조, 네트워크 인터페이스 등을 통해 운영 체제에 대하여 거의 완전히 개인화된 컨텍스트를 구성하여 운영할 수 있다.
여러 개의 컨테이너들은 동일한 커널은 공유하지만 각 컨테이너는 CPU, 메모리, 입출력과 같이 오직 정의된 양의 리소스에만 제한을 받을 수 있다. 도커를 사용하여 컨테이너를 만들고 관리하면 다수의 응용 프로그램, 작업자의 작업, 다른 프로세스들이 자율적으로 하나의 물리 머신이나 여러 개의 가상 머신을 통해 구동될 수 있게 되므로 고도의 분산 시스템을 생성하는 일이 단순해진다.
Grafana k6는 성능 테스트를 쉽고 생산적으로 만드는 오픈소스 부하 테스트 도구로 CNCF(Cloud Native Computing Foundation)에서 운영 관리하고 있다. k6는 주로 시스템의 안정성과 성능을 테스트하고, 성능 회귀 및 문제를 조기에 포착하는 데 사용된다. k6 테스트 스크립트는 자바스크립트를 이용하여 작성 및 수행하며, 가상사용자(VU)를 추가하여 병렬로 HTTP Request 등을 보내어 Http 상태, 트랜잭션 정보, 응답시간 등을 확인할 수 있다.
PM2와 Docker를 이용하여 Single Container Multiple Workers 구조를 이용했다.
Worker는 Node.js에서 사용되는 HTTP 서버 프레임워크로 ‘Express’와 ‘Fastify’가 있다. Fastify의 Http 응답 성능이 27%가량 빠른 것으로 나타났다.
이번 실험에서는 Fastify를 이용하여 구성했으며, “http:{domain}:{port}/”와 같이 인덱스 페이지를 Http로 호출하면 5천만 번의 연산 이후 결괏값을 Json 형태의 객체로 담아서 Http Response 응답하도록 설계했다.
작성된 node.js 스크립트는 도커에서 동작을 하도록 도커라이징 작업을 진행하였다. PM2를 사용하지 않은 컨테이너 이미지를 만들고, 이후 PM2 프레임워크를 추가하고 클러스터링을 2개, 4개, 8개로 설정하여 각각의 컨테이너 이미지를 생성했다.
k6를 이용하여 “http:{domain}:{port}/”을 호출하도록 테스트 스크립트를 작성하였다. 결과로는 HTTP Status code 200이 동작 유무를 검증하도록 구성했다.
k6 run -d {부하시간} -u{가상요청자수} ./load-test-script.js와 같은 CLI 명령을 통해 부하 테스트를 수행했다. 부하 테스트 요청을 위해 가상유저(VU)는 100, 150, 200으로 설정하였다.
테스트 환경은 2.3 GHz 8-Core Intel Core i9, 32GB 2667MHz DDR4로 진행했고, 도커의 리소스 할당은 CPUs 4, Memory 2GB, Swap 1GB, Disk image size 59.6GB로 공통 진행하였다. 추가로 CPU 8 할당 후, 클러스터 4개와 8개 설정도 추가하여 테스트를 진행했다.
수행 결과 클러스터에 포함된 프로세스의 수가 많을수록 높은 처리 성능을 나타냈다.
HTTP Request 처리량은 CPU 4로 같은 자원을 사용 시에 VU100 요청한 경우, Non Cluster 대비 Cluster 4는 최대 약 215% 처리량이 증가했다. 다만 Cluster 8의 경우, 도커 자원의 효율이 낮아서 살짝 Cluster 4의 경우보다 낮은 성능을 나타냈다. CPU 8로 추가 배정하였을 경우에는 VU 100 요청 시 Cluster 8 환경에서 526%로 처리량이 증가했다.
HTTP Request 대기시간은 CPU 4로 같은 자원을 사용했을 때 VU 150 요청한 경우, Non Cluster 대비 Cluster 8은 최대 218% 정도의 대기시간 단축을 보였다. 또한 동일한 VU와 클러스터에서 CPU 8로 추가 배정하였을 경우에는 527%의 대기시간 단축을 나타냈다.
초당 데이터 수신량(KB/S)은 CPU 8, VU 150 및 200으로 요청했을 때 Non Cluster 대비 Cluster 8이 602%의 데이터 수신율 증가를 나타냈다.
초당 데이터 송신량(KB/S)은 CPU 8, VU 100 및 150으로 요청했을 때 Non Cluster 대비 Cluster 8이 633%의 데이터 송신율 개선을 나타냈다.
Node.JS의 이벤트 루프 방식에 따른 스레드 제약사항의 한계를 PM2를 이용한 클러스터링을 구축하여 해결할 수 있는 것을 확인했다. 다음에는 도커를 통한 컨테이너의 이용으로 멀티 클러스터링에 대한 성능 평가 실험도 진행이 필요할 것으로 보인다.