先週金曜日(12/2)にクックパッドインフラ勉強会に参加しまして、そこで同社の成田さんから「今日からできるApacheモジュール開発と運用」という発表がありました。リアルタイム画像変換モジュールの「TOFU」を開発するに至った経緯と、Apacheモジュール開発についてのお話でした。TOFUは、S3に置かれたマスターとなる画像ファイルを取得し、与えられたパラメータでリアルタイム(オンザフライ)にリサイズ・トリミングを行うモジュール(mod_tofu)です。料理を楽しくする画像配信システム実際は、モジュールによる画像取得・変換をベースに、キャッシュや配信までも含めた一連の画像配信システムと言えそうです。この仕組みをNginxを使って実装できないかと考えて、リアルタイム変換の仕組みをNginxだけで実現する方法を実験してみました。準備するものHttpImageFilterModuleこのモジュールはパッケージに同梱されているオプションモジュールの一つで、JPEG、GIF、PNGを変換するフィルターを提供します。有効にするには、ビルド時にパラメータを指定する必要があります。--with-http_image_filter_moduleビルドし直したnginxの入れ替えを行う場合は、こちらを参考にしてください。リクエストを落とさずにnginxをアップグレードする方法とその検証また、ビルドにはlibgdが必要なのでGDライブラリを別途インストールしておく必要があります。簡単な例/imagesディレクトリにあるjpgファイルを全て150*150のサムネイルに変換したい場合は、以下の設定を書くだけです。150;}このままだと元サイズのファイルにアクセスできなかったり、エラー制御がしにくいのですが、このモジュールがいかに簡単に使えるかがわかると思います。前述の最も簡単な例を発展させて、外部からのパラメータに従って、まずはローカルディスクにある画像に対してリアルタイム変換をしてみます。バイト外部からのパラメータwidth(幅)height(高さ)type(リサイズの方法)resize(縦横比を変えずにサイズ変換)crop(サイズ変換後に指定されたサイズに切り取る)quality(JPEGの画質≒圧縮率)要件/imagesディレクトリにあるJPEGファイル(*.jpg)にパラメータを付けてアクセスをした時、パラメータに従って変換された画像を返す。以下のように、パラメータを変えるだけで欲しい画像サイズに変換されます。/images/momiji.jpg?width=150&height=150&type=resize/images/momiji.jpg?width=200&height=200&type=crop/images/momiji.jpg?width=400&height=100&type=crop&quality=5こちらのリンク先でパラメータをいろいろ変更してテストができます。パラメータの有無をチェックします。なければ通常の画像を返すようにしています。それぞれ処理をし、変換された画像が返されます。ImageFilterが処理できない場合は415エラーが発生するので、それを1*1pxの透過GIFに置き換えています。empty_gif;}パフォーマンスab(ApacheBench)で100リクエストを並列で一度に送り、1秒あたりのリクエスト処理数を5回測定した平均値。クライアントとサーバーは別でインターネットを経由して測定。ローカルでの処理なのでネットワークのボトルネックはありません。キャッシュを利用していないので、リクエストごとに変換処理が動くことになります。無変換を基準に考えると、サイズが大きめの画像へのリサイズは時間がかかりますが、小さくなるに従って無変換よりもパフォーマンスが上がります。変換に処理時間がかかるものの、Webで利用するサムネイル程度のサイズにすれば十分実用的な速度だと思います。今度はTOFUの仕様に近づくべく、S3(リモートサーバー)にあるファイルを取得するようにします。実験のサーバーは「さくらのVPS512」でS3は「USスタンダードリージョン」です。ネットワークのレイテンシは150ms程です。バイト外部からのパラメータ(ローカルと同じ)width(幅)height(高さ)type(リサイズの方法)resize(縦横比を変えずにサイズ変換)crop(サイズ変換後に指定されたサイズに切り取る)quality(JPEGの画質≒圧縮率)要件JPEGファイル(*.jpg)にパラメータを付けてアクセスをした時、S3上にある同名ファイルを取得し、パラメータに従って変換された画像を返す。変換した画像はキャシュし、2回目以降はキャッシュを利用する。結果/s3images/sanzaru.jpg?width=150&height=150&type=resize/s3images/sanzaru.jpg?width=200&height=200&type=crop/s3images/sanzaru.jpg?width=400&height=100&type=crop&quality=5こちらのリンク先でパラメータをいろいろ変更してテストができます。リクエストをそのまま渡します。/s3_originalでそれぞれS3からファイルを取得・変換処理をし、画像を80番ポートのフロントエンドへ返します。80番ポートのフロントエンドはバックエンドから返ってきたレスポンスをキャッシュし、画像を返します。以降、すでにキャッシュ済みリクエストだった場合は、バックエンドへ送らず、キャッシュを返します。}}8080ポート側にresolverの設定が入っています。NginxはOS標準の名前解決方法を利用してくれないため、ネームサーバーをresolverに設定する必要があります。DNSが設定されていますが、利用するサーバーの最寄り(通常同じDC内)のネームサーバーを指定します。パフォーマンスab(ApacheBench)で100リクエストを並列で一度に送り、1秒あたりのリクエスト処理数を5回測定した平均値。クライアントとサーバーは別でインターネットを経由して測定。S3からファイルを取得する処理を含めると、ネットワークのボトルネックがあり、1リクエスト2秒程度かかります。それを含めてしまうと、ネットワークに引きずられて正確な値がでないので、キャッシュに対して測定しています。キャッシュからの応答になるので、純粋にファイルサイズの大きさに比例していきます。マスターの画像が変更されないことがわかっている場合、もしくは、変更があった場合にハッシュ値などを使ってキャッシュを書き換えるなどすると、実用的な速度で利用できることがわかります。まとめ細かい設定(例えば、左上から右へ10px下へ20pxを起点に切り取るなど)はできませんし、pngからjpgなどのフォーマット変換もできないので、多くの機能を望む場合は、他のプログラムを利用するか、Nginxモジュールを書く事になると思います。Cacheを適切に設定したり、多段にキャッシュを用意したりすれば、用途に限りがありますが、プロダクションでも使えると思います。»さくらのクラウドでプライベートテンプレートから仮想サーバーを起動させると、こういうことが起ります。google.com(コントロールパネルのコンソールから実行)普通に外部(インターネット)と通信ができません。Cloudではテンプレートから新しくインスタンスを立ち上げた場合は、ネットワーク設定周りを自動的に調整してくれて、起動直後からインターネットにつながります。逆に言うと、さくらのクラウドは純粋な仮想サーバーと考えると扱いやすくなります。これは、クラウドのシステムから仮想サーバーに割り振られたグローバルIPアドレスと起動したサーバーのネットワークの設定がずれているのが原因です。詳細情報で、割り振られているIPアドレスなどネットワークアドレス情報、Macアドレスを確認して、OSのネットワーク設定を変更します。(クリックで拡大)以下の例は、RedHat系Linuxの場合で、割り振られたIPv4アドレスが「133.242.xxx.xxx」、ゲートウェイが「133.242.xxx.1」、eth0のMacアドレスが「9c:a3:ba:xx:xx:xx」だったとします。NICの設定ファイルネットワークインターフェースの設定を書き換えます。OKDEVICE=eth0BOOTPROTO=noneHWADDR=9c:a3:ba:xx:xx:xxNM_CONTROLLED=yesONBOOT=yesTYPE=EthernetNETMASK=255.255.255.0BROADCAST=133.242.xxx.255IPADDR=133.242.xxx.xxxNETWORK=133.242.xxx.0GATEWAY=133.242.xxx.1IPV6INIT=noUSERCTL=noudevルールファイルMacアドレスも変更されている(OS的にはハードウェアが新しくなったと認識している)ので、udevルールファイルも書き換える必要があります。NAME="eth0"再起動システムを再起動します。ms余談9C-A3-BAのOUIはさくらインターネットのなんですね。MACアドレスを取得しました。cacheを効かせるのが王道です。Cacheプラグインを利用してなるべくPHPやMySQLにアクセスさせないようにすると、手軽で絶大なパフォーマンスアップが可能です。今回はそこからもう一歩進めたチューニングについて書きたいと思います。二段階層を廃したシンプルな構成まずは、図をご覧ください。前述の王道チューニングの構成はA図となります。cacheはNginxがバックエンドのサーバーに処理を回し、返ってきたレスポンスをキャッシュして、Nginx自身がキャッシュを返すことでパフォーマンスを上げる仕組みです。A図-1がキャッシュの無いアクセス、A図-2がキャッシュが効いているアクセスを表しています。jpgなど)をフロントのNginxが直接返すことを表しています。cache自体はバックエンドのサーバーがHTTPでコンテンツを返してくれれば、どんなアプリケーションでも問わない汎用的なキャッシュ機能です。cacheを利用しようとするともうひとつHTTPサーバーが必要になり、A図のようにHTTPのレイヤーが二段構成になります。cacheという機能(HttpFcgiModule)があります。これはバックエンドのFastCGIのレスポンスをキャッシュする機能です。cacheを利用したのと同じ効果をHTTPサーバーなしに実現できます。A図の8080ポートのサーバーが必要なくなり、B図の構成になります。無駄な通信と処理がなくなり、シンプルです。Cacheは、アクセスごとに動的に生成される記事を、静的なHTMLファイルとして保存(キャッシュ)して、それを返すように振舞うプラグインです。キャッシュが効いている間はMySQLにアクセスすることが無いのでパフォーマンスが上がります。Cacheを利用すると、.htaccessに以下の設定が追加されます。Cacheによって静的なファイルが生成されている場合、PHPを介さずにApacheが直接リクエストを返すようにする設定です。Cacheの推奨設定では、index.htmlとそれを圧縮したindex.html.gzが用意されるので、冗長な設定になっています。Cacheは本来のパフォーマンスを発揮しません。cacheに到達することもなく、静的ファイルと同じ処理、パフォーマンスになります。HttpGzipStaticModuleNginxのHttpGzipStaticModuleは、同じディレクトリ内にあらかじめ圧縮ファイル(.gz)が用意されている場合に、クライアント判定を行なって自動的に圧縮ファイルを返してくれるモジュールです。(正確には、Gzip圧縮に対応しているクライアントからリクエストが来て、同ディレクトリに同タイムスタンプで同名+.gzという名前のファイルがある場合)このモジュールを導入するメリットは、gzファイル分の.htaccessを移植する手間がなくなることです。コアモジュールではないので、コンパイル時にオプションで指定する必要があります。--with-http_gzip_static_moduleコンパイルし直したnginxの入れ替えを行う場合は、こちらを参考にしてください。リクエストを落とさずにnginxをアップグレードする方法とその検証NginxにはコアモジュールでHttpGzipModuleがあり、設定をオンにすることで特定のContent-TypeにGzip圧縮をかけてレスポンスを返すことができます。こちらはリクエストごとにオンザフライで圧縮をかけるので、余分に処理がかかります。cssやjsなどの圧縮ファイルが用意できる場合は、同階層に設置してHttpGzipStaticModuleの恩恵を受けたほうがいいと思います。サンプル設定ファイル前述の項目を網羅して、かつ可能な限り正常に動いている環境の設定ファイルを載せています。これが最も安全だと思います。/”ディレクティブを通ります。(記述順番は関係ありません。)静的ファイルのチェックこのディレクティブの先頭でリクエストされた静的ファイルが存在するかのチェックを行なっています。/”ディレクティブの処理が終わります。次に、マッチするlocationディレクティブがあればそちらが実行されます。\.(css|js)$”ディレクティブへ処理が続いていきます。これがB図-3の流れです。cacheのチェック静的ファイルとして存在しなかった場合、処理は下へ進みます。Cacheが生成したキャッシュファイルの存在をチェックし、キャッシュファイルを利用するかどうかを下に続く条件で確認していきます。cacheを利用するかどうかのチェックも行なっています。\.phpディレクティブで利用されます)。/wp-content/cache/supercache/${http_host}${uri}/index\.html(\.gz)?$”ディレクティブへ処理が続きます。これがB図-4の流れです。\.php”ディレクティブへ処理が続きます。cacheが使われ(B図-2)、キャッシュがなかったり、期限が切れていた場合は、FastCGI(PHP)へ処理が渡されます(B図-1)。今回はステータスコード200の時は10分、404の時は1分キャッシュするように設定してあります。Foundは無視できないくらい重い処理です。404に対してキャッシュが作られないと、存在しない記事にアクセスがくる度にPHPとMySQLが仕事をしてしまいます。付けてレスポンスを返すことがわかりました。cacheはキャッシュを作らないようなのです。スマートな方法があると思いますが、今回はWordPressのソースを少しいじって対応することにしました。}}これで404もキャッシュされるようになりました。Tokyoに微力ながらスポンサーとして協力させて頂きました。ブログにもWordPressの記事が少なく、弊社がWordPressにコミットしている感じが伝わっていないと思ったので、開催前に何とか間に合って良かったです。cloudropcloudropは、クラウドコンピューティングを利用したサービスの提供・インターネットサービスの技術支援を行う会社です。