Files

18 KiB
Raw Permalink Blame History

웹 계층(Apache) 부하·용량 분석

2026-04-14 AWS 인스턴스 업그레이드의 배경을 현재 사양에서 재검증하고, Apache 워커·KeepAlive·DB 연결이 실제로 병목인지 실측으로 판정합니다.


1. 배경: 2026-04-14 업그레이드는 왜 했나

당시 파악된 장애 경로입니다.

동시 접속 증가
  → Apache MaxRequestWorkers 가 감당 못 함
  → 서버 waiting 과 CPU steal 급등
  → t2.micro 의 CPU 크레딧 소진
  → 약 10초간 무응답

당시 분석이 내린 결론은 t3.micro + MaxRequestWorkers 50 이었습니다. 근거는 "서비스 사이클 34초 중 서버 점유 1초(약 3%)" 라는 점유율 모델이었고, 이를 근거로 평상시 동시 사용자 약 800명까지 안정적이라고 추정했습니다.

실제로는 그보다 큰 t3a.medium 으로 갔습니다. 즉 문서의 권고안과 실제 구성이 다르므로, 그 문서의 수치는 현재 상태를 설명하지 못합니다.

t2.micro (당시) t3a.medium (현재)
vCPU / 메모리 1 / 1GB 2 / 4GB
크레딧 베이스라인 10% 20%
시간당 크레딧 6 (최대 144) 24 (최대 576)

1-1. 당시 계산 모델의 한계

기록해둡니다. 같은 모델을 다시 쓰지 않기 위해서입니다.

  • "1 페이지 로드 = 1 요청"이 아닙니다. index.html 하나에 script/link 11개 + jQuery .load() 로 header/section/footer 3개를 더 받고, 그 뒤에 PHP 엔드포인트가 붙습니다 (src/web/main/index.html:36).
  • 0.5초는 CPU 시간이 아니라 응답 시간입니다. 3% 를 "CPU 여유"로 읽으면 안 되고 "워커 점유율"로만 읽어야 합니다.
  • 평균 모델은 버스트를 설명하지 못합니다. 문제는 평균이 아니라 수업 시작 시각에 몰리는 순간입니다.
  • "평상시 500명 → 800명"은 측정값이 아니라 위 모델에서 나온 추정치입니다.

2. 측정 결과

2-1. CPU 크레딧 / steal — 문제 없음

피크 시간대(12:50~15:00) vmstat 로그 779개 샘플에서 st(steal) 컬럼을 직접 집계했습니다. 원본: result/peaktime_steal_summary.txt

9/14 (개선 전) 9/15 (개선 전) 9/16 (개선 후)
steal 평균 0.003% 0.021% 0.010%
steal 최대 1% 2% 1%
us+sy 평균 13.50% 9.48% 2.79%
베이스라인(20%) 초과 샘플 109개 (14.0%) 65개 (8.3%) 1개 (0.1%)
swpd / si / so 0 / 0 / 0 0 / 0 / 0 0 / 0 / 0

t3a.medium 의 크레딧 베이스라인은 인스턴스 전체 CPU의 20% 이고, 이는 vmstat 의 us+sy 20% 와 같은 값입니다. 개선 전에도 피크 평균이 13.5% 로 베이스라인 미만이었으므로 피크 시간에도 크레딧은 순증하고 있었습니다.

4월 장애의 직접 원인(크레딧 소진 → 스로틀링)은 현재 사양에서 재현되지 않습니다.

⚠️ 스왑이 없습니다(swpd/si/so 모두 0). 메모리 한계에 닿으면 느려지는 게 아니라 OOM Killer 가 동작하고, RSS 가 가장 큰 mysqld 가 1순위 표적입니다. 메모리 여유는 별도 감시 대상입니다.

2-2. Apache 설정 현황

