웹 서버는 HTTP를 통해 '웹 클라이언트'가 '요청한 데이터'를 제공(serve)해주는 즉, 요청에 응답하는 프로그램이다.
개발자는 서버에 Apache나 Nginx 같은 웹 서버 프로그램을 이용하여 사용자의 HTTP 요청에 응답하게 된다.
위키피디아
웹 서버(web server)는 다음의 두 가지 뜻 가운데 하나이다.
- 웹 서버: 웹 브라우저와 같은 클라이언트로부터 HTTP 요청을 받아들이고, HTML 문서와 같은 웹 페이지를 반환하는 컴퓨터 프로그램 (ex. Apache, NGINX)
- 웹 서버(하드웨어): 위에 언급한 기능을 제공하는 컴퓨터 프로그램을 실행하는 컴퓨터 (ex. EC2, 내 컴퓨터)
Apache
아파치 HTTP 서버(Apache HTTP Server)는 아파치 소프트웨어 재단에서 관리하는 무료 HTTP 웹 서버 소프트웨어다. 리눅스 등 유닉스 계열 뿐 아니라 마이크로소프트 윈도우에서도 무료로 운용할 수 있다. 프로그램의 이름은 httpd이다. 기본적인 작동 방법은 사용자의 HTTP 요청이 올 때마다 프로세스나 스레드를 새로 만든다.
- 100개의 HTTP 요청 = 100개의 프로세스 생성
따라서 사용자 동시 접속이 늘어날수록 프로세스마다 메모리를 할당하기 때문에 메모리가 부족해지고, 하나의 CPU가 여러 프로세스를 고속으로 번갈아 실행해야 하므로 CPU 부하가 높아지는 문제가 있다.
인터넷 사용이 급격히 증가하면서 서버가 동시에 처리해야 하는 소켓 수도 증가하게 되었는데, 동시에 연결된 커넥션이 1만 개가 넘어가면 하드웨어 성능이 좋아도 서버는 더 이상 커넥션을 형성하지 못하는 이른바 C10K 문제가 발생한다. 커넥션 1만 개 문제(The C10k Problem)는 1999년 단 케겔이 처음 제시한 이야기로, ‘1만 개의 클라이언트를 동시에 처리할 수 있는 네트워크 I/O 모델 설계 방법’에 대한 문제이다. 여기서 동시 처리의 기준이 되는 시간은 정확히 명시하지 않았지만, 보통은 1초를 뜻한다.
만약 Apache 방식으로 10000개의 연결을 하기 위해서는 10000개의 소켓이 필요하고 소켓을 여는 10000개의 쓰레드가 필요하다. 스레드당 1MB의 메모리가 필요하다고 하면 10000MB = 10GB가 필요하다. (보통 PC가 4G RAM, 8G RAM) 또한 코어가 4개 있다고 해도 2500개의 스레드가 코어마다 걸릴거고 컨텍스트 스위칭 오버헤드가 2500회 발생할 것이다. 즉 C10K Problem의 메모리 공간 복잡도가 연결수 N에 따라 선형으로 증가하는 O(N)의 구조이다.
Apache 구동 방식
1. Prefork MPM(multi Processing Module) 방식
- HTTP 연결 요청이 올 때마다 프로세스를 매번 복제하여 프로세스에서 싱글 스레드로 해당 HTTP 요청을 처리
- 사용자 요청 수 = 프로세스 수
2. Worker MPM 방식(멀티 스레드)
- 한 개의 프로세스가 여러 스레드를 생성하여 해당 HTTP 요청을 처리하므로 Prefork 방식보다 메모리 소모가 적다. 하지만 멀티 스레드 방식은 CPU 스케줄링을 위한 처리 시간과 문맥 전환 비용이 발생하는 단점이 있다. 스레드를 분배하기 위해 사용되는 CPU 스케줄링에 대한 연산 작업과 쓰레드 간의 전환을 위해 전환하기 직전의 쓰레드를 나중에 복귀시킬 때를 대비하여 상태를 저장해두기 위한 CPU 연산 작업으로 인해 스레드가 많아질수록 성능 저하가 발생한다.
Nginx(비동기 이벤트 기반)
Nginx는 웹 서버 소프트웨어로, Apache의 C10K 문제를 해결하고자 등장하였다. Nginx는 요청에 응답하기 위해 비동기 이벤트 기반 구조를 가진다. 이것은 아파치 HTTP 서버의 스레드/프로세스 기반 구조를 가지는 것과는 대조적이다.
Nginx의 비동기 이벤트란 HTTP 요청이 수 천 개여도 정해진 수의 프로세스(worker procss)가 이 요청들을 이벤트로 등록하고 비동기 방식으로 대기시켜 완료되는 요청(이벤트)부터 응답(처리)해주는 것을 말한다.
NGINX 공식 블로그에서는 NGINX의 worker process를 체스 그랜드마스터에 비유한다. 새로운 게임 참가자가 올 때마다, 새로운 체스 선수를 매칭시켜주는 것이 아니라, 한 명의 체스 그랜드마스터가 돌아가면서 360명의 게임 참가자와 동시에 게임을 두는 것이다.
NGINX 내부 구조
NGINX는 1개의 master process와 3종류의 자식 프로세스로 구성되어 있다.
- master process(Worker Process를 관리)
- 특권 명령을 수행하고, Single Thread로 구성된 다수의 Worker Process 관리
- listen socket에 port를 바인딩
- NGINX 설정 읽기
- 아래 3가지 타입의 자식 프로세스 생성하고 관리
- 특권 명령을 수행하고, Single Thread로 구성된 다수의 Worker Process 관리
- child processes
- cache loader
- 디스크 기반 캐시를 메모리에 불러오고, 종료
- NGINX 시작 시 실행된다.
- 보수적으로 스케줄링되므로, 이 프로세스의 리소스 요구사항은 낮음
- cache manager
- 주기적으로 실행된다.
- 디스크 캐시를 설정된 사이즈로 유지하기 위해, 캐시 엔트리를 정리
- worker process(Single Thread)
- listen 소켓을 배정받는다.
- listen 소켓으로부터 클라이언트의 요청이 들어오면 커넥션을 형성하여 처리
- 초당 수천 개의 모든 http 요청을 한 프로세스에서 처리하고 응답
- CPU 코어당 1개의 worker 프로세스(싱글 스레드)를 생성하도록 설정하는 것을 권장
- 각 worker 프로세스들은 문맥 전환을 줄이기 위해, non-blocking 방식으로 동작한다.
- cache loader
NGINX 구동 방식
NGINX에서는 커넥션 생성 및 커넥션 제거, 그리고 새로운 요청을 처리하는 것을 이벤트라고 부른다. 이 이벤트들은 OS커널이 큐 형식으로 worker 프로세스에게 전달해주고, 이벤트가 큐에 담긴 상태에서 worker 프로세스가 처리할 때까지 비동기 방식으로 대기한다. 그리고 워커 프로세스는 하나의 스레드로 이벤트를 꺼내서 처리해 나간다. 이렇게 되면 워커 프로세스가 쉴틈없이 일하기 때문에 아파치 서버에서 요청이 없다면 방치되던 프로세스보다 서버 자원을 더 효율적으로 쓸 수 있게 되는 것이다.
그러나 네모 칸(큐)에 있는 요청(이벤트) 중 하나가 시간이 오래걸리는 작업(ex. Disk I/O)이면 어떻게 될까?
그 뒤에 있는 이벤트는 요청을 처리하는 긴 시간동안 블로킹이 된다. 그래서 NGINX는 시간이 오래 걸리는 작업은 따로 수행하는 스레드 풀(Thread Pool)을 만들어놓고, 워커 프로세스는 지금 처리할 요청이 시간이 많이 걸릴 것 같다면 스레드 풀에 그 이벤트를 위임하고 큐 안에 있는 다른 이벤트를 처리하러 가게 된다.
참고
https://www.nginx.com/blog/thread-pools-boost-performance-9x/
댓글