NGINXとNGINX Plusによるキャッシュ活用ガイド

NGINX | 2015年7月23日

アプリケーションやWebサイトのパフォーマンスが、その成功を左右する重要な要素であることは周知のとおりです。しかし、パフォーマンスを向上させる方法は、必ずしも明確ではありません。コードの品質とインフラストラクチャが重要なのはもちろんですが、多くの場合、基本的なアプリケーションデリバリ手法に注力することで、アプリケーションのエンドユーザーエクスペリエンスを大幅に改善できます。その一例が、アプリケーションスタックにおけるキャッシュの実装と最適化です。このブログ記事では、初心者から上級者まで、NGINXおよびNGINX Plusに搭載されているコンテンツキャッシュ機能を活用してパフォーマンスを向上させるための手法を紹介します。

概要

コンテンツキャッシュはクライアントと「オリジンサーバー」の間に配置され、受信したすべてのコンテンツのコピーを保存します。クライアントがキャッシュに保存されているコンテンツをリクエストすると、オリジンサーバーにアクセスせずに、そのコンテンツを直接返します。コンテンツキャッシュはクライアントの近くにあるため、パフォーマンスが向上します。また、アプリケーションサーバーがリクエストのたびにページを一から生成する必要がなくなるため、サーバーをより効率的に利用できます。

Webブラウザーとアプリケーションサーバーの間には、クライアントのブラウザーキャッシュ、中間キャッシュ、コンテンツ配信ネットワーク(CDN)、アプリケーションサーバーの前段に配置されたロードバランサーやリバースプロキシなど、複数のキャッシュが存在する場合があります。リバースプロキシやロードバランサーのレベルであっても、キャッシュによってパフォーマンスを大幅に向上できます。

一例として、昨年、読み込みが遅いWebサイトのパフォーマンスチューニングを担当しました。最初に気付いたことの1つは、メインのホームページの生成に1秒以上かかっていたことです。デバッグの結果、このページはキャッシュ不可に設定されていたため、リクエストのたびに動的に生成されていることが分かりました。ページ自体は頻繁に変更されるものではなく、パーソナライズもされていなかったため、その必要はありませんでした。そこで試しに、ロードバランサーでホームページを5秒間キャッシュするよう設定したところ、それだけで明らかな改善が見られました。最初のバイトを受信するまでの時間は数ミリ秒に短縮され、ページの読み込みも目に見えて速くなりました。

NGINXは、アプリケーションスタック内のリバースプロキシまたはロードバランサーとして広く導入されており、包括的なキャッシュ機能を備えています。次のセクションでは、NGINXで基本的なキャッシュを設定する方法について説明します。

基本的なキャッシュのセットアップと設定方法

基本的なキャッシュを有効にするために必要なディレクティブは、proxy_cache_pathとproxy_cacheの2つだけです。proxy_cache_pathディレクティブでキャッシュのパスと設定を指定し、proxy_cacheディレクティブでキャッシュを有効にします。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g                  inactive=60m use_temp_path=off;

server {
    # ...
    location / {
        proxy_cache my_cache;
        proxy_pass http://my_upstream;
    }
}

