Keepalive 설정으로 NGINX HTTP 성능 향상
HTTP keepalive 연결은 지연 시간을 줄이고 웹 페이지를 더 빠르게 로드할 수 있도록 하는 중요한 기능입니다.
서버를 벤치마킹한 후 실제 트래픽에 배포했는데 벤치마크 성능에 근접하지 못한다는 사실을 알게 된 적이 있으신가요? CPU 사용률은 낮고 사용 가능한 리소스는 충분하지만 클라이언트는 느린 응답 시간에 대해 불평하고 서버의 활용도를 높일 방법을 찾지 못합니다.
여러분이 겪고 있는 현상은 “HTTP Heavy Lifting”이라고 부를 수 있는 효과 중 하나입니다. 이 포스트에서는 HTTP가 작동하는 방식과 일반적인 HTTP 서버가 HTTP 트랜잭션을 처리하는 방식을 살펴봅니다. 그리고 발생할 수 있는 몇 가지 성능 문제를 살펴보고, NGINX의 이벤트 중심 모델이 어떻게 이러한 HTTP 서버를 위한 매우 효과적인 Proxy가 되는지 살펴봅니다. NGINX를 사용하면 실제 성능을 로컬 벤치마크 수준으로 되돌릴 수 있습니다.
목차
1. HTTP 및 Keepalive 연결 개요
2. Keepalive가 HTTP 서버에 미치는 영향은 무엇인가요?
3. 실제로 이것은 무엇을 의미할까요?
4. 벤치마크 테스트 중에 이러한 효과가 나타나지 않는 이유는 무엇인가요?
5. 이 문제는 얼마나 흔한가요?
6. 가속 HTTP Proxy로 NGINX Keepalive 사용하기
7. NGINX가 서비스를 가속화하는 다른 방법
8. 일반적인 Load Balancer나 ADC가 아닙니다.
1. HTTP 및 Keepalive 연결 개요
HTTP는 간단한 텍스트 기반 프로토콜입니다. 이전에 해본 적이 없다면 웹 브라우저에 있는 것과 같은 HTTP 디버깅 도구의 출력을 살펴보고 표준 요청 및 응답 구조를 확인해 보세요.

가장 간단한 구현에서는 HTTP 클라이언트가 대상 서버에 새 TCP 연결을 생성하고 요청을 작성한 후 응답을 받습니다. 그런 다음 서버는 리소스를 해제하기 위해 TCP 연결을 닫습니다.

이 작동 모드는 특히 요소가 많은 복잡한 웹 페이지나 네트워크 링크가 느린 경우 매우 비효율적일 수 있습니다. 새 TCP 연결을 만들려면 ‘3-Way Handshake’가 필요하며, 연결을 끊으려면 양방향 종료 절차도 필요합니다. 메시지마다 하나씩 TCP 연결을 만들고 닫는 작업을 반복하는 것은 전화 통화에서 상대방이 말을 끝낸 후 전화를 끊고 다시 걸기를 반복하는 것과 비슷합니다.
HTTP는 Keepalive 연결이라는 메커니즘을 사용하여 HTTP 트랜잭션이 완료된 후에도 클라이언트와 서버 간의 TCP 연결을 열어둡니다. 클라이언트가 다른 HTTP 트랜잭션을 수행해야 하는 경우 새 TCP 연결을 만드는 대신 유휴 Keepalive 연결을 사용할 수 있습니다.

