Coding Web Performance

Webパフォーマンス最適化のためのコーディング手法

CSS Nite LP, Disk 9での講演ログ。

ビジネスインパクトとスタンス

Webパフォーマンスがビジネスにおける大きなインパクトといえば、Googleのランキングアルゴリズムにページの読み込み速度が組み込まれたことではないでしょうか。2010年4月9日の時点ではgoogle.comで英語で検索された場合にしか適用されません。また、パフォーマンスが考慮された検索クエリも1%未満とまだまだな状況ですが、いずれ日本でも適用される日がくるかと思われますので、今日はそのWebパフォーマンスについて、お勉強をしましょう。

しかし、このGoogleの評価は何をもって速いとするのでしょうか?公式ブログでは様々な指標をもとに比較検討しています…とのことでした。一体何を言っているのでしょうか… とりあえず、分かっている計測基準として2つあるので紹介したいと思います。

Google AdWords

まずはAdWords、Googleの広告サービスですね。実はAdWordsの方では2008年4月の段階から品質スコアの評価にページの読み込み速度が取り込まれていました。品質スコアが高ければ、広告の掲載順位は高くなり、料金も安く抑えられます。この計測方法なんですけども、AdWordのヘルプの方にはHTMLのダウンロード時間と記載されています。現在はまだHTMLファイルだけなんですけども、後々画像やJSファイルといった外部ファイルの計測も行われるようです。スライドの黄色部分に「リンク先ページの読み込み時間に、問題ありません」と書かれています。つまり、もしあなたの会社がAdWordsに出稿しているのであれば、Webパフォーマンスは既に対処しなければならない案件になっているということです。

Google Webmaster Tools

次にウェブマスターツールなんですけども、2009年の12月に「サイトのパフォーマンス」という項目が追加されました。これは文字通りサイトの読み込み速度に関する情報が表示されます。では、何をもって速い・遅いとするのかですが、このツールではGoogleが収集したデータの上位20%が早く、残りの80%が遅いと決めているようです。そのラインが平均読み込み速度が1.4秒、スライドの赤いラインです。そうゆうわけで、私たちの目下の目標としてはこの1.4秒以下に読み込み速度を抑えるということになります。こちらの計測方法ですが、Googleツールバーをインストールしたブラウザでなおかつページランク機能を有効にしたユーザーの読み込み込み時間となっています。AdWordsの方はHTMLファイルだけの読み込み時間ですが、こちらは実際の状態に近い計測手法となっています。

そんなわけで、ほかにもいろんな計測をしていると思うのですが、とりあえずはこの2つを意識して対策をしていけば良いでしょう。

高速サイトがもたらす利益

高速サイトがもたらす利益としては、検索だけではありません。ベージが速く読み込まれればそれだけユーザのストレスがなくなるわけですから、再訪問数の増加やセッションあったりのPV数増加にもつながります。そうすればおのずとコンバージョン率の改善、そして収益増加。また、売上が上がるということは顧客満足の向上した結果とも言えます。そういった売上の増加だけでなく、ページをコンパクトにまとめることでサーバーの転送量が少なくすることができれば、インフラコストや帯域幅の節約などにもつながります。

さて、こういったビジネスインパクトがコーダーに与える影響の面を考えると、コーダーはパフォーマンスという武器を手に入れることになるのではないでしょうか。というのも、デザイナーもそうですが、ただ単純にキレイなデザイン・キレイなコードというのはクライアントさんや、その上のマネジメント層にとっては興味のないことであって、彼らが知りたいのは売上です(そもそも彼らの仕事は数字を見ることですからね)。

ですから、商品をできるだけ目立たそうとして、デザイン的に無理な要求であったり、例外処理ばかりのコードになることも多々あります。そこで、私はデザイナーとして「トーンアンドマナーが…」など言うのですが全然聞いてくれません。ところが隣のSEOチームが「いや、それはSEO的に良くないのでやめて下さい」と言えば、素直に従ってくれます。

