Node.js 애플리케이션을 위한 5가지 성능 팁

관리자
조회수 486


"#nginx가 노드 서버 앞에 있지 않다면 뭔가 잘못하고 있는 것일 수 있습니다."
- Bryan Hughes on Twitter

Node.js는 전 세계에서 가장 널리 사용되는 프로그래밍 언어인 

JavaScript로 서버 애플리케이션을 만드는 데 사용되는 대표적인 도구입니다. 

웹 서버와 애플리케이션 서버의 기능을 모두 제공하는 

Node.js는 이제 모든 종류의 마이크로서비스 기반 개발 및 제공을 위한 핵심 도구로 간주됩니다. 

(무료 Forrester Node.js 및 NGINX에 대한 보고서 다운로드).

Node.js는 백엔드 애플리케이션 개발을 위해 Java 또는 .NET을 대체하거나 보강할 수 있습니다.

Node.js는 단일 스레드이며 논블럭킹 I/O를 사용하므로 수만 개의 동시 작업을 확장하고 지원할 수 있습니다. 

이러한 아키텍처적 특성을 NGINX와 공유하며, 

NGINX도 해결하기 위해 발명한 C10K 문제(10,000개 이상의 동시 연결 지원)를 해결합니다. 

Node.js는 높은 성능과 개발자 생산성으로 잘 알려져 있습니다.


그렇다면 무엇이 잘못될 수 있을까요?

Node.js에는 몇 가지 약점과 취약점이 있어 Node.js 기반 시스템이 성능 저하 또는 충돌을 일으킬 수 있습니다. 

Node.js 기반 웹 애플리케이션의 트래픽이 급격히 증가할 때 문제가 더 자주 발생합니다.


또한 Node.js는 웹 페이지의 핵심 가변 콘텐츠를 생성하는 애플리케이션 로직을 생성하고 실행하는 데 훌륭한 도구입니다. 

하지만 정적 콘텐츠(예: 이미지 및 JavaScript 파일)를 제공하거나 여러 서버에 걸쳐 부하를 분산하는 데는 적합하지 않습니다.


Node.js를 최대한 활용하려면 정적 콘텐츠를 캐시하고, 여러 애플리케이션 서버 간에 프록시 및 부하를 분산하고, 

클라이언트, Node.js, Socket.IO를 실행하는 서버와 같은 헬퍼 간의 포트 공유를 관리해야 합니다. 

NGINX는 이러한 모든 용도로 사용할 수 있으므로 Node.js 성능 튜닝을 위한 훌륭한 도구입니다.


다음 팁을 사용하여 Node.js 애플리케이션 성능을 개선하세요:

  1. 리버스 프록시 서버 구현
  2. 정적 파일 캐시
  3. 여러 서버에 걸쳐 트래픽 부하 분산
  4. 프록시 웹소켓 연결
  5. SSL/TLS 및 HTTP/2 구현

주: Node.js 애플리케이션 성능에 대한 빠른 해결 방법은 최신 멀티코어 서버를 활용하도록 Node.js 구성을 수정하는 것입니다. 

Node.js 문서를 확인하여 웹 서버의 CPU 수와 동일한 수의 별도의 하위 프로세스를 생성하도록 하는 방법을 알아보세요. 

그러면 각 프로세스가 마술처럼 CPU 중 하나에서만 자리를 잡게 되어 성능이 크게 향상됩니다.

팁 1 - 리버스 프록시 서버 구현하기

는 고성능 사이트의 핵심에서 사용되는 애플리케이션 서버가 들어오는 인터넷 트래픽에 직접 노출되는 것을 볼 때면 

항상 약간 경악을 금치 못합니다. 예를 들어, 많은 WordPress 기반 사이트와 Node.js 사이트가 여기에 포함됩니다.


대부분의 애플리케이션 서버보다 확장성을 위해 설계된 

Node.js는 웹 서버 측에서 많은 인터넷 트래픽을 합리적으로 잘 처리할 수 있습니다. 


하지만 웹 서비스는 Node.js의 존재 이유가 아니며, 실제로 구축된 목적도 아닙니다.

트래픽이 많은 사이트가 있는 경우 애플리케이션 성능을 향상시키는 

첫 번째 단계는 Node.js 서버 앞에 역방향 프록시 서버를 배치하는 것입니다. 

