뭘 이런걸..

Posted
Filed under Tech/안녕리눅스
이번에는 PHP 성능을 높일 수 있는 방법 2가지에 대해서 기술해 봅니다. 아래의 내용은 안녕 리눅스의 PHP package를 빌드할 때 성능을 높이기 위하여 한 작업을 기술 하는 것입니다.

먼저 PHP VM type을 변경하여 성능을 높이는 방법에 대해서 알아 보겠습니다.

PHP 5 까지 configure 옵션에 보면 --with-zend-vm 이라는 옵션이 있습니다. (7.0 부터는 없어졌습니다.) configure --help를 해 보면 아래 와 같이 설명이 나옵니다.

Zend: 
--with-zend-vm=TYPE Set virtual machine dispatch method. Type is
one of "CALL", "SWITCH" or "GOTO" TYPE=CALL


즉, zend vm type에는 "CALL", "SWITCH", "GOTO" 모드가 있고, 기본값은 "CALL" 이라는 내용입니다. 그리고 이 옵션으로 zend vm type을 변경할 수 있다고 되어 있습니다.

하지만, 실제로 configure 시에 이 옵션은 동작하지 않습니다. 그래서 7.0 부터는 제거가 되었습니다.
configure 얘기를 한 이유는, 검색을 하다 보면, 이 옵션을 이용하여 VM type을 변경할 수 있다는 글들이 나오기 때문입니다. configure 옵션으로는 VM type 변경이 되지 않기 때문에 언급을 한 것입니다. 7.0 부터 제거가 되었다는 것은 configure 옵션이 실제 동작을 하지 않기 때문에 제거가 되었다는 의미이지 7.0에서는 여기서 설명하는 것이 적용이 안된다는 의미가 아닙니다. 7.0 이후에서도 동일하게 성능을 높일 수 있습니다.

그럼, zend vm type이 뭔지, 그리고 어떻게 하면 변경할 수 있는지에 대해서 설명을 합니다. (빌드 후에 변경은 불가하고, 빌드 시에 선택을 해야 합니다.)

일단, zend vm에 대한 기본 내용은

https://github.com/php/php-src/blob/master/Zend/README.ZEND_VM

에서 확인하실 수 있습니다. 간략하게 설명을 하면, opcode optimize 방법이라고 보시면 됩니다. (설명 파일에는 executor 방법이라고 나오는데, 표현하기 쉽게 최적화라고 했습니다.) 일단 성능은 다음과 같습니다.

GOTO > SWITCH > CALL

일단,  GOTO를 기준으로 하면, php source code 안에 있는 bench.php를 기준으로 CALL보다 20% 정도의 성능 향상이 이루어 집니다.

그러면 왜 기본값이 GOTO가 아니라 가장 성능이 안 좋은 CALL이냐.. 에 대한 건 저도 아직 제대로 된 설명을 못 찾았습니다. (어쩌면 영어 자료만 있어서 그럴 수도 있습니다. ^^;)

다만, 빌드시에 GOTO의 경우에는 memory가 아주 많이 필요하며, 빌드 시간이 CALL로 빌드하는 것 보다 3~4배 정도가 더 걸립니다. build machine 성능에 따라 차이가 더 커질수도 있습니다. (특정 아키텍쳐나 compiler에 따라 case by case로 발생 합니다.) RHEL 5의 i686 시스템에서 php 5.2를 GOTO로 빌드 할 때 memory가 12G 정도를 잡아 먹어서, mameory가 모잘라  swap을 늘려서 빌드 했던 적도 있습니다. 이건 case by case이기 때문에 꼭 나타나는 증상이라고 할 수는 없지만 CALL로 빌드할 때 보다는 많은 메모리가 필요 합니다.

일단, 빌드 방법은 다음과 같습니다. ./configure를 실행하기 전에 php-src/Zend 디렉토리에서 다음의 명령을 실행합니다.

[root@an3 php-7.1.0]$ cd Zend
[root@an3 Zend]$ php zend_vm_gen.php --with-vm-kind=GOTO
[root@an3 Zend]$ cd ..
[root@an3 php-7.1.0]$ ./configure --with-옵션....
[root@an3 php-7.1.0]$ make -j 8 && make install
 

이 명령을 실행하면, zend_vm_execute.h 와 zend_vm_opcodes.h 파일이 새로 생성이 됩니다.

참고:
5.6 에서는 GOTO로 이 파일들을 생성하면, 빌드시에 syntax error가 발생하는 버그가 있습니다. 5.6에서는 zend_vm_execute.h에서 "constant_fetch_end"이 duplicate되었다는 에러가 발생 합니다. zend_vm_execute.h에서 중복된 constant_fetch_end 라벨을 constant_fetch_end1, constant_fetch_end2, constant_fetch_end3 과 같이 중복되지 않게 변경해 주면 됩니다.
zend_vm_gen.php 스크립트를 이용하여 zend_vm_execute.h 와 zend_vm_opcodes.h 파일을 GOTO type을 새로 생성한 후에, 기존의 configure; make; make install 을 이용하여 설치를 진행하면 됩니다.

