TCP segment 가 전송되지 않는다면 어딜 의심할까?
A서버에서 B서버로 대용량 데이터를 전송하는 네트워크 프로그래밍을 하나 한다고 치자. 이런 저런 삽질 끝에 프로그램을 한 쌍 — 보내는 쪽과 받는 쪽 — 만들었다. 이제 이 프로그램을 실험해 볼 차례다.
실험 결과, A 서버에서 네트워크 send()
호출하는 게 충분히 빠르지 않은 것 같다 – 사실 정말 그런진 모르겠고, send-queue가 너무 빨리 늘어나는데 반해, 실제 네트워크 사용률도 그리 높지 않아서 어느 쪽이 문제인지 모르겠다고 쳐보자.
어디가 문제라고 판단해야 할까? 근데 설상가상으로 B 서버에 직접적으로 붙어서 디버깅하거나 할 순 없는 상황이라고 치자(…).
A? B? 어느 쪽을 의심해야 할까? 그리고 그 판단의 근거는?
이제 이걸 판단할 근거를 찾아보자.
Plan A: TCP segment 를 캡쳐해서 까본다
TCP segment 는 항상 다음과 같은 window 안에서 전송된다.
이 구조를 sliding window라고 한다.1 여기서 제일 중요한 부분이 advertised window 부분이다. 이 값은 받는 쪽의 TCP segment의 header에 있는 window 필드를 통해 전달된다. 모든 전송 관련된 메시지는 이 window를 기준으로 전송된다. 만약 받는 쪽에서 ACK을 보내면 이 window의 오른쪽이 ACK된 마지막 패킷 + window의 위치로 전진한다. 물론 retransmission을 원하는 경우 그냥 같은 값이 올 수도 있다. 그리고 보내는 쪽은 advertised window 안에 있는 segment 만 보낼 수 있다.
즉, 마지막 ack #부터 시작해서 ack # + window 까지 전송할 수 있는 상태다. 그리고 이 window는 0일 수 있다. 즉, 받는 쪽에서 더 이상 segment를 못 받는 경우 이걸 0으로 설정하고, 차후에 공간이 생기면 window update 메시지를 통해 이 값을 증가시킨다 — 같은 ack #에 window 값만 증가시켜서 보낸다.
이걸 가지고 서버 A와 B 중 어느 쪽이 병목인지 판단할 수 있다. 우선 이런 ACK segment의 header를 까봐야 하니 WireShark 같은 툴로 TCP를 캡쳐하고, B 서버의 advertised window와 ack 된 범위를 확인하면 된다.
- 만약 크기 0인 window만 보내고, advertised window 크기를 늘리지 못하면, 이건 받는 쪽(서버 B)가 동작하는 방식이나, 구현체 자체의 성능 문제다. 즉 B가 TCP segment를 윗단의 응용 프로그램에 보내지 못해서 – 즉 윗 단이 느려서 — A는 보낼 여력이 있는데 B가 처리 못하는 것이다.
- 반대로 advertised window 에는 공간이 있는 경우(can be sent로 표시된 부분이 0보다 큼)0) A가 느려서(?) 못 보내는 문제다.
근데 이거 생각 외로 잘 안 될 때가 있다. 왜냐하면 속도 문제를 고민할 정도의 상황이면 캡쳐 툴이 이걸 잘 캡쳐하지 못하고 일부를 놓칠 수 있고, 일일이 까보는 것도 그렇게 쉽진 않다. 그럼 좀 더 프로그래밍 적인 방법을 찾아보자.
Plan B: B 서버를 흉내 내는 툴을 짜고 패킷을 받고 바로 버린다
별도의 처리 없이 TCP 스트림을 받아서 버리는 툴(TCP sink)을 만든다. A-B 연결의 도입부분만 적당히 흉내(!)내고, 그 이후에는 받고 무시하는 프로그램을 짜자. 그리고 실제 B 서버 대신 이 TCP sink에 접속해서 속도가 얼마나 나오나 보면 된다. 실제로 전송되나 여부는 TCP sink 쪽에서 받은 bytes 수만 세면 된다.2
하여튼 내 경우엔 Plan B로 문제의 원인을 찾았다. 모처에 있던 B 서버의 성능 문제… 사실 Plan A로 해결하고 싶었지만, Windows 용 WireShark로 전부 다 캡쳐하질 못하더라. 이건 원인을 잘 모르겠음; 그냥 WinPcap이 구리다?