第46話で、イトは移転が確認できたold pathにだけ301 bridgeを掛けた。
verification clientは、old requestへ301、new requestへ200を受け取った。
DNS addressも変えていない。
ところが、隣のviewerだけは赤い404 pageを映し続けている。
「やっぱりoriginのrouteが戻ったんだ」
イトは301設定画面を開き、同じredirectをもう一度writeしようとした。
festival中継開始まで、あと九十秒。
同じrequestに、二つの現在が見える
ピコは、二台のresponse trayを照らした。
左は、originへ直接つながる閉じたverification lane。
右は、実際のviewerが通ったdelivery laneだ。
イト
同じold URLなのに、301と404が同時に返るの?
ピコ
request条件が同じでも、途中のcacheが以前のresponseを再利用すれば、originのcurrent responseとは違うものが見えるよ
イト
cacheって、古い画像を置くfolderじゃないの?
ピコ
HTTP cacheは、以前のresponse messageを保存して、後のrequestへ使えるか判断する仕組みなんだ
この話のshared cache、origin lane、control planeは、Internetから切り離したfestival.example学習fixtureだ。
どのresponseを保存したか、誰がcacheを管理するか、観測headerが出るかを固定している。
実在CDNのpurge方法やbrowser挙動を再現するものではない。
cacheは、以前のresponseを再利用する
RFC 9111は、HTTP cachingの目的を、以前のresponse messageを再利用して現在のrequestを満たし、latencyとnetwork overheadを減らすことだと説明する。
よく保存されるのはGETに対する200 responseだ。
でも、保存対象は画像fileだけではない。
redirectや404のようなnegative resultも、条件を満たせば保存 / 再利用され得る。
だから第46話でrouteを直す前に返った404が、cache shelfにresponse丸ごと残る場合がある。
originの設定変更は、すでに別のcacheへ保存されたresponseを魔法のように一斉削除しない。
originが301を返せる現在と、cacheがfresh 404を返す現在は、一時的に並び得る。
private cacheとshared cache
HTTP cacheには、利用者一人に専用のprivate cacheと、複数clientのresponseを扱うshared cacheがある。
user agentの一部として動くcacheはprivate cacheの例だ。
proxyやCDNのように、複数のrequestを受ける場所にはshared cacheがあり得る。
ただし、名前や配置だけで、実際にどのresponseを返したかは決められない。
browser、service worker、shared delivery cache、application内部のmemoなど、似た「cache」という言葉は別の層へ使われる。
このepisodeが扱うのはHTTP shared cacheだけだ。
browser private cacheは次話へ分ける。
Ageは「あと何秒」ではない
イトはviewerの404 responseを開いた。
request: GET
/program/finale
response: 404
Cache-Control: max-age=120
Age: 43
Cache-Status: festival-shared; hit
Age 43を見て、イトは「あと四十三秒で消える」と書きかけた。
でも、RFC 9111のAgeが表すのは、originでresponseが生成またはvalidationされてからの推定経過秒だ。
残りfreshnessを直接示す数字ではない。
このfixtureのexplicit freshness lifetimeはmax-ageの120秒。
単純化したfixture clockでは、current age 43秒のresponseはまだfreshで、残りは77秒だ。
fresh responseは、originへvalidationせず再利用できる。
viewer requestがoriginのnew 301へ届かず、shared cacheのold 404だけで完了した理由が見えてくる。
実際のage calculationはnetwork delayや複数cacheも考慮する。
Ageがないだけでcache missと断定もしない。
Cache-Statusは、あれば使える追加証拠
RFC 9211は、cacheがrequestをどう処理したかを示すCache-Status response fieldを定義している。
hitは、そのcacheがrequestをforwardせず、cacheからresponseを得て満たしたことを示す。
fwdがあれば、origin方向へforwardした理由を表せる。
ただし、Cache-Statusはすべてのserviceが必ず出すfieldではない。
値がなければ「cacheなし」とは言えない。
このfixtureでは、観測学習のためfestival-shared; hitを出している。
Age、Date、Via、Cache-Status、service固有header、trace、logを、利用できる範囲で組み合わせる。
一つのheaderだけを万能判定stampにしない。
exact keyが合うresponseだけを選ぶ
cacheは、保存responseを何にでも返すわけではない。
RFC 9111では、cache keyは少なくともrequest methodとtarget URIから構成される。
多くのHTTP cacheが主にGETを保存するため、実装上URIを中心に選ぶ場合もある。
responseにVaryがあれば、指定されたrequest header fieldsもselectionへ関わる。
イトはviewer requestを固定した。
method: GET
target URI:https://festival.example/program/finale
Vary fields in fixture: none
queryが一文字違うrequestや、別method、Varyで選ばれる別representationを同じentryだと決めない。
「このpageのcache」ではなく、どのrequestに対応するstored responseかを見る。
no-cacheとno-storeを、削除buttonと思わない
イトはrequestへno-storeと書けば、棚のold 404が消えるのではと考えた。
RFC 9111のrequest no-storeは、cacheへrequest / responseを保存しないよう求めるdirectiveだ。
すでにstored responseからrequestが満たされた場合、その古いstored responseを削除する命令にはならない。
no-cacheも「保存禁止」という名前ではない。
stored responseを別requestへ使う前に、successful validationを要求する意味だ。
cache directiveは、所有外の全cacheを遠隔消去するbuttonではない。
今あるentryをどうinvalidate / purgeするかは、cacheのcontrol planeと所有権、service仕様を確認する。
イトの前に、三つの選択肢が並ぶ
中継開始まで、あと五十五秒。
viewerはまだ404。
origin設定画面には、同じ301を再writeするbuttonがある。
shared cache control planeには、all purgeとexact-key invalidateが並んでいる。
A originへ、同じ301をもう一度writeする
direct laneはすでに301→200を返す。
shared cacheのfresh 404がrequestをoriginへforwardしていないなら、同じorigin writeを重ねても、そのstored responseを直接消さない。
B festival cacheを、全部purgeする
古い404は消える可能性がある。
しかし、変更していないposter、CSS、program imagesのfresh entriesまで失う。
中継直前にorigin loadとcache missを広げ、何が原因だったかも曖昧になる。
C current originとcached responseを分け、exact keyだけinvalidateする
direct origin trayの301→200と、viewer delivery trayの404 / Age / Cache-Status hitを別々に残す。
method、target URI、Vary conditionsからold 404 entryのkeyを特定する。
そのshared cacheをイトのteamが管理していることを確認する。
fixture固有control planeで、そのexact keyだけをinvalidateする。
ピコはpurge buttonを押さなかった。
「どの棚かだけでなく、どのrequestの返事かを選ぶんだ」
イトはCを選んだ。
origin再write画面を閉じた。
左のcurrent origin trayへ301→200、右のshared cache trayへold 404とAge clockを置く。
二つを重ねず、exact GET URIのthin key plateをshared trayへ差し込んだ。
一通だけを、shared shelfから外す
control planeは、fixture cache ownerがイトのteamであることを示した。
イトはall purge coverを閉じる。
exact key previewに、old 404 response一通だけが現れた。
poster image、CSS、program imageのentriesは別keyだ。
イトは自分の手で、old 404 keyだけをinvalidateした。

