웹 개발을 하다 보면 HTTP/1.1의 한계를 많이 느낀다. 특히 모바일 환경에서 여러 리소스를 로딩할 때 느려지는 걸 보면 “왜 이렇게 느릴까?”라는 생각이 든다.
HTTP/1.1은 1997년에 나온 프로토콜인데, 지금 웹의 복잡성과는 맞지 않는다. 그래서 나온 게 HTTP/2이고, 그 다음에 HTTP/3까지 나왔다.
오늘은 이 프로토콜들이 어떻게 웹 성능을 개선했는지, 실제로 어떤 차이가 있는지 알아보자.
HTTP/1.1은 기본적으로 하나의 연결에서 하나의 요청만 처리할 수 있다. 이를 “Head-of-Line Blocking”이라고 한다.
예를 들어보자. 웹페이지를 로딩할 때 HTML, CSS, JavaScript 파일들을 순차적으로 다운로드한다고 하자.
요청 1: HTML 파일 (2초 소요)
요청 2: CSS 파일 (1초 소요)
요청 3: JS 파일 (3초 소요)
HTTP/1.1에서는 총 6초가 걸린다. 각 요청이 완료되어야 다음 요청을 보낼 수 있기 때문이다.
브라우저마다 다르지만, 보통 도메인당 6-8개의 동시 연결만 허용한다. 이는 HTTP/1.1의 또 다른 문제점이다.
// 이런 상황에서 문제가 생긴다
const images = [
'image1.jpg', 'image2.jpg', 'image3.jpg',
'image4.jpg', 'image5.jpg', 'image6.jpg',
'image7.jpg', 'image8.jpg', 'image9.jpg'
];
// 6개까지만 동시에 로딩되고, 나머지는 대기
images.forEach(src => {
const img = new Image();
img.src = src;
});
HTTP/1.1에서는 매 요청마다 헤더를 보내야 한다. 쿠키나 인증 정보 같은 헤더가 매번 반복된다.
GET /style.css HTTP/1.1
Host: example.com
Cookie: session=abc123; user=john
User-Agent: Chrome/91.0
GET /script.js HTTP/1.1
Host: example.com
Cookie: session=abc123; user=john // 같은 헤더 반복
User-Agent: Chrome/91.0
HTTP/2의 가장 큰 변화는 멀티플렉싱이다. 하나의 연결에서 여러 요청을 동시에 처리할 수 있다.
HTTP/1.1: 요청1 → 응답1 → 요청2 → 응답2 → 요청3 → 응답3
HTTP/2: 요청1 ↘
→ 응답1, 응답2, 응답3 (동시 처리)
요청2 ↗
요청3 ↗
실제로 테스트해보면 차이가 확실하다. 같은 리소스들을 로딩할 때 HTTP/1.1은 6초, HTTP/2는 3초 정도 걸린다.
HTTP/2에서는 서버가 클라이언트가 요청하기 전에 미리 리소스를 보낼 수 있다.
<!-- 클라이언트가 HTML을 요청하면 -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/style.css"> <!-- 서버가 미리 푸시 -->
<script src="/script.js"></script> <!-- 서버가 미리 푸시 -->
</head>
</html>
이렇게 하면 클라이언트가 CSS나 JS 파일을 별도로 요청하지 않아도 된다.
HTTP/2는 HPACK이라는 압축 알고리즘을 사용해서 헤더 크기를 줄인다.
HTTP/1.1: 매 요청마다 전체 헤더 전송 (500바이트 × 100개 = 50KB)
HTTP/2: 첫 번째 요청만 전체 헤더, 나머지는 압축된 인덱스만 (50KB → 5KB)
HTTP/1.1은 텍스트 기반이었지만, HTTP/2는 바이너리 프로토콜이다. 파싱이 훨씬 빠르고 오류가 적다.
HTTP/2도 완벽하지 않다. 여전히 TCP 기반이라 TCP의 문제점을 그대로 가지고 있다.
HTTP/2에서도 TCP 레벨에서 패킷 손실이 발생하면 전체 연결이 멈춘다.
패킷 순서: [1][2][3][4][5]
패킷 3이 손실되면:
- 패킷 4, 5는 도착했지만 대기
- 패킷 3 재전송 대기
- 전체 스트림 지연
TCP 연결을 맺기 위해서는 3-way handshake가 필요하다. TLS까지 포함하면 더 많은 라운드트립이 필요하다.
TCP 3-way handshake: 1.5 RTT
TLS handshake: 2 RTT
총 3.5 RTT 필요
HTTP/3는 TCP 대신 QUIC(Quick UDP Internet Connections) 프로토콜을 사용한다. QUIC는 UDP 기반이지만 TCP의 신뢰성을 제공한다.
HTTP/1.1: HTTP over TCP over IP
HTTP/2: HTTP over TCP over IP
HTTP/3: HTTP over QUIC over UDP over IP
QUIC의 큰 장점 중 하나는 연결 마이그레이션이다. 사용자가 WiFi에서 4G로 바꿔도 연결이 끊어지지 않는다.
// 사용자 경험
// WiFi → 4G 전환 시
// HTTP/1.1/2: 연결 끊어짐, 재연결 필요
// HTTP/3: 연결 유지, 끊김 없는 서비스
이전에 연결했던 서버라면 0-RTT로 연결을 재개할 수 있다. 특히 모바일에서 유용하다.
첫 연결: 1 RTT (QUIC handshake)
재연결: 0 RTT (연결 상태 복원)
QUIC는 스트림별로 독립적인 흐름 제어를 한다. 한 스트림에서 문제가 생겨도 다른 스트림에는 영향을 주지 않는다.
HTTP/2: 패킷 3 손실 → 모든 스트림 대기
HTTP/3: 패킷 3 손실 → 해당 스트림만 대기, 나머지는 계속 진행
같은 웹사이트를 다른 프로토콜로 테스트해보자.
// 테스트 환경
const testUrls = [
'https://http1.example.com', // HTTP/1.1
'https://http2.example.com', // HTTP/2
'https://http3.example.com' // HTTP/3
];
async function measureLoadTime(url) {
const start = performance.now();
await fetch(url);
const end = performance.now();
return end - start;
}
// 결과 (예시)
// HTTP/1.1: 2.3초
// HTTP/2: 1.4초 (39% 개선)
// HTTP/3: 1.1초 (52% 개선)
모바일에서는 네트워크 불안정성이 더 크기 때문에 HTTP/3의 장점이 더 크게 나타난다.
WiFi 환경:
- HTTP/1.1: 1.8초
- HTTP/2: 1.2초
- HTTP/3: 1.0초
4G 환경:
- HTTP/1.1: 3.2초
- HTTP/2: 2.1초
- HTTP/3: 1.4초 (연결 마이그레이션 효과)
거의 모든 현대 브라우저에서 지원한다.
Chrome: 41+ (2015년)
Firefox: 36+ (2015년)
Safari: 9+ (2015년)
Edge: 12+ (2015년)
아직 완전하지 않지만 빠르게 확산되고 있다.
Chrome: 87+ (2020년)
Firefox: 88+ (2021년)
Safari: 14.1+ (2021년)
Edge: 87+ (2020년)
server {
listen 443 ssl http2; # http2 추가
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# HTTP/2 서버 푸시 설정
location / {
http2_push /style.css;
http2_push /script.js;
}
}
# mod_http2 모듈 로드
LoadModule http2_module modules/mod_http2.so
<VirtualHost *:443>
Protocols h2 http/1.1
# 서버 푸시 설정
H2PushResource /style.css
H2PushResource /script.js
</VirtualHost>
// Cloudflare Workers에서 HTTP/3 지원
export default {
async fetch(request, env) {
// HTTP/3는 자동으로 지원됨
const response = await fetch(request);
// HTTP/3 헤더 추가
response.headers.set('Alt-Svc', 'h3=":443"; ma=86400');
return response;
}
}
HTTP/2나 HTTP/3를 사용하려면 HTTPS가 필수다. HTTP에서는 사용할 수 없다.
// HTTPS 강제 리다이렉트
if (location.protocol !== 'https:') {
location.replace('https:' + window.location.href.substring(window.location.protocol.length));
}
HTTP/2는 멀티플렉싱으로 인해 서버 리소스를 더 많이 사용한다. 연결당 메모리 사용량이 증가한다.
# 서버 모니터링
netstat -an | grep :443 | wc -l # HTTP/2 연결 수 확인
ss -tuln | grep :443 | wc -l # 더 정확한 연결 수
Chrome DevTools에서 HTTP/2 사용 여부를 확인할 수 있다.
1. F12 → Network 탭
2. 프로토콜 컬럼에서 "h2" 확인
3. HTTP/2 사용 시 "h2"로 표시됨
한 번에 모든 것을 바꾸기보다는 단계적으로 적용하는 것이 좋다.
1단계: HTTP/2 적용 (기존 서버 업그레이드)
2단계: 성능 모니터링 및 최적화
3단계: HTTP/3 지원 검토 (CDN 활용)
4단계: 점진적 HTTP/3 전환
실제 사용자에게 미치는 영향을 측정하기 위해 A/B 테스트를 진행한다.
// 사용자 그룹별 프로토콜 할당
const userGroup = Math.random() > 0.5 ? 'http2' : 'http3';
const apiUrl = userGroup === 'http2' ?
'https://api-v2.example.com' :
'https://api-v3.example.com';
// 성능 지표 수집
const startTime = performance.now();
fetch(apiUrl)
.then(() => {
const loadTime = performance.now() - startTime;
// 분석 도구로 데이터 전송
analytics.track('page_load_time', {
protocol: userGroup,
loadTime: loadTime
});
});
HTTP/3는 아직 초기 단계지만 빠르게 확산되고 있다. 특히 모바일 환경에서의 장점이 크다.
2023년: 25% 웹사이트가 HTTP/2 사용
2024년: 15% 웹사이트가 HTTP/3 지원
2025년 예상: 30% 웹사이트가 HTTP/3 지원
HTTP/3 기반으로 더 많은 기능들이 개발될 예정이다.
성능 변화를 정확히 측정하기 위해 적절한 모니터링 도구를 사용하자.
// Core Web Vitals 측정
import {getCLS, getFID, getFCP, getLCP, getTTFB} from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
모든 것을 한 번에 바꾸려 하지 말고, 중요한 페이지부터 시작하자.
1순위: 메인 페이지
2순위: 상품 상세 페이지
3순위: 기타 페이지들
HTTP/2와 HTTP/3는 단순한 프로토콜 업그레이드가 아니라 웹 성능의 패러다임을 바꾼 혁신이다.
특히 HTTP/3의 QUIC 프로토콜은 모바일 환경에서의 사용자 경험을 크게 개선할 것이다. 연결 마이그레이션, 0-RTT 재연결 같은 기능들은 사용자가 네트워크를 바꿔도 끊김 없는 서비스를 제공할 수 있게 해준다.
하지만 모든 기술이 그렇듯이, 무작정 최신 기술을 도입하는 것보다는 현재 상황에 맞는 적절한 선택을 하는 것이 중요하다. HTTP/2부터 시작해서 점진적으로 HTTP/3로 넘어가는 것이 현실적인 접근 방법이다.