1. 개요

MPM (Multi Processing Module)에 대한 글입니다.


2. Prefork (mpm_prefork_module)

HTTP Server 1.3 시대에 사용하던 Prefork 방식은 각 child process가 하나의 요청 처리를 담당합니다. 그리고 당연히 OS 상에서는 worker 방식에 비해서는 더 많은 메모리를 사용하게 됩니다. 대신 메모리 공간이 독립적이어서 안정적인 면이 있습니다.

빌드 옵션은 --with-mpm=prefork 이며, --with-mpm 옵션을 쓰지 않으면 기본으로 prefork입니다.

MaxClients 수정을 위해서는 server/mpm/prefork/prefork.c 소스를 수정합니다.

#define DEFAULT_SERVER_LIMIT 256 ...... static int server_limit = DEFAULT_SERVER_LIMIT;

본 문서에서 Prefork 방식에 대해서는 특별히 더 언급하지 않습니다. 아래부터는 모두 Worker 방식에 해당되는 설명입니다.


3. Worker (mpm_worker_module)

Worker 방식은 각 child process 내 복수 개의 thread 당 요청 처리를 담당합니다. Prefork 방식에 비해 적은 메모리를 사용하는 반면 process 내 thread 간 메모리 공유를 하므로 resource 경합이 발생할 수 있습니다.

빌드 옵션은 --with-mpm=worker 입니다.

3-1. 설정

아래는 default httpd_mpm.conf 중 일부입니다.

# worker MPM
# StartServers: initial number of server processes to start
# MaxClients: maximum number of simultaneous client connections
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestsPerChild: maximum number of requests a server process serves

    StartServers          2
    MaxClients          150
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadsPerChild      25
    MaxRequestsPerChild   0

3-2. ServerLimit, ThreadLimit

먼저 아래는 HTTP Server 기본으로 제공되는, 즉 위의 httpd_mpm.conf에서는 볼 수 없는 directive입니다. 하지만 중요한 의미를 지니고 있습니다.

  • ServerLimit

child process 수를 제한합니다. 

  • ThreadLimit

child process 당 thread 수를 제한합니다. 

ServerLimit과 ThreadLimit의 설정 값을 알고 싶었는데 적절한 자료가 없어 소스를 찾아보았습니다. 즉, server/mpm/worker.c 파일을 보시면 됩니다.

우선 ServerLimit입니다.

#define DEFAULT_SERVER_LIMIT 16
#define MAX_SERVER_LIMIT 20000

기본 16에 최대 20000까지 설정이 가능합니다. 설정이 가능하다는 말은 httpd_mpm.conf에서 ServerLimit directive로 설정이 가능하다는 것입니다. 만일 20000이 부족하면 소스 수정 후에 다시 빌드하면 되겠습니다만은, 그럴 일이 생길 일은 거의 없지 않을까요.

다음은 ThreadLimit입니다.

#define DEFAULT_THREAD_LIMIT 64
#define MAX_THREAD_LIMIT 20000

3-3. 주요 설정

그리고 이제야 위에서 보았던 directive에 대해서 설명합니다.

  • StartServer

HTTP Server 기동 시에 생성되는 process의 수 입니다. 하지만 child process의 수는 요청량에 따라 동적으로 변화하므로 크게 의미는 없다고 할 수 있습니다.

  • MaxClients

동시에 처리될 수 있는 최대 요청입니다. MaxClients를 너무 낮게 설정할 경우 resource가 놀게 되고, 너무 높게 설정할 경우 resource가 감당하기 힘든 많은 요청이 몰려들게 됩니다. MaxClients 이상의 요청은 queue 에 쌓이게 되며, default queue 511로 ListenBackLog 에 의하여 조정 가능합니다.

  • MinSpareThreads

default는 75 이며, child process 당 최소로 유지되는 idle thread 수지요. 만일 idle thread 가 충분하지 않다면 child process 는 MinSpareThreads 이상이 될 때까지 idle thread 를 생성합니다.

  • MaxSpareThreads

default는 250이며, child process 당 최대 idle thread 입니다. 만일 너무 많은 idle thread가 있다면 child process는 MaxSpareThread 가 될 때까지 idle thread 를 정리합니다.

  •  ThreadsPerChild

default 는 25이며, child process 당 thread 수 입니다. server/mpm/mpm_default.h에서 default 확인이 가능합니다.

#define DEFAULT_THREADS_PER_CHILD 25

max 는 ThreadLimit 을 따라갑니다. 위에서 ThreadLimit 의 default 가 64 라고 이야기 한 바 있구요, 따라서 만일 64 를 초과하는 값을 부여할 경우 다음과 같은 오류가 납니다. (512를 준 경우입니다)

$ ./start.sh
WARNING: ThreadsPerChild of 512 exceeds ThreadLimit value of 64
threads, lowering ThreadsPerChild to 64. To increase, please see the
 ThreadLimit directive.
  •  MaxRequestsPerChild