The parameters to the proxy_cache_pathディレクティブのパラメーターでは、次の設定を定義します。

  • キャッシュ用のローカルディスクディレクトリは「/path/to/cache/」です。
  • levelsは、/path/to/cache/の下に2階層のディレクトリ構造を設定します。1つのディレクトリに多数のファイルがあるとファイルアクセスが遅くなる可能性があるため、ほとんどのデプロイでは2階層のディレクトリ構造を推奨します。levelsパラメーターを指定しない場合、NGINXはすべてのファイルを同じディレクトリに配置します。
  • keys_zoneは、キャッシュキーと使用状況タイマーなどのメタデータを保存する共有メモリゾーンを設定します。キーのコピーをメモリに保持することで、NGINXはディスクにアクセスせずに、リクエストがHITかMISSかを迅速に判定できるため、確認処理が大幅に高速化されます。1 MBのゾーンには約8,000個のキーのデータを保存できるため、この例で設定されている10 MBのゾーンには約80,000個のキーのデータを保存できます。
  • max_sizeは、キャッシュサイズの上限を設定します(この例では10 GB)。この設定は任意です。値を指定しない場合、キャッシュは使用可能なディスク領域をすべて使用するまで拡大できます。キャッシュサイズが上限に達すると、キャッシュマネージャーと呼ばれるプロセスが、最後に使用されてから最も時間が経過しているファイルを削除し、キャッシュサイズを上限未満に戻します。
  • inactiveは、項目がアクセスされないままキャッシュに保持される期間を指定します。この例では、60分間リクエストされていないファイルは、有効期限が切れているかどうかにかかわらず、キャッシュマネージャープロセスによってキャッシュから自動的に削除されます。デフォルト値は10分(10m)です。非アクティブなコンテンツと有効期限切れのコンテンツは異なります。NGINXは、キャッシュ制御ヘッダーで定義された有効期限が切れたコンテンツ(例:Cache-Control:max-age=120)を自動的には削除しません。有効期限切れ(古い)コンテンツが削除されるのは、inactiveで指定された期間にわたってアクセスされなかった場合のみです。有効期限切れのコンテンツにアクセスすると、NGINXはオリジンサーバーからそのコンテンツを更新し、inactiveタイマーをリセットします。
  • NGINXは、キャッシュに格納するファイルをまず一時ストレージ領域に書き込みます。use_temp_path=offディレクティブを指定すると、NGINXはこれらのファイルをキャッシュ先と同じディレクトリに書き込みます。ファイルシステム間で不要なデータコピーが発生しないよう、このパラメーターをoffに設定することを推奨します。use_temp_pathは、NGINXバージョン1.7.10およびNGINX Plus R6で導入されました。

最後に、proxy_cacheディレクティブは、親のlocationブロックのURL(この例では/)に一致するすべてのコンテンツのキャッシュを有効にします。proxy_cacheディレクティブはserverブロックに含めることもできます。この場合、サーバー内で独自のproxy_cacheディレクティブが設定されていないすべてのlocationブロックに適用されます。

オリジン停止時のキャッシュコンテンツ配信

NGINXの強力な機能である「コンテンツキャッシュ」では、オリジンサーバーから最新のコンテンツを取得できない場合に、キャッシュ内の古いコンテンツを配信するようNGINXを設定できます。これは、キャッシュされたリソースのすべてのオリジンサーバーが停止している場合や、一時的にビジー状態になっている場合に発生する可能性があります。NGINXは、クライアントにエラーを返す代わりに、キャッシュから古いバージョンのファイルを配信します。これにより、NGINXがプロキシするサーバーの耐障害性がさらに高まり、サーバー障害やトラフィック急増時にも稼働を維持できます。この機能を有効にするには、proxy_cache_use_staleディレクティブを追加します。

location / {    # ...
    proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
}

このサンプル構成では、NGINXがオリジンサーバーからerror、timeout、または指定されたいずれかの5xxエラーを受信した場合、要求されたファイルの古いバージョンがキャッシュにあれば、クライアントにエラーを中継する代わりに、その古いファイルを配信します。

キャッシュの詳細な調整とパフォーマンスの向上

NGINXには、キャッシュのパフォーマンスを細かく調整するためのオプション設定が豊富に用意されています。以下は、そのうちのいくつかを有効にする例です。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g                  inactive=60m use_temp_path=off;

server {
    # ...
    location / {
        proxy_cache my_cache;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 3;
        proxy_cache_use_stale error timeout updating http_500 http_502
                              http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;

        proxy_pass http://my_upstream;
    }
}

