느리게 받아가는 클라이언트가 뒤쪽 서버의 스레드를 붙잡는 문제.
무엇이 문제인가
WAS 가 직접 클라이언트를 상대하면
- 모바일 사용자가 지하철에서 요청 → 응답 1MB 를 초당 10KB 로 받아간다
- 100초 동안 WAS 스레드 하나가 묶여 있다
- 스레드 200개짜리 WAS 라면 이런 사용자 200명이면 서비스가 멈춘다
요청을 처리한 시간은 10ms 인데 스레드는 100초를 붙들려 있다
앞단이 흡수한다
WAS → nginx 는 로컬 네트워크 속도로 1MB 를 즉시 넘긴다 (수 ms)
→ WAS 스레드가 바로 풀려난다
→ 느린 전송은 nginx 가 이벤트 루프로 처리한다 (스레드를 안 쓴다)
이것이 '버퍼링' 이고, nginx 의 proxy_buffering 이 기본 on 인 이유다
관련 지시자
proxy_buffering on; # 기본값
proxy_buffer_size 4k; # 응답 헤더용 첫 버퍼
proxy_buffers 8 4k; # 본문용 버퍼 개수·크기
proxy_busy_buffers_size 8k;
proxy_max_temp_file_size 1024m; # 메모리를 넘으면 디스크에 임시 저장
- 버퍼가 작으면 큰 응답이 디스크 임시 파일로 내려간다
- error log 에 "a client request body is buffered to a temporary file" 류
- 디스크 I/O 가 늘고 느려진다. 큰 응답이 잦다면 버퍼를 키운다
언제 꺼야 하나
스트리밍 응답에서는 버퍼링이 방해가 된다
-
SSE · 로그 실시간 tail · 긴 다운로드 진행률
-
응답을 모았다가 한꺼번에 보내면 '스트리밍이 아니게' 된다
-
location /events {
- proxy_pass http://backend;
- proxy_buffering off;
-
}
-
혹은 애플리케이션이 X-Accel-Buffering: no 헤더로 그 응답만 끌 수 있다
-
이쪽이 더 안전하다. 설정이 아니라 응답 단위로 제어된다
전역으로 끄지 않는다.
proxy_buffering off;를 http 블록에 두면 slow client 방어가 통째로 사라진다. 스트리밍 location 에서만 끈다.
요청 방향도 있다
- client_body_buffer_size — 요청 본문 버퍼
- client_max_body_size — 요청 본문 최대 크기 (기본 1m — 넘으면 413)
업로드가 큰 서비스는 두 값을 함께 본다 버퍼보다 크면 디스크 임시 파일로 떨어진다
왜 이 문제가 예전보다 덜 보이나
비동기 서버·경량 스레드가 퍼지면서 뒤쪽도 슬로우 클라이언트에 덜 취약해졌다
- 이벤트 루프 기반 런타임은 애초에 연결당 스레드를 안 쓴다
- 경량 스레드는 대기 중 캐리어를 놓아 준다
그래도 앞단 버퍼링을 유지하는 이유
- 뒤쪽 자원(커넥션 풀 · 메모리)은 여전히 유한하다
- 응답 압축·캐시·로깅을 앞에서 끝내는 편이 싸다
- 뒤쪽 런타임이 무엇이든 앞단 정책은 그대로 쓸 수 있다
요청 방향의 느림도 있다
느린 '업로드' 도 같은 문제다 — 요청 본문이 천천히 올라온다
- client_body_timeout — 본문 수신 중 무응답 허용 시간
- client_body_buffer_size — 버퍼 크기 (넘으면 디스크 임시 파일)
Slowloris 류 공격이 이 지점을 노린다
- 헤더를 아주 천천히 보내 연결을 오래 붙잡는다
- client_header_timeout · limit_conn 으로 방어한다
- 이벤트 기반 서버는 연결당 비용이 낮아 상대적으로 견디기 쉽다
면접 함정
- ❌ "버퍼링을 끄면 빨라진다" → 첫 바이트는 빨라지지만 slow client 방어가 사라진다.
- ❌ "스트리밍이 안 되는 건 애플리케이션 문제" → 프록시 버퍼링이 1순위 원인이다.