← 목록으로

Log

http2에 대해서 직접 테스트

2026.05.18

HTTP/2 적용 및 성능 측정

운영 중인 서비스의 nginx에 HTTP/2를 적용하고, 성능 개선 효과를 정량적으로 측정한 작업.

요약

환경HTTP/1.1HTTP/2개선율
정적 페이지, 리소스 100개250ms159ms-37%
실제 운영 서비스 페이지2.08s2.09s0%

리소스 수가 많을수록 HTTP/2의 효과가 커졌으나, 백엔드 응답이 병목인 실 운영 페이지에서는 차이가 미미했다.

작업 동기

운영 서비스는 HTTPS만 적용된 상태였고, HTTP 버전은 확인된 적이 없었다. 다음 명령으로 검증한 결과 HTTP/1.1로 동작 중이었다.

$ curl -I --http2 https://monimentoom.duckdns.org
HTTP/1.1 200 OK

--http2 옵션을 줘도 1.1로 응답하는 것은 서버가 ALPN 협상에서 h2를 제시하지 않는다는 의미다. 설정 한 줄로 적용 가능한 표준 최적화고, 실제 효과를 측정해 다음 작업 우선순위 판단의 근거로 사용하기로 했다.

시스템 구성

[브라우저] ─HTTPS─> [EC2 nginx] ─HTTP─> [Spring Boot 블루/그린]
                  └ Docker Compose ┘
  • nginx 1.29.6 (nginx:alpine)
  • Spring Boot 백엔드 (블루/그린 무중단 배포)
  • EC2 t2.micro (서울 리전)
  • 프론트엔드: Vercel (별도, 이미 HTTP/2)

HTTP/2 적용

사전 확인

컨테이너 내 nginx의 빌드 옵션 확인.

$ docker exec nginx nginx -V
nginx version: nginx/1.29.6
configure arguments: ... --with-http_v2_module --with-http_v3_module ...

http_v2_module이 포함돼 별도 빌드 없이 활성화만 하면 됐다.

설정 변경

호스트에 마운트된 nginx.conf의 443 server 블록에 http2 on;을 추가했다 (nginx 1.25.1+ 신문법).

server {
    listen       443 ssl;
    http2        on;          # 추가
    server_name  example.com;

    ssl_certificate     /etc/nginx/fullchain.pem;
    ssl_certificate_key /etc/nginx/privkey.pem;
    # ...
}

무중단 적용

docker exec -it nginx nginx -t          # 문법 체크
docker exec -it nginx nginx -s reload   # 워커 교체

nginx -s reload는 마스터 프로세스가 새 설정으로 워커만 교체하므로 다운타임이 없다. 기존 워커는 처리 중인 요청을 마무리한 뒤 종료된다.

검증

$ curl -I --http2 https://monimentoom.duckdns.org
HTTP/2 200
server: nginx/1.29.6

응답 시작 라인이 HTTP/2 200으로 바뀜. 헤더가 모두 소문자로 표시되는 것도 HTTP/2 사양의 특징이다.

측정 설계

두 가지 환경

환경목적측정 대상
정적 페이지 (test-static)프로토콜 효과 검증nginx가 직접 서빙하는 PNG 100개 동시 로드
실제 운영 서비스사용자 체감 효과 검증Vercel 프론트엔드 → EC2 백엔드 API 호출

두 환경을 분리한 이유는 변수의 차이 때문이다. 정적 페이지는 백엔드 응답 시간이라는 노이즈가 없어 프로토콜 자체의 효과를 측정할 수 있다. 실 운영 페이지는 백엔드 응답이 포함된 실제 사용자 경험을 반영한다.

측정 페이지 (test-static)

쿼리스트링으로 리소스 개수를 조절할 수 있게 구현했다.

const count = parseInt(new URLSearchParams(location.search).get('n') || '100', 10);
const start = performance.now();
let loaded = 0;

for (let i = 0; i < count; i++) {
  const img = new Image();
  img.onload = img.onerror = () => {
    loaded++;
    if (loaded === count) {
      document.title = `${(performance.now() - start).toFixed(0)}ms`;
    }
  };
  img.src = `/test-static/img/tile.png?v=${i}&t=${start}`;
  document.body.appendChild(img);
}

