nginx 에서 실제 연결을 처리하는 프로세스.
$ ps -ef | grep nginx
root 1234 1 nginx: master process
nginx 1235 1234 nginx: worker process
nginx 1236 1234 nginx: worker process
-
마스터 — root 로 뜬다
- 설정 파일을 읽고 검증한다
- 80·443 같은 특권 포트를 bind 한다 (1024 미만은 root 만 가능)
- 워커를 낳고 관리한다 · 시그널을 받는다
- 요청을 직접 처리하지 않는다
-
워커 — 비특권 사용자로 낮춰서 돈다
- 실제 처리는 전부 여기
- 서로 독립적. 하나가 죽어도 마스터가 다시 띄운다
왜 나눠 놨나 — 포트 bind 에는 root 가 필요하지만 외부 입력을 다루는 코드는 root 로 돌면 안 된다. 권한이 필요한 일만 먼저 끝내고, 위험한 일은 권한을 낮춘 프로세스가 맡는다. 권한 분리의 교과서적 배치다.
튜닝 값
worker_processes auto; # 보통 CPU 코어 수
worker_rlimit_nofile 65535; # 워커당 파일 디스크립터 한도
events {
worker_connections 4096; # 워커 하나가 동시에 다룰 연결 수
multi_accept on;
}
최대 연결 수 계산에서 흔한 실수
이론상 최대 = worker_processes × worker_connections
주의 — 이 값은 '연결' 수이지 '클라이언트' 수가 아니다
- 정적 파일만 — 클라이언트 연결 1개 = 연결 1개
- 리버스 프록시 — 클라이언트 1개 + 업스트림 1개 = 연결 2개
- 실질 동시 사용자 ≈ (프로세스 × 커넥션) / 2
이 나눗셈을 빠뜨리는 것이 가장 흔한 계산 실수다
auto 가 코어 수인 이유
워커는 이벤트 루프를 돈다 — 코어보다 많이 띄우면 컨텍스트 스위치만 는다 CPU 를 쓰는 일(TLS 핸드셰이크 · gzip)이 많으면 코어 수가 곧 상한이라는 뜻이다
- 반대로 워커가 대부분 I/O 를 기다린다면 코어 수로 충분하다
- '워커를 늘려서' 처리량을 올리는 접근은 대개 틀렸다
워커가 막히면 그 워커 전체가 멈춘다
워커 하나가 4,000 연결을 돌보는데 한 처리에서 디스크를 동기로 읽느라 50ms 를 쓰면 나머지 3,999 연결이 그 50ms 동안 진행하지 못한다
대응
aio threads; 디스크 읽기를 스레드 풀로 뺀다
sendfile on; 커널 안에서 파일→소켓 전송 (복사·모드 전환 감소)
무거운 계산은 애초에 뒤(WAS)로 보낸다
워커 사이에는 상태가 공유되지 않는다
워커는 독립된 프로세스다 → 메모리를 공유하지 않는다
그래서 공유가 필요한 기능은 명시적으로 공유 메모리를 잡는다
ssl_session_cache shared:SSL:10m;
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
proxy_cache_path ... keys_zone=api:10m;
zone 크기를 작게 잡으면 오래된 항목이 밀려난다
- 레이트 리밋이 헐거워지거나 세션 재개 적중률이 떨어진다
accept 경쟁과 부하 분산
여러 워커가 같은 리스닝 소켓에서 accept 를 기다린다 그대로 두면 특정 워커에 연결이 쏠릴 수 있다
- accept_mutex — 차례로 accept 하게 한다 (요즘 기본 off)
- reuseport — 커널이 연결을 워커별 소켓에 분산한다 (SO_REUSEPORT)
- listen 443 reuseport;
- 연결 수립 비용이 큰 워크로드에서 효과가 크다
면접 함정
- ❌ "워커를 늘리면 동시 접속이 는다" → 코어를 넘기면 오히려 손해다. 한도는 대개 파일 디스크립터다.
- ❌ "마스터도 요청을 처리한다" → 관리만 한다.