시스템 관리_21 아파치 HTTPD ServerLimit 지시어 초과 경고 및 서버 고부하 완벽 해결 가이드

 
  • 출처 / 참고: Apache HTTP Server Documentation (mpm_common, event MPM, worker MPM)

명령어: apachectl, httpd, top, ps, free, vmstat
키워드: ServerLimit, MaxRequestWorkers, MaxClients, MPM Event, MPM Worker, Prefork, High Load, Memory Exhaustion
사용처: 웹 서버 동시 접속자 급증에 따른 응답 지연 해결, 아파치 MPM 프로세스 스레드 튜닝, 메모리 고갈(OOM) 방지


실행예제

아파치 에러 로그에서 WARNING: ServerLimit of ... exceeds compile-time limit of ... 또는 server reached MaxRequestWorkers setting 경고가 발생할 때 현재 프로세스 수, 메모리 점유율, MPM 동작 모드를 파악합니다.

# 1. 아파치 에러 로그에서 ServerLimit 및 MaxRequestWorkers 관련 경고 확인
$ sudo tail -n 50 /var/log/apache2/error.log  # Ubuntu/Debian
# 또는
$ sudo tail -n 50 /var/log/httpd/error_log    # RHEL/CentOS/Rocky
# [출력 예시]
# [mpm_event:error] [pid 1234:tid 1402] AH00484: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting
# [core:warn] [pid 1234:tid 1402] AH00018: WARNING: ServerLimit of 2000 exceeds compile-time limit of 20000 servers, decreasing to ...

# 2. 현재 컴파일/활성화된 MPM(Multi-Processing Module) 모드 점검 (event, worker, prefork 확인)
$ apachectl -V | grep -i 'Server MPM'
# Server MPM:     event
# 또는
$ httpd -V | grep -i 'Server MPM'

# 3. 현재 실행 중인 아파치 자식 프로세스 수 및 스레드 수 확인
$ ps -ef | grep -E 'apache2|httpd' | grep -v grep | wc -l
# 스레드 전체 카운트 (MPM worker/event 환경)
$ ps -eLf | grep -E 'apache2|httpd' | grep -v grep | wc -l

# 4. 아파치 프로세스당 평균 메모리 사용량(RSS) 계산 (MB 단위)
$ ps -ylC apache2 --sort:rss 2>/dev/null | awk '{sum+=$8; ++n} END {if(n>1) print "Average RSS: " sum/(n-1)/1024 " MB"; else print "N/A"}'
# RHEL 계열의 경우:
$ ps -ylC httpd --sort:rss 2>/dev/null | awk '{sum+=$8; ++n} END {if(n>1) print "Average RSS: " sum/(n-1)/1024 " MB"; else print "N/A"}'

   

스크립트

현재 시스템의 가용 RAM 용량과 아파치 프로세스당 평균 메모리 소비량을 계산하여, 시스템 OOM(Out of Memory) 크래시를 유발하지 않는 최적의 ServerLimit 및 MaxRequestWorkers 값을 자동 산출해 주는 쉘 스크립트입니다.

#!/bin/bash
# tune_apache_mpm.sh - Diagnose Apache MPM limits and calculate safe ServerLimit/MaxRequestWorkers

echo "=========================================="
echo " [아파치 MPM 및 메모리 부하 진단]"
echo "=========================================="

# 1. 아파치 데몬 감지
HTTPD_BIN="apachectl"
HTTPD_NAME="apache2"
if ! command -v apachectl &>/dev/null; then
    HTTPD_BIN="httpd"
    HTTPD_NAME="httpd"
fi

if ! command -v "$HTTPD_BIN" &>/dev/null; then
    echo "  [오류] 아파치 실행 파일을 찾을 수 없습니다."
    exit 1
fi

MPM_TYPE=$($HTTPD_BIN -V | grep -i 'Server MPM' | awk '{print $3}')
echo "  - 감지된 웹 서버 데몬 : ${HTTPD_NAME}"
echo "  - 활성화된 MPM 모드   : ${MPM_TYPE}"

# 2. 시스템 메모리 현황
TOTAL_MEM_MB=$(free -m | awk '/Mem:/ {print $2}')
AVAIL_MEM_MB=$(free -m | awk '/Mem:/ {print $7}')
echo -e "\n[1] 시스템 메모리 상태:"
echo "  - 전체 RAM: ${TOTAL_MEM_MB} MB"
echo "  - 여유 RAM: ${AVAIL_MEM_MB} MB"

# 3. 아파치 프로세스 평균 메모리 소비량 측정
PROC_COUNT=$(pgrep -c "$HTTPD_NAME")
if [ "$PROC_COUNT" -gt 0 ]; then
    AVG_PROC_MB=$(ps -ylC "$HTTPD_NAME" --sort:rss 2>/dev/null | awk '{sum+=$8; ++n} END {if(n>1) printf "%.0f", sum/(n-1)/1024; else print 0}')
    [ -z "$AVG_PROC_MB" ] || [ "$AVG_PROC_MB" -eq 0 ] && AVG_PROC_MB=50
else
    AVG_PROC_MB=50
fi
echo -e "\n[2] 아파치 프로세스 통계:"
echo "  - 실행 중인 자식 프로세스 수 : ${PROC_COUNT}개"
echo "  - 프로세스당 평균 점유 메모리 : 약 ${AVG_PROC_MB} MB"

# 4. 안전 가이드라인 연산 (웹 전용 기준: 총 메모리의 70% 할당)
DEDICATED_MEM=$(( TOTAL_MEM_MB * 70 / 100 ))
SAFE_MAX_CLIENTS=$(( DEDICATED_MEM / AVG_PROC_MB ))

