실험실에서 서버의 벤치마크를 측정한 뒤 실제 트래픽을 처리하도록 배포했지만, 벤치마크 성능에 훨씬 못 미치는 결과가 나온 적이 있나요? CPU 사용률은 낮고 가용 리소스도 충분한데 클라이언트는 응답 시간이 느리다고 불평하고, 서버 활용률을 높일 방법도 찾지 못하는 상황 말입니다.
지금 관찰하고 있는 현상은 이른바 ‘HTTP의 과도한 처리 부담’이 초래하는 영향 중 하나입니다. 이 블로그 게시물에서는 HTTP의 작동 방식과 일반적인 HTTP 서버가 HTTP 트랜잭션을 처리하는 방식을 살펴봅니다. 발생할 수 있는 몇 가지 성능 문제를 알아보고, NGINX의 이벤트 기반 모델이 이러한 HTTP 서버를 위한 매우 효과적인 가속 프록시로 작동하는 원리를 설명합니다. NGINX를 사용하면 실제 환경의 성능을 로컬 벤치마크 수준으로 끌어올릴 수 있습니다.
애플리케이션의 속도와 확장성을 개선하기 위한 Linux 및 NGINX 튜닝 방법은 블로그의 성능 향상을 위한 NGINX 튜닝을 참조하세요.
HTTP 및 Keepalive 연결 소개
HTTP Keep-Alive 연결은 지연 시간을 줄이고 웹 페이지를 더 빠르게 로드할 수 있게 해 주는 필수적인 성능 기능입니다.
HTTP는 단순한 텍스트 기반 프로토콜입니다. 아직 살펴본 적이 없다면 웹 브라우저에 내장된 도구와 같은 HTTP 디버깅 도구의 출력을 확인하고, 표준 요청 및 응답 구조를 살펴보세요:

가장 단순한 구현에서는 HTTP 클라이언트가 대상 서버에 대한 새 TCP 연결을 생성하고 요청을 전송한 다음 응답을 수신합니다. 이후 서버는 리소스를 해제하기 위해 TCP 연결을 종료합니다.

이러한 작동 방식은 특히 요소가 많은 복잡한 웹 페이지나 네트워크 연결이 느린 경우 매우 비효율적일 수 있습니다. 새 TCP 연결을 설정하려면 ‘3방향 핸드셰이크’가 필요하며, 연결을 해제할 때도 2단계 종료 절차를 거쳐야 합니다. 메시지마다 TCP 연결을 반복해서 설정하고 종료하는 것은 전화 통화 중 한 사람이 말을 마칠 때마다 전화를 끊고 다시 거는 것과 같습니다.
HTTP는 연결 유지(keepalive) 연결이라는 메커니즘을 사용하여 HTTP 트랜잭션이 완료된 후에도 클라이언트와 서버 간의 TCP 연결을 열린 상태로 유지합니다. 클라이언트가 다른 HTTP 트랜잭션을 수행해야 하는 경우 새 TCP 연결을 생성하는 대신 유휴 상태의 연결 유지 연결을 사용할 수 있습니다.

