본문 바로가기

network

Nginx 등장 배경 (feat. Apache Http Server)

1. 개요

무중단 배포를 진행했을때 클라이언트 -> (HTTPS) ALB -> (HTTP) NGINX -> 백엔드 서버

이런 흐름으로 Nginx를 사용했었다. 

 

웹 서버의 종류에는 여러가지가 있고 Apache Server도 널리 사용되는 것으로 알고 있는데

과연 이 두개의 차이는 무엇이고 Nginx의 등장 배경은 무엇인지,

나는 정말 Nginx를 제대로 알고 사용하는 것인지 의문이 들어 알아보았다 !

 

2. 본문

🏃‍➡️ 2-1. Nginx의 이해

 

 

 

Nginx는 웹서버, 리버스 프록시, 로드 밸런서, 그리고 HTTP 캐시 기능을 갖춘 소프트웨어이다. Nginx는 요청에 응답하기 위해 Event 기반 구조를 채택하여, 현재 웹 서버분야에서 1등을 차지하고 있다.

Nginx는 성능이 좋고 가벼운 특징으로 인해 선택되며, 최신 기술임에도 불구하고 오랜 역사를 가진다.

 

Nginx와 아파치 서버의 차이를 이해하기 위해서는 두 서버의 탄생 당시 인터넷 상황을 살펴보아야 한다.

 

 

 

🏃‍➡️ 2-2. 1995년 - Apache 서버의 탄생

 

 

Apache Server가 탄생하기 이전에, UNIX 기반으로 만들어진 최초의 Web Server NCSA HTTPd가 있었다.

버그가 굉장히 많고, 불안정해서 개발자들이 사용할 때마다 불편함을 겪고 있었다.

도저히 못쓰겠다고 불평하던 개발자들 중 일부가 버그를 수정하고, 구조를 변경하는 과정에서 탄생한 것이 바로 아파치 서버(Apache Http server Project)다.

정식 명칭은 Apache Http Server다. 이 글에서는 편의를 위해 ‘Apache Server’라고 부른다.

 

최초의 Apache Server는 클라이언트의 요청이 들어오면 connection을 형성하기 위해 매번 새로운 Process를 생성했다.

 

이는 Unix 계열 OS가 Network Connection을 생성하는 방법을 그대로 차용한 것이다. 그러나 Process를 생성하는 시간이 워낙 오버헤드가 크다보니, 요청이 들어오기 전 미리 Process를 생성해두는 pre-fork 방식을 채택하여 사용했다.

 

Apache Server의 이런 구조는 추가적인 개발이 쉬워 뛰어난 확장성을 갖춘다는 장점이 있었다.

필요하다 싶은 기능들은 모두 모듈로 만들어 탑재할 수 있었다. 다양하고 많은 모듈이 만들어져서 빠르게 탑재되었으며, 이 덕분에 Apache Server가 동적 컨텐츠를 핸들링 할 수도 있었다. Apache Server 하나만으로도 클라이언트의 요청을 받고, 응답을 만들어 처리하는 모든 과정을 소화할 수 있던 것이다. 뛰어난 확장성을 필두로 출시한지 1년도 안된 시간만에 웹 서버 업계 1위를 달성할 수 있었다.

 

! 그러나 1999년이 되자 문제가 발생하기 시작했다.

 

 

🏃‍➡️ 2-3. 1999년 - C10K 문제 발생

 

1999년 이후 점차 컴퓨터가 보급되고 클라이언트들의 요청이 많아지면서 Server에 동시에 몰리는 connection이 많아졌다. 이 때 하나의 서버에 몰린 커넥션이 너무 많아 더 이상 커넥션을 형성하지 못하는 문제가 발생했는데,

 

이 문제를 C10K(Connection 10,000) 라고 불렀다.

 

혹시나 동시에 연결된 connection 수와 초당 요청 처리 수를 헷갈려선 안된다. 

동시에 연결된 connection 수는 Server가 한 시점에 얼마나 많은 클라이언트와 connection을 형성 하는지를 뜻하며

초당 요청 처리 수는 Server가 1초에 몇 개의 요청을 처리하는가(얼마나 빠른가) 를 뜻한다.

한 클라이언트는 하나의 connection을 이용해서 여러 번의 요청을 보낼 수 있다. Connection은 오랜 시간동안 유지될 수 있다.

따라서 동시에 연결된 커넥션 수초당 요청 처리 수는 엄연히 다르다.

 

 

게다가 각 서비스들은 매 번 connection을 만드는게 비효율적이고 속도도 느리다고 판단해서, Keep-Alive Header를 사용해 긴 시간 동안 connection을 유지시켰다. 이렇게 동시에 연결된 connection의 수가 많아지면서 Server가 더 이상 connection을 형성하지 못했다.

 