이렇게 하면 Node.js 서버가 인터넷 트래픽에 직접 노출되지 않도록 보호하고 

여러 애플리케이션 서버 사용, 서버 간 로드 밸런싱, 콘텐츠 캐싱을 유연하게 수행할 수 있습니다.


기존 서버를 역방향 프록시 서버로 설정한 후 추가로 사용하는 것은 

전 세계 수천만 개의 웹사이트에서 구현하는 NGINX의 핵심 사용 사례입니다.

NGINX를 Node.js 리버스 프록시 서버로 사용하면 다음과 같은 구체적인 이점이 있습니다:

  • 권한 처리 및 포트 할당 간소화
  • 정적 이미지를 보다 효율적으로 제공하기(다음 팁 참조).
  • Node.js 충돌을 성공적으로 관리하기
  • DoS 공격 완화하기

주: 이 튜토리얼은 Ubuntu 14.04 또는 CentOS 환경에서 NGINX를 리버스 프록시 서버로 사용하는 방법을 설명하며 

Node.js 앞에 NGINX를 배치하는 모든 사용자에게 유용한 개요입니다.

팁 2 - 정적 파일 캐시하기

Node.js 기반 사이트의 사용량이 증가하면 서버에 부하가 걸리기 시작합니다. 

이 시점에서 해야 할 일은 두 가지입니다:

  1. Node.js 서버를 최대한 활용하기
  2. 애플리케이션 서버를 쉽게 추가하고 서버 간에 로드 밸런싱하기

이는 실제로 쉽게 할 수 있습니다. 이전 팁에 설명된 대로 NGINX를 역방향 프록시 서버로 구현하는 것으로 시작하세요. 

이렇게 하면 캐싱, 로드 밸런싱(여러 대의 Node.js 서버가 있는 경우) 등을 쉽게 구현할 수 있습니다.


애플리케이션 컨테이너 플랫폼인 Modulus의 웹사이트에는 NGINX로 

Node.js 애플리케이션 성능을 강화하는 방법에 대한 유용한 글이 있습니다. 

이 글의 작성자의 사이트는 Node.js가 모든 작업을 자체적으로 수행하면서 

초당 평균 900건에 가까운 요청을 처리할 수 있었습니다. 


정적 콘텐츠를 제공하는 역방향 프록시 서버로 NGINX를 사용하면 

동일한 사이트에서 초당 1600개 이상의 요청을 처리하여 

성능이 2배 가까이 향상되었습니다.

성능이 두 배로 향상되면 사이트 디자인 검토(및 개선), 애플리케이션 코드 최적화, 추가 애플리케이션 서버 배포 등 

추가 성장을 위한 추가 조치를 취할 수 있는 시간을 확보할 수 있습니다.

다음은 모듈러스에서 실행되는 웹사이트에서 작동하는 구성 코드입니다:


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;
    }
}


패트릭 노멘슨이 작성한 이 글에서는 Node.js 애플리케이션인 Ghost 오픈 소스 블로그 플랫폼에서 실행되는 

개인 블로그의 정적 콘텐츠를 캐싱하는 방법을 자세히 설명합니다. 

일부 세부 사항은 Ghost에만 해당되지만 다른 Node.js 애플리케이션에 코드의 대부분을 재사용할 수 있습니다.

예를 들어 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 플러스 관리자 가이드를 참조하세요. 

관리자 가이드에는 구성 지침, 파일 찾기 시도의 성공 또는 실패에 대응하는 여러 옵션, 

더 빠른 성능을 달성하기 위한 최적화 접근 방식이 포함되어 있습니다.


NGINX 서버에서 정적 파일을 캐싱하면

Node.js 애플리케이션 서버의 작업이 크게 오프로드되어 훨씬 더 높은 성능을 달성할 수 있습니다.

팁 3 - Node.js 로드 밸런서 구현하기

Node.js 애플리케이션의 높은 성능, 즉 거의 무제한에 가까운 성능의 진정한 핵심은 

여러 애플리케이션 서버를 실행하고 모든 서버에서 부하를 분산하는 것입니다.


Node.js 로드 밸런싱은 특히 까다로울 수 있는데, 그 이유는 웹 브라우저에서 실행되는 

JavaScript 코드와 Node.js 애플리케이션 서버에서 실행되는 JavaScript 코드 간에 