cache missの先で、current responseに会う
同じviewerから、同じGET requestを送る。
今度のshared cacheには、選べるold 404 entryがない。
Cache-Status: festival-shared; fwd=uri-miss
fixture shared cacheはrequestをorigin方向へforwardした。
返ったのは、第46話で設定したcurrent 301だ。
viewerはLocationのnew targetへ進み、200 responseとfinale programを受け取った。
赤い404 pageが消え、gold curtainが開く。
同時に、poster imageとCSSのindicatorはcache hitのままだった。
all purgeをしなかったため、変更のないfresh entriesは再利用される。
originへ301を再writeする必要もなかった。
古い表示を見たら、originだけを疑わない
中継clockが0になった。
検証台には、三つの証拠が残る。
left origin trayのcurrent 301→200。
shared shelfから外したold 404 envelopeとAge clock。
触れずに残したposter / CSS / imageのfresh hit cards。
originのcurrent responseと、途中のcacheが再利用したstored responseを分ける。exact request、Age / freshness、optional cache evidenceを残し、所有下の該当keyだけを操作する。棚もkeyも分からないまま全cacheを消さない。
「cache hitは、originの今を見た証拠じゃない」
イトはall purge leverへ透明coverを下ろした。
そのとき、別のlaptopだけ、pageの本文はnew finaleなのに背景がold blue curtainのままだった。
shared cache indicatorは、どちらのlaptopでも同じcurrent responseを示している。
違うのは、一台のbrowserの内側だった。
次回:一台だけ、古い画像が残る
laptopの背後で、小さなprivate shelfが開いた。
HTML、CSS、image、fontのcardsが、別々のslotへ並んでいる。
「shared cacheを直しても、手元にもう一つ棚がある?」
イトはreloadを連打せず、browserが実際に使ったresponseを見ようとした。
次回、ピコルート第48話。
イトは、browser cacheを全部消す前に中身を見た|何が手元に残る?

