1.OS 튜닝과 마찬가지로, 웹 서버도 튜닝작업을 수행한다고 해서 웹 서버가 지니고 있는 성능이 2~3배 이상 향상되는 일은
없다.
어디까지나 본래 지니고 있는 서버의 성능을 충분히 발휘할 수 있도록 조정하는 것이 튜닝 작업이다.
2.웹 서버 병목현상
과부하로 웹 서버가 응답을 제대로 반환하지 못할 경우, 그원인이 되는 것은 웹 서버의 설정과는 관계없는 것이 대분분이다.
웹 서버는 비교적 안정된 소프트웨어로, 자체만으로는 시스템에 부하를 유발하는 소프트웨어는 아니다.
드러난 웹서버의 응답불능상태의 원인을 이 웹 서버에 있다고 할 수는 없다. 열이 나고 있다고 해서 해열하는 것만으로는 병이 낫지 않는 것과
마찬가지다.
이 상황에서는 아무리 아파치 설정을 만진다고 해도 그 외의 부분에서 문제가 발생하고 있는 이상. 의미가 없다는 것을 의색해야 한다.
아파치 설정을 변경하면서 상태를 살펴보는 대책이 아닌, 장애 원인을 찾기 위한 지식이 가장 중요하다.
[추측하지 말고 계산하라]
문제의 상당수는 ps나 sar,vmstat등의 툴을 사용하면 파악할 수 있다.
한편 "하드웨어나 OS가 충분히 성능을 발휘할 수 있는 상태" 이면서 "부하가 큰 상황"에서, 웹 서버의 설정으로 족쇄가 되는 항목은
분명히 있다.
3. 아파치의 병렬처리와 [MPM-mulity processing module]
아파치를 비롯하여 불특정 다수의 클라이언트에 공개된 네트워크 서버는 동시에 복수의 클라이언트로부터 접속되더라도 계속해서 처리할 수 있도록
병렬처리를 수행할 필요가 있다.
병렬처리를 수행하지 않는 서버에서는 특정 클라이엍느가 접속해서 서버와 입출력을 하고 있는 동안, 다른 클라이언트 서버에 접속할 수가
없다.
특히 아파치를 시작으로 하는 웹 서버는 얼마나 많은 다수의 접속을 동시에 처리할 수 있는가가 성능의 기준이 되는 소프트웨어이므로,
병렬처리의 구현이 서버의 성능에 미치는 영향이 상당히 크다고 할 수 있다.
*병렬처리의 구현 모델
- 프로세스를 여려 개 생성해서 병렬처리를 실현하는 멀티 프로세스 모델 [prefork]
- 프로세스가 아니라 보다 경량의 실행단위인 쓰레드를 사용하는 멀티 쓰레드 모델 [Worker]
- 입출력을 감시해서 이벤트가 발샣아는 타이밍에 처리를 전환하는, 시그널 쓰레드로 병렬처리하는 이벤트 구동 모델 [event
MPM-실험모듈]
4. prefork와 worker, 프로세스와 쓰레드
- prefork
: 기본적으로 프로세스간에 메모리를 직접 공유하지는 않는다. 메모리 공간이 독립해 있으므로 안전하다.
: 안전지향, 후방호환성이 높은 MPM
- worker
: 멀티쓰레드에서는 메모리 공간 전체를 복수의 쓰레드가 공유하므로, 리소스 경합이 발생하지 않도록 주의할 필요가 있다. 이것이 멀티쓰레드
프로그래밍이 복잡하다고 하는 이유다.
: 확장성이 높은 MPM
mod_perl을 worker로 작동시킬 경우에는 펄이 ithreads로 쓰레드를 생성한다. 펄의 쓰레드 구현은 다소 특수하기 때문에,
prefork로 작동시킨 경우와는 다소 사양의 차이가 있다. 이를 꺼려해서 prefork를 선택하는 사용자가 많을 수 있다.
5. 성능관점에서 본 멀티프로세스/멀티쓰레드의 차이
일반적으로 멀티프로세스 보다 멀티쓰레드 방식이 가볍고 더 빠르다고 한다. 주된 이유는 다음 두가지다.
- 복수의 메모리 공간을 각각 지닌 멀티프로세스보다 메모리 공간을 공유하는 멀티쓰레드쪽이 메모리 소비량이 적다.
- 멀티쓰레드는 메모리 공간을 공유하고 있으므로, 쓰레드 전환에 걸리는 비용이 더 적다.
실제 아파치를 이용함에 있어서는 어떨까.
메모리 소비에 관해서는 실제 멀티쓰레드를 이용한느 worker에 손이 올라간다.
다만, prefork방식에서도 멀티프로세스의 부보 자식 프로세스에서 갱신이 되지 않는 메모리 공간은 공유되므로(실질적으로 초반에는
70%이상 메모리 공유가 있다), 현저한 차이가 나는 것은 아니다.
또 한가지는 "컨텍스트 스위치" 비용의 차다.
멀티태스킹 OS는 서로 다른 처리를 수행하는 처리단위로 프로세스/쓰레드를 짧은 시간에 전환하여 병렬처리를 실현하고 있다.
이 때의 프로세스/쓰레드 전환처리를 "컨텍스트 스위치"라고 한다.
따라서 멀티쓰레드방식인 worker는 메모리 공간을 공유하기 때문에 메모리 공간의 전환처리는 생략할 수 있고, 이는 이에 발생되는
CPU상의 메모리 캐시를 사용하지 않고 그대로 유지할 수 있게 하므로 성능에 미치는 영향은 현저하다고 할 수 있다.
->정리
- prefork를 worker로 변경하더라도 하나의 클라이언트에 대한 응답시간이 고속화되는 것은 아니다.
- prefork를 worker로 변경하더라도 메모리가 충반하다면 동시에 처리할 수 있는 접속 수는 변하지 않는다.
- prefork를 worker로 변경하더라도 대량의 컨텍스트 스위치가 없다면(동시에 병렬적으로 대량의 액세스가 없다면)효과는 크지
않다.
"prefork를 worker로 변경했더라도 성능이 개선되는 상황은 제한되어 있다."
"메모리 용량이 크지 않거나 소비량을 줄이고자 할 경우, 컨텍스트 스위치 횟수가 많아서(대량의 액세스) CPU의 리소스를 줄이고자 할 경우
worker 방식으로의 전환을 고려해 볼 필요가 있다"
6. 하나의 클라이언트에 대한 하나의 프로세스/쓰레드
둘의 공통점은, 아파치 클라이언트로부터 온 하나의 요청에 대해 기본적으로 하나의 프로세스 혹은 하나의 쓰레드를 할당해서 처리한다는
점이다.
즉 동시에 10개의 클라이언트로부터 요청이 있을 경우,10개의 프로세스 또는 10개의 쓰레드를 생성해서 응답한다.
따라서, 동시에 생성할 수 있는 프로세스/쓰레드 수가 아파치의 성능을 좌우하는 항목이 된다. 이러한 프로세스/쓰레드 수를 제어하는
설정항목의 최적화를 찾는 것이 아파치 튜닝의 핵심이라 할 수 있을 것이다.
7. httpd.conf 설정 - 아파치의 안전판 MaxClients
웹 서버는 "언제 어느 정도의 트래픽이 발생할지는 예상할 수 없다"는 점을 전제로 설계되어 있다.
그래서 아파치는 프로세스/쓰레드 수를 부하에 맞게 동적으로 제어하여 한다. 그러나 동적으로 제어한 결과가 머신의 리소스를 고갈시킬정도로
많은 프로세스/쓰레드를 생성하는 것이면 곤란하다.
이 때문에 안전판으로서, "동시에 접속할 수 있는 클라이언트 개수의 상한값"이 준비되어 있다. 이 안전판이 없다면 해당 시스템이 허용할 수
있는 수 이상의 요청이 동시에 몰려올 경우, 메모리를 다 써버려서 OS가 hang-up되거나 CPU사용률을 모두 점유해서 응답불가능 상태가 되는
치명적인 장애를 초래하게 된다.
오버 요청이 있을 경우 Maxclients는 다음 동작을 하게 된다.
-처리를 마치지 못한 용청에는 대기행렬내에서 일정시간 기다려 준다.
-대기행렬이 넘쳐날 듯 하면 해당 요청에 대해 에러를 반환해서 클라이언트로 응답을 돌려준다.
이로써 OS 자체가 hang-up되는 등의 쵝악의 사태를 피할 수 있게 된다.
이 상한값은 정적인 값이다. 머신이 지니고 있는 리소스에 맞게 수동으로 설정할 필요가 있다. 이 조정이 아파치 튜닝의 핵심이 된다. 반대로
이 외의 항목으로 성능에 영향을 미치는 것은 많지 않다.
prefork - MaxClients
prefork의 경우 안전판이 되는 것은 ServerLimit와 MaxClients 라는 두 가지 디렉티브로 설정된다. 둘 다 아파치가
생성하는 프로세스 수의 상한값이다.
-ServerLimit : 서버 수, 이를테면 프로세스 수의 상한
-MaxClients : 동시에 접속할 수 있는 클라이언트 수의 상한
본래는 위의 의미이지만 하나의 클라이언트를 하나의 프로세스에서 처리하므로 양자는 거의 같은 의미다. (같은 값으로 설정하면 된다.)
문제는 어느 정도의 값으로 설정하느냐인데, "이 값으로 해야 한다"라고 단정된 수치는 없다.
상한 수치를 계산하기 위해서는 다음 두 가지를 먼저 알아야 한다.
- 서버가 탑재되어 있는 물리 메모리의 양
-프로세스 하나당 평균 메모리 소비량
전자는 하드웨어 스펙을 참조하면 알 수 있다.(free 등의 명령)
후자는 ps나 top으로도 확인 가능하지만, proc 파일 시스템을 통해 조사해 보겠다.
cat /proc/프로세스 PID/status
[root@nehal-testweb1 proc]# cat /proc/27350/status
Name: httpd
State: S
(sleeping)
SleepAVG: 98%
Tgid: 27350
Pid: 27350
PPid: 19888
TracerPid: 0
Uid: 99 99 99 99
Gid: -1 -1 -1 -1
FDSize: 64
Groups: -1
VmPeak: 160576 kB
VmSize: 160572 kB
VmLck: 0
kB
VmHWM: 14728 kB <- 해당 프로세스가 실제 사용하고 있는
메모리 영역의 크기가 된다.
VmRSS: 14724 kB
VmData: 10196
kB
VmStk: 84 kB
VmExe: 536 kB
VmLib: 15376
kB
VmPTE: 248 kB
StaBrk: 19d80000 kB
Brk: 1a70f000
kB
StaStk: 7fffe86b1f00
kB
Threads: 1
SigQ: 0/63488
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000001200
SigCgt: 000000018400446b
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
Cpus_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00ffffff
Mems_allowed: 00000000,00000001
[root@nehal-testweb1
proc]#
언뜻 보면 이 VmHWM 값으로부터 httpd의 각 프로세스의 평균값을 구하면 될 것처럼 보인다.
예를 들면 VmHWM: 14728 kB 의 경우 평균 메모리사용량을 15MB로 계산하고, 서버 총
메모리를 4GB, OS 할당을 512MB로 잡았을 경우 MaxClients의 값은 230이 되게 된다.
ex)
4GB(총) - 0.5GB(OS) = 3.5G
3.5GB/0.15GB = 230
그러나 이것만으로는 판단재료가 불충분하다. 리눅스는 물리 메모리를 절약하기 위해 부모 프로세스와 자식 프로세스에서 일부 메모리를 공유한다.
이런 공유 메모리를 고려하면 보다 큰 값을 설정하는 것이 가능하다.
"카피 온 라이트" -부모/자식 프로세스간 메모리 공유
모든 프로세스에는 부모 프로세스가 있다. prefork인 아파치의 경우 하나의 특정 httpd 부모 프로세스가 우선 가동해서 복수의
httpd 자식 프로세스를 생성한다. 부모와 자식은 서로 독립된 메모리 공간을 실현하기 위해 fork에 동반해서 부모로부터 자식에게 메모리의
내용을 있는 그대로 복사하지만 이러한 복사 처리는 매우 비용이 높은 처리이다.
그래서 리눅스는 fork 단계에서 가상 메모리 공간에 매핑된 물리 메모리 영역은 복사하지 않고 부모와 자식간에 이를 공유한다.
이러한 공유는 부모 자식에게 가상 메모리 공간은 각각 마련해 주고, 각각의 메모릭 공간으로 부터 동일한 물리 메모리 영역을 매핑함으로써
실현된다.
부모 혹은 자식이 가상메모리에 대해 쓰기를 수행하면, 해당 쓰기 작업이 수행된 영역만 더 이상 공유할 수 없게 된다.
역으로 쓰기 작업이 수행되지 않은 메모리 영역은 항상 계속해서 공유되어 있을 수 있으며, 이에 따라 메모리상의 페이지 중복을 피해서
메모리를 효율적으로 이용할 수가 있다.
이런한 구조를 " Copy on Write" 라고 한다. "쓰기 작업시에 복사한다" 라는 의미다.
카피 온 라이트로 공유 메모리 사이즈 확인
MaxClients를 설정하려면 실제로 사용하고 있는 메모리 영역 내의 공유 물리 메모리 크기도 고려할 필요가 있다.
공유 메모리 영역은 /proc/프로세스PID/smaps 의 데이터 참조로 확인할 수 있지만, 데이터량이 많으므로 그 자체로는
어렵다.