데이터 교환 매체로 JSON 객체를 사용하여 높은 수준의 상호 작용이 가능하기 때문입니다. 

이는 특정 클라이언트 세션이 특정 애플리케이션 서버에서 지속적으로 실행된다는 것을 의미하며, 

여러 애플리케이션 서버에서 세션 지속성은 본질적으로 달성하기 어렵습니다.


인터넷과 웹의 주요 장점 중 하나는 요청된 파일에 액세스할 수 있는 모든 서버에서 

클라이언트 요청을 처리할 수 있는 높은 수준의 상태 비저장성입니다. 

Node.js는 상태 비저장성을 파괴하며 동일한 서버가 특정 클라이언트의 요청에 

일관되게 응답하는 상태 저장 환경에서 가장 잘 작동합니다.


이 요구 사항은 NGINX 오픈 소스보다는 < data-dl-uid="131">NGINX Plus가 가장 잘 충족할 수 있습니다. 

두 버전의 NGINX는 매우 유사하지만 한 가지 큰 차이점은 서로 다른 부하 분산 알고리즘을 지원한다는 점입니다.

NGINX는 상태 비저장 로드 밸런싱 방법을 지원합니다:

  • 라운드 로빈 - 새 요청이 목록의 다음 서버로 이동합니다.
  • 최소 연결 - 활성 연결이 가장 적은 서버로 새 요청을 보냅니다.
  • IP 해시 - 클라이언트 IP 주소의 해시에 할당된 서버로 새 요청이 전송됩니다.

이 방법 중 하나인 IP 해시는 특정 클라이언트의 요청을 동일한 서버로 안정적으로 전송하므로 

Node.js 애플리케이션에 유리합니다. 


그러나 IP 해시는 부하 분산 기술에 대한 이 블로그 게시물에 설명된 대로 

한 서버가 다른 서버를 희생시키면서 불균형한 수의 요청을 쉽게 수신할 수 있습니다. 

이 방법은 서버 리소스 전반에 걸쳐 잠재적으로 최적이 아닌 요청을 할당하는 대신 상태 저장성을 지원합니다.


NGINX와 달리 NGINX Plus는 세션 지속성를 지원합니다. 

세션 지속성을 사용하면 동일한 서버가 특정 클라이언트의 모든 요청을 안정적으로 수신합니다. 

클라이언트와 서버 간의 상태 저장 통신을 지원하는 Node.js와 고급 로드 밸런싱 기능을 갖춘 NGINX의 장점은 모두 극대화됩니다.

따라서 여러 Node.js 서버에 걸쳐 로드 밸런싱을 지원하기 위해 NGINX 또는 NGINX Plus를 사용할 수 있습니다. 

그러나 NGINX Plus를 사용해야만 최대 로드 밸런싱 성능과 Node.js 친화적인 상태 저장성을 모두 달성할 수 있습니다. 

애플리케이션 상태 확인 및 모니터링 기능은 NGINX Plus에 내장되어 있어 여기에서도 유용합니다.

또한 NGINX는 서버가 서비스 중단 요청을 받은 후 

애플리케이션 서버가 현재 세션을 정상적으로 완료할 수 있도록 하는 세션 드래닝을 지원합니다.

팁 4 -프록시 웹소켓 연결

HTTP는 모든 버전에서 클라이언트가 서버에 파일을 요청하는 "풀" 통신을 위해 설계되었습니다. 

웹소켓은 클라이언트가 요청하지 않은 파일을 서버가 선제적으로 보낼 수 있는 "푸시" 및 "푸시/풀" 통신을 가능하게 하는 도구입니다.

웹소켓 프로토콜은 전송되는 데이터의 양을 줄이고 지연 시간을 최소화하면서 

클라이언트와 서버 간의 보다 강력한 상호 작용을 쉽게 지원할 수 있게 해줍니다. 


필요한 경우 클라이언트와 서버 모두 필요에 따라 요청을 시작하고 수신하는 전이중 연결이 가능합니다.

웹소켓 프로토콜은 강력한 자바스크립트 인터페이스를 갖추고 있으므로 애플리케이션 서버로서 Node.js에 적합하며, 

트랜잭션 규모가 적당한 웹 애플리케이션의 경우 웹 서버로도 적합합니다. 