클라이언트는 일반적으로 서버에 대한 여러 개의 동시 TCP 연결을 열고 이 모든 연결에서 Keepalive 트랜잭션을 수행합니다. 이러한 연결은 일반적으로 유휴 시간 초과로 인해 클라이언트나 서버가 더 이상 필요하지 않다고 판단할 때까지 열려 있습니다.
현대 웹 브라우저는 일반적으로 6~8개의 Keepalive 연결을 열고 몇 분 동안 연결 상태를 유지한 후 시간이 초과되면 연결을 닫습니다. 웹 서버는 이러한 연결의 시간을 제한하고 더 빨리 닫도록 구성할 수 있습니다.
2. Keepalive 가 HTTP 서버에 미치는 영향은 무엇인가요?
많은 클라이언트가 HTTP Keepalive를 사용하는데 웹 서버에 동시성 제한 또는 확장성 문제가 있는 경우, 해당 허용 한도에 도달하면 성능이 급격히 떨어집니다.
위의 접근 방식은 개별 클라이언트에 최상의 성능을 제공하도록 설계되었습니다. 하지만 안타깝게도 ‘Tragedy of the Commons‘와 같은 시나리오에서 모든 클라이언트가 이러한 방식으로 작동하면 많은 일반 웹 서버 및 웹 애플리케이션의 성능에 악영향을 미칠 수 있습니다.
그 이유는 많은 서버가 고정된 동시성 제한을 가지고 있기 때문입니다. 예를 들어, 일반적인 구성에서 Apache HTTP 서버는 제한된 수의 동시 TCP 연결만 처리할 수 있습니다: Worker MPM(Multiprocessing Module)을 사용하는 경우 150개, Prefork MPM을 사용하는 경우 256개입니다. 각 유휴 HTTP Keepalive 연결은 이러한 동시성 슬롯 중 하나를 사용하며, 모든 슬롯이 사용되면 서버는 더 이상 HTTP 연결을 수락할 수 없습니다.
일반적인 상식으로는 웹 서버에서 Keepalive를 끄거나 매우 짧은 수명으로 제한하는 것이 좋습니다. Keepalive는 SlowHTTPTest 및 Slowloris 서비스 거부 공격에 매우 간단한 벡터를 제공합니다(빠른 해결책은 serverfault.com에서 Keep-Dead 서비스 거부로부터 보호하기를 참조하세요).
또한 이러한 웹 및 애플리케이션 서버는 일반적으로 각 연결에 대해 운영체제 스레드 또는 프로세스를 할당합니다. TCP 연결은 매우 가벼운 운영 체제 객체이지만 스레드나 프로세스는 매우 무거운 객체입니다. 스레드와 프로세스는 메모리가 필요하고 운영체제에서 적극적으로 관리해야 하며 스레드 또는 프로세스 간의 ‘컨텍스트 전환’은 CPU를 소모합니다. 각 연결에 자체 스레드나 프로세스를 할당하는 것은 매우 비효율적입니다.

