Node.js 애플리케이션을 위한 5가지 성능 팁
Node.js 는 세계에서 가장 인기있는 프로그래밍 언어인 Javascript로 서버 애플리케이션을 만드는데 필수적인 도구입니다. 웹 서버와 애플리케이션 서버 기능을 모두 제공하기 때문에, Node.js는 이제 모든 종류의 Microservices 기반 개발과 배포에 필수적인 도구로 간주됩니다.
Node.js는 백엔드 애플리케이션 개발을 할 때 Java 또는 .NET을 대체하거나 보완할 수 있습니다.
Node.js는 싱글 스레드이며 논블로킹(non-blocking) I/O를 사용하여 수십만 개의 동시 작업을 지원하고 확장할 수 있습니다.
이러한 아키텍처적 특성은 NGINX와 공유됩니다. NGINX도 C10K 문제 (10,000개 이상의 동시 연결 지원) 를 해결하기 위해 개발되었습니다. Node.js는 높은 성능과 개발자 생산성으로 잘 알려져 있습니다.
그렇다면 단점이 없는 걸까요?
Node.js는 성능 저하나 심지어 충돌로 이어질 수 있는 몇 가지 약점과 취약점을 가지고 있습니다.
특히 Node.js 기반 웹 애플리케이션이 급격한 트래픽 증가를 경험할 때 문제가 더 자주 발생합니다.
또한, Node.js는 웹 페이지의 핵심 가변 콘텐츠를 생성하고 실행하는데 탁월한 도구입니다.
그러나 정적 콘텐츠, 예를 들어 이미지 및 JavaScript 파일을 서비스 하거나 여러 서버 간의 로드 밸런싱에는 적합하지 않습니다.
Node.js를 최대한 활용하기 위해서는 정적 콘텐츠를 캐시하고, 다중 애플리케이션 서버 간 프록시 및 로드 밸런싱을수행하며, 클라이언트, Node.js 및 Socket.IO 서버와 같은 도우미 간 포트 충돌을 관리해야 합니다.
이러한 모든 목적에 적합한 Node.js 성능 튜닝 도구가 바로 NGINX입니다.
목차
1. Reverse Proxy 서버 구현
2. 정적 파일 캐시
3. Node.js 로드 밸런서 구현
4. Node.js Proxy WebSocket 연결
5. Node.js SSL/TLS 및 HTTP/2 구현
6. 결론
1. Reverse Proxy 서버 구현
NGINX는 고성능 사이트의 핵심으로 직접 노출되는 애플리케이션 서버를 볼 때마다 항상 조금 놀라곤 합니다.
이는 예를 들어 많은 WordPress 기반 사이트 및 Node.js 사이트를 포함합니다.
Node.js는 대부분의 애플리케이션 서버보다 확장성에 대한 설계가 더 잘 되어 있으며, 웹 서버 측면에서도 상당량의 인터넷 트래픽을 처리할 수 있습니다. 하지만 웹 서빙 (Web Serving)이 Node.js의 존재 이유는 아닙니다.
높은 트래픽의 사이트를 운영해야 하는 경우, 애플리케이션의 성능을 향상시키는 첫 번째 단계는 Node.js 서버 앞에 리버스 프록시 (Reverse Proxy) 서버를 배치하는 것입니다. 이는 Node.js 서버가 직접 인터넷 트래픽에 노출되지 않도록 보호하며, 여러 애플리케이션 서버를 사용하고, 서버 간의 부하 분산 및 콘텐츠 캐싱을 유연하게 수행할 수 있도록 합니다.

