[번역]Inside NGINX: How We Designed for Performance & Scale

Posted by yunki kim on August 11, 2022

  Nginx는 설계 방식 덕분에 우수한 웹 퍼포먼스를 보이고 있다. 수 많은 WS와 WAS가 스레드 또는 프로세스 기반 아키텍처를 사용하는 반면, nginx는 접속을 수백 수천개의 동시 접속으로 확장할 수 있는 정교한 event-driven 아키텍처를 사용하고 있다.

  Inside NGINX 는 고수준 프로세스 아키텍처부터 저수준으로 내려가면서 Nginx가 여러개의 커넥션을 싱글 프로세스로 처리하는 과정을 서술하고 있다. 이 글은 한발 더 나아가 더 디테일하게 Nginx의 동작 과정을 서술하려 한다.

Setting the Scene - The NGINX Process Model

  

    Nginx의 설계를 이해하기 위해선 Nginx가 어떻게 동작하는지 알아야 한다. Nginx는 하나의 마스터 프로세스와 워커프로세스, 헬퍼 프로세스들로 이루어져 있다.

1
2
3
4
5
6
7
8
9
10
11
# service nginx restart
* Restarting nginx
# ps -ef --forest | grep nginx
root     32475     1  0 13:36 ?        00:00:00 nginx: master process /usr/sbin/nginx 
                                                -/etc/nginx/nginx.conf
nginx    32476 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32477 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32479 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32480 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32481 32475  0 13:36 ?        00:00:00  _ nginx: cache manager process
nginx    32482 32475  0 13:36 ?        00:00:00  _ nginx: cache loader process
cs

  위 예제에서는 쿼드코어 서버에서 nginx가 동작하고 있다. Nginx의 마스터 프로세스는 4개의 워커 프로세스와 disck cache를 완리하는 한 쌍의 cache helper process를 생성한다.

Why Is Architecture Important?

  모든 유닉스 애플리케이션의 근간은 스레드 또는 프로세스이다. 스레드, 프로세스는 운영체제가 CPU 코어에서 실행되게 스케줄할 수 있는 명령어들의 집합이다. 대부분의 복잡한 애플리케이션들이 멀티 쓰레드, 멀티 프로세스를 사용하는 이유는 다음과 같다.

    - 더 많은 컴퓨팅 코어를 동시에 사용할 수 있다.

    - 스레드와 프로세스를 통해 간단하게 동시 연산을 할 수 있다.

  프로세스와 스레드는 리소스를 소비한다. 이들은 메모리와 운영체제 자원을 사용하고 컨텍스트 스위칭을 한다. 대부분의 현대적 서버들은 수백개의 활성화된 작은 프로세스, 스레드들을 동시에 감당할 수 있다. 하지만, 메모리를 많이 사용하거나 무거운 I/O로 인한 컨텍스트 스위칭이 대량으로 발생한다면 퍼포먼스는 심각하게 저하된다.

  네트워크 애플리케이션을 설계할 때는 일반적으로 각 커넥션 하나 당 하나의 스레드 또는 프로세스를 할당한다. 이 아키텍처는 간단하고 적용하기 쉽다. 하지만 이 설계는 수천개의 커넥션을 동시에 감당하지 못한다.

How Does Nginx Work?

  Nginx는 사용 가능한 하드웨어 리소스에 맞게 조정된 범용 프로세스 모델을 사용한다.

    - 마스터 프로세스는 설정(configuration) 읽기, 포트 바인딩, 여러개의 child process 생성 등 마스터 프로세스만 할 수 있는 연산을 수행한다.

    - cache loader process는 Nginx 시작 초기에 실행되서 disck cache를 메모리에 올리고 종료된다. cache loader processsms 보수적으로 스케줄하기 때문에 리소스 사용량이 적다.

    - cache manager process는 주기적으로 실행되며 disk cache에 할당된 용량을 사용해 실행된다.

    - worker process는 모든 일을 한다. 네트워크 연결, 디스크에 읽고 쓰기, 업스트림 서버와 커뮤니케이션하기 등의 일을 한다.

  Nginx는 하나의 워커 프로세스당 CPU 코어 하나를 할당하는 것을 권장하고 있다. 이는 worker_processes에 auto 파라미터를 줌으로서 설정할 수 있다.

1
worker_processes auto;
cs

  Nginx 서버가 active 상태가 되면, 각 워커 프로세스가 여러 커넥션들을 non-blocking 방식으로 처리한다. 이를 통해 컨텍스트 스위칭 비용을 줄인다.

  각 워커 프로세스는 싱글 스레드이며 독립적으로 실행된다. 워커 프로세스간에 커뮤니케이션은 공유된 메모리를 사용해 이루어진다. 이 공유된 메모리는 공유된 캐시 데이터, session persistence data, 등 공유된 리소스를 위한 메모리다.

