개발 일기

[NGINX] NGINX의 탄생 배경 (Apache HTTP Server의 문제점) 본문

DevOps/기타

[NGINX] NGINX의 탄생 배경 (Apache HTTP Server의 문제점)

개발 일기장 주인 2024. 5. 20. 14:52

공부하게 된 계기

나의 블로그 초반 게시글 중 하나인 [WEB] 웹 어플리케이션 서버(WAS)와 웹 서버 비교에서 다뤘던 웹서버에 대해서 알아볼 예정이다. 한창 Docker, CI/CD 등 배포 쪽을 공부하면서 다른 사람들의 인프라 아키텍처 같은 것을 보면서 어떤 방법으로 어떤 구조로 배포를 했는지 보면 많은 사람들이 NGINX를 웹서버로 둔 것을 확인할 수 있었다. 사실 내 배포를 보면 그냥 프론트 따로 백 따로 왜 NGINX가 필요한지 지금도 충분히 잘 돌아가서 잘 모르겠지만 많은 사람들이 쓰는 데에는 이유가 있지 않을 까 싶어서 공부해보고 내 EC2에도 설치하여 적용해보기로 했다.

 

NGINX전에는?

NGINX도 웹 서버이지만 그만큼 또 유명한 웹서버로는 흔히들 아는 Apache HTTP Server가 있다. Apache HTTP Server는 1995년, NGINX가 2002년에 개발됐는데 NGINX가 왜 탄생하게 됐는지에 대해서 이해하기 위해 Apache HTTP Server에 대해 먼저 알아보자.

Apache HTTP Server

1995년 그 당시에 AHS가 세상에 나오기 전 유닉스 기반의 NCSA HTTPd라는 최초의 웹서버가 있었는데 버그가 너무 많아 개발자들이 이러한 버그 문제들을 해결하고 기능을 추가하여 탄생하게 된 것이 AHS이다. 

Apache HTTP Server는 요청을 어떻게 처리하는지 보자.

  • AHS는 요청이 들어오게 되면 Connection을 형성하기 위해 Process를 생성한다.
    그래서 새로운 클라이언트로부터 요청이 들어올때마다 프로세스를 생성하게 된다. 
  • 그러나 프로세스를 생성하는 것 자체가 시간이 오래걸리기 때문에 매번 요청이 들어올때 마다 Process를 생성하게 되면 시간이 많이 걸린다. 그래서 Prefork 방식을 사용하여 미리 프로세스를 만들어 놓고 새로운 클라이언트로 부터 요청이 들어오게 되면 미리 만들어둔 프로세스를 할당했다.
  • 이렇게 미리 만들어둔 프로세스를 가져다 할당하다가 또 미리 만들어둔 프로세스가 모두 소진되면 다시 새로운 프로세스를 만드는 방식이였다.

AHS가 나온 시점에서는 트래픽이 이러한 구조로 처리하기에 거뜬하여 인기를 얻을 수 있었다.

그러나 시간이 흐르고 1999년 문제가 생겼다. 바로 C10K(Connection 10000 Problem)이라는 문제가 발생한 것이다. 

컴퓨터의 보급으로 트래픽의 증가로 자연스레 요청이 증가하였고 서버에 동시에 연결된 커넥션의 수가 많아졌을 때 더이상 커넥션이 형성되지 않는 문제였다.

 

‼️ 이때, 동시에 연결된 커넥션 수와 초당 요청 처리수 이 둘은 다르다. 후자는 서버가 얼마나 빠르게 요청을 처리할 수 있는지 나타낸 지표이다. 그러나 전자는 한 시점에 서버가 얼마나 많은 클라이언트와 커넥션을 형성하고 있냐 이다.
한 클라이언트는 하나의 커넥션을 통해 여러 요청을 보낼 수 있고 커넥션은 긴 시간동안 유지될 수 있다. 이때 TCP 3-way handshake라는 커넥션 형성 과정을 거치는데 매 요청마다 이러한 커넥션 형성 과정이 발생한다면 상당히 비효율적이다. 그렇기때문에 우리가 흔히 아는 HTTP Header의 keep-alive 헤더를 통해 커넥션을 유지하고 요청을 보낼때 현재 유지되고 있는 커넥션이 있다면 이것을 재활용하게 된것이다.

 

 

 

 

 

