본문 바로가기

Backend/Node.js

Node.JS 어플리케이션 확장

현대의 개발 세계에선 다들 너무나도 잘 알고 있을 듯한 어플리케이션을 확장하는 방법에 대해 기록하는 차원에서 남겨둘까 합니다.

Node.JS 는 I/O 작업에 특화되어 있는 만큼 수많은 짧은 요청을 처리하는데 장점이 있고 운영하는 서버의 스펙은 크게
신경쓰지 않을 정도로 좋아졌다고 하지만, 기본적으로 단일 스레드에서 처리하는 양에 한계는 분명히 있습니다.

 

이러한 환경에서 Node.JS 서버의 처리량을 향상시키려면 멀티 프로세스와 멀티 머신 확장입니다.
물론 이렇게 사용했을 때 따러오는 이점은 처리량 뿐만이 아니라, 장애가 발생했을 때에 대한 고가용성도 얻을 수 있습니다.

 

어플리케이션을 확장하는데에 있어 각 프로세스 또는 머신으로 부하가 분산되는데, 이 부분에 대해 스케일 큐브라는 모델에선

세 가지 측면에서 확장성을 설명합니다.

 

X 축이 가장 간단하고 저렴하며 효과적입니다. 동일한 프로세스 또는 인스턴스가 복제된 수만큼 작업량의 1/n 을 처리한다고 보면 되겠습니다. Node.JS 에선 클러스터 모듈 사용 및 로드 밸런싱이 이 방법에 해당합니다.

 

Y 축은 MSA 의 측면으로 보면 이해가 쉬울 것 같습니다. 기능별로 어플리케이션을 분리한다고 볼 수 있으며, 요즘의 추세는 모놀리식 아키텍쳐보다는 이 방식을 많이 따라가죠.

 

Z 축은 어플리케이션이 전체 데이터의 일부만을 처리하게 하는 것입니다. DB 의 수평 분할 또는 샤딩과 비슷한 개념으로도 볼 수 있을 것 같은데, 예를 들어 사용자를 어떠한 국가나 해시 함수를 사용해 분할하여 어플리케이션들이 분할된 만큼 할당된 일부 사용자에 대한 요청만 처리하도록 나누는 것입니다.

꽤나 복잡한 방식이기 때문에 무턱대고 Z 축으로의 확장을 고려하기보단, 우선 X 와 Y 축에 의한 확장을 완전히 활용한 후 고려해봐야 할 것 같습니다.

 

회사에서는 완벽히 들어맞는 예시는 아니지만 한국으로 들어오는 요청은 한국에 위치한 서버에서, 유럽에서 들어오는 요청은 유럽에 위치한 서버에서 처리하도록 작업했던 경험이 있는데 이게 어떤 면으로는 Z 축으로의 확장 중 일부로 볼 수 있을 것 같습니다.

 

Node.JS 에서 X 축으로의 확장 방법으로 가장 간단한 것은 Cluster module 을 사용하는 것입니다.

 

아마 많은 Node.JS 개발자들이 경험해본 방식일 것으로 생각됩니다. 내장 모듈로 제공되며 기본적으로 round-robin 알고리즘을 통해
모든 worker 로 요청이 균등하게 로드밸런싱됩니다.

 

import os from 'os';

// master || primary
for (let i = 0; i < os.cpus().length; i++) {
  cluster.fork();
}


Cluster module 을 통해 worker 를 생성하는건 위 예시 코드처럼 너무 간단하며 운영하는 서버의 cpu 개수만큼 worker 프로세스를

복제하는 것이 일반적입니다.

 

Cluster module 을 사용해 Node.JS 에서 어플리케이션을 확장하면, 위에서 적었던 것처럼 복원성 이라고 하는 장애가 발생해도
일정 수준의 서비스를 유지하는 부분에 대한 이점을 가져갈 수 있습니다. Cluster module 에선 일부 worker 가 서비스를 할 수 없는
상황이 되었을 때, 새로운 worker 를 아주 쉽게 생성할 수 있습니다.

 

// master || primary
cluster.on('exit', async (worker: ClusterHandlerWorker, code) => {
  if (code === 0 && worker.exitedAfterDisconnect === true) {
    ...
  } else {
    console.info('worker %s died unexpectedly..', worker.process.pid);
    cluster.fork();
  }
});


master 또는 primary 프로세스에서 worker 가 종료된 이벤트를 받았을 때 새로운 worker 를 생성하는 코드입니다.

exit code 가 0 이고(사실 이걸로 충분할 수도 있습니다) worker 의 exitedAfterDisconnect 값이 true 라면, 정상 종료로 판단하지만
그렇지 않은 경우엔 worker 의 비정상 종료로 간주하고 cluster.fork 를 사용해 즉시 새로운 worker 를 생성합니다.

실로 이 방법은 가용성이 그리 나쁘지 않기에 어플리케이션 내부에서 프로세스를 관리하는 코드가 상대적으로 다른 언어에 비해
자주 쓰이는 것 같습니다. 어플리케이션의 graceful exit 를 지원할 때도 Cluster 모듈은 유용하게 사용 가능하니 이 방식으로
Node.JS 어플리케이션을 쉽게 확장해보면 많은 도움이 될 것입니다.

'Backend > Node.js' 카테고리의 다른 글

MSA  (0) 2023.01.14
peer-to-peer load balancing  (0) 2023.01.08
Service Locator (서비스 로케이터)  (0) 2022.12.25
의존성 주입  (0) 2022.12.10
Use Github private packages  (0) 2022.11.20