これはまぁ、SEO的に悪い=検索順位が下がる=売上が下がるといった思考パターンですよね。つまり、今回WebパフォーマンスがGoogleのランキングアルゴリズムに組み込まれたということで、その程度はどうであれ、SEOとパフォーマンスを絡めることができる。そしてパフォーマンス対策できるコーダーの存在感が高まるのではないでしょうか。社内の存在感が高まれば、それだけ自分の好きなこと・やりたいことを貫ける可能性も高まるのではないかと私は考えます。

とはいえ、コーダー・デザイナーは忙しいです。HTML5/CSS3は次から次へといろんなAPI、プロパティが出てきています。最近、jQueryナウいよねってことでJavaScriptを多用すれば、アクセシビリティやユーザービリティに関しても気を使わなければなりません。またサイト全体の情報アーキテクチャも考えなければならないし、アクセス解析も嗜みたいところ。そこで、今回取り上げるWebパフォーマンスですか?それはちょっと、無理無理カタツムリよーってことで。

とはいえ、来週あなたの上司が「パフォーマンス対策はどうなっとらんやー!」って聞いてきたらどうしましょうか?HTMLのコード量を減らしましょうか?JSの変数名やCSSセレクタを短いものに置き換えていきましょうか?そういった明後日の方向に進まない(まず最初に取り組まなければならない問題ではない)ためにも今回のセッションの目的は最小限の対策で最大限の効果をあげれるような対策を紹介します。

ボトルネックはどこか?

最小限の対策で最大限の効果をあげるためには、ボトルネックを探しそれを取り除いてあげればいいんですね。とは言っても、Webパフォーマンスという目に見えないものを対策するにあたって、それをビジュアライズするツールが必要になってきます、それが、このHTTPWatchです。こういったツールは他にもFirebugの接続パネルやSafariのリソースパネルなどがありますが、HTTPWatchはWindows上でIEとFxで動作するので採用しています。

このツール最大の機能はウォーターフォールチャートです。これが何を表しているのかというと、ページの読み込まれ方を表しています。この1つ1つのバーがページのコンポーネント、画像であったり、CSS、JSファイルであったりページの部品を表しています。横軸が時間経過を表しているのでこれが横に長ければ長いほど読み込み時間に時間がかかかっているのが読み取れます。とは言っても、バーの赤色とか緑色は何を表しているの?って感じですよね。HTTPWatchのヘルプには以下のように記載されていました。

  • ブロッキング
  • DNSルックアップ
  • コネクト - 接続
  • センド - 送信
  • ウェイト - 待機
  • レシーブ - 受信
  • キャッシュ読み込み

用語だけ言われても分かりませんので、具体的に見ていきますと。まず、サーバーとクライアントであるブラウザがあります。例えばCSS Niteの公式サイトを見たいとするのならば、ブラウザのURL欄にcssnite.jpと打ち込むでしょう。

リクエストの各段階

そうすると、ブラウザは打ち込まれたURLをIPアドレスに変換するためにISPなどに問い合わせにいきます。そして問い合わせた結果が届いた時間までがDNSルックアップです。

次に、そのIPアドレス上にあるサーバーに接続を試み、うまく接続できたならばOKのレスポンスを返します。ここまでがコネクトです。ソケット接続とかTCP接続などと呼ぼれる部分です。この部分は持続接続可能なので2回目以降は省略されます。

そして次がもっとも大事なHTTPリクエスト。cssnite.jpのindex.htmlなどのリソースを探しにいきます。そしてサーバーがこれねこれね、ということで見つけたリソースの最初のパケットデータが届いた時点がウェイトです。

そして残りのデータをすべて送りブラウザに届いた時点がレシーブです。このレシーブが一般的にファイルのダウンロード時間と考えらています。

このことをふまえて、もう一度先ほどのウォーターフォールチャートを見て見ますと、どうでしょうか?何色が目立ちますか?どう見たって赤色ですよね。

Webパフォーマンスと言われるとファイルのダウンロード時間を短くすればいいんじゃね?と考えがちなんですが、この場合、緑色の部分はなんてほとんどないですよね。