C10K 문제에서 하드웨어 스펙은 이미 충분히 발전되어 있는 상태였고, 결국 Apache Server의 connection 형성 구조가 문제였다. (Apache Server 외 대다수 서버 프로그램도 마찬가지) 동시에 처리하는 커넥션이 많아지면 덩달아 생성하는 Process가 많아졌고, 자연스레 Memory 부족 현상으로 이어졌다.

설상 가상으로 많은 Process가 차지하는 resource 양도 많다. 결국 CPU Core가 Process를 바꿔가며 Context Switching을 수행하기 바빴다.

 

 

즉, Apache Server가 가지고 있는 구조 자체가 수 많은 동시 connection을 감당하기에 부적합했다. 현재까지도 Apache Server 개발자들은 Apache Server가 가지고 있는 기본 구조를 유지하면서도 수 많은 동시 connection을 감당하기 위한 방법을 찾기 위해 노력하고 있다.

 

그와 별개로 완전히 다른 구조를 채택하는 경우도 존재했는데, 그 것들 중 하나가 바로 2004년에 등장 Nginx다.

 

 

 

🏃‍➡️ 2-4. 2004년 - Nginx의 등장

 

 

초창기 Nginx는 Apache Server와 함께 사용하기 위한 용도로 탄생했다.

 

Web Server이긴 했지만 Apache Server를 완전히 대체할 목적은 아니었다.

대게 Apache Server 앞단에 Nginx를 두는 것으로 동시 connection의 부하를 분산시켰다.

 

 

또한 Nginx는 그 자체로도 Web Server의 역할을 수행할 수 있어서, 정적 컨텐츠를 Nginx가 반환하고 Apache Server는 개발자가 원하는 비즈니스 로직만 처리하게 구성할 수도 있었다.

 

그렇다면 Nginx는 어떤 구조를 가지고 있길레 대량의 connection 부하를 버틸 수 있었을까?

 

 

Nginx는 기본적으로 Master Process가 nginx.conf 설정 파일을 읽고, 그 설정 파일에 맞게 Worker Process를 생성한다. 그리고 Worker Process가 만들어질 때 각자 지정된 Listen Socket을 배정 받는다.

그 Socket에 새로운 클라이언트로부터 요청이 들어오면 connection을 형성하고 요청을 처리한다. (그 후 Keep-Alive Header에 따라 connection을 유지한다.)

 

여기서 Apache Server와 다른 점이 또 있는데, connection이 형성된 후 Worker Process에서 connection 하나만을 한정적으로 관리하진 않는다. 관리중인 connection에 아무런 요청이 없을 경우 새로운 connection을 형성하거나 이미 만들어진 connection으로부터 요청을 받아 처리한다.

 

 

Nginx에서는 이런 connection 형성, connection 제거, 그리고 새로운 요청 처리를 Event라는 단위로 부른다. 그리고 이 event들은 OS kernel이 Queue 형식으로 각각의 Worker Process에게 전달해준다.

Event들은 Queue에 담긴 상태에서 Worker Process가 처리해줄 때까지 비동기 방식으로 대기한다. Worker Process는 하나의 thread로 Event를 꺼내서 처리해나간다.

 

이 덕분에 적은 수의 Worker Process가 쉬지 않고 계속해서 일을 한다는 장점이 있다.

 

 

기존 Apache Server는 pre-forked 방식 때문에 클라이언트의 요청이 없을 경우 Process가 방치된다는 단점이 있었는데, Nginx는 Server 자원을 훨씬 효율적으로 활용하게 된다.

 

그런데 만약 만약 요청 중 하나가 시간이 오래 걸리는 작업(Disk I/O 등)이면 Nginx는 어떻게 동작할까?

 

Nginx는 이런 상황을 방지하기 위해 시간이 오래걸리는 Event를 따로 수행하는 Thread Pool을 만들어둔다. 그리고 시간이 오래 걸리는 Event를 감지하면 해당 Event를 Thread Pool에 넘기고 다음 Event를 수행하러 간다.

 

 

이러한 Worker Process는 보통 CPU의 Core 수 만큼 생성한다.

즉, CPU Core가 담당하는 Process를 바꾸는 횟수를 획기적으로 줄인다. Context Switching으로 인한 오버헤드가 대폭 감소한다. 여기까지가 바로 Nginx가 채택한 Event-Driven-Model 이벤트 기반 구조이다.

 

물론 Nginx에도 단점은 존재한다. 개발자가 기능 추가를 시도 했다가 돌아가고 있는 Worker Process를 종료하는 상황이 생길 수도 있다. 그럼 Worker Process가 관리 중이던 Event(커넥션과 요청)들을 더 이상 처리 할 수 없게 된다.

그래서 Nginx는 개발자가 직접 모듈을 만들기 까다롭다.

 

 

그럼에도 장점이 워~낙 경력해서 현재 서비스 시장에서 많이 사용된다.

게다가 적은 Worker Process로 동작하는 Nginx의 구조는 Nginx 자체의 설정을 동적으로 변경하는 것을 가능하게 한다.

 

 

