gunicorn sync worker는 keep-alive를 무의미하게 만든다
·수정 2026.06.02·수정 2회
요약
- gunicorn
--keep-alive는 기본 2초로 매우 짧다. sync worker가 keepalive 동안 묶여서 다른 요청을 처리 못 하기 때문이다. - 실무 패턴: nginx를 앞단에 두고 nginx ↔ client 구간에서 keepalive를 길게 유지, gunicorn은 짧게(2~5s) 둔다.
- keepalive를 길게 가져가려면 async/threaded worker(
gthread,gevent,UvicornWorker)로 바꿔야 한다.
본문
gunicorn keep-alive 설정
gunicorn --keep-alive 5 app:wsgi
--keep-alive N(기본 2초): 응답 후 N초간 다음 요청 대기, 없으면 close- 다른 서버 대비 매우 짧다 (nginx 75s, Apache 5s, Node 5s)
왜 기본값이 2초로 짧은가
gunicorn 기본 worker는 sync worker — 워커 1개가 요청 1개를 처리하는 동안 완전히 묶임.
keepalive로 연결을 유지하면:
- 워커가 idle 연결을 붙들고 다음 요청을 기다림
- 그 동안 다른 요청은 큐에서 대기
- 워커 수만큼만 동시 처리 가능 → 사실상 동시성이 keepalive로 인해 떨어짐
→ 짧게 둬서 빨리 워커를 풀어주는 게 sync 모델의 합리적 선택.
실무 패턴: nginx 앞단 처리
client ──(keepalive 75s)── nginx ──(keepalive 2s)── gunicorn
- nginx ↔ client: 길게 유지 → handshake 비용 회피, latency 이득
- nginx ↔ gunicorn: 짧게 → 워커 회전율 확보
- nginx가 connection multiplexer 역할 (수만 개 client 연결 → 적은 수의 backend 연결)
nginx 설정:
upstream gunicorn {
server 127.0.0.1:8000;
keepalive 32; # nginx → gunicorn 풀 크기
}
server {
keepalive_timeout 75s; # client → nginx
location / {
proxy_pass http://gunicorn;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 필수: 기본 close 방지
}
}
keepalive를 길게 쓰고 싶다면 worker 변경
# threaded
gunicorn --worker-class gthread --threads 4 --keep-alive 30 app:wsgi
# async (gevent)
gunicorn --worker-class gevent --keep-alive 60 app:wsgi
# ASGI (FastAPI 등)
gunicorn --worker-class uvicorn.workers.UvicornWorker --keep-alive 60 app:asgi
이런 worker는 idle 연결을 점유해도 다른 요청을 동시에 처리할 수 있어 keepalive 길게 두는 이득이 살아남.
타임아웃 사다리
LB/프록시/gunicorn 각 단의 idle timeout 정렬이 필요:
| 레이어 | 권장 |
|---|---|
| LB (ALB/GCP) | 60s |
| nginx (client) | 75s (LB보다 길게) |
| nginx (upstream) | keepalive_timeout 65s |
| gunicorn sync | 2~5s |
| gunicorn async | 30~60s |
상위 레이어 timeout < 하위 레이어 timeout 이어야 한다. 역전되면 502 race 발생.
참고
- gunicorn settings - keepalive
- gunicorn deploy - nginx pattern
HTTP keepalive 타임아웃을 늘리면 latency는 줄지만 idle 리소스가 늘어난다