Apacheで.htaccessを無効にすると場合によってはかなり早くなるよというお話

Apacheの高速化の手法というのは、多くのサイトで紹介されています。そのうちの一つとして、.htaccessを無効にするという話がありますが、実はこれは場合によっては非常にパフォーマンスが向上します。それが一体どの程度なのかという実験を簡単にしてみました。

まずはApacheの設定

実験で使用するprefork版の設定ファイルはこのようにしています。プロセスの生成破棄が生じないような設定をしているので、パフォーマンス計測等、色々な実験に汎用的に使えておすすめです。

ServerTokens Prod
ServerRoot "/etc/httpd"
PidFile run/httpd.pid
Timeout 100
KeepAlive On
MaxKeepAliveRequests 256
KeepAliveTimeout 256
StartServers 256
MinSpareServers 256
MaxSpareServers 256
ServerLimit 256
MaxClients 256
MaxRequestsPerChild 0
Listen 80
User apache
Group apache
ServerAdmin root@example.com
UseCanonicalName Off
DocumentRoot "/var/www/html"
DefaultType text/html
HostnameLookups Off
ErrorLog logs/error_log
LogLevel warn
ServerSignature Off
AddDefaultCharset UTF-8
AccessFileName .htaccess
<Directory />
    AllowOverride None
</Directory>

こんな感じで実験時はシンプルな設定にしています。この設定の下から2行目のAllowOverride Noneの設定をいれると、.htaccessが無効になります。では、以下のようなディレクトリを作って実験してみました。

mkdir -p /var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/

さて、この程度のディレクトリの階層があった場合に、.htaccessの有効無効でどの程度パフォーマンスに差がでるのでしょうか。実際にやってみましょう。

.htaccessはディレクトリ毎にファイルを探索する

Apacheの動作としては、.htaccessを有効にしていると、範囲を指定していない場合は/からアクセスのあったディレクトリまで一階層ごとに全てのディレクトリで.htaccessファイルを探索していきます。そのため、階層が深ければ深い程、探索のコストがどんどん増加してしまいます。例えば、今回の26階層の場合だと、どの程度パフォーマンスに差がでるのでしょうか。まずは、straceでアクセス時のシステムコールを確認します。ブラウザからのアクセスで、304が返った場合のstraceのログです。

.htaccessが有効の場合