そもそもこのウォーターフォールチャートはどこのページを読み込んでいるかというと、後ほど紹介するとある一般的な企業サイトです。どのコンポーネントも数キロバイトの画像です。

このスライドから読み取れるに、4キロバイトの画像も5キロバイトの画像もダウンロードにかかっている時間はそう変わらなくて(だからと言って画像の最適化を怠ってはいいということではありません)、ダウンロード時間の何倍もの時間をウェイト時間に費やされているのが分かります。ウェイト時間はサーバーの待ち時間なので、私たちデザイナー・コーダーにとってはどうしようもできない部分です。必ずかかる税金のようなものです。

この状況をハンバーガー屋さんで例えてみますと、レシーブ時間をハンバーガーを作る時間、ウェイト時間をお会計にかかる時間とします。さらにこのお店は一回の注文につき一つの商品(リクエスト)しかできません。つまりチーズバーガーが2個欲しければ、チーズバーガー1つくださいと言って、作ってもらってお会計をして、またチーズバーガー1つのくださいと言って作ってもらってお会計するというようなめんどくさいことをしなければなりません。

もう一度、先ほどのウォーターフォールチャートを見てみますと、これは実はIE6で読み込まれたウォーターフォールチャートです。ここにはもう一つ重要なことが隠されていてます。

とりあえず、この同じページをFirefox3.6で読み込んでみますとこのようなウォーターフォールチャートになりました。皆さん、一体どこが違うでしょうか?ここでは、バー1つ1つの長さは比較対象にはしていません。それよりも、なんだかコンポーネントの読み込まれ方が違いませんか?どうやらIEのほうはなだらかに読み込まれていて、Fxの方は急な勾配になっているのが分かりますね。これがどういった違いによるものかと言いますと、

ホスト名毎の同時接続数

ホスト名毎の同時接続数によるものなんですね。HTTP/1.1の仕様ではひとつのホスト名(www.cssnite.jpのような完全修飾ドメイン)に対して同時接続できるコンポーネントは2つまでと決まっています。実際のブラウザの実装はどうなっているかと言いますと、FxやChrome,Safariなどは6や5つ同時接続できるのですが、ここで着目してほしいのはIE6,7のようなシェアの高いブラウザのが2つだという点です。

先ほどのウォータフォールチャートを見てみますと、実装どうりの挙動をしているのがわかりますね。より多く同時接続できている方が早いです。

ブロッキング

これをふまえた上でもう1度ウォーターフォールチャートみてみますと、このIE6のなだらかな階段状になっているのは冒頭で紹介したブロッキング時間(灰色の部分)に時間を費やされているのががわかります。サーバーに同時接続できるのは2つまでなので、それ以降のコンポーネントはダウンロードが完了するまで待たなければいけません。そしてダウンロード完了すれば次のコンポーネントが1つだけ接続されて、また次のコンポーネントは待たなければいけない。つまり、接続出来ていないコンポーネントは順番待ちをしている状態です。一番最後のコンポーネントを見ますとかなりの時間を順番待ち(ブロッキング)に時間を費やしているのが読み取れます。

この状態をまたとあるハンバーガー屋さんで例えてみますと、先程紹介したお店は1回の注文につき1つの商品しか頼めません。しかも、このお店はレジが2つしかないんですね。ですから、当然大勢のお客さんが群がれば(大量のコンポーネントをリクエストすれば)待ち時間が多くなる。当然、ページの読み込み時間も遅くなるということです。

つまり、ウェイト時間とブロッキング時間から、HTTPリクエストは非常にコストが高いものだということが理解できます。

とあるサイトの改善事例

ボトルネックはHTTPリクエストだということが理解できたので、今度は実際にどのようにHTTPリクエストを減らしていくのか、とあるサイトを例に考えていきましょう。

改善手法として使ったのは以下の5つです。

  • CSSスプライト
  • データURIスキーム
  • CSS,JSファイルの結合
  • CSS3プロパティ
  • 奥の手

CSSスプライト

