본문 바로가기
Research/Node.js

nodejs_번역_고성능 및 확장 가능한 Node.js 어플리케이션에 대해 - 1

by RIEM 2023. 2. 28.

이 글은 미디엄 게시글 <Good practices for high-performance and scalable Node.js applications [Part 1/3]>(https://medium.com/iquii/good-practices-for-high-performance-and-scalable-node-js-applications-part-1-3-bb06b6204197)
를 번역한 게시글입니다.


이 글은 Nodej.js에 대한 기본 지식을 가진 분들이 효율성과 성능 관점에서 더 나은 어플리케이션 아키텍처를 구축하는데 도움을 주기 위해 작성되었다고 합니다.

목차

  1. Node.js 앱을 수평적으로 확장하는 방법
  2. Node.js 앱을 확장 시 고려해야 할 것들
  3. 적용할 사례

1. Node.js 앱을 수평적으로 확장하는 방법

How to horizontally scale a Node.js application

웹 어플리케이션의 Throughput(시간당 처리량)을 높이는 방법은 'Scale'하는 것입니다. 인스턴스를 여러개 만들어 들어오는 요청에 따라 유연하게 대처하는 방법입니다.

수평적 확장이란?

  • 수평적 확장(Horizontal scaling)은 어플리케이션 인스턴스를 복제하여 들어오는 대량 요청을 대응하는 방식입니다. 머신 1개에서 멀티-코어로도 수행할 수도 있고 여러 머신들에서 함께 수행될 수도 있습니다.

수직적 확장이란?

  • 수직적 확장은 단일 머신의 성능을 올리는 것을 말합니다.

Screen Shot 2023-02-28 at 6 05 44 PM

Multiple Processes on same machine

어플리케이션의 throughput(시간당 처리량)을 높이는 일반적인 방법은 단일 머신 내 여러 코어에 프로세스를 만드는 것입니다. Node.js에서 동시성 관리가 복제되고 parallelized될 수 있습니다.

그렇다고 해서 코어의 수보다 프로세스의 수를 더 늘리는 방법은 현명하지 못한 방식입니다. 왜냐하면 운영체제(OS) 단에서 프로세스들 사이 CPU 시간을 자동으로 조절할 것이기 때문입니다.

단일 머신에서 스케일링을 하는 방법은 여러가지입니다. 그중 일반적인 것은 같은 포트에 여러 프로세스들을 돌린 뒤 외부에서 들어오는 요청을 로드 밸런싱으로 제어하는 것방식이 있습니다.

Screen Shot 2023-02-28 at 5 13 57 PM

Native cluster 모드

Node.js의 cluster 모듈은 단일 머신에서 노드 앱을 확장하는 기본적인 방법이다. 하나의 'Master' 프로세스의 인스턴스가 자식 프로세스(workers)들을 생성한다. 자식 프로세스들은 하나의 코어에 하나씩 배정되는 방식이다. 대량 요청이 들어올 경우 round-robin 방식으로 작업이 워커들에게 분산하여 처리한 뒤 같은 포트로 다시 서비스를 보낸다.

이 방식의 아쉬운 점은 마스터와 워커 프로세스 관리를 if-else문을 사용하여 수동으로 해줘야 한다는 점이다.

const cluster = require(‘cluster’);
const http = require(‘http’);
const numCPUs = require(‘os’).cpus().length;

if (cluster.isMaster) {

 console.log(`Master ${process.pid} is running`);

 // Fork workers.
 for (let i = 0; i < numCPUs; i++) {
  cluster.fork();
 }

 cluster.on(‘exit’, (worker, code, signal) => {
  console.log(`worker ${worker.process.pid} died`);
 });

} else {

 // Workers can share any TCP connection
 // In this case it is an HTTP server
 http.createServer((req, res) => {
  res.writeHead(200);
  res.end(‘hello world\n’);
 }).listen(8000);

 console.log(`Worker ${process.pid} started`);

}

PM2 Cluster 모드

process 매니저로 PM2를 만약 사용한다면, 클러스터 모듈을 어떻게 써야하는지에 대해 걱정할 필요 없이 프로세스를 스케일 할 수 있는 '클러스터' 기능을 사용할 수 있다고 한다. 작성자는 Node.js Cluster 모드 대신 PM2 Cluster 모드를 사용할 것을 추천한다.

PM2 daemon이 Master 프로세스의 역할을 대신하여, round-robin 밸런싱 방식으로 워커들의 프로세스들을 생성해줄 것이다.

덕분에 우리는 멀티코어에 대한 걱정에서 조금은 해방될 수 있다.
Screen Shot 2023-02-28 at 5 22 46 PM

PM2는 종료된 프로세스들을 재시작해주기도 한다.

그러나 이것도 만능은 아니다. 스케일을 더 키우고 싶다면 결국 머신의 수를 늘려야 한다.

Multiple machines with network load balancing

머신들을 늘리는 컨셉은 멀티 코어로 스케일링하는 방식과 유사하다. 여기서 밸런서가 각 머신에게 부하를 리다이렉트 해준다.

Screen Shot 2023-02-28 at 5 51 46 PM

네트워크 밸런서는 다양한 방식으로 배포할 수 있다. AWS를 쓴다면 ELB(Elastic Load Balancer)가 좋은 선택일 것이다. 왜냐하면 오토스켈링, 간편한 세팅 방식 등 여러 장점이 있기 때문이다.

만약 과거의 방식으로 진행하고 싶다면, 머신을 배포하고 NGINX으로 직접 밸런서를 설정하는 방식이 있다. 리버스 프록시 설정을 하는 방식은 아래와 같다.

http {

 upstream myapp1 {
   server srv1.example.com;
   server srv2.example.com;
   server srv3.example.com;
 }

 server {
   listen 80;
   location / {
    proxy_pass http://myapp1;
   }
 }
}

이 방식의 문제은 로드 밸런서가 외부에 노출되는 유일한 출구라는 점이다. 인프라에서 단일 지점에 대한 문제가 발생되는 것이 우려된다면, 여러 로드밸런서들을 배포하는 방식도 있다.

Screen Shot 2023-02-28 at 6 02 18 PM
각 ip 주소를 가진 밸런서들간 트래픽을 분산시키기 위해, 여러개의 DNS 'A' 레코드들을 당신의 메인 도메인에 추가할 수 있다. 그렇게 되면 DNS resolver가 각자 다른 IP를 가진 밸런서들에게 트래픽을 분산시켜줄 것이다.

마치기

멀티 프로세서, 멀티 밸란서를 적용하는 것이 정답은 아니니 적용 상황을 고려해서 사용하는게 옳다.

댓글