TCP 연결 / Keep-Alive / Connection Pool
3줄 요약
- Keep-Alive는 TCP/TLS 연결을 재사용해 latency와 CPU 비용을 줄이지만, idle socket, file descriptor, upstream slot 같은 상태 비용을 남긴다.
- Connection Pool은 성능 장치이자 장애 격리 장치다. active, idle, pending, acquire timeout을 보지 못하면 pool 고갈을 늦게 발견한다.
- 간헐적인 reset, 502, 504, WebSocket 끊김은 Keep-Alive, idle timeout, backlog, thread, downstream pool을 함께 봐야 한다.
핵심 정리
- HTTP Keep-Alive는 TCP keepalive와 다르다. HTTP Keep-Alive는 한 연결에서 여러 요청/응답을 재사용하는 정책이고, TCP keepalive는 OS가 죽은 peer를 감지하는 기능이다.
- 서버가 요청을 받는 과정에는 SYN queue, accept queue, max connection, worker thread, event loop가 있다. 연결이 성공해도 handler 실행 전 대기열에서 밀릴 수 있다.
- idle timeout은 client, CDN, LB, proxy, app, downstream마다 다르다. client가 중간 계층보다 오래 idle 연결을 보관하면 stale connection 재사용으로 reset이 날 수 있다.
- 장기 연결인 WebSocket, SSE, long polling은 일반 REST 요청과 다른 timeout/heartbeat 정책이 필요하다.
- Pool 고갈은 DB뿐 아니라 HTTP client, Redis, executor, proxy upstream, NAT port, conntrack에서도 발생한다. pool size를 키우기 전에 점유 시간이 왜 길어졌는지 봐야 한다.
- retry는 pool 고갈을 완화하기보다 악화할 수 있다. retry budget, backoff, jitter, circuit breaker, idempotency를 함께 설계한다.
헷갈리는 지점
- Keep-Alive를 길게 잡으면 항상 성능이 좋아진다고 생각하기 쉽다. 재사용 이득은 있지만 idle 연결도 자원을 점유하고 배포/장애 우회를 늦출 수 있기 때문이다.
- 핵심 판단 포인트는 첫 요청 latency와 재사용 latency를 분리하고, file descriptor, upstream max connection, NAT port 한계를 함께 보는 것이다.
- 서버 CPU가 낮으면 connection이나 thread 병목이 아니라고 착각하기 쉽다. thread가 DB/API/lock을 기다리면 CPU는 낮아도 요청 처리는 멈출 수 있기 때문이다.
- 핵심 판단 포인트는 busy thread, accept queue, DB/HTTP pool pending, thread dump를 같은 시간대에 보는 것이다.
- timeout을 늘리면 장애가 줄어든다고 믿기 쉽다. 실패가 늦게 보일 뿐 자원 점유 시간이 길어져 전체 장애가 커질 수 있기 때문이다.
- 핵심 판단 포인트는 사용자 대기 시간, 서버 자원 보호, downstream 보호 사이의 균형을 잡는 것이다.
- pool size를 키우면 고갈이 해결된다고 생각하기 쉽다. downstream이 느린 상태에서는 더 많은 동시 요청을 밀어 넣어 장애를 키울 수 있기 때문이다.
- 핵심 판단 포인트는 active, idle, pending, acquire timeout, downstream latency, retry count를 함께 보는 것이다.
확인 질문
- HTTP Keep-Alive와 TCP keepalive는 어떻게 다른가?
- HTTP Keep-Alive는 HTTP 요청/응답을 같은 TCP 연결에서 재사용하는 정책이고, TCP keepalive는 OS가 유휴 TCP 연결의 peer 생존을 확인하는 기능이다.
- accept queue와 worker thread 병목은 어떻게 구분하는가?
- accept queue 병목은 handler 실행 전 listen/connector 레벨에서 밀리는 문제이고, worker thread 병목은 accept된 요청을 처리할 실행 자원이 부족한 문제다.
- idle timeout mismatch가 위험한 이유는 무엇인가?
- 한 계층은 연결이 살아 있다고 믿지만 다른 계층은 이미 닫았을 수 있어 재사용 시 reset, broken pipe, 502가 발생할 수 있기 때문이다.
- pool 고갈의 핵심 신호는 무엇인가?
- active가 max에 붙고 idle이 줄며 pending queue, acquire duration, acquire timeout이 증가하는 것이다.
- pool을 늘리기 전에 확인해야 할 것은 무엇인가?
- downstream latency, retry, timeout, connection leak, thread 대기, instance 수를 곱한 전체 동시성, 인프라 NAT/conntrack 한계를 확인해야 한다.