Inside the Nginx Worker Process

  각 Nginx 워커 프로세스는 Nginx 환경 설정에 의해 초기화 되고 마스터 프로세스에 의해 수신 소켓(listen socket) 세트가 제공된다.

  워커 프로세스는 수신 소켓에의 이벤트를 대기하는 것으로 시작한다. 이벤트는 Nginx 측으로 들어오는 새로운 커넥션에 의해 초기화된다. 이 커넥션들은 상태 머신에 할당된다. HTTP 상태 머신이 일반적으로 사용된다, 하지만 Nginx는 스트림(raw TCP) 트래픽과 메일 프로토콜을 위한 상태머신들도 사용하고 있다.

  이 상태 머신은 본질적으로 Nginx에게 요청 처리 방식을 알려주는 명령의 집합이다. Nginx와 같은 기능을 하는 웹 서버들은 대부분 비슷한 상태 머신을 사용하고 있다. 이런 상태 머신들의 차이는 구현에 존재한다.

Scheduling the State Machine

  상태 머신을 체스 룰에 빗대어 생각해보자. 아래의 모든 설명에서 Nginx의 동작을 체스에 빗대어 설명할 것이다. 각 HTTP 트랜잭션은 하나의 체스 게임이다. 체스 판의 한 편은 웹 서버이(이 웹서버를 체스 장인이라 해보자)며 빠른 결정을 내릴 수 있다. 다른 한편에는 원격 클라이언트가 존재하며 상대적으로 느린 네트워크로 애플리케이션에 접속하고 있다.

  그러나, 이 게임의 룰은 복잡해 질 수 있다. 예를 들어, 웹 서버가 프록시와 커뮤니케이션 해야 한다거나, 서드 파티 모듈들이 게임의 룰을 확장하거나 할 수 있다.

A Blocking State Machine

   스레드, 프로세스는 운영체제가 CPU 코어에서 실행되게 스케줄할 수 있는 명령어들의 집합이라는 것을 상기시켜 보자. 많은 웹 서버들과 웹 애플리케이션들은 커넥션 하나 당 하나의 스레드 또는 하나의 프로세스를 사용해 체스 게임을 진행한다. 각 프로세스, 스레드는 게임을 끝날때 까지 진행할 수 있는 명령어들을 포함하고 있다. 이 때, 프로세스는 서버에 의해 실행되며 대부분의 시간을 블록된(blocked)채로 기다린다(클라이언트가 다음 동작을 완료하길 기다려야 하기 때문이다).

  1. 웹 서버 프로세스는 수신 소켓에서 새로운 커넥션을 기다린다(listen).

  2. 새로운 게임이 시작되면, 새로운 게임을 진행한다. 말을 한번 욺직일 때마다 클라이언트의 응답을 기다린다.

  3. 게임이 종료되면, 웹 서버 프로세스는 클라이언트가 새로운 게임을 시작하길 원하는지를 알기 위해 대기한다(keepalive 커넥션과 관련있다). 만약 커넥션이 종료된다면(클라이언트가 연결을 끊거나 timeout된다면), 해당 웹 서버 프로세스는 새로운 게임을 대기하기 위해 반환된다.

  여기서 중요한 것은 모든 HTTC 커넥션(각 체스 게임)은 전용 프로세스, 스레드가 필요하다는 것이다. 이 아키텍처는 심플하고 서드 파티 모듈을 사용해 확장하기 쉽다. 그러나, 여기에는 심각한 불균형이 존재한다. 단일 파일 디스크립터와 적인 양의 메모리로 대표되는 경량 HTTP 커넥션이 무거운 운영체제 객체인 분리된 스레드, 프로세스에 매핑된다. 이는 개발 편의를 위한것이지만, 낭비가 심각하다.

Nginx is a True Grandmaster

  한 명의 체스 장인이 수십명의 플레이어와 동시에 체스게임을 진행하는 장면을 본적 있는가? 이것이 Nginx 워커 프로세스가 "체스"를 플레이하는 방식이다. 각 워커(통상적으로 각 CPU 코어 하나 당 하나의 워커가 존재하는 것을 기억하라.)는 동시에 수백개의 체스 게임을 진행할 수 있는 체스 장인이다.

  1. 워커 프로세스는 수신 소켓과 연결 소켓(connection socket)에서 이벤트를 기다린다.

  2. 이벤트가 소켓에서 발생하면 워커는 이들을 처리한다.

    - 수신 소켓에서의 이벤트는 클라이언트가 새로운 체스 게임을 시작한다는 의마다. 워커는 새로운 연결 소켓을 생성한다.

    - 연결 소켓에서의 이벤트는 클라이언트가 새로운 말을 욺직였다는 의미다. 워커는 신속히 응답한다.

  워커는 네트워크 트래픽을 block하지 않고 클라이언트의 응답을 기다린다. 클라이언트가 말을 욺직였다면 워커는 즉시 욺직임을 기다리고 있는 다른 게임들로 넘어가거나 새로운 플레이어를 기다린다.