안녕 리눅스 1/2/3 에 들어있는 PHP는 모두 GOTO 모드로 빌드 되어 있고, 네오위즈에서 사용하는 PHP와 TMON에서 사용하는 PHP도 모두 GOTO로 빌드되어 동작하고 있습니다. 실제 상용 시스템에서 사용하여 검증은 되었으니, 직접 빌드해서 사용하는 분들은 빌드시에 적용을 해 보시면 좋을 겁니다.

OS 선택을 고려해야 하고 CentOS 6/7 을 생각하고 계신 분들이라면 안녕 리눅스를 선택하는 것을 고려해 보심이 ^^; CentOS 6/7 호환이라 전환을 한다고 해서 특별하게 달라질 것은 없습니다.

다음은 realpath_cache_force 기능에 대해서 기술 합니다.

이 기능에 대해서는 안녕 리눅스 3 사용자 가이드의 PHP 문서의 4.7 realpath_cache_force 항목에서 자세히 설명을 하고 있습니다.

위의 링크의 내용을 간략히 말하자면, PHP 파일에서 realpath_cache 라는 기능이 있습니다. 이 기능은 PHP가 파일 시스템에 접근을 할 때에 mtime(modify time) 을 체크하여 일정 시간동안 caching을 하여 성능을 높이는 것인데, link()와 symlink() function으로 race condition을 이용해서 open_basedir 을 무력화 시킬 수 있는 보안 버그가 발견이 되어, PHP 개발자들이 open_basedir을 사용할 경우에는 realpath_cache를 사용하지 못하도록 코드를 변경해 버렸습니다. 그래서 open_basedir을 사용할 경우에 접속이 많은 사이트의 경우에는 30% 정도의 성능이 감소되는 문제가 있습니다. (이것 역시 case by case로 접속이 많은 수록 성능의 차이가 많이 나게 됩니다.)

안녕 리눅스 2.0 부터는 realpath_cache_force 라는 옵션을 추가하여, open_basedir을 사용하더라도, 이 옵션이 활성화가 되어 있으면, realpath_cache를 할 수 있도록 해 주며, 문제가 되는 link()와 symlink() 함수들을 사용하지 못하게 하여 문제를 해결하고 성능을 높일 수 있도록 합니다.

이 기능은 TMON에서 주유권 이벤트시에 접속이 너무 몰려서 최적화 작업을 하면서 만든 기능으로 안녕 리눅스 2.0의 PHP에 반영이 되었습니다.

CentOS 6 또는 7 사용자들의 경우에는 CentOS를 안녕 리눅스로 전환을 시키면 이 기능을 사용할 수 있습니다. (물론 PHP 버전이 다르므로 약간의 migration은 필요할 수 있습니다.)


안녕 리눅스 전환을 얘기를 하면 강요같이 받아들이는 분들도 계셔서 안녕 리눅스에 반영된 patch file을 아래에 공유 합니다. 다음은 안녕 리눅스 3의 php 7.1.0에 적용된 패치 파일 입니다. (copy & paste 이기 때문에 tab이 space로 변환이 되어 copy & paste로는 patch가 적용이 안되니 어디를 수정하는지 부분만 참고를 하세요.)

diff -urNp php-7.1.0.org/ext/standard/link.c php-7.1.0/ext/standard/link.c
--- php-7.1.0.org/ext/standard/link.c 2016-12-02 11:07:40.000000000 +0900
+++ php-7.1.0/ext/standard/link.c 2016-12-09 17:28:34.301410867 +0900
@@ -126,6 +126,16 @@ PHP_FUNCTION(symlink)
char dirname[MAXPATHLEN];
size_t len;

+ if (PG(open_basedir) && *PG(open_basedir) && PG(realpath_cache_force)) {
+ php_error_docref(
+ NULL TSRMLS_CC,
+ E_ERROR,
+ "The relapath_cache_force option is enabled. "
+ "For security issue, symlink function is not usable."
+ );
+ RETURN_FALSE;
+ }
+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &topath, &topath_len, &frompath, &frompath_len) == FAILURE) {
return;
}
@@ -182,6 +192,16 @@ PHP_FUNCTION(link)
char source_p[MAXPATHLEN];
char dest_p[MAXPATHLEN];

