7つのウェブ性能障害を回避する
7つのフロントエンドの障害物を取り除いてウェブサイトのスピードを加速させる
ウェブサイトの速度が遅いとビジネスに悪影響を与えます。GoogleやAmazon.comなどといったウェブのリーダーは、ウェブサイトの速度が落ちると売り上げも減少すると報告しています。その逆にウェブサイトの速度が上がると売り上げ増大につながります。しかしウェブサイトの性能向上に投資する理由は売り上げ増大だけではありません。他にも以下のような理由が挙げられます。
- お客様はウェブサイトのスピードが速いと信頼性や信用度も高いとみなす
- ページを読み込むのを待っている間にユーザーは思考の脈絡を忘れてしまう
- ウェブサイトの速度が遅いと「壊れている」ように見える
- ウェブサイトの速度が速いほうがGoogleの検索アルゴリズムで上位にランクされる
私はIBM iとPHPの性能を専門とするコンサルタントとして、ウェブの性能が低いためにプロジェクトが中止になりそうなときに、情報部門を支援するよう要請されてきました。反応が遅いサイトをリハビリさせるのを助けてきて、読み込みに時間がかかるページやお客様の不満の原因にはいくつか共通した誤りがあることがわかりました。本稿ではウェブの性能を遅らせる7つの共通した障害物あるいは障害について説明し、次のウェブ・プロジェクトを開発するときにそうした障害物を避ける方法をご紹介します。
多くはフロントエンドで発生している
開発者はデータベースのスループットなどといったサーバー・レベルの測定基準に目を向けがちです。そのためHTML、JavaScript、カスケード・スタイルシート(CSS)、HTTPヘッダーなどといったフロントエンドの要素の性能を軽視してしまいます。モダンWeb2.0の要素やウィジェットの使用が増えているため、フロントエンドを効率よく実行できるように管理することがここ数年重要になってきています。フロントエンドの性能を理解している開発者はウェブ開発プロセスの早い段階からガイダンスを求め、サイトやアプリケーションが運用に入るときに問題を回避しています。
複雑なウェブ
ウェブのページが純粋なHTMLだけの1つのファイルで構成されているという時代は過ぎ去りました。今日の複雑なウェブページは多数のファイルで構成されていて(たとえば、画像、CSS、外部JavaScript)、各々のファイルが図1に示すHTTPリクエストのサイクルに従います。
- ブラウザがファイルをリクエストする
- リクエストがウェブ・サーバーに到達する
- ウェブ・サーバーは要求されたファイルを応答としてブラウザに送信し、そのファイルが構文解析されて目に見えるコンテンツとして表示される
このサイクルがどのように動作するのかを理解するため、IBM iのコンテンツが欲しいのでiProDeveloper.comに行ってみようと決めたとしましょう。同サイトのURLを入力すると、ブラウザはホームページの主となるHTMLコンテンツをリクエストします。次にウェブ・サーバーがリクエストされたHTMLで応答し、このHTMLがブラウザに戻ってきて構文解析されます。応答内容が構文解析されている間、他の要素(たとえば画像、CSS、JavaScript)が識別され、リクエストされ、そして一度に1つのファイルが順番にダウンロードされます。これらのファイルが構文解析されるときに、そのうちのいくつかはさらにダウンロードする要素を参照します。
任意のウェブページは多数のHTTPリクエストを生成する可能性があります。図2に示した通り、各HTTPリクエストは複数のレイヤーを通って送られます。ウェブ・サーバーが存在しているネットワークの中からだけテストしていますか? もしそうであれば、ネットワーク外のユーザーが直面するファイルごとの遅延を経験していないでしょう。ユーザーがDSLやモバイル接続を導入するにつれて速度は変わる可能性があります。ファイヤーウォール、プロキシ・サーバー、誤ったネットワーク構成などがさらに各ファイルの流れを邪魔します。賢明なガイドライン: HTTPリクエストの数とサイズを減らす。
性能を可視化するためのツール
性能の測定基準を可視化するのに便利なフリーのツールが利用可能です。有名な2つのツールがFirebug (https://addons.mozilla.jp/firefox/details/1843)とWeb Page Test (http://www.webpagetest.org/)です。FirebugはFirefoxのアドオンでジョー・ヒューイット氏らによって開発されました。Firebugの特徴は必要なときにいつでもブラウザ中にウィンドウをポップアップ表示させることができることです(図3)。Web Page Testはクラウド・ベースのツールでパトリック・ミーナン氏が開発し、ネットワークの外部から速度を測定するものです。実際、Web Page Testはさまざまな接続タイプ(たとえばDSLやT1)でいろいろな地理的場所からさまざまなブラウザを使ったアクセスをシミュレーションできます。
賢明な性能専門家は性能を測定して問題を診断するだけでなく、こうしたツールを使用して「改善前後」の画面ショットを保存しておいて、変更を加えるたびに改善の度合いをトラッキングしやすくしています。こうしたドキュメントは改善を可視化したものを提供してくれ、性能改善のための施策の価値を企業の役員などといった他の人に説明する際に役立ちます。
障害物の回避
ウェブの性能を観測して改善する施策において、私は7つの共通した障害を見つけました。これらの障害を回避するためにそれぞれの解決策を以下に説明します。
障害1: 圧縮を利用しない
圧縮されていないファイルは圧縮されているファイルに比べてサイズが大きいです。特定のウェブ・サーバー・ディレクティブを追加することでHTML、スタイルシート、JavaScript、JavaScript Object Notation (JSON) などといったすべてのテキスト・ベースのウェブ出力のサイズを劇的に小さくすることができます。このテクニックは圧縮と呼ばれており、Apacheではgzipまたはmod_deflateで実装します。Apacheでは圧縮はデフォルトでは無効になっていますので、この簡単なテクニックを見逃しがちです。
圧縮はすぐに恩恵をもたらします。たとえば、Netflix(有名なストリーミング・メディアのプロバイダ)は圧縮を実装することでユーザー向けのサイトの性能を13~25パーセント向上させました。さらに、同社はアウトバウンドのネットワーク・トラフィックを半減させることもできました。
IBM i用のApacheディレクティブの例は以下の通りです。
- 圧縮を実行するIBM iモジュールを読み込む
- 圧縮するコンテンツ・タイプを指定する
圧縮されたコンテンツがFirebugでどのように見えるかを図4に示します。
障害2: 多すぎるHTTPリクエスト
リクエストが多すぎるとブロッキングの原因となり他のリクエストが待たされることになります。ブラウザからのリクエストは全てウェブサーバーへ往復して来たことをもって成功したことになります。最近のブラウザは任意のウェブサーバーに対して2~6件のリクエストを並行して送信することができますので、ブラウザはこのリクエストが完了するのを待ってから次のファイルのリクエストを送信しなければなりません。ブラウザが待っている間の時間のことをブロッキングといいます。
良く知られているJavaScriptやCSSファイルの数を減らす方法はファイルを結合してファイルの数を減らし、そこから生じるブロッキングを避けようというものです。たとえば、複数の小さなCSSファイルを1つの大きなCSSファイルに結合することができます。開発の際にはサイズが小さくてモジュール性のあるファイルを使用したいと思うでしょうが、実運用環境ではそうした複数のファイルを統合してファイルの数を減らします。非常にサイズの小さいファイルについては、そのコンテンツをHTMLページのコードの中に移すことや特定の外部ファイルを一緒に削除することも検討してください。
障害3: 多すぎるAjaxリクエスト
Ajax(Asynchronous JavaScript and XML、非同期JavaScriptおよびXML)を適度に利用すると性能が向上しますが、多量に使用するとブロッキングの原因となります。Ajaxを使用すると、ウェブページ全体を再読み込みせずにページの一部だけを更新することによって、応答性の良い迅速なウェブ体験を提供することができます。Ajaxは、一時はあまり知られていないテクニックでしたが、jQuery、EXT JS、DojoなどといったJavaScriptライブラリのおかげで大変使いやすくなりました。これらのライブラリはグリッド、ドロップダウン・ボックス、入力の自動補完ボックスなどのグラフィカルなウィジェットを提供します。ウィジェットのデータは通常サーバーからAjax経由で到達し、JSON中でフォーマットされた小さなサイズの応答として戻されます。
では何が問題なのでしょうか。それは、良いテクニックでも使いすぎは良くない、ということです。1ページの読み込みの中に最大で4つのAjaxリクエストが埋め込まれているのを見たことがあります。ウィジェットを使ったテクニックでコードをモジュール化することができますが、各ウィジェットがサーバーにデータの結果をバラバラに要求することになり、同時リクエストが多すぎる状態となります。次のようなシーケンスを考えてみてください。まずメインのページを読み込み、次にAjaxのリクエストが開始されて全てのリクエストデータを一度に要求します。リクエストが溜まっていくにつれてブロッキングが発生します。ブラウザはシーケンス中の全てのリクエストが完了するまで待たなければなりません。
Ajaxリクエストが多すぎて最初のページの読み込みに時間がかかりすぎているのではと思うのであれば、最初に読み込むときに、ウィジェットが必要とするすべてのJSONデータをすでに含んだ状態にしておくような設計をしてみてはどうでしょうか。その後でユーザーが各ページでやり取りをする際に、ウィジェットはAjaxを経由して最新のデータを要求することができます。
Ajaxに関するもうひとつの障害は同じデータに対して繰り返しリクエストを送ることで、これはサーバー資源の無駄遣いになります。もしそのデータがかなりの場合において静的なデータで(たとえば地理的地域や州の名前の一覧)めったに変更がないものであれば、何度も繰り返しデータベースから再取得する必要があるでしょうか。そうせずにキャッシングの戦略を選択するのです。ブラウザ・レベルで、あるいはサーバー上にデータをキャッシュしておくのです。たとえばPHPを採用しているのであれば、Zend Data CacheやPage Cacheといったツールを使用することでデータのキャッシュが容易になります。
障害4 : ブラウザのキャッシングが不十分
ブラウザはファイルをキャッシュするように構成されていますが、どのファイルをどれくらいの期間キャッシュするのかのガイダンスが必要です。ブラウザはウェブの速度を上げるように最善を尽くしますので、通常ブラウザはダウンロードしたページとファイルをキャッシュし、キャッシュされたコピーを以後のサイトの訪問者に対して使用しようとします。ブラウザは最新のデータになるようにサーバーをチェックして、キャッシュされたファイルの最終更新日と比較します。キャッシュされたファイルが最新のものであればサーバーはHTTPコード304(「not modified」)をブラウザに対して送信し、キャッシュされたコピーを使用しても大丈夫であると伝えます。しかしこの最終更新日をチェックするという小さなリクエストでさえもサーバーへの問い合わせが必要となり、ブロッキングの原因となり得ます。大量のHTTP 304ステータス・コード(「not modified」)とその結果生じたブロッキングを図5に示します。
ではこの問題をどのように解決したら良いでしょうか。静的ファイルが(たとえばJavaScript、CSS、画像)が実運用システム上でめったに変更されないのであれば、それらのファイルにExpiresヘッダーを設定してください。Expiresヘッダーを設定することで、ブラウザに対してそれらのファイルを長期間サーバーにチェックすることなくキャッシュするよう指示します。Apacheの構成ファイルhttpd.conf中でキャッシュの強化を有効にした例を図6に示します。
障害5: ファビコンがない
ブラウザはすべてのサイトに「お好みのアイコン(Favorite icon)」別名ファビコン(favicon)があると思っています。サイトにファビコンがない場合、ブラウザは仮想のドアが開くまでノックをし続けます。ファビコンは小さなサイズの画像で、ウェブ・ブラウザがサイトのアドレスとタイトルの隣に表示するものです。ブラウザは、favicon.icoという名前のファイルを各サイトのドキュメントのルートにリクエストすることでその画像を入手します。ファビコンが見つかればブラウザはそれを「覚えて」おきます。ファビコンが見つからない場合、ブラウザは繰り返しそのファイルをリクエストします(ウェブサーバーのアクセス・ログを見てみれば簡単にそれがわかります)。ファイルが見つからないときに実行される特殊なエラー処理アプリケーションのロジックで、知らずにオーバーヘッドをかけてしまってサーバーにより大きな負荷をかける原因となっているサイトもあります。ファビコンのあるサイトを図7に、ファビコンのないサイトを図8に示します。
障害6: リダイレクトが多すぎる
リダイレクトも使いすぎると害を及ぼすもう一つのプログラミング・テクニックです。リダイレクトはウェブページにとっては通常のリクエストとして始まりますが、違いはその応答にあります。ウェブサーバーはロケーション・ヘッダーをつけて応答し、実際のページは他の場所を見るようにブラウザに伝えることがあるからです。リダイレクトを使う一般的で正当な理由には以下のものがあります。
- URLの標準フォームを確立して検索エンジンが正確にその人気度を測定できるようにするため。そのようなリダイレクトを見るには、alanseiden.comのサイトに行って「www」を追加してwww.alanseiden.comとしてみてください。ステータス「301 moved permanently」を図9に示します。
- ユーザーがログインを試みた後にそのユーザーを適切なページにダイレクトするため、このタイプのリダイレクトは、ウェブ・ベースの電子メール・プログラムにログインしたときに見られます。正しい証明書によりメッセージのリストにリダイレクトされます。証明書が正しくなければログインのページに戻されます。
時折リダイレクトを使用するのは構いませんが、コンテンツを一切取り出さないうちに2つあるいは3つのリダイレクトにはまりこんでどうしようもなくなってしまったアプリケーションをいくつか目にしてきました(図10)。リダイレクトごとにもう一度インターネットを渡って旅をし、もうひとつの接続スロットを使い、そしておそらくサーバー上でプログラム・ロジックがさらに実行されることになります。
障害7: クローズした接続
各ファイルをダウンロードするたびに接続がクローズしてしまうとリクエストごとにオーバーヘッドを加えていることになります。現代のウェブサーバーは永続的接続をサポートしており、ウェブページを構成する多数のファイル(たとえばHTML、JavaScript、CSS、画像など)をリクエストして受け取っている間に、ブラウザがTCP接続を再利用できるようにしています。このウェブサーバーの機能はキープ・アライブ機能と呼ばれており、TCP接続を指定された期間、指定されたファイルの最大数に対してオープンにしておくというものです。しかしこの機能を無効にすると、TCP接続はリクエストのたびにクローズされてしまいます。
Firebugは「Connection: close」と「Connecting」に要した時間を示すことでキープ・アライブ機能が無効になったことを知らせます。キープ・アライブ機能が有効であれば防げていたかもしれない、接続に費やした時間の極端な例を図11に示します。キープ・アライブ機能を有効にすることによる恩恵を図12に示します。
キープ・アライブの最適な設定はどのようなものでしょうか。使用しても構いませんが使いすぎないようにしてください。というのも、TCP接続が自分専用になっている間TCP接続を占有しているからです。1ページまたは2ページを読み込むのに必要な秒数に近い時間をタイムアウトとして指定してください。私の場合、安全をとって15秒にすることが多いです。接続あたりの最大リクエスト数を100にするのが普通ですが、その数字を増やすことも簡単です。次のディレクティブの例はApacheのhttpd.confファイル用のものです。
障害を監視することで効果あり
上記のような一般的なフロントエンドの性能に関する障害を取り除くことで性能を改善することができる一方で、バック・エンド側の変更はあったとしても少量ですむという恩恵が得られます。さらに、サイトの反応が早くなればなるほどビジネスの結果もよくなるかもしれません。