기존 서버를 Reverse Proxy 서버로 설정하고, 이어서 추가적인 용도로 NGINX를 사용하는 것은 NGINX의 핵심적이고 가장 모범적인 사례 중 하나이며, 이미 전 세계 수 백만 개의 웹 사이트에서 구현되고 있습니다.
NGINX를 Node.js Reverse Proxy 서버로 사용하면 다음과 같은 특별한 이점이 있습니다.
- 권한 처리 및 포트 할당 간소화
- 정적 이미지를 보다 효율적으로 제공
- Node.js 충돌을 성공적으로 관리
- DoS 공격 완화
2. 정적 파일 캐시
Node.js 기반 사이트의 사용량이 증가함에 따라 서버가 부담을 느끼기 시작합니다. 이 시점에서 수행해야 하는 두 가지 작업이 있습니다.
- Node.js 서버 최대한 활용하기
- 애플리케이션 서버를 쉽게 추가하고 서버 간 로드 균형 조정
이 작업은 실제로 쉽게 진행할 수 있습니다. NGINX를 Reverse Proxy 서버로 구현하면 캐싱, 로드 밸런싱 (여러 개의 Node.js 서버가 있는 경우), 기타 기능을 쉽게 구현할 수 있습니다.
애플리케이션 컨테이너 플랫폼인 Modulus 웹사이트에서는 Node.js 애플리케이션 성능을 NGINX로 최적화하는 유용한 글을 제공하고 있습니다. Node.js만으로 모든 작업을 처리할 때, 작성자 사이트는 평균 거의 900개의 요청을 처리할 수 있었습니다. 하지만 NGINX를 Reverse proxy 서버로 사용하여 정적 콘텐츠를 제공할 경우, 동일한 사이트는 초당 1600개 이상의 요청을 처리할 수 있었습니다. 이는 약 2배의 성능 향상을 의미합니다.
성능이 2배로 향상되면 사이트 디자인을 검토하고 개선하거나, 애플리케이션 코드를 최적화하거나, 추가 애플리케이션 서버를 배포하는 등의 추가적인 성장 대책을 마련할 시간이 생깁니다.
다음은 Modulus에서 실행되는 웹사이트에서 작동하는 구성 코드입니다.
server {
listen 80;
server_name static-test-47242.onmodulus.net;
root /mnt/app;
index index.html index.htm;
location /static/ {
try_files $uri $uri/ =404;
}
location /api/ {
proxy_pass http://node-test-45750.onmodulus.net;
}
}
예를 들어, NGINX의 location 블록에서는 캐싱되지 않도록 몇 가지 콘텐츠를 제외시키는 것이 좋습니다. 예를 들어 블로그 플랫폼의 관리 인터페이스를 캐시하고 싶지 않을 것입니다. 다음은 Ghost 관리 인터페이스의 캐싱을 비활성화하는 구성 코드입니다.
location ~ ^/(?:ghost|signout) {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://ghost_upstream;
add_header Cache-Control "no-cache, private, no-store,
must-revalidate, max-stale=0, post-check=0, pre-check=0";
}
NGINX STORE 블로그 포스트에서는 정적 컨텐츠 제공에 대한 일반 정보를 얻을 수 있습니다. 구성 지침, 파일 검색 시 성공 또는 실패한 경우에 대한 여러 옵션, 그리고 더 빠른 성능을 위한 최적화 접근 방법이 포함되어 있습니다.
NGINX 서버에서 정적 파일을 캐싱하면 Node.js 애플리케이션 서버의 작업이 크게 줄어들어 더 높은 성능을 달성할 수 있습니다.
3. Node.js 로드 밸런서 구현
많은 Node.js 애플리케이션의 높은(거의 무제한) 성능을 달성하는 실제 핵심은 여러 애플리케이션 서버를 실행하고 모든 서버에 걸쳐 부하를 분산시키는 것입니다.
Node.js 로드 밸런싱은 특히 까다로울 수 있습니다. 왜냐하면 Node.js는 웹 브라우저에서 실행되는 JavaScript 코드와 Node.js 애플리케이션 서버에서 실행되는 JavaScript 코드 간에 높은 수준의 상호작용을 가능하게 합니다. 그리고 데이터 교환의 매개체로 JSON 객체를 사용합니다. 이는 특정 클라이언트 세션이 특정 애플리케이션 서버에서 계속 실행되어야 하며, 다중 애플리케이션 서버에서 세션 지속성을 유지하는 것이 내재적으로 어렵다는 것을 의미합니다.
인터넷과 웹의 주요 이점 중 하나는 높은 상태비(statelessness) 수준인데, 이는 클라이언트 요청이 요청된 파일에 접근할 수 있는 어떤 서버에서든 처리될 수 있다는 능력을 말합니다. 하지만 Node.js는 이러한 상태비를 뒤엎고, 동일한 서버가 특정 클라이언트 요청에 일관적으로 응답하는 상태 유지환경(stateful environment)에서 가장 잘 작동합니다.
이러한 요구 사항은 NGINX 오픈소스보다는 NGINX Plus를 사용하여 가장 잘 충족될 수 있습니다. NGINX의 두 버전은 매우 유사하지만, 하나의 주요한 차이점은 다른 로드 밸런싱 알고리즘을 지원하는 것입니다.
NGINX는 상태를 유지하지 않는(stateless) 로드 밸런싱 방법을 지원합니다.
- 라운드 로빈 – 새 요청이 목록의 다음 서버로 이동합니다.
- 최소 연결 – 새 요청은 활성 연결이 가장 적은 서버로 이동합니다.
- IP Hash – 새로운 요청은 클라이언트 IP 주소의 해시에 할당된 서버로 이동합니다.
NGINX의 로드 밸런싱 알고리즘 중 IP Hash는 특정 클라이언트의 요청을 항상 같은 서버로 전송하여 Node.js 애플리케이션에 이점을 제공하지만, 이 방법은 다른 서버에 비해 한 서버가 지나치게 많은 요청을 받는 결과를 초래할 수 있으며, 서버 자원을 비효율적으로 할당할 수 있다는 문제점이 있습니다.
NGINX와 달리 NGINX Plus는 세션 지속성을 지원합니다. 세션 지속성을 사용하면 동일한 서버에서 특정 클라이언트의 모든 요청을 안정적으로 수신할 수 있습니다. 클라이언트와 서버 간 상태 유지 통신을 사용하는 Node.js의 이점과 고급 로드 밸런싱 기능을 제공하는 NGINX Plus의 이점이 모두 극대화됩니다.
여러 개의 Node.js 서버에 대한 로드 밸런싱을 지원하기 위해 NGINX 또는 NGINX Plus를 사용할 수 있습니다.
그러나 NGINX Plus만 사용하면 최대 로드 밸런싱 성능과 Node.js 친화적인 상태 유지를 모두 달성할 수 있습니다.
NGINX Plus에 내장된 애플리케이션 Health Checks 및 모니터링 기능도 유용하게 사용됩니다.
NGINX Plus는 세션 드레인 (session draining)도 지원합니다. 이는 서버가 서비스에서 자체를 제외하도록 요청한 후 현제 세션을 정상적으로 완료할 수 있도록 합니다.
4. Node.js Proxy WebSocket 연결
HTTP는 모든 버전에서 “pull” 통신을 위해 설계되었습니다. 즉, 클라이언트가 서버에서 파일을 요청하는 방식입니다. WebSocket은 “push” 및 “push/pull” 통신을 가능하게 하는 도구입니다. 이는 서버가 클라이언트가 요청하지 않은 파일을 미리 보낼 수 있는 방식입니다.
WebSocket 프로토콜은 클라이언트와 서버 간 더 견고한 상호작용을 지원하면서 전송되는 데이터 양을 줄이고 대기 시간을 최소화하는 데 도움이 됩니다. 필요할 때, 클라이언트와 서버가 모두 요청을 초기화하고 받는 전이중(Full Duplex) 연결이 가능합니다.
WebSocket 프로토콜은 강력한 JavaScript 인터페이스를 가지고 있어서 Node.js 어플리케이션 서버로서 자연스러운 매칭이 가능하며, 중간 규모의 트랜잭션 양을 처리하는 웹 서버로도 적합합니다. 그러나 트랜잭션 양이 많아지면, 정적 파일 캐싱 및 다중 어플리케이션 서버 간 로드 밸런싱을 위해 클라이언트와 Node.js 웹 서버 사이에 NGINX를 삽입하는 것이 좋습니다.
Node.js는 Socket.IO와 함께 사용되는 경우가 많습니다. Socket.IO는 Node.js 어플리케이션과 함께 사용하기에 매우 인기 있는 WebSocket API입니다. 이로 인해 80번 포트 (HTTP) 또는 443번 포트 (HTTPS)가 혼잡해지는 경우가 생기는데, 이 문제를 해결하기 위해서는 Socket.IO 서버로 프록시를 설정해야 합니다. 이때, 앞서 설명한 것과 같이 NGINX를 프록시 서버로 사용하면, 정적 파일 캐싱, 로드 밸런싱 등 추가적인 기능을 얻을 수 있습니다.