클라이언트는 일반적으로 서버에 여러 개의 TCP 연결을 동시에 열고, 모든 연결에서 연결 유지(keepalive) 트랜잭션을 수행합니다. 이러한 연결은 클라이언트나 서버가 더 이상 필요하지 않다고 판단할 때까지 열린 상태로 유지되며, 대개 유휴 시간 제한으로 인해 종료됩니다.
최신 웹 브라우저는 일반적으로 6~8개의 연결 유지(keepalive) 연결을 열고, 시간 초과로 종료하기 전까지 몇 분 동안 유지합니다. 웹 서버는 이러한 연결의 시간 초과 시간을 더 짧게 설정하여 더 일찍 닫도록 구성할 수 있습니다.
Keepalive가 HTTP 서버에 미치는 영향은 무엇인가요?
많은 클라이언트가 HTTP 연결 유지를 사용하고 웹 서버에 동시 처리 한계나 확장성 문제가 있는 경우, 그 한계에 도달하는 순간 성능이 급격히 저하됩니다.
위의 접근 방식은 개별 클라이언트에 가능한 한 최고의 성능을 제공하도록 설계되었습니다. 하지만 ‘공유지의 비극’과 같은 상황에서 모든 클라이언트가 이런 방식으로 작동하면 널리 사용되는 여러 웹 서버와 웹 애플리케이션의 성능에 악영향을 미칠 수 있습니다.
그 이유는 많은 서버에 고정된 동시 처리 한도가 있기 때문입니다. 예를 들어 일반적인 구성에서 Apache HTTP Server가 동시에 처리할 수 있는 TCP 연결 수는 제한되어 있습니다. worker 다중 처리 모듈(MPM)을 사용할 경우 150개, prefork MPM을 사용할 경우 256개입니다. 유휴 상태인 각 HTTP 연결 유지(keepalive) 연결은 이러한 동시 처리 슬롯 중 하나를 차지하며, 모든 슬롯이 사용 중이면 서버는 더 이상 HTTP 연결을 수락할 수 없습니다.
일반적으로는 웹 서버에서 연결 유지를 비활성화하거나 유지 시간을 매우 짧게 제한하는 것이 좋다고 알려져 있습니다. 연결 유지는 SlowHTTPTest 및 Slowloris 서비스 거부 공격에 매우 손쉽게 악용될 수 있습니다(빠른 해결 방법은 serverfault.com의 ‘Keep-Dead 서비스 거부 공격 방어’를 참조하세요).
또한 이러한 웹 서버와 애플리케이션 서버는 일반적으로 각 연결마다 운영 체제 스레드나 프로세스를 할당합니다. TCP 연결은 매우 가벼운 운영 체제 객체이지만, 스레드나 프로세스는 매우 무겁습니다. 스레드와 프로세스에는 메모리가 필요하고 운영 체제에서 능동적으로 관리해야 하며, 스레드나 프로세스 간의 ‘컨텍스트 전환’에는 CPU 자원이 소모됩니다. 각 연결에 별도의 스레드나 프로세스를 할당하는 방식은 매우 비효율적입니다.