[pid 10780] waitpid(-1, 0xbfea0908, WNOHANG|WSTOPPED) = 0
[pid 10780] select(0, NULL, NULL, NULL, {1, 0} <unfinished ...>
[pid 10784] <... accept resumed> {sa_family=AF_INET, sin_port=htons(60928), sin_addr=inet_addr("172.16.0.10")}, [16]) = 8
[pid 10784] getsockname(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("172.16.1.11")}, [16]) = 0
[pid 10784] fcntl64(8, F_GETFL) = 0x2 (flags O_RDWR)
[pid 10784] fcntl64(8, F_SETFL, O_RDWR|O_NONBLOCK) = 0
[pid 10784] read(8, "GET /a/b/c/d/e/f/g/h/i/j/k/l/m/n"..., 8000) = 571
[pid 10784] gettimeofday({1343192674, 835819}, NULL) = 0
[pid 10784] stat64("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/index.html", {st_mode=S_IFREG|0644, st_size=19, ...}) = 0
[pid 10784] open("/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/index.html/.htaccess", O_RDONLY|O_LARGEFILE) = -1 ENOTDIR (Not a directory)
[pid 10784] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/index.html", O_RDONLY|O_LARGEFILE) = 9
[pid 10784] open("/etc/localtime", O_RDONLY) = 10
[pid 10784] fstat64(10, {st_mode=S_IFREG|0644, st_size=331, ...}) = 0
[pid 10784] fstat64(10, {st_mode=S_IFREG|0644, st_size=331, ...}) = 0
[pid 10784] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f47000
[pid 10784] read(10, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\0\0\0\3\0\0\0\0"..., 4096) = 331
[pid 10784] close(10) = 0
[pid 10784] munmap(0xb7f47000, 4096) = 0
[pid 10784] close(9) = 0
[pid 10784] read(8, 0x92d1070, 8000) = -1 EAGAIN (Resource temporarily unavailable)
[pid 10784] writev(8, [{"HTTP/1.1 304 Not Modified\r\nDate:"..., 183}], 1) = 183
[pid 10784] poll([{fd=8, events=POLLIN}], 1, 256000 <unfinished ...>
[pid 10780] <... select resumed> ) = 0 (Timeout)
[pid 10780] gettimeofday({1343192675, 235409}, NULL) = 0
[pid 10780] write(6, "[Wed Jul 25 14:04:35 2012] [erro"..., 110) = 110

このようにopenシステムコールをディレクトリ毎に実行して、.htaccessファイルがあるかどうかを確認していますね。

.htaccessを無効にしている場合

[pid 9951] waitpid(-1, 0xbfb22168, WNOHANG|WSTOPPED) = 0
[pid 9951] select(0, NULL, NULL, NULL, {1, 0} <unfinished ...>
[pid 9953] <... accept resumed> {sa_family=AF_INET, sin_port=htons(60192), sin_addr=inet_addr("172.16.0.10")}, [16]) = 8
[pid 9953] getsockname(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("172.16.1.11")}, [16]) = 0
[pid 9953] fcntl64(8, F_GETFL) = 0x2 (flags O_RDWR)
[pid 9953] fcntl64(8, F_SETFL, O_RDWR|O_NONBLOCK) = 0
[pid 9953] read(8, "GET /a/b/c/d/e/f/g/h/i/j/k/l/m/n"..., 8000) = 571
[pid 9953] gettimeofday({1343192355, 346240}, NULL) = 0
[pid 9953] stat64("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/index.html", {st_mode=S_IFREG|0644, st_size=19, ...}) = 0
[pid 9953] open("/var/www/html/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/index.html", O_RDONLY|O_LARGEFILE) = 9
[pid 9953] open("/etc/localtime", O_RDONLY) = 10
[pid 9953] fstat64(10, {st_mode=S_IFREG|0644, st_size=331, ...}) = 0
[pid 9953] fstat64(10, {st_mode=S_IFREG|0644, st_size=331, ...}) = 0
[pid 9953] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f10000
[pid 9953] read(10, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\0\0\0\3\0\0\0\0"..., 4096) = 331
[pid 9953] close(10) = 0
[pid 9953] munmap(0xb7f10000, 4096) = 0
[pid 9953] close(9) = 0
[pid 9953] read(8, 0x9452070, 8000) = -1 EAGAIN (Resource temporarily unavailable)
[pid 9953] writev(8, [{"HTTP/1.1 304 Not Modified\r\nDate:"..., 183}], 1) = 183
[pid 9953] poll([{fd=8, events=POLLIN}], 1, 256000 <unfinished ...>
[pid 9951] <... select resumed> ) = 0 (Timeout)
[pid 9951] gettimeofday({1343192355, 445827}, NULL) = 0
[pid 9951] write(6, "[Wed Jul 25 13:59:15 2012] [erro"..., 110) = 110

このように、ファイルの探索を行っていない事がわかります。

パフォーマンスを比較

では、実際にabコマンドでどの程度パフォーマンスに差がでるかを確認してみましょう。ベンチマークコマンドは同時接続数100総接続数100000です。アクセスするindex.htmlはit worksと表示するだけのhtmlです。また、上述した通り、Apacheの設定で子サーバプロセスの生成破棄が生じてそれが性能のボトルネックにならないように、最初から最後まで256プロセスがforkされっぱなしになるようにしています。3回程計測してみました。

ab -c 100 -n 100000 http://example.com/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/index.html

.htaccessを有効にしている場合の1秒間にサーバが処理できたリクエスト数

Requests per second: 5108.45 [#/sec] (mean)
Requests per second: 5095.08 [#/sec] (mean)
Requests per second: 5097.39 [#/sec] (mean)

.htaccessを無効にしている場合の1秒間にサーバが処理できたリクエスト数

Requests per second: 9938.46 [#/sec] (mean)
Requests per second: 9973.51 [#/sec] (mean)
Requests per second: 9975.19 [#/sec] (mean)

このように、26階層だと.htaccessを有効にしている場合と比較し無効にしていると2倍程度のパフォーマンスになることがわかりました。今回はあえて極端なディレクトリ階層で実験を行いました。しかし、それでもこれは結構あなどれない数字ですし、階層の浅さ深さに関わらずディレクトリの数だけ探索が行われるので、アクセスの多いサイトにおいてはとても大きな性能差になると思われます。使う場合は、ディレクトリを指定してあげるのが良さそうですね。

階層数によるパフォーマンスの変動

階層を深くしていくにつれて、どのようにパフォーマンスが変動していくかをグラフにしてみました。

大体想像通り、htaccessを有効にしていた場合は階層に比例してパフォーマンスが劣化していっている事がわかります。また、階層が深いとhtaccessに関わらずApacheの性能が少しずつ低下している事もわかりました。

こういう結構当たり前とされる設定の有無で、大きな性能差が生じる場合があるという典型的なお話でした。