不要なApacheやPHPの拡張モジュールを削除するとどれくらい効果があるか調べてみる | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

よく、Apacheで読み込まれている不要なモジュールを解除すればApacheが軽くなるよ!みたいなエントリを見たりしますが、実際問題どれぐらいプロセスが軽くなるのかをメモリの使用量の観点から調べてみました。

ちょっと色々疑問が出たりして曖昧な部分も多いのですが。


調べるにあたって、下記のエントリを参考にさせていただきました。


Linux のプロセスが Copy on Write で共有しているメモリのサイズを調べる @ naoyaのはてなダイアリー


※ 上記エントリ内で紹介されているShibuya.pmの発表資料が特にわかりやすかったです。


apacheのMaxClientを算出するスクリプトを作った @ webネタ



ApacheとPHPのモジュールのメモリ使用量を調べる


で、Apacheのメモリ使用量は/proc/PID/smapsをみて、Apacheの個々のプロセスにおけるRss、Shared_Clean、Shared_Dirtyの値から導き出せるということがわかりましたので、ここから計算するとします。

Apacheで読み込まれているモジュールはhttpd.confのLoadModuleディレクティブ(※)から、PHPの方は同じく設定ファイルであるphp.iniのextension_dir内のsoファイルから利用している拡張モジュールがわかるので、この辺の内容を元に調べてみるとします。


※ conf.d以下にも拡張モジュールが存在してたりしますが、今回は対象外にしました。

  また、静的に組み込まれているモジュールも対象外にしています。


調べるスクリプトはLinuxのコマンド結果を元にPHPで処理して整形させています。


<?php

// httpd.confの中から利用しているモジュール一覧を取得
exec("cat /etc/httpd/conf/httpd.conf | awk -F / '/^LoadModule/ { print \$NF }'", $httpd_modules);

$ext_dir = ini_get('extension_dir');

// 拡張モジュール用のディレクトリからモジュール一覧を取得
if ($dh = opendir($ext_dir)) {
    while (($file = readdir($dh)) !== false) {
        if ($file != "." && $file != "..") {
            $httpd_modules[] = $file;
        }
    }
}

$all = 0;

exec("pgrep httpd", $httpd_pid);

foreach($httpd_modules as $module) {

    $sum = 0;

    echo "----" . $module . PHP_EOL;

    $i = 0;
    foreach ($httpd_pid as $pid) {
        // 物理メモリ使用量を取得
        $rss = chop(`cat /proc/$pid/smaps | grep $module -A 4 | grep Rss | awk '{ rss += $2 } END { print rss; }'`);

        // 共有メモリ(Shared_Clean)を取得
        $clean = chop(`cat /proc/$pid/smaps | grep $module -A 4 | grep Shared_Clean | awk '{ cln += $2 } END { print cln; }'`);

        // 共有メモリ(Shared_Dirty)を取得
        $dirty = chop(`cat /proc/$pid/smaps | grep $module -A 4 | grep Shared_Dirty | awk '{ diy += $2 } END { print diy; }'`);

        // プロセスIDごとのメモリ使用量出力
        echo "Pid:" . $pid . " Rss:" . $rss . " Shared_Clean:" . $clean . " Shared_Dirty:" . $dirty . PHP_EOL;

        // 親プロセス
        if ($i === 0) {
            $sum += $rss;
        } else {
        // 子プロセス
            $sum += $rss - ($clean + $dirty);
        }
        $i++;
    }
    // モジュールごとのメモリ使用量
    echo $module . ":" . $sum . "kb" . PHP_EOL;
    $all += $sum;
}

// Totalのメモリ使用量
echo "All:" . $all . "kb" . PHP_EOL;


どちらかというとLinux上のコマンドだけで値を取っている部分が多いので、やってることの説明はその部分画』中心ですが、まず最初に


cat /etc/httpd/conf/httpd.conf | awk -F / '/^LoadModule/ { print $NF }'