쿼리스트링이 매번 달라 브라우저 캐시 영향을 받지 않는다.

측정 변수

  • HTTP 버전: 1.1, 2
  • 리소스 개수: 10, 50, 100
  • 시나리오당 측정 횟수: 5회 (평균 사용)

HTTP/1.1로 전환은 http2 on;을 주석 처리하고 reload하는 방식.

측정 노이즈 통제

초기 측정에서 같은 페이지의 새로고침 결과가 16ms, 355ms, 5.43s까지 변동했다. 원인 분석 과정:

가설검증 방법결과
keep-alive 만료연속 새로고침 vs 간격 새로고침일부 영향 (수백 ms)
EC2 CPU 크레딧 고갈top, load average정상 (idle 99%+)
DNS 변동time dig 측정주범 (1~3초 변동)

무료 동적 DNS(DuckDNS)의 응답 시간이 1~3초까지 변동하는 것이 5초 단위 측정 편차의 주된 원인이었다.

DNS 변수 제거 후 검증

/etc/hosts에 IP를 박아 DNS 조회를 우회한 뒤 curl로 단계별 시간을 측정했다.

$ curl -w "DNS: %{time_namelookup}s | TCP: %{time_connect}s | TLS: %{time_appconnect}s | Total: %{time_total}s\n" \
       -o /dev/null -s https://monimentoom.duckdns.org

DNS: 0.000s | TCP: 0.001s | TLS: 0.038s | Total: 0.040s   (5회 모두 유사)

서버 응답은 일관되게 40ms 내외. 측정 편차의 원인은 클라이언트 측 DNS였음이 확인됐다.

측정 절차 표준화

1. /etc/hosts에 IP 박기 (DNS 변수 제거)
2. macOS DNS 캐시 비우기: sudo dscacheutil -flushcache
3. 크롬 완전 종료 후 재시작
4. chrome://net-internals에서 DNS와 소켓 풀 비우기
5. Secure DNS(DoH) 비활성화
6. 시크릿창 + Disable cache 활성화
7. 시나리오당 5회 측정, 평균 사용

측정 결과

환경 A: 정적 페이지

리소스 수HTTP/1.1 (ms)HTTP/2 (ms)개선율
10개8870-21%
50개170110-35%
100개250159-37%

리소스 수에 비례해 개선 폭이 커지는 경향이 명확히 나타났다.

환경 B: 실제 운영 서비스

환경HTTP/1.1 (s)HTTP/2 (s)개선율
Vercel 프론트 + EC2 API2.0762.092+0.8% (측정 노이즈 수준)

실 운영 페이지에서는 의미 있는 차이가 측정되지 않았다.

결과 해석

환경 A: 멀티플렉싱 효과 확인

HTTP/1.1은 도메인당 6개 동시 연결 제한이 있어 100개 리소스를 17라운드에 나눠 받는다(100÷6 올림). HTTP/2는 단일 연결에서 100개 모두 멀티플렉싱으로 처리하므로 1라운드에 끝난다.

HTTP/1.1: 6개 × 17라운드 = 100개
HTTP/2:   100개 × 1라운드

라운드 수 차이가 시간 차이로 직접 반영됐다. 리소스 10개에서는 차이가 작고, 50~100개로 갈수록 격차가 벌어지는 곡선이 이 원리와 일치한다.

환경 B: 차이가 없는 이유

세 가지 요인이 복합적으로 작용했다.

  1. 백엔드 응답 시간이 페이지 로드의 대부분을 차지. Spring Boot의 DB 쿼리와 비즈니스 로직 처리 시간이 HTTP 프로토콜 오버헤드보다 훨씬 크다. 프로토콜 차이가 백엔드 처리 시간에 묻혔다.

  2. API 호출 개수가 적음. 멀티플렉싱 효과는 동시 요청이 많을 때 큰데, 페이지당 API 호출이 소수다.

  3. Vercel은 이미 HTTP/2 적용. 프론트엔드 정적 자원(React 번들, CSS, 이미지)은 처음부터 HTTP/2로 전송되고 있었다. 이번 작업으로 영향받는 부분은 EC2 백엔드 API 호출뿐이다.

