モバイル時代を生き抜くためのWebパフォーマンスモデル「RAIL」 ~Response、Animation、Idle、Loadから来る「速さの目安」を知って改善しよう!~

第3回Webの「Load(読込み)」を改善しよう(前篇)

はじめに

Webページの読み込みは、ブラウザが生まれて以来、変わることなく持ち続けたコアの機能です。誰もが無意識のうちにその機能に触れており、もはや当たり前のようにそこに存在しています。

前回その機能の中には他のタスクをブロックするような処理が含まれており、ブラウザを複雑にしていると説明しました。ブラウザはその仕組み上、CPUやネットワークのリソースをフル活用することが難しいのです。Webのパフォーマンスモデル「RAIL」では、これらの課題をなんとかクリアし、Webページの読み込みを表すL(Load)が1,000ミリ秒未満となることを推奨しています。これは、とてもシビアな時間です。

一言にLoadとは言っても、ユーザ体験に及ぼす要因は多く、そのメトリクスは多岐に渡ります。皆さんのWebサイトの置かれている状況に合わせて、Loadの意味を適切な指標に読み替え、最適なブロック解決方法を選択し、最適化を進めていかなくてはいけません。

このあたりの理解を持つために、まずはブラウザの中でどのような処理が行われているのか、前回とは別の観点から見てみましょう。

Webページの表示には、様々なブロック要因が潜んでいる

ブラウザは、ハイパーリンクがクリックされたり、あるいはブックマークからWebサイトを指定されると、DNSによる名前解決や、サーバとのTLS鍵交換、TCPスリーウェイハンドシェイクなどを経て、HTMLドキュメントの読み込みを開始します。

図1 ブラウザでURLを開くときの処理
図1 ブラウザでURLを開くときの処理

HTMLドキュメントは、TCPを通じてブラウザ内のバッファへと徐々に蓄積されていきます。ブラウザは何も処理を行っていない場合、バッファに置かれた分のHTMLドキュメントを読み込み、パースし、レイアウトを決定し、スクリーン上へと描画します。Webページは、HTMLドキュメントが読み込まれた分に応じて、徐々にスクリーン上へと描かれていくことになります。

その際、パース処理は大きく分けて2種類の状態を持ちます。Chromeではこれらを「ドキュメントパース」「プリロードスキャン」と呼んでいます。これらの状態は、FirefoxやIE、Safariであっても共通して持っています。Web標準として決められたものではありませんが、実質的にどのブラウザも同じように振る舞います。

ブラウザは通常時は、ドキュメントパースが動き続けます。バッファから取得したHTMLドキュメントを最初から順番に解釈し、その過程でCSSや画像ファイルなどのリソースが必要だとわかると、その都度ダウンロードを開始していきます。そして淡々とレイアウトを決定し、ブラウザ上への描画を行っていきます。

図2 ドキュメントパース
図2 ドキュメントパース

しかし、JavaScriptとCSSだけは「描画ブロッキング」という特徴を持っています。これらはリソースをダウンロードし内容を解釈して実行されるまで、その先のHTMLドキュメントをパースすることができません。結果、レイアウトを決めることができないため、描画が一切行えなくなってしまいます。

ダウンロードの処理自体は平行して行うことができ、1つのドメインに対して、最大6つまで接続できます。なのに、JavaScriptやCSSが発見される都度、HTMLドキュメントのパースを止め、リソースがダウンロードされるのを待っているようでは、ネットワークの効率がとても悪くなってしまいます。どうせなら、複数平行してリソースをダウンロードしてしまいたいところです。

では、どうすればよいでしょうか? ブラウザはブロックされている間も、HTMLドキュメントは淡々とダウンロードし、バッファに置いています。その中身を覗けば、これから先ダウンロードを必要とするようなリソースを見つけることができるはずです。

そこでブラウザは、ブロック中も、バッファ内のHTMLドキュメントを常に最後までスキャンし続け、リソースだけを先にダウンロードするという動作を行います。これを、プリロードスキャンと呼びます。

図3 プリロードスキャン
図3 プリロードスキャン

プリロードスキャンは、通常時のドキュメントパースの時とは違い、あまり複雑な処理を行いません。淡々とリソースを見つけ、素直にダウンロードし続けます。CSSで非可視化され、今すぐに必要とはしないような画像ファイルであっても、それを知る術はプリロードスキャンにはなく、どんなリソースであっても無差別にダウンロードしていきます。リソース読み込みに対する制限(Content Security Policy)がHTMLドキュメント内で行われている場合、何もスキャンしないという極端な選択を行ったりもします。

