Rasmus 아저씨의 "Simple is Hard"라는 PT 중 PHP 성능에 관한 내용 일부를 소개해볼까 한다. 이 아저씨의 Simple is Hard라는 PT가 좀 여러 버전이 있는데, 2008년 DrupalCon 발표자료를 참고했다. 예전에  소개했던 자료랑 거의 비슷하긴 한데 좀 추가된 내용이 있어서 재탕을 해볼까 한다.

그나저나, 참 뭐 하나 만들고 운영하고 하는데 신경 쓸 일이 한두 가지가 아니다. 설계도 중요하고, 보안도 생각해야되고, 성능도 빼놓을 수 없고 ... 잘 한답시고 주구장창 붙잡고 있어도 안 된다. 인생은 타이밍이니까. 그런데 요즘 HipHop도 공개되고 그러는 분위기니까 '성능'이란 주제에 대해 좀 더 생각해보는 시간을 갖도록 하자. 안그래도 구글이 빠른 웹사이트에 랭킹 보너스를 줄지도 모른다는 얘기도 있고. 빠른 웹사이트가 빠르면 좋은거다.

오늘은 잡설은 짧게 끝내고 본론 바로 갑니다. 이 PT는 Scalability, Performance, Security 이렇게 세 부분으로 나뉜다. Scalability는 한국말로 옮기기가 참 애매한데, 확장성이라고 주로 옮기기는 하는 모양이지만 꼭 그 뜻은 아니고, 간혹 가용성 이렇게 옮기기도 하는데 꼭 그 뜻도 아닌 것 같다. 아.. 이것도 본론은 아닌데. 다음 문단부터 본론 나갑니다.

이 자료에서 Rasmus 아저씨는 "PHP가 성능상의 병목이 되는 경우는 흔치 않지만, 몇 가지 도구를 써서 성능을 향상하는 기법을 소개합니다.

8페이지부터 시작합니다. 우선 Laconica, Habari, Wordpress, Magento를 차례대로 Siege로 테스트합니다. 표 안의 숫자는 높을수록 빠른거에요. 1초에 몇 번의 트랜잭션(즉 페이지뷰)을 처리하는지. (Siege는 Apache ab랑 비슷한 벤치마킹 툴)

normal with APC enabled 
Laconica - PHP 5.2.7-dev 18.71   45.37
Laconica - PHP 5.3.0-dev 20.58   46.14
Habari - PHP 5.2.10-dev 11.41   26.69
Wordpress 7.27  33.08
Magento 2.33 4.10

일단 APC를 안 쓸 이유가 없다는 걸 알 수가 있다. APC는 eAccelerator같은 OP Code Cache의 하나다. Rasmus 본인이 개발한거고. 보통은 eAccelerator를 많이들 쓰던데 특별한 이유는 모르겠다. 내 경우 APC나 eAccelerator에서 문제가 있던 부분을 xCache(lighttpd 개발자가 만든 OPCode cacher)를 써서 해결본 경험이 있기는 하다. 자세하게 분석하거나 벤치마킹 해본 건 아니니 그냥 참고하시라고.

이어서 10페이지부터는 System call overhead를 줄여서 성능상의 이득을 얻는 과정이 나온다. 이를테면 Apache httpd.conf에 DirectoryIndex index.php를 명시해줘서 index.html index.cgi index.pl index.php를 차례대로 찾는 수고를 줄이는 것 만으로도 시스템콜을 절약할 수 있다는 얘기다. 개발자로서는 놓치기 쉬운 부분 되겠다.

다음은 include_path다.

;include_path = ".:/usr/local/lib/php"
include_path = "/usr/local/lib/php:."

현재 디렉토리(.)를 먼저 찾은 다음에 /usr/local/lib/php를 찾도록 되어있던 걸 순서만 바꿔준건데 이것만으로 시스템콜이 많이 줄어든 걸 볼 수 있다.

수정하기전:
close(10)                               = 0

