連載
» 2003年05月10日 00時00分 UPDATE

事例に学ぶWebシステム開発のワンポイント(12):ブラウザキャッシュでパフォーマンス向上―負荷分散装置の落とし穴に注意−

本連載では、現場でのエンジニアの経験から得られた、アプリケーション・サーバをベースとしたWebシステム開発における注意点やヒントについて解説する。巷のドキュメントではなかなか得られない貴重なノウハウが散りばめられている。読者の問題解決や今後システムを開発する際の参考として大いに活用していただきたい。(編集局)

[金子崇之,(株)NTTデータ]

今回のワンポイント

Webシステムのパフォーマンスを考慮するうえで、実はコンテンツのキャッシュに関する知識は欠かせない。今回はブラウザ側のキャッシュ、サーバ側のキャッシュをいかに制御し、Webシステムのパフォーマンスを向上させるかについて解説する。


キャッシュされる場所は3カ所

 コンテンツがキャッシュされる場所は、図1のように大きく分けて以下の3つの場所がある。

  • クライアントマシン上のブラウザ
  • クライアントマシンとWebサーバの間に位置するProxyサーバ
  • Webサーバの背後にあるアプリケーションサーバ

 キャッシュをうまく制御することによって、システムの正しい動作を保証するだけではなく、応答時間の短縮、回線の有効活用、サーバリソースの節約といった重要な性能対策になる。

 本稿では、HTTPのキャッシュ機構の説明とともに、キャッシュを有効活用するための指針、ブラウザのキャッシュ機構が正常に動作しないというトラブル事例について述べる。なお本稿ではアプリケーションサーバのキャッシュ機能については割愛する。

図1 キャッシュされる場所は「クライアント」「Proxyサーバ」「APサーバ」の3カ所にある 図1 キャッシュされる場所は「クライアント」「Proxyサーバ」「APサーバ」の3カ所にある

キャッシュ制御の方法

 サーバサイドからキャッシュを制御するには、以下の2つの方法がある。

  • HTTPヘッダによる制御
  • METAタグによる制御

 まずは、これらがどのようなものか、軽くおさらいしておく。

■HTTPヘッダによる制御

 HTTPプロトコルでは、HTTPヘッダにさまざまな情報を格納することができる。そのうちいくつかの情報は、キャッシュ制御のためのヘッダである。リクエスト(クライアント→サーバ)用のものと、レスポンス(サーバ→クライアント)用、リクエスト/レスポンス共通のものが存在する。

■リクエスト用
If-Modified-Since
日時を指定する。指定した日時より新しいコンテンツの場合のみデータを返却するようにサーバに指示する。ローカルキャッシュの最新確認に使用される

If-None-Match
指定したエンティティタグに一致しない場合のみコンテンツを返却するようにサーバに指示する。最新情報の取得や競合の排除のために指定される

■レスポンス用
Expires
コンテンツの有効期限を示す

Last-Modified
コンテンツの最終更新時刻を示す。If-Modified-Sinceと対で使用される

ETag
コンテンツの全体や一部を特定する固有値を示す。If-None-Matchと対で使用される

■リクエスト/レスポンス共通
Cache-Control
キャッシュのコントロールに関する指示や情報を示す

Pragma
関連するクライアント/Proxy/サーバそれぞれに認識させるための特殊な追加情報を記述する。例えば、「no-cache」を指定することで無条件に最新リソースを転送させることができる

■METAタグ

 METAタグはHTMLで規定されていない情報をHTMLページに持たせるためのタグだ。<meta http-equiv="name" content="content">というMETAタグを指定すると、HTTPヘッダにname: contentというフィールドを追加したのと同じ働きとなる。そのため静的なHTMLコンテンツの内部からクライアントのキャッシュを制御することが可能となる。

 また、METAタグは基本的にはブラウザのキャッシュを操作するためのものだ。なぜなら通常はProxyサーバはMETAタグを処理しないからである。ブラウザの種類によってはMETAタグをサポートしないものがあるので、確実にキャッシュをコントロールしたい場合には、HTTPヘッダとMETAタグの両方を使用するのがよいだろう。

