サーバとアプリ側でサイトを高速化するAV女優.comの基本施策

  • 2011年8月25日 10:00
  • @sunomaru
  • Linux
  • 高速化, JavaScript, 画像, キャッシュ, CSS

どうも、最近はサーバ側ばかり触っているすのまるです。
前回はゴンゾーが CSSの高速化 について、まとめてくれました。

今日は、私が サーバとアプリ側の高速化施策 を紹介します。
NginxとCakePHPを対象としますが、その他のウェブサーバやウェブフレームワークにも応用可能です。

サーバとアプリ側高速化の原則

サーバとアプリ側の高速化の原則は簡単です。

  • HTTPコネクションと転送量を減らすこと

これを原則とし、ウェブサーバやアプリケーションにチューニングを施します。
クライアントの状況に左右されるCSSやJavaScriptの高速化チューニングよりも、対策が わかりやすく効果的 です。

高速化の効果測定

高速化の効果測定と、施策を確認するにはGoogleの Page Speed が便利です。

Page Speed Online
URLを入力すると、そのサイトの高速化施策が100点満点で採点されます。
的確なアドバイス も表示され、大変分かりやすいサービスです。
Page Speed for Chrome
Chromeにインストール出来るPage Speedです。
デバッガウィンドウ に直接、Page Speedが追加されるので、とても便利です。

Page Speedで高速化のポイントが分かれば、後はウェブサーバとアプリケーションの設定です。

Nginxの高速化設定

/img/posts/019/nginx.png

Nginxに基本的な施策である、以下の2点を設定します。

Expiresヘッダ
Expiresヘッダを設定することで、画像などの静的ファイルをクライアントにキャッシュさせます。
Gzip圧縮転送
HTMLやJS、CSSをGzipで圧縮し、転送します。

これに加え、 画像のリサイズによる圧縮HTMLの縮小化 による転送量削減を紹介します。

Expiresヘッダの設定

一度設置すると変更のない画像やJavaScript、CSSにのみExpiresヘッダをセットする設定です:

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires 1y;  # 画像ファイルとJavaScript、CSSファイルを1年間キャッシュ
}

Expiresにはこの他に、以下の値が設定出来ます:

expires  modified +24h;  # Exipresヘッダを修正時刻からプラス24時間後にセット
expires  max;            # Exipresヘッダを2037-12-31 23:59:59、
                          # Cache-Controlを最長の10年にセット

この他の設定値に関しては、ドキュメントを参照してください。

HttpHeadersModule
HTTPヘッダを操作するモジュールのドキュメントです。
add_headerとexpiresディレクティブについてのシンプルな解説があります。

JavaScriptとCSSにExpiresヘッダを付ける場合、ファイル名に タイムスタンプ か、 バージョン を付与すると良いでしょう。
例えば、 AV女優.com ではデプロイ毎に、以下のタイムスタンプがセットされます。

  • /css_cache/default. v20110819210110 .css
  • /css_cache/default. v20110822232219 .css

これを実施ためには、フレームワークのプラグインと CapistranoFabric といったデプロイツールを活用し、この処理を自動化してやる必要があります。

Gzip圧縮転送の設定

実は、Gzip圧縮転送はデフォルトでNginxに設定されています:

gzip on;               # Gzip圧縮転送を有効化
gzip_disable "msie6";  # UserAgentがMSIE6の場合、Gzip圧縮転送をオフにセット

こちらも細かい設定はドキュメントを参照してください。

HttpGzipModule
Gzip圧縮転送を設定するHttpGzipModuleのドキュメントです。
gzipの圧縮レベルや、圧縮するファイルの最小サイズも指定可能です。

画像の自動リサイズの設定

HttpImageFilterModuleを使うことで、Nginx側で画像のリサイズを行えます:

location /img {
    # image.100x100.jpgのファイル名でリクエストがあった場合に、
    # リクエストに応じて等倍率で画像をリサイズします。
    if ($request_filename ~ [a-zA-Z0-9]+\.([0-9]+)x([0-9]+)\.jpg) {
        set $img_width $1;
        set $img_height $2;
        rewrite ^(.*)\.[0-9]+x[0-9]+\.jpg$ $1.jpg break;
    }
    image_filter_jpeg_quality 95;
    image_filter resize $img_width $img_height;
    image_filter_buffer 1M;
}

これで良いのですが、この設定だけではサーバのリソースを多く使ってしまいます。
一番ベストなのは、別途 プロキシサーバ を用意し、そこにリサイズした画像をキャッシュさせることです。
同一サーバにプロキシ機能を持たせることも出来ます。
このあたりの話は以下のページにまとまっています。