트랜잭션 볼륨이 증가하면 클라이언트와 Node.js 웹 서버 사이에 NGINX를 삽입하고, 


여러 애플리케이션 서버 간에 정적 파일 캐시 및 로드 밸런스에 NGINX 또는 NGINX Plus를 사용하는 것이 합리적입니다.

Node.js는 종종 Node.js 애플리케이션과 함께 사용하기 위해 널리 사용되는 웹소켓 API인 Socket.IO와 함께 사용됩니다. 

이로 인해 포트 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(); // 여기에 초기화 코드. // ]]> </script>

NGINX 구성을 포함한 전체 지침은 NGINX 및 NGINX 사용과 Node.js 및 Socket.IO에 대한 블로그 게시물을 참조하세요. 

이러한 종류의 웹 애플리케이션에 대한 잠재적인 아키텍처 및 인프라 문제에 대한 

자세한 내용은 실시간 웹 애플리케이션 및 WebSocket에 대한 블로그 게시물을 참조하세요.

팁 5 - SSL/TLS 및 HTTP/2 구현하기

점점 더 많은 사이트에서 사이트의 모든 사용자 상호 작용을 보호하기 위해 SSL/TLS를 사용하고 있습니다. 

이러한 전환 여부와 시기는 사용자가 결정할 수 있지만, 전환할 경우 NGINX는 두 가지 방법으로 전환을 지원합니다:

  1. NGINX를 역방향 프록시로 설정한 후 NGINX에서 클라이언트에 대한 SSL/TLS 연결을 종료할 수 있습니다. Node.js 서버는 암호화되지 않은 요청과 콘텐츠를 NGINX 역방향 프록시 서버와 주고받습니다.
  2. 초기 징후에 따르면 새로운 버전의 HTTP 프로토콜인 HTTP/2를 사용하면 SSL/TLS 사용으로 인한 성능 저하를 상당 부분 또는 완전히 상쇄할 수 있습니다. NGINX는 HTTP/2를 지원하며 SSL/TLS와 함께 HTTP/2를 종료할 수 있으므로 Node.js 애플리케이션 서버를 변경할 필요가 없습니다.

구현 단계 중에는 Node.js 구성 파일에서 URL 업데이트, 

NGINX 구성에서 보안 연결 설정 및 최적화, 원하는 경우 SPDY 또는 HTTP/2 사용 등이 있습니다. 

HTTP/2 지원을 추가하면 HTTP/2를 지원하는 브라우저 버전은 새 프로토콜을 사용하여 

애플리케이션과 통신하고, 이전 브라우저 버전은 계속 HTTP/1.x를 사용합니다.

http2-backward-compatibility.png

다음 구성 코드는 여기에 설명된 대로 SPDY를 사용하는 Ghost 블로그용입니다. 

여기에는 OCSP 스테이플링과 같은 고급 기능이 포함되어 있습니다. 

OCSP 스테이플링 옵션을 포함하여 SSL 종료에 NGINX를 사용할 때 고려해야 할 사항은 여기를 참조하세요. 

동일한 주제에 대한 일반적인 개요는 여기를 참조하세요.

지금 또는 2016년 초에 SPDY 지원이 중단될 때 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;
    }
} 

결론

이 블로그 게시물에서는 Node.js 애플리케이션에서 수행할 수 있는 가장 중요한 몇 가지 성능 향상에 대해 설명합니다. 

이 글에서는 역방향 프록시 서버, 정적 파일 캐시, 로드 밸런싱, 웹소켓 연결 프록시, SSL/TLS 및 HTTP/2 프로토콜 종료를 위해 

NGINX를 사용하여 Node.js와 함께 애플리케이션 조합에 NGINX를 추가하는 데 중점을 둡니다.

새로운 마이크로서비스 친화적인 애플리케이션을 만들거나

Java 또는 Microsoft.NET을 사용하는 기존 SOA 기반 애플리케이션에 유연성과 기능을 추가하는 방법으로 

NGINX와 Node.js 의 조합이 널리 인정받고 있습니다. 이 게시물은 Node.js 애플리케이션을 최적화하고, 

원하는 경우 Node.js와 NGINX 간의 파트너십을 실현하는 데 도움이 됩니다.


위 내용과 같이 NGINX Plus를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요