LAMP 시스템 조율, Part 2: 아파치와 PHP 최적화
아파치가 느려지는 이유와 PHP 성능을 최대로 끌어내는 방법 연재물은 응용 프로그램 성능을 향상시킬 서버 환경 설정 항목을 다룹니다. 첫 번째 기사는 LAMP 아키텍처, 성능 기법, 기본적인 리눅스 커널, 디스크, 파일 시스템 미조정을 다뤘습니다. 두 번째 기사에서는 아파치와 PHP 컴포넌트를 최적화하는 방법에 초점을 맞춥니다.
요약: LAMP(Linux®, Apache, MySQL, PHP/Perl) 아키텍처를 활용하는 응용 프로그램은 끊임없이 개발되고 배포되고 있습니다. 하지만 때로 서버 관리자는 다른 사람이 작성했다는 이유만으로 응용 프로그램 자체에 대한 통제권이 거의 없습니다. 기사 셋으로 이뤄진 이번 아파치 조율
아파치는 환경 설정이 자유로운 소프트웨어다. 기능도 많지만 각 기능마다 비용을 치뤄야 한다. 아파치 조율을 위해 적절한 자원 할당이 필요하며, 환경 설정을 줄여 필요한 항목만 남겨두는 지혜가 필요하다.
MPM 환경 설정
아파치는 기능을 쉽게 추가하거나 삭제할 수 있는 모듈화된 구조를 따른다. MPM(Multi-Processing Module)은 이런 모듈화된 구조를 네트워크 연결 관리와 요청 처리를 위한 아파치 핵심 기능으로 제공한다. MPM은 스레드를 사용하도록 만들고 심지어 아파치를 다른 운영체제로 이동하도록 만들어준다.
한번에 MPM 하나만 활성화되며,
--with-mpm=(worker|prefork|event)
를 사용해 정적으로 컴파일해야 한다.요청당 프로세스 하나를 띄우는 전통적인 모델을 prefork라고 한다. 스레드를 적용한 새로운 모델은 worker라고 하는데, 다중 프로세스를 사용하며 부하를 줄이고 성능을 높이기 위해 다중 스레드를 사용한다. 최종적으로 event MPM은 실험적인 모듈로 다양한 작업을 수행하도록 독립된 스레드 풀을 유지한다. 현재 사용 중인 MPM을 살펴보려면
httpd -l
명령을 수행한다.MPM 선택은 여러 가지 요인에 달려있다. 실험 상태를 벗어날 때까지 event MPM 설정을 미뤄두야 하므로, 스레드를 사용할지 스레드를 사용하지 않을지를 놓고 선택이 필요하다. 표면적으로 PHP가 사용하는 모든 라이브러리를 비롯하여 모든 기반 모듈이 스레드 안전을 보장하면 thread 방식은 fork 방식보다 그럴 듯하게 들린다. prefork는 좀더 안전한 선택이다. worker 모델을 선택할 때는 신중하게 실험해야 한다. 성능 향상은 또한 배포판과 하드웨어에 따라오는 라이브러리에 달려있다.
선택한 MPM 종류가 무엇이든 제대로 설정해야 한다. 일반적으로 MPM 설정은 아파치에게 얼마나 많은 worker가 동작할지 제어하는 방법과 스레드나 프로세스 모델을 선택하는 기준을 알려준다. prefork MPM을 위한 중요한 환경 설정 옵션을 Listing 1에 제시한다.
Listing 1. prefork MPM을 위한 환경 설정
StartServers 50 MinSpareServers 15 MaxSpareServers 30 MaxClients 225 MaxRequestsPerChild 4000 |
prefork 모형에서 새로운 프로세스는 요청 단위로 생성된다. 여분의 프로세스는 들어오는 요청을 처리하기 위해 쉬고 있으며, 이는 초기 시동 대기 시간을 줄여준다. 직전에 보여준 환경 설정 항목에 따르면 웹 서버가 시동하면서 프로세스 50개를 시작하며, 쉬고 있는 서버가 10개와 20개 사이를 유지하도록 시도한다. 프로세스 최대 한계 수치는
MaxClients
로 지정한다. 프로세스가 연속적인 요청을 처리할 수 있음에도 불구하고, 아파치는 접속이 4000개가 넘어간 다음에 프로세스를 죽여 메모리 누수 위험을 방지한다.스레드 MPM 설정은 비슷하지만 사용하는 스레드와 프로세스 개수를 결정해야만 한다는 점이 다르다. 아파치 문서는 모든 매개변수와 필요한 계산 방법을 설명한다.
사용할 값을 선택하는 동안 시행 착오가 필요하다. 가장 중요한 값은
MaxClients
다. 목표는 충분한 작업 프로세스나 스레드가 과도하게 서버 스왑 현상을 일으키지 않으면서 동작하는 데 있다. 처리할 수 있는 용량보다 요청이 많이 들어오면, 최소한 들어온 요청까지는 서비스를 해줘야 하며, 나머지는 기다리도록 만든다.MaxClients
가 너무 높으면 모든 클라이언트가 형편 없는 서비스를 경험하게 된다. 웹 서버가 프로세스 하나를 스왑 아웃하고 다른 프로세스를 돌려야 하기 때문이다. 설정 값을 너무 낮춰잡으면 불필요하게 서비스를 거부할지도 모른다. 높은 부하에서 동작하는 프로세스 수와 아파치 프로세스가 사용하는 메모리 점유 상황을 보여주는 결과는 이 값을 설정할 때 힌트를 준다. MaxClients
가 256을 넘어갈 경우 ServerLimit
를 동일한 숫자로 설정해야 한다. 이와 관련한 경고를 설명하는 MPM 문서를 주의 깊게 읽어보자.시작하고 여분으로 남겨둘 서버 수 조율은 서버쪽 역할에 의존한다. 서버가 아파치만 돌릴 경우 Listing 1에 보여준 적당한 값을 사용할 수 있다. 서버를 완전히 활용할 수 있기 때문이다. 시스템이 데이터베이스나 다른 서버를 공유한다면 동작할 서버 여분 숫자를 줄여야 한다.
옵션 활용과 효과적인 중복 지정
아파치가 처리하는 각 요청은 웹 서버가 반드시 따라야 할 제약이나 특별한 명령을 지정하기 위한 복잡한 설정 규칙을 거쳐야 한다. 폴더 접근은 특정 폴더에 대한 IP 주소로 제약을 가하거나 사용자와 암호로 설정할 수 있다. 또한 이런 옵션은 디렉터리 목록을 제공한다면, 특정 파일 유형 처리 방식이나 출력 결과 압축 같은 특정 파일 처리도 포함한다.
이와 같은 환경 설정은 httpd.conf에 디스크 위치를 참조하도록 설정을 명세하는 <Directory>나 참조 값이 URL에서 경로를 나타내는 <Location> 같은 컨테이너 형태를 따른다. Listing 2는 Directory 컨테이너를 예로 든다.
Listing 2. 루트 디렉터리에 적용한 디렉터리 컨테이너
<Directory /> AllowOverride None Options FollowSymLinks </Directory> |
Listing 2에서,
Directory
와 /Directory
태그로 둘러쌓인 환경 설정은 특정 디렉터리와 이 디렉터리 하부에 적용된다. Listing 2의 경우에는 루트 디렉터리가 된다. 여기서 AllowOverride
태그는 사용자가 다른 옵션을 중복 지정하지 못하게 만든다(나중에 설명한다). FollowSymLinks
옵션을 활성화하면, 웹 파일을 포함하는 디렉터리 외부에 파일이 존재할지라도 아파치가 요청을 처리하기 위해 심볼릭 링크를 따라가도록 만든다. 이는 웹 디렉터리에 있는 파일이 /etc/passwd를 가리키는 심볼릭 링크일지라도 웹 서버가 기꺼히 요청한 파일을 제공하리라는 사실을 의미한다. 대신 -FollowSymLinks
를 사용하면, 이 기능은 비활성화되며, 동일한 요청은 클라이언트에 오류로 반환된다.마지막 시나리오는 두 가지 사항을 고려하도록 만든다. 먼저 성능 문제다.
FollowSymLinks
를 비활성화하면, 아파치는 심볼릭 링크가 아님을 확인하기 위해 파일 이름에 속한 각 컴포넌트(디렉터리와 파일 자체)를 점검해야만 한다. 이는 디스크 활동량을 높이므로 부하가 추가로 발생한다. 관련 옵션인 FollowSymLinksIfOwnerMatch
는 파일 소유주가 링크 소유주일 경우에 심볼릭 링크를 따라가도록 만든다. 이는 symlinks를 따라가지 못하도록 막는 경우와 비슷한 성능 저하가 일어난다. 최고 성능을 위해서는 Listing 2에 나온 옵션을 사용한다.보안을 염두에 두는 독자에게 지금 경종을 울리겠다. 보안은 항상 기능과 위험 사이에서 절충을 벌여야 한다. 이 경우에는 속력이 우선이므로 시스템에 존재하는 파일에 대해 무조건 접근을 허용하도록 위험을 감수한다. 한가지 다행인 점은 LAMP 응용 프로그램 서버는 일반적으로 특수 목적으로 운영되며, 사용자는 잠재적으로 위험한 심볼릭 링크를 걸지 못한다는 것이다. 심볼릭 링크 점검을 활성화해야 한다면, Listing 3에서 보여주듯 특정 파일 시스템 영역에만 적용한다.
Listing 3. 사용자 디렉터리로
FollowSymLinks
를 제한하기<Directory /> Options FollowSymLinks </Directory> <Directory /home/*/public_html> Options -FollowSymLinks </Directory> |
Listing 3에서, 사용자 홈 디렉터리에 있는 public_html 디렉터리는 자신과 하위 디렉터리에 대해
FollowSymLinks
옵션을 제거한 상태가 된다.지금까지 살펴보았듯이, 옵션은 주 서버 환경 설정을 통해 디렉터리 단위로 설정할 수 있다. (
AllowOverrides
로 관리자가 허락했다면) 사용자는 .htaccess라는 파일을 디렉터리에 놓아두는 방법으로 이런 서버 환경 설정 자체를 중복 지정할 수 있다. 시스템에 사용자가 없다는 초기 설명에도 불구하고, 많은 LAMP 응용 프로그램은 이런 기능을 활용해 접근 통제와 URL 다시쓰기를 구현하므로 동작 원리를 이해하는 편이 좋겠다.AllowOverrides
구문이 원하지 않는 모든 작업을 막아버릴지라도, 아파치는 여전히 해야 할 일이 남아있는지 확인하기 위해 .htaccess 파일을 살펴봐야 한다. 어버이 디렉터리는 자식 디렉터리에서 오는 요청을 처리하도록 지시자를 명세할 수 있는데, 이렇게 하기 위해서는 아파치가 요청 파일 앞에 나오는 디렉터리 구조를 구성하는 각 요소를 살펴봐야만 한다. 이렇게 되면 당연히 각 요청마다 디스크 활동량이 상당히 늘어난다.어떤 중복 지정도 허용하지 않는 가장 손쉬운 방법으로 아파치가 .htaccess 파일 점검을 하지 않도록 만들면 된다. 특별한 환경 설정은 httpd.conf에 직접 지정한다. Listing 4는 .htaccess 파일을 만들어넣고
AllowOverrides
에 의존하는 대신에 사용자 프로젝트 디렉터리를 암호로 보호하도록 httpd.conf에 추가한 내용을 보여준다.Listing 4. .htaccess 환경 설정을 httpd.conf로 옮기기
<Directory /home/user/public_html/project/> AuthUserFile /home/user/.htpasswd AuthName "uber secret project" AuthType basic Require valid-user </Directory> |
환경 설정을 httpd.conf로 옮기고
AllowOverrides
를 비활성화하면 디스크 활동량이 줄어든다. 사용자 프로젝트만으로는 그다지 흥미를 끌지 못할지도 모르지만, 바쁜 사이트에 적용할 때 이런 기법이 얼마나 강력한지 생각해보기 바란다.종종 .htaccess 파일을 제거하지 못하는 경우도 있다. 예를 들어 Listing 5를 보면 특정 파일 시스템에 제약을 두는 옵션이 있을 때, 중복 지정 옵션 자체도 제약을 가할 수 있다.
Listing 5. .htaccess 점검 범위를 줄이기
<Directory /> AllowOverrides None </Directory> <Directory /home/*/public_html> AllowOverrides AuthConfig </Directory> |
Listing 5를 구현한 다음에, 아파치는 여전히 어버이 디렉터리에서 .htaccess 파일을 찾지만, public_html 디렉터리에서 멈춘다. 나머지 파일 시스템은 기능적으로 비활성화 상태가 되기 때문이다. 예를 들어, /home/user/public_html/project/notes.html에 사상된 파일을 요청할 경우, 단지 public_html과 project 디렉터리만 탐색한다.
마지막 주의 사항으로 디렉터리 단위 환경 설정에는 순서가 있다. 아파치 조율에 대한 문서는
HostnameLookups off
지시자를 통해 DNS 탐색을 비활성으로 만들어라고 조언한다. 서버에 연결한 각 IP 주소를 역으로 도메인 이름으로 바꾸는 시도는 자원 낭비다. 하지만 호스트 이름에 기반을 둔 제약을 통해 웹 서버가 클라이언트 IP 주소를 거꾸로 찾아내고 이름 진위를 파악하기 위한 결과를 찾아내도록 만든다. 따라서 클라이언트 호스트 이름을 기반으로 하는 접근 제어는 피하되 필요할 때만 기술하도록 영역을 줄이는 방식이 바람직하다.지속적인 접속
클라이언트가 웹 서버에 접속할 때, 동일한 TCP 연결을 통한 다중 요청을 허용하면 다중 연결에 따른 접속 지연을 줄인다. 이런 방식은 웹 페이지가 여러 이미지를 참조할 경우 유용하다. 클라이언트는 페이지를 요청해 연결 하나로 모든 이미지를 받을 수 있다. 단점으로 서버 쪽 작업 프로세서가 다음 요청으로 옮겨가기 전에 클라이언트가 닫은 세션을 기다려야 한다.
아파치는 지속적인 접속 설정을 다루는 방법인 keepalives 값을 바꾸도록 허용한다. httpd.conf에서
KeepAlive 5
를 전역으로 설정해 놓으면, 연결을 강제로 끊기 전에 서버가 연결 하나에 요청 다섯 개를 처리하도록 허용한다. 이 값을 0으로 만들면 지속적인 접속 사용을 비활성화한다. 또한 KeepAliveTimeout
을 전역으로 설정해 놓으면 아파치가 세션을 닫기 전에 다른 요청을 얼마나 오랫동안 기다릴지 설정한다.지속적인 접속 처리는 만능이 아니다. keepalives를 비활성(
KeepAlive 0
)으로 만들어 놓으면 좋은 사이트도 있고, 활성으로 만들어 놓는 편이 상당한 효과를 발휘하는 사이트도 있다. 유일한 해법을 찾으려면 둘 다 시도해 직접 실험해보면 된다. keepalives를 활성화할 경우 KeepAliveTimeout 2
를 지정해 2초 정도로 낮은 타임아웃을 사용하는 편을 권장한다. 이렇게 하면 연속으로 요청을 만들어내기를 바라는 클라이언트에 충분한 시간을 제공하면서도 작업 프로세스가 결코 오지 않을 다른 요청을 기다리느라 시간을 낭비하지도 않게 만든다.압축
웹 서버는 클라이언트에게 자료를 반환하기 전에 결과물을 압축할 수 있다. 이렇게 하면 웹 서버 CPU 사이클을 사용해 인터넷으로 전송하는 페이지를 좀더 작게 만들 수 있다. CPU 부하를 견딜만한 서버라면 이는 페이지를 훨씬 더 빨리 내려받도록 만드는 훌륭한 방법이다. 압축 후에 페이지 크기가 1/3로 줄어드는 경우도 흔하다.
이미지는 일반적으로 이미 압축되어 있으므로 압축은 텍스트 출력으로 제한해야 한다. 아파치는
mod_deflate
를 통해 압축을 지원한다. mod_deflate
활성화 자체는 쉬울지 모르겠지만, 상당히 복잡한 기능을 제공하므로 매뉴얼을 살펴서 설명을 읽어봐야 한다. 이 기사는 적절한 문서 링크(참고자료 절 참조)를 제외한 나머지 압축 환경 설정은 다루지 않는다.PHP 조율
PHP는 응용 프로그램 코드를 돌리는 엔진이다. 사용할 계획이 있는 모듈만 설치해야 하며, 정적 파일이 아니라 (일반적으로 .php로 끝나는 파일인) 스크립트 파일에 대해서만 PHP를 사용하도록 웹 서버 환경 설정을 바꿔야 한다.
중간 코드 캐싱
PHP 스크립트를 요청할 때, PHP는 스크립트를 읽어 실행하기 위한 코드의 이진 표현인 젠드 중간 코드로 컴파일한다. 이 중간 코드는 PHP 엔진이 수행한 다음에 버린다. 중간 코드 캐시는 컴파일된 중간 코드를 저장해 다음 번에 페이지를 호출할 때 재사용한다. 이런 캐시는 시간을 상당히 절약해준다. 여러 중간 코드 캐시가 존재하는데, 나는 eAccelerator로 재미를 봤다.
eAccelerator를 설치하려면 컴퓨터에 PHP 개발 라이브러리가 필요하다. 리눅스 배포판마다 다른 위치에 파일을 두므로, eAccelerator 웹 사이트에서 직접 설치 명령을 참조하는 편이 최선이다(참고자료 절에 있는 링크를 참조하자). 또한 배포판이 이미 패키지로 묶인 중간 코드 캐시를 탑재하고 있다면, 설치만 하면 끝난다.
eAccerlerator를 어떻게 가져왔거나에 상관없이 몇 가지 살펴볼 환경 설정 옵션이 있다. 이 환경 설정 파일은 /etc/php.d/eaccelerator.ini다.
eaccelerator.shm_size
는 컴파일된 스크립트를 저장하는 공간인 공유 메모리 캐시 크기를 정의한다. 이 값은 메가바이트 단위다. 적절한 크기는 응용 프로그램에 따라 다르다. eAccelerator는 메모리 사용을 포함하여 캐시 상태를 보여주는 스크립트를 제공한다. 64메가바이트면(eaccelerator.shm_size="64"
) 적당한 시작값이다. 또한 여러분이 선택한 값을 받아들이지 않을 경우 커널의 최대 공유 메모리 크기를 조정할 필요가 있다. kernel.shmmax=67108864
항목을 /etc/sysctl.conf에 넣고 sysctl -p
명령을 내리면 설정값이 반영된다. kernel.shmmax
값은 바이트 단위다.공유 메모리 할당을 초과하면 eAccelerator는 메모리에서 옛날 스크립트를 제거한다. 기본적으로 이런 동작은 비활성으로 남아있다.
eaccelerator.shm_ttl = "60"
을 지정하면 eAccelerator가 공유 메모리 부족을 감지했을 때 60초 동안 접근하지 않은 스크립트를 제거한다.eAccelerator를 대체할 다른 대안은 Alternative PHP Cache(APC)다. 젠드 제작사는 효율성을 높이기 위한 최적화기를 포함한 상용 중간 코드 캐시를 판매한다.
php.ini
php.ini에서 PHP 환경을 설정한다. 표 1에 정리한 네 가지 중요한 설정 값은 PHP가 얼마나 시스템 자원을 소비할지 통제한다.
표 1. php.ini에서 자원 관련 설정값
설정 | 설명 | 권장값 |
---|---|---|
max_execution_time | 얼마나 많은 CPU-초를 스크립트가 소비하는지를 지정 | 30 |
max_input_time | 얼마나 오랫동안(초) 스크립트가 입력 자료를 기다릴지를 지정 | 60 |
memory_limit | 얼마나 많은 메모리를(바이트) 죽기 전에 스크립트가 소비할지를 지정 | 32M |
output_buffering | 얼마나 많은 자료를(바이트) 클라이언트에게 전달하기 전에 버퍼에 저장할지를 지정 | 4096 |
max_input_time
을 증가시켜야 한다. 비슷하게 CPU나 메모리를 많이 소비하는 프로그램은 더 큰 설정 값을 지정한다. 목적은 폭주한 프로그램을 막아내는 데 있으므로 전역 설정 값을 비활성화하는 상황은 바람직하지 못하다. max_execution_time
에 대한 설명을 추가하겠다. 이 설정값은 절대 시간이 아니라 프로세스의 CPU 시간을 참조한다. 따라서 I/O 작업이 많고 계산이 적은 프로그램일 경우 max_execution_time
보다 더 오래 동작할지도 모른다. 이는 한 max_input_time
이 max_execution_time
보다 큰 경우를 설명한다.PHP로 로그 파일을 남기는 양도 조정이 가능하다. 실제 운영 환경에서는 가장 중요한 로그 파일을 제외한 나머지 로그를 끄면 디스크 쓰기를 줄인다. 문제 해결용으로 로그가 필요하면 원하는 만큼 로그 단계를 높일 수 있다.
error_reporting = E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR
는 문제를 추적할 만큼 충분한 정보를 로그에 남기지만, 스크립트에서 나오는 수다스러운 내용은 제거한다.요약
이번 기사는 아파치와 PHP라는 웹 서버 조율에 초점을 맞춘다. 아파치의 경우에는 .htaccess 파일 처리와 같은 웹 서버가 반드시 실행해야 하는 추가적인 점검 과정을 건너뛰도록 만든다. 또한 MPM을 조율해 작업 요청을 받아주는 일꾼 숫자와 시스템 자원 사이에 균형을 잡아줘야 한다. PHP로 할 수 있는 최선의 방법은 중간 코드 캐시 설치다. 모든 사람을 위해 스크립트가 시스템을 느리게 만들고 자원을 잡고 있지 않도록 만들기 위해 몇 가지 자원 설정에 촉각을 곤두세워야 한다.
다음에 소개할 연재 기사 마지막 회에서는 MySQL 데이터베이스 조율 기법을 살펴본다. 계속해서 기대하시라!
참고자료
교육
- "Quantify performance changes using application tracing" (developerWorks, 2006년 7월)은 아파치에서 환경 설정 변경 영향을 응용 프로그램이 추적하는 방법을 보여준다.
- "Using the new memory manager" (developerWorks, 2007년 3월)는 PHP 5.2에서 메모리를 다루는 최신 변경 내역을 소개한다. PHP는 지속적으로 시스템 자원 활용을 다듬는다.
- mod_deflate는 아파치 모듈로 동작 중에 결과를 압축한다. 또한 PHP에서 출력 압축 기능을 사용해도 비슷한 결과를 얻는다.
- 자바스크립트 코드와 같은 압축된 정적 파일을 미리 캐시해놓자. CSS는 성능 개선을 위한 또 다른 방안이다. Compressing and concatenating all your JavaScript code and CSS는 이 내용을 더욱 자세히 다룬다.
- MPM(Multi-Processing Modules)을 다루는 아파치 문서는 각 기능을 익히는 데 도움을 준다. 링크를 따라가서 MPM에 대한 구체적인 문서를 찾아보자.
- developerWorks 리눅스 영역에서 리눅스 개발자를 위한 자료를 더 찾아보기 바란다.
- developerWorks 기술 행사와 웹 캐스트를 놓치지 말기 바란다.
- 배포판이 eAccelerator를 포함하지 않는다면 Install From Source 사용설명서가 도움이 될 것이다.
- eAccelerator에 대한 대안으로 Alternative PHP 캐시와 젠드 플랫폼이 있다.
- Siege는 사용자를 흉내내도록 만들어주며, 사이트가 얼마나 많은 트래픽을 버틸지 알려준다.
- 조만간 다중 웹 서버 사이에서 부하를 분산하고 사이트에서 특정 요소를 캐시하기를 원할 것이다. (역 프록시로 알려진) 가속 모드에서 Squid와 리눅스 가상 서버 프로젝트는 훌륭한 도구다.
- IBM 평가판 소프트웨어: developerWorks에서 직접 내려 받아 다음번 리눅스 프로젝트에 활용하자.
블로그, 포럼, 포드캐스트, 새로운 developerWorks space에 있는 새로운 공동체 토픽을 통해 developerWorks 공동체에 참여한다.
댓글 없음:
댓글 쓰기