『Httpキャッシュを使いこなして、Webアプリを快適に』を読む

これはなに

よむやつです。よむのはこれ。連続した記事。

https://eng-blog.iij.ad.jp/archives/18584

読む - HTTPキャッシュを使いこなして、Webアプリを快適に(1)

更新時間が予想外に遅延する

  • キャッシュ期間は前回キャッシュが作成されてからの時間
  • データの更新直前にキャッシュされると、予想外にキャッシュが保存されてしまう
  • キャッシュ期間を短くすると、今度は効率が落ちる

読む- HTTPキャッシュを使いこなして、Webアプリを快適に(2)

HTTPキャッシュの検証

  • サーバーのデータとキャッシュを比較する検証機能
  • キャッシュ期間を過ぎていても、検証の結果同一と判定されれば通信せずに済む
  • 同一と判定したとき、304 Not Modifiedが返される

Webアプリ側の「304 Not Modified」の処理

  • webアプリ側が実装してないといみなし

実装の仕方はふたつ

  • コンテンツ(ファイル)の更新時間の新しさで判断する(If-Modified-Sinceヘッダー利用)
  • コンテンツ(ファイル)の識別子の一致で判断する(If-None-Matchヘッダー利用)

コンテンツの更新時間の新しさで判断する

  • ブラウザがデータを参照したときに、サーバから更新時間もらう
    • 次回のリクエストで更新時間を貼り付ける
  • キャッシュ期間と見比べて、更新するか304を返すか決める

コンテンツ識別子で「304 Not Modified」

  • コンテンツの識別子で判断
  • 時刻の代わりにEtagを渡している
    • 柔軟な形で一致判定できるので、よい!
    • modified-sinceとnone-matchが一緒に送られてきたら、none-matchを優先する
  • 感想
    • どうサーバーサイドで実装するんだろうか、、

同じURLの異なるWebページ

  • 通常はURL単位でキャッシュする
  • URL同じなのにコンテンツが違うケース
    • 多言語対応とか
  • URLだけでは判断できない場合はVaryヘッダーをつける

プロキシのキャッシュ

  • プロキシもブラウザ同様キャッシュする
  • どんなデータをキャッシュするか? は区別する必要がある
    • クレジットカードの情報などはプロキシにキャッシュされたら困る。
    • 管理してるのが第三者だし
  • ブラウザ側で考える?
    • プロキシ自身が、こういうのは受け付けちゃダメだよーってする?

続・HTTPキャッシュを使いこなして、Webアプリを快適に(1)

Cache-Controlヘッダー

  • Cache-Controlヘッダーには複数のディレクティブを指定できる
    • no-cacheとか、、
  • いくつかのルール
    • 矛盾しないように書く
    • 新しいディレクティブは古いブラウザで無視される
      • とはいえ、古いブラウザに準拠しててもしゃあないから新しいの使ってええで

2種類のcache-control

  • 要求と反応で意味が違う

リクエストのcache-control

  • ブラウザ側からの要求
    • スーパーリロードとか、、
    • no-cache
      • キャッシュを無視して新しいデータをもらう
    • max-age
      • 生存できるキャッシュ期間を秒で指定できる。

レスポンスのcache-control

  • ブラウザにキャッシュの扱いについて要求できる。
    • へー
  • けどめちゃくちゃ複雑
    • おー
  • みっつの判断(?)でキャッシュをどうするか決まる
    • キャッシュの検証をするか
    • キャッシュは新鮮か
    • 接続失敗したときにキャッシュを使うか

A判断「 検証は必要か?」

  • ブラウザがキャッシュ期間を無視して検証するかを決める判断
    • ここのキャッシュ期間ってのは、レスポンスで帰ってきたやつ? サーバー側が指定してる?
      • そうっぽい、おけおけ
  • それを無視するかどうかの判断軸をサーバーがブラウザに渡してあげている
    • 判断軸に使えるディレクティブは下記
条件
対象
検証の有無
補足
no-cache
有り
ブラウザおよびプロキシ
検証する
no-store
有り
ブラウザおよびプロキシ
検証する
C判断にも影響を与える
private
有り
プロキシ
検証する
ブラウザは
private
を無視する
その他(デフォルト)
ブラウザおよびプロキシ
検証しない

B判断「キャッシュは新鮮か?」

  • キャッシュが新鮮かどうかの判定
    • Aで常に検証するとなった場合は、これを考慮しない
    • つまりAの検証しないとなってもBで判定する?
      • Aはコンテンツの更新時間とかでの検証で、Bのこれは別の判断軸?
条件
キャッシュ期間が適用されるシステム
max-age
のみ指定
ブラウザもプロキシもどちらも適応される。
s-maxage
のみ指定
プロキシのみに適応される。
max-age
s-maxage
の両方を指定
ブラウザには
max-aga
の値が、プロキシには
s-maxage
の値が適応される。
ブラウザとプロキシに異なるキャッシュ期間を設定したいときに使う。
  • ちがうな、、ここでも検証にキャッシュ期間つかってる。
    • キャッシュ期間の比較してるだけで、検証(サーバーデータ比較)とは異なるのでok
  • 分からんポイント
    • キャッシュが古い、となったときも検証つきのデータを取得している
      • あそうか、検証はキャッシュ期間が古いかどうかを見てて、データ更新の有無ではない
    • Aで検証不要となった上でBで新鮮かどうかの検証してるのがよく分からない
      • Aではno-cacheとかのやつ。強制リロードじゃないけど、期間決まってないかはBで検証するよ、ってことか。
      • 検証はキャッシュ期間を見ることだよな、、じゃあAの検証不要は、ほんとうに不要なのではなくて、詳細に検証する、くらいの意味だな?
      • いや検証ってのはデータ更新の有無を見ることなのか…?
      • つまり、A検証判断は必要 or 些細に判断、B新鮮か? で検証の要不要を見ている

