앵지에서의 HTTP/2와 HTTP/3: 기술적 설정, 문제점 및 성능
앵지는 HTTP/2와 HTTP/3를 기본 지원하는 현대적이고 고성능 웹 서버입니다. Nginx와 달리, 이 프로토콜들의 구현에는 BPF 가속, 커널 수준 QUIC 최적화, 그리고 UDP를 통한 업스트림 서버와의 통합이 포함됩니다. 본 글은 이론이 아니라, 생산 환경에서의 활성화, 세밀한 조정, 그리고 HOL 블로킹부터 다중화된 연결에서의 DoS 취약점까지의 일반적인 문제 해결에 초점을 맞춥니다.
왜 앵지에서는 HTTP/2와 HTTP/3에 특별한 접근이 필요한가?
전통적인 서버와 앵지의 핵심 차이는 단순히 기능의 유무가 아니라, 처리 모델을 중심으로 설계된 방식에 있습니다. HTTP/1.1에서는 각 TCP 소켓이 별도의 워커 프로세스에 의해 처리되어 자연스럽게 부하가 분산됩니다. 반면, HTTP/2와 HTTP/3은 이러한 패턴을 깨고, 하나의 클라이언트가 하나의 장기 연결(QUIC 연결 또는 ALPN이 적용된 TLS 세션)을 확립하면, 이를 전적으로 단일 워커 프로세스가 처리합니다. 이는 낮은 지연 시간에서는 효율성을 높이지만, 트래픽이 매우 집중될 경우 핫스팟을 발생시킵니다. 특히 HTTP/2에서는 더욱 심각한데, http2_max_concurrent_streams를 1024로 설정하고 클라이언트가 900개의 스트림을 열면, CPU 코어가 8개라도 해당 클라이언트의 모든 트래픽이 단일 프로세스를 통해 처리됩니다. HTTP/3의 경우 상황은 더 복잡합니다: QUIC 연결은 인터페이스 간에 마이그레이션이 가능하지만, 앵지의 현재 구현에서는 마이그레이션 시에도 워커 간에 부하를 재분배하지 않고, 연결을 원래 프로세스에 고정시킵니다.
또한 앵지는 단순히 이러한 프로토콜을 활성화하는 것뿐만 아니라, Nginx에서는 사용할 수 없는 시스템 수준의 최적화를 적용합니다. 예를 들어, quic_bpf on은 eBPF 프로그램을 활성화하여 커널 공간에서 QUIC 패킷을 필터링하고 사전 처리함으로써 컨텍스트 스위치를 줄입니다. 그리고 quic_gso on은 네트워크 스택 수준에서 Generic Segmentation Offload을 활성화하여 대용량 객체(예: 비디오 조각이나 WebAssembly 번들)를 전송할 때 CPU 부하를 낮추는 데 매우 중요합니다.
HTTP/2의 주요 파라미터: 보안부터 성능까지
앵지에서 HTTP/2를 구성하는 것은 단순히 http2 on을 설정하는 것 이상입니다. 안정성과 보안에 실질적인 영향을 미치는 세 가지 그룹의 파라미터가 있습니다:
1. 스트림 및 리소스 제한
http2_max_concurrent_streams는 단순한 제한이 아니라 부하 분산에 중요한 요소입니다. 기본값인 128은 중간 정도로 동적인 웹 애플리케이션에는 적합합니다. 하지만 많은 롱폴링 연결을 가진 API 게이트웨이의 경우, 단일 워커에 과부하가 걸리는 것을 방지하기 위해 이를 64로 낮추는 것이 좋습니다.http2_max_requests는 한 연결당 최대 요청 수를 설정합니다. 기본값은 1000입니다. 이를 0으로 설정하면 제한이 해제되지만, 이는 위험합니다: 클라이언트가 연결을 무기한으로 유지해worker_connections제한을 모두 소진시킬 수 있기 때문입니다.http2_idle_timeout은 유휴 상태의 연결이 얼마나 오래 활성 상태로 유지되는지를 정의합니다. 표준 값인 3분은 LTE가 불안정한 모바일 클라이언트의 경우 5분으로 늘릴 수 있지만, 그 이상은 피해야 합니다. 그렇지 않으면 ‘좀비 연결’을 통한 DoS 공격 위험이 높아집니다.
2. 버퍼링 및 청크 크기
http2_chunk_size는 응답 청크의 크기를 제어합니다. 8KB의 값은 지연 시간과 처리량 사이의 균형을 잘 맞춥니다. 미디어 콘텐츠(MSE/DASH)의 경우 64KB로 늘리는 것이 타당하지만, 이는 우선순위 설정의 유연성이 줄어듭니다.http2_recv_buffer_size는 수신 데이터의 버퍼 크기를 조정합니다. 기본값인 128KB를 넘어서 늘리는 것은 성능 향상에 거의 도움이 되지 않지만, 연결당 메모리 사용량은 증가합니다.http2_body_preread_size는 핸들러가 실행되기 전에 읽어들이는 데이터 양을 지정합니다. 이는 요청 본문을 검사하는 미들웨어(예: WAF 필터링)에 매우 중요합니다. JSON API의 경우 64KB, 파일 업로드의 경우 1MB로 설정하는 것이 권장됩니다.
3. 레거시 기능 비활성화
서버 푸시는 앵지 코드베이스에서 삭제되었습니다—설정 오류 때문이 아니라 의도적인 결정입니다. http2_push나 http2_push_preload를 사용하려는 시도는 모두 설정 파싱 오류를 일으킵니다. 이로 인해 전통적인 캐싱 문제가 사라졌습니다: 푸시 리소스는 조건부로 전달될 수 없으며, 종종 이미 클라이언트에 캐시된 데이터를 중복하게 됩니다.
HTTP/3 구성: QUIC, TLS 1.3, 그리고 네트워크 준비
앵지에서 HTTP/3를 활성화하려면 라이브러리, 네트워크, 그리고 DNS 인프라의 세 가지 수준에서 준비가 필요합니다.
라이브러리와 TLS
필수 요구사항은 기본 HTTP/3의 경우 OpenSSL ≥ 1.1.1, 0-RTT의 경우 ≥ 3.5.1입니다. 그러나 OpenSSL 버전이 호환성을 보장하는 것은 아닙니다: 일부 배포판(예: RHEL 9.3)은 QUIC 확장을 지원하지 않는 openssl-fips 포크를 사용합니다. 이를 확인하려면 다음 명령을 실행하세요:
angie -V 2>&1 | grep -i quic
출력이 비어 있으면 컴파일 시 QUIC이 비활성화된 것입니다.
네트워크 요구사항
UDP/443은 방화벽뿐만 아니라 모든 중간 장치에서도 열려 있어야 합니다: L4 로드 밸런서, DDoS 보호 시스템, SD-WAN 게이트웨이 등. QUIC은 가변 길이 패킷을 사용하며, 많은 상태 기반 필터는 명시적인 허가 없이 1500바이트를 초과하는 패킷을 차단합니다. 또한 커널 수준에서 PMTU 디스커버리를 비활성화해야 합니다(net.ipv4.ip_no_pmtu_disc = 1)—그렇지 않으면 클라이언트 측에서의 분할로 인해 패킷 손실이 발생합니다.
공지 메커니즘
앵지는 클라이언트에게 HTTP/3를 알리는 두 가지 방법을 지원합니다:
- Alt-Svc 헤더—표준이자 가장 호환성이 높은 옵션입니다. 아래는 예시 설정입니다:
add_header Alt-Svc 'h3=":443"; ma=86400, h3-29=":443"; ma=86400';
여기서 ma=86400은 공지의 수명을 초 단위로 나타냅니다. ma=0으로 설정해 이를 ‘비활성화’하는 것은 권장되지 않습니다—클라이언트는 이런 헤더를 무시하게 됩니다.
- DNS의 HTTPS 레코드—클라이언트 지원(Chrome ≥ 117, Safari ≥ 17)과 적절한 DNSSEC 구성이 필요합니다. 레코드 형식은 다음과 같습니다:
_https.example.com. IN HTTPS 1 . alpn="h3,h3-29" port=443
두 방법을 결합할 수도 있습니다: DNS 공지를 지원하는 클라이언트는 이를 사용하고, 나머지는 Alt-Svc에 의존합니다.
무엇이 중요한가?
- 앵지의 HTTP/2와 HTTP/3은 클라이언트당 단일 연결을 생성하며, 이는 완전히 단일 워커 프로세스에 묶입니다—따라서
worker_connections와events { use epoll; }의 제한을 재검토해야 합니다. - 서버 푸시는 앵지에서 영구적으로 삭제되었습니다—설정에 언급되면 파싱 오류가 발생합니다.
- HTTP/3의 경우 TLS 1.3 지원이 필수적이지만, 모든 OpenSSL 빌드가 이를 포함하는 것은 아닙니다—
angie -V로 확인하세요. - QUIC 연결은 L4 로드 밸런서와 DDoS 시스템을 포함한 모든 네트워크 단계에서 UDP/443이 열려 있어야 합니다.
quic_bpf on과quic_gso on은 100Mbps 이상 전송 시 CPU 부하를 15–22% 감소시키는 핵심 최적화입니다.
생산 환경에서의 성능과 모니터링
HTTP/2/3의 효과를 평가하려면 연결 수준의 메트릭이 필수적입니다. 앵지는 다음과 같은 내부 로그 변수를 제공합니다:
$http2—연결이 HTTP/2를 사용하면 1, 그렇지 않으면 비어 있음;$http3—HTTP/3일 경우 1;$quic_version—QUIC 버전(예:ff00001d);$bytes_sent와$request_length—프로토콜 수준의 오버헤드를 계산할 수 있게 해줍니다.
HOL 블로킹을 분석하려면 request_time과 http2_stream_id의 관계를 나타내는 그래프를 사용하세요: 높은 스트림 ID에서 시간이 급격히 증가하면 대기 지연이 발생했음을 의미합니다. HTTP/3에서는 이런 효과가 나타나지 않습니다—각 스트림은 독립적으로 처리됩니다.
wrk나 hey로 부하 테스트를 수행할 때는 항상 -H "Connection: keep-alive"와 -H "Accept-Encoding: gzip, br" 플래그를 지정하세요; 그렇지 않으면 테스트가 HTTP/1.1의 동작을 모방하게 됩니다. HTTP/3의 경우 hey -h3나 curl --http3을 사용하세요.
흔히 저지르는 실수는 ab(ApacheBench)만을 신뢰하는 것입니다: 이는 HTTP/2나 HTTP/3을 지원하지 않으므로 결과가 왜곡될 수 있습니다.
— Editorial Team
아직 댓글이 없습니다.