Post

Nginx - 이론편

Nginx 소개

웹 서버

웹 서버는 HTTP를 통해 클라이언트의 요청을 받고, 응답으로 데이터를 전송해주는 소프트웨어다. 주로 정적 데이터를 다룰 때 웹서버라는 표현을 사용하며 동적 데이터를 다룰 땐 WAS, Web Application Server를 이용한다.

현대의 WAS는 웹 서버의 기능도 포함하지만 여전히 분리하여 운영하는 것이 주류이다. 웹 서버 분리는 정적 컨텐츠 제공 외에도 리버스 프록시를 비롯하여 큰 도움이 되기때문이다.

그림으로 표현하면 위와 같다. 이때 중간의 웹 서버를 리버스 프록시라고 부른다. 클라이언트와 WAS사이의 중개자 역할을 맡는다. 리버스 프록시를 사용함으로써 얻는 장점은 아래와 같다.

  • 보안, 클라이언트는 WAS 서버의 존재를 모르며 nginx를 통하지 않고는 접근하지 못하도록 막을 수 있다. 이는 익명성, 접근 제한을 의미한다.
  • 로드 밸런스/대역폭 조절, 하나의 웹 서버에 다수의 WAS를 띄워 연결하여 부하를 분산할 수 있다. 이 외에도 대역폭을 제어할 수 있는 수단을 가진다.
  • 캐싱
  • 로깅
  • 무중단 배포
  • 가상 호스팅

웹 서버와 구분하기 위해 설명에선 WAS라는 명칭을 사용하였는데 Reverse proxy 뒷단에 있는 서버가 꼭 WAS일 필요는 없다. 웹서버 뒤에 웹서버를 두거나 S3 서버를 두는 방식도 가능하다.

웹서버 트렌드

현재 가장 많이 사용하는 웹 서버는 Apache HTTP 서버와 Nginx다. w3에서 제공하는 실제 트렌드를 살펴보자.

2020년 까진 아파치가 앞섰으나 그 이후 Nginx가 미묘하게 앞서며 비슷한 수준을 유지하고 있다. 먼 과거에는 아파치가 압도적인 인기를 자랑하였는데 어째서 Nginx가 등장했을까?

Apache HTTP Server

아파치는 프로세스 또는 스레드를 기반으로 요청을 처리한다.

아파치는 리눅스를 대상으로한 소프트웨어였다. 대다수의 서버는 리눅스로 돌렸기 때문에 점유율도 그에 따라 매우 높았다. 그러다 인터넷 사용자가 많아지며 문제가 하나 발견되었다.

초기 아파치 모델은 매 요청에 프로세스를 할당하였다. HTTP는 Keep-Alive 헤더를 통해 커넥션을 유지할 수 있다. 각 커넥션은 하나의 프로세스가 맡으므로, Keep-Alive를 위해 프로세스는 계속해서 서버상에 메모리를 유지해야만 했다.

클라이언트는 Keep-Alive를 통해 커넥션을 유지하고, 하나의 커넥션을 통해 여러 요청을 보낼 수 있다.

프로세스가 10,000개 가까이 생성되면 CPU와 메모리에 과부하가 발생하여 서버가 정상적으로 동작하지 못하는 현상이 생겼다. 프로세스가 많아질수록 메모리가 꽉차며 컨텍스트 스위칭이 빈번하게 발생하기 때문이다.

이러한 문제를 C10K Problem, Connection 10K Problem이라고 부른다. 이런 동시 접속 문제를 해결하기 위해 Nginx가 등장하였다.

참고로 이는 과거의 이야기로, 현재의 아파치는 여러 MPM(Multi Process Module)을 제공한다. 현재는 prefork(위에서 설명한 방식), worker (스레드 기반), event 등 다양한 튜닝 모듈을 제공한다. 특히 2.4 버전의 Event MPM 등장 이후 속도 문제는 크게 개선되었다.

preforkworker도 스레드풀처럼 여러개의 프로세스, 스레드를 미리 생성해둔다. (애초에 이름이 pre-fork니까..)

아파치는 여전히 확장성안정성을 장점으로 많은 점유율을 보유하고 있다. 두 가지 키워드로 함축되었기 때문에 “뭐야 별 장점 없네?”라고 생각할 수도 있다. 하지만 서버를 운영할 때 가장 중요한 두 가지가 확장성과 안정성이다. 극단적인 예시로, 안정성이 가장 중요한 은행 서버는 최근까지도 코볼과 같은 레거시 코드를 사용한다.

최근 점유율이 다시 감소하고 있는데 HTTP 3.0 지원의 기미가 보이지 않는 영향도 있다.

Nginx

Nginx는 어떻게 C10K 문제를 해결하였고, 왜 빠를까?

Nginx는 하나의 Master Process와 고정된 수의 Worker Process가 존재한다.

공식 블로그의 설명은 아래와 같다.

  • Master Process는 설정을 읽고, 포트를 바인드하고 자식 프로세스를 생성한다.
  • Cache Loader는 디스크에 저장된 캐시를 로드한다.
  • Cache Manager는 주기적으로 디스크 캐시를 가지친다.
  • Worker Process는 그 외 나머지 모든 작업을 한다. 네트워크 커넥션을 처리하고, Disk I/O, 업스트림 서버와의 통신 모든 것을 포함한다.

아파치와 다른점은, 아파치는 매 커넥션마다 하나의 프로세스를 생성한다면 Nginx는 하나의 워커 프로세스가 여러 커넥션을 처리할 수 있다. 이는 Nginx가 Event-Driven 방식으로 요청을 처리한 덕분이다.

Nginx는 커넥션 수립, 제거, 요청 처리를 비롯한 모든 작업을 이벤트, Event라고 부른다. 이벤트 단위로 작업을 처리하기 때문에 Event-Driven 이라는 표현을 사용하며, 이벤트는 비동기로 처리된다.

워커의 수는 주로 CPU 코어 수와 동일하게 유지한다. (auto 설정을 권장하며 이는 CPU 코어 수에 맞도록 워커 프로세스를 생성한다.) 이벤트를 비동기 방식으로 처리하기 때문에 프로세스가 쉬지 않고 일을 하며, 컨텍스트 스위칭이 자연스레 줄어든다.

위의 특징으로 인해 Nginx는 다음의 장점을 가진다.

  • 커넥션을 처리하는 속도가 증가한다.
  • 동시 커넥션 처리양이 증가한다.
  • 프로세스가 적게 유지되기 때문에 동적 리로드가 가능하다. (손쉽게 설정을 바꿀 수 있다.)

이는 실제 성능 테스트 지표에서도 확인할 수 있다. 테스트 자료는 DreamHost의 자료다.

동적 리로드는 어떤 장점이 있을까?

한 가지 상황을 가정해보자. 위에서 리버스 프록시의 장점 중 한 가지로 로드 밸런싱을 언급했다. 로드 밸런싱을 위해선 여러 개의 WAS를 띄워야한다. 만약 WAS를 추가하거나 제거하기 위해 설정을 변경한다면, 동적 리로드 덕분에 nginx 서버를 종료하지 않아도 설정을 반영할 수 있다.

이러한 성질을 이용해 무중단 배포도 가능한 것이다.

마치며

웹서버, 특히 Nginx에 대해 간략히 알아보았다.

다음 포스트에선 직접 Nginx를 세팅해 볼 예정이다.

This post is licensed under CC BY 4.0 by the author.