동시에 연결되는 클라이언트 수가 많고 각 연결에 스레드나 프로세스가 할당되면, 가벼운 HTTP 트랜잭션을 처리하는 데 지나치게 많은 작업이 필요한 이른바 ‘HTTP 중노동’ 현상이 발생합니다.
실제로는 이것이 무엇을 의미할까요?
오늘날의 많은 웹 서버와 애플리케이션 서버에서는 소수의 클라이언트만으로도 동시 접속 한도에 도달할 수 있습니다.
클라이언트가 TCP 연결 8개를 열고 마지막 사용 후에도 각 연결을 15초 동안 유지하면, 해당 클라이언트는 15초 동안 동시 연결 슬롯 8개를 차지합니다. 클라이언트가 초당 1명씩 웹사이트에 접속하면 유휴 Keep-Alive 연결이 동시 연결 슬롯 120개를 계속 점유합니다. 접속 속도가 초당 2명이면 슬롯 240개가 점유됩니다. 슬롯이 모두 소진되면 기존 연결이 시간 초과로 종료될 때까지 새 클라이언트는 연결할 수 없습니다.
이로 인해 서비스 수준이 매우 불균등해질 수 있습니다. Keep-Alive 연결을 성공적으로 확보한 클라이언트는 서비스를 자유롭게 이용할 수 있습니다. 반면 동시 연결 슬롯이 모두 사용 중일 때 연결을 시도하는 클라이언트는 접속하지 못하고 대기열에서 기다려야 합니다.
벤치마크 테스트 중에는 왜 이러한 영향이 나타나지 않을까요?
이러한 문제는 클라이언트가 많은 느린 네트워크에서만 발생합니다. 빠른 로컬 네트워크에서 단일 클라이언트로 벤치마크 테스트를 수행할 때는 나타나지 않습니다.
벤치마크에서는 이러한 영향이 나타나지 않을 수 있는 데에는 몇 가지 이유가 있습니다.
- 벤치마크 중에 연결 유지를 활성화하지 않으면 클라이언트는 각 트랜잭션마다 새 TCP 연결을 생성하며, 트랜잭션이 완료되면 해당 연결은 종료됩니다. 벤치마크는 대부분 빠른 로컬 네트워크에서 실행되므로 성공적으로 완료되며, 연결 유지를 사용하지 않아 발생하는 성능 문제는 드러나지 않습니다.
- 만약 연결 유지를 활성화하면, 서버의 연결 제한보다 적은 수의 동시 연결만 실행할 가능성이 높으며, 벤치마크 클라이언트가 각 연결을 포화 상태로 만들 정도로 반복해서 사용하여 서버가 최대 용량에 도달하게 됩니다. 하지만 이는 실제 환경의 연결 양상과는 다릅니다.
대부분의 벤치마크 도구는 성공한 트랜잭션만 보고한다는 점에 유의하세요. 리소스 고갈로 인해 정체된 연결은 보고되지 않거나, 성공한 연결 중 극히 일부에 불과한 것처럼 보일 수 있습니다. 이로 인해 실제 트래픽에서 발생하는 문제의 본질이 가려집니다.
이 문제는 얼마나 흔한가요?
스레드 또는 프로세스 기반의 모든 웹 서버나 애플리케이션 서버는 동시성 제한에 취약합니다.
이 문제는 각 연결에 스레드나 프로세스를 할당하는 모든 웹 또는 애플리케이션 플랫폼에 내재되어 있습니다. 최적화된 벤치마크 환경에서는 쉽게 발견되지 않지만, 실제 환경에서는 성능 저하와 과도한 CPU 사용률로 나타납니다.
이 문제를 해결하기 위해 취할 수 있는 조치는 다음과 같습니다:
- 스레드 또는 프로세스 수 늘리기 – 이는 매우 단기적인 조치입니다. 스레드와 프로세스는 운영 체제에서 많은 리소스를 사용하는 객체이므로, 생성되는 수가 늘어날수록 관리 오버헤드가 급격히 증가합니다.
- HTTP 연결 유지(keepalive) 사용을 비활성화하거나 제한합니다. 이렇게 하면 동시 접속 한도에 도달하는 시점을 늦출 수 있지만, 각 클라이언트의 성능이 크게 저하됩니다.
- 특수한 keepalive 처리를 사용합니다. Apache HTTP Server(웹 서버)에는 비교적 새로운 event MPM이 있으며, 연결 상태가 ‘활성’과 ‘유휴 keepalive’ 사이에서 전환될 때 작업자 스레드와 전용 이벤트 스레드 간에 연결을 이동합니다. 사용 중인 다른 모듈이 이 MPM을 지원한다면 이 방식을 선택할 수 있습니다. 단, SSL/TLS 연결은 여전히 전용 스레드에서만 처리됩니다.
- 더 효율적인 처리 모델 사용 – 가장 간단하면서도 효과적인 방법은 웹 서버나 애플리케이션 서버 앞에 효율적인 HTTP 프록시를 배치하는 것입니다. NGINX와 같은 이벤트 기반 프록시는 앞서 설명한 동시성 제한이 없습니다. 느린 연결과 유휴 keepalive 연결도 거뜬히 처리합니다. 또한 여러 유휴 keepalive 연결이 포함된 느린 클라이언트 측 연결을 빠르고 로컬에서 처리되는 매우 효율적인 벤치마크 방식의 연결로 효과적으로 변환하여 웹 서버와 애플리케이션 서버에서 최상의 성능을 끌어냅니다.
NGINX를 가속 HTTP 프록시로 사용
NGINX는 위에서 설명한 동시성 문제의 영향을 받지 않는 다른 아키텍처를 사용합니다. 느린 클라이언트 연결을 벤치마크 환경과 유사하게 최적화된 연결로 변환하여 서버에서 최상의 성능을 끌어냅니다.
NGINX는 매우 효율적인 이벤트 기반 모델을 사용하여 연결을 관리합니다.
각 NGINX 프로세스는 여러 연결을 동시에 처리할 수 있습니다. 새 연결이 수락될 때 발생하는 오버헤드는 매우 적으며(새 파일 디스크립터와 폴링할 새 이벤트로 구성됨), 이는 위에서 설명한 프로세스별 또는 스레드별 모델과 대조적입니다. NGINX에는 매우 효율적인 이벤트 루프가 있습니다:

이를 통해 각 NGINX 프로세스는 수십 개에서 수천 개, 수십만 개에 이르는 동시 연결을 손쉽게 처리할 수 있습니다.
그런 다음 NGINX는 로컬 keepalive 연결 풀을 사용하여 요청을 업스트림 서버로 프록시합니다. TCP 연결을 열고 닫는 데 따른 오버헤드가 발생하지 않으며, TCP 스택은 최적의 윈도 크기와 재시도 매개변수에 빠르게 적응합니다. 로컬에서 최적화된 네트워크를 통해 요청을 작성하고 응답을 읽는 작업이 훨씬 더 빠르게 이루어집니다:

결과적으로 업스트림 서버는 빠른 네트워크를 통해 단일 로컬 클라이언트(NGINX)와 통신하게 됩니다. 이 클라이언트는 HTTP 연결 유지(keepalive)를 최적으로 활용하여 불필요하게 연결을 계속 열어 두지 않으면서도 연결 설정을 최소화합니다. 이를 통해 서버는 벤치마크 환경과 유사한 최적의 상태로 돌아갑니다.
NGINX가 HTTP 프록시로 작동하면 다음과 같은 결과를 확인할 수 있습니다:
- 기존 리소스를 더 효율적으로 활용할 수 있습니다. 웹 서버와 애플리케이션 서버가 더 이상 HTTP 관련 부하가 큰 작업을 처리하지 않아도 되므로 초당 더 많은 트랜잭션을 처리할 수 있습니다.
- 오류 발생률 감소. NGINX가 모든 클라이언트의 중앙 스케줄러 역할을 하므로 HTTP 시간 초과가 발생할 가능성이 크게 줄어듭니다.
- 최종 사용자 성능 향상. 서버가 더 효율적으로 실행되고 연결을 더 빠르게 처리합니다.
NGINX로 서비스 속도를 높이는 다른 방법
HTTP의 과중한 처리 부담을 덜어주는 것은 NGINX가 과부하 상태의 애플리케이션 인프라 성능을 획기적으로 개선하기 위해 제공할 수 있는 여러 방안 중 하나일 뿐입니다.
NGINX의 HTTP 캐싱 기능은 표준 캐시 의미 체계에 따라 캐시할 항목과 캐시 기간을 제어하면서 업스트림 서버의 응답을 캐시할 수 있습니다. 여러 클라이언트가 동일한 리소스를 요청하면 NGINX는 캐시에서 응답하여 중복 요청으로 인한 업스트림 서버의 부담을 줄일 수 있습니다.
NGINX는 업스트림 서버의 다른 작업도 대신 처리할 수 있습니다. 대역폭 사용량을 줄이기 위한 데이터 압축 작업을 대신 처리하고, SSL/TLS 암호화 및 복호화를 중앙에서 수행하며, HTTP 기본 인증, 외부 인증 서버에 대한 하위 요청, JSON 웹 토큰 등을 사용해 초기 클라이언트 인증을 수행하고, 필요할 때 트래픽 속도를 제한하기 위한 다양한 규칙을 적용할 수 있습니다.
일반적인 로드 밸런서나 ADC와는 다릅니다
마지막으로, 다른 가속 프록시, 로드 밸런서 또는 애플리케이션 전송 컨트롤러(ADC)와 달리 NGINX는 완전한 웹 서버이기도 하다는 점을 잊지 마세요. NGINX를 사용하여 정적 콘텐츠를 제공하고, Java, PHP, Python, Ruby 및 기타 언어용 애플리케이션 서버로 트래픽을 전달하며, 미디어(오디오 및 비디오)를 전송하고, 인증 및 보안 시스템과 통합할 수 있습니다. 또한 NGINX 구성에 포함된 규칙을 사용하여 트랜잭션에 직접 응답할 수도 있습니다.
NGINX와 NGINX Plus에는 기본적으로 성능 제한이 없으므로 현재는 물론 향후에도 배포된 하드웨어의 성능을 최대한 활용할 수 있습니다.
NGINX Plus를 사용해 보려면 지금 30일 무료 평가판을 시작하거나, 사용 사례에 대해 논의하려면 문의해 주세요.
작성자 소개

관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.