+ if (PG(open_basedir) && *PG(open_basedir) && PG(realpath_cache_force)) {
+ php_error_docref(
+ NULL TSRMLS_CC,
+ E_ERROR,
+ "The relapath_cache_force option is enabled. "
+ "For security issue, link function is not usable."
+ );
+ RETURN_FALSE;
+ }
+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &topath, &topath_len, &frompath, &frompath_len) == FAILURE) {
return;
}
diff -urNp php-7.1.0.org/main/main.c php-7.1.0/main/main.c
--- php-7.1.0.org/main/main.c 2016-12-09 17:28:25.502936720 +0900
+++ php-7.1.0/main/main.c 2016-12-09 17:28:34.302410921 +0900
@@ -725,6 +725,7 @@ PHP_INI_BEGIN()

STD_PHP_INI_ENTRY("realpath_cache_size", "16K", PHP_INI_SYSTEM, OnUpdateLong, realpath_cache_size_limit, virtual_cwd_globals, cwd_globals)
STD_PHP_INI_ENTRY("realpath_cache_ttl", "120", PHP_INI_SYSTEM, OnUpdateLong, realpath_cache_ttl, virtual_cwd_globals, cwd_globals)
+ STD_PHP_INI_ENTRY("realpath_cache_force", "0", PHP_INI_SYSTEM, OnUpdateLong, realpath_cache_force, php_core_globals, core_globals)

STD_PHP_INI_BOOLEAN("upload_image_check", "0", PHP_INI_SYSTEM, OnUpdateBool, upload_image_check, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("upload_image_check_log", "0", PHP_INI_ALL, OnUpdateBool, upload_image_check_log, php_core_globals, core_globals)
@@ -1779,7 +1780,7 @@ int php_request_startup(void)
}

/* Disable realpath cache if an open_basedir is set */
- if (PG(open_basedir) && *PG(open_basedir)) {
+ if (PG(open_basedir) && *PG(open_basedir) && !PG(realpath_cache_force)) {
CWDG(realpath_cache_size_limit) = 0;
}

@@ -2362,7 +2363,7 @@ int php_module_startup(sapi_module_struc
zend_register_standard_ini_entries();

/* Disable realpath cache if an open_basedir is set */
- if (PG(open_basedir) && *PG(open_basedir)) {
+ if (PG(open_basedir) && *PG(open_basedir) && !PG(realpath_cache_force)) {
CWDG(realpath_cache_size_limit) = 0;
}

diff -urNp php-7.1.0.org/main/php_globals.h php-7.1.0/main/php_globals.h
--- php-7.1.0.org/main/php_globals.h 2016-12-09 17:28:25.503936774 +0900
+++ php-7.1.0/main/php_globals.h 2016-12-09 17:28:34.303410975 +0900
@@ -154,6 +154,7 @@ struct _php_core_globals {
zend_long max_input_nesting_level;
zend_long max_input_vars;
zend_bool in_user_include;
+ zend_bool realpath_cache_force;

zend_bool upload_image_check;
zend_bool upload_image_check_log;
diff -urNp php-7.1.0.org/php.ini-development php-7.1.0/php.ini-development
--- php-7.1.0.org/php.ini-development 2016-12-09 17:22:39.522475756 +0900
+++ php-7.1.0/php.ini-development 2016-12-09 17:28:34.304411028 +0900
@@ -351,6 +351,12 @@ disable_classes =
; http://php.net/realpath-cache-ttl
;realpath_cache_ttl = 120

+; If set on safe_mode or open_basedir, realpth_cache is disable. Set 1 this
+; directive, realpath_cache is enable with realpath_cache_size and realpath_cache_ttl
+; although safe_mode or open_basedir set enable. If you want to enable this variable,
+; We recommand that symlink function is set to disable_functions
+;realpath_cache_force = 0
+
; Enables or disables the circular reference collector.
; http://php.net/zend.enable-gc
zend.enable_gc = On
diff -urNp php-7.1.0.org/php.ini-production php-7.1.0/php.ini-production
--- php-7.1.0.org/php.ini-production 2016-12-09 17:22:39.523475809 +0900
+++ php-7.1.0/php.ini-production 2016-12-09 17:28:34.305411082 +0900
@@ -351,6 +351,12 @@ disable_classes =
; http://php.net/realpath-cache-ttl
;realpath_cache_ttl = 120

+; If set on safe_mode or open_basedir, realpth_cache is disable. Set 1 this
+; directive, realpath_cache is enable with realpath_cache_size and realpath_cache_ttl
+; although safe_mode or open_basedir set enable. If you want to enable this variable,
+; We recommand that symlink function is set to disable_functions
+;realpath_cache_force = 0
+
; Enables or disables the circular reference collector.
; http://php.net/zend.enable-gc
zend.enable_gc = On
2016/12/14 02:29 2016/12/14 02:29