キャッシュの流れ

 ブラウザやProxyサーバにコンテンツがキャッシュされる場合の動作は次のようになる。コンテンツがブラウザ内にキャッシュされた場合、ブラウザがそのコンテンツのキャッシュを使用する場合にはサーバに対してリクエストを発行しない。ブラウザ内にキャッシュがない場合や、キャッシュを使用するかどうか判断できない場合には、サーバにリクエストを発行する。

 コンテンツがProxyサーバにキャッシュされている場合、Proxyサーバではコンテンツと一緒にHTTPヘッダ情報を保存している。キャッシュが有効な場合にはブラウザからのリクエストに対してProxyサーバがレスポンスを返す。Proxyサーバでは、異なるクライアント(ブラウザ)が以前にリクエストしたコンテンツをキャッシュし、そのキャッシュを使用することができる。

 また、キャッシュが無効な場合や、キャッシュを使用しないことを指示されている場合には、ProxyサーバはWebサーバに対してコンテンツを取得するためにリクエストを発行する。

 リクエストがWebサーバまで到達すると、If-Modified-SinceヘッダやIf-None-Matchヘッダが付いていた場合、WebサーバはコンテンツのタイムスタンプとIf-Modified-Sinceヘッダの時間、コンテンツの全体や一部を特定する固有値とIf-None-Matchヘッダの値を比較し、もしコンテンツが更新されていればコンテンツを送信する。更新されていなければ304 Not Modified をレスポンスとして返す。

 「304 Not Modified」の場合にはレスポンスボディに実際のファイルを含まないヘッダだけのレスポンスなため、非常に軽量のデータである。

図2 キャッシュの流れ 図2 キャッシュの流れ

何でもキャッシュすればよいわけではない

 WebシステムをWebサーバ、アプリケーションサーバを連携させて構築する場合、画像ファイルやCSSファイル、めったに更新されないHTMLファイルなどの静的なコンテンツは、通常Webサーバに配置しアプリケーションサーバの負荷を減らすようにする。そのためWebサーバでは適切なヘッダを送信するように正しく設定する必要がある。

 一方アプリケーションサーバで生成されるコンテンツは、通常、ユーザーのセッション情報やDBアクセスなどを基に動的に生成されるものである。そのためブラウザ、Proxyサーバにキャッシュされてしまうと、検索結果がいつまでも更新されなかったり、ほかのユーザーが検索した結果が参照できてしまったりするなどの不具合が発生する可能性がある。そのためキャッシュされないようにするためのキャッシュコントロールが必要である。

 J2EEアプリケーションサーバでは動的なHTMLページを生成するのにJSPが使用される。そこでJSPで生成したページをキャッシュさせないようにするための方法を紹介する。

■動的に生成したページをキャッシュさせないために

 JSPで生成したページをキャッシュさせたくない場合には、以下のようなファイルを用意して、<%@ page include="Nocache.inc"%>のようにインクルードするとよい。

Nocache.inc
<%! private String getHTTPDate() {
    java.text.SimpleDateFormat formatter =
      new java.text.SimpleDateFormat("E, dd MMM yyyy hh:mm:ss zzz", java.util.Locale.US);
    formatter.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
    return formatter.format(new java.util.Date());
  }
%><%
  response.setHeader("Expires", getHTTPDate());
  response.setHeader("Pragma","no-cache");
  response.setHeader("Cache-Control","no-cache");
%>

 また、レスポンスヘッダは以下のようになる。

HTTP/1.1 200 OK
Content-Type: text/html;charset=Shift_JIS
Cache-control: no-cache
Pragma: no-cache
Expires: Tue, 17 12 2002 15:34:27 GMT

Date: Tue, 17 Dec 2002 15:34:27 GMT
Server: Apache Coyote/1.0
Proxy-Connection: close
Connection: close

