- Published on
HTTP/2 탄생 배경과 핵심 기술 이해하기 (1편): APLN 오류로 시작된 HTTP/2 학습기
- Authors

- Name
- Deokgoo Kim
HTTP/2 탄생 배경과 핵심 기술 이해하기 (1편)
시작은 단순한 APLN 오류였습니다
최근에 서버 환경을 설정하다가 APLN negotiation failure 오류를 만났습니다. 처음에는 단순한 설정 문제인 줄 알았는데, 파고들수록 제가 HTTP/2에 대해 제대로 모르고 있다는 걸 깨달았습니다.
"HTTP/2? 그거 병렬로 스트림해서 빠른 거 아닌가?" 정도의 얕은 지식으로 지금까지 개발해왔다는 게 부끄러웠습니다. APLN(Application-Layer Protocol Negotiation)이 뭔지, HTTP/2 협상이 어떻게 이뤄지는지, 제 애플리케이션이 실제로 HTTP/2의 장점을 제대로 활용하고 있는지 전혀 모르고 있었습니다.
그래서 이번 기회에 HTTP/2가 왜 등장했는지, 어떤 문제를 해결하는지부터 제대로 공부해보고, 특히 개발자가 현실적으로 체크해야 하는 요소들을 정리해보려고 합니다.
HTTP/1.1의 근본적인 문제: HOL 블로킹
HTTP/2를 이해하려면 먼저 HTTP/1.1이 왜 한계에 부딪혔는지 알아야 합니다. 가장 큰 문제는 HOL 블로킹(Head-of-Line Blocking)이었습니다.
이는 마치 좁은 1차선 도로와 같습니다. 앞 차(큰 이미지 요청)가 느리게 가면 뒤따르는 모든 차(다른 리소스 요청)가 하염없이 기다려야 하는 상황입니다. 하나의 연결(Connection)에서 요청과 응답이 순차적으로 처리되어야 했기 때문입니다.
// HTTP/1.1의 문제점
// 큰 이미지 요청이 완료될 때까지 CSS/JS 요청이 대기
1. GET /large-image.jpg (5초 소요)
2. GET /critical.css (대기 중...)
3. GET /app.js (대기 중...)
웹페이지가 복잡해지고 리소스가 많아질수록 이 문제는 더욱 심각해졌습니다. 개발자들은 이를 우회하기 위해 다음 같은 꼼수를 써야 했죠:
- 도메인 샤딩(여러 도메인에 리소스 분산):
cdn1.example.com,cdn2.example.com으로 나누어 브라우저의 도메인당 동시 연결 제한 우회 - CSS 스프라이트: 여러 이미지를 하나로 합쳐서 HTTP 요청 수 줄이기
- 인라인 리소스: 작은 CSS/JS를 HTML에 직접 포함시켜 별도 요청 방지
Google SPDY에서 시작된 해결책
Google은 2009년 이 문제를 해결하기 위해 SPDY라는 실험적 프로토콜을 만들었습니다. 핵심 아이디어는 다음과 같았습니다:
- 멀티플렉싱: 하나의 연결에서 여러 요청을 동시에 처리
- 바이너리 프로토콜: 텍스트 대신 기계가 빠르게 처리할 수 있는 바이너리 형식
- 헤더 압축: 반복되는 헤더 정보를 압축
- 서버 푸시: 클라이언트 요청 전에 미리 리소스 전송
SPDY가 실제로 성능 향상을 보이자, 이것이 표준화되어 2015년 HTTP/2가 탄생했습니다.
HTTP/2의 4가지 핵심 살펴보기
위에서 언급한 SPDY의 4가지 핵심 아이디어가 HTTP/2에서 어떻게 구현되고, 실제로는 어떤 특성을 가지는지 자세히 살펴보겠습니다.
1. 멀티플렉싱
HTTP/2는 멀티플렉싱(Multiplexing)이라는 혁신적인 기능으로 HOL 블로킹 문제를 해결했습니다. 비유하자면, 1차선 도로를 여러 차선이 있는 고속도로로 바꾼 것과 같습니다. 하나의 TCP 연결 안에 여러 개의 독립적인 길(Stream)을 만들어, 여러 요청을 병렬로 동시에 처리하는 방식입니다.
// HTTP/2 멀티플렉싱
// 모든 요청이 동시에 처리됨
1. GET /large-image.jpg (스트림 1) ████████████
2. GET /critical.css (스트림 2) ████
3. GET /app.js (스트림 3) ██████
덕분에 HOL 블로킹이 사라지고 불필요한 왕복 시간(RTT)이 줄어들었습니다. 하지만 여기서 중요한 점은, 이 모든 차선이 결국 하나의 물리적인 도로(단일 TCP 연결) 위에 있다는 사실입니다.
이론적으로는 하나의 연결만으로 충분해야 하지만, 실제 웹 환경은 달랐습니다. 'Is The Web HTTP/2 Yet?' 연구에 따르면, HTTP/2는 HTTP/1.1에 비해 평균적으로 절반의 TCP 연결만 필요로 했습니다. 그러나 동시에 **"오늘날 HTTP/2를 사용하는 웹사이트의 50%가 최소 20개의 TCP 연결을 사용한다"**는 흥미로운 사실도 밝혀졌습니다.
많은 웹사이트가 HTTP/2로 전환한 후에도 리소스를 여러 도메인에 분산시키는 '도메인 샤딩(Domain Sharding)' 기법을 그대로 사용하고 있었기 때문입니다.
2. 바이너리 프로토콜
HTTP/1.x는 사람이 읽고 이해할 수 있는 텍스트(ASCII) 기반의 프로토콜이었습니다. 하지만 HTTP/2는 컴퓨터가 훨씬 더 효율적으로 처리할 수 있는 바이너리(Binary) 프로토콜로 전환되었습니다.
# HTTP/1.1 요청 (사람이 읽을 수 있음)
GET /api/users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0...
# HTTP/2 요청 (바이너리 프레임)
# 사람은 읽을 수 없지만, 기계가 처리하기엔 훨씬 효율적
[바이너리 데이터: 0x000400...]
이러한 변화는 세 가지 주요 이점을 가져왔습니다:
더 빠른 파싱, 적은 오류: 컴퓨터가 텍스트와 불필요한 공백을 해석할 필요 없이 바로 데이터를 처리할 수 있게 되어 파싱 속도가 빨라졌습니다. 또한 텍스트 기반 프로토콜에서 발생할 수 있었던 모호한 해석으로 인한 오류 가능성도 크게 줄었습니다.
효율적인 네트워크 사용: 텍스트보다 훨씬 간결하게 데이터를 표현할 수 있어, 같은 정보를 더 적은 데이터로 전송할 수 있습니다.
새로운 기능의 기반: 이 바이너리 구조는 HTTP/2의 다른 핵심 기능들을 구현하는 튼튼한 기반이 되었습니다. 특히, 데이터를 명확한 길이와 타입을 가진 기계 판독 가능한 프레임(frame) 단위로 나누는 것이 가능해졌습니다. 바로 이 프레임 구조 덕분에 하나의 연결 위에서 여러 스트림의 데이터를 섞어서 보낼 수 있는 멀티플렉싱이 실현될 수 있었습니다.
3. 헤더 압축 (HPACK)
웹페이지 하나를 로드하기 위해서는 수십, 수백 개의 HTTP 요청이 발생합니다. HTTP/1.1에서는 각 요청마다 User-Agent, Cookie 등 거의 동일한 내용의 헤더 정보가 반복적으로 전송되어 불필요한 데이터 낭비를 유발했습니다.
HTTP/2는 이 문제를 해결하기 위해 HPACK이라는 매우 영리한 압축 방식을 도입했습니다. HPACK의 핵심 원리는 인덱싱(Indexing)과 허프만 코딩(Huffman Coding)입니다.
클라이언트와 서버는 헤더 정보를 담은 공유 사전을 유지합니다:
정적 테이블(Static Table): :method: GET과 같이 아주 흔하게 사용되는 헤더들을 미리 정의해 둔 '인쇄된 사전'과 같습니다. 양측이 이미 알고 있으므로, 실제 내용은 보내지 않고 해당 항목의 인덱스 번호(예: 2번)만 보내면 됩니다.
동적 테이블(Dynamic Table): 처음에는 비어있는 '새로운 단어장'입니다. 대화 중에 처음 등장한 헤더(예: 긴 User-Agent 문자열)를 양측이 함께 "앞으로 이걸 63번 항목이라고 부르자"고 약속하며 채워나갑니다. 다음부터는 이 긴 문자열 대신 '63'이라는 숫자만 보내면 됩니다.
4. 서버 푸시
SPDY의 네 번째 혁신적 아이디어는 서버 푸시(Server Push)였습니다. 클라이언트가 요청하기 전에 서버가 미리 필요한 리소스를 보내주는 기능입니다. HTML을 받은 브라우저가 CSS와 JavaScript를 요청하기 전에, 서버가 "아, 이것들도 필요하겠네"하며 먼저 보내주는 것이죠.
// 전통적인 방식 (HTTP/1.1)
1. 브라우저: "index.html 주세요"
2. 서버: "index.html 여기 있어요"
3. 브라우저: "아, style.css도 필요하네요. 주세요"
4. 서버: "style.css 여기 있어요"
5. 브라우저: "app.js도 필요합니다"
6. 서버: "app.js 여기 있어요"
// Server Push 방식 (HTTP/2)
1. 브라우저: "index.html 주세요"
2. 서버: "index.html 드리고, style.css와 app.js도 미리 보내드릴게요!"
이론적으로는 왕복 시간(RTT)을 크게 줄일 수 있는 획기적인 기능처럼 보였습니다. 하지만 실제로 사용해보니 예상보다 복잡한 문제들이 있었습니다.
서버 푸시의 딜레마들:
캐시 무시 문제: 서버는 브라우저의 캐시 상태를 정확히 알 수 없습니다. 이미 캐시된 CSS 파일을 또다시 푸시해서 불필요한 트래픽을 발생시키는 경우가 많았습니다.
우선순위 충돌: 서버가 생각하는 중요도와 실제 페이지 렌더링에 필요한 우선순위가 다를 수 있습니다. 서버가 푸시한 대용량 이미지 때문에 Critical CSS 로딩이 지연되는 역설적 상황도 발생했습니다.
복잡한 관리: 어떤 리소스를 언제 푸시할지 결정하는 것이 생각보다 어려웠습니다. 사용자의 네트워크 상황, 기기 성능, 브라우저 종류에 따라 최적의 전략이 달라지기 때문입니다.
결국 많은 웹사이트에서 서버 푸시를 사용하지 않거나, 사용하더라도 매우 제한적으로만 활용하게 되었습니다. 심지어 HTTP/3에서는 서버 푸시 기능이 아예 제거되었습니다. 대신 <link rel="preload"> 같은 Resource Hints가 더 안전하고 효과적인 대안으로 자리잡았죠.
TCP의 한계와 HTTP/3로의 발전
HTTP/2를 학습하면서 가장 흥미로웠던 발견은 완벽해 보이는 멀티플렉싱에도 근본적인 한계가 있다는 점이었습니다. HTTP/2는 HTTP 수준의 HOL 블로킹은 해결했지만, TCP 수준의 HOL 블로킹이라는 새로운 병목을 드러냈습니다.
- HTTP 수준의 HOL 블로킹 (해결됨): 큰 이미지 응답이 다른 작은 CSS 파일 응답을 막지 않습니다.
- TCP 수준의 HOL 블로킹 (여전히 존재): TCP 패킷 하나가 유실되면, 유실된 패킷이 재전송될 때까지 모든 스트림의 데이터 전송이 중단됩니다.
모든 HTTP/2 스트림이 하나의 TCP 연결을 공유하기 때문에, 네트워크에서 패킷이 하나라도 유실되면 전체 연결이 멈춥니다. 특히 패킷 손실이 잦은 모바일 네트워크에서는 이 문제가 더욱 심각했습니다.
흥미롭게도, HTTP/1.1 시대의 "나쁜 습관"으로 여겨졌던 도메인 샤딩이 의도치 않게 이 문제를 완화시켰습니다. 여러 도메인을 사용함으로써 여러 TCP 연결을 만들어 위험을 분산시킨 것입니다. 하나의 연결에서 패킷 손실이 발생해도 다른 연결들은 계속 동작할 수 있었죠.
이러한 TCP의 근본적인 한계 인식이 Google의 QUIC 프로토콜 연구로 이어졌습니다. QUIC은 TCP 대신 UDP 기반으로 설계되어 연결 수준의 HOL 블로킹을 완전히 해결합니다. 그리고 이 QUIC이 바로 HTTP/3의 기반이 되었습니다.
결국 HTTP/2에서 발견된 한계가 차세대 웹 프로토콜인 HTTP/3의 탄생으로 이어진 셈입니다. 기술의 발전이란 이렇게 한계를 발견하고, 그 한계를 뛰어넘으려는 노력의 연속이라는 걸 다시 한번 깨달았습니다.
마무리: HTTP/2 이해의 첫걸음
APLN 오류로 시작된 이 여정을 통해 HTTP/2가 단순히 "빠른 프로토콜"이 아니라는 걸 깨달았습니다. HTTP/1.1의 HOL 블로킹 문제를 해결하기 위해 등장한 Google SPDY가 HTTP/2로 표준화되기까지, 그리고 멀티플렉싱, 바이너리 프로토콜, HPACK 압축, 서버 푸시의 현실, TCP 한계와 HTTP/3로의 발전까지.
HTTP/2의 핵심 기술들을 이해하고 나니, 이제 실제로 제 애플리케이션에서 HTTP/2를 제대로 활용하고 있는지, 어떤 부분을 개선할 수 있는지 궁금해집니다.
다음 편 예고: HTTP/2 실무 현실 점검하기
2편에서는 실제 개발 환경에서 HTTP/2를 제대로 활용하고 있는지 확인하는 구체적인 방법들을 다루겠습니다:
브라우저 개발자 도구 완전 활용법
- Chrome DevTools Network 탭의 숨겨진 기능들
- Connection ID로 멀티플렉싱 제대로 동작하는지 확인
- Firefox about:networking으로 HTTP/2 스트림 상태 디버깅
백엔드 서버 HTTP/2 설정 완전 점검
- Nginx, Apache, Node.js별 HTTP/2 설정 체크리스트
- Server Push 최적화 (언제 사용하고 언제 피해야 하는가)
- 혼합 콘텐츠와 SSL/TLS 설정 문제 해결
프론트엔드 최적화 실무 가이드
- 도메인 샤딩 제거해야 할까, 유지해야 할까?
- 리소스 우선순위 설정으로 성능 20% 개선하기
- HTTP/1.1 시대 최적화 기법 중 버려야 할 것들
성능 측정과 자동화
- WebPageTest, Lighthouse로 HTTP/2 효과 측정하기
- 실무에서 놓치기 쉬운 함정들과 해결책
- CDN과 HTTP/2 설정에서 자주 발생하는 실수들
실제 코드 예제와 함께 여러분의 서비스가 HTTP/2를 제대로 활용하고 있는지 직접 확인해보세요!