다음은 포트 5000에서 수신 대기하는 프록시 서버(웹 서버가 아님)로서 요청을 올바른 포트로 라우팅하는 노드 애플리케이션 파일인 server.js의 코드입니다.
var io = require('socket.io').listen(5000);
io.sockets.on('connection', function (socket) {
socket.on('set nickname', function (name) {
socket.set('nickname', name, function () {
socket.emit('ready');
});
});
socket.on('msg', function () {
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
});
});
});
index.html 파일에서는 서버 애플리케이션에 연결하고 애플리케이션과 사용자 브라우저 간에 WebSocket을 인스턴스화하기 위해 다음 코드를 추가하세요.
<script src="/socket.io/socket.io.js"></script>
<script>// <![CDATA[
var socket = io(); // your initialization code here.
// ]]>
</script>
5. Node.js SSL/TLS 및 HTTP/2 구현
최근에는 많은 사이트들이 SSL/TLS를 사용하여 사이트에서 이루어지는 모든 사용자 상호작용을 보안하고 있습니다.
이러한 전환이 필요한지, 그리고 언제 이루어져야 하는지는 여러분의 결정입니다.
하지만 이전이 필요한 경우, NGINX는 다음 두 가지 방법으로 전환을 지원합니다.
- NGINX는 리버스 프록시로 설정한 후 클라이언트로의 SSL/TLS Termination을 종료할 수 있습니다. Node.js 서버는 NGINX 리버스 프록시 서버와 암호화되지 않은 요청 및 컨텐츠를 송수신합니다.
- HTTP 프로토콜의 새 버전인 HTTP/2의 사용은 SSL/TLS의 성능 하락을 크게 완화하거나 완전히 해결할 수 있다는 초기 조사 결과가 있습니다. NGINX는 HTTP/2를 지원하며, Node.js 애플리케이션 서버에 변경 사항이 필요하지 않도록 SSL/TLS와 함께 HTTP/2를 종료할 수 있습니다.
구현 단계 중 하나는 Node.js 구성 파일에서 URL을 업데이트 하는 것입니다.
또한 NGINX 구성에서 안전한 연결을 설정하고 최적화 해야합니다. 이를 위해 SPDY나 HTTP/2를 사용할 수 있습니다.
HTTP/2 지원을 추가하면 HTTP/2를 지원하는 브라우저 버전은 새 프로토콜을 사용하여 애플리케이션과 통신하고, 이전 브라우저 버전은 여전히 HTTP/1.x 를 사용합니다.