C判断「 接続失敗時、キャッシュを使用するか?」

  • 通信が失敗したとき、古いキャッシュで動き続ける
    • これを防ぐ。以下を設定すると、キャッシュを使わなくなる

ディレクティブ名
対象のシステム
補足
no-store
ブラウザとプロキシの両方
no-cache
must-revalidate
の並記と同等
must-revalidate
ブラウザとプロキシの両方
通信が成功したときの動作に影響はない
proxy-revalidate
プロキシのみを対象
通信が成功したときの動作に影響はない
  • わからん
    • 応答時のやつなのに、どうやって伝える?
    • すでにこれらがレスポンスとして送られていて、その状態でリクエストに失敗すると、これらが使われる?

外部ストレージへの保存について

  • cache-controlで外部ストレージにキャッシュを保存するかどうかを決められる
    • no-store, privateは外部ストレージに保存されないとRFCは言ってるけど、でかいデータを送受信するとき、メモリに乗り切らなかったら外部ストレージに保存せざるを得ない。
    • ので、これらのディレクティブは信頼しない方が良い。将来変わるかも。

続・HTTPキャッシュを使いこなして、Webアプリを快適に(2)

ディレクティブを組み合わせる

  • ディレクティブを組み合わせて、例のフローを制御する

例1: 認証情報を表示するページを保護したい

  • 各判断
    • A判断 – 常に最新のページを表示したい。(no-cacheまたは、no-storeが必要)
    • B判断 – 短時間でも検証しない状況は許さない。(指定するならmax-age=0ですが、常に検証するので使われない)
    • C判断 – 認証情報が含まれるので、通信失敗時は表示させたくない。(no-storeまたは、must-revalidateが必要)
  • 結論
    • no-store
  • おもしれ

例2: 単に情報をリアルタイムに更新したい

  • 各判断
    • A判断 – プログラムがリアルタイム生成する最新のページを表示したい。(no-cacheまたは、no-storeが必要)
    • B判断 – 頻繁に検証させたい。(指定するならmax-age=0だが、A判断の結果、使われない)
    • C判断 – 通信失敗時は前回のデータで表示した方が良い。(no-storemust-revalidateproxy-revalidateの利用は避ける)
  • 結論
    • no-cache

例3: 認証無しの共有ページなので、接続数を減らしつつ、変更の反映はすみやかにしたい

  • 各判断
    • A判断 – キャッシュを多少は使って接続数を減らしたいほしい。(no-cacheおよびno-storeは使えない)
    • B判断 – リアルタイムで反映される必要はないので、反映まで1分ぐらいは待てる。(キャッシュ期間は短めにする必要があるので、仮に30秒でmax-age=30)
    • C判断 – 通信失敗時は前回のデータで表示した方が良い。(no-storemust-revalidateproxy-revalidateはどれも使わない)
  • 結果
    • Cache-Control: max-age=30

例4: 顧客情報を扱うページだけど、サーバは非力なので更新頻度は低いからキャッシュしたい

  • 各判断
    • A判断 – ブラウザのキャッシュを再利用してほしい。プロキシでは、キャッシュを再利用してほしくない。(private一択。no-cacheおよびno-storeは使えない。)
    • B判断 – 反映の遅れが許されるので、キャッシュ期間を長くできる。(仮に300秒で、max-age=300 )
    • C判断 – 認証失敗で表示されたくない。(no-storemust-revalidateのどちらかが必要)
  • 結論
    • Cache-Control: max-age=300, private, must-revalidate
  • おもしれーっ!!

WebサーバとWebアプリ

  • Webサーバのプロキシ機能でWebアプリを動かすと、プロトコルの処理やキャッシュ機能をWebサーバが代行してくれるので、楽ができます。
    • へー
  • けどハマりどころもあるよ

Cache-Controlが共有されてしまう

  • ブラウザ、プロキシ、Webサーバの全てでCache-controlが共有される
    • プロキシはブラウザとしての側面も持つので、Webサーバ(プロキシ)みたいになると、みんなブラウザの側面を持つことになる
      • えじゃプロキシとブラウザで振る舞いをかえるみたいな話はどこにいったんだ
  • 対策1: どこでもちゃんと動くようにする
    • むり
    • できたとしても読みにくい
  • 対策2: webアプリとwebサーバーで別のcache-controlを指定する
    • webサーバーでcache-controlを付け替えるかんじ
      • ブラウザ向けのCache-Controlヘッダーは、Webサーバで設定する。
      • Webサーバ用のCache-Controlヘッダーは、Webアプリに出力させる。
      • Webアプリの出力したCache-Controlヘッダーは、Webサーバで消す。(Webサーバしか見ない。

nginxでキャッシュ使うときのTIPS

  • 省略

感想

  • なるほどね〜
    • リクエスト時とレスポンス時で動きが違うとか、サーバー、プロキシ、ブラウザで適用されるされないが違うとか、そういう細かな動きが分かって良かった
    • みっつの判断があり、そこでフローチャートが分岐するのも分かってよかった
      • たぶん実際はもうちょい細かいんだろうけど、、
    • あーあとなにより、キャッシュ期限切れかの確認と、検証(コンテンツの更新有無)が別物というのを認識できてよかった
      • ここ混ざっちゃいそうだから
  • 分からんのは
    • 具体的な実装
    • webサーバーがどのようにwebアプリと協調するのかの話
      • ここはずっと分からん。wsgiってなんやと思ってた頃からずっとふわふわしてる、一瞬分かった気になってそのあと忘れた