부가 발견

측정 과정에서 발견한 다른 최적화 후보:

  • S3 이미지가 HTTP/1.1로 강제 전송됨. S3는 HTTP/2를 지원하지 않는다. CloudFront 도입 시 HTTP/2/3 자동 적용 + 엣지 캐싱 효과를 동시에 얻을 수 있다.
  • DuckDNS의 느린 응답. 1~3초 변동은 첫 접속 사용자에게 그대로 영향을 준다. Route 53 또는 Cloudflare DNS로 이전을 고려할 수 있다.
  • 백엔드 응답 시간이 최대 병목. HTTP 프로토콜보다 큰 개선 여지가 백엔드 로직과 DB 쿼리에 있다.

HTTP/3 적용 계획

직접 실험은 향후 작업으로 두고, 적용 방법과 예상 효과를 정리해둔다.

동작 원리

HTTP/3는 TCP 대신 QUIC(UDP 기반) 위에서 동작한다. HTTP/2의 한계인 TCP 레이어의 Head-of-Line Blocking을 해결한다.

항목HTTP/2HTTP/3
전송 계층TCPQUIC (UDP)
멀티플렉싱애플리케이션 계층전송 계층 (스트림 독립)
패킷 손실 시전체 스트림 블록해당 스트림만 영향
핸드셰이크TCP+TLS = 2~3 RTT1 RTT (재방문 시 0-RTT)
네트워크 전환연결 끊김연결 유지

적용 방법

server {
    listen       443 ssl;
    listen       443 quic reuseport;        # UDP 리스너 추가
    http2        on;
    server_name  example.com;

    ssl_protocols TLSv1.3;                  # HTTP/3는 TLS 1.3 필수
    add_header Alt-Svc 'h3=":443"; ma=86400';
    # ...
}

추가 작업:

  • AWS 보안그룹에 UDP 443 포트 인바운드 허용
  • docker-compose.yaml의 ports에 "443:443/udp" 추가 후 컨테이너 재생성
  • nginx 빌드에 --with-http_v3_module 포함 확인 (현재 이미지에 포함됨)

예상 효과

시나리오예상 효과
정적 페이지 (이상적 환경)HTTP/2 대비 동등 또는 소폭 개선
모바일/패킷 손실 환경10~20% 개선 (HTTP/2 대비)
재방문 사용자100~200ms 단축 (0-RTT)
네트워크 전환 (WiFi ↔ LTE)연결 끊김 방지 (UX 개선)

HTTP/3의 효과는 단순 속도보다 악조건에서의 안정성에서 더 크게 나타날 것으로 예상한다. 측정 시 크롬 개발자도구의 Network Throttling으로 모바일 환경을 시뮬레이션해 비교한다.

한계

  • 측정 횟수. 시나리오당 5회는 경향 확인에는 충분하나, 통계적 신뢰구간을 보고하기에는 부족하다.
  • 단일 클라이언트. 단일 위치, 단일 네트워크에서의 측정이라 일반화에 한계가 있다.
  • 모바일 환경 미측정. Throttling 시뮬레이션은 가능하나 실제 디바이스 측정은 진행하지 않았다.

사용한 명령어

# HTTP 버전 확인
curl -I --http2 https://monimentoom.duckdns.org

# nginx 빌드 옵션 확인
docker exec nginx nginx -V

# 설정 적용
docker exec nginx nginx -t
docker exec nginx nginx -s reload

# DNS 응답 시간
time dig example.com +short

# 단계별 응답 시간
curl -w "DNS: %{time_namelookup}s | TCP: %{time_connect}s | TLS: %{time_appconnect}s | TTFB: %{time_starttransfer}s | Total: %{time_total}s\n" \
     -o /dev/null -s https://monimentoom.duckdns.org

# DNS 변수 제거 (측정 환경 통제)
echo "X.X.X.X example.com" | sudo tee -a /etc/hosts
sudo dscacheutil -flushcache

참고 자료

  • RFC 7540 (HTTP/2)
  • RFC 9114 (HTTP/3)
  • nginx HTTP/2 documentation
  • nginx HTTP/3 (QUIC) documentation