mod_xsendfile for Apache2/Apache2.2 という Apache モジュールがありまして、これを使うとレスポンスヘッダに X-Sendfile: path/to/file と出力することで、Apache がレスポンスのボディをファイルの中身で差し替えてくれる。
Webアプリケーションで認証後、大きなファイルをダウンロードさせるような用途に便利。
このモジュールはその名の通り sendfile システムコールを(使えれば)使うので、アプリケーションが自前でファイルの中身を読んで送信するよりも速い(軽い)はず。http://www.linux.or.jp/JM/html/LDP_man-pages/man2/sendfile.2.html
ってことでベンチマーク取ってみた。
- 1. 普通に静的ファイルを Apache が serve
- 2. mod_xsendfile を使う
- 3. アプリケーションが read / write
ファイルサイズ 10KB
1. static Requests per second: 2283.74 [#/sec] (mean) Transfer rate: 23403.13 [Kbytes/sec] received 2. sendfile Requests per second: 1189.99 [#/sec] (mean) Transfer rate: 12169.56 [Kbytes/sec] received 3. read / write Requests per second: 1159.27 [#/sec] (mean) Transfer rate: 11743.68 [Kbytes/sec] received
ファイルサイズ 100KB
1. static Requests per second: 753.24 [#/sec] (mean) Transfer rate: 75547.56 [Kbytes/sec] received 2. sendfile Requests per second: 708.89 [#/sec] (mean) Transfer rate: 71079.67 [Kbytes/sec] received 3. read / write Requests per second: 660.16 [#/sec] (mean) Transfer rate: 66108.62 [Kbytes/sec] received
ファイルサイズ 10MB
1. static Requests per second: 7.00 [#/sec] (mean) Transfer rate: 77610.81 [Kbytes/sec] received 2. sendfile Requests per second: 7.00 [#/sec] (mean) Transfer rate: 77665.38 [Kbytes/sec] received 3. read / write Requests per second: 7.00 [#/sec] (mean) Transfer rate: 77702.50 [Kbytes/sec] received
サイズが 100KB を超えるあたりで GBit ether の帯域を使い切ってしまうので、大きなファイルではリクエスト数や転送量では差が出ないという結果に……
とはいえサーバの CPU 使用率には差があって、当然ながら 1 < 2 < 3 となった。read/write よりも sendfile の方が、User CPU 時間の消費がかなり少ない。
[追記]
1MBのベンチマーク中のサーバ CPU 負荷を vmstat で見ると以下のような感じ。
user system idle static 3 19 78 sendfile 7 27 66 read/write 16 29 55
一応、ベンチマークに使ったソースも。ModPerl::Registry で動かした。
sendfile
#!/usr/bin/perl use strict; my $file = $ENV{PATH_INFO}; $file =~ s{^/}{}g; print "Content-Type: text/plain;\n"; print "X-Sendfile: $file\n"; print "\n";
read / write
#!/usr/bin/perl use strict; my $file = $ENV{PATH_INFO}; $file =~ s{^/}{}g; print "Content-Type: text/plain;\n"; print "\n"; open my $in, "<", $file or die $!; my $buf; while ( read $in, $buf, 4096 ) { print $buf; } close $in;