고성능, 확장가능한 nodejs 앱을 위한 Good Practice [Part1/3 번역]

QQQ
nodejs backend
Published in
7 min readMar 21, 2021

“It is transtlated from virgafox’s post

이번 시리즈에서는 3개의 글을 통해 Node.js 백엔드 어플리케이션 개발의 모범 예시들을 살펴볼 것입니다.

이 시리즈는 Node 튜토리얼 글이 아닙니다. 이번 시리즈는 이미 Node.js의 기본 개념들에 익숙한 개발자, 혹은 아키텍쳐를 향상시키는데에 힌트를 얻고 싶은 분들을 위해서 작성되었습니다.

더 적은 리소스로 최상의 결과를 얻기 위해 주된 초점은 “효율성”과 “성능”입니다.

웹 어플리케이션의 처리량을 향상시키는 단순한 방법은 정말 말그대로 확장시키는 것입니다. 여러 곳에 서버를 띄워두고 여러 서버에서 균형있게, 한곳에 몰리지 않게 처리한다면 성능을 향상시킬 수 있습니다. 이번 글은 “어떻게 Node.js 어플리케이션을 수평적으로 확장하는 지” 알아볼 것입니다.

애플리케이션 확장을 진행할 때, 프로그램의 상태부터 인증까지 여러 상황들을 신중히 파악해야합니다. 두번 째 글에서는 Node.js 애플리케이션을 확장할 때 반드시 고려해야하는 것들에 대해 이야기하겠습니다.

필수적인 것들부터 살펴본 뒤에는 알아두면 좋을만한 모범 사례들을 3번째 글에서 살펴볼 것입니다. 대표적으로 api 분산시키기, worker process, 우선순위 큐 적용하기, 주기적인 작업 관리(ex, cron processes)와 같은 작업들이 있습니다. 이와 같은 작업들의 특징은 N개의 프로세스, 머신으로 확장할 때 확장된 갯수만큼 실행되면 안되는 작업들이라는 점입니다.

Chapter 1- Node.js 어플리케이션 수평 확장

수평 확장은 수많은 요청들을 관리하기 위해 어플리케이션 인스턴스를 복제하는 것을 말합니다. 이 작업은 한개의 머신(multi core)에서 이루어질수도 있고 서로 다른 머신들 사이에서 진행될 수도 있습니다.

수직 확장은 하나의 머신의 성능을 향상시키는 것을 의미합니다. 수직 확장은 코드 측면에서의 일이 아니라, CPU, RAM 교체와 같은 하드웨어적인 작업을 의미합니다.

Multiple processes on same machine

처리량을 향상시키는 일반적인 방법 중 하나는 각 코어마다 프로세스를 만드는 것입니다. 이 방법으로 이미 효육적인 Node.js의 동기적 요청 관리(event driven, non-blocking I/O)를 배로 향상시키거나 병렬화 시킬 수 있게 됩니다.(주의: 프로세스를 코어의 갯수보다 많이 만드는 것은 좋은 방법이 아닙니다. 왜냐하면 OS가 프로세스간의 CPU time을 균형화시킬수 있기 때문입니다.)

하나의 머신에서 확장하는 방법은 여러가지가 있습니다. 그러나 일반적인 컨셉은 하나의 포트에 반응하는 여러개의 프로세스를 가지고, 내부적으로 들어오는 요청들을 분산시킬수 있는 로드밸런서를 이용하는 것입니다.

이 방법을 이용하는 방식은 크게 Node의 내재되어있는 1. cluster mode 혹은 외부 프로그램인 2. PM2 cluster를 이용하는 것입니다.

Navive cluster mode