getcwd("/var/www/laconica", 4096)       = 18
time(NULL)                              = 1216449985
lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/laconica", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/laconica/PEAR.php", 0xbffcf5fc) = -1 ENOENT (No such file or directory)
open("/var/www/laconica/PEAR.php", O_RDONLY) = -1 ENOENT (No such file or directory)
time(NULL)                              = 1216449985
open("/usr/local/lib/php/PEAR.php", O_RDONLY) = 10
fstat64(10, {st_mode=S_IFREG|0644, st_size=34813, ...}) = 0
stat64("./PEAR.php", 0xbffd1654)        = -1 ENOENT (No such file or directory)
stat64("/usr/local/lib/php/PEAR.php", {st_mode=S_IFREG|0644, st_size=34813, ...}) = 0
close(10)                               = 0

수정한다음:
close(10)                               = 0
time(NULL)                              = 1216450700
open("/usr/local/lib/php/PEAR.php", O_RDONLY) = 10
fstat64(10, {st_mode=S_IFREG|0644, st_size=34813, ...}) = 0
stat64("/usr/local/lib/php/PEAR.php", {st_mode=S_IFREG|0644, st_size=34813, ...}) = 0
close(10)                               = 0

각각이 뭐하는건지 자세히는 모르더라도, 여러 디렉토리에서 파일이 있나없나 찾아보는데 시간을 많이 썼었다는 걸 짐작해볼 수 있다. 이렇게 고치고 나니 45.37 trans/sec 나오던 것이 46.63 trans/sec으로 빨라졌다. 2.7% 빨라진건데, 큰 수치가 아니라 생각할지도 모르지만 소스코드 한 줄 안 고치고 이정도면 대단한거다. 시스템이 크고 바쁠수록 더.

그런데 혹시나 해서 부연 설명하는건데, 무조건 저렇게 고친다고 빨라지는게 아니라, 로그를 남기고 그걸 분석해서 뭘 할지 정해야된다. "아, include_path에 .를 무조건 마지막에 넣는구나."와 같은 결론을 내리면 곤란하다.

다음은 apc.stat값을 0으로 바꾸는 것이 어떤 영향을 미치는지를 보여주고 있다. apc.stst은 APC OP cache의 설정값 중 하나인데 소스코드가 거의 바뀌지 않는 상황(예를들어 실제 운영중인 서버)에서 유용하게 쓸 수 있다. 이 값은 기본이 켜진(1) 상태인데, 이 상태에서는 APC가 혹시 소스코드에 바뀐게 있나 계속 검사를 한다. 그런데 이걸 꺼버리면(apc.stat=0) 웹서버를 재시작하거나 강제로 캐시를 비우지 않는 한 계속 캐시된 OP Code를 쓴다. 메뉴얼에 다 나온 내용이니 RTFM하시기 바란다. 이런류의 설정은 굉장히 유용하긴 한데 정확한 의미를 모르고 쓰거나, apc.stat=0으로 해놓고 까먹거나 하게되면 "왜 고쳤는데 반영이 안 되는거야!?" 따위의 어이없는 혼선을 빚을 수 있다.

왼쪽이 apc.stat 고치기 전, 오른쪽이 후.


왼쪽에는 있는 것들 중에 오른쪽에는 안보이는 것들이 좀 있다. 그만큼은 절약이 된거라고 보면 되겠다. 벤치마크 결과를 확인해보면 46.15 trans/sec. 큰 의미는 없는 수치로 보인다. 하지만 로그에서 보았듯이 실제로는 절약되고 있기 때문에 아주 바쁜 상황에서라면 고려해볼 수 있는 방법이다.

14, 15장에서는 include hierarchy를 고쳐 성능상의 이득을 보는 방법을 설명한다. include 계층이 어떻게 되는지 조사하는데 PECL/Inclued(스펠링 주의. 오타아님)를 쓴다. 쪼고만 모니터에서 비교해보느라 눈 빠지는 줄 알았다.