Nginx image resize configuration
先ほど紹介した画像のリサイズと、それをキャッシュさせる設定がまとまっています。
この設定を参考にすれば、Nginxに画像のリサイズ機能を、簡単に設定出来ます。

画像の自動リサイズを取り入れる場合、アプリケーション側との連携が不可欠です。
フレームワークのヘルパーやフィルター機能を使い、リサイズした画像へのアクセスをしっかりと 管理 しましょう。
後述しますが、これを行わないと余分なリソースを使う羽目になります。

HTMLの縮小化の設定

Nginxのモジュールには、HTMLを縮小化するものも存在します。

HttpStripModule
HTMLを縮小化するサードパーティモジュールです。
まだ、バージョンv0.1と若く、バグもあるようです。

ここではこのモジュールを紹介するにとどめます。
理由は、このモジュールのバージョンが低く、 信頼性 が微妙なことと、Nginxが 動的なモジュール読み込み に対応していないことです。
サードパーティのモジュールを取り込むには、モジュールを取り込んだ状態でコンパイルする必要があり、セットアップが若干手間です。

これらの理由から、私はPHPの MinifyHtml を使った方法に切り替えています。

CakePHPの高速化設定

/img/posts/019/cakephp.png

CakePHPに基本的な施策である、以下の3点を設定します。

JavaScriptとCSSの圧縮
JavaScriptとCSSを縮小化し、 転送量 を削減します。
これに加え、JavaScriptは難読化し、さらに圧縮率を高めます。
JavaScriptとCSSの結合
プラグインや、機能別にモジュール化されたJavaScriptやCSSを結合します。
結合し、1ファイルに固めることで HTTPコネクション を大幅に減らせます。

さらに、アプリケーション側の HTML縮小化 も紹介します。

JavaScriptとCSSの圧縮と結合

CakePHPのコアデベロッパの一人である Mark Story さんが作成したAssetCompressPluginを利用します。

AssetCompressPlugin
JavaScriptやCSSの結合 を行ってくれるプラグインです。
フィルタ機能が付いており、結合したファイルに更に処理を加えることが出来ます。
このフィルタ機能と YUI-Compressor を使い、JavaScriptとCSSを圧縮出来ます。

ドキュメントが詳しいので、解説がほとんど不要です。
ここでは、 どのような形で使うのか を説明します。

1. 設定ファイルを作成する
設定ファイルには、利用するフィルタや、結合・圧縮したファイルのキャッシュ先を記述します。
YUI-Compressor に対応したフィルタはプラグインで用意されているので、ここのファイルに記述するだけで、JavaScriptとCSSを圧縮出来ます。
2. ビューに使用するCSSやJavaScriptを記述する

次の形で記述します:

<?php
$this->AssetCompress->addCss(
    array('tutorial.css', 'blog.css', 'top.css'),
    'top.css');
$this->AssetCompress->addScript(
    array('jquery.tablesorter.js', 'compare.js'),
    'top.js');

この形で、それぞれのページで利用したいCSSとJavaScriptを記述して行きます。

3. レイアウトにタグの出力を記述する

CSSはHTMLヘッダの中に以下を記述します:

<?php
$options = array('raw' => !(Configure::read('debug') < 2));
print $this->AssetCompress->includeCss('empty', 'default', $options);
print '<!--[if IE 7]>' .
    $this->AssetCompress->includeCss('empty', 'ie7', $options) .
    '<![endif]-->';
print $this->AssetCompress->includeCss($options);

JavaScriptは </body> の手前に以下を記述します:

<?php
$options = array('raw' => !(Configure::read('debug') < 2));
print $this->AssetCompress->includeJs('empty', 'default', $options);
print $this->AssetCompress->includeJs($options);

ここではデバグモードの場合に、rawオプションを指定しています。
このrawオプションは、圧縮や結合を行わずにタグを出力するもので、デバッグ時には必須です。

4. 圧縮・結合ファイルを作成する

次のコマンドで圧縮・結合ファイルを作成出来ます:

>>> cake asset_compress build
Scanning /home/sunomaru/workspace/av-jyo/views/words/index.ctp...
Scanning /usr/lib/cakephp1.3/cake/libs/view/elements/sql_dump.ctp...
...

Saving file for top.css
Saving file for compare.js
...

設定ファイルにタイムスタンプの付与をセットしておけば、自動で タイムスタンプ も付けてくれます。

このようにプラグインを使うとお手軽に、JavaScriptとCSSを結合・圧縮出来ます。