まずは、CSSスプライトなんですが皆さんも使ったことがある人は多いのではないでしょうか。CSSスプライトを改めて紹介しますと、複数の画像を1つの合成画像としてグループ化して背景の位置指定を使って表示する方法です。簡単に言えば、1つの画像を使い回ししているのですね。例えば、通常であればグローバルナビメニューなどは各メニューごとに画像を切りだしてコーディングしていくと思うのですが、そうしてしまいますと、先程言いましたようにその画像1つ1つにHTTPリクエストが発生してしまいます。それでは良くないので、全部まるっとまとめちゃっいますと、それだけHTTPリクエストが減らすことができます。

実際どうなのかというところをHTTPWatchで確認してみますと、私の環境ではスプライト前とスプライト後では150msも変化が置きました。使っている画像も同じでコーディングの仕方を変更するだけでこれほど違いが出るのはとても有効な手段かと感じます。

そう考えますと、CSSスプライト最強じゃね?全部スプライトしちゃえばいいんじゃね?って考えがちですが、スプライトのジレンマというのがあります。ページ数、保守性、最適化。スプライトをする上でこの3つの中から2つしかとれないんですね。例えば、多くのページ数を保守性を保ちながらスプライトすると、最適化はちょっとあきらめなければいけない。また、多くのページ数を可能な限り最適化すれば保守性はごめんってなります。はたまた、保守性を意識しつつ最適化すれば、適用できるページ数は少なくなってしまいます。

先程の改善事例もHTTPリクエストを可能な限り少なくするには全部まとめれば良いのですが、それではグローバルナビに変更が起きた場合、関係ないローカルナビの位置も変更しなければならない手間も増えてしまいます。ですので、この改善事例の場合は3つのグループに分けて運用しています。

とはいえ、HTTPリクエストはスプライトを適用する前から11->3になったので8つも減らすことができました。

データURIスキーム

次にデータURIスキームですが、これは画像ファイルをリクエストすることなく画像をマークアップ中に埋め込むことできるシロモノです。一体何を言ってるのか分かんねぇと思うが、起こったことをありのまま話すぜ。まぁ、上記のスライドを見ればわかると思います。単にリソースのパスを書けばリクエストしにいっちゃいますんで、そうではなくて長ーい文字列に画像を変換しているのですね。このような変換はオンラインの変換ツールがありますので、利用すれば良いと思います。

これまたデータURIスキーム最強じゃね?って思うのですが、これにも弱点がありまして、まずIE7以下が非対応であること。改善事例のサイトでは残念ながらこのようなブラウザに対してはCSSハックで分岐処理して通常の画像を読み込ませています。次に、サイズ制限の問題。一番厳しいOpera7.2で4.1KBとなっています。なので、データURIスキームを適用できるのはアイコン画像のような比較的小さな画像だけと考えておきましょう。

そんなわけで、データURIスキームのおかげでIE7以下以外は HTTPリクエストを1個減らせることができました。

JavaScript/CSSファイルの結合

これはそのままです、単にまとめるだけです。例えば、事例のサイトではリセットCSSにYUIのreset.css,font.cssを使用し、プラス、ページの構造を記述したCSSを使ってました。これでは3つのリクエストが発生してしまいますので、全部まとめてcommon.cssなどにします。また、JSファイルもjQueryを読み込んでいたのと、そのプラグインJSを2つほど読み込んでいたのでこれもまたcommon.jsにまとめちゃいました。このサイトは僕1人で運営しているので、これで特に問題ないのですが、中・大規模サイトであれば複数人でのコーディングが考えられますので、ファイルのバッティングなどの問題があります。今、俺触ってるから君触っちゃだめ!みたいな。そうゆう場合は、PHPのinclude文など使って擬似的に@importを再現すれば良いでしょう。ただ@importすれば、それはHTTPリクエストしたことと同じですので注意が必要です。

そんなわけで、HTTPリクエストは6つから2つに。4つ減らすことができました。

CSS3プロパティ