많은 수의 동시 클라이언트 연결과 각 연결에 스레드 또는 프로세스가 할당되면 경량 HTTP 트랜잭션을 처리하는 데 불균형적으로 많은 노력이 필요한 “HTTP Heavy Lifting”이라는 현상이 발생합니다.
3. 실제로 이것은 무엇을 의미할까요?
최신 웹 및 애플리케이션 서버에서는 많은 클라이언트가 동시성 제한을 소진하는 데 많은 시간이 걸리지 않습니다.
클라이언트가 8개의 TCP 연결을 열고 마지막 사용 후 각 연결을 15초 동안 유지하면 클라이언트는 15초 동안 8개의 동시성 슬롯을 소비합니다. 클라이언트가 초당 1명의 비율로 웹사이트에 도착하는 경우 120개의 동시 접속 슬롯이 유휴 Keepalive 연결에 의해 지속적으로 점유됩니다. 초당 클라이언트 수가 2명인 경우 240개의 동시 접속 슬롯이 사용됩니다. 슬롯이 모두 소진되면 기존 연결이 시간 초과될 때까지 새 클라이언트가 연결할 수 없습니다.
이로 인해 서비스 수준이 매우 고르지 않을 수 있습니다. Keepalive 연결을 성공적으로 획득한 클라이언트는 마음대로 서비스를 탐색할 수 있습니다. 동시 접속 슬롯이 모두 사용 중일 때 연결을 시도하는 클라이언트는 접속이 차단되어 Queue에서 대기해야 합니다.
4. 벤치마크 테스트 중에 이러한 효과가 나타나지 않는 이유는 무엇인가요?
이러한 문제는 많은 클라이언트가 있는 느린 네트워크에서만 나타납니다. 빠른 로컬 네트워크에서 단일 클라이언트로 벤치마킹할 때는 이러한 문제가 나타나지 않습니다.
벤치마크에서 이러한 효과가 표시되지 않는 데에는 몇 가지 이유가 있습니다.
- 벤치마크 중에 Keepalive를 활성화하지 않으면 클라이언트는 각 트랜잭션에 대해 새 TCP 연결을 생성합니다(트랜잭션이 완료되면 연결이 끊어집니다). 빠른 로컬 네트워크에서 벤치마크를 실행할 가능성이 높기 때문에 벤치마크가 성공하고 Keepalive를 사용하지 않을 때 발생하는 성능 문제가 나타나지 않습니다.
- Keepalive를 활성화하면 서버의 제한보다 적은 수의 동시 연결을 실행할 수 있으며 벤치마크 클라이언트가 각 연결을 포화시켜(반복적으로 사용) 서버를 최대 용량으로 끌어올릴 가능성이 높습니다. 그러나 이는 실제 연결 패턴과 유사하지 않습니다.
Note: 대부분의 벤치마크 도구는 성공적인 트랜잭션만 보고합니다. 리소스 고갈로 인해 중단된 연결은 보고되지 않거나 성공한 연결의 극히 일부에 불과한 것처럼 보일 수 있습니다. 이는 실제 트래픽 문제의 본질을 은폐합니다.
5. 이 문제는 얼마나 흔한가요?
모든 스레드 또는 프로세스 기반 웹 또는 애플리케이션 서버는 동시성 제한에 취약합니다.
이 문제는 각 연결에 스레드 또는 프로세스를 할당하는 모든 웹 또는 애플리케이션 플랫폼에 내재되어 있습니다. 최적화된 벤치마크 환경에서는 발견하기 쉽지 않지만 실제 환경에서는 성능 저하와 과도한 CPU 사용률로 나타납니다.
이 문제를 해결하기 위해 취할 수 있는 몇 가지 조치가 있습니다.
- 스레드 또는 프로세스 수 늘리기 – 이는 상당히 임시방편적인 조치입니다. 스레드와 프로세스는 무거운 운영 체제 객체이며 점점 더 많이 생성됨에 따라 관리 오버헤드가 급격히 증가합니다.
- HTTP Keepalive 사용 비활성화 또는 사용 제한하기 – 이렇게 하면 동시성 제한이 연기되지만 각 클라이언트의 성능이 훨씬 저하됩니다.
- 특수 Keepalive 처리 사용 – 아파치 HTTP 서버(웹 서버)에는 작업자 스레드와 전용 이벤트 스레드 간에 연결이 ‘Active’과 ‘유휴 Keepalive’ 상태 사이를 이동할 때 연결을 이동하는 비교적 새로운 event MPM이 있습니다. 사용하는 다른 모듈이 이 MPM을 지원하는 경우 이 옵션을 사용할 수 있지만, SSL/TLS 연결은 여전히 전용 스레드에서 전적으로 처리된다는 점에 유의하세요.
- 보다 효율적인 처리 모델 사용하기 – 가장 간단하고 효과적인 방법은 웹 또는 애플리케이션 서버 앞에 효율적인 HTTP Proxy를 배치하는 것입니다. NGINX와 같은 이벤트 기반 Proxy에는 위에서 설명한 동시성 제한이 없습니다. 느린 연결과 유휴 Keepalive에도 웃으며 대처할 수 있습니다. 또한 여러 개의 유휴 Keepalive 연결이 있는 느린 클라이언트 측 연결을 웹 및 애플리케이션 서버에서 가능한 최상의 성능을 추출하는 빠르고 로컬이며 매우 효율적인 벤치마크 스타일 연결로 효과적으로 변환합니다.
6. 가속 HTTP Proxy로 NGINX Keepalive 사용하기
NGINX는 위에서 설명한 동시성 문제를 겪지 않는 다른 아키텍처를 사용합니다. 느린 클라이언트 연결을 벤치마크에 최적화된 연결로 변환하여 서버에서 최상의 성능을 이끌어냅니다.
NGINX는 매우 효율적인 이벤트 중심 모델을 사용하여 연결을 관리합니다.
각 NGINX 프로세스는 동시에 여러 연결을 처리할 수 있습니다. 새 연결이 수락되면 위에서 설명한 Process 별 또는 Thread 별 모델과 달리 오버헤드가 매우 낮습니다(새 파일 기술자와 Polling할 새 이벤트로 구성되어 있습니다). NGINX에는 매우 효과적인 이벤트 반복 기능이 있습니다.

