Nginx 버퍼 튜닝: `client_body_buffer_size` 및 `proxy_buffer_size` 최적화
메모리 낭비나 불필요한 디스크 버퍼링 없이 Nginx 요청 및 프록시 버퍼를 튜닝합니다.
Nginx 버퍼 튜닝: client_body_buffer_size 및 proxy_buffer_size 최적화
Nginx 버퍼 튜닝은 모든 버퍼를 크게 만드는 것이 아닙니다. 어떤 데이터를 메모리에 저장하고, 어떤 데이터를 안전하게 디스크로 넘기며, 어떤 응답을 완전히 버퍼링하지 않고 스트리밍할지 결정하는 것입니다.
혼란스러운 점은 여러 지시어가 비슷하게 들린다는 것입니다. client_body_buffer_size는 클라이언트로부터 오는 요청 본문에 적용됩니다. proxy_buffer_size와 proxy_buffers는 Nginx가 리버스 프록시로 작동할 때 업스트림 서버에서 돌아오는 응답에 적용됩니다. FastCGI에는 자체적인 일치 지시어가 있습니다. 잘못된 쪽을 튜닝하면 아무것도 개선되지 않습니다.
좋은 튜닝 세션은 증상으로 시작합니다:
- Nginx 오류 로그에 임시 파일이 언급됩니다.
- 업로드 또는 대형 POST 요청이 느리게 느껴집니다.
- 리버스 프록시된 API 응답이 부하 중에 일시 중지됩니다.
- 작업자가 예상보다 더 많은 메모리를 사용합니다.
- 큰 응답 헤더가 업스트림 오류를 유발합니다.
이러한 증상 모두 동일한 해결책을 가지지는 않습니다. 더 큰 버퍼는 디스크 I/O를 줄일 수 있지만, 요청당 메모리 압력도 증가시킵니다. 바쁜 사이트에서는 요청당 작아 보이는 설정이 수천 개의 동시 연결로 곱해지면 커질 수 있습니다.
요청 본문 버퍼링: client_body_buffer_size
client_body_buffer_size는 Nginx가 클라이언트 요청의 본문을 읽는 동안 사용되는 버퍼를 제어합니다. 양식 제출, JSON POST 본문 및 업로드를 생각하세요. 요청 본문이 이 버퍼보다 크면 Nginx는 그 일부를 client_body_temp_path 아래의 임시 파일에 쓸 수 있습니다.
기본 설정은 다음과 같습니다:
http {
client_body_buffer_size 128k;
}
이것은 최대 허용 업로드 크기를 변경하지 않습니다. 최대 업로드 크기는 client_max_body_size에 의해 제어됩니다:
server {
client_max_body_size 20m;
client_body_buffer_size 128k;
}
이 두 지시어는 다른 질문에 답합니다. client_max_body_size는 "Nginx가 요청을 거부하기 전에 요청이 얼마나 클 수 있습니까?"라고 말합니다. client_body_buffer_size는 "Nginx가 임시 파일을 사용하기 전에 요청 본문 중 얼마나 많은 부분을 메모리에 유지해야 합니까?"라고 말합니다.
대부분의 요청 본문이 32KB 미만이고 일부가 200KB인 JSON API의 경우 128k 버퍼는 과도하지 않으면서 임시 파일 쓰기를 줄일 수 있습니다. 50MB 비디오를 허용하는 파일 업로드 서비스의 경우 전역적으로 client_body_buffer_size 50m을 설정하는 것은 나쁜 절충입니다. 디스크 버퍼링이 허용될 수 있는 워크로드에 너무 많은 메모리를 예약하게 됩니다.
오류 로그에서 다음과 같은 단서를 확인하세요:
[warn] a client request body is buffered to a temporary file
이 경고는 자동으로 실패를 의미하지 않습니다. Nginx가 본문이 메모리 내 버퍼를 초과했음을 알리는 것입니다. 대용량 업로드 중에 가끔 발생한다면 괜찮습니다. 일반적인 API 요청에서 지속적으로 발생한다면 버퍼를 튜닝하거나 대용량 페이로드를 보내는 클라이언트를 수정하세요.
프록시 응답 버퍼링: proxy_buffer_size 및 proxy_buffers
Nginx가 업스트림 앱으로 프록시할 때 응답 버퍼링은 일반적으로 기본적으로 활성화됩니다. Nginx는 업스트림 응답을 빠르게 읽고 버퍼에 저장한 다음 클라이언트로 보냅니다. 응답이 메모리 버퍼에 맞지 않으면 일부가 임시 파일에 기록될 수 있습니다. 공식 Nginx 프록시 모듈 문서는 이 동작을 설명하고 임시 파일 쓰기를 proxy_max_temp_file_size 및 proxy_temp_file_write_size에 연결합니다.
첫 번째 버퍼는 proxy_buffer_size에 의해 제어됩니다:
proxy_buffer_size 16k;
이 버퍼는 응답 헤더와 응답의 첫 번째 부분을 처리합니다. 업스트림이 많은 쿠키, 큰 인증 헤더 또는 과도한 추적 메타데이터와 같은 큰 헤더를 보내는 경우 "upstream sent too big header"와 같은 오류가 발생할 수 있습니다. 이 경우 proxy_buffer_size가 검토할 지시어인 경우가 많습니다.
나머지 응답 버퍼는 proxy_buffers에 의해 제어됩니다:
proxy_buffers 8 32k;
이는 프록시된 응답 데이터에 대해 각각 32KB의 8개 버퍼를 의미합니다. 모든 요청이 처음부터 끝까지 항상 전체 양을 소비한다는 의미는 아니지만, 부하 시 이러한 설정을 요청당 메모리 설정으로 취급해야 합니다.
일반적인 리버스 프록시 블록은 다음과 같습니다:
location /api/ {
proxy_pass http://app_backend;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
}
이것을 맹목적으로 복사하지 마세요. 작은 HTML 사이트, JSON API 및 파일 다운로드 서비스는 각각 다른 요구 사항이 있습니다.
임시 파일 및 proxy_max_temp_file_size
프록시된 응답 버퍼링이 활성화되고 응답이 구성된 버퍼보다 큰 경우 Nginx는 그 일부를 디스크에 쓸 수 있습니다. proxy_max_temp_file_size는 해당 임시 파일의 크기를 제한합니다. Nginx의 기본값은 일반적으로 1024m으로 문서화되어 있으며, 0으로 설정하면 프록시된 응답을 임시 파일에 버퍼링하는 것을 비활성화합니다.
location /api/ {
proxy_pass http://app_backend;
proxy_buffering on;
proxy_max_temp_file_size 50m;
}
0을 신중하게 사용하세요:
proxy_max_temp_file_size 0;
이는 큰 응답이 무료가 된다는 의미가 아닙니다. Nginx가 버퍼링된 프록시 응답에 대해 임시 파일을 사용하지 않는다는 의미입니다. 응답, 클라이언트 속도 및 버퍼링 동작에 따라 더 많은 메모리, 다른 버퍼링 설정 또는 스트리밍 디자인이 필요할 수 있습니다.
Nginx 문서의 중요한 미묘한 점: proxy_max_temp_file_size 제한은 디스크에 캐시되거나 저장될 응답에는 적용되지 않습니다. proxy_cache 또는 proxy_store를 사용하는 경우 해당 설정을 별도로 검토하세요.
proxy_buffering off가 더 나은 답인 경우
때로는 응답을 전혀 버퍼링하지 않아야 합니다. 스트리밍 엔드포인트, 서버 전송 이벤트, 대용량 다운로드 및 장기 실행 출력은 종종 프록시 버퍼링을 비활성화하면 더 잘 작동합니다:
location /events/ {
proxy_pass http://app_backend;
proxy_buffering off;
}
버퍼링이 꺼져 있으면 Nginx는 전체 응답을 수집하려고 시도하는 대신 업스트림 응답을 수신하는 대로 클라이언트에 전달합니다. proxy_buffer_size는 여전히 중요합니다. Nginx는 업스트림에서 수신된 데이터에 대한 버퍼가 필요하기 때문입니다. 그러나 proxy_buffers 및 임시 파일 동작은 덜 중요해집니다.
절충점을 이해하지 않고 전역적으로 버퍼링을 끄지 마세요. 버퍼링은 느린 클라이언트로부터 업스트림 서버를 보호합니다. 클라이언트가 느리게 다운로드하고 버퍼링이 꺼져 있으면 업스트림 연결이 더 오래 점유될 수 있습니다.
PHP 및 유사한 설정을 위한 FastCGI 버퍼
Nginx가 PHP-FPM과 통신하는 경우 proxy_* 지시어가 필요한 것이 아닙니다. FastCGI는 일치하는 이름을 사용합니다:
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_buffer_size 16k;
fastcgi_buffers 8 32k;
}
동일한 논리가 적용됩니다: 첫 번째 버퍼는 헤더와 초기 데이터를 처리하고, 버퍼 배열은 더 많은 응답 콘텐츠를 처리합니다. PHP가 큰 헤더를 보내는 경우 fastcgi_buffer_size에 주의가 필요할 수 있습니다. PHP 생성 페이지가 디스크로 넘어가는 경우 fastcgi_buffers와 워크로드를 검토하세요.
추측 없이 튜닝하는 방법
로그 확인부터 시작하세요:
sudo tail -f /var/log/nginx/error.log
요청 본문이나 업스트림 응답이 임시 파일에 버퍼링되고 있다는 경고를 찾으세요. 그런 다음 관련된 실제 크기를 측정하세요. API의 경우 액세스 로그, 애플리케이션 로그 또는 관찰 가능성 시스템에서 요청 및 응답 크기를 샘플링하세요. 업로드의 경우 메타데이터 요청을 파일 업로드 엔드포인트와 분리하세요. 동일한 가정을 공유해서는 안 됩니다.
가장 좁은 컨텍스트에서 튜닝하세요. 다음과 같이 하는 대신:
http {
client_body_buffer_size 10m;
}
업로드 엔드포인트에 대해서만 다음과 같이 하는 것을 선호하세요:
location /upload/ {
client_max_body_size 50m;
client_body_buffer_size 512k;
proxy_pass http://upload_backend;
}
가끔 1MB 응답이 있는 JSON API의 경우 해당 API 위치만 튜닝할 수 있습니다:
location /reports/ {
proxy_pass http://report_backend;
proxy_buffering on;
proxy_buffer_size 32k;
proxy_buffers 16 64k;
proxy_max_temp_file_size 20m;
}
그런 다음 최악의 경우를 계산하세요. 1,000개의 동시 요청이 각각 수백 KB의 응답 버퍼를 사용할 수 있다면 실제 메모리입니다. 작업자 프로세스, TLS, 업스트림 연결, 캐시, OS 페이지 캐시 및 기타 서비스를 위한 공간을 남겨두세요.
구성 검증 및 시스템 모니터링
모든 변경 후:
sudo nginx -t
sudo systemctl reload nginx
그런 다음 메모리, 디스크 I/O 및 오류 로그를 모니터링하세요. 성공적인 구문 테스트는 파일이 구문 분석된다는 것만 증명합니다. 값이 좋다는 것을 증명하지는 않습니다.
유용한 확인 사항:
free -h
iostat -xz 1
sudo tail -f /var/log/nginx/error.log
임시 파일 경고가 사라지지만 메모리 압력이 증가하면 과도하게 설정한 것입니다. 메모리가 건강하게 유지되고 영향을 받는 워크로드 중 디스크 I/O가 감소하면 변경이 도움이 되었을 것입니다.
Nginx 버퍼 튜닝은 로컬에서 수행되고, 측정되며, 실제 트래픽과 연결될 때 가장 잘 작동합니다. 일반 요청은 지연 시간을 절약할 수 있을 때 메모리에 유지하고, 진정으로 큰 업로드나 다운로드는 올바른 경로를 사용하게 하며, 모든 연결이 가장 큰 극단적인 경우에 대해 비용을 지불하게 하는 전역 설정을 피하세요.