プリロードスキャンはネットワークを効率的に扱うことができる反面、ブロックの要因となるリソースの特性次第では、その読み込みを遅延させてしまうリスクを持ちます。ブラウザは平行してリソースをダウンロードできる、とは言いましたが、その際に利用するネットワークは同じものを共有しており、帯域にも限界があります。他のリソースのダウンロードに帯域が逼迫され、ブロック要因となっているリソースの読み込みが遅れてしまうということは、往々にしてよくあることです。

さて、Webページの読み込みの仕組みがだいたい把握できたところで、ここからは「速さ」を決定づける3つの指標について紹介します。

  1. Speed Index
  2. Hero Image Custome Metrics
  3. ATF Time

1. Speed Index

Webページの読み込みを行う際、どれぐらいの速さで読み込まれたのか。それを表す有名な標値としては「ページ読込時間(PLT⁠⁠」が挙げられます。Webページを構成する様々なリソースが、全て読み込みを終えるまでにどれだけの時間を要したのか計測します。RAILのL(Load)は、多くの場合この指標を指します。PLTは直感的で、多くの現場で活用されていますが、昨今の複雑なWebにおいては、それだけでは不充分と考えられています。

何が欠けているのでしょうか? 一言で言うなら「描画の過程(Visual Progress⁠⁠」です。ページ読み込みの間に、どのようにWebページが表示されていくかという、その過程の評価が重要とされています。

例えば、PLTが12秒だったとします。その12秒間中10秒間が、ほとんど表示されている場合とそうでない場合とでは、ユーザから感じられる「待ち」のストレスは大きく変わってきます。Webページの読み込み時に、ユーザの意識をどれだけ速く次のコンテンツへ向けられるのか。クリックして一瞬にして目を向けられるコンテンツがあるのと、10秒間も見るものが無いのとでは、ユーザが得られる体験は全く異なります。

図4 WebPagetest - Speed Indexより
図4 「WebPagetest - Speed Index」より

これを評価するため、コミュニティベースでSpeed Indexと呼ばれる指標値が考案されています。単純にPLTとその過程のパフォーマンスを計測したいというなら、最も適した評価方法といえます。簡単にできるため、あなたが運営しているWebサイトもWebpagetest.orgを使って、計測してみるとよいでしょう。

図5 WebPagetestより
図5 「WebPagetest」より

運用している環境、つまりブラウザのJavaScript APIから計測するのには、Navigation Timingが有名です。ただし、さきほどのPLTを計測する分には充分ですが、Speed Indexにまで踏み込んだ計測値は取得できません。Navigation TimingでSpeed Indexを取得できるようにしないか? という議論はW3Cでも一時期議論されていましたが、実現には至っていません。

現場ではどのような方法で計測されているのか? Navigation Timingよりも柔軟な計測が行えるUser Timingがよく活用されています。JavaScriptの実行は、ドキュメントパースでscriptタグが発見されたタイミングで行われることから、この特性を活かしてUser Timingで計測を行います。チェックポイントとなるタイミングをJavaScriptでマークし、どの程度の時間を要したのか記録していくわけです。Speed Indexと同じ精度とまではいかなくても、⁠過程」を評価するうえでは、充分に有益な情報が得られます。

Speed Indexを改善する代表的な対策としては、ビジュアルを決定する要因であるCSSのみをhead要素内から読み込むようにし、ユーザイベントをメインとして扱うJavaScriptの読み込みは、body要素の末端に指定するという方法です。これは多くWebサイトでは、もはや当たり前の取り組みとして行われています。

また、JavaScriptやCSSをMinifyしたり、1つのファイルにまとめてConcatするというアプローチも有名です。ファイル数と容量を抑えていくことは、ブロックの緩和に高い効果を与えます。最近だとUglifyJSを用いてJavaScriptの内容を解釈したうえでの最適化を図ったり、あるいは、UnCSSを用いて使用していないCSSプロパティを削除するといった、コンテンツに踏み込んだ改善も行われるようになっています。

ただし、それだけでは解決できない課題もあります。

2. Hero Image Custome Metrics

2014年頃から、多くのWebサイトでスクリーン全体を覆う巨大な画像を扱うことが多くなりました。このような画像を「ヒーローイメージ(Hero Image⁠⁠」と呼びます。

ヒーローイメージはそのページのメインコンテンツであり、無くてはならない重要な存在です。読み込みを完了していない場合、ページ内の情報が全く読み取れないというケースもあったりします。

しかし、ヒーローイメージはその特性上、JavaScriptやCSSのような描画ブロックなリソースの影響を受けにくく、当然ですがこれらに対する対策も意味を持ちません。仮にプリロードスキャンでダウンロードを開始したとしても、ファイルサイズが大きいため、ドキュメントパースに切り変わり描画を開始した段階でも、まだ読み込みが完了していないことが多かったりします。

図6 stevesouders.com - Hero Image Custom Metricsより
図6 「stevesouders.com - Hero Image Custom Metrics」より

対策としては、CDNを活用するといったネットワークパフォーマンス向上対策あたりが妥当です。ただ、ブラウザが動いている端末がボトルネックとなっている場合は、Kraken.ioなどのツールを活用し画像自体のファイルサイズを変えてみたり、srcset属性を活用したレスポンシブイメージに挑戦してみると良いでしょう。読み込み優先度を指定できるようにしようという検討も進んでおり、Web標準ではResource Hints - Hint probabilityの延長上に、その対策が期待できそうです。

運用環境下でのブラウザ内からの取得方法について、Steve Soudersのブログのエントリが有益です。その中でも、以下のようなヒーローイメージの計測方法は、最近のブラウザの開発者ツールを活用しても計測できない、貴重な値を取得することができます。

計測
<img src="hero.jpg" onload="performance.mark('hero1')">
<script>performance.mark('hero2');</script>
結果の取得
<script>
// 時間差分の計算
window.performance.measure('hero_render_time', 'hero2', 'hero1');
// 結果をコンソールへ出力
var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length(); ++i) {
  if( items[i].name === "hero_render_time" ) {
    console.log(items[i].duration);
  }
}
</script>

このサンプルでは、User Timingを活用し、プリロードスキャンでダウンロードを開始したヒーローイメージが、ドキュメントパースによってスクリーン上への画像の描画が開始されて、完了するまでにどれぐらいの時間を要したのかを取得できます。これはSpeed Indexを改善していく中で、良いヒントとなるでしょう。

3. ATF Time

Speed Indexは非常に有益な指標値になりますが、モバイルのようなシビアな環境ではまだ不足していると考えられています。特に、スマートフォンの場合は、最初にスクリーンへ表示される領域はとても小さいうえ、ネットワークの帯域もそこまで大きくないため、デスクトップ向けサイトよりもさらに高度な対策が求められます。

モバイルで重要視される指標としては、⁠ATF(Above The Fold⁠⁠ Time」がよく取り上げられます。ATFとは、Webページアクセス直後の、スクリーン内の状態です。この領域の描画は速ければ、全てのコンテンツが読み込まれていなかったとしても、ユーザの意識はWebページのコンテンツに向けられるようになり、体験は大きく改善されます。

ATF Timeを速くするための対策としては、head内のCSSのインライン化が取り上げられます。より具体的には、ATF内のレイアウトを決定するCSSのインライン化することで、ATF内だけはドキュメントパースのみで全て完結できるようにしてしまおうというものです。

図7 PageSpeed Insights - Inline CSSより
図7 「PageSpeed Insights - Inline CSS」より

モバイルにおけるパフォーマンス改善には、画像やアニメーションを極力CSSに置き換えることが推奨されるため、CSSの扱いが非常に重要視されます。こうした中、CSSのインライン化については海外でも賛否両論で意見が大きく割れいます。Googleはついこの前までは全力でこの方法を推奨していましたが、最近はやや控えめな傾向に見えます。とはいえ、Googleの検索ページは未だにこの方法を採用しており、この対策に依存していることが否めません。

ATFが描画されるまではプリロードスキャンは一切させたくない、インライン化されたCSSだけで動くようにしたい、という要求は強く、W3Cでも一度はそれを助ける機能を作ろうと議論にはなりました。しかし、実現には至っていません。

筆者はモバイルに限定して本対策を活用しています。モバイルはそもそもCSSのファイルサイズがそこまで大きくならないため、TCPのオーバヘッドとキャッシュの効果を秤にかけた結果、インライン化の方がトータルに見た時に高い効果を上げることが多いためです。

ツールとして代表的なものは、Filament GroupのCritical CSSが挙げられます。これは、ATFを構成するCSSのみを別ファイルとして出力することができるツールです。これのGrunt版は、実際の運用のワークフローへの組み込んでいく上で大きな助けになるでしょう。

最後に

今回のお話は、主にWeb開発者に向けて、Loadが抱えている問題とその解決方法について紹介しました。開発者側でのパフォーマンス改善策は、あまり本質的なものでもなく、泥臭いことが多いように見えたでしょう。

ただ、最近はインフラやアーキテクチャといったレベルでも大きな変化が起きています。その対策次第では、今回ご紹介した対策の一部は、不要になったりもします。このあたり、次回にご紹介させていただきます。

それではまた、お会いしましょう。

おすすめ記事

記事・ニュース一覧