これらのディレクティブでは、次の動作を設定します。

  • proxy_cache_revalidateは、オリジンサーバーからコンテンツを更新する際に、条件付きGETリクエストを使用するようNGINXに指示します。クライアントが、キャッシュされているもののキャッシュ制御ヘッダーの定義に従って期限切れとなった項目をリクエストすると、NGINXはオリジンサーバーに送信するIf-Modified-SinceフィールドをGETリクエストのヘッダーに含めます。これにより帯域幅を節約できます。サーバーが項目全体を送信するのは、NGINXが最初にキャッシュした際にファイルに付加されたLast-Modifiedヘッダーに記録された時刻以降に、その項目が変更されている場合に限られるためです。
  • proxy_cache_min_usesは、NGINXがアイテムをキャッシュするまでに、クライアントからそのアイテムがリクエストされる必要がある回数を設定します。これにより、アクセス頻度の高いアイテムだけがキャッシュに追加されるため、キャッシュが常にいっぱいになる場合に役立ちます。デフォルトでは、proxy_cache_min_usesは1に設定されています。
  • The updatingパラメーターをproxy_cache_use_staleディレクティブに指定し、proxy_cache_background_updateディレクティブを有効にすると、期限切れの項目、またはオリジンサーバーから更新中の項目をクライアントがリクエストした際に、NGINXは古いコンテンツを配信します。すべての更新はバックグラウンドで実行されます。更新後のファイルが完全にダウンロードされるまで、すべてのリクエストに対して古いファイルが返されます。
  • With proxy_cache_lockを有効にしている場合、複数のクライアントがキャッシュ内で最新ではないファイル(MISS)をリクエストすると、そのうち最初のリクエストだけがオリジンサーバーに送信されます。残りのリクエストは、そのリクエストが処理されるまで待機してから、キャッシュからファイルを取得します。proxy_cache_lockを有効にしていない場合、キャッシュミスとなったすべてのリクエストがオリジンサーバーに直接送信されます。

複数のハードドライブへのキャッシュの分割

複数のハードドライブがある場合、NGINXを使用してキャッシュを各ドライブに分散できます。次の例では、リクエストURIに基づいてクライアントを2台のハードドライブに均等に振り分けます。

proxy_cache_path /path/to/hdd1 levels=1:2 keys_zone=my_cache_hdd1:10m                 max_size=10g inactive=60m use_temp_path=off;
proxy_cache_path /path/to/hdd2 levels=1:2 keys_zone=my_cache_hdd2:10m
                 max_size=10g inactive=60m use_temp_path=off;

split_clients $request_uri $my_cache {
              50%          “my_cache_hdd1”;
              50%          “my_cache_hdd2”;
}

server {
    # ...
    location / {
        proxy_cache $my_cache;
        proxy_pass http://my_upstream;
    }
}

2つの「proxy_cache_path」ディレクティブは、異なる2台のハードドライブ上に2つのキャッシュ(「my_cache_hdd1」と「my_cache_hdd2」)を定義します。「split_clients」設定ブロックでは、リクエストの半分(50%)の結果を「my_cache_hdd1」に、残りの半分を「my_cache_hdd2」にキャッシュするよう指定します。「$request_uri」変数(リクエストURI)に基づくハッシュによって、各リクエストに使用するキャッシュが決まります。そのため、特定のURIに対するリクエストは常に同じキャッシュに保存されます。

この方法は、RAIDによるハードドライブ構成の代わりにはならないことにご注意ください。ハードドライブに障害が発生すると、障害が発生したハードドライブに振り分けられたリクエストに対してユーザーに500レスポンスコードが表示されるなど、システムが予期しない動作をする可能性があります。適切なRAID構成であれば、ハードドライブ障害に対応できます。

よくあるご質問(FAQ)

このセクションでは、NGINXのコンテンツキャッシュに関するよくある質問に回答します。

NGINXキャッシュを計測できますか?

はい、add_headerディレクティブを使用します。

add_header X-Cache-Status $upstream_cache_status;

この例では、クライアントへのレスポンスにX-Cache-Status HTTPヘッダーを追加します。$upstream_cache_statusに指定できる値は次のとおりです。

  • MISS – レスポンスがキャッシュに見つからなかったため、オリジンサーバーから取得されました。その後、レスポンスがキャッシュされた可能性があります。
  • BYPASS – リクエストがproxy_cache_bypassディレクティブに一致したため、レスポンスはキャッシュから提供されず、オリジンサーバーから取得されました(以下の「キャッシュに穴を開けることはできますか?」を参照)。その後、レスポンスがキャッシュされた可能性があります。
  • EXPIRED – キャッシュ内のエントリの有効期限が切れています。レスポンスにはオリジンサーバーから取得した最新のコンテンツが含まれています。
  • STALE – オリジンサーバーが正常に応答しておらず、proxy_cache_use_staleが設定されているため、コンテンツは古い状態です。
  • UPDATING – 以前のリクエストに応じてエントリが現在更新中であり、proxy_cache_use_stale updatingが設定されているため、コンテンツは古い状態です。
  • REVALIDATED – proxy_cache_revalidateディレクティブが有効になっており、NGINXは現在キャッシュされているコンテンツが引き続き有効であることを検証しました(If-Modified-SinceまたはIf-None-Match)。
  • HIT – レスポンスには、キャッシュから直接取得した有効かつ最新のコンテンツが含まれています。

