이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • NGINX timeout이 connect, send, read, client body 단계별로 어떻게 다르게 동작하는가?
  • buffering이 upload, SSE, streaming, gRPC에서 어떤 장애를 만드는가?
  • proxy timeout/body limit을 앱 timeout과 어떻게 맞춰야 하는가?

개요

NGINX timeout과 buffering은 사용자 경험과 backend 보호 사이의 조절 장치다. 앱이 정상 처리 중이어도 proxy가 먼저 끊으면 사용자는 504를 보고, client upload가 proxy에서 막히면 앱 로그 없이 413이 발생한다. streaming endpoint는 buffering 설정 하나로 “실시간”이 아니라 “모아서 한 번에” 보내지는 문제가 생긴다.

  • proxy_connect_timeout은 upstream 연결을 맺는 시간 제한이다.
  • proxy_read_timeout은 upstream 응답을 기다리는 시간 제한이다.
  • client_max_body_size는 요청 body 크기를 proxy에서 제한한다.
  • proxy_buffering은 upstream 응답을 NGINX가 버퍼링할지 결정한다.
  • proxy_request_buffering은 client request body를 먼저 다 받은 뒤 upstream에 보낼지 결정한다.

원리

timeout은 “전체 요청 시간” 하나가 아니다.

client upload
  -> NGINX receives body
  -> NGINX connects upstream
  -> upstream processes
  -> NGINX reads response
  -> NGINX sends response to client

어느 구간에서 시간이 걸리는지에 따라 설정과 로그가 달라진다. upstream timed out while reading response header from upstream은 upstream 연결은 되었지만 응답 header를 제때 못 받았다는 뜻이다.

주요 설정 판단

설정의미흔한 증상
client_max_body_size허용 request body 크기앱 로그 없는 413
client_body_timeoutclient body 수신 대기느린 업로드 중 끊김
proxy_connect_timeoutupstream 연결 대기502/504, connect timeout
proxy_send_timeoutupstream으로 request 전송 대기큰 body 전송 중 실패
proxy_read_timeoutupstream 응답 read 대기long API 504
proxy_bufferingresponse bufferingSSE/stream 지연
proxy_request_bufferingrequest body buffering대용량 upload latency/디스크 사용

Endpoint별 설정 예시

일반 API는 보수적인 timeout과 body limit을 둔다.

location /api/ {
    proxy_pass http://app_upstream;
    proxy_connect_timeout 2s;
    proxy_send_timeout 10s;
    proxy_read_timeout 30s;
    client_max_body_size 10m;
}

파일 업로드는 body limit과 request buffering을 별도로 판단한다.

location /upload/ {
    proxy_pass http://app_upstream;
    client_max_body_size 200m;
    proxy_request_buffering off;
    proxy_read_timeout 120s;
}

SSE는 response buffering을 끈다.

location /events/ {
    proxy_pass http://app_upstream;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_read_timeout 1h;
}

앱 Timeout과의 정렬

proxy timeout은 app timeout, DB timeout, client timeout보다 무작정 길거나 짧으면 안 된다.

  • app 처리 timeout보다 proxy read timeout이 너무 짧으면 proxy가 먼저 504를 만든다.
  • proxy timeout이 너무 길면 느린 upstream에 연결이 오래 묶인다.
  • retry가 있는 client라면 proxy timeout 이후 재시도가 중복 처리를 만들 수 있다.
  • upload는 client timeout, proxy body timeout, app multipart limit을 모두 맞춰야 한다.

코드와 명령으로 확인하기

업로드 limit을 확인한다.

dd if=/dev/zero of=/tmp/large.bin bs=1m count=25
 
curl -i https://api.example.com/upload \
  -F "file=@/tmp/large.bin"

응답 시간 분리를 본다.

curl -o /dev/null -s -w 'connect=%{time_connect} first_byte=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' \
  https://api.example.com/api/slow

NGINX 설정은 include까지 펼친 실제 적용본으로 확인한다.

nginx -T | grep -nE 'proxy_(connect|read|send)_timeout|client_max_body_size|proxy_buffering|proxy_request_buffering'

인프라 협업 포인트

  • 앱 팀은 endpoint별 예상 처리 시간, body 크기, streaming 여부, idempotency 여부를 알려야 한다.
  • 인프라 팀은 proxy timeout, LB idle timeout, gateway timeout, CDN timeout의 전체 체인을 공유해야 한다.
  • 대용량 upload는 proxy 임시 디스크 사용량과 backend pressure를 함께 본다.
  • timeout 변경은 SLO와 downstream 보호 정책에 영향을 주므로 장애 대응 중 임시 완화인지 장기 정책인지 구분한다.

실전 팁

  • 30초를 넘는 동기 API는 UX와 retry 관점에서 다시 설계한다. job 제출 + polling/webhook이 더 나을 수 있다.
  • SSE/WebSocket/gRPC는 일반 REST location과 분리해 timeout/buffering을 따로 둔다.
  • 413은 앱 validation이 아니라 proxy limit일 수 있다. 사용자 메시지와 문서에 최대 크기를 맞춘다.
  • proxy_request_buffering off는 streaming upload에 유리하지만 upstream을 느린 client에 직접 노출한다.
  • 개인 프로젝트는 /api, /upload, /events 정도만 분리해도 많은 장애를 피한다.

위험 신호!

  • 모든 endpoint가 같은 timeout/body limit을 공유한다.
  • proxy timeout은 늘렸지만 DB/query timeout은 그대로다.
  • SSE endpoint에서 proxy_buffering을 확인하지 않는다.
  • 504가 발생해도 앱 처리 완료/중복 처리 여부를 확인하지 않는다.

확인 질문

확인 질문

  • 앱 로그가 없는 413에서 먼저 볼 설정은 무엇인가?
    • NGINX나 gateway의 client_max_body_size 같은 request body limit을 먼저 확인한다.
  • SSE에서 buffering이 문제인 이유는 무엇인가?
    • proxy가 작은 이벤트를 모아 두었다가 한 번에 보내면 client가 실시간 이벤트를 받지 못하기 때문이다.
  • proxy timeout과 app timeout을 함께 봐야 하는 이유는 무엇인가?
    • proxy가 먼저 끊으면 앱은 처리 중이어도 사용자는 실패를 보고, retry가 중복 처리를 만들 수 있기 때문이다.

참고 문서