우아한테크코스 피케이의 테코톡을 참고해서 정리한 내용이다!
🌲 Nginx란 무엇인가요?
리버스 프록시, 로드밸런스, http 캐시 등 다양한 기술을 제공하는 웹 서버 프로그램이다. 이벤트 기반 구조(Event-Driven-Model)를 가지고 있으며, 현재 아파치 서버와 함께 웹 서버 분야에서 1,2 등을 다투고 있다. 도대체 이벤트 기반 구조란 무엇이며, 어떤 점이 좋아서 Nginx를 사용하는 것일까? 또, 아파치 서버와는 어떤 차이점을 가지고 있을까?
이것들을 알아보기 전에, 우선 아파치 서버가 탄생한 1995년으로 돌아가보자.
🌲 1995년 - 아파치 서버의 탄생
아파치 서버가 탄생하기 이전에, 유닉스 기반으로 만들어진 최초의 웹서버 NCSA HTTPd가 있었다. 버그가 굉장히 많고, 불안정해서 개발자들이 사용할 때마다 불편함을 겪고 있었다. 도저히 못쓰겠다고 불평하던 개발자들 중 일부가 버그를 수정하고, 구조를 변경하는 과정에서 탄생한 것이 바로 아파치 서버(Apache Http server Project)다.
정식 명칭은 APache Http Server다. 이 글에서는 편의를 위해 ‘아파치 서버’라고 부른다.
최초의 아파치 서버는 클라이언트의 요청이 들어오면 커넥션을 형성하기 위해 매번 새로운 프로세스를 생성했다. 이는 Unix 계열 OS가 네트워크 커넥션을 생성하는 방법을 그대로 차용한 것이다. 그러나 프로세스를 생성하는 시간이 워낙 오버헤드가 크다보니, 요청이 들어오기 전 미리 프로세스를 생성해두는 pre-fork 방식을 채택하여 사용했다.
아파치 서버의 이런 구조는 추가적인 개발이 쉬워 뛰어난 확장성을 갖춘다는 장점이 있었다. 필요하다 싶은 기능들은 모두 모듈로 만들어 탑재할 수 있었다. 다양하고 많은 모듈이 만들어져서 빠르게 탑재되었으며, 이 덕분에 아파치 서버가 동적 컨텐츠를 핸들링 할 수도 있었다. 아파치 서버 하나만으로도 클라이언트의 요청을 받고, 응답을 만들어 처리하는 모든 과정을 소화할 수 있던 것이다. 뛰어난 확장성을 필두로 출시한지 1년도 안된 시간만에 웹 서버 업계 1위를 달성할 수 있었다.
그러나 1999년이 되자 문제가 발생하기 시작했다.
🌲 1999년 - C10K 문제 발생
1999년 이후 점차 컴퓨터가 보급되고 클라이언트들의 요청이 많아지면서 서버에 동시에 몰리는 커넥션이 많아졌다. 이 때 하나의 서버에 몰린 커넥션이 너무 많아 더 이상 커넥션을 형성하지 못하는 문제가 발생했는데, 이 문제를 C10K(Connection 10,000) 라고 불렀다.
혹시나 동시에 연결된 커넥션 수와 초당 요청 처리 수를 헷갈려선 안된다.
동시에 연결된 커넥션 수는 서버가 한 시점에 얼마나 많은 클라이언트와 커넥션을 형성 하는지를 뜻하며
초당 요청 처리 수는 서버가 1초에 몇 개의 요청을 처리하는가(얼마나 빠른가) 를 뜻한다.
한 클라이언트는 하나의 커넥션을 이용해서 여러 번의 요청을 보낼 수 있다. 커넥션은 오랜 시간동안 유지될 수 있다. 따라서 동시에 연결된 커넥션 수와 초당 요청 처리수는 엄연히 다르다.
게다가 각 서비스들은 매 번 커넥션을 만드는게 비효율적이고 속도도 느리다고 판단해서,
Keep-Alive
헤더를 사용해 긴 시간 동안 커넥션을 유지시켰다.
이렇게 동시에 연결된 커넥션의 수가 많아 지면서 서버가 더 이상 커넥션을 형성하지 못했다.
C10K 문제에서 하드웨어 스펙은 이미 충분히 발전되어 있는 상태였고, 결국 아파치 서버의 커넥션 형성 구조가 문제였다.
(아파치 서버 외 대다수 서버 프로그램도 마찬가지)
동시에 처리하는 커넥션이 많아지면 덩달아 생성하는 프로세스가 많아졌고, 자연스레 메모리 부족 현상으로 이어졌다.
설상 가상으로 많은 프로세스가 차지하는 리소스 양도 많다.
결국 CPU 코어가 프로세스를 바꿔가며 컨텍스트 스위칭을 수행하기 바빴다.
즉, 아파치 서버가 가지고 있는 구조 자체가 수 많은 동시 커넥션을 감당하기에 부적합했다. 현재까지도 아파치 서버 개발자들은 아파치 서버가 가지고 있는 기본 구조를 유지하면서도 수 많은 동시 커넥션을 감당하기 위한 방법을 찾기 위해 노력하고 있다. 그와 별개로 완전히 다른 구조를 채택하는 경우도 존재했는데, 그 것들 중 하나가 바로 2004년에 등장 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. 이벤트 기반 구조다.
물론 Nginx에도 단점은 존재한다. 개발자가 기능 추가를 시도 했다가 돌아가고 있는 워커 프로세스를 종료하는 상황이 생길 수도 있음. 그럼 워커 프로세스가 관리 중이던 이벤트(커넥션과 요청)들을 더 이상 처리 할 수 없게 된다. 그래서 NGINX는 개발자가 직접 모듈을 만들기 까다롭다.
그럼에도 장점이 워~낙 경력해서 현재 서비스 시장에서 많이 사용된다.
게다가 적은 워커 프로세스로 동작하는 Nginx의 구조는 Nginx 자체의 설정을 동적으로 변경하는 것을 가능하게 한다.
개발자가 nginx.conf
설정 파일을 변경하고 Nginx에 해당 설정을 적용하면
마스터 프로세스는 그 설정에 맞는 워커 프로세스를 새로 생성한다.
그리고 기존 워커 프로세스가 더 이상 커넥션을 형성하지 않도록 하고,
기존 워커 프로세스가 커넥션의 모든 이벤트를 마무리하면 종료하도록 한다.
이후 새롭게 생성된 워커 프로세스들이 커넥션을 형성하고 뒤 따라 들어온
요청들을 수행하게 된다.
Nginx의 특성 덕분에 동시 커넥션을 유지하며 기존 요청을 계속 처리함과 동시에 뒷단에 새로운 서버를 추가할 수 있다. 즉 로드밸런서의 역할을 수행할 수 있다.
이렇게나 대단한 Nginx도 2007년까지는 웹 서버 순위권에 존재하지 않았다. 여전히 아파치가 압도적인 1등을 유지하고 있었다. 그런데 2008년부터 점점 지표가 움직이기 시작했다. 아파치가 점점 하락세를 보이고, Nginx가 급부상하기 시작했다.
도대체 2008년에 무슨 일이 있었던걸까?
🌲 2008년 - 스마트 폰의 보급
바로 스마트폰의 보급이 시작된 것이다. 스마트 폰의 보급으로 인터넷 사용이 폭발적으로 늘면서 유튜브와 같은 영상 스트리밍 서비스, SNS 등 수 많은 동시 커넥션과 방대해진 리소스 요청을 감당할 수 있는 웹 서버가 필요했다. 모든 요청마다 프로세스를 생성하던 아파치 서버에서 Nginx라는 대체제로 눈을 돌릴 수 밖에 없었다.
덕분에 현재 Nginx가 인터넷 트래픽에 관여하는 비중은 계속 높아지고 있다.
물론 아파치 진영도 마냥 손을 놓고 있진 않았다. 기존 pre-forked 방식과 워커 방식을 기호에 따라 선택할 수 있는 모듈도 만들어냈다. (“워커 어디서 많이 봤죵? ㅎㅎ”)
그럼에도 메모리 사용률과 초당 요청 처리수에서 압도적인 성능차이를 보였다. 결국 아파치 서버가 Nginx를 대체할 수 없는 수준이 된 것이다. 그렇다고 해서 아파치 서버가 무너진 것은 아니다. 여전히 Nginx와 1,2위를 다투고 있다.
아파치 서버는 NCSA Httpd의 불편함과 버그를 고치면서 탄생했기 때문에, 기본적으로 호환성과 확장성이 뛰어나다는 장점이 있다. 대표적인 사례로 Nginx는 윈도우에서 제대로된 성능을 발휘하지 못한다는 이슈가 있지만, 아파치 서버는 이미 아주 오래전에 OS 호환성 관련 문제를 해결했다.
또한 모듈을 추가해서 기능을 확장하기 쉽다는 장점 덕분에, 아파치 서버로 웹 서버를 사용중인 서비스에 언제든지 모듈을 등록해서 새로운 기능을 추가할 수도 있다. 이러한 모듈의 종류도 당연히 아파치 서버가 훨씬 많다.
🌲 2021 - 우리는 Nginx를 어떻게 사용해야 하는가?
Nginx는 비단 웹 서버 뿐만 아니라 로드 밸런서, 웹 서버 가속기, SSL 터미네이션으로도 쓰인다.
SSL 터미네이션
클라이언트와는 https, 서버와는 http 통신. 이 구조를 통해 뒷단의 서버가 복호화 과정을 감당하지 않고 비즈니스 처리에 더 집중할 수 있도록 한다. 보통 뒷단 서버와 Nginx를 같은 서브넷에 속하게 해서 보안이슈를 해결한다.
또, HTTP 프로토콜을 사용해서 전달되는 컨텐츠를 캐싱할 수도 있다. (캐싱을 하는 경우에는 NGINX를 클라이언트 쪽에 가깝게 구성한다.) 이 외에도 HSTS, CORS처리, TCP/UDP 커넥션 부하 분산, HTTP/2 등… 정말 많은 일을 한다.
수 많은 기능들을 하나씩 사용해보면서 본인에게 필요한 것이 무엇인지 알아보면 좋겠다. 또, 현업에서 일하는 개발자들이 모은 Nginx 설정 템플릿도 존재한다. 굉장히 유용하니 요것도 사용해보자~
댓글남기기