TCP_NODELAY가 기본인 이유

반응형

분산 시스템에서 지연(latency)을 줄이려면 TCP_NODELAY부터 확인해야 하는 이유를 Nagle 알고리듬과 Delayed ACK의 상호작용 관점에서 정리합니다

TCP_NODELAY가 기본인 이유

TCP_NODELAY가 기본인 이유

분산 시스템의 지연(latency)을 디버깅할 때 가장 먼저 확인할 항목으로 TCP_NODELAY가 자주 언급됩니다.
이유는 단순합니다.
Nagle 알고리듬이 “작은 패킷을 묶어 효율을 높이는” 목표로 설계되었지만, 지연된 ACK(Delayed ACK)와 결합되면 오히려 “서로 기다리는” 상황이 생기기 때문입니다.

한 줄 요약

현대 데이터센터/분산 시스템처럼 RTT가 짧고 지연에 민감한 환경에서는, Nagle 알고리듬의 이점보다 지연 비용이 더 크게 체감됩니다.
그래서 많은 경우 TCP_NODELAY를 켜서 Nagle을 비활성화하는 선택이 합리적입니다.
(물론 “무조건 정답”이라기보다, 트래픽 특성과 메시지 크기/빈도에 맞춰 판단해야 합니다.)

Nagle 알고리듬이 생긴 배경

1984년 RFC 896에서 제안된 Nagle 알고리듬의 문제의식은 단순했습니다.
키보드 입력처럼 작은 데이터(예: 1바이트)를 보낼 때, TCP/IP 헤더(대략 수십 바이트) 오버헤드가 지나치게 커지는 상황을 줄이자는 것.
그래서 “아직 ACK되지 않은 데이터가 있는 동안은, 작은 세그먼트 전송을 지연하고 묶어 보내자”는 정책이 등장했습니다.

당시의 네트워크 환경과 사용 패턴에서는 효율 개선 효과가 컸지만, 오늘날처럼 지연이 핵심인 시스템에서는 다른 부작용이 더 크게 드러날 수 있습니다.

Nagle과 Delayed ACK가 만나면 생기는 일

핵심은 “기다림의 합성”입니다.
Nagle은 ACK를 기다리며 전송을 멈추고,
Delayed ACK는 ACK를 바로 보내지 않고 응답 데이터가 생기거나 타이머 만료까지 지연합니다.
결과적으로 양쪽이 서로를 기다리면서 추가 지연이 발생할 수 있습니다.

지연 민감형 트래픽에서는 “한 RTT만큼 늦어지는 느낌”이 그대로 사용자 체감이나 P99 지표 악화로 이어집니다.
그래서 지연 이슈를 쫓을 때 TCP_NODELAY 설정 여부가 우선순위로 올라갑니다.

현대 환경에서 Nagle 이점이 줄어든 이유

  • 데이터센터/동일 리전 네트워크는 RTT가 매우 짧아, 한 번의 지연도 손실로 느껴지기 쉬움
  • TLS, 직렬화, 프로토콜 오버헤드 등으로 실제 전송 단위가 이미 충분히 커지는 경우가 많음
  • “작은 메시지 최적화”는 애플리케이션 계층(버퍼링, 배치, 프레이밍 등)에서 더 정교하게 처리 가능

정리하면, “작은 패킷을 무조건 줄여야 하는 시대”에서 “지연을 통제해야 하는 시대”로 중심축이 이동했습니다.

그래서 TCP_NODELAY를 켜야 하는가

지연에 민감한 분산 시스템(예: RPC, 게임/실시간, 채팅형 상호작용, 짧은 요청-응답 루프 등)에서는 TCP_NODELAY 활성화가 흔히 권장됩니다.
이는 “비효율적”이라서가 아니라, 현대 하드웨어/네트워크/트래픽 특성에 더 맞는 선택일 수 있기 때문입니다.

TCP_NODELAY가 유리한 경우

작은 메시지를 자주 주고받고,
응답 지연이 사용자 체감/지표(P95~P99)에 직접 영향을 주는 경우.
커널이 “조금 더 모아서 보내자”라고 잡아두는 순간이 손해가 될 수 있습니다.

Nagle이 유리할 수 있는 경우

지연보다 처리량/효율이 우선이고,
애플리케이션이 매우 잘게 쪼개어 write()를 호출하는 패턴이 많은 경우.
다만 이런 패턴은 애플리케이션 측 개선(버퍼링/배치) 여지도 큽니다.

결론적으로, “무조건 Nagle이 나쁘다”가 아니라
“지연을 최우선으로 두는 시스템이라면 TCP_NODELAY가 기본값에 가깝다”는 관점이 핵심입니다.

관련 옵션을 함께 볼 때 주의점

옵션 의미 주의 참고 링크
TCP_NODELAY Nagle 알고리듬 비활성화 지연 감소에 유리할 때가 많지만, 작은 write() 남발은 앱 레벨 개선 필요 man7.org
TCP_QUICKACK ACK 지연을 줄이려는 힌트 이식성/동작 일관성이 약할 수 있어 근본 해법으로 보기 어려움 man7.org
TCP_CORK / MSG_MORE 더 보낼 데이터가 있음을 알려 묶어서 전송 유도 “언제 flush할지”를 앱이 통제하는 방식에 가까우나, 사용 패턴 설계가 중요 man7.org
진짜 핵심 문제는 “커널이 애플리케이션이 의도한 시점보다 데이터를 오래 들고 있는가”입니다.
지연이 중요한 메시지는 write() 이후 즉시 전송되길 기대하는데, 그 기대와 커널 정책이 어긋나면 지연이 커집니다.

실전 체크리스트

  1. 증상 정의: 지연이 평균이 아니라 P95/P99에서 튀는지, 특정 경로에서만 발생하는지 확인
  2. 메시지 크기/빈도: 작은 write()가 연속되는 패턴인지, 요청-응답이 촘촘한지 점검
  3. TCP_NODELAY 확인: 클라이언트/서버 양쪽 소켓에서 설정 상태 확인
  4. 앱 레벨 버퍼링: 프레이밍/배치/플러시 전략을 명시적으로 설계
  5. 재현 실험: NODELAY on/off 비교로 지연 분포(P99) 변화 확인

간단 예시

Linux C에서 TCP_NODELAY 설정
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
Go는 기본적으로 Nagle을 끄는 편(상황에 따라 다름)
// net.TCPConn 기반이라면 보통 아래처럼 조절 가능
conn.SetNoDelay(true)

언어/프레임워크에 따라 기본값이 다를 수 있으니, “설정했다고 믿는 것”보다 “실제로 확인하는 것”이 안전합니다.
특히 RPC 스택을 여러 겹 사용하면 중간 계층에서 소켓 옵션이 재설정되거나, 새로운 커넥션이 만들어질 수 있습니다.

정리

Nagle 알고리듬은 과거 네트워크 효율을 높이기 위한 훌륭한 발명이었습니다.
하지만 현대의 고속 네트워크/분산 시스템에서는 Delayed ACK와 결합될 때 지연을 키우는 요인이 될 수 있습니다.
그래서 지연 민감형 시스템에서는 TCP_NODELAY를 기본으로 고려하는 것이 자연스러운 설계 원칙이 됩니다.

한 문장으로 끝내면:
“지연이 문제라면, 가장 먼저 TCP_NODELAY부터 의심하자.”

반응형