apache2ctl -V | grep -i "Server MPM"
ls /etc/apache2/mods-enabled/ | grep mpm
grep -RiE "^[[:space:]]*(MaxRequestWorkers|MaxClients|ServerLimit|StartServers|MaxConnectionsPerChild)" /etc/apache2/
grep -rihE '^\s*(KeepAlive|MaxKeepAliveRequests|KeepAliveTimeout)' /etc/apache2/apache2.conf /etc/apache2/conf-enabled/ /etc/apache2/sites-enabled/
apache2ctl -M | grep -iE 'php|proxy_fcgi|http2|status'
항목 비고
Apache 2.4.58 (Ubuntu) 호스트 설치. 도커 아님
MPM prefork mod_php 때문에 고정
StartServers 5
ServerLimit 400 Debian 기본 150에서 상향돼 있음
MaxRequestWorkers 400
MaxConnectionsPerChild 0 자식 프로세스 무한 재사용
KeepAlive On
KeepAliveTimeout 5 워커 점유의 주 원인 (2-5 참조)
MaxKeepAliveRequests 100
PHP php7_module (mod_php) PHP-FPM 없음
HTTP/2 미적용 mod_http2 없음 → 브라우저가 origin당 연결 최대 6개
mod_status 있음 Require local (/etc/apache2/mods-enabled/status.conf)

mods-available/mpm_event.conf·mpm_worker.conf 의 150은 비활성 MPM 이므로 무관합니다.

2-3. Apache 실부하 — 병목 아님

curl -s http://localhost/server-status?auto 누적값 (RestartTime 9/12 06:44 이후 394,325초 = 4일 13시간)

지표 계산
총 요청 Total Accesses 1,398,433 3.55 req/s
평균 응답 시간 Total Duration 21,596,481ms ÷ 요청 수 15.4 ms
요청당 CPU (CPUChildrenUser 822.34 + CPUChildrenSystem 835.02)초 ÷ 요청 수 1.19 ms
Apache 총 CPU 점유 1,657초 ÷ 394,325초 코어 1개의 0.42%
응답 1건 평균 크기 4,093,249KB ÷ 요청 수 2.9 KB

