Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ふつうのWebアプリケーションとキャッシュ
Search
Yasuyuki Higa
March 27, 2024
Programming
1
130
ふつうのWebアプリケーションとキャッシュ
2024/3/27 に開催された PHP勉強会 in 大阪 での発表資料です。
httpキャッシュと動的コンテンツの話
Yasuyuki Higa
March 27, 2024
Tweet
Share
More Decks by Yasuyuki Higa
See All by Yasuyuki Higa
AI as a Tester
yasuyuki_higa
0
32
エンジニアが知っておくべき文字コード問題のあれこれ
yasuyuki_higa
0
150
Other Decks in Programming
See All in Programming
Beyond ORM
77web
8
1.2k
Online-Dokumentation, die hilft: Strukturen, Prozesse, Tools
ahus1
0
100
Haze - Real time background blurring
chrisbanes
1
520
良いユニットテストを書こう
mototakatsu
8
3.1k
htmxって知っていますか?次世代のHTML
hiro_ghap1
0
350
Webエンジニア主体のモバイルチームの 生産性を高く保つためにやったこと
igreenwood
0
340
情報漏洩させないための設計
kubotak
4
740
今年やったこと最近やったこと2024
saito5656
0
100
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
4
380
rails stats で紐解く ANDPAD のイマを支える技術たち
andpad
1
300
Androidアプリのモジュール分割における:x:commonを考える
okuzawats
1
180
Effective Signals in Angular 19+: Rules and Helpers
manfredsteyer
PRO
0
120
Featured
See All Featured
Code Review Best Practice
trishagee
65
17k
Statistics for Hackers
jakevdp
796
220k
Building Better People: How to give real-time feedback that sticks.
wjessup
366
19k
How to Ace a Technical Interview
jacobian
276
23k
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Speed Design
sergeychernyshev
25
670
Rails Girls Zürich Keynote
gr2m
94
13k
YesSQL, Process and Tooling at Scale
rocio
169
14k
The Pragmatic Product Professional
lauravandoore
32
6.3k
A Modern Web Designer's Workflow
chriscoyier
693
190k
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
Transcript
ふつうのWebアプリケー ションとキャッシュ PHP勉強会 in 大阪 Yasuyuki Higa @yh65743
自己紹介 名前: 比嘉 康至(ひが やすゆき) X(旧Twitter): @yh65743 勤務先: 株式会社ことば研究所 居住地:
奈良(フルリモートで勤務しています) ❏ PHP ❏ React ❏ TypeScript 普段はこのあたりを使ってWebアプリケーション開発/ 保守やってます
今日話すこと • キャッシュとは何か • HTTPキャッシュの仕組み • 動的コンテンツのキャッシュ 今日話さないこと • Redis
や APCu 等キャッシュ用ストレージの話 • DBキャッシュの話 • read-through, write-through といったキャッシュのアーキテクチャの話
何が「ふつうのWebアプリケーション」で何が「キャッシュ」 なのか キャッシュとは → 高コストな処理/計算の結果をどこかに保存しておいて再利用できるようにす る仕組みのこと httpにおいては 「高コストな処理/計算」: アプリケーションサーバーの計算結果 「結果」:
http レスポンス 「どこか」:ブラウザ、CDN、リバースプロキシ等
ふつうのWebアプリケーションとは • ブログ、ニュースサイト、ECサイト、求人サイト、etc… • PHPでよく作られるCMS的なアプリケーション
• うちのサイトそんなにPVないし。。。 • 静的コンテンツはWebサーバーが勝手に キャッシュしてくれるし動的コンテンツは キャッシュするの無理だから気にしなくても いいでしょ キャッシュについて考慮されてないことが多い ふつうのWebアプリケーションとは •
ブログ、ニュースサイト、ECサイト、求人サイト、etc… • PHPでよく作られるCMS的なアプリケーション
• うちのサイトそんなにPVないし。。。 → マナーの悪いbotが大挙して押し寄せてき ても大丈夫? → メディアに取り上げられたりして急なスパ イクがあっても耐えられる? → リクエスト毎に重めのクエリが発行される
ようになってませんか? ふつうのWebアプリケーションとは • ブログ、ニュースサイト、ECサイト、求人サイト、etc… • PHPでよく作られるCMS的なアプリケーション
ふつうのWebアプリケーションとは • ブログ、ニュースサイト、ECサイト、求人サイト、etc… • PHPでよく作られるCMS的なアプリケーション • 静的コンテンツはWebサーバーが勝手に キャッシュしてくれるし動的コンテンツは キャッシュするの無理だから気にしなくて もいいでしょ
→ ここで質問
皆さん、Laravel使ってますか?
皆さん、Laravel使ってますか? 問題: Laravelの返すレスポンスはデフォルトでブラウザに キャッシュとして保存されるようになっている Yes or No?
皆さん、Laravel使ってますか? 問題: Laravelの返すレスポンスはデフォルトでブラウザに キャッシュとして保存されるようになっている Yes or No?
皆さん、Laravel使ってますか? 問題: Laravelの返すレスポンスはデフォルトでブラウザに キャッシュとして保存されるようになっている Yes or No? → 正確に言うと、Laravel のレスポンスはデフォルトだと「キャッシュされる
けど、そのキャッシュが使われることはない」状態になっている
LaravelのCache-Controlヘッダを見てみる • HTTPキャッシュの制御は Cache-Control ヘッダで行う • Laravel のCache-Control ヘッダ Cache-Control:
no-cache, private なーんだ no-cache って書いてあるじゃん
LaravelのCache-Controlヘッダを見てみる • HTTPキャッシュの制御は Cache-Control ヘッダで行う • Laravel のCache-Control ヘッダ Cache-Control:
no-cache, private なーんだ no-cache って書いてあるじゃん → no-cache はキャッシュしないという意味では ない!
Cache-Controlヘッダ Cache-Control: no-cache は 「キャッシュを使うときは必ずオリジンに問い合 わせ、キャッシュが有効でない限り使用しない」という意味 キャッシュさせたくないときは no-store を指定する。 「オリジンに問い合わせ」→
配信元に対して「条件付きリクエスト」という特殊 なリクエストを送る
条件付きリクエスト 条件付きリクエストとは: サーバーにキャッシュが新鮮か尋ねるリクエスト。も し新鮮ならステータスコード 304 Not Modified とヘッダだけが返ってきてブラ ウザはキャッシュをボディとして再利用する。 キャッシュが新鮮でなければ普通にボディが返ってくる。
条件付きリクエスト 条件付きリクエストとは: サーバーにキャッシュが新鮮か尋ねるリクエスト。も し新鮮ならステータスコード 304 Not Modified とヘッダだけが返ってきてブラ ウザはキャッシュをボディとして再利用する。 キャッシュが新鮮でなければ普通にボディが返ってくる。
ではキャッシュが新鮮かどうかはどうやって判定しているか?
条件付きリクエストの判定 リソースの最終更新日。 ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" Last-Modified: Thu, 21 Mar 2024 17:38:20
GMT キャッシュ制御のためにレスポンスに含まれているヘッダは Cache-Control 以外にもある。キャッシュが新鮮かどうか判定するために使われるのが ETag と Last-Modified ETag レスポンスとして返すコンテンツのバージョン番号のようなもの。何をETagとして返すか はサーバ側の実装次第だけどコンテンツの更新日時やコンテンツそのもののハッシュ等が 使われる。 Last-Modified
条件付きリクエストの判定 (ETagの場合) サーバーは初回リクエストしてきたブラウザに対して Cache-Control: no-cache ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" というヘッダのレスポンスを返す ブラウザはレスポンスをキャッシュする。ETagの値も覚えておく。
条件付きリクエストの判定 (ETagの場合) ブラウザは同じURLに対して再度リクエストする。そのとき If-None-Match というヘッダ を付与し、その値として覚えていた ETag の値を使用する If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
サーバーは If-None-Match 付きのリクエストを受け取ると、ETag の値を計算したのと同 じ方法でリソースから値を得る。例えば、ファイルの更新日のハッシュを計算する。その結 果を If-None-Match の値と比較し、一致していたらステータスコード 304 Not Modified とヘッダ部分のみのレスポンスを返す ブラウザは304のレスポンスが得られたらキャッシュをレスポンスのボディとしてユーザー に表示する
条件付きリクエストの判定 (Last-Modifiedの場合) サーバーは初回リクエストしてきたブラウザに対して Cache-Control: no-cache Last-Modified: Tue, 26 Mar 2024
00:40:43 GMT というヘッダのレスポンスを返す ブラウザはレスポンスをキャッシュする。Last-Modified の値も覚えておく。
条件付きリクエストの判定 (Last-Modifiedの場合) ブラウザは同じURLに対して再度リクエストする。そのとき If-Modified-Since という ヘッダを付与し、その値として覚えていた Last-Modified の値を使用する If-Modified-Since: Tue,
26 Mar 2024 00:40:43 GMT サーバーは If-Modified-Since 付きのリクエストを受け取ると、リソースの最終更新日と If-Modified-Sinceの値を比較し、リソースが更新されていなければステータスコード 304 Not Modifiedとヘッダ部分のみのレスポンスを返す ブラウザは304のレスポンスが得られたらキャッシュをレスポンスのボディとしてユーザー に表示する
Laravelのレスポンスキャッシュが何故使われないか • レスポンスヘッダの ETag/Last-Modified がキャッシュを使っていいかどう かの判定に使われることがわかりました。 • ではここで、すっぴんの Laravel 君が返してくれたレスポンスヘッダを眺め
てみましょう。
Laravelのレスポンスキャッシュが何故使われないか • レスポンスヘッダの ETag/Last-Modified がキャッシュを使っていいかどう かの判定に使われることがわかりました。 • ではここで、すっぴんの Laravel 君が返してくれたレスポンスヘッダを眺め
てみましょう。 あれ?
Laravelのレスポンスキャッシュが何故使われないか • レスポンスヘッダの ETag/Last-Modified がキャッシュを使っていいかどう かの判定に使われることがわかりました。 • ではここで、すっぴんの Laravel 君が返してくれたレスポンスヘッダを眺め
てみましょう。 あれ? ETag も Last-Modified もない!
Laravelのレスポンスキャッシュが何故使われないか • レスポンスヘッダに ETag も Last-Modified もないので、次回のリクエスト でブラウザは条件付きリクエストを送ることができない • よって通常のリクエストが送られ、サーバは常にボディ付きのレスポンスを
返すことになる。
Laravelのレスポンスキャッシュが何故使われないか • レスポンスヘッダに ETag も Last-Modified もないので、次回のリクエスト でブラウザは条件付きリクエストを送ることができない • よって通常のリクエストが送られ、サーバは常にボディ付きのレスポンスを
返すことになる。 ・・・
Laravelのレスポンスキャッシュが何故使われないか • レスポンスヘッダに ETag も Last-Modified もないので、次回のリクエスト でブラウザは条件付きリクエストを送ることができない • よって通常のリクエストが送られ、サーバは常にボディ付きのレスポンスを
返すことになる。 ・・・ 安全側に倒すなら no-store にしとけばいいところ、あえて no-cache にしてい るところにメッセージを感じる(と、私は勝手に思っている)
キャッシュのロジックを自分で実装してみよう 雑実装につき注意 middleware を使えば この辺のことは勝手に やってくれますが、わ かりやすいように自力 でやっています
キャッシュのロジックを自分で実装してみよう • レスポンスボディのハッシュを比較するだけのシンプルな実装 • これだとレスポンスボディの計算コストはそのまま。途中重たいクエリの実 行が含まれていたら負荷軽減にはならない。 • じゃあ動的コンテンツのキャッシュは不可能?
キャッシュのロジックを自分で実装してみよう • レスポンスボディのハッシュを比較するだけのシンプルな実装 • これだとレスポンスボディの計算コストはそのまま。途中重たいクエリの実 行が含まれていたら負荷軽減にはならない。 • じゃあ動的コンテンツのキャッシュは不可能? → 実際に計算しなくてもコンテンツが更新される根拠を知ることができるので
は?
キャッシュのロジックを自分で実装してみよう • 例えばECサイトの「商品一覧」ページ • productsテーブルが更新されていないならコンテンツとして同一である、み たいなことが言えるのでは?
Laravel門外漢の雑実装その2 擬似コード的な内容ですがやり たいことは伝わるのではないで しょうか。 実際にはこの辺のETag絡みの処 理は middleware に切り分けた ほうがいいと思います。 ここでは省略していますがこの
ままだとviewのコードをいじっ てもキャッシュが更新されない ので、アプリケーションのバー ジョン情報などを設けておいて ETagに含めたほうがいいか も。
さらなる負荷軽減のために • これなら2回目以降のリクエストがproductsの更新前なら、products に対す る重たいクエリの代わりにレコード一件の取得で済む。 • ただし、これだと条件付きリクエストのたびにDBアクセスが発生することに なる。 • 新商品入荷日にF5連打するような人がたくさんいたらどうしよう。
• もう少し改善できないものか。。。?
さらなる負荷軽減のために • これなら2回目以降のリクエストがproductsの更新前なら、products に対す る重たいクエリの代わりにレコード一件の取得で済む。 • ただし、これだと条件付きリクエストのたびにDBアクセスが発生することに なる。 • 新商品入荷日にF5連打するような人がいたらどうしよう。
• もう少し改善できないものか。。。? → 商品の更新を必ずしもリアルタイムで反映できなくてもいいのでは?
max-age、Expires • Cache-Control: max-age=300 のように指定するとキャッシュのTTLを指 定できる。この例だとレスポンスが生成された時点から300秒間はキャッ シュを使用し、条件付きリクエストも送られなくなる。300秒経過後、再度 リクエストした場合には条件付きリクエストが送られる。 • Expires
は相対的な秒数ではなくて、TTLが切れる時刻を絶対時間で指定で きる。Expires: Wed, 21 Oct 2015 07:28:00 GMT のように指定。 max-ageと同時に指定すると max-age が優先される。max-ageに対応して ない古いブラウザ向け。 • キャッシュのTTLを指定してやるとリアルタイムに更新が反映されなくなる が、条件付きリクエストの頻度を抑えることができる。
Proxy, CDN • ここまででブラウザキャッシュの話をしてきましたが、これはいわゆる private キャッシュで、同じ人が2回目以降にアクセスしてきた時の話。 • つまり、初見のユーザーが大量に押し寄せてきた、みたいなケースには対応 できない。 •
「商品一覧」は見る人によって表示が変わるわけではないので一度キャッ シュされた内容が広く共有されてほしい → sharedキャッシュが必要。 • そこでブラウザだけでなく Proxy や CDN にもキャッシュしてもらう。
Proxy, CDN Proxy: いわゆるリバースプロキシ。Varnish, Squid, Nginx等。Webサーバーの 前でリクエストを処理してくれる。 CDN: Contents Delivery
Network。ウェブコンテンツをインターネット経由で 配信するために最適化されたネットワークのこと。様々な機能があるが、Proxy 同様Webサーバーの前でキャッシュによるレスポンスを行ってくれる。
Proxy/CDN Origin 1. ユーザーAが商品一覧ページ をリクエスト 2. Proxy/CDN はキャッシュがな いのでコンテンツをオリジンに リクエスト
3. オリジンは Proxy/CDN にコ ンテンツをレスポンス 4. Proxy/CDN はオリジンからの レスポンスをキャッシュしたう えでユーザーにレスポンスす る。 5. ユーザーBが商品一覧ページ をリクエスト 6. Proxy/CDNはユーザーAのリ クエスト時にキャッシュしたコ ンテンツをBにレスポンス
Cache-Control: private, public • これでキャッシュを共有して初見のユーザーにも対応できるようになりまし た。 • でも共有されてほしくない情報もある。例えば個人情報とか。その手のセン シティブなコンテンツが shared
キャッシュに載らないかちょっと不 安。。。 • そういえば Laravel の返してきた Cache-Control ヘッダに no-cache 以外 にも何かついてましたね。
Cache-Control: private, public Cache-Control: private レスポンスがプライベートキャッシュ(ブラウザーのローカルキャッシュなど) にのみ保存できることを示す。これが付いてると Proxy や CDN
にはキャッシュ されなくなる。 Cache-Control: public これがないと Proxy や CDN がキャッシュしてくれない・・・わけではない。リ クエストに Authorization ヘッダが付いていると普通そのレスポンスは shared キャッシュとして保存してはいけないのだが、それでもキャッシュさせたい、と いう場合に付ける。
Cache-Control: private, public • Cache-Control: private/public は単にキャッシュの保存先を指定するだけで はなく、「通常キャッシュされないレスポンスでもキャッシュする」という 意味を持っていることに注意。 •
例えば 403 のようなステータスコードでもキャッシュされてしまう可能性が ある(ブラウザや Proxy/CDN の実装によります) • CDN が public を付けないとキャッシュしない仕様だとか、リクエストに Authorization ヘッダが付いていてもキャッシュしたいとかでなければ public は付けないほうがいい
まとめ • キャッシュは思ってるより身近なもの • 動的コンテンツでも工夫次第ではキャッシュできるよ • キャッシュと仲良くしてエコなアプリケーション開発をしよう 参考文献 田中 祥平
『Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する』技術評論社 (2021/2/10) MDN Web Docks “Cache-Control - HTTP | MDN” https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cache-Control IETF “RFC 9110: HTTP Semantics” https://www.rfc-editor.org/rfc/rfc9110.html IETF “RFC 9111: HTTP Caching” https://www.rfc-editor.org/rfc/rfc9111.html