NGINXはキャッシュするかどうかをどのように判断するのか

NGINXがレスポンスをキャッシュするのは、オリジンサーバーが次のいずれかを含む場合に限られます。将来の日時が指定された「Expires」ヘッダー、または「Cache-Control」ヘッダーで「max-age」ディレクティブに0以外の値が設定されている場合です。

デフォルトでは、NGINXはCache-Controlヘッダー内のその他のディレクティブにも従います。このヘッダーにPrivate、No-Cache、またはNo-Storeディレクティブが含まれている場合、レスポンスをキャッシュしません。また、Set-Cookieヘッダーを含むレスポンスもキャッシュしません。さらに、GETおよびHEADリクエストに対するレスポンスのみをキャッシュします。以下の回答で説明するように、これらのデフォルト設定は変更できます。

NGINX does not cache responses if proxy_bufferingがoffに設定されている場合、NGINXはレスポンスをキャッシュしません。デフォルトではonです。

Can Cache-Controlヘッダーを無視できますか?

はい、proxy_ignore_headersディレクティブを使用すれば無視できます。たとえば、次のように設定します。

location /images/ {
    proxy_cache my_cache;
    proxy_ignore_headers Cache-Control;
    proxy_cache_valid any 30m;
    # ...
}

NGINXは、Cache-Controlヘッダーを/images/配下のすべてに対して無視します。proxy_cache_validディレクティブは、キャッシュデータに有効期限を設定します。Cache-Controlヘッダーを無視する場合は、このディレクティブが必要です。NGINXは、有効期限のないファイルをキャッシュしません。

NGINXはヘッダーにSet-Cookieが含まれるコンテンツをキャッシュできるか

はい、前の回答で説明したとおり、proxy_ignore_headersディレクティブを使用すれば可能です。

NGINXはPOSTリクエストをキャッシュできますか?

はい、proxy_cache_methodsディレクティブを使用します。

proxy_cache_methods GET HEAD POST;

この例では、POSTリクエストのキャッシュを有効にします。

NGINXは動的コンテンツをキャッシュできますか?

はい、Cache-Controlヘッダーで許可されている場合は可能です。動的コンテンツを短時間でもキャッシュすると、リクエストごとにページを再生成する必要がなくなるため、オリジンサーバーとデータベースの負荷を軽減し、最初のバイトを受信するまでの時間を短縮できます。

キャッシュを部分的にバイパスできますか?

はい、proxy_cache_bypassディレクティブを使用します。

location / {
    proxy_cache_bypass $cookie_nocache $arg_nocache;
    # ...
}