にて、Apacheの設定ファイル内からLoadModuleディレクティブ(コメントアウトされていないもの)を取り出します。

PHPは、ini_get('extension_dir')から拡張モジュール用のディレクトリパスを取り出して、その中にあるファイルを同様に対象に詰め込んでいっています。

後は、pgrepにてHTTPのプロセス一覧を取得して、


cat /proc/$pid/smaps | grep $module -A 4 | grep Rss | awk '{ rss += $2 } END { print rss; }'

というように、smaps内のRssやShared_CleanやShared_Dirtyの値を取り出してモジュールごとに使用量を計算していってます。


で、実行してみると下記のような結果が出ました。

(実行はrootで実行する必要があります)


読み込んでいるモジュール数と起動しているApacheのプロセス数により、かなり量が出力されます。


----mod_auth_basic.so
Pid:20437 Rss:12 Shared_Clean:0 Shared_Dirty:8
Pid:20439 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20440 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20441 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20442 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20443 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20444 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20445 Rss:8 Shared_Clean:0 Shared_Dirty:8
Pid:20446 Rss:8 Shared_Clean:0 Shared_Dirty:8
mod_auth_basic.so:12kb
----mod_auth_digest.so
Pid:20437 Rss:24 Shared_Clean:4 Shared_Dirty:8
Pid:20439 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20440 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20441 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20442 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20443 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20444 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20445 Rss:12 Shared_Clean:4 Shared_Dirty:8
Pid:20446 Rss:12 Shared_Clean:4 Shared_Dirty:8
mod_auth_digest.so:24kb
-snip-
----curl.so
Pid:20437 Rss:116 Shared_Clean:100 Shared_Dirty:16
Pid:20439 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20440 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20441 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20442 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20443 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20444 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20445 Rss:16 Shared_Clean:0 Shared_Dirty:16
Pid:20446 Rss:16 Shared_Clean:0 Shared_Dirty:16
curl.so:116kb
All:3364kb


各モジュールともにメモリの使用量はかなり微量だったりします。

そして、トータル(All:の部分)でも3MBちょっとということで本当にこれが正しいのかな・・・?って感じです。

ロードしているモジュールだけを対象にしていますし、Apacheのプロセスを再起動した直後なんで、こんなもんなんでしょうか。


1つ疑問になったのが、親プロセスの共有メモリ量は差し引かず、物理メモリ使用量(Rss)のみを利用して計算したんですが、これってあってる・・・?

親プロセスから共有メモリ量も引くとメモリ使用量が0になるモジュール(上記のcurl.soとか)があって、そのモジュールでメモリを使っていないものが出ることになってしまいます。

先のエントリでも、共有メモリは親プロセスと子プロセスの間で共有されるとあるので、親プロセスの共有メモリ量は換算しなくてよいのかなと。


で、ここまで作っておいて解説しておいてなんなんですが、このプログラム自体では正確なロードされているモジュールのメモリ使用量が出せないということに気がつきました。

というのも、smapsを見ていると1つのモジュールから他の共有モジュールが呼び出されていたりしますので、それらも計算に入れる必要が出てきます。


例えば、


# ldd /etc/httpd/modules/mod_auth_basic.so
        linux-vdso.so.1 =>  (0x00007fffb46ff000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00002b98cf90b000)
        libc.so.6 => /lib64/libc.so.6 (0x00002b98cfb26000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003c5d800000)

mod_auth_basicの中では上記のような共有ライブラリを参照していることがわかります。

上記の中のlibpthread.so.0についてsmpasでgrepしてみると


