본문 바로가기
APM

서버 메모리 관리에 도움글

by 누피짱 2012. 11. 3.

# cat /proc/meminfo | grep -i swapcached

SwapCached:        213 kB


swap 은 메모리의 부족분을 hdd 로 때우는 겁니다.
이것을 사용한다는 것 자체가 메모리가 충분하지 않다는 거죠.

swap 은 암만 많아봤자 메모리가 가득 차서 swap 을 쓰기 시작하면,
메모리 확보가 빨리 되지 않는 이상 그냥 다운이라 보면 됩니다.

즉, 가끔 저렇게 조회 했을 때 반드시 0 kB 가 되어야 합니다.



# cat /proc/meminfo
MemTotal:        4127052 kB # 전체 메모리
MemFree:          122168 kB # 남은 메모리
Buffers:          105472 kB # 버퍼된 메모리
Cached:          700480 kB # 캐시된 메모리
SwapCached:            0 kB # 스왑에 캐시된 메모리

남은 메모리는 MemFree + Buffers + Cached 입니다.
Buffers 와 Cached 는 알아서 조절되므로 남은메모리 취급해도 됩니다.



1. httpd.conf
<IfModule mpm_prefork_module>
    StartServers        5
    MinSpareServers    5
    MaxSpareServers    10
    MaxClients          128 # 이거 많이 주면 안됩니다.
    MaxRequestsPerChild 10000
</IfModule>

아파치 특성상 처음 start 하면 StartServers 정도밖에 뜨지 않습니다.
그러니 실험을 위해 StartServers 를 100 정도로 늘려서 테스트를 해 보면,
답이 나옵니다. 뒈지면 숫자를 줄여야 하고 곧 그것이 MaxClients 값이 되어야 합니다.
시작은 물론 5죠.
구시대의 아파치 설치문서에 있는 1024 같은걸 적용해도 뜨긴 뜹니다.
당연히 뜨죠.
그런데, 아파치의 특성상 접속이 늘어나면 서버가 하나씩 늘어나는데,
MaxRequestsPerChild 횟수를 채우기 전에는 이게 줄어들지는 않습니다.
당연히 오래되면 메모리를 다 먹고 뻗습니다.


2. php
웬만하면 컴파일 하지말고 바이너리 쓰세요.
컴파일을 한다면 각종 확장을 모조리 shared 옵션을 주고,
php.ini 에서 꼭 쓰는 것만 로딩하세요.
그게 당연히 불편하니까 바이너리 쓰세요.
확장 하나하나가 무시하지 못할 용량입니다.
바이너리는 보통 /etc/php/ext 같은 곳에 ini 파일로 로딩하고 말고를 간단히 조절할 수 있습니다.

-rw-r--r-- 1 root root 1.5M 2009-06-22 00:38 ZendOptimizer.so
-rw-r--r-- 1 root root  37K 2009-09-14 21:56 bcmath.so
-rw-r--r-- 1 root root  24K 2009-09-14 21:56 bz2.so
-rw-r--r-- 1 root root  56K 2009-07-05 03:50 cubrid.so
-rw-r--r-- 1 root root  54K 2009-09-14 21:56 dba.so
-rw-r--r-- 1 root root  37K 2009-09-14 21:56 dbase.so
-rw-r--r-- 1 root root  60K 2009-09-14 21:56 exif.so
-rw-r--r-- 1 root root  41K 2009-07-05 07:22 ffmpeg.so
-rw-r--r-- 1 root root  15K 2009-07-05 03:49 fileinfo.so
-rw-r--r-- 1 root root  58K 2009-09-14 21:56 ftp.so
-rw-r--r-- 1 root root  19K 2009-07-05 03:48 geoip.so
-rw-r--r-- 1 root root  16K 2009-09-14 21:56 gettext.so
-rw-r--r-- 1 root root  49K 2009-09-14 21:56 gmp.so
-rw-r--r-- 1 root root 384K 2009-07-05 03:49 http.so
-rw-r--r-- 1 root root 427K 2009-09-14 22:27 imagick.so
-rw-r--r-- 1 root root 2.0M 2009-09-14 21:56 mbstring.so
-rw-r--r-- 1 root root  40K 2009-09-14 21:56 mcrypt.so
-rw-r--r-- 1 root root  11K 2009-09-14 21:56 mhash.so
-rw-r--r-- 1 root root  58K 2009-09-14 21:56 mssql.so
-rw-r--r-- 1 root root  54K 2009-09-14 21:56 mysql.so
-rw-r--r-- 1 root root 125K 2009-09-14 21:56 mysqli.so
-rw-r--r-- 1 root root  85K 2009-09-14 21:56 ncurses.so
-rw-r--r-- 1 root root 130K 2009-09-14 21:56 oci8.so
-rw-r--r-- 1 root root  23K 2009-09-14 21:56 pcntl.so
-rw-r--r-- 1 root root  19K 2009-09-14 21:56 pdo_dblib.so
-rw-r--r-- 1 root root  32K 2009-09-14 21:56 pdo_mysql.so
-rw-r--r-- 1 root root  31K 2009-09-14 21:56 pdo_oci.so
-rw-r--r-- 1 root root  32K 2009-09-14 21:56 pdo_pgsql.so
-rw-r--r-- 1 root root  28K 2009-09-14 21:56 pdo_sqlite.so
-rw-r--r-- 1 root root 112K 2009-09-14 21:56 pgsql.so
-rw-r--r-- 1 root root 323K 2009-09-14 21:56 soap.so
-rw-r--r-- 1 root root  40K 2009-09-14 21:56 sockets.so
-rw-r--r-- 1 root root  16K 2009-09-14 21:56 sysvmsg.so
-rw-r--r-- 1 root root  11K 2009-09-14 21:56 sysvsem.so
-rw-r--r-- 1 root root  15K 2009-09-14 21:56 sysvshm.so
-rw-r--r-- 1 root root  49K 2009-09-14 21:56 tidy.so
-rw-r--r-- 1 root root  35K 2009-09-14 21:56 wddx.so
-rw-r--r-- 1 root root  93K 2009-09-14 21:56 xmlrpc.so
-rw-r--r-- 1 root root  32K 2009-09-14 21:56 xsl.so
-rw-r--r-- 1 root root  77K 2009-09-14 21:56 zip.so
-rw-r--r-- 1 root root  32K 2009-09-14 21:56 zlib.so

