1. Nginx란?
리버스 프록시, 로드밸런스서, http 캐시 등 다양한 기술을 제공하는 웹 서버 프로그램이다.
이벤트 기반 구조(Event-Driven-Model)를 가지고 있으며, 현재 아파치 서버와 함께 웹 서버 분야에서 1, 2등을 다투고 있다.
여기서 이벤트 기반 구조는 무엇이며, 어떤 점이 좋아서 Nginx를 사용하는 것일까?
먼저 Nginx를 알기전에 Apache와 왜 비교를 하는지 또는 무엇인지에 대해서 작성할 것이다.
2. 아파치 서버 탄생 - 1995년
아파치 서버가 탄생하기 이전에, 유닉스 기반으로 만들어진 최초의 웹서버 NCSA HTTPd가 있었다. 버그가 굉장히 많고, 불안정해서 개발자들이 사용할 때마다 불편함을 겪고 있었다. 도저히 못쓰겠다고 불평하던 개발자들 중 일부가 버그를 수정하고, 구조를 변경하는 과정에서 탄생한 것이 바로 아파치 서버(Apache Http server Project)다.
최초의 아파치 서버는 클라이언트의 요청이 들어오면 커넥션을 형성하기 위해 매번 새로운 프로세스를 생성했다. 이는 Unix 계열 OS가 네트워크 커넥션을 생성하는 방법을 그대로 차용한 것이다.
그러나 프로세스를 생성하는 시간이 워낙 오버헤드가 크다보니, 요청이 들어오기 전 미리 프로세스를 생성해두는 pre-fork 방식을 채택하여 사용했다.
이러한 아파치 서버의 구조는 추가적인 개발이 쉬워 뛰어난 확장성을 갖춘다는 장점이 있다.
필요하다 싶은 기능들은 모두 모듈로 만들어 탑재할 수 있었다. 다양하고 많은 모듈이 만들어져서 빠르게 탑재되었으며, 이 덕분에 아파치 서버가 동적 컨텐츠를 핸들링 할 수도 있었다. 아파치 서버 하나만으로도 클라이언트의 요청을 받고, 응답을 만들어 처리하는 모든 과정을 소화할 수 있던 것이다. 뛰어난 확장성을 필두로 출시한지 1년도 안된 시간만에 웹 서버 업계 1위를 달성할 수 있었다.
3. C10K문제 발생 - 1999년
1999년 이후 점차 컴퓨터가 보급되고 클라이언트들의 요청이 많아지면서 서버에 동시에 몰리는 커넥션이 많아졌다. 이 때 하나의 서버에 몰린 커넥션이 너무 많아 더 이상 커넥션을 형성하지 못하는 문제가 발생했는데, 이 문제를 C10K(Connection 10,000) 라고 불렀다.
- 동시에 연결된 커넥션 수는 서버가 한 시점에 얼마나 많은 클라이언트와 커넥션을 형성하는지를 뜻한다.
- 초당 요청 처리 수는 서버가 1초에 몇 개의 요청을 처리하는가(얼마나 빠른가)를 뜻한다.
게다가 각 서비스들은 매 번 커넥션을 만드는게 비효율적이고 속도가 느리다고 판단해서, Keep-Alive 헤더를 사용해 긴 시간동안 코넥션을 유지시켰다.
이렇게 동시에 연결된 커넥션의 수가 많아지면서 서버가 더 이상 커넥션을 형성하지 못했다. C10K 문제에서 하드웨어 스펙은 이미 충분히 발전되어 있는 상태였고, 결국 아파치 서버의 커넥션 형성 구조가 문제였다.
동시에 처리하는 커넥션이 많아지면 덩달아 생성하는 프로세스가 많아졌고, 자연스레 메모리 부족 현상으로 이어졌다. 설상 가상으로 많은 프로세스가 차지하는 리소스 양도 많다. 결국 CPU 코어가 프로세스를 바꿔가며 컨텍스트 스위칭을 수행하기 바빴다.
즉, 아파치 서버가 가지고 있는 구조 자체가 수 많은 동시 커넥션을 감당하기에 부적합했다. 현재까지도 아파치 서버 개발자들은 아파치 서버가 가지고 있는 기본 구조를 유지하면서도 수 많은 동시 커넥션을 감당하기 위한 방법을 찾기 위해 노력하고 있다. 그와 별개로 완전히 다른 구조를 채택하는 경우도 존재했는데, 그 것들 중 하나가 바로 2004년에 등장 Nginx다.
4. Nginx - 2004년
초창기의 Nginx는 아파치 서버와 함께 사용하기 위한 용도로 탄생했다. 웹 서버이긴 했지만 아파치 서버를 완전히 대체할 목적은 아니었다. 대게 아파치 서버 앞단에 Nginx를 두는 것으로 동시 커넥션의 부하를 분산시켰다.
또한 Nginx는 그 자체로도 웹 서버의 역할을 수행할 수 있어서, 정적 컨텐츠를 Nginx가 반환하고 아파치 서버는 개발자가 원하는 비즈니스 로직만 처리하게 구성할 수도 있었다.
여기서 그러면 Nginx가 아파치 앞단에서 대량의 커넥션 처리는 한다는 의미인데, Nginx가 어떻게 대량의 커넥션 부하를 버틸 수 있을까?
Nginx는 기본적으로 마스터 프로세스가 nginx.conf 설정 파일을 읽고, 그 설정파일에 맞게 워커 프로세스를 생성한다. 그리고 워커 프로세스가 만들어질 때, 각자 지정된 Listen Socket을 배정 받는다. 그 소켓에 새로운 클라이언트로부터 요청이 들어오면 커넥션을 형성하고 요청과 응답을 처리한다. 커넥션을 연결하면 Keep-Alive 헤더에 따라 커넥션을 유지한다.
여기서 아파치 서버와 다른 점이 또 있는데, 커넥션이 형성된 후 워커 프로세스에서 커넥션 하나만을 한정적으로 관리하진 않는다. 관리중인 커넥션에 아무런 요청이 없을 경우 새로운 커넥션을 형성하거나 이미 만들어진 커넥션으로부터 요청을 받아 처리한다.
Nginx에서는 이런 커넥션 형성, 커넥션 제거, 그리고 새로운 요청 처리를 이벤트라는 단위로 부른다. 그리고 이 이벤트들은 OS 커널이 Queue 형식으로 각각의 워커 프로세스에게 전달해준다. 이벤트들은 큐에 담긴 상태에서 워커 프로세스가 처리해줄 때까지 비동기 방식으로 대기한다. 워커프로세스는 하나의 스레드로 이벤트를 꺼내서 처리해나간다. 이 덕분에 적은 수의 워커 프로세스가 쉬지 않고 계속해서 일을 한다는 장점이 있다.
기존 아파치 서버는 pre-forked 방식 때문에 클라이언트의 요청이 없을 경우 프로세스가 방치된다는 단점이 있었는데, Nginx는 서버 자원을 훨씬 효율적으로 활용하게 된다.
그런데 만약 만약 요청 중 하나가 시간이 오래 걸리는 작업(Disk I/O 등)이면 Nginx는 어떻게 동작할까? Nginx는 이런 상황을 방지하기 위해 시간이 오래걸리는 이벤트를 따로 수행하는 스레드 풀을 만들어둔다. 그리고 시간이 오래 걸리는 이벤트를 감지하면 해당 이벤트를 스레드 풀에 넘기고 다음 이벤트를 수행하러 간다.
이러한 워커 프로세스는 보통 CPU의 코어 수 만큼 생성한다. 즉, CPU 코어가 담당하는 프로세스를 바꾸는 횟수를 획기적으로 줄인다. 컨텍스트 스위칭으로 인한 오버헤드가 대폭 감소한다. 여기까지가 바로 Nginx가 채택한 Event-Driven-Model. 이벤트 기반 구조다.
5. 현재의 Nginx
현재에는 Nginx를 단순 웹 서버로만 사용하지 않고, 로드 밸런서, 웹 서버 가속기, SSL 터미네이션으로도 사용되고 있는 범용 웹 서버이다.
[참고]
'Web Server > Nginx' 카테고리의 다른 글
[Nginx] django에서 nginx static file serve 하기 (1) | 2022.11.16 |
---|---|
Nginx 개념 및 configuration (0) | 2022.11.12 |