lighttpdでのファイルアップロードを高速化するハック

この時期にも花粉症に苦しむ村瀬です。こんにちは。

軽量で高速な動作がウリの lighttpd ですが、動的コンテンツで大きなファイルのやり取りを行おうとするととたんにパフォーマンスが落ちるという問題があります。

この問題は、lighttpdで動的コンテンツを扱う場合FastCGIを使用することが多いのですが、その構造に問題があります。

FastCGIを使用する場合のデータ通信の構造は以下のようになっています。

ブラウザ <---(1)---> lighttpd <---(2)---> FastCGI
  1. ブラウザとlighttpdとのデータのやり取り
  2. lighttpdとFastCGIプロセスとのやり取り

プロセス数が有限なFastCGIでは、即座にデータを受け取り即座にレスポンスを返すというのが鉄則で、ここで時間のかかる処理をしてしまうとすぐにプロセスが埋まり待ち状態が発生してしまいます。

つまり、(2)のデータのやり取りで大きなデータのやり取りをするとFastCGI内で時間のかかる処理をしていなくても単純にファイルのやり取りに時間がかかってしまい、それが原因でプロセスが埋まってしまいます。

これを回避するために lighttpd では X-Sendfile という仕組みがあります。

これは、FastCGI側で大きなファイルを出力するかわりにレスポンスヘッダに

X-Lighttpd-Sendfile: /path/to/file

とファイルのパスを指定すると、lighttpd がレスポンスの中身をそのファイルの内容に置き換えてくれるという仕組みです。

これを使用することで FastCGI から lighttpd 通信での大きなファイルの送信 (クライアント側からみたダウンロード) は問題なく行えます。

しかし、アップロード処理ではそういう仕組みがないため、この構成で大きなファイルアップロードを受け付ける機能を作ってしまうと非常に残念なことになってしまいます。

そこで、X-Sendfileの逆方向の機能をモジュールとして実装してみました。

http://github.com/typester/toybox/tree/mod_postfile

POSTで受信したデータをファイルに書き込み、FastCGIへはファイルデータを送る代わりに X-Sendfile: ヘッダにファイルのパスを書き込んで送るという仕組みです。

YATTA! すばらしい! とおもいきや lighttpd の 1.4x 系にのモジュール機構にはファイル受信時のフックポイントがないため、いちどlighttpdが受信したファイルをモジュール内でコピーしているため、実装的に微妙です。

1.5系にはそのフックポイントがあるようなんですが、いつリリースされるかわからないし。。。

ということでエイヤッっとlighttpdにそのフックポイントを追加するパッチを書いてそれを使うように書き換えたバージョンも作りました。

http://github.com/typester/toybox/tree/mod_postfile_hack_ver

lighttpd の src ディレクトリにダウンロード後

patch -p3 < lighttpd-1.4.20-add_read_post_hook.patch

して後は普通にmakeでOKです。

で、モジュールを使用するには

server.modules = (
    "mod_postfile",
    "mod_fastcgi",
)

で、モジュールをロード。ここで注意することは mod_postfile モジュールを mod_cgimod_fastcgi より前にロードする必要があるということです。これはバッドノウハウ的ですがそれらのモジュールと同じフックポイントを使っているためやむを得ません。

そして、この機能を有効にしたいところに

$HTTP["url"] =~ "^/upload" {
    postfile.dir = "/tmp"
}

などとファイルを保存するパスを指定すれば機能が有効になります。

実際に試してみると、かなりのパフォーマンスの改善が見られると思います。

ただこれをサービスで使うとなると別途セッション認証モジュールなども作る必要があるでしょうし、受け側のFastCGI側も実装を工夫する必要があり、なかなか難しいかもしれません。

ただこのような実装がない限りlighttpd+FastCGIだけで巨大なファイルアップロードを扱うということは現実的ではないですね。

このあたりのところはまた機会があれば書こうと思います。

ではまた。

カヤックでは apache モジュールや lighttpd モジュール、また perlbal プラグインなどをがんがん書くことが出来る技術者を募集しています!