1. 도입: 왜 Nginx인가?

전통적인 웹 서버인 Apache HTTP 서버는 각 클라이언트 연결마다 별도의 프로세스나 스레드를 생성합니다.
이러한 방식은 여러 가지 단점이 있습니다

  • 과도한 메모리 사용: 각 연결에 대해 새로운 프로세스가 생성되므로 메모리 사용량이 급증합니다.
  • 성능 저하: 스레드나 프로세스를 전환하는데 드는 시간(컨텍스트 스위칭)이 성능을 저하시킵니다.
  • 동시 연결 수 제한: 수천 또는 수백만 개의 동시 연결을 처리하기 어려운 구조입니다.

반면, Nginx는 이러한 문제들을 해결하며 고성능을 자랑하는 웹 서버로 자리잡았습니다.
Nginx가 왜 100만 개의 동시 연결을 처리할 수 있는지, 그 핵심 아키텍처를 깊이 파헤쳐 보겠습니다.

2. Nginx의 핵심 아키텍처

2.1 Master-Worker 모델

Nginx는 마스터-워커(Master-Worker) 모델을 채택하여 서버의 요청을 처리합니다.
이 모델은 효율적인 리소스 활용과 높은 성능을 제공합니다.

  • 마스터 프로세스: 서버의 설정 파일을 읽고, 워커 프로세스를 생성하고 관리합니다.
  • 워커 프로세스: 실제 클라이언트 요청을 처리하는 프로세스입니다.

이 모델의 주요 이점은 병렬 처리입니다.

여러 개의 CPU 코어가 있다면 각 워커 프로세스는 개별 코어에서 독립적으로 실행되므로,
멀티코어 시스템에서 높은 성능을 발휘합니다.

2.2 이벤트 기반 처리 방식

Nginx는 이벤트 기반 비동기 처리 모델을 사용하여, 하나의 워커 프로세스가 여러 클라이언트 요청을 동시에 처리할 수 있게 합니다.

이는 단일 스레드에서 여러 연결을 처리하는 방식으로, CPU와 메모리 자원을 효율적으로 사용합니다.

  • 비동기 처리: 각 요청은 이벤트 큐에 추가되고, 이벤트 루프가 이를 비동기적으로 처리합니다.

  • 논블로킹 I/O: 요청을 기다리는 동안 다른 요청을 처리할 수 있습니다. I/O 작업이 완료될 때까지 기다리지 않고, 다른 클라이언트 요청을 계속해서 처리할 수 있습니다.

  • 이벤트 큐: 각 요청은 큐에 저장되어 순차적으로 처리됩니다. 이를 통해 여러 연결을 동시에 관리할 수 있습니다.

2.3 스레드 풀 활용

Nginx는 스레드 풀(thread pool) 기능을 통해 차단 작업(blocking tasks)을 효율적으로 처리합니다.

일부 요청은 CPU 집약적인 작업을 포함할 수 있는데, 이러한 작업을 메인 이벤트 루프에서 처리하면 성능이 저하될 수 있습니다.

따라서 Nginx는 차단 작업을 스레드 풀로 오프로드하여 처리합니다.

  • 차단 작업 위임: 블로킹 작업은 스레드 풀에 위임하고, 메인 이벤트 루프는 계속해서 다른 요청을 처리합니다.

  • 콜백 방식: 차단 작업이 완료되면, 해당 결과를 이벤트 큐에 다시 넣어 워커 프로세스가 응답을 클라이언트에 전달합니다.

2.4 공유 메모리 활용

Nginx는 여러 워커 프로세스 간의 데이터 공유를 위해 공유 메모리를 활용합니다.
이를 통해 리소스를 효율적으로 관리하고 성능을 극대화합니다.

  • 캐시 공유: 여러 워커가 동일한 캐시 데이터를 공유함으로써, 데이터 중복을 방지하고 메모리 사용을 최적화합니다.

  • 세션 데이터 관리: 세션 데이터를 공유 메모리에 저장하고, 이를 기반으로 백엔드 서버로 요청을 라우팅합니다.

  • 속도 제한 추적: 요청 속도를 추적하여 트래픽을 관리하고, 각 클라이언트의 연결 제한을 효율적으로 제어합니다.