Node.js의 cluster module은 한 개의 머신에서 활용할 수 있는 가장 기본적인 확장 방법입니다.(https://nodejs.org/api/cluster.html). 각 코어마다의 프로세서에서 하나의 인스턴스(“master”)는 다른 child process들(“worker”)을 만드는 역할을 합니다. 그리고 들어오는 요청들은 round-robin 전략에 따라 child processes(“worker”)에 분산됩니다.

이 방식의 대표적인 단점은 프로세스들을 코드로 직접 관리하여야 한다는 점입니다. if-else문을 통해 코드내에서 직접 관리하여야하며, 상황에 따라 즉석에서 프로세스의 숫자를 수정할 수 없습니다.

아래 예제는 공식 문서에서 가져온 것입니다:

PM2 Cluster mode

만약 프로세스 관리를 위해 PM2를 이용한다면(나도 추천합니다!), PM2에는 아주 쓸만한 클러스터 기능이 있습니다. 그 기능은 모든 코어에 걸쳐 클러스터 모듈을 걱정할 필요 없이 프로세스를 확장시킬수 있습니다. PM2 daemon은 “master” 프로세스 역할을 하여 N개의 프로세스들을 round-robin 방식으로 worker 프로세스로 생성합니다.

이 방식을 이용하면, 당신이 원하는 방식으로 싱글 코어 어플리케이션을 만들듯이 코드를 작성하면 됩니다. 그 코드를 바탕으로 PM2가 알아서 멀티 코어에도 문제 없이 작동하게 해줍니다.(다음 글에서 조심해야할 것들을 살펴볼것입니다.)

어플리케이션이 cluster mode로 실행되면 단순히 인스턴스의 갯수를 즉석에서 “pm2 scale”를 이용하여 조절할 수 있습니다. 그리고 “0-second-downtime”으로 리로드됩니다. 항상 하나 이상의 프로세스를 유지하기 위해 프로세스가 연속적으로 다시 실행되며 항상 온라인 상태를 유지시킵니다.

프로세스 관리자로써 PM2는 프로세스들이 제대로 작동하게 하기 위해 프로세스가 멈추거나 에러가 발생했을 때 알아서 재시작시킵니다.

만약 PM2가 제공해주는 성능 이상의 확장이 필요하다면 머신을 추가해야할 것입니다.

Multiple machines with network load balancing

여러개의 머신에서 확장하는 방식은 여러개의 코어에서의 확장과 유사합니다. 각각의 머신은 하나 이상의 프로세서들을 가지게됩니다. 그리고 단 하나의 밸런서를 통해 요청들을 각각의 머신에 분산시키게 됩니다. 여러 머신을 컨트롤 하는 밸런서가 하나 존재한다는 것입니다.

특정 노드로 요청이 들어오면, 내부 밸런서는 특정 프로세스로 요청을 전달합니다.

네트워크 밸런서는 여러 방법으로 배포가능합니다. 만약 AWS를 이용한다면 ELB(Elastic Load Balancer)를 이용하는 것이 좋은 선택이 될 수 있습니다. ELB는 자동 확장 기능을 제공하고 설정 방식도 쉬운 편입니다.

만약 올드스쿨 방식이 좋으시다면, 하나의 머신을 따로 배포하여 NGINX를 직접 설치하는 방법도 있습니다. 이 방법을 흔히 “리버스 프록시”라고 합니다. 아래는 리버스 프록시 예시입니다:

이 방식으로 로드벨런서는 어플리케이션의 단 하나뿐인 진입점이 됩니다. 만약 단 하나의 진입점만을 유지하는 것이 불안하다면 여러개의 로드밸런서를 배포하여도 됩니다.

밸런서간의 트래픽을 분산시키기 위해 어플리케이션의 메인 도메인에 복수개의 DNS “A” records를 추가할 수 있습니다. A record들을 추가하면 여러 도메인으로 요청을 받을 수 있고 각 도메인에 따라 요청을 특정 벨런서에 분산시킬 수 있습니다.

다음 단계

이번 글에서 우리는 어떻게 Node.js 앱을 여러 단계에서 확장시킬수 있는지 알아봤습니다. 다음 편에서는 이번편에서 알아본 확장 방법을 실행하기 위해 어떤 사전 조건들이 필요한지 알아볼 것입니다.

원본은 이 글입니다. [ 링크 ]

--

--