개발자가 nginx.conf 설정 파일을 변경하고 Nginx에 해당 설정을 적용하면 Master Process는 그 설정에 맞는 Worker Process를 새로 생성한다.

 

그리고 기존 Worker Process가 더 이상 connection을 형성하지 않도록 하고, 기존 Worker Process가 커넥션의 모든 Event를 마무리하면 종료하도록 한다.

이후 새롭게 생성된 Worker Process들이 connection을 형성하고 뒤따라 들어온 요청들을 수행하게 된다.

 

 

Nginx의 특성 덕분에 동시 connection을 유지하며 기존 요청을 계속 처리함과 동시에 뒷단에 새로운 Server를 추가할 수 있다.

Load Balancer의 역할을 수행할 수 있다.

 

 

이렇게나 대단한 Nginx도 2007년까지는 Web Server 순위권에 존재하지 않았다.여전히 Apache가 압도적인 1등을 유지하고 있었다.

 

그런데 2008년부터 점점 지표가 움직이기 시작했다.Apache가 점점 하락세를 보이고, Nginx가 급부상하기 시작했다.

 

 

도대체 2008년에 무슨 일이 있었던걸까?

 

 

 

🏃‍➡️ 2-5. 2008년 - 스마트 폰의 보급

 

 

 

바로 스마트폰의 보급이 시작된 것이다.

스마트 폰의 보급으로 인터넷 사용이 폭발적으로 늘면서 유튜브와 같은 영상 스트리밍 서비스, SNS 등 수 많은 동시 connection과 방대해진 리소스 요청을 감당할 수 있는 Web Server가 필요했다.

 

모든 요청마다 Process를 생성하던 Apache Server -> Nginx라는 대체제로 눈을 돌릴 수 밖에 없었다.

덕분에 현재 Nginx가 인터넷 트래픽에 관여하는 비중은 계속 높아지고 있다.

 

 

 

물론 Apache 진영도 마냥 손을 놓고 있진 않았다.

기존 pre-forked 방식 vs Worker 방식을 기호에 따라 선택할 수 있는 모듈도 만들어냈다. 

 

 

 

그럼에도 Memory 사용률과 초당 요청 처리수에서 압도적인 성능차이를 보였다.

 

결국 Apache Server가 Nginx를 대체할 수 없는 수준이 된 것이다.

그렇다고 해서 Apache Server가 무너진 것은 아니다. 여전히 Nginx와 1,2위를 다투고 있다.

 

 

Apache Server는 NCSA Httpd의 불편함과 버그를 고치면서 탄생했기 때문에, 기본적으로 호환성과 확장성이 뛰어나다는 장점이 있다. 대표적인 사례로 Nginx는 윈도우에서 제대로된 성능을 발휘하지 못한다는 이슈가 있지만, Apache Server는 이미 아주 오래전에 OS 호환성 관련 문제를 해결했다.

 

또한 모듈을 추가해서 기능을 확장하기 쉽다는 장점 덕분에, Apache Server로 Web Server를 사용중인 서비스에 언제든지 모듈을 등록해서 새로운 기능을 추가할 수도 있다.

이러한 모듈의 종류도 당연히 Apache Server가 훨씬 많다.

 

 

 

🏃‍➡️ 2-6. 2021 - 우리는 Nginx를 어떻게 사용해야 하는가?

 

 

 

Nginx는 비단 Web Server 뿐만 아니라 Load Balancer, Web Server 가속기, SSL 터미네이션으로도 쓰인다.

 

SSL 터미네이션

Client와는 https, Server와는 http 통신.
이 구조를 통해 뒷단의 서버가 복호화 과정을 감당하지 않고 비즈니스 처리에 더 집중할 수 있도록 한다.
보통 뒷단 서버와 Nginx를 같은 서브넷에 속하게 해서 보안이슈를 해결한다.

 

 

 

또, HTTP 프로토콜을 사용해서 전달되는 컨텐츠를 Caching할 수도 있다.

(캐싱을 하는 경우에는 NGINX를 클라이언트 쪽에 가깝게 구성한다.)

이 외에도 HSTS, CORS처리, TCP/UDP Connection 부하 분산, HTTP/2 지원 등등… 정말 많은 일을 한다.

 

 

 

3. 마무리

Nginx의 등장 배경을 알아보니 더 깊이 있는 이해가 된 것 같다.

Thread Pool을 두거나 Worker Process로 비동기적으로 Event를 처리하는 점 등등 Event 기반 모델이기 때문에 뭔가 Kafka와도 비슷한 동작 방식인것 같다는 생각도 들었다.

 

 

커널이 어떻게 이벤트를 제공하는지는 epoll, 멀티플렉싱에 대해 공부해보고

최근 Apache Server의 구조인 APACHE MPM에 대해서도 알아봐야겠다!

 

 


 

 

reference

 

https://hyeon9mak.github.io/nginx-vs-apache/

https://www.youtube.com/watch?v=6FAwAXXj5N0