NGINX가 연결을 처리하기 위해 비동기, 이벤트 기반 접근 방식을 사용한다는 것은 잘 알려진 사실입니다. 즉, 기존 아키텍처의 서버처럼 각 요청에 대해 전용 프로세스나 스레드를 생성하는 대신, 하나의 작업자 프로세스에서 여러 연결과 요청을 처리합니다. 이를 달성하기 위해 NGINX는 비차단 모드에서 소켓을 사용하고 epoll 및 kqueue 와 같은 효율적인 방법을 사용합니다.
전체 가중치 프로세스의 수가 적고(일반적으로 CPU 코어당 하나만 있음) 일정하기 때문에 메모리 소모가 훨씬 적고 작업 전환에 CPU 사이클이 낭비되지 않습니다. 이러한 접근 방식의 장점은 NGINX 자체의 예를 통해 잘 알려져 있습니다. 수백만 개의 동시 요청을 성공적으로 처리하고 확장성도 매우 뛰어납니다.
하지만 비동기 이벤트 기반 접근 방식에는 여전히 문제가 있습니다. 아니면, 내가 생각하기에는 "적"이라고 할 수도 있겠습니다. 그리고 적의 이름은 ' 차단' 입니다. 안타깝게도 많은 타사 모듈이 차단 호출을 사용하고 있으며, 사용자(때로는 모듈 개발자조차도)는 이러한 단점을 인식하지 못합니다. 작업을 차단하면 NGINX 성능이 저하될 수 있으므로 어떻게 해서든 피해야 합니다.
현재 공식 NGINX 코드에서도 모든 경우에 작업 차단을 방지하는 것은 불가능하며, 이 문제를 해결하기 위해 NGINX 버전 1.7.11 및 NGINX Plus 릴리스 7 에 새로운 "스레드 풀" 메커니즘이 구현되었습니다. 그것이 무엇이고 어떻게 사용되는지에 대해서는 나중에 다루겠습니다. 이제 우리의 적과 직접 맞서 봅시다.
편집자 - NGINX Plus R7 개요는 블로그에서 NGINX Plus R7 발표를 참조하세요.
NGINX Plus R7의 다른 새로운 기능에 대한 자세한 설명은 다음 관련 블로그 게시물을 참조하세요.
먼저, 문제를 더 잘 이해하기 위해 NGINX의 작동 방식에 대해 몇 가지 설명해 보겠습니다.
일반적으로 NGINX는 이벤트 핸들러이자, 연결에서 발생하는 모든 이벤트에 대한 정보를 커널에서 수신한 후 운영 체제에 수행할 작업에 대한 명령을 내리는 컨트롤러입니다. 실제로 NGINX는 운영 체제를 조정하여 모든 어려운 작업을 수행하고, 운영 체제는 바이트를 읽고 보내는 일상적인 작업을 수행합니다. 그래서 NGINX가 신속하고 시기적절하게 대응하는 것이 매우 중요합니다.
이벤트에는 시간 초과, 읽거나 쓸 준비가 된 소켓에 대한 알림, 발생한 오류에 대한 알림 등이 있습니다. NGINX는 여러 이벤트를 수신한 후 하나씩 처리하여 필요한 작업을 수행합니다. 따라서 모든 처리가 하나의 스레드에서 대기열을 통한 간단한 루프로 수행됩니다. NGINX는 큐에서 이벤트를 제거하고 소켓을 쓰거나 읽는 등의 방법으로 이에 반응합니다. 대부분의 경우, 이 작업은 매우 빠르게 진행됩니다(아마도 메모리에 있는 일부 데이터를 복사하는 데 몇 번의 CPU 사이클만 필요할 수도 있음). NGINX는 대기열에 있는 모든 이벤트를 즉시 처리합니다.
하지만 길고 힘든 작업이 진행된 경우에는 어떻게 될까요? 이 작업이 완료될 때까지 이벤트 처리의 전체 주기가 중단됩니다.
따라서 "차단 작업"이란 상당한 시간 동안 이벤트 처리 주기를 중단하는 모든 작업을 의미합니다. 다양한 이유로 작업이 차단될 수 있습니다. 예를 들어, NGINX는 길고 CPU를 많이 사용하는 처리 작업으로 바쁠 수도 있고, 리소스(예: 하드 드라이브, 동기 방식으로 데이터베이스에서 응답을 받는 뮤텍스나 라이브러리 함수 호출 등)에 액세스하기 위해 기다려야 할 수도 있습니다. 중요한 점은 이러한 작업을 처리하는 동안 작업자 프로세스는 다른 작업을 수행할 수 없고 다른 이벤트를 처리할 수 없다는 것입니다. 사용 가능한 시스템 리소스가 더 있고 대기열에 있는 일부 이벤트가 해당 리소스를 활용할 수 있더라도 마찬가지입니다.
매장에 긴 줄이 서 있는 판매원을 상상해 보세요. 줄을 선 첫 번째 사람은 매장에는 없지만 창고에 있는 것을 요구합니다. 영업사원은 창고로 가서 상품을 배달합니다. 이제 줄을 선 사람들 모두가 이 배달을 위해 몇 시간을 기다려야 하며, 줄을 선 모든 사람이 불만스러워합니다. 사람들의 반응이 어떨지 상상해 보세요? 이 시간 동안 줄을 서 있는 모든 사람의 대기 시간은 늘어나지만, 그들이 사려고 하는 물건은 바로 그 매장에 있을 수도 있다.
메모리에 캐시되지 않고 디스크에서 읽어야 하는 파일을 읽으려고 할 때 NGINX에서도 거의 동일한 상황이 발생합니다. 하드 드라이브는 느리며(특히 회전하는 드라이브의 경우) 대기열에 있는 다른 요청은 드라이브에 액세스할 필요가 없더라도 어차피 기다려야 합니다. 결과적으로 지연 시간이 늘어나고 시스템 리소스가 완전히 활용되지 않게 됩니다.
일부 운영 체제는 파일을 읽고 보내기 위한 비동기 인터페이스를 제공하며 NGINX는 이 인터페이스를 사용할 수 있습니다( aio 지시어 참조). 여기서 좋은 예는 FreeBSD입니다. 불행히도, 리눅스에 대해서는 같은 말을 할 수 없습니다. Linux는 파일을 읽기 위한 일종의 비동기 인터페이스를 제공하지만, 몇 가지 심각한 단점이 있습니다. 그 중 하나는 파일 액세스와 버퍼에 대한 정렬 요구 사항인데, NGINX는 이를 잘 처리합니다. 하지만 두 번째 문제는 더 심각합니다. 비동기 인터페이스는 파일 기술자에 O_DIRECT
플래그를 설정해야 하는데, 이는 파일에 대한 모든 액세스가 메모리의 캐시를 우회하여 하드 디스크의 부하를 증가시킨다는 것을 의미합니다. 이는 분명히 많은 경우에 최적이 아닙니다.
특히 이 문제를 해결하기 위해 NGINX 1.7.11 및 NGINX Plus 릴리스 7에서 스레드 풀이 도입되었습니다.
이제 스레드 풀이 무엇이고 어떻게 작동하는지 알아보겠습니다.
먼 창고에서 상품을 배달하는 불쌍한 판매 보조원의 이야기로 돌아가 보겠습니다. 하지만 그는 더 똑똑해졌습니다.(아니면 화난 손님들에게 구타당한 후에 더 똑똑해졌을까요?) 배달 서비스를 고용했습니다. 이제 누군가가 멀리 떨어진 창고에 무언가를 요청하면 직접 창고에 가는 대신 배달 서비스에 주문을 내면 배달 서비스에서 주문을 처리하고, 판매 직원은 다른 고객에게 서비스를 제공합니다. 따라서 매장에 상품이 없는 고객만 배송을 기다리고 다른 고객은 즉시 서비스를 받을 수 있습니다.
NGINX의 경우 스레드 풀은 배달 서비스의 기능을 수행합니다. 이는 작업 대기열과 대기열을 처리하는 여러 개의 스레드로 구성됩니다. 작업자 프로세스가 잠재적으로 긴 작업을 수행해야 하는 경우, 작업을 스스로 처리하는 대신 풀의 대기열에 작업을 넣습니다. 그러면 모든 자유 스레드가 해당 작업을 가져와서 처리할 수 있습니다.
그러면 또 다른 대기열이 있는 것 같습니다. 오른쪽. 하지만 이 경우 대기열은 특정 리소스에 의해 제한됩니다. 드라이브가 데이터를 생성할 수 있는 속도보다 더 빠르게 드라이브에서 데이터를 읽을 수는 없습니다. 이제 최소한 드라이브가 다른 이벤트 처리를 지연시키지 않고 파일에 액세스해야 하는 요청만 대기합니다.
"디스크에서 읽기" 작업은 종종 차단 작업의 가장 일반적인 예로 사용되지만 실제로 NGINX의 스레드 풀 구현은 주 작업 주기에서 처리하기에 적합하지 않은 모든 작업에 사용될 수 있습니다.
현재, 스레드 풀로의 오프로드는 세 가지 필수 작업에 대해서만 구현됩니다. 대부분 운영 체제의 read()
시스템 호출, Linux의 sendfile()
, 캐시와 같은 임시 파일을 쓸 때 사용되는 Linux의 aio_write()
입니다. 우리는 구현에 대한 테스트와 벤치마킹을 계속할 것이며, 확실한 이점이 있다면 향후 릴리스에서 다른 작업을 스레드 풀로 오프로드할 수도 있습니다.
편집기 - NGINX 1.9.13 및 NGINX Plus R9에서 aio_write()
시스템 호출 에 대한 지원이 추가되었습니다.
이제 이론에서 실천으로 옮길 때입니다. 스레드 풀을 사용하는 효과를 보여주기 위해 차단 및 비차단 작업의 최악의 조합을 시뮬레이션하는 합성 벤치마크를 수행하려고 합니다.
메모리에 들어가지 않는 데이터 세트가 필요합니다. RAM이 48GB인 머신에서 4MB 파일에 256GB의 임의 데이터를 생성한 다음 NGINX 1.9.0을 구성하여 제공했습니다.
구성은 매우 간단합니다.
worker_processes 16;
이벤트 {
accept_mutex off;
}
http {
mime.types 포함;
기본 유형 application/octet-stream;
액세스 로그 off;
sendfile on;
sendfile_max_chunk 512k;
서버 {
listen 8000;
위치 / {
루트 / 저장소;
}
}
}
보시다시피, 더 나은 성능을 달성하기 위해 몇 가지 튜닝이 수행되었습니다. 로깅
및 accept_mutex가
비활성화되었고, sendfile이
활성화되었으며, sendfile_max_chunk가
설정되었습니다. 마지막 지시어는 sendfile()
호출을 차단하는 데 소요되는 최대 시간을 줄일 수 있는데, 그 이유는 NGINX가 전체 파일을 한 번에 보내지 않고 512KB 청크로 전송하기 때문입니다.
이 머신에는 Intel Xeon E5645(총 12개 코어, 24개 HT 스레드) 프로세서 2개와 10Gbps 네트워크 인터페이스가 있습니다. 디스크 하위 시스템은 RAID10 어레이로 배열된 4개의 Western Digital WD1003FBYX 하드 드라이브로 표현됩니다. 이 모든 하드웨어는 Ubuntu Server 14.04.1 LTS로 구동됩니다.
고객은 동일한 사양을 갖춘 두 대의 기계로 표현됩니다. 이러한 머신 중 하나에서 wrk는
Lua 스크립트를 사용하여 부하를 생성합니다. 이 스크립트는 200개의 병렬 연결을 사용하여 무작위 순서로 서버에 파일을 요청하고, 각 요청은 캐시 미스와 디스크에서의 차단 읽기로 이어질 가능성이 높습니다. 이 부하를 무작위 부하라고 부르겠습니다.
두 번째 클라이언트 머신에서는 50개의 병렬 연결을 사용하여 동일한 파일을 여러 번 요청하는 또 다른 wrk
사본을 실행합니다. 이 파일은 자주 접근되므로 항상 메모리에 남아 있습니다. 일반적인 상황에서 NGINX는 이러한 요청을 매우 빠르게 처리하지만, 작업자 프로세스가 다른 요청에 의해 차단되는 경우 성능이 저하됩니다. 이 하중을 일정 하중이라고 부르자.
성능은 ifstat를
사용하여 서버 머신의 처리량을 모니터링하고 두 번째 클라이언트에서 작업
결과를 얻어 측정됩니다.
이제 스레드 풀 없이 처음 실행하면 그다지 흥미로운 결과가 나오지 않습니다.
% ifstat -bi eth2 eth2 Kbps 입력 Kbps 출력 5531.24 1.03e+06 4855.23 812922.7 5994.66 1.07e+06 5476.27 981529.3 6353.62 1.12e+06 5166.17 892770.3 5522.81 978540.8 6208.10 985466.7 6370.79 1.12e+06 6123.33 1.07e+06
보시다시피, 이 구성을 사용하면 서버는 총 1Gbps의 트래픽을 생성할 수 있습니다. top
의 출력에서 모든 작업자 프로세스가 대부분의 시간을 차단 I/O에 소비하는 것을 볼 수 있습니다( D
상태에 있음):
상위 - 10:40:47 11일 동안 업, 1:32, 1명의 사용자, 로드 평균: 49.61, 45.77 62.89작업:375 총,2 달리기,373 자고 있는,0 멈췄다,0 좀비 %Cpu(s):0.0 우리를,0.3 시,0.0 니,67.7 ID,31.9 와,0.0 안녕,0.0 시,0.0 st KiB 메모:49453440 총,49149308 사용된,304132 무료,98780 버퍼 KiB 스왑:10474236 총,20124 사용된,10454112 무료,46903412 캐시된 메모리 PID 사용자 PR NI VIRT RES SHR S %CPU %MEM 시간+ 명령 4639 vbart 20 0 47180 28152 496 D 0.7 0.1 0:00.17 nginx 4632 vbart 20 0 47180 28196 536 D 0.3 0.1 0:00.11 nginx 4633 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.11 nginx 4635 vbart 20 0 47180 28136 480 D 0.3 0.1 0:00.12 nginx 4636 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.14 nginx 4637 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.10 nginx 4638 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx 4640 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx 4641 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx 4642 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.11 nginx 4643 vbart 20 0 47180 28276 536 D 0.3 0.1 0:00.29 nginx 4644 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.11 nginx 4645 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.17 nginx 4646 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx 4647 vbart 20 0 47180 28208 532 D 0.3 0.1 0:00.17 nginx 4631 vbart 20 0 47180 756 252 S 0.0 0.1 0:00.00 nginx 4634 vbart 20 0 47180 28208 536 D 0.0 0.1 0:00.11 nginx< 4648 vbart 20 0 25232 1956 1160 R 0.0 0.0 0:00.08 상위 25921 vbart 20 0 121956 2232 1056 S 0.0 0.0 0:01.97 sshd 25923 vbart 20 0 40304 4160 2208 S 0.0 0.0 0:00.53 zsh
이 경우 처리량은 디스크 하위 시스템에 의해 제한되는 반면 CPU는 대부분의 시간 동안 유휴 상태입니다. wrk
의 결과도 매우 낮습니다.
1m 테스트 실행 @ http://192.0.2.1:8000/1/1/1 12개 스레드와 50개 연결
스레드 통계 평균 표준 편차 최대 +/- 표준 편차
대기 시간 7.42초 5.31초 24.41초 74.73%
요청/초 0.15 0.36 1.00 84.62%
1.01m에서 488개 요청, 2.01GB 읽기
초당 요청: 8.08
전송/초: 34.07MB
그리고 이건 메모리에서 제공되어야 하는 파일이라는 걸 기억하세요! 지나치게 큰 대기 시간은 모든 작업자 프로세스가 첫 번째 클라이언트에서 온 200개 연결로 인해 생성된 무작위 부하를 처리하기 위해 드라이브에서 파일을 읽는 데 바빠서 적절한 시간 내에 요청을 처리할 수 없기 때문입니다.
이제 스레드 풀을 활용할 시간입니다. 이를 위해 location
블록에 aio
threads
지시문을 추가하기만 하면 됩니다.
위치 / { root /storage;
aio threads;
}
NGINX에 구성을 다시 로드하도록 요청합니다.
그 후에 우리는 테스트를 반복합니다:
% ifstat -bi eth2 eth2 입력 Kbps 출력 Kbps 60915.19 9.51e+06 59978.89 9.51e+06 60122.38 9.51e+06 61179.06 9.51e+06 61798.40 9.51e+06 57072.97 9.50e+06 56072.61 9.51e+06 61279.63 9.51e+06 61243.54 9.51e+06 59632.50 9.50e+06
이제 우리 서버는 스레드 풀 없이 약 1Gbps를 생성하는 데 비해 9.5Gbps를 생성합니다!
더 많은 것을 생산할 수도 있겠지만, 이미 실질적인 최대 네트워크 용량에 도달했기 때문에, 이 테스트에서는 NGINX가 네트워크 인터페이스에 의해 제한됩니다. 작업자 프로세스는 대부분의 시간을 잠자고 새 이벤트를 기다리며 보냅니다( top
의 S
상태에 있음).
상위 - 10:43:17 11일 동안 업, 1:35, 1명의 사용자, 로드 평균: 172.71, 93.84, 77.90작업:376 총,1 달리기,375 자고 있는,0 멈췄다,0 좀비 %Cpu(s):0.2 우리를,1.2 시,0.0 니,34.8 ID,61.5 와,0.0 안녕,2.3 시,0.0 st KiB 메모:49453440 총,49096836 사용된,356604 무료,97236 버퍼 KiB 스왑:10474236 총,22860 사용된,10451376 무료,46836580 캐시된 메모리 PID 사용자 PR NI VIRT RES SHR S %CPU %MEM 시간+ 명령 4654 vbart 20 0 309708 28844 596 S 9.0 0.1 0:08.65 nginx 4660 vbart 20 0 309748 28920 596 S 6.6 0.1 0:14.82 nginx 4658 vbart 20 0 309452 28424 520 S 4.3 0.1 0:01.40 nginx 4663 vbart 20 0 309452 28476 572 S 4.3 0.1 0:01.32 nginx 4667 vbart 20 0 309584 28712 588 S 3.7 0.1 0:05.19 nginx 4656 vbart 20 0 309452 28476 572 S 3.3 0.1 0:01.84 nginx 4664 vbart 20 0 309452 28428 524 S 3.3 0.1 0:01.29 nginx 4652 vbart 20 0 309452 28476 572 S 3.0 0.1 0:01.46 nginx 4662 vbart 20 0 309552 28700 596 S 2.7 0.1 0:05.92 nginx 4661 vbart 20 0 309464 28636 596 S 2.3 0.1 0:01.59 nginx 4653 vbart 20 0 309452 28476 572 S 1.7 0.1 0:01.70 nginx 4666 vbart 20 0 309452 28428 524 S 1.3 0.1 0:01.63 nginx 4657 vbart 20 0 309584 28696 592 S 1.0 0.1 0:00.64 nginx 4655 vbart 20 0 30958 28476 572 S 0.7 0.1 0:02.81 nginx 4659 vbart 20 0 309452 28468 564 S 0.3 0.1 0:01.20 nginx 4665 vbart 20 0 309452 28476 572 S 0.3 0.1 0:00.71 nginx 5180 vbart 20 0 25232 1952 1156 R 0.0 0.0 0:00.45 상단 4651 vbart 20 0 20032 752 252 S 0.0 0.0 0:00.00 nginx 25921 vbart 20 0 121956 2176 1000 S 0.0 0.0 0:01.98 sshd 25923 vbart 20 0 40304 3840 2208 S 0.0 0.0 0:00.54 zsh
여전히 CPU 리소스가 충분합니다.
wrk
의 결과 :
1m 테스트 실행 @ http://192.0.2.1:8000/1/1/1 12개 스레드와 50개 연결
스레드 통계 평균 표준 편차 최대 +/- 표준 편차
대기 시간 226.32ms 392.76ms 1.72초 93.48%
요청/초 20.02 10.84 59.00 65.91%
1.00m에서 15045개 요청, 58.86GB 읽기
초당 요청: 250.57
전송/초: 0.98GB
4MB 파일을 제공하는 데 걸리는 평균 시간이 7.42초에서 226.32밀리초(33배 감소)로 단축되었고, 초당 요청 수는 31배(250개 대 8개) 증가했습니다!
그 설명은 작업자 프로세스가 읽기 작업을 차단하는 동안 요청이 더 이상 이벤트 대기열에서 처리를 기다리지 않고 자유 스레드에 의해 처리된다는 것입니다. 디스크 하위 시스템이 첫 번째 클라이언트 머신에서 발생하는 무작위 부하를 처리하는 데 최선을 다하는 한, NGINX는 나머지 CPU 리소스와 네트워크 용량을 사용하여 메모리에서 두 번째 클라이언트의 요청을 처리합니다.
작업 차단에 대한 모든 우려와 흥미로운 결과에 따라, 여러분 중 대부분은 이미 서버에서 스레드 풀을 구성하려고 할 것입니다. 서두르지 마세요.
사실 다행히도 대부분의 파일 읽기 및 보내기 작업은 느린 하드 드라이브를 다루지 않습니다. 데이터 세트를 저장할 만큼 충분한 RAM이 있다면 운영 체제는 자주 사용되는 파일을 소위 "페이지 캐시"에 캐시할 만큼 똑똑해질 것입니다.
페이지 캐시는 꽤 잘 작동하며 NGINX가 거의 모든 일반적인 사용 사례에서 뛰어난 성능을 보일 수 있도록 해줍니다. 페이지 캐시에서 읽는 작업은 매우 빠르며 아무도 이러한 작업을 "차단"이라고 부를 수 없습니다. 반면, 스레드 풀로 오프로드하는 데는 약간의 오버헤드가 발생합니다.
따라서 적당한 양의 RAM이 있고 작업 데이터 세트가 크지 않다면 NGINX는 스레드 풀을 사용하지 않고도 가장 최적의 방식으로 작동합니다.
읽기 작업을 스레드 풀로 오프로드하는 것은 매우 구체적인 작업에 적용할 수 있는 기술입니다. 이 기능은 자주 요청되는 콘텐츠의 양이 운영 체제의 VM 캐시에 맞지 않는 경우에 가장 유용합니다. 예를 들어, 부하가 많은 NGINX 기반 스트리밍 미디어 서버의 경우가 그렇습니다. 이는 우리가 벤치마크에서 시뮬레이션한 상황입니다.
읽기 작업을 스레드 풀로 오프로드하는 기능을 개선하면 좋을 것 같습니다. 우리에게 필요한 것은 필요한 파일 데이터가 메모리에 있는지 아닌지 알 수 있는 효율적인 방법뿐이며, 후자의 경우에만 읽기 작업을 별도의 스레드로 오프로드해야 합니다.
판매 비유로 돌아가면, 현재 판매원은 요청한 품목이 매장에 있는지 알 수 없고 항상 모든 주문을 배달 서비스에 전달하거나 항상 직접 처리해야 합니다.
원인은 운영 체제에 이 기능이 없기 때문입니다. 2010년에 fincore()
시스템 호출로 Linux에 추가하려는 첫 번째 시도가 있었지만 실현되지 않았습니다. 나중에 RWF_NONBLOCK
플래그를 사용하여 새로운 preadv2()
시스템 호출로 구현하려는 시도가 여러 번 있었습니다(자세한 내용은 LWN.net에서 비차단 버퍼링 파일 읽기 작업 및 비동기 버퍼링 읽기 작업을 참조하세요). 이 모든 패치의 운명은 아직 불확실합니다. 여기서 슬픈 점은 이러한 패치가 아직 커널에 수용되지 않은 주된 이유가 지속적인 bikeshedding 때문인 것 같다는 점입니다.
반면, FreeBSD 사용자는 전혀 걱정할 필요가 없습니다. FreeBSD에는 이미 파일을 읽기 위한 충분히 좋은 비동기 인터페이스가 있는데, 스레드 풀 대신 이를 사용해야 합니다.
따라서 사용 사례에서 스레드 풀을 사용하면 어떤 이점을 얻을 수 있다고 확신한다면 구성에 대해 자세히 알아볼 때입니다.
구성은 매우 쉽고 유연합니다. 가장 먼저 해야 할 일은 configure
명령에 --with-threads
인수를 추가하여 컴파일한 NGINX 버전 1.7.11 이상입니다. NGINX Plus 사용자는 릴리스 7 이상이 필요합니다. 가장 단순한 경우, 구성은 매우 단순해 보입니다. 적절한 컨텍스트에 aio
threads
지시문을 포함하기만 하면 됩니다.
# 'http', 'server', 또는 'location' 컨텍스트 스레드에서;
이는 스레드 풀의 최소 가능 구성입니다. 실제로 이는 다음 구성의 단축 버전입니다.
# 'main' 컨텍스트에서 thread_pool 기본 threads=32 max_queue=65536;
# 'http', 'server' 또는 'location' 컨텍스트에서
aio threads=default;
32개의 작업 스레드와 최대 65536개의 작업 대기열을 갖는 default 라는 스레드 풀을 정의합니다. 작업 대기열이 과부하되면 NGINX는 요청을 거부하고 다음 오류를 기록합니다.
스레드 풀 " NAME " 큐 오버플로: N개의 작업이 대기 중입니다
이 오류는 스레드가 작업이 큐에 추가되는 것만큼 빠르게 작업을 처리할 수 없다는 것을 의미합니다. 최대 대기열 크기를 늘려볼 수 있지만 그래도 도움이 되지 않는다면 시스템이 그렇게 많은 요청을 처리할 수 없다는 것을 의미합니다.
이미 알고 계시듯이 thread_pool
지시어를 사용하면 스레드 수, 대기열의 최대 길이, 특정 스레드 풀의 이름을 구성할 수 있습니다. 마지막은 여러 개의 독립적인 스레드 풀을 구성하여 구성 파일의 여러 위치에서 사용하여 다양한 목적을 달성할 수 있음을 의미합니다.
# 'main' 컨텍스트에서
thread_pool one threads=128 max_queue=0;
thread_pool two threads=32;
http {
server {
location /one {
aio threads=one;
}
location /two {
aio threads=two;
}
}
# ...
}
max_queue
매개변수가 지정되지 않으면 기본적으로 65536 값이 사용됩니다. 표시된 대로 max_queue를
0으로 설정할 수 있습니다. 이 경우 스레드 풀은 구성된 스레드 수만큼의 작업만 처리할 수 있으며, 대기열에서 대기하는 작업은 없습니다.
이제 3개의 하드 드라이브가 있는 서버가 있다고 가정해 보겠습니다. 그리고 이 서버를 백엔드의 모든 응답을 캐시하는 "캐싱 프록시"로 작동시키고자 합니다. 캐시된 데이터의 예상 양이 사용 가능한 RAM을 훨씬 초과합니다. 실제로는 개인 CDN을 위한 캐싱 노드입니다. 물론 이 경우 가장 중요한 것은 드라이브에서 최대 성능을 달성하는 것입니다.
옵션 중 하나는 RAID 어레이를 구성하는 것입니다. 이런 접근 방식에는 장단점이 있습니다. 이제 NGINX를 사용하면 또 다른 것을 수행할 수 있습니다.
# 각 하드 드라이브가 다음 디렉토리 중 하나에 마운트되어 있다고 가정합니다.# /mnt/disk1, /mnt/disk2 또는 /mnt/disk3
# 'main' 컨텍스트에서
thread_pool pool_1 threads=16;
thread_pool pool_2 threads=16;
thread_pool pool_3 threads=16;
http {
proxy_cache_path /mnt/disk1 levels=1:2 keys_zone=cache_1:256m max_size=1024G
use_temp_path=off;
proxy_cache_path /mnt/disk2 levels=1:2 keys_zone=cache_2:256m max_size=1024G
use_temp_path=off;
proxy_cache_path /mnt/disk3 레벨=1:2 키_존=캐시_3:256m 최대_크기=1024G
use_temp_path=off;
split_clients $request_uri $disk {
33.3% 1;
33.3% 2;
* 3;
}
server {
# ...
location / {
proxy_pass http://backend;
proxy_cache_key $request_uri;
proxy_cache cache_$disk;
aio threads=pool_$disk;
sendfile 켜짐;
}
}
}
이 구성에서 thread_pool
지시문은 각 디스크에 대한 전용 독립 스레드 풀을 정의하고 proxy_cache_path
지시문은 각 디스크에 대한 전용 독립 캐시를 정의합니다.
split_clients
모듈은 캐시 간(그리고 결과적으로 디스크 간) 부하 분산에 사용되며, 이 작업에 완벽하게 적합합니다.
proxy_cache_path
지시문에 use_temp_path=off
매개변수를 설정하면 NGINX가 해당 캐시 데이터가 있는 디렉토리에 임시 파일을 저장하게 됩니다. 캐시를 업데이트할 때 하드 드라이브 간에 응답 데이터를 복사하는 것을 방지해야 합니다.
이 모든 것이 합쳐져서 NGINX가 별도의 스레드 풀을 통해 드라이브와 병렬적이고 독립적으로 상호 작용하기 때문에 현재 디스크 하위 시스템에서 최대 성능을 얻을 수 있습니다. 각 드라이브는 파일을 읽고 보내기 위한 전용 작업 대기열이 있는 16개의 독립 스레드로 구성됩니다.
귀하의 고객들은 이런 맞춤형 접근 방식을 좋아할 거라고 확신합니다. 하드 드라이브도 이것을 좋아하는지 확인하세요.
이 예제는 NGINX가 사용자의 하드웨어에 맞게 얼마나 유연하게 조정될 수 있는지를 잘 보여줍니다. NGINX에 머신과 데이터 세트와의 상호 작용에 대한 최상의 방법에 대한 지침을 제공하는 것과 같습니다. 그리고 사용자 공간에서 NGINX를 미세 조정하면 소프트웨어, 운영 체제, 하드웨어가 가장 최적의 모드로 함께 작동하여 모든 시스템 리소스를 가능한 한 효과적으로 활용할 수 있습니다.
요약하자면, 스레드 풀은 NGINX의 잘 알려지고 오랜 적대 세력인 블로킹을 제거함으로써 NGINX의 성능을 새로운 수준으로 끌어올리는 훌륭한 기능입니다. 특히, 정말 방대한 양의 콘텐츠를 다룰 때 더욱 그렇습니다.
그리고 앞으로 더 많은 것이 나올 것입니다. 이전에 언급했듯이, 이 새로운 인터페이스를 사용하면 성능 저하 없이 길고 막힌 작업을 오프로드할 수 있습니다. NGINX는 수많은 새로운 모듈과 기능을 탑재함으로써 새로운 지평을 열었습니다. 많은 인기 있는 라이브러리는 여전히 비동기 비차단 인터페이스를 제공하지 않아 이전에는 NGINX와 호환되지 않았습니다. 우리는 어떤 라이브러리의 비차단 프로토타입을 개발하는 데 많은 시간과 리소스를 투자할 수 있지만, 항상 노력할 가치가 있을까요? 이제 스레드 풀이 탑재되어 이런 라이브러리를 비교적 쉽게 사용할 수 있으며, 성능에 영향을 주지 않고도 모듈을 만들 수 있습니다.
계속 지켜봐 주세요.
NGINX Plus에서 스레드 풀을 직접 사용해보세요. 오늘 무료 30일 체험판을 시작하거나, 사용 사례에 대해 논의하기 위해 저희에게 연락하세요 .
"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."