プログラミング初心者がアーキテクトっぽく語る

見苦しい記事も多数あるとは思いますが訂正しつつブログと共に成長していければと思います

Web APIのキャッシュ

Web APIの性能を最適化する方法としてPagination、キャッシング、圧縮がある。

今回はキャッシングについてを取り上げる。

既に取り上げたPaginationについてはこちらを参照いただきたい。

architecting.hateblo.jp


メリット

  • 応答速度が上がる。
  • サーバの負荷が低減される。
  • 中間経路の帯域が節約できる。

デメリット

  • クライアントが古い情報を取得してしまう可能性がある。
    • 防止のためにValidationや条件付きリクエストを利用する。

適用箇所

キャッシュの適用箇所は大きく分けてクライアント、サーバ、中間経路の3箇所がある。

1. クライアント

  • Client-side caching。
  • 現代のブラウザは背後で頻繁にキャッシュを利用している。

2. サーバ

  • Server-side caching。
  • Reverse Proxyなどを利用して広く行われている。
    • Varnishなど。
  • Web APIではAPI Gatewayがキャッシング機能を提供するケースがある。

3. 中間経路

  • 中間経路上のプロキシやCDN
    • 企業のプロキシ。
    • プロバイダのプロキシ。

適用するデータ

Webにおいて一般的にキャッシュを設定すべきなのは、画像、CSSJavaScriptといった変更が少ないResourceだ。

Web APIも基本は同じと考えてよいだろう。

DBの内容は頻繁に変更される可能性があるので(例えば「最新のメッセージ10件」とか)DBへのクエリ結果を含むコンテンツはキャッシュさせない方が得策だろう。こうした動的なコンテンツはCache-Control:no-storeでよいと考える。


キャッシュを制御するHTTPヘッダ

1. Cache-Controlヘッダ

  • キャッシュ制御の中心となるヘッダ。
  • RequestとResponseの両方に含めることができるGeneralヘッダである。

Request

  • max-age: クライアントはmax-age以上のageのResponseを受け取らない。単位は秒。
  • no-cache: キャッシュで応答する前にOrigin ServerへValidationすることを要求する。
  • no-store: このReqesutも、これに対するResponseもキャッシュしないことを要求する。

Response:

  • max-age: このResponseのキャッシュが有効な期間。単位は秒。
  • no-cache: キャッシュしてもよいが利用する前に毎回Origin ServerへValidationすることを要求する。
  • no-store: そのResponseをキャッシュしてはいけない。
  • public: だれでもキャッシュしてよい。
    • 通常、ResponseにAuthorizationが含まれるとキャッシュしないが、publicだとキャッシュ可になる
  • private: ブラウザのみキャッシュ可。
  • must-revalidate: キャッシュが期限切れだった場合、キャッシュを使用する前にOrigin ServerへValidationすることを要求する。

2. Expiresヘッダ

  • レスポンスヘッダ
  • キャッシュが無効になる日付と時間。
  • システム間のクロックがずれていると影響を受ける。
  • Cache-Controlヘッダの max-ageが優先される。

3. Pragmaヘッダ

  • リクエストヘッダ
  • HTTP/1.0 のヘッダで後方互換性のために許容されている。
  • Cache-Controlヘッダが優先される。
  • Cache-Controlヘッダがなく、Pragma: no-cacheが指定されていた場合はCache-control: no-cacheと同じ振る舞いをする。
  • RFCの規定上、PragmaヘッダはRequest専用でResponseは未定義。
  • RequestにPragmaヘッダが記述された場合の挙動は実装依存となる。

4. Last-Modified

  • レスポンスヘッダ
  • リソースに対して最後に変更があった日付と時刻。
  • 次回リクエストのIf-Modified-Sinceに入れてValidationや条件付きリクエストに利用できる。
  • 1秒以内に変更が複数回発生した場合は、同じ値になってしまう。
    • 弱いValidator(weak validator)に分類される

5. If-Modified-Since

  • リクエストヘッダ
  • クライアントはResourceが指定した日付/時刻以降に変更された場合にのみ受け取る。
    • 通常は前回のResponseに含まれていたLast-Modifiedヘッダの値を入れる。
  • Origin ServerははIf-Modified-Sinceの値とResourceの変更時刻を比較する。
  • Resourceが変更されていればサーバは200 OKを返信する(=キャッシュは使わない)。
  • 変更されていなければサーバは304 Not Modifiedを返信する(=キャッシュを使う)。