echo -e "\n[3] MPM 모드별 추천 튜닝 가이드 (메모리 70% 안전 기준):"
if [ "$MPM_TYPE" == "prefork" ]; then
    echo "  * Prefork MPM (1 프로세스 = 1 연결):"
    echo "    - ServerLimit         = ${SAFE_MAX_CLIENTS}"
    echo "    - MaxRequestWorkers   = ${SAFE_MAX_CLIENTS}"
else
    # worker or event (기본 ThreadsPerChild = 25 가정)
    THREADS_PER_CHILD=25
    SAFE_SERVER_LIMIT=$(( SAFE_MAX_CLIENTS / THREADS_PER_CHILD ))
    [ "$SAFE_SERVER_LIMIT" -lt 1 ] && SAFE_SERVER_LIMIT=1
    CALC_WORKERS=$(( SAFE_SERVER_LIMIT * THREADS_PER_CHILD ))
    
    echo "  * ${MPM_TYPE} MPM (프로세스 기반 멀티 스레드):"
    echo "    - ThreadsPerChild     = ${THREADS_PER_CHILD} (기본값)"
    echo "    - ServerLimit         = ${SAFE_SERVER_LIMIT}"
    echo "    - MaxRequestWorkers   = ${CALC_WORKERS}"
fi

echo -e "\n=========================================="
echo " [진단 완료]"
echo "=========================================="

   

해설

아파치 웹 서버에서 발생하는 ServerLimit directive exceeds maximum 경고와 대량 트래픽 인입 시의 고부하(High Load) 현상은 서버의 가용 하드웨어 자원(CPU/RAM)과 MPM(Multi-Processing Module) 설정 간의 불균형 때문에 발생합니다.

1. ServerLimit과 MaxRequestWorkers의 관계 및 지시어 순서

  1. MaxRequestWorkers (과거 MaxClients):
    • 아파치가 동시에 처리할 수 있는 최대 동시 요청(동시 접속자) 수입니다.
    • 이 제한에 도달하면 신규 연결 요청은 ListenBacklog 큐에 대기하게 되며, 큐마저 차면 클라이언트에 연결 거절(Connection refused 또는 지연 후 타임아웃)이 발생합니다.
  2. ServerLimit:
    • 생성 가능한 최대 아파치 자식 프로세스 수의 상한선입니다.
    • 지시어 순서의 중요성: 아파치는 설정 파일을 위에서 아래로 파싱합니다. 반드시 ServerLimit을 MaxRequestWorkers보다 먼저 선언해야 합니다. 만약 MaxRequestWorkers를 올렸는데 ServerLimit이 그 아래에 있거나 누락되면, 아파치는 기존 기본 ServerLimit 상한선에 맞춰 MaxRequestWorkers 값을 강제로 깎아버리며 에러 로그를 남깁니다.
  3. MPM 모드별 계산 공식:
    • MPM Prefork: 스레드를 쓰지 않으므로 MaxRequestWorkers = ServerLimit 공식이 성립합니다.
    • MPM Event / Worker: 멀티 프로세스 멀티 스레드 방식이므로 아래 공식이 성립합니다. \(\text{MaxRequestWorkers} = \text{ServerLimit} \times \text{ThreadsPerChild}\)

2. 고부하 및 OOM 방지를 위한 설정 가이드

서버 메모리가 16GB이고 데이터베이스와 분리된 전용 웹 서버 환경에서 MPM Event를 사용할 때의 모범 설정 예시입니다.

# /etc/apache2/mods-available/mpm_event.conf (Ubuntu)
# 또는 /etc/httpd/conf.modules.d/00-mpm.conf (RHEL)

<IfModule mpm_event_module>
    # 반드시 ServerLimit이 MaxRequestWorkers보다 위에 위치해야 함
    ServerLimit              32
    StartServers              4
    MinSpareThreads          75
    MaxSpareThreads         250
    ThreadsPerChild          25
    MaxRequestWorkers       800   # 32 * 25 = 800
    MaxConnectionsPerChild 5000   # 메모리 누수 방지를 위한 자식 프로세스 재활용 수
</IfModule>

   

주의사항

  1. 컴파일 타임 하드 리밋(Hard Limit) 초과 주의:
    • 아파치 소스 코드에 정의된 컴파일 상한치(Event/Worker는 기본 20,000, Prefork는 200,000)를 넘는 비현실적인 값을 ServerLimit에 기재하면 아파치가 구동 시 컴파일 상한값으로 강제 하향 조정합니다.
  2. Prefork MPM 사용 시 메모리 폭발 위험:
    • PHP 구형 모듈(mod_php) 연동을 위해 여전히 prefork를 사용하는 환경이 많습니다. Prefork는 프로세스당 수십~수백 MB의 메모리를 먹기 때문에 동시 접속자를 감당하려고 ServerLimit을 1,000 이상 올리는 순간 시스템 스왑(Swap)이 급증하고 커널 OOM Killer가 동작하여 아파치나 DB 데몬이 강제 종료됩니다. 동시 접속 처리가 많은 환경이라면 반드시 PHP-FPM + MPM Event 조합으로 전환해야 합니다.
  3. MaxConnectionsPerChild를 0(무한)으로 두는 위험:
    • C/C++ 확장 모듈이나 PHP 코드에서 발생하는 미세한 메모리 누수(Memory Leak)가 자식 프로세스에 누적되어 프로세스 점유 메모리가 계속 불어날 수 있습니다. 프로덕션 환경에서는 MaxConnectionsPerChild를 5000~10000 정도로 지정하여 프로세스가 주기적으로 자동 재생성되도록 유도하는 것이 안전합니다.