대규모 트래픽에서 가장 먼저 막히는 곳은 애플리케이션 로직이 아니라 연결입니다. 한 번 맺은 연결을 오래·안전하게 쓰고, 필요한 수만큼 효율적으로 돌리는 게 승부처예요. 아래에서는 기존 소제목을 유지하면서, 운영에서 바로 적용할 수 있는 디테일을 더해 자연스럽게 이어가겠습니다.
1) 연결 비용을 줄이는 기본기: Keep-Alive와 프로토콜 선택
Keep-Alive는 요청마다 TCP/TLS를 다시 맺는 왕복·CPU 낭비를 없애 줍니다. 포인트는 “몇 개를, 얼마나 오래” 유지하느냐예요.
- 최대 동시 연결 수 산정식
필요 연결 수 ≈ (초당 요청수 × 평균 처리시간) × 여유 계수(1.1~1.3)
예) 5천 rps, 평균 80ms면 400개가 이론치이므로 440~520개로 시작합니다. - 유휴 시간 정렬
상위 계층이 더 짧으면 예고 없이 끊어져 재시도가 몰려요. 일반적으로 클라이언트 < 앱 < LB/프록시 순으로 길게 맞춥니다.
예) 앱 65초, LB 75초, 프록시 90초. - HTTP/2/3 전환 효과
소형 요청이 많으면 HTTP/2 멀티플렉싱만으로도 연결 수와 문맥 전환이 크게 줄어요. 손실·지터 많은 모바일 구간은 HTTP/3로 지연 꼬리(p95/p99) 가 안정됩니다. - TLS 재개와 인증 최적화
세션 재개, OCSP 스테이플링, ECDSA 인증서로 핸드셰이크 비용을 최소화하세요. 인증서 체인도 꼭 슬림하게.빠른 적용 예시
Nginx(다운스트림): keepalive_timeout 65s;
Nginx(업스트림): upstream api { keepalive 512; }
Node.js: new Agent({ keepAlive: true, maxSockets: 512, maxFreeSockets: 128, timeout: 65000 })
2) 포트·소켓을 아끼는 기술: TCP 재사용과 커널 레벨 설정
짧은 연결이 많이 열렸다 닫히면 TIME_WAIT과 임시 포트 고갈에 걸립니다. 재사용을 기본으로 설계하세요.
- 임시 포트 범위 확대
서버가 클라이언트 역할도 한다면 범위를 넓혀 동시 접속 여유를 확보합니다.
예) 리눅스: net.ipv4.ip_local_port_range = 20000 60999 - 소켓 큐와 수신 버퍼
급격한 피크를 흡수하려면 연결 대기 큐와 버퍼 한도를 넉넉히:
net.core.somaxconn = 4096, net.core.netdev_max_backlog = 16384 - TIME_WAIT 관리
핵심은 “새 연결 남발 금지”. Keep-Alive 재사용률을 올리면 TIME_WAIT 자체가 줄어듭니다. 서버 재기동이 잦다면 SO_REUSEADDR/SO_REUSEPORT로 바인딩 실패를 완화하세요. - 재전송 정책은 보수적으로
공격적인 재전송·짧은 RTO는 혼잡을 키워요. 손실이 체질적으로 높은 구간은 전송 계층을 HTTP/3로 옮기는 편이 안전합니다.
3) 병목의 80%: 커넥션 풀 설계(최대/최소/큐/백프레셔)
풀 설정 몇 줄이 성공과 실패를 가릅니다. 최대값, 대기 큐, 백프레셔를 한 몸으로 보세요.
- Max/Min, 증감 정책
minIdle로 워밍업 시간을 줄이고 max로 상한을 씌웁니다. 급등형 트래픽은 빠른 증가/느린 감소(지수 증가·선형 감소)로 흔들림을 줄입니다. - 대기 큐와 타임아웃
풀 고갈 시 요청을 무한 대기시키면 전체 반응성이 무너집니다. 큐 길이와 대기 상한을 명시하고, 초과는 즉시 실패(429/503) 혹은 저우선 큐로 이동하세요. - 엔드포인트별 분리
대용량 리포트와 가벼운 조회를 한 풀에서 섞지 마세요. 도메인/엔드포인트 단위로 풀을 분리하거나 가중치를 주면 공정성이 살아납니다. - 멀티 오리진·리전
헬스 지표에 따라 연결 비율을 조정하고, 서킷 브레이커로 느린 리전을 신속히 덜어내면 풀 고갈 연쇄를 막을 수 있어요.
적용 예시
- OkHttp: ConnectionPool( maxIdle=128, keepAlive=65, unit=SECONDS )
- HikariCP(HTTP 아님, DB 참고): 큐 상한·대기 타임아웃을 명확히 두는 설계 관점은 동일합니다.
- Node undici: connections: 256, pipelining: 1(HTTP/2라면 멀티플렉싱 활용)
4) 프록시·LB·CDN과 맞물리는 운영 포인트
경계에서 조그만 불일치가 연쇄 재시도를 부릅니다.
- 아이들·헤더·바디 타임아웃 정렬
하나라도 과도하게 짧으면 중간에서 끊기고, 상위 계층은 모른 채 재시도를 날립니다. 서비스 경로별로 통일하세요. - 헤더·쿠키 다이어트
거대한 쿠키는 동일 연결의 처리량을 깎습니다. 추적용 값을 축약하고, 불필요 쿠키는 경로·도메인 스코프를 줄이세요. - 압축 임계치
작은 페이로드까지 전부 압축하면 CPU가 먼저 막힙니다. 최소 바이트 임계치를 두고, JSON은 Brotli, 다이내믹 바이너리는 무압축을 기본으로 비교해 보세요. - HTTP/2 우선순위 정책
브라우저의 최신 우선순위 정책과 서버 설정이 충돌할 때가 있습니다. 효과가 미미하면 복잡한 커스텀 우선순위는 과감히 접는 게 낫습니다.
5) 관측: “느리다”를 재현 가능한 숫자로
튜닝은 측정→가설→조정→검증의 반복입니다. 한 화면에 아래 지표를 모으세요.
- 지연/성공률: TTFB, 전체 응답시간, p95/p99, 4xx/5xx/타임아웃 분리
- 연결: 활성/유휴, 새 연결/초, ESTABLISHED/TIME_WAIT 카운트, 재사용률
- 풀/큐: 사용률, 큐 길이, 대기 시간, 즉시 실패 건수
- 네트워크 품질: 재전송률, 혼잡윈도우 추정, 손실 지표
- 리소스: FD, 스레드, 메모리, CPU(유저/시스템 비율)
검증 루틴
- 베이스라인 24시간 수집 → 2) 한 번에 하나만 변경 → 3) 동일 요일·시간대 비교 → 4) p95/p99와 즉시 실패 비율이 함께 개선되는지 확인.
6) 체크리스트: 바로 적용
- Keep-Alive: 오리진/프록시 idle timeout 정렬, P95 기준 Max Connections 여유 10~20%
- TCP 재사용: 임시 포트 범위 확대, backlog 상향, 재사용률 증가로 TIME_WAIT 억제
- 커넥션 풀: 엔드포인트별 분리, 큐 상한/대기 타임아웃, 백프레셔 정책
- 프로토콜: HTTP/2 기본, 손실 많은 구간은 HTTP/3 병행
- TLS: 세션 재개, OCSP 스테이플링, 짧은 체인
- 헤더/쿠키: 불필요 값 제거, 압축 임계치 설정
- 관측: 지연·성공률·연결·큐·FD/메모리·재전송 대시보드 고정
7) 흔한 실수와 해결책
- 유휴 시간 불일치 → 중간에서 연결이 끊기며 재시도 폭주
→ 모든 계층의 idle timeout을 긴 쪽에 맞춰 정렬 - 단일 풀에 모든 요청 혼합 → 무거운 요청이 가벼운 요청을 막음
→ 엔드포인트 분리/가중치 적용 - 새 연결 남발 → FD/포트 고갈, TIME_WAIT 폭증
→ Keep-Alive 재사용률을 핵심 지표로 관리 - 무한 재시도 → 백엔드 자해
→ 지수 백오프 + 상한 + 서킷 브레이커 - 측정 없이 튜닝 → 성능 롤러코스터
→ 변경마다 전/후 지표를 동일 조건으로 비교
마무리
Keep-Alive, TCP 재사용, 커넥션 풀은 따로가 아니라 세트 전략입니다.
- 연결을 오래·적게 만들고, 2) 재사용률을 높이며, 3) 풀과 큐로 공정하게 나눠 주면 동시성 한계가 눈에 띄게 넓어집니다. 오늘은 idle timeout 정렬과 풀 분리부터, 내일은 헤더 다이어트와 HTTP/3 병행 적용—대시보드에서 p95 지연·즉시 실패·새 연결/초가 함께 내려가는 걸 확인하게 될 거예요.