위와 같은 배경으로 커넥션의 수가 10,000단위로 넘어가게 됐을 때 더이상 커넥션이 형성되지 않았는데 이 당시에 하드웨어 적인 문제는 없었다. 그냥 AHS의 구조 문제였다.

  • 매 클라이언트와의 커넥션이 형성될때마다 프로세스가 할당되는데 그렇기 때문에 커넥션의 증가는 메모리 부족으로 이어졌다.
  • AHS는 확장성이 좋다는 장점이 있었는데 여러 기능이 추가되다 보니 프로세스가 차지하는 리소스 양이 증가했다. (무거운 프로그램)
  • 여러 커넥션으로부터 요청이 들어오다보니 CPU 코어가 계속해서 Context Switching을 통해 프로세스를 바꿔가며 작업을 해야했고 이는 CPU의 부하를 증가시켰다.

이러한 이유들로 어찌보면 Apache HTTP Server은 대규모 트래픽을 처리하기에는 너무 적합하지 않았던 것이다.

 

Apache HTTP Server + NGINX

그래서 나중에 개발자들이 이러한 문제들을 해결하기위해 NGINX를 도입했고 처음에는 이 AHS와 함께 사용하여 보완하기 위한 장치였다.

AHS의 앞단에 NGINX를 두어 수많은 커넥션을 AHS가 들고있는 것이아니라 NGINX가 감당하는 것이였다.

정적 파일에 대한 요청은 웹서버인 NGINX가 처리하고 동적 파일에 대한 요청을 받았을 때만 뒷단의 서버와 커넥션을 형성하여 요청을 처리했다.

어떻게 NGINX는 많은 커넥션을 견딜까?

이때 Apache HTTP Server은 하지 못하는 것을 NGINX는 어떻게 극복하여 여러 커넥션들을 견딜 수 있는지 궁금해졌다.

NGINX의 구조를 보면 Master Process를 두고 이 프로세스가 설정 파일을 통해 Worker Process를 생성하는 역할을 하며 이 Worker 프로세스가 실제로 작업을 처리하는 역할을 한다.

워커 프로세스는 각각 Listen Socket이라는 것을 할당 받고 그 소켓으로 새 클라이언트로 부터 요청이 들어오게되면 커넥션을 형성하고 들어오는 요청을 처리하게 된다. 이때도 마찬가지로 keep-alive를 통해 커넥션을 유지하고 있는데 AHS와의 차이점이 지금부터 나오게 된다.

 

메모리 부족 해결

바로 하나의 프로세스가 하나의 커넥션만 한정적으로 유지하지 않는다는 것이다. 형성된 커넥션에 아무런 요청이 없으면 새로운 커넥션을 새로 형성하거나 이미 형성됐던 다른 커넥션의 요청을 처리하기도 한다. NGINX에서는 커넥션 형성, 커넥션 제거, 요청 처리한는 것들을 이벤트라고 부르며 이 이벤트들은 OS Kernal이 큐 형식으로 워커 프로세스에게 전달하고 이 이벤트는 비동기 방식으로 대기하고 처리된다. 

덕분에 Worker Process는 쉬지 않고 일을 할 수 있게 되고 AHS와는 다르게 낭비되는 리소스 또한 확실히 줄일 수 있다.

만약 요청 중 이벤트 큐의 이벤트 하나가 Disk I/O와 같이 오래걸리는 작업이라면? 
그 뒤에 있는 이벤트들이 오랜시간 블로킹될 수 있기 때문에 이를 방지하기 위해 이런 작업들을 처리할 수 있는 Thread Pool을 따로 만들어 놓고 시간이 오래걸릴 것 같은 이벤트들은 이 Thread Pool에게 위임하고 큐의 다른 이벤트를 처리한다.

CPU 부하 증가 문제 해결

Worker Process는 CPU의 코어 갯수만큼 생성하며 AHS보다 Context Switching 해야하는 프로세스의 수가 크게 감소하기 때문에 CPU의 부하 또한 감소시킬 수 있다.

 

당연히 단점 또한 있다. 개발자가 기능추가를 했다가 돌아가는 작업중인 Worker Process를 종료시킬 수도 있다. 즉, 기존보다 확장성은 안좋아진 것이다.

 

