콘텐츠로 이동

네트워크 2편 - TCP 핸드셰이크, 연결의 비용

작성일: 2026년 4월 13일

티켓팅하는 순간을 생각해보자. 수만 명이 동시에 클릭하는 그 순간, 서버는 데이터를 처리하기도 전에 이미 무너져 있다. 이상한 일이다. 실제로 주고받을 데이터는 아직 한 글자도 오가지 않았는데, 무엇이 서버를 그토록 지치게 만들었을까?

답은 단순하다. 데이터가 오가기 전에 앞서 끝내야 하는 일이 있다. 그 일이 마무리되지 않으면, 거래는 시작조차 못한다.



세상의 모든 거래에는 시작 비용이 있다. 상대를 신뢰해도 되는지 확인하고, 조건을 맞추고, 서로의 준비 상태를 점검하는 시간. 거래의 본질이 신뢰일수록 이 시작 비용은 더 비싸진다.

네트워크도 다르지 않다. 데이터를 안전하게 주고받으려면, 양쪽이 먼저 서로를 확인해야 한다. 패킷이 제대로 도착했는지, 순서가 맞는지, 누락된 게 없는지. 이 모든 신뢰의 전제 조건을 먼저 합의해야 비로소 한 글자라도 보낼 수 있다.

이 합의는 공짜가 아니다. 시간이라는 비용을 반드시 지불해야 한다. 그리고 그 비용은 우리가 흔히 생각하는 것보다 훨씬 비싸다.


TCP의 선택 — 데이터보다 계약이 먼저다

섹션 제목: “TCP의 선택 — 데이터보다 계약이 먼저다”

TCP는 이 문제 앞에서 분명한 선택을 했다. 데이터를 보내기 전에 반드시 계약을 먼저 맺는다.

양쪽이 신호를 주고받으며 서로의 준비를 확인하는 절차. 이 절차가 완전히 끝나기 전까지, 실제로 보내려는 데이터는 단 1바이트도 전송되지 않는다. 신뢰를 얻기 위해 속도를 포기한 선택이다.

문제는 이 계약이 단순히 “한 번”으로 끝나지 않는다는 점이다. 새 연결이 필요할 때마다, 매번 처음부터 다시 계약을 맺어야 한다. 한 명의 사용자에게 한 번이면 작은 비용이지만, 수만 명이 동시에 접속하는 순간 이 비용은 서버를 무너뜨릴 만큼 거대해진다.

Client Server
| |
| ———————————— SYN ——————————> | "연결할 수 있어?"
| |
| <————————— SYN-ACK ————————— | "가능해, 너도 준비됐어?"
| |
| ———————————— ACK ———————————> | "응, 시작하자"
| |
| [ 데이터 전송 시작 ] |

세 번의 신호가 오가야 비로소 데이터를 주고받을 수 있다. 중요한 점은 데이터를 주고받기 전에 반드시 계약을 맺어야 한다. SYN과 SYN-ACK는 조건 협상이고, ACK는 최종 서명이다. 실제 거래, 즉 데이터 전송은 이 세 단계가 모두 끝난 뒤에야 시작된다.


연결을 열 때 — RTT

계약을 맺는 데 걸리는 시간은 물리적 거리에 묶여 있다. 서울에서 미국 서버까지 신호가 한 번 왕복하는 데 약 150ms가 소모된다. 이를 RTT(Round Trip Time)라 부른다.

TCP는 데이터가 서버에 도달하기까지 최소 1.5번의 왕복이 필요하다. 사용자가 클릭하는 순간부터 서버가 그 데이터를 인지하기까지, 이미 225ms(150ms × 1.5)가 증발하는 셈이다.

요청 하나에 225ms. 동시 접속자가 1만 명이면 이 비용이 1만 번 발생한다. 서버 성능을 아무리 높여도 줄일 수 없다. 연결을 맺는 시간은 물리적 거리에 묶인 고정 비용이기 때문이다.