他言語のフレームワークでも同様のプラグインが存在します。

Ruby on Rails Guide: Asset Pipeline
Ruby on Rails 3.1ではJavaScriptとCSSの結合・圧縮の機能が組み込まれています。
django-assets
PythonのフレームワークDjangoのプラグインです。
Static Assets - The Pyramid Web Application Development Framework v1.2a1
PythonのフレームワークPyramidにも、フレームワーク自体に機能が組み込まれています。

その他のフレームワークでも、プラグインが存在するでしょう。
[フレームワーク名] Asset で検索すると、ヒットします。

アプリケーション側のHTML縮小化

PHPでHTMLを縮小化するには、 MinifyHtml を使います。

HTML.php - minify - Combines, minifies, and caches JavaScript and CSS files on demand to speed up page loads. - Google Project Hosting
minifyは ハイパフォーマンスウェブサイト のルールに従って、サイトを高速化することを目的としたライブラリです。
これは、そのうちのHTMLを縮小化するためのクラスです。
1ファイル1クラスで利用出来るので、手軽です。

CakePHPであれば、minifyライブラリをvendorsディレクトリに放り込み、 ヘルパー を作成します:

<?php
class MinifyHelper extends Helper
{
    function afterLayout()
    {
        $View =& ClassRegistry::getObject('view');
        $MinifyHtml = new Minify_HTML($View->output);
        $View->output = $MinifyHtml->process();
    }
}

これもそれぞれの言語で、ライブラリが存在するでしょう。
[言語名] HTML Minify で検索すると出てきます。

高速化チューニングのためのモジュール化

/img/posts/019/modules.png

高速化チューニングを実施するためには、各機能の モジュール化とそのテスト が重要です。
これを怠ると、チューニングどころの話ではなくなります。

圧縮や縮小によるバグの発生

しっかりと各機能をモジュール化していないと、 予想外のバグ が発生します。

最も簡単な例は、HTMLの縮小化によるバグです。
空白をスタイリングに使っていたために、レイアウトが崩れることがあります。
これを防ぐには、 HTMLが文書構造を担い、CSSがスタイリングを担うといった基本 を忠実に守ることです。

バグ原因特定の容易さ

モジュール化されていないソースコードはバグの原因の特定が困難です。
特にJavaScript難読化で発生したバグは、スパゲッティコードでは修正が不可能でしょう。

無駄なリソースの消費

高速化のためのモジュール を作らなければ、無駄なリソースを消費します。

例えば、Nginxの画像圧縮とアプリケーションの連携です。
ヘルパーやフィルタを作らずに、直接imgタグを指定すると、本来は圧縮処理が不要な画像も圧縮を掛けてしまう可能性が高くなります。
image.100x100.jpgとimage.100x110.jpgはNginxの画像圧縮の処理結果は同じにも関わらず、Nginxは圧縮を掛けます。
これは、URLによってNginxが画像をキャッシュするためです。
このようなことが起こらないように、このような処理は アプリケーション側で自動化 する必要があります。

機能のモジュール化 は基本です。
ソースコードを見直す上でも、高速化の施策は良いかもしれません。

AV女優.comで実施している施策

/img/posts/019/av-jyo.png

AV女優.com で実施しているのは、現在のところ以下の3点です。

  • Expiresヘッダ
  • Gzip圧縮転送
  • JavaScriptとCSSの圧縮

残念ながら、 AV女優.com ではここで紹介した施策を全て実行出来ていません。
原因はやはり、 機能のモジュール化を怠ったこと です。
JavaScriptとCSSの圧縮はなんとか設定しましたが、このためのリファクタリングにも時間が掛かりました。
今後は、画像の圧縮とHTMLの縮小化を進めていきます。

私自身の復習と今後の施策のために、サーバとアプリ側の高速化についてまとめてみました。
しっかりとしたアーキテクチャとコードを持っていれば、これらの施策は簡単に実施出来ます。
逆に、サボりにサボって適当なスパゲッティコードを書いていれば、これらの施策は一切実施出来ません。

言えることは一点です。
先を見据えて、 良いアーキテクチャを設計し、良いコードを書いていきましょう

何か、ご指摘があればコメント欄まで。

ハイパフォーマンスWebサイト ―高速サイトを実現する14のルール

続・ハイパフォーマンスWebサイト ―ウェブ高速化のベストプラクティス

ちょっと一言

sunomaru

@sunomaru

Python on vimの環境を整えようと思っているのですが、なかなか手が付けられませんね。vimの設定は触り出すと止まりません...。