-rwxr-xr-x 1 root root 3.5M 2009-09-14 21:56 php

php 만 해도 3.5M 인데, 1.5M 짜리 ZO 랑 2M 짜리 mbstring 만 포함한다 해도
벌써 7M 입니다.
7M * 100 = 700M 네요.
(php ext 를 .so 로 빼면 아마 여기서 이득이 있을겁니다.)

아파치 prefork 도 비슷한 방식으로 계산됩니다.
worker 는 multi thread 라서 메모리 이득이 있습니다만,
계산법은 비슷합니다.
그리고 그건 아파치 문제지 아파치가 띄우는 php 메모리를 절약해 줄 것 같지는 않군요.




3. my.cnf
max_connections = 138
아파치의 MaxClients 보다 mysql 이 받을 수 있는 클라이언트 수가 10 정도는 많아야 합니다.
애초에 mysql 커넥션이 모자르다는 메세지는 나올 일이 없습니다.
그게 나온다면 max_connections 를 늘리거나 아파치 MaxClients 를 줄여야 합니다.
그 밖에 cache, size, length 등등의 값을 적절히 조절하세요.





이제 이런 정보들을 안 상태에서 새로운 사실을 추측 할 수 있게 됩니다.
아파치 수 제한은 곧 php 수 제한이며,
php 를 쓰는 주소건 그냥 이미지 주소건 아파치의 커넥션은 소모되죠.


nginx + php-fcgi 조합은 그런 제약에서 벗어나게 해 줍니다.
nginx 자체는 epoll 이라는 방식으로 동작합니다.

아파치는 prefork (fork), worker (multi threads) 라는 방식인데,
두 방식 모두 사용량에 비례해서 메모리가 소모됩니다.

epoll 방식은 사용량이 많아도 메모리가 많이 소모되지 않습니다.
쉽게, 같은 프로그램이 계속 복사되어 일을 처리하는 것과,
하나의 프로그램이 혼자 여러 일을 처리하는 차이로 보면 됩니다.

php-fcgi 는 독립실행입니다.
동적 수량조절이 안되긴 하지만
아파치 prefork 같은 방식으로 지정한 커넥션 수 만큼 서버가 생성됩니다.
이렇게 되면 7~80 정도만 줘도 충분합니다.
mysql 의 max_connections 도 역시 100 이하로 줄일 수 있게 되죠.

더 줄여도 될겁니다.

nginx 도 문제는 있습니다.
timeout 값을 조절해도 적용이 안되는 것 같더군요.
php 와 분리되어 있어서 php 의 set_time_limit(0) 같은것의 영향을 받지도 않고,
nginx.conf 에서 조절을 해도 이게 적용되는 것 같지 않더군요.
php 의 수행이 조금 길어지면 어김없이 504 gateway timeout 을 뿌립니다.
또한 php 와 분리가 되어 있어서,
아파치처럼 php_value 로 개별 설정이 불가능합니다.





브라우저와 웹서버와의 통신에서
웹서버가 커넥션을 모두 소모된 상태에서 브라우저가 요청을 하면,
응답할 커넥션이 없으므로 그냥 대기상태가 되게 됩니다.
커넥션이 없다고 에러 페이지가 뜨는것이 아니라는 거죠.
그런데 mysql 커넥션이 모자라는 거면 php 에서 에러를 냅니다.
차이가 있죠.




Written by Song Hyo-Jin (shj at xenosi.de)
License : Creative Commons - Attribution (CC-BY)

댓글