Nginx의 탄생 배경 및 구조에 대해서 알아보기O
시작하려는 사이드 프로젝트에 Nginx를 적용하고자 하기 때문에 Nginx에 대한 개요를 알아보고 내부 구조를 이해하기 위함
웹 서버가 발전해온 과정을 통해 Nginx를 이해하고자 한다. Nginx가 나오기 이전에 사용되던 Apache Http Server와 비교해보고자 한다.
"Apache HTTP Server"(이 하 아파치 서버)가 세상에 최초 공개 된 해이다. 아파치 서버가 세상에 나오기 전에는 "NCSA HTTPd"가 존재하였다.
하지만 "NCSA HTTPd"는 버그가 많아 사용에 불편함이 많았다. "NCSA HTTPd"의 문제점을 개선하고 발전시켜 개발 된 것이 아파치 서버 인 것이다.
아파치 서버는 Client에게 요청이 들어오면 커넥션을 형성하기 위해 프로세스를 형성한다.
즉 새로은 요청이 들어올 때 마다 프로세스를 형성하게 된다. 이는 유닉스 계열의 OS가 네트워크 커넥션 형성을 하는 모델을 그대로 적용한 것이다.
커넥선을 형성하는 과정이 시간이 오래걸리다 보니 PREFORK
사용하였다. 미리 프로세스를 형성한 후 요청이 들어오면 만들어진 프로세스를 사용하는 것이다.
하지만 미리 만들어진 프로세스가 전부 할당되었다면 아파치 서버는 새로운 프로세스를 만들어 커넥션 형성과정을 진행하게 된다.
이러한 구조는 개발하기 쉽다는 장점을 가지고 있어 개발자가 새로운 모듈을 개발하여 추가하기 좋은 구조를 가지게 되었으며 아파치 서버가 인기를 얻을 수 있는 비결이였다.
이 시기는 인터넷 트래픽이 증가하는 추세였다. 이전에는 그 당시 기술로 감당할 수 있는 정도였지만 컴퓨터가 보급되고 요청이 많아짐에 따라 서버에 문제가 발생하기 시작하였다.
문제는 서버에 동시에 연결되어 있는 커넥션이 많아져서 일정 커넥션 수가 넘어가면 커넥션을 맺을 수 없는 문제가 발생하게 되었다. 이를 C10K(Connection 10000 Problem)
문제라고 한다.
이 당시에는 매번 커넥션을 맺는 것이 속도도 느리고 비효율적이라고 판단하여 HTTP Header
중 Keep-Alive
헤더를 이용하여 한번 연결한 커넥션을 유지하여 사용하는 것이 통상적이였다.
그렇기 때문에 Client수가 많아지고 동시에 연결되어 있는 커넥션 수가 많아지다 보니 일정 갯수의 커넥션이 형성된 이 후에 서버에서 커넥션을 형성하지 못하는 상황이 놓이게 된 것이다
그렇다면 하드웨어가 문제였을까? 아니다 하드웨어는 그 당시 엄청난 속도로 발전되고 있었으며 충분한 스팩을 가지고 있었다.
문제는 앞에서 살펴본 아파치 서버 구조를 확인해보면 어디서 문제가 발생하는지 확인할 수 있게 된다.
아파치 서버는 1995년에서 보았듯 요청이 들어오면 커넥션 형성을 위해 프로세스를 형성한다고 하였다. 그렇다 Client가 많아진 시점에 프로세스가 계속해서 생성되다 보니 서버 메모리 부족현상이 발생하게 된다.
또한 각 프로세스 간 작업을 진행하기 위해 CPU에서 Context Switching이 계속해서 발생하다 보니 CPU에도 부하가 많아진 것이
문제가 된 것이였다.
쉽게 말해 아파치 서버는 동시 커넥션을 처리하기에는 구조적으로 부적한 것이였다.
2004년에 드디어 Nginx가 나오게 된다. 초창기 Nginx는 아파치 서버를 대체하기 위해 나온것이 아니라 아파치 서버를 보완하기 위해 나온 것 이였다.
초창기 사용은 아래와 같은 구조를 띄고 있었다.
이렇게 되면 이전 아파치 서버가 감당해야 했던 동시 커넥션을 Nginx가 감당하면서 아파치 서버의 부하를 크게 줄일 수 있다.
Nginx는 또한 웹서버의 역할을 감당할 수 있기 때문에 정적 파일들을 Client에게 서빙이 가능했다.
Nginx에는 기본적으로 2가지의 프로세스가 존재한다.
Master Process가 하는 역할은 간단하다. Config
파일을 읽어 Worker Process를 생성하거나 Update하는 일을 한다.
사용자에 요청에 맞게 커넥션을 맺고 관리하며 요청을 처리하는 프로세스가 Worker Process가 하는 역할이다.
Worker Process는 생성될 때 각자 지정된 Listen 소켓을 지정받게 된다. 지정 받은 소켓에서는 Client에게 요청이 들어오면 커넥션을 형성하고 요청을 처리한며 Keep-Alive
만큼 커넥션을 유치하고 만료 된 커넥션의 연결을 끊는다.
아파치 서버는 하나의 요청에는 하나의 프로세스가 담당하였었는데 Nginx는 하나의 Worker Process가 하나의 요청만 담당하지 않는다.
이미 연결되어 있는 커넥션에서 요청이 없다면 새로운 요청에 대한 커넥션을 형성하거나 이미 연결되어 있는 커넥션으로 부터 들어오는 요청을 처리하게 된다.
NginX에서는 커넥션에 대한 관리, 요청 처리와 같은 작업을 이벤트라고 정의하며 OS 커넉에서 Queue
형태로 Worker Process에게 전달되며 처리 될 때 까지 비동기 방식으로 대기하게 된다.
Worker Process는 Queue
에서 이벤트를 하나의 쓰래드로 이벤트를 꺼내어 처리해 나간다.
이러 한 방식을 채택함으로 Worker Process가 쉬지 않고 일한다는 장점을 가지게 될 수 있다.
만약 이벤트 중 시간이 오래걸리는 이벤트가 들어오게 된다면 또한 그 작업을 다른 이벤트들과 동일하게 처리한다면 Queue
의 특성 상 FIFO
구조이다 보니 시간이 오래 걸리는 구조의 작업이 끝날 때 까지 블로킹 될 것이다.
Nginx는 위와 같이 시간이 오래걸리는 이벤트같은 경우 별도의 쓰래드 풀을 형성하여 위임하게 된다.
Worker Process는 통상적으로 CPU의 코어만큼 생성을 하게되는데 이를 통해 얻을 수 있는 장점으로는 아파치 서버에서 빈번하게 일어나던 Context Switching
을 줄여 CPU의 부하 또한 줄일 수 있게 되어 성능을 높일 수 있다.
이 구조가 바로 Nginx가 채택한 Event Driven Model
이다.
Nginx의 이러한 구조는 단점도 존재한다. 만약 기능을 추가하기 위해 Nginx를 종료하게 된다면? Worker Process가 관리하던 커넥션 및 요청을 처리하지 못하게 된다.
스마트 폰이 보급되면서 인터넷의 사용이 더 증가되게 되었다. 즉 동시 커넥션을 더 많이 맺게 되는 계기가 되었으며 브라우저에서도 빠르게 리소스를 가져오기 위해 여러 커넥션을 동시에 맺게 되는 상황들이 벌어지고 있었다.
결국 동시 커넥션을 처리해야하는 서버들이 증가하였고 회사들은 Nginx에 눈을 돌리게 되게 되면서 Nginx가 인터넷 트래픽에 관여하는 비중이 증가하면서 지금과 같은 자리를 차지하게 되었다.
아파치 서버와 Nginx는 대립관계 보다는 각자 탄생하게 된 목적이 달랐던 것이다.
아파치 서버가 주로 사용되던 환경에서는 이전 "NCSA HTTPd"를 사용할 때 경험했던 버그와 불완전한 서버 운영을 보완하기 위해 안정성과 확장성을 중요시 하게 되던 시기였고
Nginx가 주로 사용되고 있는 환경에서는 동시 커넥션 및 성능과 같은 것들을 신경써야 하는 시기이기 때문이라 생각한다. (밴치마킹 결과 Nginx가 더 좋은건 안 비밀)
실무에서 Nginx를 직접 만저볼 경험이 없던지라 사이드 프로젝트에서 적용해보고 싶은 마음에 Nginx의 탄생 과정 및 개요, 내부구조에 대한 자료를 찾던 중 우테코 - Nginx를 만날 수 있어서 참 감사했다. 15분의 영상이지만 Nginx에 대한 이해도를 높이기에는 충분했던 것 같다.
무엇보다 기술을 선택할 때 해당 기술에 대한 배경을 알고있으면 어떠한 상황에서 적용하면 좋겠구나 라는 판단이 조금 더 명확해진다는 확신을 얻었다.
TODO: 프로젝트에 적용할 때 Nginx를 사용한 사용법들을 정리해보기.