このディレクティブでは、NGINXが最初にキャッシュ内を検索せず、オリジンサーバーからコンテンツを直ちに取得するリクエストの種類を定義します。これは、キャッシュに「穴を開ける」と表現されることもあります。この例では、NGINXはnocache Cookieまたは引数を含むリクエスト(例:http://www.example.com/?nocache=true)に対してこの処理を行います。NGINXは、バイパスされない今後のリクエストに備えて、取得したレスポンスを引き続きキャッシュできます。

NGINXが使用するキャッシュキー

NGINXが生成するキーのデフォルト形式は、次のNGINX変数のMD5ハッシュに似ています:$scheme$proxy_host$request_uri。ただし、実際に使用されるアルゴリズムはもう少し複雑です。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g                 inactive=60m use_temp_path=off;

server {
    # ...
    location / {
        proxy_cache my_cache;
        proxy_pass http://my_upstream;
    }
}

このサンプル構成では、http://www.example.org/my_image.jpgのキャッシュキーはmd5(“http://my_upstream:80/my_image.jpg”)として計算されます。

ハッシュ値には、実際のホスト名($proxy_host変数が使用されることに注意してください(www.example.com)ではなく、$proxy_host)。proxy_passディレクティブで指定されたプロキシ先サーバーの名前とポートとして定義されます。

キーの基準として使用する変数(またはその他の項目)を変更するには、proxy_cache_keyディレクティブを使用します(次の質問も参照してください)。

Cookieをキャッシュキーの一部として使用できますか?

はい。キャッシュキーには任意の値を設定できます。例:

proxy_cache_key $proxy_host$request_uri$cookie_jessionid;

この例では、JSESSIONID Cookieの値をキャッシュキーに組み込みます。URIが同じでも、JSESSIONID の値が異なる項目は、それぞれ一意の項目として個別にキャッシュされます。

NGINXはETagヘッダーを使用しますか?

NGINX 1.7.3およびNGINX Plus R5以降では、ETagヘッダーがIf-None-Matchとともに完全にサポートされています。

NGINXはバイト範囲リクエストをどのように処理しますか?

キャッシュ内のファイルが最新の場合、NGINXはバイト範囲リクエストに応じ、指定されたバイトのみをクライアントに配信します。ファイルがキャッシュされていない場合、またはキャッシュが古い場合、NGINXはオリジンサーバーからファイル全体をダウンロードします。リクエストで単一のバイト範囲が指定されている場合、NGINXはダウンロードストリーム内でその範囲に到達すると、直ちにクライアントへ送信します。同じファイル内の複数のバイト範囲が指定されている場合、NGINXはダウンロードの完了後にファイル全体をクライアントへ配信します。

ダウンロードが完了すると、NGINXはリソース全体をキャッシュに移動します。これにより、それ以降のバイト範囲リクエストは、単一範囲か複数範囲かを問わず、キャッシュから即座に処理されます。

NGINXがupstreamサーバーへのバイト範囲リクエストを処理するには、そのupstreamサーバーがバイト範囲リクエストをサポートしている必要があります。

NGINXはキャッシュのパージをサポートしていますか?

NGINX Plusは、キャッシュされたファイルの選択的なパージをサポートしています。これは、オリジンサーバー上でファイルが更新されていても、NGINX Plusのキャッシュではまだ有効な場合(Cache-Control:max-ageがまだ有効で、inactiveパラメーターによってproxy_cache_pathディレクティブに設定されたタイムアウトが経過していない場合)に役立ちます。NGINX Plusのキャッシュパージ機能を使用すると、このファイルを簡単に削除できます。詳細については、「キャッシュからのコンテンツのパージ」を参照してください。

NGINXはPragmaヘッダーをどのように処理するか

The Pragma:no-cacheヘッダーは、すべての中間キャッシュをバイパスし、リクエストしたコンテンツをオリジンサーバーから直接取得するために、クライアントによって追加されます。NGINXはデフォルトではPragmaヘッダーを処理しませんが、次のproxy_cache_bypassディレクティブでこの機能を設定できます。

location /images/ {
    proxy_cache my_cache;
    proxy_cache_bypass $http_pragma;
    # ...
}

NGINXはstale-while-revalidateおよびstale-if-errorというCache-Controlヘッダーの拡張機能をサポートしていますか?

はい、NGINX Plus R12およびNGINX 1.11.10以降でサポートされています。これらの拡張機能の動作は次のとおりです。

  • The stale-while-revalidateは、Cache-Control HTTPヘッダーの拡張機能で、キャッシュ済みのレスポンスが現在更新中の場合に、古いレスポンスの使用を許可します。
  • エラーが発生した場合、stale-if-errorはCache-Control HTTPヘッダーの拡張機能として、期限切れのキャッシュ済みレスポンスの使用を許可します。

これらのヘッダーは、上記のproxy_cache_use_staleディレクティブよりも優先度が低くなります。

NGINXはVaryヘッダーをサポートしていますか?

はい、NGINX Plus R5およびNGINX 1.7.7以降でサポートされています。こちらで「Vary」ヘッダーの概要をご確認いただけます。

関連情報

NGINXのキャッシュは、ほかにもさまざまな方法でカスタマイズおよびチューニングできます。NGINXを使用したキャッシュについてさらに詳しく知るには、以下のリソースをご覧ください。


共有

著者について

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります