오전 10:05 2006-01-31
조경민 bro@shinbiro.com
TCP 성능 향상
====================================================================


Piggy-back
request - response 통신 중 일때 수신측에서 잘 받았다는 ACK를 따로 전송하지 않고
response 보내기 시에 header의 ACK 필드를 사용하여 ACK를 response 패킷에 함께 보내는 방식

Nagle 알고리즘
작은 양의 데이터를 여러번 send() 호출로 보내는 경우 네트워크 효율이 좋지 못하여
send()를 여러번 호출해도 일정 기간동안 지연해서 TCP 스택의 보내기 버퍼에 쌓아둔 후
한번에 보내는 알고리즘


INFO: Design Issues - Sending Small Data Segments Over TCP w/Winsock
--------------------------------------------------------------------

데이터 패킷을 받게 되면 잘 받았다고 TCP 스택은 ACK 메시지를 보내게 되는데,
Microsoft TCP 스택은 200ms delay 타이머가 다운 카운드 되게 된다.

- delay timer가 expire되기 전에 두번째 데이터 패킷을 받게 되면 ACK를 보낸다.
- (두번째 데이터 패킷이 도착되거나 delay timer가 expire되기) 전에 데이터를 또 보내
게 되면 ACK는 데이터 패킷에 piggy-back(등에 엎혀서)되어 함께 보내진다.
- delay timer가 expire되면 ACK가 보내진다.

작은 데이터 패킷들로 네트웍이 혼잡해지는 것을 피하기 위해서 Microsoft TCP 스택은
Nagle 알고리즘을 기본적으로 사용하게 되는데, 작은 데이터를 여러번 send 호출하면
작은 데이터 들은 TCP 스택 보내기 버퍼에 쌓이게 되고 이전에 보낸 데이터 패킷이
상대방에서 받아졌다는 ACK가 나올때 까지 지연되었다가 한번에 보내진다.
아래는 nagle 알고리즘이 적용안되는 예외 상황이다.

- 만일 스택에 Maximum Transmission Unit (MTU)보다 큰 데이터 버퍼가 쌓였다면
상대측으로 부터 ACK를 기다리지 않고 즉시 full-sized packet(한 패킷)을 보내게 된다.
- TCP_NODELAY 소켓 옵션이 적용되어 Nagle 알고리즘이 비활성화되면 작은 데이터 패킷들은
지연없이 바로 상대방으로 보내진다. 따라서 각 작은 데이터 send 마다 즉각즉각 ACK가
날아온다. 하지만 한번에 쌓아서 보낼 수 없어 효율적이지 못하다.

애플리케이션 상에서 성능을 최적화하기 위해서 Winsock은 애플리케이션에서 send 호출시
Winsock kernel 보내기 버퍼에 복사해 둔다. 그런 후, tcp 스택은 Nagle 알고리즘같은
학습된 지능적인 방식으로 네트워크 상에 패킷을 실제 보내야 할 시기를 결정한다.
SO_SNDBUF 옵션으로 할당된 윈속 커널 보내기 버퍼의 크기(기본 8K)를 변경할 수 있다.
대부분 경우, 애플리케이션에서 보내기 완료라는 것은 애플리케이션 send 호출로 데이터
버퍼가 윈속 tcp 스택 보내기 커널 버퍼에 복사되었다는 것을 알려주는 것일 뿐이며,
네트워크 상에 패킷이 발생되었다는 것을 알려주는 것은 아니다. 이 보내기 완료의 의미가
다른 딱 하나의 상황은 SO_SNDBUF를 0으로 설정하여 Winsock 버퍼링을 비활 성화한 경우이다.

Winsock은 다음 규칙에 따라 애플리케이션에게 보내기가 완료되었음을 알려준다.
- 소켓 보내기가 아직 SO_SNDBUF 보내기 버퍼 할당 크기내에 있으면 윈속은 애플리케이션에서
send한 데이터를 보내기 버퍼에 복사하여 쌓아두고 애플리케이션에게 보내기가 완료되었다고
알려준다.
- 소켓이 SO_SNDBUF 범위 이상을 보내려 하고, 이전에 쌓아진(buffered) 하나의 send가 스택
커널 버퍼에 있다면, 윈속은 애플리케이션 send 데이터를 복사해 오고, 애플리케이션에게 보내기
완료를 알려준다.
- 소켓이 SO_SNDBUF 범위를 넘어 있고, 이전에 쌓아진 하나 이상의 send가 스택 커널버퍼에
있다면 윈속은 애플리케이션 send로 부터 데이터를 복사한다. 윈속은 소켓이 SO_SNDBUF에 다시
데이터를 채울 수 있을때까지 애플리케이션에게 보내기 완료를 발생시키지 않는다.
즉 SO_SNDBUF가 0이 아닌 상황에서 스택 보내기 버퍼가 꽉차지 않았다면 애플리케이션 send()는
스택 보내기 버퍼에 복사하여 쌓아둔 후 보내기가 완료되었다고 말한다.

Case Study 1
TCP 클라이언트가 10000 레코드를 TCP 서버에 데이터베이스 저장을 위해서 보낸다.
레코드의 크기는 20바이트에서 100바이트로 가변적이다. 다음과 같이 애플리케이션이 짜여져있다.
- 클라이언트는 blocking send만 하며, server는 blocking recv만 한다.
- 클라이언트는 SO_SNDBUF를 0으로 하여 하나의 데이터 버퍼에 각 레코드들을 보낸다.
  (TCP 스택 버퍼를 사용하지 않고 하나의 사용자 버퍼로 각 레코드를 담아 여러번 send()를 한다.)
- 서버는 loop를 돌면서 recv()를 호출한다. 200바이트 버퍼를 recv()에 넣어 각 recv()호출로
  각 레코드를 받는다.

성능: 테스트결과 서버로 일초에 5개의 레코드만 보낼 수 있었다. 총 10000 레코드를 보내는데
최대 976K 바이트의 데이터(10000*100/1024)를 보내는 것인데 30분이상이 소요되었다.

분석: 클라이언트에서 TCP_NODELAY 옵션을 설정하지 않아 TCP 스택은 Nagle 알고리즘에 따라
다른 send() 패킷을 네트웍상에 바로 보내지 않고 ACK를 기다리고 ACK가 오면 쌓인 버퍼를
보내게 된다. 그러나 SO_SNDBUF = 0으로 클라이언트가 winsock 버퍼링을 사용하지 않는다 하였으므로
10000 send() 호출은 각각 네트웍 상으로 보내져야 하고 매번 ACK를 받아야 한다.
각 ACK는 아래 서버의 TCP 스택에서 발생되는 일로 200ms 지연이 된다.
- 서버는 패킷을 받고 200ms delay timer를 다운 카운트 한다.
- 서버는 어떤 것도 response할 것이 없기 때문에 ACK은 piggyback되지 못한다.
- 서버에서 delay timer가 expire되어 ACK가 클라이언트로 전송된다.

향상: 두가지 설계적인 문제가 있다. 첫째로 delay timer 문제가 있는데 클라이언트는 200ms지연
내에 서버로 두개이상의 패킷을 보낼 필요가 있다. 클라이언트는 기본적으로 Nagle 알고리즘을
사용하기 때문에 SO_SNDBUF를 0으로 설정하지 않게 하여 기본 윈속 버퍼링을 사용하게 하면 된다.
TCP 스택이 Maximum Transmission Unit(MTU)보다 큰 버퍼로 쌓이게 되면 상대측으로 ACK를 기다리지
않고 즉시 한번에 full-sized 패킷을 보내게 된다.
두번째로 이런 설게에서 작은 레코드들 마다 send()를 호출하였다. 작은 데이터를 보내는 것은
매우 효율적이지 못하다. 이런 경우 각 데이터 레코드를 100바이트로 pad(작은 데이터도 더미 데이터를
붙여) 만들어 한번에 80 레코드들을 send() 시킬 수 있다. 얼마나 많은 레코드를 보냈는지 서버에서
알 수 있도록 클라이언트는 레코드 묶음을 보내기 전에 고정 크기의 헤더에 레코드 갯수를 담아 보낼 수 있다.

Case Study 2
윈속 TCP 클라이언트는 두개의 TCP 연결을 연 후, 상품 견적 서비스를 제공하려 한다.
첫번째 연결은 서버로 상품 번호를 보내는 명령 채널로 사용되고, 두번째 연결은 상품 견적을
받는 데이터 채널로 사용된다. 두 채널은 연결된 후, 클라이언트는 서버로 상품 번호를 보내고
데이터 채널에서 데이터가 올때까지 기다리게 된다. 처음 상품 견적이 받아진 후에야 서버로
다음 상품 번호를 보낸다. 클라이언트와 서버는 모두 SO_SNDBUF와 TCP_NODELAY 옵션을 설정하지 않았다.

성능: 테스트 결과 초당 5개의 견적을 받을 수 있었다.

분석: 이 설계에서는 한번에 하나의 상품 견적 요청만 가능하다. 첫번째 상품 번호가 명령채널을 통해
서버로 보내지고 response는 서버에서 클라이언트로 데이터 채널을 통해 바로 보내진다. 그러면
클라이언트는 즉시 두번째 상품 번호 요청을 보내기 위해 윈속 보내기 버퍼에 복사된다.
그러나 클라이언트 TCP 스택은 커널 버퍼에 있는 요청을 바로 보내지 못한다. 첫번째 명령 채널로
보낸 send에 대한 ACK가 아직 안왔기 때문이다. 서버 명령 채널의 200ms delay timer 후 expire되어
첫번째 상품 번호 요청에 대한 ACK를 클라이언트에게 보내게 된다. 그러면 두번째 견적 요청은
200ms 지연후 서버로 보내지게 된다. 두번째 상품 번호에 대한 견적 응답은 데이터 채널로 바로
오게 되는데 이 당시는 클라이언트 테이터 채널의 delay timer가 expire되었기 때문이다.
서버는 이전 견적 응답에 대한 ACK를 받은 것이다. 클라이언트는 200ms 동안 두번째 상품 견적 요청을
보내지 못하여 클라이언트의 delay timer의 expire에 의해 ACK가 서버로 보내지게 된 것이다.
결과적으로 클라이언트는 두번째 견적 응답을 받고 다른 견적 요청을 하지만 같은 방식으로 지연이 이뤄진다.

향상: 두개의 연결 채널을 맺는 것은 여기서 필요치 않다.만일 한개의 연결만 맺고, request, response를
하였다면 request에 대한 ACK는 response에 piggyback되어 함께 돌아올 수 있다.
더 향상된 방식으로 클라이언트는 여러개의 요청을 한번에 보내고 서버도 역시 여러개의 응답을
한번에 보낼 수 있다. 만일 두개의 단방향 채널 설계가 필요하다면 두 채널에 TCP_NODELAY 옵션을
주어 작은 패킷을 이전 패킷의 ACK가 올때까지 기다리게 하지 말고 바로 바로 보낼 수 있게 하면된다.

권고: 위 두가지 경우는 꾸며진 상황이다. 최악의 상황을 설명하기 위해서이다.

- 만일 데이터 세그먼트가 time critical하지 않다면 애플리케이션은 하나의 큰 데이터 블럭으로
묶은 다음 한번의 send()를 하라. 너무 크지 않은 버퍼여야 하는데 8K이하면 항상 효율적이다.
윈속 보내기 버퍼가 MTU보다 크다면 보내기 버퍼내 한번에 보낼 수 있는 패킷 크기안의 여러
full-sized 패킷을 보내고 마지막 패킷은 남겨진다. 마지막 패킷을 제외하고는 200ms delay timer
지연을 받지 않는다. 마지막 패킷이 홀수 번째 패킷이면 지연된 ACK 알고리즘과 관련되고
보내기 스택 끝에 MTU 범위를 넘는 다른 보내야할 블럭이 있다면 이는 Nagle 알고리즘으로 처리된다.
- 가능하다면 단방향 데이터 흐름의 연결을 피해야한다. Nagle과 지연된 ACK 알고리즘의 악영향을
받게 된다.
- 만일 작은 데이터 세그먼트를 즉시 보내야 한다면 TCP_NODELAY 옵션을 보내는 쪽에서 설정한다.
- 만일 윈속이 보내기 완료를 알려주는 것이 네트워크 상에 패킷이 실제 보낸 것이라는 것을
보장하길 원치 않는다면 SO_SNDBUF를 0으로 설정하지 말아야 한다. 즉, SO_SNDBUF를 0으로
설정한다면 네트웍상으로 패킷을 보낸 것을 보내기 완료로 윈속이 알려준다는 의미이다.
SO_SNDBUF가 0이 아닌 값이면 보내기 완료는 TCP 스택 버퍼에 복사되어 쌓인것을 의미한다.
보내기 버퍼의 8K 버퍼 크기는 대부분의 상황에서 잘 동작되도록 설정된 값이라 더 나은 성능을
보이는 상황 테스트를 하지 않은 이상 변경하지 말아야 한다.
SO_SNDBUF를 0으로 하는 것은 벌크 데이터 전송에 성능 이득을 준다. 최대 효율을 위해서
더블 버퍼링(하나 이상의 보내기 버퍼를 묶음)과 Overlapped I/O를 함께 사용해야 한다.
- 만일 데이터 전달이 신뢰성을 보장받지 않아도 된다면 UDP를 사용하라.

참고 :
Common Performance Issues in Network Applications Part 1: Interactive Applications
이 문서에서 TCP같은 Stream형 프로토콜의 Overlapped I/O를 사용하는 경우만 SO_SNDBUF = 0
를 추천하고 있다.

'KB > MFC/Win32' 카테고리의 다른 글

COM 스레드 모델 STA, MTA  (0) 2006.03.24
원격 프로세스 제어 (원격 스레드 생성)  (0) 2006.02.01
ATL COM 로컬 서버 Keep Alive 시키기  (0) 2006.01.06
DCOM with xp sp2  (0) 2006.01.06
ShowWeb function  (0) 2004.05.24

+ Recent posts