3. 성능 최적화 전략

3.1 연결 풀링(Connection Pooling)

Nginx는 연결 풀링을 통해 리소스를 절약하고 성능을 최적화합니다.
각 연결을 재사용하여 네트워크 연결을 효율적으로 관리할 수 있습니다.

  • Keep-Alive: HTTP 연결을 재사용하여 새로운 연결을 맺는 비용을 절감합니다.
  • 연결 풀 크기 동적 조정: Nginx는 연결 풀의 크기를 동적으로 조정하여 서버의 부하에 맞춰 최적화합니다.

Nginx 설정 파일 (nginx.conf) 최적화

아래는 Nginx가 100만 동시 연결을 처리할 수 있도록 최적화된 nginx.conf의 예입니다.

  • nginx.conf 설정
user nginx;
worker_processes auto;  # 시스템의 CPU 코어 수에 맞게 워커 프로세스 수 자동 설정
worker_cpu_affinity auto;  # 워커 프로세스를 CPU 코어에 자동 할당

# 워커 프로세스가 최대 1024개의 연결을 처리할 수 있도록 설정
worker_connections 1024;

events {
    worker_connections 1024;  # 워커당 처리할 최대 연결 수
    use epoll;  # 리눅스에서 이벤트 기반 비동기 I/O 처리 방식을 사용
    multi_accept on;  # 여러 개의 연결을 한 번에 처리할 수 있게 설정
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # 로그 레벨 설정 (디버깅이나 로깅 최적화 필요시)
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    server {
        listen 80;

        server_name localhost;

        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
        }
    }

    # 연결 풀링 설정
    upstream backend {
        # 로드 밸런싱을 위한 백엔드 서버 설정 (예시)
        server backend1.example.com;
        server backend2.example.com;

        keepalive 32;  # 백엔드 서버와의 지속적인 연결을 위한 설정
    }

    # 백엔드 서버와의 로드 밸런싱
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        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 $scheme;
    }
}
  • 설명하자면..

  • worker_processes auto;: Nginx는 시스템의 CPU 코어 수에 맞춰 워커 프로세스를 자동으로 설정합니다. 이는 성능을 최적화하는 데 중요한 설정입니다.

  • worker_connections 1024;: 각 워커 프로세스가 동시에 처리할 수 있는 연결 수를 설정합니다. 이 값을 적절히 설정하면, Nginx가 더 많은 연결을 처리할 수 있습니다.

  • worker_cpu_affinity auto;: 워커 프로세스를 각 CPU 코어에 효율적으로 할당하여 멀티코어 시스템에서 성능을 극대화합니다.

  • use epoll;: 리눅스에서 비동기 이벤트 기반 I/O 방식을 사용하여 성능을 최적화합니다. (다른 시스템에서는 kqueue나 select를 사용할 수 있음)

  • multi_accept on;: 하나의 이벤트에서 여러 연결을 동시에 수락할 수 있도록 설정합니다.
    이는 Nginx가 한 번에 많은 연결을 처리하도록 도와줍니다.

  • keepalive_timeout 65;: 클라이언트와의 연결이 유지되는 최대 시간을 설정합니다.
    적절한 값으로 설정하여 리소스를 효율적으로 사용합니다.

  • proxy_pass와 upstream 설정: 백엔드 서버에 요청을 전달하기 위한 로드 밸런싱 설정입니다. 이 설정을 통해 여러 서버로 트래픽을 분배하여 부하를 분산시킬 수 있습니다.

3.2 시스템 리소스 모니터링 및 조정

Nginx의 성능을 유지하려면 시스템 리소스 모니터링이 필수적입니다.
적절한 워커 프로세스 수와 연결 타임아웃 설정을 통해 리소스를 효율적으로 사용하고, 서버의 성능을 최대화할 수 있습니다.

  • 워커 프로세스 수: CPU 코어 수에 맞춰 워커 프로세스 수를 최적화하여 성능을 보장합니다.

  • 연결 타임아웃 설정: 클라이언트와의 연결 타임아웃을 적절히 설정하여 리소스를 낭비하지 않도록 합니다.

  • 성능 최적화 추가 설정

    • worker_rlimit_nofile: 워커 프로세스가 열 수 있는 최대 파일 수를 설정합니다.
      많은 동시 연결을 처리하려면 이 값을 적절히 늘려야 합니다.
