생각해 보면 지금보다 훨씬 더 불편해야 마땅한 사실이 하나 있다. HTTP/3 밑에 깔린 전송 계층, 이제 웹 트래픽의 꽤 큰 몫을 나르고 있는 QUIC는 신뢰성이 있다. 잃어버린 패킷을 재전송한다. 데이터를 순서대로 전달한다. 혼잡 제어를 한다. 모든 걸 암호화한다. TCP의 기능 목록 전부에, 덤까지 얹은 셈이다.
그런데 그게 UDP 위에서 돈다. 존재 이유 자체가 “그런 걸 하나도 하지 않는다”인 바로 그 전송 프로토콜 위에서.
그러니까 우리는 연결도 없고 신뢰성도 없는, 쏘고 잊어버리는 데이터그램 프로토콜을 가져다가, 그 위에 연결과 신뢰성과 순서 보장을 유저스페이스에서 다시 쌓아 올린 것이다. 2005년에 어떤 네트워크 엔지니어한테 이 계획을 설명했다면, 대체 뭘 증명하려는 거냐고 되물었을 거다. 그런데 알고 보면 그게 남은 선택지 중 유일하게 멀쩡한 수였다. 그 이유가 프로토콜 자체보다 더 흥미롭다.
TCP는 고칠 수가 없다
QUIC를 정직하게 표현하면, TCP에는 도저히 적용할 수 없었던 전송 계층 개선을, 그걸 가로막던 것들 몰래 밀반입한 결과물이다.
TCP는 커널 안에 산다. TCP가 회선에서 동작하는 방식을 바꾸려면 모든 운영체제에 새 커널 코드를 넣어야 하고, 그다음엔 사람들이 실제로 업그레이드할 때까지 몇 년을 기다려야 한다. 그것만으로도 느리다. 그런데 진짜 벽은 따로 있다.
진짜 벽은 미들박스다. 당신과 서버 사이에 있는 모든 NAT, 방화벽, 로드 밸런서, “투명” 가속기는 지난 30년간 TCP를 읽는 법을 익혔다. 시퀀스 번호를 들여다본다. 옵션을 고쳐 쓴다. 어떤 놈들은 명세에 없는 방식으로 패킷을 친절하게 “교정”해 준다. 그래서 새 TCP 기능을 내보내면, 인터넷의 적잖은 경로가 살짝 이상해 보이는 TCP 패킷을 만나 조용히 버리거나 망가뜨린다. 너무 많은 박스가 너무 철저하게 파싱한 탓에, 이 프로토콜은 더 이상 진화할 수 없다. 여기엔 이름이 붙어 있다. 고착화(ossification). TCP는 자기 자신의 성공으로 화석이 됐다.
반면 UDP는 거의 빈 봉투다. 출발지 포트, 목적지 포트, 길이, 체크섬, 끝. 미들박스는 그냥 통과시킨다. 그 안에 영리하게 손댈 게 없으니까. 기능적으로 보면, 네트워크에 남은 유일한 ‘새 프로토콜 모양의 구멍’이 바로 UDP다. 이제 진짜로 새로운 IP 프로토콜 번호를 인터넷 배관에 통과시키는 건 불가능하다 — TCP도 UDP도 아닌 건 너무 많은 박스가 차단한다. 그러니 이 시대에 새 전송 프로토콜을 배포하고 싶으면, UDP로 감싸고 그 UDP 페이로드를 진짜 헤더로 삼는 수밖에 없다.
QUIC 설계자들은 그 교훈을 한 발 더 밀어붙였다. 전송 헤더를 거의 통째로 암호화한 것이다. 페이로드만이 아니라 전송 메커니즘 자체를, TCP가 미들박스 좋으라고 평문으로 드러내던 바로 그 부분을. 미들박스는 UDP 패킷이 어디론가 가는 건 볼 수 있다. 하지만 그 안은 볼 수 없고, 따라서 고착화시킬 수도 없다. QUIC는 단지 오늘 배포 가능한 게 아니다. 내일도 배포 가능하도록 일부러 자기 내부를 숨겨 놨다.
이게 실제로 푸는 문제
배포의 영리함을 걷어내면 밑에 진짜 성능 버그가 하나 있다. 이름도 있다. 헤드 오브 라인 블로킹(head-of-line blocking).
HTTP/2가 이걸 끝장냈어야 했다. HTTP/1.1처럼 연결마다 요청 하나씩 곡예하는 대신, HTTP/2는 여러 독립 스트림을 하나의 TCP 연결 위에 다중화했다 — 이미지, 스크립트, HTML이 한 소켓 위에서 서로 엇갈려 흐른다. 좋은 아이디어다. 단, 밑에 깔린 TCP에게는 단 하나의 임무가 있고 그걸 곧이곧대로 수행한다. 모든 바이트를 순서대로 전달한다. 패킷 하나가 사라지면, TCP는 재전송이 도착할 때까지 그 뒤에 도착한 그 무엇도 애플리케이션에 넘겨주지 않는다. 전부 그 구멍 뒤에 줄을 선다.
그래서 한 스트림에서 패킷 하나만 잃어도 그 연결을 공유하는 다른 모든 스트림이 얼어붙는다. 패킷을 흘린 이미지가, 완벽하게 도착한 스타일시트를 발목 잡는다. HTTP/2는 헤드 오브 라인 블로킹을 없앤 게 아니라, 애플리케이션에서 전송 계층으로 끌어내렸다. 거기선 오히려 더 고약했다. 이제 모든 스트림이 같은 소켓의 인질이 됐으니까.
QUIC는 이걸 고칠 수 있는 유일한 계층에서 고친다. 모든 스트림에 독립적인 전달 컨텍스트를 준다. 3번 스트림에서 패킷을 잃어도 1번, 2번, 4번은 계속 흐른다 — QUIC는 이들이 별개라는 걸 안다. TCP와 달리 애초에 스트림이라는 개념을 알고 설계됐기 때문이다. 이게 대표 성과고, 진짜 성과다.
나머지는 처음부터 다시 만든다면 이렇게 짰겠다 싶은 구성이다. TLS 1.3이 나중에 덧붙은 게 아니라 처음부터 안에 박혀 있다 — 암호화되지 않은 QUIC란 존재하지 않는다(RFC 9001). 핸드셰이크는 암호화 설정과 전송 설정을 하나로 접어, 새 연결은 1번 왕복으로 서고 재개되는 연결은 0번 왕복에 데이터를 보낼 수 있다. 그리고 QUIC 연결은 출발지 IP와 포트가 아니라 연결 ID(connection ID)로 식별되기 때문에, 카페를 나서며 휴대폰이 와이파이에서 셀룰러로 갈아타 주소가 완전히 바뀌어도 연결은 그냥 이어진다. TCP였다면 문턱에서 끊겼을 일이다.
표준은 2021년에 자리 잡았다. 전송은 RFC 9000, TLS 통합은 9001, 손실 탐지는 9002. HTTP의 익숙한 의미 체계를 QUIC 위에 얹은 HTTP/3는 2022년에 RFC 9114로 뒤따랐다. 그 전에 이미 구글이 자체 서비스와 크롬에 초기 버전을 몇 년간 굴리고 있었고, 그래서 이만큼 야심 찬 프로토콜이 이미 실전에서 검증된 채로 도착할 수 있었다.
그런데 왜 다 먹어치우지 못했나
여기서부터는 승리 행진이 아니다.
QUIC는 빠르게 올라간 뒤 평평해졌다. 요청 점유율 기준으로 20%대 후반 어딘가에서 정점을 찍고 다시 5분의 1 언저리로 흘러내렸다 — 아무것도 아닌 건 아니지만, 그렇다고 떠들썩하게 약속됐던 천하 통일도 아니다. 그리고 이 정체의 이유가 이 모든 사연에서 가장 조용히 뼈아픈 대목이다.
2024년 ACM Web Conference에 논문 한 편이 올라왔다. 제목이 “QUIC is not Quick Enough over Fast Internet”(QUIC는 빠른 인터넷에서 충분히 빠르지 않다). 빠른 회선에서 QUIC의 처리량은 그냥 TCP 위의 HTTP/2보다 최대 45% 낮게 나왔다. 같은 브라우저, 같은 파일인데 극적으로 더 느렸다. 그리고 회선이 빨라질수록 격차는 더 벌어졌다.
근본 원인은 정확히 QUIC를 가능하게 했던 바로 그것이다. QUIC는 유저스페이스에서 돈다. 모든 패킷, 모든 ACK가 커널에서 애플리케이션 코드로 올라와 처리돼야 한다. TCP가 40년간 두들겨 다진 초최적화된 커널 내부 경로를 타는 대신에. 느리거나 손실 많은 모바일 회선에서는 그 오버헤드가 잡음에 묻힌다 — 병목은 CPU가 아니라 네트워크니까. 그런데 깨끗한 기가비트 광 회선에서는 네트워크가 더 이상 병목이 아니고, 수신 측이 병목이 된다. 유저스페이스로 패킷을 충분히 빠르게 퍼 나르지 못하기 때문이다. QUIC를 애초에 출시할 수 있게 해 준 그 유연함이, 운영 비용을 비싸게 만드는 그 유연함이다.
그래서 QUIC 채택률이 실제로 가장 높은 곳을 보면, 짐작과 달리 광 인프라가 풍부한 시장이 아니다. 중앙값 대역폭이 낮고 회선이 더 잘 끊기는 지역이다 — 헤드 오브 라인 블로킹 제거가 가장 크게 와닿고 유저스페이스 오버헤드가 가장 덜 거슬리는, 바로 그 조건. 이 프로토콜은 TCP가 가장 아팠던 곳에서 이기고, TCP가 이미 멀쩡했던 곳에서 진다. 그 둘은 같은 사실을 양쪽 끝에서 본 것이다.
우리는 TCP를 대체하지 못했다. 두 번째 TCP를 만들었다 — 유저스페이스에서, UDP 위에서, 첫 번째에는 손도 못 대게 하던 그 참견 박스들에 맞서 암호화한 채로, 원본이 약했던 네트워크를 위해서. 그리고 그러는 과정에서 알게 됐다. 커널은 그동안 내내 제 밥값을 조용히 하고 있었다는 걸.