mod_xsendfile を使う

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;