とあるサイトの高速化についてフロントエンドでやったことまとめ。

業務で携わっている案件なのですが、アクセス数の急増が見込まれるイベントがありまして。準備期間も少なく、バックエンド側でできることがほぼないという状況でサイトを落とさないようにがんばる!というお仕事でした。レガシーソースてんこ盛り。CSSプリプロセッサとか何それ状態。

そこで実施した対策のまとめです。サーバー・アプリケーション・サイトの構成によって、効果の大小はありますが、比較的効果があったと思われるものをつらつらと。

リクエストの削減とファイルサイズの最適化

まず一番最初に考えなければいけないのがリクエスト数です。すごいおおざっぱに言うと、WEBサーバー(ApacheとかNginxとか)への負荷は、PV数×リクエスト数です。PVがそんなに無くてもそのページのリクエストがめちゃくちゃ多いとそれだけでかなりの負荷になります。リクエストを半分にできれば2倍の人数がさばけるってことに、すげーおおざっぱに言うとなります。

ファイルサイズについてはそりゃ当然、軽ければ軽いほど良いわけで。

Lazy Loadを導入して画像を遅延ロード

Lazy Loadは、画像を遅延ロードするプラグインです。画像がウィンドウの描画範囲に現れるまでロードしないというやつです。

<img width="160" height="45" src="dummy.png" class="lazy" data-original="http://example.com/example.jpg" alt="" />

<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js"></script>

<script>
$(function() {
    $("img.lazy").lazyload();
});
</script>

追記:当然、何でもかんでもLazy Loadにして良いかという話ではないです。HTMLの仕様的にどうだとか、JSが無効だと見れなくなるだとか。個人的には、緊急回避の手段かなと思ってます。

CSS Spriteで画像を結合

メンテナンス性とのトレードオフな部分もあるんですが、それでも可能な限りCSS Spriteにするとそれなりにリクエストを削れます。

また、同じような画像だと、トータルの画像サイズも下がる場合があります。

CSSやJSも結合

当然、CSSやJSも結合できるものはとにかく結合。grunt-contrib-cssmingrunt-contrib-uglifyとかを使うと、それなりにメンテナンスせいを保ったまま、圧縮と結合ができるのでなかなか具合が良いです。

画像の圧縮

画像の圧縮も当然ですが、grunt-contrib-imageminや、 grunt-imageなどを使って圧縮するのが手軽です。

ただ、JPEGは元から圧縮されている関係上、この手の方法だとあまり効果が無い場合があります。ファイルサイズが大きいものは、Photoshopで地道に目視で圧縮しました。品質を60%くらいでもそんなに見た目変わらないので、やるなら思い切って削ると良いかなと。

CSS / JS / 画像などの配信の最適化

ファイルサイズを削ったり、リクエストを削ったりってのは、CSSの書き方とか静的リソースの作り方そのものの話でしたけど、これをいかにして高速に配信するかというのも結構大事です。

静的リソース用サーバー立てる

おいおい、フロントエンドなのかそれは、と言われたらそりゃそうなんですけど。

静的リソース用サーバーとアプリケーションサーバーに求められるものは当然別です。画像なんて一度アップロードしたら更新しないなんてことはざらにありますし。でもHTMLはそれなりに更新がありますし、CMSとかが入ってるとまた大変な話。

サーバーからいじれたら、nginxをサーバーのフロントエンドに立てて、直接nginxから見に行くようにするとかいろいろあるんですが、そういうわけにもいかなかったわけで。今回は事情によりApache。

Amazon S3とか活用するのも有りだと思います。というかたぶんそっちの方がイマドキだし、絶対早い。

Keep-Aliveを有効に

普通に、HTTPでリクエストが飛ぶと、TCPハンドシェイクという動作が発生します。1リクエストごとに、これが発生してしまうのですが、Keep-Aliveを設定しておくと、一定時間ハンドシェイクを省略できるというやつです。

細かいこと言い出すときりがないので何ともいえないんですが、リクエスト数が多ければデフォルト値でもそれなりに有効な気がします。

<ifModule mod_headers.c>
Header set Connection keep-alive
</ifModule>

サーバーによっては、.htaccessに上記の記述で有効になります。まぁ.htaccessでやる話でもない気はしますけどね。

Expiresヘッダの設定

Expireはファイルの有効期限です。この有効期限内であれば、ブラウザのキャッシュを利用してくれます。これが未設定だと1週間くらいで失効してしまうのですが、これを適切に設定することでリクエストをそれなりに削れます。

今回の案件は、7割か8割くらいはリピーターだったりで初見さんが少なかったのもあって、これもそれなりに大きな効果があったのかなと思います。

最近では、.htaccessで設定できることが多いです。拡張子ごとに設定できます。以下のコードだと、とりあえずCSSとJS、画像は1ヶ月キャッシュを有効にしてます。

ExpiresActive On
ExpiresByType image/gif "access plus 2592000 seconds"
ExpiresByType image/png "access plus 2592000 seconds"
ExpiresByType image/jpeg "access plus 2592000 seconds"
ExpiresByType image/x-icon "access plus 2592000 seconds"
ExpiresByType text/css "access plus 2592000 seconds"
ExpiresByType application/x-javascript "access plus 2592000 seconds"
ExpiresDefault "modification plus 1 week"

CDNを活用する

jQueryだとかfontawesomeとか有名どころのライブラリは、CDNから呼びましょう。キャッシュがあれば、当然それを使ってくれます。cdnjs.comなんかは有名ですね。

また、一つのホスト(origin)への同時接続数はブラウザの標準設定で6つまでなので、そのぶん他のリソースをロードできます。

まとめ

フロントエンドの高速化といってもやることめちゃくちゃ多いし、幅広いなーというのが印象でした。これ以外にもサーバーにサブドメインを複数割り当てて、一度にロードするファイル数を水増ししてみるとかいろいろやってみました。

結果としてとりあえず、Lazy Loadはかなり強力な手段だなと思いました。リクエストが発生しなければそもそもサーバーに負荷はかからないので。細かい画像やバナーも多かったので、リクエストがこれだけでかなりの数削減できました。

結局フロントエンドチューニングに銀の弾丸はやっぱり存在しないし、システム構成等でもだいぶ変わってくる部分は大きいと思います。

レガシーなソースでもgruntとか入れるメリットはでかいなぁとも感じました。

やっぱり、基本的には「塵も積もれば山となる」的発想で立ち向かうしかないのかなと。そもそも画像やCSSにしたって一つ一つのサイズはたいしたことないという事を考えると、その「塵が積もって」サイトを遅くしてるんですからね。

参考