0이면 무제한입니다. 간혹 장기간 운영시 메모리 누수에 따른 이슈가 발생하는 경우가 있습니다. 이 때는 MaxRequestsPerChild 설정을 통해 각 프로세스 처리 횟수를 제한하여 초기화되도록 하는 것이 좋습니다.

3-4. MPM 확인

HTTP Server 가 어떤 방식으로 동작하는지 확인하려면 httpd -l 하면 됩니다.

$ ./httpd -l
Compiled in modules:
  core.c
  worker.c
  http_core.c
  mod_so.c 

4. 세부 설정

4-1. 기본

사실 제가 이야기하고 싶은 것은 지금부터 입니다. 다시 설정을 한번 봅니다.

<IfModule mpm_worker_module>
    StartServers          2
    MaxClients          150
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadsPerChild      25
    MaxRequestsPerChild   0
</IfModule>

명시적으로 표현되지 않은 ServerLimit는 default 16 이고, ThreadsPerChild이 25 이므로 최대 16*25, 총 400개의 동시 요청 처리가 가능하긴 합니다. 하지만 MaxClients 가 150 으로 설정되어 있으므로 실제적으로 최대로 기동되는 process의 수는 150/25 인 6개가 됩니다. 결론적으로 최초에 2개의 process 가 기동되어 50개의 요청처리가 가능한 상태가 되고 (50 threads) 이후 1개의 process가 증가하면서 25개의 요청처리가 더 가능해지고  (+25 threads) 최대 150개의 요청처리가 가능합니다. (150 threads)

4-2. MaxClients 증가

<IfModule mpm_worker_module>
    StartServers          2
    MaxClients         1000
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadsPerChild      25
    MaxRequestsPerChild   0
</IfModule>

이번에는 MaxClients 가 1000 으로 증가하였습니다. 결론을 얘기하자면 1000개의 동시 요청처리는 불가능합니다. 앞서 설명한대로 ServerLimit 16 에 ThreadsPerChild 25 이기 때문에 최대 400개의 동시 요청까지만 처리 가능하기 때문입니다. 만일 의도한대로 1000개의 동시 요청을 처리하게 하려면 ThreadsPerChild 를 63 이상으로 설정하거나 (16*63 = 1008), ServerLimit 을 40 이상으로 설정하던지 (40*25 = 1000), 두 값을 모두 조정하던지 해야 합니다.

4-3. ThreadsPerChild & ThreadLimit

ThreadsPerChild 는 ThreadLimit 보다 같거나 작아야 합니다. ThreadLimit 은 그냥 두고 (default 64), ThreadsPerChild 를 256 으로 올리면 다음과 같은 오류가 납니다.

WARNING: ThreadsPerChild of 256 exceeds ThreadLimit value of 64
threads, lowering ThreadsPerChild to 64. To increase, please see the
 ThreadLimit directive.

4-4. ThreadLimit 주의사항

ThreadLimit 설정시에도 주의할 점이 있습니다.

<IfModule mpm_worker_module>
    StartServers          4
    ServerLimit          32
    MaxClients         2048
    MinSpareThreads      64
    MaxSpareThreads     128
    ThreadLimit         128
    ThreadsPerChild     128
    MaxRequestsPerChild   0
</IfModule>

이론상 이렇게 설정하면 ThreadLimit이 128로 설정되어야 하겠지요.

WARNING: ThreadsPerChild of 128 exceeds ThreadLimit value of 64
threads, lowering ThreadsPerChild to 64. To increase, please see the
 ThreadLimit directive.

그런데 여전히 ThreadLimit이 64라고 합니다.

해결을 위하여 ThreadLimit의 순서를 바꾸어봅니다.

<IfModule mpm_worker_module>
    StartServers          4
    ServerLimit          32
    ThreadLimit         128
    MaxClients         2048
    MinSpareThreads      64
    MaxSpareThreads     128
    ThreadsPerChild     128
    MaxRequestsPerChild   0
</IfModule>

이번에는 오류가 발생하지 않습니다.

추가로 더 테스트 해본 바, ServerLimit 과 ThreadLimiit 은 반드시 MaxCLients 보다 앞서 기술되어야 합니다. ServerLimit * ThreadLimit >= MaxClients 어야 하기 때문일 것 같네요.

4-5. ServerLimit & MaxClients

또한 ServerLimit 을 그냥 두고 (default 16) MaxClients 를 올려서 16 개 이상의 Server 가 기동되어야 하는 경우 다음과 같은 오류가 납니다.

WARNING: MaxClients of 2048 would require 32 servers,
 and would exceed the ServerLimit value of 16.
 Automatically lowering MaxClients to 1024.  To increase,
 please see the ServerLimit directive.