# 항상 문제는 TCP_NODELAY다

> Clean Markdown view of GeekNews topic #25277. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=25277](https://news.hada.io/topic?id=25277)
- GeekNews Markdown: [https://news.hada.io/topic/25277.md](https://news.hada.io/topic/25277.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-12-23T21:33:08+09:00
- Updated: 2025-12-23T21:33:08+09:00
- Original source: [brooker.co.za](https://brooker.co.za/blog/2024/05/09/nagle.html)
- Points: 18
- Comments: 1

## Summary

**지연(latency) 문제를 추적할 때는 언제나 TCP_NODELAY부터 확인해야 합니다.** 1980년대 저속 네트워크를 위해 설계된 **Nagle 알고리듬**은 작은 패킷을 묶어 전송 효율을 높였지만, 오늘날의 짧은 RTT 환경에서는 오히려 지연을 유발합니다. 특히 delayed ACK와 결합될 경우 송수신이 서로를 기다리며 멈추는 교착 상태가 발생하므로, 현대 분산 시스템에서는 TCP_NODELAY를 기본으로 활성화하는 것이 합리적입니다.

## Topic Body

- 분산 시스템의 **지연(latency) 문제를 디버깅할 때 가장 먼저 확인해야 하는 항목이 TCP_NODELAY 설정**임  
- **Nagle 알고리듬**은 1984년 RFC896에서 제안된 방식으로, 작은 패킷 전송 시 TCP 헤더 오버헤드를 줄이기 위해 설계됨  
- 그러나 **지연된 ACK(delayed ACK)** 메커니즘과 결합될 때, 데이터 전송이 ACK 수신까지 지연되어 **지연 민감형 애플리케이션의 성능을 악화**시킴  
- 현대 데이터센터 환경에서는 RTT가 매우 짧고, 대부분의 시스템이 이미 큰 메시지를 전송하므로 **Nagle 알고리듬의 이점이 거의 사라짐**  
- 따라서 **현대 분산 시스템에서는 TCP_NODELAY를 기본으로 활성화해야 하며**, Nagle 알고리듬은 더 이상 필요하지 않음  

---
### Nagle 알고리듬의 배경
- 1984년 **John Nagle의 RFC896**은 키보드 입력처럼 작은 데이터 전송 시 발생하는 **40바이트 헤더 대비 1바이트 데이터의 4000% 오버헤드** 문제를 해결하기 위해 제안됨  
  - 당시 문제는 사용자가 한 글자씩 입력할 때마다 작은 패킷이 전송되어 네트워크 효율이 낮아지는 현상  
  - 해결책은 **이전 데이터가 ACK되지 않은 상태에서는 새로운 세그먼트를 전송하지 않도록 제한**하는 방식  
- 이 접근은 당시 네트워크 환경에서는 효과적이었으나, **지연 시간(latency)** 이 중요한 현대 시스템에는 부적합  

### Nagle 알고리듬과 Delayed ACK의 상호작용
- **Delayed ACK**(RFC813, RFC1122)는 수신 측이 즉시 ACK를 보내지 않고, 응답 데이터가 생기거나 타이머가 만료될 때까지 ACK를 지연시키는 방식  
- Nagle 알고리듬은 ACK를 기다리며 전송을 멈추고, delayed ACK는 ACK를 늦추므로 **양쪽이 서로 기다리는 교착 상태**가 발생  
- John Nagle 자신도 이 조합을 “**끔찍한 조합**”이라 표현하며, 두 기능이 독립적으로 도입되었지만 함께 사용될 때 **지연을 유발**한다고 지적  

### 현대 환경에서의 문제점
- 데이터센터 내 **RTT는 약 500μs**, 동일 리전 내에서도 수 밀리초 수준으로 매우 짧음  
- 이런 환경에서 한 RTT만큼 전송을 지연하는 것은 **성능 손실로 이어짐**  
- 또한 현대 분산 시스템은 **TLS, 직렬화, 프로토콜 오버헤드** 등으로 인해 이미 충분히 큰 메시지를 전송하므로, **단일 바이트 패킷 문제는 거의 존재하지 않음**  
- 작은 메시지 최적화는 이제 **애플리케이션 계층에서 처리**되고 있음  

### TCP_NODELAY의 필요성
- **지연에 민감한 분산 시스템**에서는 TCP_NODELAY를 활성화해 Nagle 알고리듬을 비활성화하는 것이 권장됨  
  - 이는 “비효율적”이거나 “잘못된 설정”이 아니라, 현대 하드웨어와 트래픽 특성에 맞는 선택  
- 저자는 **TCP_NODELAY가 기본값이 되어야 한다**고 주장  
  - 일부 “write() 호출마다 전송”하는 코드가 느려질 수 있으나, 그런 코드는 근본적으로 수정되어야 함  

### 기타 관련 옵션
- **TCP_QUICKACK** 옵션은 ACK 지연을 줄이지만, **이식성 문제와 비일관적 동작**으로 인해 근본 해결책이 아님  
- 핵심 문제는 **커널이 애플리케이션이 의도한 시점보다 데이터를 오래 보유하는 것**이며, write() 호출 시 즉시 전송되어야 함  

### 결론
- Nagle 알고리듬은 과거 네트워크 효율을 높이기 위한 훌륭한 발명이었으나,  
  **현대의 고속 네트워크와 분산 시스템 환경에서는 오히려 지연을 초래하는 구시대적 기능**  
- 따라서 **TCP_NODELAY를 항상 활성화하는 것이 현대 시스템 설계의 기본 원칙**으로 제시됨

## Comments



### Comment 48185

- Author: neo
- Created: 2025-12-23T21:33:08+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=46359120) 
- 예전 **멀티포인트 네트워킹** 시절에 만들어진 Nagle 알고리즘의 배경을 설명함  
  당시 여러 호스트가 하나의 이더넷 채널을 공유했기 때문에 충돌을 피하기 위해 [CSMA/CD](https://en.wikipedia.org/wiki/Carrier-sense_multiple_access_with_collision_detection)를 사용했음  
  하지만 오늘날 대부분의 이더넷은 **포인트 투 포인트** 구조로, 송수신이 동시에 가능한 **풀 듀플렉스** 환경임  
  따라서 CSMA는 더 이상 필요하지 않으며, TCP_NODELAY를 설정해 Nagle 알고리즘을 비활성화하는 것이 대부분의 경우 합리적이라 생각함
  - CSMA 관련 동기가 Nagle 알고리즘 설계에 실제로 있었던 건지, 아니면 단순히 시대적 배경을 언급한 것인지 궁금함
  - 사실 Nagle 알고리즘은 단순히 **패킷 병합(coalescing)** 목적이었음  
    기본값으로 설정된 것은 네트워킹 역사상 큰 실수 중 하나라고 생각함
  - 참고로 이더넷은 CSMA/CD, WiFi는 CSMA/CA를 사용함  
    2014년쯤 데이터센터 스위치를 교체할 때, 10Mbit 하프 듀플렉스를 지원하지 않아 일부 구형 장비를 유지해야 했던 경험이 있음
  - 애플리케이션이 패킷 크기를 신경 쓰지 않거나 지연에 민감하지 않을 때는 Nagle이 꽤 합리적임  
    너무 작은 패킷 생성을 방지해줌
  - 네트워크 **레이어 혼동**이 있는 것 같음  
    Nagle은 TCP 계층의 최적화로, 작은 패킷을 묶어 효율을 높이는 역할임  
    CSMA는 물리/데이터링크 계층의 문제로, Nagle과는 별개임
- 게임 개발 중 네트워크 지연 문제를 디버깅하다가 이 글을 발견했음  
  Go로 작성된 백엔드는 기본적으로 **TCP_NODELAY**가 설정되어 있어서 원인은 아니었지만, Nagle의 문제 인식에 대한 부분이 흥미로웠음  
  예전 토론도 있었는데 [이 스레드](https://news.ycombinator.com/item?id=40310896) 참고 가능함
  - Julia Evans의 [좋은 글](https://jvns.ca/blog/2015/11/21/why-you-should-understand-a-little-about-tcp/)도 추천함  
    DICOM 프로토콜처럼 **채팅형 통신**에서는 TCP_NODELAY=1로 설정 시 처리량이 크게 향상됨
  - 어떤 게임을 개발 중인지 궁금함. 나도 **Ebitengine**과 **Golang**으로 게임 개발을 즐기고 있어서 관심이 많음
- Nagle 본인이 10년 전쯤 말하길, 진짜 문제는 **delayed ACK**라고 함  
  [관련 링크](https://news.ycombinator.com/item?id=10608356) 참고  
  요즘 워크로드에서는 delayed ACK이 큰 이점을 주지 않는다고 생각함  
  HTTP 중심의 현대 환경에서는 Nagle과 delayed ACK 둘 다 끄는 게 낫다고 봄
  - 원문에서도 이를 다룸  
    데이터센터 간 **RTT가 수백 마이크로초** 수준이라, 한 RTT라도 지연시키는 건 오히려 손해일 수 있음
- 폴란드어로 “nagle”은 “갑자기”라는 뜻인데, 알고리즘 이름과 너무 잘 어울려서 놀라웠음
  - **Nominative determinism**의 또 다른 사례 같음  
    [위키 링크](https://en.wikipedia.org/wiki/Nominative_determinism)
  - 흥미롭게도 “NODELAY on”일 때는 갑자기 보내고, “off”일 때는 한꺼번에 보내는 식으로 단어 의미가 양쪽 설정을 모두 담고 있는 듯함
  - 실제로는 **John Nagle**이라는 사람이 작성한 [RFC 896](https://datatracker.ietf.org/doc/html/rfc896)에 기반한 알고리즘임
- Nagle 알고리즘이 **커널 기본값**으로 설정된 건 이상하다고 생각함  
  언제 보낼지, 언제 버퍼링할지는 애플리케이션이 결정해야 함
- 글에서 **MSG_MORE**를 언급하지 않은 게 의외였음  
  Linux에서는 추가 데이터가 곧 전송될 것임을 커널에 알려주는 힌트로, 헤더와 데이터를 따로 보낼 때 유용함  
  **io_uring**과 함께 쓰면 더 효율적임
  - 사실 하나의 시스템 콜로 여러 조각 데이터를 복사 없이 보낼 수도 있음
- Nagle 알고리즘의 문제는 **소켓 API에 즉시 전송(flush)** 기능이 없다는 점이라 생각함  
  즉시 응답이 필요한 메시지 이후에 버퍼를 비워 보내는 기능이 있으면 좋겠음  
  요즘 TCP 채널은 동기·비동기 메시지가 섞여 있어서 더 복잡함  
  **SCTP** 같은 프로토콜이 더 널리 쓰였으면 함
  - 스트림 API에 flush 기능이 없다는 점에 동의함. 명백한 설계 누락이라 생각함
  - 네트워크 I/O를 파일처럼 다루려는 **UNIX 철학**은 이해하지만, 메시지 지향 API가 처음부터 있었으면 이런 문제는 없었을 것임  
    TLS 같은 래핑에서도 메시지 경계를 찾는 게 번거로움
  - 모든 send에 **MSG_MORE**를 붙이고 마지막에만 빼면 간접적으로 flush 효과를 낼 수 있을 듯함
  - 스트림 API는 여러모로 불편함  
    이상적으로는 “버퍼링 허용” 비트를 설정해 큰 전송을 나누고 마지막에 “즉시 전송”을 지정할 수 있어야 함  
    **TCP_CORK**는 그나마 비슷한 대안이지만 좀 조잡함  
    파일 I/O도 비슷한 문제를 겪음
  - TCP_CORK가 뭔지 궁금함
- (2024) 이전 토론은 [이 링크](https://news.ycombinator.com/item?id=40310896)에서 있었음
- [Oxide and Friends 팟캐스트 에피소드](https://oxide-and-friends.transistor.fm/episodes/mr-nagles-wild-ride)에서 이 주제를 다룸  
  꽤 흥미로운 내용임
  - **Oxide**는 서버 OS와 하드웨어를 새로 설계하는 회사라, 전통적인 프로토콜을 재검토하는 접근이 브랜드 철학과 잘 맞음
- Nagle 알고리즘은 **정책을 커널에 넣은 것** 같아 어색함  
  애플리케이션이 지연과 처리량의 균형을 직접 조절할 수 있어야 함
  - delayed ack이 없을 때는 합리적인 알고리즘이며, TCP 스택의 일부로 존재하는 이유는 그 계층에서 문제를 해결하려 했기 때문임  
    하지만 애플리케이션 수준에서 구현하려면 **unacked data**를 알아야 해서 비효율적임
  - 이론적으로는 맞지만, 현실적으로는 대부분의 유저스페이스 코드가 네트워크 하부를 신경 쓰지 않음  
    단순한 **20ms flush 타이머**만 있었어도 훨씬 나았을 것임
  - 사실 TCP_NODELAY는 소켓 단위로 설정되므로, 애플리케이션이 직접 선택하는 **유저스페이스 결정**에 가깝다고 봄
  - 한 프로그램의 트레이드오프가 다른 프로그램에 영향을 줄 수 있으므로, 커널이 전체 시스템 관점에서 **중재자 역할**을 하는 게 필요하다고 생각함
