왜 3-way 핸드셰이크가 문제인가
일반적인 TCP 연결은 SYN → SYN-ACK → ACK의 3단계를 거친 뒤에야 데이터를 보낼 수 있다. 즉 실제 요청 바이트를 전송하기 전에 최소 1 RTT(왕복 시간)를 소비한다. 국내 서버 간 통신이면 수 ms 수준이라 무시할 만하지만, 대륙 간 통신이나 모바일 네트워크에서는 한 번의 RTT가 100ms를 넘기도 한다.
문제는 이 비용이 연결마다 반복된다는 점이다. HTTP/1.1의 짧은 연결, keep-alive가 끊긴 재연결, TLS 위에서의 새 세션 등 짧은 요청을 자주 맺고 끊는 워크로드일수록 핸드셰이크 지연의 누적 비중이 커진다.
TCP Fast Open의 동작 원리
TCP Fast Open(TFO, RFC 7413)은 첫 연결에서 서버가 발급한 쿠키를 클라이언트가 캐시해 두었다가, 이후 연결에서 SYN 패킷에 데이터를 실어 보내는 방식이다. 서버는 쿠키를 검증하면 핸드셰이크가 끝나기 전에 그 데이터를 애플리케이션으로 전달한다. 결과적으로 재연결 시 데이터 전송 시작까지의 1 RTT를 절약한다.
- 첫 연결: 클라이언트가 빈 TFO 옵션으로 쿠키를 요청 → 서버가 SYN-ACK에 쿠키 발급. 여기서는 절약이 없다.
- 이후 연결: SYN에 쿠키 + 데이터 동봉 → 서버가 즉시 처리.
커널·서버 설정
리눅스는 net.ipv4.tcp_fastopen sysctl로 제어한다. 비트 플래그이며 클라이언트(1)와 서버(2)를 조합한다.
# 1 = 클라이언트로 TFO 사용, 2 = 서버로 TFO 수신, 3 = 둘 다
sysctl -w net.ipv4.tcp_fastopen=3
# 영구 적용
echo "net.ipv4.tcp_fastopen = 3" >> /etc/sysctl.d/99-tfo.conf
sysctl --system
# 확인
cat /proc/sys/net/ipv4/tcp_fastopen
Nginx는 listen 지시자의 fastopen 파라미터로 켠다. 값은 검증되지 않은 상태로 큐에 대기시킬 최대 요청 수다.
server {
# 큐 최대 256개까지 TFO 데이터 허용
listen 443 ssl fastopen=256;
server_name api.example.com;
# ...
}
애플리케이션 코드에서 쓰기
커널만 켠다고 자동 적용되진 않는다. 서버는 소켓에 TCP_FASTOPEN 옵션을 설정해야 하고, 클라이언트는 connect 대신 데이터와 함께 보내는 API를 써야 한다. 파이썬 예시는 다음과 같다.
import socket
# 서버: TFO 큐 길이 지정
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_FASTOPEN, 256)
srv.bind(("0.0.0.0", 9000))
srv.listen(128)
# 클라이언트: connect 없이 SYN에 데이터 동봉 (MSG_FASTOPEN)
MSG_FASTOPEN = 0x20000000
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.sendto(b"GET / HTTP/1.1\r\n\r\n", MSG_FASTOPEN, ("10.0.0.5", 9000))
어디서 얼마나 효과가 있나
| 워크로드 | TFO 효과 |
|---|---|
| 짧은 요청 반복(REST, RPC) | 재연결마다 1 RTT 절약, 체감 큼 |
| keep-alive 장기 연결 | 연결 자체가 드물어 효과 미미 |
| 고지연 모바일/원거리 | RTT가 크므로 절대 이득 큼 |
| 동일 LAN 내부 통신 | RTT가 작아 이득 제한적 |
주의점과 함정
TFO는 만능이 아니다. 실무 적용 전 아래를 반드시 확인한다.
- 미들박스 간섭: 일부 방화벽·NAT가 알 수 없는 SYN 데이터를 폐기하거나 옵션을 제거한다. 이 경우 커널은 자동으로 일반 핸드셰이크로 폴백하지만, 폴백까지의 재시도로 오히려 느려질 수 있다.
- 멱등성: SYN에 실린 데이터는 상황에 따라 재전송될 수 있어 중복 처리 위험이 있다. POST 같은 비멱등 요청을 첫 데이터로 보내는 설계는 피한다.
- 블랙리스트: 리눅스 커널은 TFO 실패가 반복되는 경로를 일시적으로 비활성화한다.
ss -i나netstat -s | grep -i fastopen로 실제 사용·실패 통계를 확인해야 한다. - 보안: 쿠키는 자원 소진 공격을 완화하지만 완전히 막지는 못한다. TFO 큐 길이를 과도하게 키우지 않는다.
정리
TCP Fast Open은 재연결이 잦고 RTT가 큰 환경에서 데이터 전송 시작을 1 RTT 앞당기는 실용적인 최적화다. 다만 커널·서버·애플리케이션 3계층 모두에서 켜야 하고, 미들박스 폴백과 멱등성 문제가 있어 무조건 적용할 대상은 아니다. 대상 트래픽의 연결 패턴과 경로 특성을 측정한 뒤, netstat 통계로 실제 적용률을 검증하며 도입하는 것이 안전하다.