REST制約に従った分散キャッシュシステムは、計算資源だけでなくネットワーク資源も節約しします。
https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
(REST論文の図。キャッシュはサーバーサイドではなくクライアントサイドに保存されています)
この記事ではキャッシュをストレージの場所で考察します。
キャッシュが行われる場所
キャッシュは次の3つのいずれかに存在します。
- サーバーサイド
- CDN (共有キャッシュ)
- クライアント
理想的なキャッシュシステムを目指し、HTML、APIに関わらずこの分散型のキャッシュシステムを設計します。
キャッシュのTTLを知るのは誰か?
コンテンツがイベントドリブンではなく、正確なコンテンツの生存期間(TTL = Time to live)で制御する場合、サーバーサイドがTTLを決定します。例えば製品の発売日まで有効なキャンペーンサイトや、月曜の相場開始までの時間が正確に計算できる株価のデータなどです。
HTTPキャッシュはレスポンスヘッダーにCache-Control
ヘッダーを付与することで、CDNやクライアントに対してキャッシュのTTLを伝えます。
キャッシュの無効化
キャッシュが無効化される理由は2つあります。TTLの指定で時間が切れたかよるものか、イベントによるものかです。
TTLによるCDNキャッシュの無効化
TTLによる無効化は容易で、CDNやクライアント双方に使う事ができます。コンテンツの有効期間を秒数で指定します。
イベントによるCDNキャッシュの無効化
一方、イベントによる無効化は注意が必要です。サーバーサイドのWebアプリケーションのイベントでサーバーサイドのキャッシュを無効化することは容易ですが、CDNに対して無効化するときは、インスタントパージに対応したCDNを利用する必要があります。FastlyやAkamaiなどです。そのときはイベントドリブンコンテンツはTTLでキャッシュが消去されないようにするために、キャッシュ時間を最大(1年間)にします。
インスタントパージが利用できないCDNでは、イベントドリブンコンテンツをCDNでキャッシュすることができません。キャッシュ時間をゼロにして、must-revalidate
で都度確認するようにします。
ETag管理が最適されていると、最小限のネットワークおよびCPU資源の消費ですみますが、インスタントパージ可能なCDNのキャッシュの効率性には及びません。
クライアントキャッシュ
Cache-Control: max-age=3600
レスポンスディレクティブのmax-age=3600 は、レスポンスが生成されてから3600 秒後まで、レスポンスが新鮮なままであることを示します。このヘッダーはCDNでもキャッシュを作成しますが、クライアントでもキャッシュを作成します。最も強力なキャッシュで、ネットワークコストを節約します。
TTLが正確に予測できる場合にのみ使用します。この期間、イベントでコンテンツを変更する事はできません。
イベントドリブンコンテンツの場合は、クラアイントはコンテンツに変更があったかを条件付きリクエストで確認します。変更がなければHTTPステータスコード304
が返されるのでクライアントはクライアントサイドで保存したキャッシュを再利用します。
また、パーソナライズされたページなど、クライアント間でキャッシュが共有できないときはprivate
を使います。
Cache-Control: private, max-age=3600
CDNキャッシュ
クライアントにキャッシュを保存しないときは以下のどちらかのヘッダーで指定します。
Cache-Control: s-maxage=3600
Surrogate-Control: max-age=3600
サーバーサイドのキャッシュ
サーバーサイドは3つのレイヤーで構成します。
- ETagリポジトリレイヤー
- サーバーサイドキャッシュレイヤー
- オリジンレイヤー
- CDNから到達したHTTPリクエストが、条件付きリクエストの場合には、コンテンツに変更がないかをETag リポジトリレイヤーが判定してなければ304を返します。ストレージに容量はあまり必要ないのでMemcachedのようなストレージが向いています。
- 対応するURLのサーバーサイドのキャッシュがあればそれを返却します。Redisなどを使います。
- キャッシュがなければ、オリジンレイヤーでレスポンスを返しますが、その時のキャッシュ可能なコンテンツはETagをETagレポジトリに格納してレスポンスデータをサーバーサイドキャッシュに格納します。
キャッシュが生成される順番
サーバーからクライアントまでキャッシュコンテンツが伝わる様子は以下のとおりです。
- オリジンサーバーがコンテンツを生成します。
- コンテンツのETagをETagレポジトリに格納します。
- コンテンツのキャッシュをサーバーサイドのキャッシュに保存します。
- オリジンはHTTPヘッダーで
ETag
を返します。 ETag: "" - CDNはそのETagとその内容をCDNキャッシュに保存します。
- クライアントはコンテンツをローカルにキャッシュします。
クライアントAのリクエストでこのキャッシュが生成された時に、別のクライアントBは4で保存されたCDNキャッシュの恩恵を受けます。
WebブラウザにはCache-Control
ヘッダーを理解しその通りに振る舞いますが、APIクライアントの場合にはCache-Control
ヘッダーを理解するためにRFC7234
対応のクライアントを利用する必要があります。
RFC7234対応APIクライアント
キャッシュの強さ順
より強力なキャッシュの順番にリストします。
- ネットワークコストの発生しないクライアントキャッシュ(TTL)
- 都度確認をするクライアントキャッシュ(ETag)
- 304を返すCDN(ETag)
- キャッシュコンテンツを返すCDN
- 304を返すサーバーサイド(Etag)
- キャッシュコンテンツを返すサーバーサイド
キャッシュシステム設計
サーバーサイドのアプリケーションは、コンテンツの変更の際にキャッシュ管理処理をトリガーします。
- サーバーサイドのキャッシュを保存 (Redisなど)
- サーバーサイドでETagを保存 (memcacheなど)
- CDNに対してのAPIでパージ操作
BEAR.Sundayは一般的なMVCフレームワークよりこれらの操作に適した特徴があります。
- リソースはURIがあり、その操作はステートレス
- リソースのメソッドにセマンティックがありどれが読み込みで(onGet)どれが更新か区別がある
- AOP可能
ユーザーがアプリケーションで操作をすることなく、大部分のキャッシュ操作がフレームワークにより自動化されれます。