worker_rlimit_nofile 1000000;  # 최대 파일 디스크립터 수 설정
  • client_max_body_size: 클라이언트가 보낼 수 있는 최대 요청 본문 크기를 설정합니다.
    대용량 파일 업로드 등을 처리하는 경우 이 값을 증가시킬 수 있습니다.
client_max_body_size 100m;  # 최대 요청 본문 크기 100MB 설정
  • 성능 모니터링
    시스템의 성능을 모니터링하고 Nginx 설정을 동적으로 조정하기 위해 stub_status 모듈을 사용할 수 있습니다.
    이를 통해 서버의 상태를 실시간으로 확인할 수 있습니다.
server {
    listen 127.0.0.1:8080;
    server_name localhost;

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;  # 로컬에서만 접근 가능
        deny all;
    }
}

이 설정을 통해 Nginx 상태 페이지에서 서버의 동시 연결 수,
요청 수 등을 모니터링할 수 있습니다.

3.3 백엔드 서버와의 로드 밸런싱

Nginx는 로드 밸런싱 기능을 통해 백엔드 서버의 부하를 분산시킬 수 있습니다.
이를 통해 확장성을 높이고, 대규모 트래픽을 효율적으로 처리할 수 있습니다.

Nginx는 upstream을 사용하여 여러 백엔드 서버로 트래픽을 분산할 수 있습니다.
아래는 백엔드 서버로 트래픽을 분산하는 예시입니다.

upstream backend {
    server backend1.example.com;
    server backend2.example.com;

    # keepalive 설정으로 백엔드 서버와의 연결을 재사용
    keepalive 32;  # 연결 풀 크기 설정 (32개 연결을 지속적으로 재사용)
}

4. Nginx의 확장성과 고성능

Nginx가 100만 동시 연결을 지원할 수 있는 이유는 바로 효율적인 이벤트 기반 처리, 최적화된 프로세스 모델, 리소스 관리의 효율성, 그리고 확장 가능한 아키텍처 덕분입니다.

  • 이벤트 기반 처리: 요청을 비동기적으로 처리하고, I/O를 차단하지 않음으로써 성능을 최적화합니다.

  • 효율적인 리소스 사용: 적은 리소스로 많은 요청을 처리할 수 있도록 설계되었습니다. 과도한 스레드나 프로세스 생성 없이 병렬로 작업을 처리합니다.

  • 확장성: 공유 메모리와 스레드 풀을 활용하여 성능과 확장성을 극대화합니다.

5. Nginx 성능 테스트

설정을 완료한 후, 성능 테스트를 진행하여 100만 동시 연결을 실제로 처리할 수 있는지 확인할 수 있습니다.

wrk 또는 ab와 같은 도구를 사용하여 부하 테스트를 실행해볼 수 있습니다.
예를 들어, wrk로 부하 테스트를 진행할 수 있습니다.

wrk -t12 -c1000000 -d30s http://localhost/

위 명령은 12개의 스레드를 사용하여 100만 개의 동시 연결을 30초 동안 처리하는 부하 테스트입니다.

결론

Nginx의 아키텍처는 효율성과 확장성을 중시하는 설계로, 100만 동시 연결을 처리할 수 있는 강력한 기반을 제공합니다.

Nginx의 고성능 웹 서버로서의 특성은 다양한 고유 기술들이 결합된 결과입니다.

  • 마스터-워커 모델을 통한 병렬 처리
  • 이벤트 기반 비동기 처리로 리소스를 효율적으로 활용
  • 스레드 풀과 공유 메모리 활용으로 고성능을 유지
  • 연결 풀링과 로드 밸런싱으로 확장성 확보

이 모든 요소들이 결합되어 Nginx는 현대 웹 애플리케이션에서 요구하는 고성능, 고동시성, 높은 확장성을 제공할 수 있습니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글