Nginx 워커 프로세스 최적화: 최대 성능을 위한 실용 가이드
고트래픽 환경에서 Nginx 서버를 최적화하는 실용 가이드입니다. 핵심 성능 지시어 설정을 통해 CPU 코어에 맞춘 `worker_processes` 설정, `worker_connections`으로 동시성 극대화, OS 파일 디스크립터 제한(`ulimit`) 준수 방법을 배웁니다. 이 문서는 실행 가능한 설정 예제와 필수 튜닝 팁을 제공하여 지연 시간을 최소화하고 서버 처리량을 극적으로 향상시킵니다.
Nginx 워커 프로세스 최적화: 최대 성능을 위한 실용 가이드
Nginx는 작은 프로세스 풋프린트로 많은 동시 연결을 처리할 수 있지만, 워커 제한이 기본 시스템과 일치해야 합니다. 가장 먼저 조정하는 두 가지 설정은 worker_processes와 worker_connections입니다. 이들은 유용하지만 과도하게 튜닝하기 쉽습니다. 두 값을 모두 크게 설정한다고 해서 무료 용량이 생기지는 않습니다. 단지 병목 현상을 파일 디스크립터, 메모리, 업스트림 서버 또는 네트워크 스택으로 옮길 뿐입니다.
실용적인 목표는 Nginx가 보유한 CPU 코어를 충분히 활용할 수 있는 워커 수, 실제 트래픽에 충분한 연결 슬롯, 그리고 정상적인 버스트 동안 한계에 도달하지 않도록 충분한 운영 체제 제한을 제공하는 것입니다.
Nginx 워커 아키텍처 이해
Nginx는 마스터-워커 모델로 작동합니다. 마스터 프로세스는 설정을 읽고 검증하며, 포트에 바인딩하고, 워커 프로세스를 관리합니다. 시스템 리소스 모니터링 및 필요 시 워커 재시작과 같은 비핵심 작업을 수행합니다.
워커 프로세스는 실제 작업이 이루어지는 곳입니다. 이 프로세스들은 (표준 Nginx 컴파일에서) 단일 스레드이며 논블로킹 시스템 호출을 사용합니다. 각 워커는 이벤트 루프를 사용하여 수천 개의 동시 연결을 효율적으로 처리하므로, 하나의 프로세스가 차단 없이 여러 요청을 관리할 수 있어 Nginx 성능의 핵심입니다.
적절한 최적화는 워커 수(CPU 리소스에 연결)와 각 워커가 처리할 수 있는 최대 연결 수를 균형 있게 설정하는 것입니다.
worker_processes 설정: CPU 코어 요소
worker_processes 지시어는 Nginx가 생성할 워커 프로세스 수를 결정합니다. 이 설정은 서버의 CPU 리소스를 Nginx가 어떻게 활용하는지에 직접적인 영향을 미칩니다.
모범 사례: 워커를 코어에 맞추기
가장 일반적이고 권장되는 모범 사례는 워커 프로세스 수를 서버에서 사용 가능한 CPU 코어 수와 동일하게 설정하는 것입니다. 이렇게 하면 컨텍스트 스위칭으로 인한 과도한 오버헤드 없이 모든 코어가 효율적으로 활용됩니다.
워커 수가 코어 수를 초과하면 운영 체제는 경쟁하는 Nginx 프로세스 간에 CPU 포커스를 자주 전환(컨텍스트 스위칭)해야 하므로 지연 시간이 발생하고 전반적인 성능이 저하됩니다.
auto 지시어 사용
최신 Nginx 버전(1.3.8 이상)의 경우 가장 간단하고 효과적인 설정은 auto 매개변수를 사용하는 것입니다. Nginx가 사용 가능한 CPU 코어 수를 자동으로 감지하고 그에 따라 워커 프로세스를 설정합니다.
# 대부분의 배포에 권장되는 설정
worker_processes auto;
수동 설정
수동 제어가 필요하거나 이전 버전을 사용하는 경우 정확한 워커 수를 지정할 수 있습니다. 시스템 유틸리티를 사용하여 코어 수를 찾을 수 있습니다:
# CPU 코어 수 찾기
grep processor /proc/cpuinfo | wc -l
시스템에 8개의 코어가 있는 경우 설정은 다음과 같습니다:
# 워커 프로세스를 8로 수동 설정
worker_processes 8;
팁: 사용 가능한 코어 수와 일치시키는 것이 가장 안전한 시작점입니다. 특이한 I/O 집약적 워크로드에서는 다른 값을 테스트할 수 있지만, 실제 트래픽에서 벤치마킹한 후 유지하세요. 일반적인 정적 파일 제공, 프록시 및 TLS 종료의 경우
auto가 일반적으로 가장 예측 가능한 선택입니다.
worker_connections 설정: 동시성 요소
worker_connections 지시어는 events 블록 내에서 설정되며, 단일 워커 프로세스가 처리할 수 있는 최대 동시 연결 수를 정의합니다. 여기에는 클라이언트 연결, 업스트림 프록시 서버 연결 및 내부 상태 확인 연결이 포함됩니다.
최대 클라이언트 계산
Nginx 서버가 처리할 수 있는 이론적 최대 동시 클라이언트 연결 수는 다음과 같이 계산됩니다:
$$\text{Max Clients} = \text{worker_processes} \times \text{worker_connections}$$
4개의 워커 프로세스와 프로세스당 10,000개의 워커 연결이 있는 경우 Nginx는 이론적으로 40,000개의 동시 연결을 처리할 수 있습니다.
이 숫자는 대략적인 상한선일 뿐입니다. 프록시된 요청은 동시에 하나의 클라이언트 연결과 하나의 업스트림 연결을 사용할 수 있습니다. WebSocket 및 롱폴링 트래픽은 일반 페이지 요청보다 훨씬 오래 슬롯을 점유할 수 있습니다. Keep-alive 연결은 거의 작업을 수행하지 않으면서 열려 있을 수 있습니다. Nginx가 주로 정적 파일을 제공하는 경우 계산은 간단한 공식에 가깝습니다. 리버스 프록시 역할을 하는 경우 여유 공간을 남겨두세요.
연결 제한 설정
메모리 및 파일 디스크립터 제한이 이를 지원할 수 있다고 가정할 때, 바쁜 서버에서는 worker_connections를 수천 이상으로 설정하는 것이 일반적입니다. 큰 값을 맹목적으로 복사하지 말고 예상 동시성에 버스트 여유를 더한 값을 선택하세요.
# events 블록 예제 설정
events {
# 워커 프로세스당 최대 동시 연결 수
worker_connections 16384;
# 버스트 시 도움이 될 수 있지만, 부하 상태에서 공정성을 테스트하세요.
multi_accept on;
}
시스템 제한(ulimit) 제약
중요한 점은 worker_connections 설정은 프로세스당 허용되는 열린 파일 디스크립터(FD) 수에 대한 운영 체제 제한에 의해 제약을 받으며, 이는 종종 ulimit -n 설정에 의해 제어됩니다.
Nginx는 OS가 파일 디스크립터를 허용하는 것보다 더 많은 연결을 열 수 없습니다. 모든 연결(클라이언트 소켓, 로그 파일, 프록시 소켓)에는 파일 디스크립터가 필요하므로 시스템 제한이 충분히 높게 설정되는 것이 중요합니다.
파일 디스크립터 제한 확인 및 증가
현재 제한 확인:
ulimit -n일시적으로 제한 증가(현재 세션):
ulimit -n 65536영구적으로 제한 증가(
/etc/security/limits.conf통해):다음 줄을 추가하고,
nginx_user를 Nginx가 실행되는 사용자(종종www-data또는nginx)로 바꾸세요:# /etc/security/limits.conf nginx_user soft nofile 65536 nginx_user hard nofile 65536
경고: Nginx 워커 사용자에 대한 프로세스당 파일 디스크립터 제한이
worker_connections보다 높고, 로그, 업스트림 소켓, 캐시 파일 및 기타 열린 파일을 위한 추가 공간이 있는지 확인하세요. 시스템 전체 제한도 중요하지만, 프로세스당 제한이 가장 자주 문제를 일으킵니다.
Nginx가 systemd로 관리되는 경우 /etc/security/limits.conf만으로는 충분하지 않을 수 있습니다. 많은 배포판에서 유닛 파일의 제한으로 서비스를 시작합니다. 다음 명령으로 활성 제한을 확인하세요:
cat /proc/$(pgrep -o nginx)/limits | grep "open files"
systemd 오버라이드의 경우 다음을 사용하세요:
sudo systemctl edit nginx
그런 다음 추가:
[Service]
LimitNOFILE=65536
유지보수 기간 동안 systemd를 다시 로드하고 Nginx를 재시작하세요:
sudo systemctl daemon-reload
sudo systemctl restart nginx
고급 튜닝 및 모니터링
핵심 지시어 외에도 몇 가지 추가 고려 사항이 성능을 미세 조정하는 데 도움이 될 수 있습니다:
1. 워커 프로세스 고정
고성능 환경, 특히 여러 CPU 소켓(NUMA 아키텍처)이 있는 시스템에서는 worker_cpu_affinity 지시어를 사용할 수 있습니다. 이는 특정 워커 프로세스를 특정 CPU로 제한하도록 OS에 지시하여 CPU 캐시를 유지하고 메모리 지역성 문제를 방지함으로써 성능을 향상시킬 수 있습니다.
8코어 시스템 예제:
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
이 설정은 복잡하며 일반적으로 극한의 고부하 상황에서만 유용합니다. 대부분의 배포에서는 worker_processes auto로 충분합니다.
2. 성능 메트릭 모니터링
최적화를 적용한 후에는 영향을 모니터링하는 것이 중요합니다. Nginx Stub Status 모듈(또는 Prometheus/Grafana와 같은 도구)을 사용하여 주요 메트릭을 추적하세요:
| 메트릭 | 설명 | 최적화 확인 |
|---|---|---|
| 활성 연결 | 현재 처리 중인 총 연결 수. | 이론적 최대치보다 낮아야 합니다. |
| 읽기/쓰기/대기 | 다양한 상태의 연결. | 높은 대기 수는 장기 HTTP Keep-Alive(좋음) 또는 처리 리소스 부족(나쁨)을 나타냅니다. |
| 요청 속도 | 초당 요청 수. | 설정 변경 후 실제 성능 향상을 측정하는 데 사용됩니다. |
모든 코어에서 높은 CPU 사용률과 높은 요청 속도가 관찰되면 worker_processes가 올바르게 설정된 것입니다. 피크 트래픽 중에 유휴 CPU 코어가 있다면 설정을 검토하거나 Nginx 외부의 차단 I/O 작업을 확인하세요.
3. 연결 오버플로 전략
서버가 최대 연결 제한(worker_processes * worker_connections)에 도달하면 새 연결이 실패하거나 시간 초과될 때까지 대기열에 머물 수 있습니다. worker_connections를 늘리는 것은 Nginx가 실제 병목일 때만 도움이 됩니다. 업스트림 애플리케이션 서버가 포화 상태라면 제한을 높이면 느린 백엔드 뒤에 더 많은 요청이 쌓이므로 장애가 더 심하게 느껴질 수 있습니다.
오류 로그를 신호로 사용하세요. worker_connections are not enough와 같은 메시지는 Nginx 제한을 직접 가리킵니다. upstream timed out, connect() failed 또는 502/504 응답의 증가는 백엔드 용량, 네트워크 문제 또는 시간 초과 설정을 더 가리킵니다.
합리적인 시작 설정
소규모 또는 중간 규모의 리버스 프록시의 경우 다음은 적절한 기준입니다:
worker_processes auto;
worker_rlimit_nofile 65536;
events {
worker_connections 8192;
multi_accept off;
}
여기서 왜 multi_accept off일까요? 많은 시스템에서 기본값입니다. 켜면 워커가 보류 중인 accept 큐를 빠르게 비울 수 있지만, 일부 트래픽 패턴에서는 한 워커가 큰 배치를 가져가고 다른 워커는 유휴 상태가 될 수 있습니다. 버스트 트래픽이 있고 활성화할 테스트된 이유가 있다면 그렇게 하세요. 범용 웹 서버를 튜닝하는 경우 기준을 단순하게 유지하고 먼저 측정하세요.
서버가 많은 WebSocket 연결, Server-Sent Events 또는 장기 API 스트림을 처리하는 경우 연결 제한을 더 적극적으로 높이고 메모리에 주의를 기울이세요. 20,000개의 대부분 유휴 WebSocket 클라이언트가 있는 서버는 20,000개의 짧은 정적 파일 요청을 수행하는 서버와 다른 프로필을 가집니다.
변경 사항 검증 방법
프로덕션을 변경하기 전에 작은 기준을 캡처하세요:
nginx -T | grep -E 'worker_processes|worker_connections|worker_rlimit_nofile'
ss -s
ulimit -n
변경 후 Nginx가 실제로 로드했는지 확인하세요:
sudo nginx -t
sudo systemctl reload nginx
ps -o pid,comm,nlwp,pcpu,pmem -C nginx
cat /proc/$(pgrep -n nginx)/limits | grep "open files"
그런 다음 실제 트래픽 중 동작을 관찰하세요. 모든 CPU 코어가 바쁘고 지연 시간이 증가하면 Nginx가 유용한 작업을 수행하고 CPU 용량에 도달한 것일 수 있습니다. CPU는 낮지만 연결이 대기열에 쌓이거나 시간 초과되면 파일 디스크립터, 업스트림 포화, DNS 확인, 디스크 I/O 또는 방화벽 제한을 확인하세요. 워커 튜닝은 하나의 레버일 뿐, 전체 성능 이야기가 아닙니다.
숫자를 맥락에서 읽기
일반적인 실수는 "활성 연결"을 "활성 사용자"와 동일하게 취급하는 것입니다. 그렇지 않습니다. 하나의 브라우저는 자산을 위해 여러 연결을 열 수 있습니다. 하나의 API 클라이언트는 요청 사이에 연결을 유지할 수 있습니다. 하나의 WebSocket 클라이언트는 거의 트래픽을 보내지 않으면서 몇 시간 동안 연결을 유지할 수 있습니다. worker_connections 크기를 정할 때는 사람이 아닌 동시 소켓 측면에서 생각하세요.
리버스 프록시의 경우 업스트림 측도 기억하세요. 4,000명의 클라이언트가 프록시된 응답을 기다리고 있다면 Nginx는 수천 개의 업스트림 소켓도 보유하고 있을 수 있습니다. 이것이 서버가 간단한 클라이언트 측 계산이 예측하는 것보다 먼저 파일 디스크립터가 부족해질 수 있는 이유입니다. 이는 업스트림 애플리케이션이 느려질 때 특히 두드러집니다. 요청이 더 오래 열려 있고, 동시성이 증가하며, 들어오는 요청 속도가 변경되지 않았음에도 Nginx가 더 많은 소켓을 소비하기 시작합니다.
Keep-alive 설정도 이에 영향을 미칩니다. 긴 keep-alive 시간 초과는 연결 변동을 줄여 바쁜 사이트에 도움이 될 수 있지만, 유휴 소켓을 더 오래 유지합니다. 매우 짧은 keep-alive 시간 초과는 소켓을 더 빨리 해제하지만 TLS 핸드셰이크 및 연결 설정 오버헤드를 증가시킬 수 있습니다. 완벽한 값은 없습니다. 트래픽 형태를 가이드로 사용하세요. 짧은 방문이 많은 공개 웹 사이트는 소수의 영구 클라이언트가 있는 내부 API와 다른 균형이 필요할 수 있습니다.
컨테이너 내부에서 튜닝하는 경우 컨테이너 내부 및 호스트 또는 오케스트레이터 수준에서 제한을 확인하세요. Kubernetes 파드, Docker 컨테이너 또는 systemd 서비스는 테스트에 사용한 호스트 셸보다 낮은 nofile 제한을 가질 수 있습니다. 항상 로그인 세션이 아닌 실행 중인 Nginx 프로세스를 확인하세요.
모범 사례 요약
| 지시어 | 권장 값 | 근거 |
|---|---|---|
worker_processes |
auto (또는 코어 수) |
최적의 CPU 활용을 보장하고 컨텍스트 스위칭 오버헤드를 최소화합니다. |
worker_connections |
수천에서 시작; 측정된 동시성에 따라 증가 | 다른 병목 현상을 숨기지 않으면서 연결 여유 공간을 제공합니다. |
OS 제한(ulimit -n) |
워커당 연결 요구보다 높고 추가 공간 확보 | 클라이언트 소켓, 업스트림 소켓, 로그 및 캐시 파일에 대한 파일 디스크립터를 제공합니다. |
multi_accept |
활성화 전 테스트 | 버스트에 도움이 될 수 있지만 모든 워크로드에 자동으로 더 나은 것은 아닙니다. |
최고의 Nginx 워커 설정은 일반적으로 간단합니다: worker_processes auto, 실제 동시성을 반영하는 연결 제한, 그리고 워크로드에 충분히 높은 파일 디스크립터 제한. 튜닝하고, 활성 프로세스 제한을 확인하고, 오류 로그를 계속 관찰하세요. 증상이 업스트림을 가리키면 Nginx가 애플리케이션이 완료할 수 있는 것보다 더 많은 작업을 수락하도록 만드는 대신 업스트림을 수정하세요.