負荷分散装置でキャッシュが効かないのは?

 負荷分散装置を利用したWebサーバ、アプリケーションサーバのクラスタ化は、パフォーマンスやスケーラビリティの向上のために採用されることが多い。ここでは、負荷分散装置を利用したWebサーバのクラスタ化において、Webサーバ(Apache)の設定によりキャッシュが有効とならなかった事例を紹介しよう。

 システムは図3のように、Webサーバ、アプリケーションサーバを分割し、Webサーバは静的コンテンツのみ、アプリケーションサーバは動的コンテンツを処理するようになっていた。またWebサーバは多重化され、セッション情報などを持たないため負荷分散装置によってラウンドロビンに振り分けられていた。

図3 問題となったシステム構成 図3 問題となったシステム構成

 Webサーバに配置されたコンテンツは主に画像ファイルで、応答時間劣化の懸念となっていた。しかし画像ファイルは多くの遷移先ページで同一の画像を使用しており、まためったに更新されないことが分かっていた。事実Webサーバが1台の場合の試験環境ではブラウザのキャッシュが有効となり、応答時間が改善されることが確認できていた。

 しかし実際のシステムでの状況は、画像へのリクエストに対して、期待されたレスポンスコード304を全く返さず、ほぼ毎回レスポンスコード200として画像ファイルを送信してしまっていた。そのため応答時間も悪化、Webサーバの負荷も上がってしまった。

■原因はHTTPヘッダのETag

 原因は、先ほど説明したHTTPヘッダのETagであった。このETagはコンテンツに対してユニークなIDを割り振り、変更されているかどうかを確認するためのものである。ブラウザからはIf-None-Matchタグにこの値が付けられ、サーバ側ではこの値とETagの値が異なればレスポンスコード200でコンテンツを送信する。

 ETagの値は、同じ内容、同じ更新日付のファイルであってもWebサーバごとに異なる値が返されていた。今回使用していたシステムではApacheを採用していたのだが、Apacheはファイルのi-nodeによりユニークなIDを付けていた。リクエストを負荷分散装置がラウンドロビンで振り分けていたため、同じWebサーバにアクセスが行かない限り異なるコンテンツと判断され、結果が送信されていたのだ。

■どう対策するのか?

 Apache 1.3.23以降では、サーバ側でETagを付けないことにより、ETagによる比較を行わなくすることが可能だ。Apacheのhttpd.confに「FileETag None」と記述すればよい。以下にいくつかの設定例を示す。

/var/www/icons配下のコンテンツにはETagを付けない
<Directory "/var/www/icons">
  FileETag None
</Directory>

画像ファイルの拡張子を持つコンテンツにはETagを付けない
<FilesMatch "\.(gif|jpe?g|png)$">
  FileETag None
</FilesMatch>

このApacheはETagを付けない
FileETag None

 今回の記事ではHTTPのキャッシュ機構、J2EEでのキャッシュの制御方法、キャッシュに関するトラブル事例を紹介した。

 キャッシュによる効果は、クライアントからすれば応答時間の短縮、サーバからすれば、サーバリソースの節約に非常に大きく発揮される。そのためキャッシュの仕組みを知り、正しく制御することはとても重要である。

 トラブル事例のときのように、試験環境と本番環境が異なる場合には特に注意が必要である。うっかり設定を見落とすことによって、避けられるはずの顧客のイライラやサーバの過負荷によるシステム管理者の悲鳴などに遭遇しないように注意するべきだ。

著者プロフィール

金子 崇之(かねこ たかゆき)

現在、株式会社NTTデータビジネス開発事業本部に所属。 技術支援グループとして、J2EEをベースにしたWebシステム開発プロジェクトを対象に、技術サポートを行っている。特に、性能・信頼性といった方式技術を中心に活動中。



Copyright© 2016 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

編集部からのお知らせ

@IT編集部では、編集記者を募集しています。ご興味のある方は奮ってご応募ください。

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。