그러나 장점이 너무 명확했다.

  • 동시 커넥션 양 최소 10배 증가(일반적으로 100~1000배 증가)
  • 동일한 커넥션 수일 때 속도 2배 향상
  • 동적 설정 변경
    예를 들어 NGINX 뒤에 새로운 서버를 추가할때 NGINX를 종료하고 설정을 수정한 뒤 재 시작해야한다면 그닥 좋은 상황은 아니지만 동적 설정 변경이 가능하여 커넥션을 유지한 채로 뒷단에 서버를 추가할 수 있다.

시장 흐름

그러나 NGINX는 출시되고 위와 같이 좋은 웹 서버임이 분명함에도 불구하고 순위에 없었고 2008년부터 치고올라오게 된다.

바로 2007년에 아이폰과 같은 스마트 폰이 세상에 나왔고 이는 동시 커넥션 형성이 증가하게된 계기가 됐다. 클라이언트에서는 리소스를 빠르게 가져오기 위해 keep-alive를 통해 수많은 커넥션 형성이 이뤄졌고 회사들은 AHS로 감당할 수 없게 되자 NGINX를 쓰기 시작한 것이다. 이러한 시대 흐름에 따라 Apache도 MPMs(Multi Processing Module)라는 모듈을 통해 성능향상을 위한 모드를 제공했다.

Apache HTTP Server, NGINX 동시 커넥션 수에 따른 Memory 사용량 지표

그러나 왼쪽 그래프에서 볼 수 있듯이 동시 커넥션의 수가 많아지게 되면 Apache는 확연히 큰 메모리 사용량을 보여주고 있는 것을 볼 수 있고 NGINX는 그에 반해 동시 커넥션이 증가하더라도 메모리 사용량이 크게 변하지 않아 확실히 대용량 트래픽 처리에 효과적임을 보이고 있다.

또한 오른쪽 그래프에서 보이 듯이 초당 요청 처리 횟수 또한 동시 커넥션이 적던 크던 NGINX가 Apache를 압살하는 것을 볼 수 있다.

 

왜 아직도...?

2023-10-25 웹서버 시장 점유율

위 내용들을 보면 Nginx를 안 쓸 이유가 없다. 무조건 Nginx만 써야될 것 같다는 생각이 들었다. 그러나 2023년 10월 25일 기준 웹 서버 점유율을 봤더니 Nginx가 높긴 하지만 성능적인 것들을 봤을때 점유율이 그렇게 압도적이지 않다는 생각이 들었다. 34.25와 30.9%, 큰 차이가 없는 것 같았다. 여기에도 당연히 이유가 있었다.

바로 Apache HTTP Server은 버그 수정의 계기로 시작했기에 다양한 OS에서 안정적이였다. 바로 호환성에 강세를 띄는 것이였다. 그에 반해 NGINX는 윈도우 환경에서 제대로 성능을 발휘하지 못한다고 한다.

또한 Apache HTTP Server은 확장성이 좋은 것이 장점이였는데 모듈 또한 다양하고 모듈을 덧 붙히기 좋았다.

  • 웹 서버
  • 로드 밸런서
  • 웹 서버 가속기
    • SSL 터미네이션
      : 클라이언트와 https, 서버와 http 통신을 하여 서버가 복호화 과정을 담당하지 않아도 됨(부하 감소)
    • 캐싱
  • HSTS
  • CORS 처리
  • TCP/UDP 커넥션 부하 분산
  • HTTP/2

등의 기능들을 지원한다. 

 

결론

내가 딱히 윈도우 환경에 배포할 일은 없을 것 같다. 맥북을 쓰고 실제 배포 환경 또한 우분투 OS 등 리눅스 기반 운영체제를 거의 사용하기 때문이다. 따라서 NGINX의 호환성 문제는 내게 해당되지 않기 때문에 저러한 성능을 최대한 살리기 위해 NGINX를 택하는 것이 좋을 것 같다.

 

그래서 다음 블로그에서는 NGINX의 저러한 기능들에 대해 이해해봐야겠다.

 

멀티 플렉싱, epoll

 

참고 : 피케이의 Nginx

 

'DevOps > 기타' 카테고리의 다른 글

[NGINX] Forward/Reverse Proxy Server & Load Balancing  (0) 2024.05.20