Zettelkasten

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로 연결을 유지하면:

  1. 워커가 idle 연결을 붙들고 다음 요청을 기다림
  2. 그 동안 다른 요청은 큐에서 대기
  3. 워커 수만큼만 동시 처리 가능 → 사실상 동시성이 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 발생.

참고

참고