Why Is This Faster Than a Blocking, Multiprocess Architecture?

  Nginx는 하나의 워커 프로세스가 수백 수척개의 커넥션을 감당할 수 있게 높은 확장성을 가지고 있다. 각 새로운 거넥션은 파일 디스크립터를 생성하고 워커 프로세스에서 소량의 추가 메모리를 사용한다. 따라서 하나의 커넥션은 오직 소량의 오버헤드만 존재하게 된다. Nginx 프로세스들은 CPU에 고정된 상태로 남아있을 수 있다. 그러면 컨텍스트 스위칭을 상대적으로 줄일 수 있고 완료해야 하는 일이 없을 경우에 발생하게 된다.

  블로킹 환경에서 커넥션 하나 당 프로세스 하나를 사용하는 접근법은 각 커넥션이 많은 양의 리소스와 오버헤드를 사용하게 한다. 또 한, 컨텍스트 스위칭도 빈번히 발생한다.

  더 자세한 설명이 필요하다면 링크를 참고하자.

  올바른 시스템 튜닝을 한다면, Nginx는 하나의 워커 프로세스가 동시에 발생하는 HTTP 커넥션은 수백 수천개까지 감당할 수 있게 확장할 수 있다. 또 한, 트래픽 급증도 착오없이 감당할 수 있다.

Updating Configuration and Upgrading Nginx

  적은 수의 워커 프로세스를 가지는 Nginx 프로세스 아키텍처는 환경 세팅을, 심지어는 Nginx 바이너리를 효율적으로 업데이터 하게 해준다.

  Nginx 환경 세팅을 업데이트 하는 것은 아주 간단한 작업이다. 일반적으로 "nginx -s reload" 명령어를 입력하기만 하면 된다. 이 명령어는 디스크에 존재하는 환경 설정을 체크하고 마스터 프로세스에게 SIGHUP 신호를 보낸다.

  마스터 프로세스가 SIGHUP 신호를 받으면, 다음 두 가지 일을 한다.

  1. 환경 설정을 다시 로딩하고 새로운 워커 프로세스 집합을 포크한다. 이 새로운 워커 프로세스들은 새로운 환경 설정을 사용해 즉시 커넥션과 프로세싱 프래픽을 받아 처리한다.

  2. 이전에 사용된 워커 프로세스가 정상적으로 종료되게 신호를 보낸다. 이 워커 프로세스들은 새로운 커넥션을 더이상 받지 않는다. 현재 진행중인 각 HTTP 요청이 종료되면 종료 대상이 되는 워커프로세스들은 커넥션을 중단한다(keepalive를 하지 않는다). 모든 커넥션이 종료된 후, 워커 프로세스들은 종료된다.

  이런 프로세스를 다시 로딩하는 과정은 CPU와 메모리 사용량을 약간 증가시킬 수 있다, 하지만 리소스를 활성화된 커넥션들로 부터 받아오는 것에 비하면 미미한 사용량이다. 환경 설정을 1초에 여러번 리로드할 수도 있다. 흔치 않은 경우지만, 몇번의 리로딩을 거치는 동안 이전의 워커 프로세스들이 커넥션을 종료하지 못하고 종료되길 기다리는 경우가 발생한다. 하지만, 이런 문제는 금방 해결된다.

  Nginx의 바이너리 업그레이드는 고가용성을 달성했다. 바이너리 업그레이드를 통해 서비스의 중단, 커넥션 중지 없이도 소프트웨어를 업그레이드할 수 있다.

  바이너리 업그레이드의 접근법은 환경 설정 리로딩과 비슷하다. 새로운 Nginx 마스터 프로세스가 기존 마스터 프로세스와 동시에 동작한다. 그리고 수신 소켓을 공유한다. 두 마스터 프로스세들은 모두 활성화 되있고, 각자의 워커 프로세스가 트래픽을 감당한다. 이제 기존 마스터 프로세스를 종료시키면 된다. 더 자세한 동작과정은 링크를 참고하자.

  만약 Nginx 최적화에 관심있다면 다음 글들을 참고하자.

 

How to Get Started With NGINX - NGINX

In this webinar, we help you get started using NGINX, the de facto standard building block for modern microservices-based architectures. During this practical workshop, we take you through installing and configuring NGINX as a web server, load balancer, an

www.nginx.com

 

Tuning NGINX for Performance - NGINX

Learn about some common Linux and NGINX settings that can be tuned to obtain optimal performance.

www.nginx.com

 

The Architecture of Open Source Applications (Volume 2): nginx

nginx (pronounced "engine x") is a free open source web server written by Igor Sysoev, a Russian software engineer. Since its public launch in 2004, nginx has focused on high performance, high concurrency and low memory usage. Additional features on top of

www.aosabook.org

Socket Sharding in NGINX Release 1.9.1

 

Socket Sharding in NGINX OSS Release 1.9.1

Learn how enabling the SO_REUSEPORT socket option, newly supported in NGINX 1.9.1, can improve performance by load balancing connections across workers

www.nginx.com

 

출처 - Inside NGINX: How We Designed for Performance & Scale