2b75d0530000-2b75d0546000 r-xp 00000000 03:02 4608393                    /lib64/libpthread-2.5.so
Size:                88 kB
Rss:                 60 kB
Shared_Clean:        52 kB
Shared_Dirty:         0 kB
Private_Clean:        8 kB
Private_Dirty:        0 kB
Swap:                 0 kB
Pss:                 15 kB
2b75d0546000-2b75d0745000 ---p 00016000 03:02 4608393                    /lib64/libpthread-2.5.so
Size:              2044 kB
Rss:                  0 kB
Shared_Clean:         0 kB
Shared_Dirty:         0 kB
Private_Clean:        0 kB
Private_Dirty:        0 kB
Swap:                 0 kB
Pss:                  0 kB


と、Apacheのプロセスでロードされていることがわかります。

※ libpthread.so.0は、libpthread-2.5.soへのシンボリックリンクです。


これらを一つずつ調べていくのはかなり骨が折れますし、共有ライブラリってことで他のモジュールからも参照されていたりして、正確にそのモジュールで利用しているメモリ使用量を調べることが出来ません。



Apacheのプロセスごとにメモリ使用量を求めてみる


ってことで、結局のところApacheのプロセスごとにメモリ使用量を求めてみて、現在のApacheの状況と不要なモジュールを削除した場合とでメモリ使用量を比較してみるということにしてみました。


Apache全体のメモリ使用量を求めるPHPスクリプトはこんな感じです。


<?php

exec("pgrep httpd", $httpd_pid);

$sum = 0;
$i = 0;

foreach ($httpd_pid as $pid) {
    // 物理メモリ使用量を取得
    $rss = chop(`cat /proc/$pid/smaps | grep Rss | awk '{ rss += $2 } END { print rss; }'`
);

    // 共有メモリ(Shared_Clean)を取得
    $clean = chop(`cat /proc/$pid/smaps | grep Shared_Clean | awk '{ cln += $2 } END { print cln; }'`);

    // 共有メモリ(Shared_Dirty)を取得
    $dirty = chop(`cat /proc/$pid/smaps | grep Shared_Dirty | awk '{ diy += $2 } END { print diy; }'`);

    // プロセスIDごとのメモリ使用量出力
    echo "Pid:" . $pid . " Rss:" . $rss . " Shared_Clean:" . $clean . " Shared_Dirty:" . $dirty . " Total:" . ($rss - ($clean + $dirty)) . PHP_EOL;

    // 親プロセス
    if ($i === 0) {
        $sum += $rss;
    } else {
    // 子プロセス
        $sum += $rss - ($clean + $dirty);
    }
    $i++;
}

// Totalのメモリ使用量
echo "All:" . $sum . "kb" . PHP_EOL;


やってることは先ほどのものとほぼ同じです。

モジュールごとの値をとるのを止めて、PIDごとにメモリ使用量を取っていってます。


これを使って、現在のApacheのメモリ使用量を計算してみます。

ちなみに、Apacheで読み込んでいるモジュールは下記の通りで、パッケージで入れたApacheのデフォルトの状態のものです。


LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule auth_digest_module modules/mod_auth_digest.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_alias_module modules/mod_authn_alias.so
LoadModule authn_anon_module modules/mod_authn_anon.so
LoadModule authn_dbm_module modules/mod_authn_dbm.so
LoadModule authn_default_module modules/mod_authn_default.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_owner_module modules/mod_authz_owner.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_dbm_module modules/mod_authz_dbm.so
LoadModule authz_default_module modules/mod_authz_default.so
LoadModule ldap_module modules/mod_ldap.so
LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
LoadModule include_module modules/mod_include.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule logio_module modules/mod_logio.so
LoadModule env_module modules/mod_env.so
LoadModule ext_filter_module modules/mod_ext_filter.so
LoadModule mime_magic_module modules/mod_mime_magic.so
LoadModule expires_module modules/mod_expires.so
LoadModule deflate_module modules/mod_deflate.so
LoadModule headers_module modules/mod_headers.so
LoadModule usertrack_module modules/mod_usertrack.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule mime_module modules/mod_mime.so
LoadModule dav_module modules/mod_dav.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule info_module modules/mod_info.so
LoadModule dav_fs_module modules/mod_dav_fs.so
LoadModule vhost_alias_module modules/mod_vhost_alias.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule dir_module modules/mod_dir.so
LoadModule actions_module modules/mod_actions.so
LoadModule speling_module modules/mod_speling.so
LoadModule userdir_module modules/mod_userdir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule cache_module modules/mod_cache.so
LoadModule suexec_module modules/mod_suexec.so
LoadModule disk_cache_module modules/mod_disk_cache.so
LoadModule file_cache_module modules/mod_file_cache.so
LoadModule mem_cache_module modules/mod_mem_cache.so
LoadModule cgi_module modules/mod_cgi.so
LoadModule version_module modules/mod_version.so