이를 통해 각 NGINX 프로세스는 동시에 수만, 수천, 수십만 개의 연결로 쉽게 확장할 수 있습니다.
그런 다음 NGINX는 Keepalive 연결의 로컬 Pool을 사용하여 요청을 Upstream 서버로 Proxy합니다. TCP 연결을 열고 닫을 때 발생하는 오버헤드가 발생하지 않으며, TCP 스택이 최적의 창 크기와 retry 매개변수에 빠르게 적응합니다. 최적화된 로컬 네트워크를 통해 요청을 작성하고 응답을 읽는 속도가 훨씬 빨라집니다:

그 결과 Upstream 서버는 빠른 네트워크를 통해 단일 로컬 클라이언트(NGINX)와 통신하게 되고, 이 클라이언트는 HTTP Keepalive 연결을 최적으로 활용하여 불필요하게 연결을 열어두지 않고 연결 설정을 최소화할 수 있습니다. 이렇게 하면 서버가 벤치마크와 같은 최적의 환경으로 돌아갑니다.
NGINX가 HTTP Proxy 역할을 하는 것을 확인할 수 있습니다.
- 기존 리소스의 활용도 향상. 웹 및 애플리케이션 서버는 더 이상 HTTP 작업을 많이 수행하지 않기 때문에 초당 더 많은 트랜잭션을 처리할 수 있습니다.
- 오류 감소. NGINX가 모든 클라이언트에 대한 중앙 스케줄러 역할을 하기 때문에 HTTP 시간 초과 발생 가능성이 훨씬 적습니다.
- 최종 사용자 성능 향상. 서버가 더 효율적으로 실행되고 서비스 연결이 더 빨라집니다.
7. NGINX가 서비스를 가속화하는 다른 방법
HTTP Heavy Lifting의 부담을 제거하는 것은 과부하가 걸린 애플리케이션 인프라에 NGINX가 가져올 수 있는 성능 향상 방법 중 하나에 불과합니다.
NGINX의 HTTP 캐싱 기능은 표준 캐시 규칙에 따라 Upstream 서버의 응답을 캐시하여 캐시되는 항목과 기간을 제어할 수 있습니다. 여러 클라이언트가 동일한 리소스를 요청하는 경우, NGINX는 캐시에서 응답할 수 있으며 중복 요청으로 인해 Upstream 서버에 부담을 주지 않습니다.
또한 NGINX는 Upstream 서버에서 다른 작업을 Offload할 수 있습니다. 대역폭 사용량을 줄이기 위해 데이터 압축 작업을 Offload하고, SSL/TLS 암호화 및 복호화를 중앙 집중화하고, 초기 클라이언트 인증(예: HTTP 기본 인증, 외부 인증 서버에 대한 하위 요청 및 JSON Web Token)을 수행하고, 필요한 경우 모든 종류의 규칙을 적용하여 트래픽을 제한할 수 있습니다.
8. 일반적인 Load Balancer나 ADC가 아닙니다.
마지막으로, 다른 가속 Proxy, Load Balancer 또는 Application Delivery Controller(ADC)와 달리 NGINX는 완전한 웹 서버라는 점도 잊지 마세요. NGINX를 사용하여 정적 콘텐츠를 제공하고, Java, PHP, Python, Ruby 및 기타 언어의 애플리케이션 서버로 트래픽을 전달하고, 미디어(오디오 및 비디오)를 전송하고, 인증 및 보안 시스템과 통합하고, NGINX 구성에 내장된 규칙을 사용하여 트랜잭션에 직접 응답할 수도 있습니다.
NGINX Plus를 직접 사용해 보거나 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.
댓글을 달려면 로그인해야 합니다.