웹 서버는 크게 하드웨어와 소프트웨어로 나뉘어저 설명되고는 합니다. 웹서버는 크롬이나 사파리와 같은 브라우저로부터 HTTP 요청을 받아들이고, HTML, CSS, Javascript 와 같은 리소스들을 요청에 따라 HTTP 응답을 해주는 프로그램/하드웨어라고 생각할 수 있습니다.
웹 서버는 리소스 제공뿐만아니라 클라이언트로부터 오는 HTTP 요청을 받아들이고 적절한 응답을 생성하여 클라이언트에게 전송합니다. 또한 동적 웹 애플리케이션(WAS)을 호스팅하여 서비스를 제공하고, 해커들의 공격을 방지하여 서비스의 안정성을 유지하고, 로그를 기록하여 서버의 상태와 성능을 모니터링하는 보안, 모니터링 기능을 제공합니다.
아파치 서버는 요청이 들어오면 커넥션을 생성하기 위해 프로새스를 생성합니다. 따라서 새로운 클라이언트 요청이 들어올때마다 프로세스를 생성하여 요청을 처리합니다. 이는 유닉스 계열의 운영체제가 네트워크 커넥션을 형성하는 모델을 그대로 가져와 적용한 것입니다. 그러나 프로세스를 생성하는 것은 시간과 많은 컴퓨터 리소스를 요구하다보니, 미리 프로세스를 생성하여 사용하는 프리포크 방식을 사용하였습니다. 따라서 미리 만들어놓은 프로세스에 요청이 들어오면 만들어놓은 프로세스를 생성하고, 프로세스가 부족할 경우에 프로세스를 새로 생성하여 요청을 처리했습니다.
이러한 방식은 개발하기 쉽다는 장점이 있었기 때문에, 개발자는 새로운 모듈이나 기능을 만들어 적용할 수 있었습니다. 이러한 확장성이 좋다는 장점 덕분에 요청을 받고 응답을 처리하는 과정을 하나의 서버에서 처리할 수 있었습니다. 이러한 장점이 아파치 서버가 인기가 많았던 이유였습니다.
하지만 점차 컴퓨터 보급의 증가에 따라 서버에 동시에 연결된 커넥션이 많을때, 더이상 커넥션을 생성할 수 없는 문제가 발생하였습니다. 이러한 문제가 C10K 문제였습니다. 커넥션이 생성되고 요청들을 처리하기 위해 커넥션을 재활용하기 위해 커넥션을 종료하지 않고 커넥션을 유지하게 되었는데, 이러한 점때문에 서버에 연결된 커넥션이 많아지게 되며 위와 같은 문제가 발생하였습니다.
아파치 서버는 프로세스를 생성하고 할당하는 구조로 인해 동시에 커넥션이 많이 연결될수록 많은 프로세스가 생성되어야만 했습니다. 또한 이러한 각 커넥션의 요청을 처리하기 위해 CPU는 컨텍스트 스위칭을 빈번하게 하여 요청을 처리해야만 했습니다. 또한 프로세스의 개수가 커넥션의 개수와 비례하여 증가하였고, 메모리가 부족해지는 경우가 발생했습니다. 이러한 구조는 수많은 커넥션의 요청들을 처리하는데는 성능적인 이슈를 야기했습니다.
이러한 문제를 해결하기 위한 방법으로 Nginx가 등장하였습니다. 프로세스를 미리 생성하여 사용하는 방식대신, 마스터 프로세스와 워커 프로세스로 이루어진 구조로 동작합니다. 워커 프로세스는 클라이언트로부터 요청이 들어오게되면 커넥션 생성, 요청처리, 커넥션 해제를 하며 요청을 처리하빈다. 다만, 하나의 커넥션을 처리하는 Apache와는 달리, keep-alive 중인 커넥션의 요청이 없으면 다른 요청을 처리하기도 합니다. 이처럼 커넥션 생성, 요청처리, 커넥션 해제와 같은 것을 이벤트라고하고, 이러한 이벤트들은 커널의 Queue에 저장되어 순차적으로 처리되며, Queue에서 대기중인 이벤트들은 비동기 방식으로 대기하게 됩니다.
이러한 방식은 아파치 서버와 비교하게되면 여러 장점이 있습니다. 첫번째로는 각 커넥션의 생성,요청,제거를 수행하기 위해 CPU는 컨텍스트 스위칭을 할 필요가 없습니다. 컨텍스트 스위칭은 그 자체로 컴퓨터 리소스 자원을 필요로 하므로 서버의 부하를 줄일 수 있습니다. 두번째로는 메모리 자원을 절약할 수 있습니다. 수많은 커넥션의 요청을 처리하기 위해 해당 커넥션 수 만큼의 프로세스를 생성해야하는 방식과는 달리 하나의 워커 프로세스가 여러 커넥션의 이벤트를 처리하므로 더 적은 수의 프로세스로 수많은 커넥션의 이벤트를 처리할 수 있습니다. 프로세스의 수가 적다는 것은 프로세스가 차지하는 메모리의 크기를 줄일 수 있습니다.