메모리 (/proc/*/smaps_rollup 의 PSS 합계, 유휴 시각 기준):

apache2 14개 / PSS 합계 29MB / 1개당 평균 2MB

PSS 합계는 공유 페이지를 중복 없이 한 번만 계산하므로 Apache 전체의 실제 메모리 비용입니다. 단, 이 값은 유휴 자식 기준입니다. PHP 요청을 처리한 자식은 더 커지므로 피크 재측정이 필요합니다.

2-4. DB 연결 — 제약 아님

지표
max_connections 151
Max_used_connections (MariaDB 시작 이래 고수위) 27
요청 대비 신규 DB 연결 비율 요청 130건당 36건 = 28%

요청의 28%만 DB를 타고(나머지는 정적 파일), 연결 하나가 수 ms만 삽니다. 동시 DB 연결은 0.04개 수준으로, 151에 닿으려면 초당 만 단위 신규 연결이 필요합니다.

MaxRequestWorkers 400max_connections 151 을 넘는 것은 실무상 문제가 되지 않습니다.

2-5. KeepAlive 점유 구조 — 확정 (2026-09-17 측정)

scoreboard 분해 샘플 (curl -s http://localhost/server-status?autoScoreboard: 행)

시각 busy idle K(KeepAlive 대기) W(전송) R(수신) 구간 요청률
20:16 2 8 1 1 0
20:30:25 15 3 11 1 3
20:30:30 15 5 6 1 2 2.2 req/s
20:30:40 7 7 5 1 0 2.2 req/s
20:33:12 9 6 8 1 0
20:33:20 5 9 3 1 0 1.5 req/s
20:33:30 14 0 11 1 2 13 req/s

busy 워커의 70~80%가 K(KeepAlive 대기) 입니다. 실제 작업 중인 W 는 일관되게 1개입니다.

prefork 에서는 워커가 요청 처리 중만이 아니라 TCP 연결이 열려 있는 내내 점유됩니다. 응답을 다 보낸 뒤에도 KeepAliveTimeout(5초) 동안 자식 프로세스가 연결을 붙잡습니다. 워커 점유 효율은 15ms / 5,000ms = 0.3% 입니다.

2026-09-17 피크 780샘플로 관계가 확정됐습니다. 5장 참조 — K 는 요청률에 선형 비례합니다. (위 저녁 샘플만 보고 "비례하지 않는다"고 판단했던 것은 표본 부족에 따른 오판이었습니다.)


3. 결론

3-1. 확정된 것

항목 결론
CPU 크레딧 / steal 문제 없음. steal 최대 2%, 피크에도 베이스라인 미달
Apache CPU 코어의 0.42%
Apache 메모리 14프로세스 29MB (유휴 기준)
평상시 워커 400 중 2~15개 사용
DB 연결 상한 151 제약 아님. 고수위 27
MaxRequestWorkers 400 유지. 9/17 피크 busy 204(51%)로 적정 (5장)

현재 지표는 전부 여유입니다. 지금 설정을 바꿀 이유가 없습니다.

3-2. 분석 중 폐기한 가설

같은 오류를 반복하지 않기 위해 기록합니다.

가설 왜 틀렸나
mod_php 자식 1개당 10~30MB → 400개면 OOM 실측 PSS 2MB (유휴). 400개여도 800MB 수준
워커 1개 = DB 연결 1개 → 400 > 151 은 결함 요청의 28%만 DB 사용. K 상태 워커는 DB 연결을 안 씀. 고수위 27
Little's law 로 동시 요청 0.055개 → 120이면 13배 여유 Total Duration처리 시간만 포함. KeepAlive 점유가 빠져 실측(busy 2~15)과 100배 차이
K ≈ 요청률 × KeepAliveTimeout 선형 외삽 2.2 req/s 와 13 req/s 모두 K=11. 버스트에서 연결 재사용이 작동
MaxRequestWorkers 120 권장 9/17 피크 busy 최대 204. 120으로 조였다면 수업 시간에 요청이 큐에 쌓였음 (5-3절)
K 는 요청률에 비례하지 않는다 (저녁 샘플 7개 기준) 780샘플 회귀에서 K = 1.28 × req/s, 상관 0.862. 표본 부족에 따른 오판
외부에서 /server-status 403 확인 도메인 오류로 무효. SSL 인증서는 www_chocomae_com (ssl/README:7), chocomae.jinaju.com 은 http 전용

3-3. DB 분리 검토(09번 문서)에 주는 시사점

4월 장애가 Apache 워커 + 인스턴스 크레딧에서 비롯됐다면, DB를 RDS로 분리해도 웹 계층 문제는 그대로 남습니다. 반면 9/16 실측은 DB 개선만으로 피크 CPU가 3.4배 줄었음을 보여줍니다.

09번 문서의 "DB 분리 불필요" 결론을 강화합니다.

다만 09번 문서 2장의 원인 진단이 "DB와 PHP의 리소스 경쟁"으로만 서술돼 있고 인스턴스 크레딧이라는 세 번째 원인이 빠져 있습니다. 4월 이력을 반영한 보완이 필요합니다.


4. 남은 측정

4-1. 피크 모니터링 (등록 완료)

/home/ubuntu/temp/apache-peak.sh — 평일 12:50부터 10초 간격 780회(130분). 기존 vmstat 모니터링과 같은 시각에 돌아 로그를 나란히 대조할 수 있습니다.

50 12 * * 1-5 /home/ubuntu/temp/chocomae-peaktime-monitoring.sh
50 12 * * 1-5 /home/ubuntu/temp/apache-peak.sh >> /home/ubuntu/temp/apache-peak.cron.log 2>&1
0 0 * * 1 find /home/ubuntu/temp \( -name "vmstat_*.log" -o -name "apache_*.log" \) -mtime +30 -delete

수집 컬럼: time busy idle K W R total_accesses db_conn db_max db_total

  • sleep $(( 10 - $(date +%s) % 10 )) 으로 10초 격자에 고정 — vmstat 로그와 시각이 어긋나지 않습니다.
  • DB 조회는 sudo -n /usr/bin/mariadb 경유 (OS 계정 ubuntu 는 DB 직접 접속 권한 없음, NOPASSWD sudo 가능).
  • db_conn(Threads_connected) 은 순간값이라 짧은 PHP 연결을 대부분 놓칩니다. 판정에는 db_max(Max_used_connections, 고수위)를 쓰세요.

4-2. 판정 기준

관측 판단
피크 busy 최대가 400에 근접 워커가 병목. KeepAliveTimeout 단축 우선 검토
Kbusy 의 대부분이고 요청률과 무관하게 큼 KeepAliveTimeout 5→2초. 워커 수요 감소, CPU 여유(0.42%)로 TLS 재핸드셰이크 흡수 가능
피크 busy 최대가 100 미만 400은 과하지만 해가 없음. 변경 불필요
idle=0 샘플이 잦음 StartServers/MinSpareServers 상향 검토
db_max 가 27에서 오름 DB 연결 재평가

4-3. 미확인 항목

  • 외부에서 /server-status 접근 차단 확인 (도메인 정정 후 재시도)
    curl -s -o /dev/null -w 'jinaju http  : %{http_code}\n' http://chocomae.jinaju.com/server-status
    curl -s -o /dev/null -w 'chocomae https: %{http_code}\n' https://www.chocomae.com/server-status
    
    403 이면 정상. 리버스 프록시를 거치면 Require local 이 프록시 IP를 통과시킬 수 있어 실제 응답 확인이 필요합니다.
  • EC2 크레딧 모드가 Unlimited 인지 Standard 인지 (콘솔 → EC2 → 인스턴스 → 크레딧 사양, 또는 CloudWatch CPUSurplusCreditsCharged)
  • PHP opcache.enable / memory_consumption / memory_limit
    grep -rhE '^\s*(memory_limit|opcache\.enable|opcache\.memory_consumption)' /etc/php/*/apache2/php.ini /etc/php/*/apache2/conf.d/
    
  • 피크 시각 자식 프로세스 PSS 재측정 (유휴 2MB 가 아니라 실제 값)

5. 피크 측정 결과 (2026-09-17)

원본: result/peaktime/apache_20260917-1250.log, result/peaktime/vmstat_20260917-1250.log 측정 구간 12:50:01~14:59:50, 10초 간격 780샘플.

5-1. 요약

항목 상한 대비
busy 워커 최대 204 (13:54:00) MaxRequestWorkers 400의 51%
자식 프로세스 최대 (busy+idle) 230 ServerLimit 400의 57.5%
busy 평균 52.9
busy ≥ 100 샘플 110개 (14.1%)
busy ≥ 150 샘플 50개 (6.4%)
busy ≥ 200 샘플 3개
K(KeepAlive 대기) 최대 152 (13:52:50) busy 의 69%
W(전송) 최대 / 평균 4 / 1.06
idle=0 샘플 39개 (5.0%)
요청률 최대 132.4 req/s (14:01:40) 평균 22.3 req/s
DB 신규 연결 최대 31.7 conn/s 평균 8.1 conn/s
Max_used_connections 27 (변동 없음) max_connections 151의 18%

같은 시각 vmstat:

항목
us+sy 평균 / p95 / 최대 2.14% / 5% / 9%
크레딧 베이스라인(20%) 초과 샘플 0개
steal 평균 / 최대 0.000% / 0%
swpd / si / so 0 / 0 / 0
free 최소 / cache 평균 396MB / 2,455MB

5-2. 시간대별 추이

수업 교시에 맞춰 뚜렷한 파형이 나옵니다.

구간 busy 평균 busy 최대 K 평균 req/s 평균
12:50 13.3 25 8.0 7.3
13:00 17.5 32 9.4 6.2
13:10 4.6 16 1.8 0.8
13:20 4.3 14 2.1 1.3
13:30 10.3 26 5.4 5.5
13:40 46.9 94 33.6 33.0
13:50 155.9 204 111.6 66.9
14:00 115.2 176 79.7 46.6
14:10 52.9 84 36.2 16.9
14:20 65.7 106 46.0 26.1
14:30 42.8 79 30.6 15.9
14:40 83.4 121 56.9 33.3
14:50 74.5 120 50.9 29.6

최대 부하 구간(13:50~14:00)은 busy 평균 156 / 최대 204, K 평균 112, 67 req/s 입니다.

피크 요청률 132 req/s 는 4일 누적 평균 3.55 req/s 의 37배입니다. 누적 평균으로 용량을 계산하면 안 되는 이유가 이 숫자입니다.

5-3. 워커 수요 모델 (780샘플 회귀)

관계 회귀식 상관계수
요청률 → K K = 1.28 × req/s + 7.9 0.862
요청률 → busy busy ≈ 1.74 × req/s 0.860

K 는 요청률에 선형 비례합니다. 9/16 저녁 샘플 7개로 "비례하지 않는다"고 본 것은 오판이었습니다.

KeepAliveTimeout 이 5초인데 계수가 1.28 이라는 것은, 연결 하나가 평균 약 3.9개 요청을 처리하고 끊긴다는 뜻입니다(5 ÷ 1.28). MaxKeepAliveRequests 100 설정 대비로는 재사용률이 낮지만, 재사용 자체는 작동하고 있습니다.

이 모델로 상한 도달점을 계산하면:

현재 (KeepAliveTimeout 5초):  400 워커 ÷ 1.74 = 약 230 req/s 에서 포화
9/17 실측 피크                = 132 req/s
→ 여유 약 1.74배

5-4. 판정

항목 판정
MaxRequestWorkers 400 적정. 유지. 피크 51% 사용
max_connections 151 적정. 고수위 27 (18%)
CPU / 메모리 / steal 전부 여유. 피크에도 us+sy 최대 9%, steal 0%
KeepAliveTimeout 5 ⚠️ 워커의 69%를 소비. 여유가 1.74배뿐이므로 성장 대비 검토 대상
idle=0 5.0% ⚠️ 예비 워커 소진 구간 존재. MinSpareServers 검토 대상

120으로 낮췄다면 피크에 요청이 큐에 쌓였습니다. 실측 204 > 120 이고, busy≥150 샘플이 50개(6.4%)였습니다. 4월 분석의 50은 더 말할 것도 없습니다. MaxRequestWorkers 400 은 근거 있는 설정이었습니다.


6. 권고

6-1. 지금 할 것 — 없음 (필수 변경 사항 없음)

모든 지표가 상한 대비 여유입니다. 서비스에 문제가 없으므로 긴급히 바꿀 설정은 없습니다.

6-2. 선택 — 성장 대비 (트래픽 1.7배 이상 증가가 예상될 때)

A. KeepAliveTimeout 5초 → 2초

KeepAliveTimeout 2

K 계수가 1.28 → 약 0.51 로 줄어 busy ≈ 0.97 × req/s 가 됩니다.

포화 요청률 9/17 피크 대비 여유
현재 (5초) 약 230 req/s 1.74배
변경 후 (2초) 약 410 req/s 3.1배

메모리도 DB 연결도 늘지 않고 여유만 1.8배가 됩니다. 비용은 TCP·TLS 재핸드셰이크 증가인데, 피크 CPU 가 us+sy 최대 9% 라 충분히 흡수됩니다.

B. MaxConnectionsPerChild 0 → 10000

MaxConnectionsPerChild 10000

자식 프로세스를 영원히 재활용하지 않는 현재 설정은 mod_php 누수가 누적됩니다. 위험도 낮은 위생 조치입니다.

C. MinSpareServers 검토

idle=0 샘플이 39개(5.0%)이고 그때 busy 평균이 89였습니다. 예비 워커가 떨어지면 다음 요청은 fork 를 기다립니다. 13:40~13:50 구간처럼 busy 가 10 → 204 로 급등할 때 특히 영향이 있습니다.

현재 값을 먼저 확인해야 합니다.

grep -E 'MinSpareServers|MaxSpareServers' /etc/apache2/mods-enabled/mpm_prefork.conf

Debian 기본값(5 / 10)이라면 피크 busy 평균 53 에 비해 지나치게 작습니다. 20 / 50 정도로 올리면 fork 급증 구간이 완만해집니다.

적용은 무중단입니다.

sudo cp /etc/apache2/mods-available/mpm_prefork.conf /etc/apache2/mods-available/mpm_prefork.conf.bak-$(date +%Y%m%d)
sudo apache2ctl configtest && sudo systemctl reload apache2

6-3. 재측정

설정을 바꾸면 같은 스크립트로 하루 더 측정해 K 계수가 예상대로 내려갔는지 확인하세요. cron 은 평일 12:50 에 계속 돌고 있습니다.

6-4. 남은 확인 항목

  • EC2 크레딧 모드 (Unlimited / Standard) — 9/17 steal 0%, 베이스라인 초과 0샘플이라 실질 위험은 낮음
  • 외부에서 /server-status 접근 차단 확인
  • PHP opcache.enable 여부