하나의 연결에 스트림을 다중화하는 프로토콜.
-
HTTP/1.1 — 연결 하나에 요청을 한 줄로 보낸다
- 앞 응답이 느리면 뒤가 막힌다 (HTTP 레벨 HoL)
- 브라우저가 연결을 6개씩 여는 것으로 우회했다
-
HTTP/2 — 하나의 연결에 스트림을 다중화한다
- 요청·응답이 섞여 오갈 수 있다
- 헤더를 HPACK 으로 압축한다
- 연결 하나로 충분해진다
HPACK 이 생각보다 크게 기여한다
요청마다 쿠키 · User-Agent · Accept 가 반복된다 큰 쿠키를 쓰는 서비스는 헤더만 수 KB 가 된다
- HPACK 은 이전에 보낸 헤더를 테이블로 기억하고 인덱스만 보낸다
- 반복 요청의 헤더가 수십 바이트로 준다
API 호출이 잦은 서비스에서 체감이 크다
남는 문제 — TCP 레벨 HoL
HTTP/2 는 TCP 위에서 돈다 패킷 하나가 유실되면 TCP 가 재전송을 기다린다
-
그 동안 '그 연결의 모든 스트림' 이 멈춘다
-
HTTP 레벨 HoL 은 없앴지만 TCP 레벨 HoL 이 남았다
-
손실률이 높은 모바일 환경에서는 이득이 줄어든다
-
이것을 풀려고 HTTP/3 가 TCP 를 버렸다
설정
listen 443 ssl;
http2 on; # nginx 1.25.1+ 의 형태
# listen 443 ssl http2; # 그 이전 형태 — 이 표기의 문서가 아직 많다
- HTTP/2 는 사실상 TLS 위에서만 쓴다 (브라우저가 평문 h2 를 지원하지 않는다)
- nginx → 업스트림 구간은 별개다. 기본이 HTTP/1.1 이다
- (nginx 1.31 부터 proxy 모듈이 HTTP/2 백엔드를 지원한다)
HTTP/1.1 시대의 최적화를 되돌려야 한다
-
도메인 샤딩 — 정적 자원을 여러 도메인으로 쪼개 연결 수를 늘리던 기법
- HTTP/2 에서는 연결이 늘어 다중화 이점이 사라진다 → 되돌린다
-
스프라이트·인라이닝 — 작은 파일을 합치던 기법
- 요청 비용이 싸졌으므로 이득이 줄었다
- 다만 압축 효율 때문에 여전히 유효한 경우가 있다
-
"HTTP/1.1 최적화를 그대로 두고 HTTP/2 를 켜면" 기대만큼 안 빨라진다
서버 푸시는 사실상 사라졌다
HTTP/2 서버 푸시는 브라우저에서 제거되는 방향으로 정리됐다
- 캐시에 이미 있는 것을 다시 밀어 낭비가 잦았다
- 복잡도 대비 이득이 작았다
대체 — 103 Early Hints · <link rel=preload>
스트림 수와 흐름 제어
http2_max_concurrent_streams 128; # 연결당 동시 스트림 상한
클라이언트가 한 연결에 스트림을 무한히 열 수 있으면 자원 고갈이 된다 과거 이 지점을 노린 공격이 여러 차례 있었다
-
(Rapid Reset — 스트림을 열자마자 취소하는 것을 반복)
-
상한과 함께 요청률 제한을 둔다
-
nginx 버전을 최신으로 유지하는 것이 가장 확실한 대응이다
뒤쪽 구간은 별개다
- 클라이언트 ↔ nginx — HTTP/2
- nginx ↔ 업스트림 — 기본 HTTP/1.1
프론트만 h2 를 켜고 "전 구간이 빨라졌다" 고 생각하기 쉽다 뒤 구간의 이득을 원하면 upstream keepalive 부터 챙기는 편이 효과가 크다 (연결 재사용이 다중화보다 먼저다)
면접 함정
- ❌ "HTTP/2면 HoL 블로킹이 없다" → TCP 레벨에는 남아 있다.
- ❌ "켜기만 하면 빨라진다" → 도메인 샤딩 같은 1.1 시대 최적화를 되돌려야 한다.