Extremely Agile/General2011.03.10 15:15
TCP_NODELAY 옵션은 TCP의 Nagel 알고리즘을 무력화시키는 옵션입니다. Nagel 알고리즘은 무슨 알고리즘이냐 하면, 짤막한 패킷을 보낼 때 가능하면 버퍼링을 했다가, 버퍼가 다 차면 한번에 보내는 알고리즘입니다. 짤막한 패킷이 네트워크에 너무 많이 뿌려지면 네트워크 뿐 아니라 서버의 성능도 저하되기 때문에 도입된 일종의 congestion-control 알고리즘이죠.

이 알고리즘은 이미 보낸 패킷에 대한 ACK이 올 때 까지는 가능한 한 (그러니까 버퍼가 찰 때 까지) 많은 패킷을 버퍼링하려고 합니다. 그래서 TCP의 Delayed ACK과 같이 쓰이면 좀 안좋은 결과가 빚어질 수도 있습니다. 내가 보내는 패킷의 크기가 항상 작다고 해 보죠. 그럼 이 패킷은 ACK이 올 때 까지 전송되지 못하고 계속 버퍼에서 대기하게 될 겁니다. 내가 보낸 패킷에 대한 응답을 상대 서버가 보내게 되어 있고, 그 패킷을 받은 다음에야 다른 패킷을 또다시 전송하도록 되어 있다면? 상황에 따라 다릅니다만 최대 500ms 까지의 전송 지연을 체감하게 될 수도 있습니다.

if there is new data to send
  if the window size >= MSS and available data is >= MSS
    send complete MSS segment now
  else
    if there is unconfirmed data still in the pipe
      enqueue data in the buffer until an acknowledge is received
    else
      send data immediately
    end if
  end if
end if
[알고리즘 출처: 위키피디아]

어떤 상황에서 그런 일이 벌어지나요? write-write-read를 하는 상황에서 벌어질 수 있습니다. 가령 제가 패킷을 한 번에 보내도 되는데, 두 개의 작은 패킷으로 나누어 보낸다고 해 보죠. 첫 번째 패킷은 서버에 잘 도착할 거에요. 그런데 서버는 아직 데이터를 온전히 다 읽지 못했고, 두 번째 패킷을 다 받아야 응답을 보낼 수 있다고 해 봅시다. delayed ACK은 서버가 보낼 응답 데이터가 있을 때, 그 위에 ACK을 piggyback한다는 정책이니까, 아마 클라이언트 쪽으로 첫번째 패킷에 대한 ACK은 발송되지 못할 거에요. Nagel은 ACK이 올 때 까지 버퍼가 차기를 기다릴 테니까, (ACK이 왔다면 보내겠지만) 두 번째 패킷은 서버 측에서 delayed ACK 타이머가 만료되고 (200ms 후) 첫 번째 패킷에 대한 ACK이 서버에서 클라이언트 쪽으로 전송된 이후에나 보내질 수 있게 됩니다.

따라서 전송해야 하는 패킷의 크기가 그다지 크지 않은 상황에서 전송 지연을 줄이려면 '클라이언트의 요청에 서버는 무조건 패킷으로 응답하'고, 'write는 무조건 한 번에 한다'는 원칙을 지켜야 합니다.
The user-level solution is to avoid write-write-read sequences on sockets. write-read-write-read is fine. write-write-write is fine. But write-write-read is a killer. So, if you can, buffer up your little writes to TCP and send them all at once. Using the standard UNIX I/O package and flushing write before each read usually works. - 출처: http://developers.slashdot.org/comments.pl?sid=174457&threshold=1&commentsort=0&mode=thread&cid=14515105

그런데 그 원칙을 잘 지켜도 전송 지연이 생기는 경우가 있을 수 있습니다. (사람은 실수하게 되어 있는 동물이니까요.) 그러면 이제 최후 수단으로 Nagel 알고리즘을 꺼버리는 방법을 써야 합니다. 끄려면 setsockopt와 TCP_NODELAY 옵션을 함께 사용하면 됩니다.

Win32에서는 다음과 같이 하면 됩니다.

BOOL opt = TRUE;
setsockopt(_sock_d, IPPROTO_TCP, TCP_NODELAY, (const char*)&opt, sizeof(opt));

UNIX라면 다음과 같이 하면 되구요.

int opt = 1;
setsockopt(_sock_d, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));

여기서 _sock_d는 socket에 대한 descriptor입니다.

Java에서는 java.net.Socket 객체에 정의되어 있는 setTcpNoDelay 메소드를 true 인자와 함께 호출하면 됩니다.


 

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^