6. ETag

  • レスポンスヘッダ
  • ハッシュのようなもので、計算方法は要件や実装によって異なる。
  • 計算方法次第でstrong validatorにもweak validatorにもなる。
  • リクエストのIF-MatchやIF-Not-Matchに入れてValidationや条件付きリクエストに利用できる。

7. If-Not-Match

  • リクエストヘッダ
  • クライアントは指定した値とResourceのETagが一致しない場合にのみResourceを受け取る。
  • Origin ServerはIf-Not-Matchの値とResourceのETagを比較する。
  • 一致しない場合、サーバは200 OKを返信する(=キャッシュは使わない)。
  • 一致した場合、サーバは304 Not Modifiedを返信する(=キャッシュを使う)。

Validation

1. 実施契機

クライアントは次の状況ではキャッシュの有効性を確認しなくてはならない。これをValidationと呼ぶ。

  1. リクエストにCache-Control: no-cacheが指定されている
  2. リクエストにPragram: no-cacheが指定されている
  3. 利用しようとしたキャッシュのレスポンスにCache-Control: no-cacheが指定されている
  4. 利用しようとしたキャッシュのレスポンスにCache-Control: max-ageがExpireし、かつCache-Control: must-revalidateが指定されている

mandatory revalidationのメリット

契機1〜3のような毎回Validationを行うことをmandatory revalidationと呼ぶ。

毎回問い合わせるくらいならCache-Control: no-storeにしてリソースを毎回、取得しても大差ないのではないか? と思うかもしれないが、実際のところValidationの方がリソースを取得するよりも目に見えて処理が軽い。

このためmandatory revalidationの方がクライアントの応答速度は少し向上するし、沢山のクライアントからの要求を処理するサーバの処理負荷はかなり軽減され、クライアントもサーバも恩恵を受ける。

max-ageの注意点

契機4のようにある一定期間が経過した後にValidationを行う方式では、期間内に他のユーザがリソースを変更した場合に更新前の古いキャッシュ情報を取得してしまう。

リアルタイム性が重要ではない静的コンテンツ向けだ。

2. 条件付きリクエス

Validationは主に以下の方法で実施される。

  • ETag ヘッダーで受け取った値をリクエストのIf-Not-Matchヘッダに入れる。
  • Last-Modified ヘッダーで受け取った値をリクエストのIf-Modified-Sinceヘッダに入れる。

このように特定の条件が成立したときにResourceを処理するリクエストを条件付きリクエストと呼ぶ。

もしリソースの有効性だけ確認したくて、リソース自体が不要な場合はHEADメソッドを利用できる。


弱いValidator

1秒以内にResourceの変更が複数回発生した場合、Last-Modifiedの値は同じになってしまう。Last-Modified ヘッダーで受け取った値を使ってValidationを行うと本当はResourceは更新されているのにキャッシュを返されてしまうことがあるかもしれない。

このように古いResourceを新しいResourceと勘違いする可能性がある脆弱なValidatorをWeak Validatorと呼ぶ。

Validatorが脆弱になる要素は以下の通り。

  • 時刻の粒度が粗い
  • ハッシュ値が衝突する可能性がある
  • ビジネス上の判断で特定の箇所を無視したい(広告、Content-type等)

Last-ModifiedはWeak Validatorである。


強いValidator

反対に、少しでも異なれば違うファイルと判断できる強固なValidatorをStrong Validatorと呼ぶ。

ETagは計算の方法によってStrong ValidatorにもWeak Validatorにもなる。

例えばファイルの中身、サイズ、更新日時、Content-typeなど複数の要素を加味して衝突しないハッシュアルゴリズムで計算すればStrong Validatorとなるだろう。

しかし、衝突するハッシュアルゴリズムを使用すればweak validatorとなる。また広告やContent-typeを計算から除外した場合もweak validatorとなる。

ETagをStrong ValidatorにするかはWeak Validatorにするかは要件次第であり、この柔軟性がLast-Modifiedと比較したときのETagの魅力である。

ETagがWeak Validatorの場合、値は「W/」から始まる。値の先頭に「W/」がなければそのEtagはStrong Validatorであることを表す。

ETagをStrong Validatorとして使用する場合、その値はResourceの版数変更に追従して変更されるのが推奨とされている。