연결을 닫은 뒤 — TIME_WAIT

비용은 계약이 끝난 뒤에도 남는다. TCP는 연결이 종료된 후 포트를 즉시 반환하지 않는다. 최대 2분간 TIME_WAIT 상태로 붙잡아 둔다. 뒤늦게 도착하는 패킷과 새로운 연결이 충돌하는 것을 막기 위해서다.

네트워크 1편에서 사용 가능한 포트가 약 28,000개라고 했다. 초당 500개의 연결이 맺어지고 끊어지는 서버를 생각해보자. 2분이 지나기 전에 TIME_WAIT 상태의 포트가 60,000(500/초 x 120초)개 쌓인다. 한계를 두 배 넘게 초과하는 순간, 서버는 새로운 연결을 받을 수 없는 상태에 빠진다.

연결 종료 후:
Port 5001 [TIME_WAIT ——————————— 2분 ———————————]
Port 5002 [TIME_WAIT ——————————— 2분 ———————————]
Port 5003 [TIME_WAIT ——————————— 2분 ———————————]
...
Port 5028 [TIME_WAIT ——————————— 2분 ———————————]
→ 28,000개 소진. 신규 연결 불가.

RTT는 연결을 시작하는 비용이고, TIME_WAIT는 연결을 끝내는 비용이다. TCP는 시작과 끝, 양쪽 모두에서 값을 청구한다.


1937년, 경제학자 로널드 코스는 이런 질문을 던졌다. 시장에서 거래는 왜 항상 마찰 없이 일어나지 않는가. 그의 답은 단순했다. 모든 거래에는 준비 비용이 따른다. 상대를 찾고, 조건을 협상하고, 계약을 체결하는 과정 자체가 비용이다. 올리버 윌리엄슨은 이를 거래 비용 이론(Transaction Cost Theory)으로 발전시켰다.

TCP 핸드셰이크가 정확히 그렇다. 모든 연결마다 협상 수수료가 청구되는 구조다. 그리고 거래 비용 이론은 이 구조에 단 하나의 해법을 제시한다.

거래 비용을 줄이는 가장 효과적인 방법은 거래 횟수 자체를 줄이는 것이다.

연결을 빠르게 맺는 게 아니라, 연결을 적게 맺는 것. 이것이 HTTP Keep-Alive의 핵심 논리다. 요청마다 연결을 새로 맺고 끊는 대신, 한 번 맺은 연결을 여러 요청에 걸쳐 재사용한다. 핸드셰이크 비용을 연결 하나가 아닌, 수십 개의 요청으로 분산시키는 것이다.

그런데 Keep-Alive만으로는 부족했다. 한 번 맺은 계약 안에서도 “먼저 들어온 요청이 처리될 때까지 뒤의 요청들이 줄을 서서 기다려야 하는” HTTP 고유의 구조적 한계가 있었기 때문이다. 다음 편에서는 이 한계를 해결하기 위해 HTTP가 어떻게 진화해왔는지를 살펴본다.


TCP 핸드셰이크는 ‘신뢰’라는 상품을 얻기 위해 지불해야 하는 가격(Price)이다. RTT는 연결을 열 때 내는 비용이고, TIME_WAIT는 연결을 닫은 뒤에 치르는 대가다. 거래 비용 이론의 언어로 말하자면, TCP는 모든 연결마다 ‘협상 수수료’를 청구하는 프로토콜이다.

결국 최적화의 핵심은 연결을 빠르게 맺는 것이 아니라, 연결 횟수 자체를 최소화하는 데 있다. HTTP는 그 방향으로 진화해왔다.

다음 편에서는 HTTP/1.1이 Keep-Alive로 이 문제를 어떻게 완화했는지, HTTP/2가 멀티플렉싱으로 한 단계 더 나아갔는지, 그리고 왜 HTTP/3가 TCP 자체를 버리는 선택을 했는지를 살펴본다.