위가 시술전 아래가 시술후다. 이 아저씨가 뭘 어떻게 고쳤는가 모르겠다만, 내가 이해한 건 이렇다. 시술전에는 common.php가 DataObject.php를 require_once함에도 불구하고, common.php가 불러들이는 다른 여러 파일들이 DataObject.php를 또 불러댄다. 즉 점선으로 표시된 의존관계 중 어떤 것들은 군더더기라는 뜻이다. 이런식으로 몇 군데를 고친 것 같은데 다들 숨은그림 찾듯이 찾아보시고 저도 좀 알려주세요. 30인치 모니터가 있으면 저도 볼 수 있는데....

중요한게, 그래서 얼마나 효과를 봤느냐? 46.15 에서 49.84로 뛰었다. 1%에 조금 못 미치지만 괜찮지 않습니까?

지금까지는 주로 설정이나 순서 이런걸 바꿔서 효과를 보는, 상대적으로 쉬운 방법들이었다. 이번에는 프로파일링을 해 본다. 프로파일링 툴로 valgrind나 xdebug를 쓴다. 프로파일링 결과를 여기서 보는것처럼 그래프로 그려서 볼 수도 있는데, 꼭 그래프로 안 봐도 되기는 하지만 이렇게 보면 멋있기는 하다. 암튼 결과를 보고 어느 함수가 자원을 많이 잡아먹는지를 확인할 수 있다. 확인했는게 그 루틴이 너무 중요한거면 어쩔 수 없지만 만약 대안이 있다면 .....

첫 번째 사례로 Remove some XMLWriter code라는걸 소개했는데, 아마 꼭 필요하지 않은 코드였거나 좀 더 가벼운 (자원 덜 먹는) 코드로 대체했다는 뜻인 것 같다. 특히 XML하고 관련된 것들이 메모리나 자원을 엄청 잡아먹는 경향이 있는데 간단한 XML이라면 DOM이나 OO스타일의 라이브러리 안 쓰고 그냥 문자열로 만들어도 된다는게 내 생각이다.

두 번째 케이스에서는 PHP가 시스템의 기본 Timezone을 쓰느라 헛짓하는 걸 간단하게 php.ini파일에 date.timezone 설정을 추가해서 정리해준다. 이렇게만 해줘도 160 trans/sec정도였던 것이 180으로 향상되었다.

다음이 진짜 재밌는데, 내가 이걸 보면서 "앗 정리해서 포스팅해봐야겠다"고 생각했었다. 간단하게 "Hello World"만 출력하는 루틴의 성능을 비교해보는건데 Plain HTML부터 CakePHP, CodeIgniter, Zend Framework 등 널리 쓰이는 프레임웍에다 .. 두둥 .. Drupal 6.4도 같이 비교를 해놔서 아주 재밌었다. (DrupalCon에서 발표한거니까 ㅋㅋ) 이 페이지부터 차례로 넘겨보면 되겠다. 행여나 이런 단순 벤치마크를 가지고 확대해석하는 분들은 없었으면 하는게 나의 작은 소망이자 바램이다.

 Method  transactions / sec
 Plain HTML 611.78 
 Trivial PHP 606.77 
 CakePHP 1.2.0.rc2 25.88 
 Symfony 1.1 100.63 
 Solar 1.0.0alpha1 271.18 
 Agavi 1.0-beta1 (production) 126.91
 Zend Framework 1.6.0-rc1(with include_path tweak) 130.08 
 CodeIgniter 1.6.3 305.90 
 Prado 3.1.2 76.95 
 Drupal 6.4 51.37 

참 가지각색이죠? 2년차 드루팔러로서 변호를 좀 해보자면, 드루팔은 다른 MVC 웹프레임웍하곤 좀 다르죠. 그것들보다는 좀 더 상위에 있다고 보는게 맞고, 아무리 Hello World만 출력하는 모듈을 짜넣었다고 해도 기본적으로 권한이라던가 캐시라던가 DB연결 이런게 다 돌아가고 있다는겁니다. 그러니까 단순비교는 무리.

자 지금까지 Simple is Hard라는 라스무스 아저씨의 발표자료를 살펴봤다. 직접 발표장에 간 것도 아니고 동영상을 본 것도 아니라 내가 잘못 이해한게 있을지도 모르겠지만 손가락을 보지 말고 달을 보는 지혜를 발휘해보도록 하자.

저작자 표시
신고