CSS3は事例のサイトでは使ってはいないのですが、来るべき時代に備えときましょう。ここであげたプロパティに関して共通して言えることは、今まで画像を使わないと表現できなかったことがCSSのコード数行で実現できる点です。とはいえ、ここにいない子のことを考えますと、そのようなブラウザに対してどのような対応していくのかサイトオーナー、方針などともに考えていく必要性があるでしょう。

奥の手

今まで、4つの対策を見てきたのですが、どれもなにかしらの弱点があります。非常にコマッタピョンです。なにか一発お手軽簡単な方法はないかなーないかなーとt32k考えました。考えた考えた上でデザイン変えればいいんじゃね?(※コーダーさんに向けてのセッションでした)

これはちょっと冗談でなく、実体験からの考えです。WDE2009でNicole Sulivanさん(続・ハイパフォーマンスWebサイトにも寄稿している)のワークショップに参加しました。そこで僕は今回の改善事例のサイトを題材にパフォーマンスに関してアドバイスをいただきました。そこで言われたことは「JSファイルが重いようだけど、どこで使っているの?」と問われ、スムーススクロールを実現するためにjQueryとそのプラグインを読み込んでいますと言うと、「コストをかけすぎじゃない?」と言われました。彼女は元はJavaのエンジニアで、そういったエンジニアの方からデザイン・振る舞いに関して何かを言われたということはあまりないことでとても印象的でした。というか、ちょっと凹みました。

そこで、帰ってもう1度対策を考えてみました。まず、指摘されたスムーススクロール。これは長いページでページ内遷移する場合、通常であれば瞬間的に移動するのでページ遷移したと感じてしまう可能性があるので、ゆっくりスクロールして移動することでページ内遷移であることを示す効果があります。とはいえ、改善事例のサイトではそのような必要性があるような長いページもほとんどありませんでした。また、本当に使われていないかGoogle Analyticsで「このページの先頭へ」というリンクがどれほどクリックされているのか計測してみたところ月で数回程度、このサイトは月間2万PVあるので、ほとんど使用されていないことが分かりましたので、不採用としました。

デザインを考えなおしてみる

また、検索ボタンの画像も、画像だからといって検索されやすいこともないだろうと考えブラウザデフォルトのSubmitボタンに変更しました。(これも月数回程度の利用率だったので)

一般的にWebデザイナーというのはビジュアル重視しがちであまり数値を気にしない職種かもしれません。(かもねかもね.. )そこでコーダーさんは今回Webパフォーマンス対するコスト意識を学んだので、それをデザイナーさんに説明することでデザインを変更してもらうというのも1つの手段になるかと考えます。

以上のような対策をしてきた結果、冒頭で紹介したこのサイトのパフォーマンスは常時、1.4秒以下に抑えることができました。もう少し詳しく説明しますとトップページのHTTPリクエスト数は32から17に減少。平均読み込み時間は0.5秒。Googleが収集しているデータ全体の中で上位2%にはいる高速サイトとなりました。(2010/4/8)

今回の対策にあたり、私はHTMLを1から組み直したわけでもなく、サーバーの設定をカリカリにチューニングしたわけではありません。当初の目標に設定したとおり、HTTPリクエストを減らすという最小限の対策でこのような最大限の効果が得られたと考えています。

今日のまとめ

Webパフォーマンスがビジネスに与える影響は非常に大きなものですので、それをうまく利用することでコーダーさんの武器にしてください。

パフォーマンスをあげるにはHTTPリクエストを減らすことが一般的に最も効果が高いと思われます(そうでない場合はHTTPWatchなどのツールを使って検証してみてください)。ボトルネックはHTTPリクエストです。

しかし改善事例から学ぶに、 HTTPリクエストを減らす、1発簡単お手軽な方法は皆無です。それぞれ何かしらの問題を抱えています。そこでこれ以上減らすことができないのであればデザイナーさんに相談してみるのも1つの手でしょう。

反対にデザイナーはなぜそこで画像を使わなければならないのか?なぜそのデザインなのか?という説明責任を果たす必要があります。

長々、喋ってきたましたがコーダーさんもデザイナーさんもエンジニアさんも仲良くWebパフォーマンスに取り組んでいけたら幸いです。

ありがとうございました。