ここから、下記のものだけに絞込み、残りはコメントアウトしました。


LoadModule authn_default_module modules/mod_authn_default.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_default_module modules/mod_authz_default.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule logio_module modules/mod_logio.so
LoadModule env_module modules/mod_env.so
LoadModule ext_filter_module modules/mod_ext_filter.so
LoadModule mime_magic_module modules/mod_mime_magic.so
LoadModule expires_module modules/mod_expires.so
LoadModule deflate_module modules/mod_deflate.so
LoadModule headers_module modules/mod_headers.so
LoadModule usertrack_module modules/mod_usertrack.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule mime_module modules/mod_mime.so
LoadModule vhost_alias_module modules/mod_vhost_alias.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule dir_module modules/mod_dir.so
LoadModule actions_module modules/mod_actions.so
LoadModule speling_module modules/mod_speling.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule cache_module modules/mod_cache.so
LoadModule disk_cache_module modules/mod_disk_cache.so
LoadModule file_cache_module modules/mod_file_cache.so
LoadModule mem_cache_module modules/mod_mem_cache.so
LoadModule php5_module        /usr/lib64/httpd/modules/libphp5.so

設定は結構適当にしたので、実際に不要かどうかはその環境に合わせて調整してください。

また、PHPも下記のような拡張モジュールを読込んでいるものを


# ls /usr/lib64/php/modules/
bcmath.so  fileinfo.so  ldap.so      pdo_pgsql.so   phar.so
curl.so    gd.so        mbstring.so  pdo_sqlite.so  phpcups.so
dba.so     json.so      pdo.so       pgsql.so       zip.so


下記のように幾つか削除しました。


# ls /usr/lib64/php/modules/
curl.so      json.so      pdo.so        pdo_sqlite.so  phar.so     zip.so
fileinfo.so  mbstring.so  pdo_pgsql.so  pgsql.so       phpcups.so


この状態で、初期と不要なモジュール削除後の状態とでApacheのメモリ使用量を比べてみます。

まずは、初期のApacheプロセスごとのメモリ使用量。


Pid:20437 Rss:8852 Shared_Clean:3192 Shared_Dirty:4036 Total:1624
Pid:20439 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20440 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20441 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20442 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20443 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20444 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20445 Rss:5372 Shared_Clean:1144 Shared_Dirty:4012 Total:216
Pid:20446 Rss:4828 Shared_Clean:636 Shared_Dirty:4036 Total:156
All:10520kb


次に、不要なモジュール削除後のApacheプロセスごとのメモリ使用量。


Pid:2573 Rss:7424 Shared_Clean:2376 Shared_Dirty:3436 Total:1612
Pid:2575 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2576 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2577 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2578 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2579 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2580 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2581 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
Pid:2582 Rss:4164 Shared_Clean:592 Shared_Dirty:3448 Total:124
All:8416kb


2MBほどですが、若干メモリ使用量が低下しました。

これは、Apache起動後の状態なので、プロセスが使用されていく過程で共有メモリの使用量が変化したり、Apacheのプロセス数が増加することで効果は大きくなっていくかと思います。


劇的に軽くなるわけでは無そうですが、不要なモジュールを排除することで効果があることは明確なようですね。