다음 구성 코드는 여기에서 설명된 대로 SPDY를 사용하는 Ghost 블로그를 위한 것입니다.
이 구성 코드에는 OSCP 스테이플링과 같은 고급 기능이 포함되어 있습니다. SSL 해제에 NGINX를 사용하는 경우 OSCP 스테이플링 옵션을 포함하여 여기에서 설명된 고려 사항을 참조 하십시오.
Node.js 애플리케이션을 구성하고 SPDY에서 HTTP/2로 업그레이드 하는데 필요한 수정은 매우 작습니다.
server {
server_name domain.com;
listen 443 ssl spdy;
spdy_headers_comp 6;
spdy_keepalive_timeout 300;
keepalive_timeout 300;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_certificate /etc/nginx/ssl/domain.crt;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 24h;
ssl_buffer_size 1400;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/trust.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';
add_header X-Cache $upstream_cache_status;
location / {
proxy_cache STATIC;
proxy_cache_valid 200 30m;
proxy_cache_valid 404 1m;
proxy_pass http://ghost_upstream;
proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
proxy_hide_header X-powered-by;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
expires 10m;
}
location /content/images {
alias /path/to/ghost/content/images;
access_log off;
expires max;
}
location /assets {
alias /path/to/ghost/themes/uno-master/assets;
access_log off;
expires max;
}
location /public {
alias /path/to/ghost/built/public;
access_log off;
expires max;
}
location /ghost/scripts {
alias /path/to/ghost/core/built/scripts;
access_log off;
expires max;
}
location ~ ^/(?:ghost|signout) {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://ghost_upstream;
add_header Cache-Control "no-cache, private, no-store,
must-revalidate, max-stale=0, post-check=0, pre-check=0";
proxy_set_header X-Forwarded-Proto https;
}
}
6. 결론
이 블로그 포스트는 Node.js 애플리케이션의 가장 중요한 성능 향상 기술 중 일부를 설명했습니다.
이는 NGINX를 Node.js와 함께 사용하여 애플리케이션 믹스에 추가함으로써 이루어지며, NGINX를 Reverse Proxy 서버로 사용하여 정적 파일을 캐시하고 로드 밸런싱, WebSocket 연결 프록시 및 SSL/TLS 및 HTTP/2 프로토콜 종료에 사용합니다.
NGINX와 Node.js의 조합은 Java 또는 Microsoft .NET를 사용하는 기존 SOA 기반 애플리케이션에 유연성과 기능을 추가하거나 새로운 Microservices 친화적인 애플리케이션을 만드는 방법으로 널리 인정되고 있습니다.
이 포스트는 Node.js 애플리케이션을 최적화 하는데 도움이 되며, 선택하는 경우 Node.js와 NGINX의 파트너십을 실현할 수 있도록 지원합니다.
NGINX Plus의 30일 무료 평가판을 시작하거나 NGINX Plus 및 NGINX가 Node.js 애플리케이션의 성능을 개선하는 방법에 대해 자세히 알아보기 위해 문의하십시오. 또한 아레 뉴스레터를 구독하여 NGINX 및 NGINX Plus에 대한 여러 정보들을 발 빠르게 받아보세요.
댓글을 달려면 로그인해야 합니다.