Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
ふつうのWebアプリケー ションとキャッシュ PHP勉強会 in 大阪 Yasuyuki Higa @yh65743
Slide 2
Slide 2 text
自己紹介 名前: 比嘉 康至(ひが やすゆき) X(旧Twitter): @yh65743 勤務先: 株式会社ことば研究所 居住地: 奈良(フルリモートで勤務しています) ❏ PHP ❏ React ❏ TypeScript 普段はこのあたりを使ってWebアプリケーション開発/ 保守やってます
Slide 3
Slide 3 text
今日話すこと ● キャッシュとは何か ● HTTPキャッシュの仕組み ● 動的コンテンツのキャッシュ 今日話さないこと ● Redis や APCu 等キャッシュ用ストレージの話 ● DBキャッシュの話 ● read-through, write-through といったキャッシュのアーキテクチャの話
Slide 4
Slide 4 text
何が「ふつうのWebアプリケーション」で何が「キャッシュ」 なのか キャッシュとは → 高コストな処理/計算の結果をどこかに保存しておいて再利用できるようにす る仕組みのこと httpにおいては 「高コストな処理/計算」: アプリケーションサーバーの計算結果 「結果」: http レスポンス 「どこか」:ブラウザ、CDN、リバースプロキシ等
Slide 5
Slide 5 text
ふつうのWebアプリケーションとは ● ブログ、ニュースサイト、ECサイト、求人サイト、etc… ● PHPでよく作られるCMS的なアプリケーション
Slide 6
Slide 6 text
● うちのサイトそんなにPVないし。。。 ● 静的コンテンツはWebサーバーが勝手に キャッシュしてくれるし動的コンテンツは キャッシュするの無理だから気にしなくても いいでしょ キャッシュについて考慮されてないことが多い ふつうのWebアプリケーションとは ● ブログ、ニュースサイト、ECサイト、求人サイト、etc… ● PHPでよく作られるCMS的なアプリケーション
Slide 7
Slide 7 text
● うちのサイトそんなにPVないし。。。 → マナーの悪いbotが大挙して押し寄せてき ても大丈夫? → メディアに取り上げられたりして急なスパ イクがあっても耐えられる? → リクエスト毎に重めのクエリが発行される ようになってませんか? ふつうのWebアプリケーションとは ● ブログ、ニュースサイト、ECサイト、求人サイト、etc… ● PHPでよく作られるCMS的なアプリケーション
Slide 8
Slide 8 text
ふつうのWebアプリケーションとは ● ブログ、ニュースサイト、ECサイト、求人サイト、etc… ● PHPでよく作られるCMS的なアプリケーション ● 静的コンテンツはWebサーバーが勝手に キャッシュしてくれるし動的コンテンツは キャッシュするの無理だから気にしなくて もいいでしょ → ここで質問
Slide 9
Slide 9 text
皆さん、Laravel使ってますか?
Slide 10
Slide 10 text
皆さん、Laravel使ってますか? 問題: Laravelの返すレスポンスはデフォルトでブラウザに キャッシュとして保存されるようになっている Yes or No?
Slide 11
Slide 11 text
皆さん、Laravel使ってますか? 問題: Laravelの返すレスポンスはデフォルトでブラウザに キャッシュとして保存されるようになっている Yes or No?
Slide 12
Slide 12 text
皆さん、Laravel使ってますか? 問題: Laravelの返すレスポンスはデフォルトでブラウザに キャッシュとして保存されるようになっている Yes or No? → 正確に言うと、Laravel のレスポンスはデフォルトだと「キャッシュされる けど、そのキャッシュが使われることはない」状態になっている
Slide 13
Slide 13 text
LaravelのCache-Controlヘッダを見てみる ● HTTPキャッシュの制御は Cache-Control ヘッダで行う ● Laravel のCache-Control ヘッダ Cache-Control: no-cache, private なーんだ no-cache って書いてあるじゃん
Slide 14
Slide 14 text
LaravelのCache-Controlヘッダを見てみる ● HTTPキャッシュの制御は Cache-Control ヘッダで行う ● Laravel のCache-Control ヘッダ Cache-Control: no-cache, private なーんだ no-cache って書いてあるじゃん → no-cache はキャッシュしないという意味では ない!
Slide 15
Slide 15 text
Cache-Controlヘッダ Cache-Control: no-cache は 「キャッシュを使うときは必ずオリジンに問い合 わせ、キャッシュが有効でない限り使用しない」という意味 キャッシュさせたくないときは no-store を指定する。 「オリジンに問い合わせ」→ 配信元に対して「条件付きリクエスト」という特殊 なリクエストを送る
Slide 16
Slide 16 text
条件付きリクエスト 条件付きリクエストとは: サーバーにキャッシュが新鮮か尋ねるリクエスト。も し新鮮ならステータスコード 304 Not Modified とヘッダだけが返ってきてブラ ウザはキャッシュをボディとして再利用する。 キャッシュが新鮮でなければ普通にボディが返ってくる。
Slide 17
Slide 17 text
条件付きリクエスト 条件付きリクエストとは: サーバーにキャッシュが新鮮か尋ねるリクエスト。も し新鮮ならステータスコード 304 Not Modified とヘッダだけが返ってきてブラ ウザはキャッシュをボディとして再利用する。 キャッシュが新鮮でなければ普通にボディが返ってくる。 ではキャッシュが新鮮かどうかはどうやって判定しているか?
Slide 18
Slide 18 text
条件付きリクエストの判定 リソースの最終更新日。 ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" Last-Modified: Thu, 21 Mar 2024 17:38:20 GMT キャッシュ制御のためにレスポンスに含まれているヘッダは Cache-Control 以外にもある。キャッシュが新鮮かどうか判定するために使われるのが ETag と Last-Modified ETag レスポンスとして返すコンテンツのバージョン番号のようなもの。何をETagとして返すか はサーバ側の実装次第だけどコンテンツの更新日時やコンテンツそのもののハッシュ等が 使われる。 Last-Modified
Slide 19
Slide 19 text
条件付きリクエストの判定 (ETagの場合) サーバーは初回リクエストしてきたブラウザに対して Cache-Control: no-cache ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" というヘッダのレスポンスを返す ブラウザはレスポンスをキャッシュする。ETagの値も覚えておく。
Slide 20
Slide 20 text
条件付きリクエストの判定 (ETagの場合) ブラウザは同じURLに対して再度リクエストする。そのとき If-None-Match というヘッダ を付与し、その値として覚えていた ETag の値を使用する If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" サーバーは If-None-Match 付きのリクエストを受け取ると、ETag の値を計算したのと同 じ方法でリソースから値を得る。例えば、ファイルの更新日のハッシュを計算する。その結 果を If-None-Match の値と比較し、一致していたらステータスコード 304 Not Modified とヘッダ部分のみのレスポンスを返す ブラウザは304のレスポンスが得られたらキャッシュをレスポンスのボディとしてユーザー に表示する
Slide 21
Slide 21 text
条件付きリクエストの判定 (Last-Modifiedの場合) サーバーは初回リクエストしてきたブラウザに対して Cache-Control: no-cache Last-Modified: Tue, 26 Mar 2024 00:40:43 GMT というヘッダのレスポンスを返す ブラウザはレスポンスをキャッシュする。Last-Modified の値も覚えておく。
Slide 22
Slide 22 text
条件付きリクエストの判定 (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のレスポンスが得られたらキャッシュをレスポンスのボディとしてユーザー に表示する
Slide 23
Slide 23 text
Laravelのレスポンスキャッシュが何故使われないか ● レスポンスヘッダの ETag/Last-Modified がキャッシュを使っていいかどう かの判定に使われることがわかりました。 ● ではここで、すっぴんの Laravel 君が返してくれたレスポンスヘッダを眺め てみましょう。
Slide 24
Slide 24 text
Laravelのレスポンスキャッシュが何故使われないか ● レスポンスヘッダの ETag/Last-Modified がキャッシュを使っていいかどう かの判定に使われることがわかりました。 ● ではここで、すっぴんの Laravel 君が返してくれたレスポンスヘッダを眺め てみましょう。 あれ?
Slide 25
Slide 25 text
Laravelのレスポンスキャッシュが何故使われないか ● レスポンスヘッダの ETag/Last-Modified がキャッシュを使っていいかどう かの判定に使われることがわかりました。 ● ではここで、すっぴんの Laravel 君が返してくれたレスポンスヘッダを眺め てみましょう。 あれ? ETag も Last-Modified もない!
Slide 26
Slide 26 text
Laravelのレスポンスキャッシュが何故使われないか ● レスポンスヘッダに ETag も Last-Modified もないので、次回のリクエスト でブラウザは条件付きリクエストを送ることができない ● よって通常のリクエストが送られ、サーバは常にボディ付きのレスポンスを 返すことになる。
Slide 27
Slide 27 text
Laravelのレスポンスキャッシュが何故使われないか ● レスポンスヘッダに ETag も Last-Modified もないので、次回のリクエスト でブラウザは条件付きリクエストを送ることができない ● よって通常のリクエストが送られ、サーバは常にボディ付きのレスポンスを 返すことになる。 ・・・
Slide 28
Slide 28 text
Laravelのレスポンスキャッシュが何故使われないか ● レスポンスヘッダに ETag も Last-Modified もないので、次回のリクエスト でブラウザは条件付きリクエストを送ることができない ● よって通常のリクエストが送られ、サーバは常にボディ付きのレスポンスを 返すことになる。 ・・・ 安全側に倒すなら no-store にしとけばいいところ、あえて no-cache にしてい るところにメッセージを感じる(と、私は勝手に思っている)
Slide 29
Slide 29 text
キャッシュのロジックを自分で実装してみよう 雑実装につき注意 middleware を使えば この辺のことは勝手に やってくれますが、わ かりやすいように自力 でやっています
Slide 30
Slide 30 text
キャッシュのロジックを自分で実装してみよう ● レスポンスボディのハッシュを比較するだけのシンプルな実装 ● これだとレスポンスボディの計算コストはそのまま。途中重たいクエリの実 行が含まれていたら負荷軽減にはならない。 ● じゃあ動的コンテンツのキャッシュは不可能?
Slide 31
Slide 31 text
キャッシュのロジックを自分で実装してみよう ● レスポンスボディのハッシュを比較するだけのシンプルな実装 ● これだとレスポンスボディの計算コストはそのまま。途中重たいクエリの実 行が含まれていたら負荷軽減にはならない。 ● じゃあ動的コンテンツのキャッシュは不可能? → 実際に計算しなくてもコンテンツが更新される根拠を知ることができるので は?
Slide 32
Slide 32 text
キャッシュのロジックを自分で実装してみよう ● 例えばECサイトの「商品一覧」ページ ● productsテーブルが更新されていないならコンテンツとして同一である、み たいなことが言えるのでは?
Slide 33
Slide 33 text
Laravel門外漢の雑実装その2 擬似コード的な内容ですがやり たいことは伝わるのではないで しょうか。 実際にはこの辺のETag絡みの処 理は middleware に切り分けた ほうがいいと思います。 ここでは省略していますがこの ままだとviewのコードをいじっ てもキャッシュが更新されない ので、アプリケーションのバー ジョン情報などを設けておいて ETagに含めたほうがいいか も。
Slide 34
Slide 34 text
さらなる負荷軽減のために ● これなら2回目以降のリクエストがproductsの更新前なら、products に対す る重たいクエリの代わりにレコード一件の取得で済む。 ● ただし、これだと条件付きリクエストのたびにDBアクセスが発生することに なる。 ● 新商品入荷日にF5連打するような人がたくさんいたらどうしよう。 ● もう少し改善できないものか。。。?
Slide 35
Slide 35 text
さらなる負荷軽減のために ● これなら2回目以降のリクエストがproductsの更新前なら、products に対す る重たいクエリの代わりにレコード一件の取得で済む。 ● ただし、これだと条件付きリクエストのたびにDBアクセスが発生することに なる。 ● 新商品入荷日にF5連打するような人がいたらどうしよう。 ● もう少し改善できないものか。。。? → 商品の更新を必ずしもリアルタイムで反映できなくてもいいのでは?
Slide 36
Slide 36 text
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を指定してやるとリアルタイムに更新が反映されなくなる が、条件付きリクエストの頻度を抑えることができる。
Slide 37
Slide 37 text
Proxy, CDN ● ここまででブラウザキャッシュの話をしてきましたが、これはいわゆる private キャッシュで、同じ人が2回目以降にアクセスしてきた時の話。 ● つまり、初見のユーザーが大量に押し寄せてきた、みたいなケースには対応 できない。 ● 「商品一覧」は見る人によって表示が変わるわけではないので一度キャッ シュされた内容が広く共有されてほしい → sharedキャッシュが必要。 ● そこでブラウザだけでなく Proxy や CDN にもキャッシュしてもらう。
Slide 38
Slide 38 text
Proxy, CDN Proxy: いわゆるリバースプロキシ。Varnish, Squid, Nginx等。Webサーバーの 前でリクエストを処理してくれる。 CDN: Contents Delivery Network。ウェブコンテンツをインターネット経由で 配信するために最適化されたネットワークのこと。様々な機能があるが、Proxy 同様Webサーバーの前でキャッシュによるレスポンスを行ってくれる。
Slide 39
Slide 39 text
Proxy/CDN Origin 1. ユーザーAが商品一覧ページ をリクエスト 2. Proxy/CDN はキャッシュがな いのでコンテンツをオリジンに リクエスト 3. オリジンは Proxy/CDN にコ ンテンツをレスポンス 4. Proxy/CDN はオリジンからの レスポンスをキャッシュしたう えでユーザーにレスポンスす る。 5. ユーザーBが商品一覧ページ をリクエスト 6. Proxy/CDNはユーザーAのリ クエスト時にキャッシュしたコ ンテンツをBにレスポンス
Slide 40
Slide 40 text
Cache-Control: private, public ● これでキャッシュを共有して初見のユーザーにも対応できるようになりまし た。 ● でも共有されてほしくない情報もある。例えば個人情報とか。その手のセン シティブなコンテンツが shared キャッシュに載らないかちょっと不 安。。。 ● そういえば Laravel の返してきた Cache-Control ヘッダに no-cache 以外 にも何かついてましたね。
Slide 41
Slide 41 text
Cache-Control: private, public Cache-Control: private レスポンスがプライベートキャッシュ(ブラウザーのローカルキャッシュなど) にのみ保存できることを示す。これが付いてると Proxy や CDN にはキャッシュ されなくなる。 Cache-Control: public これがないと Proxy や CDN がキャッシュしてくれない・・・わけではない。リ クエストに Authorization ヘッダが付いていると普通そのレスポンスは shared キャッシュとして保存してはいけないのだが、それでもキャッシュさせたい、と いう場合に付ける。
Slide 42
Slide 42 text
Cache-Control: private, public ● Cache-Control: private/public は単にキャッシュの保存先を指定するだけで はなく、「通常キャッシュされないレスポンスでもキャッシュする」という 意味を持っていることに注意。 ● 例えば 403 のようなステータスコードでもキャッシュされてしまう可能性が ある(ブラウザや Proxy/CDN の実装によります) ● CDN が public を付けないとキャッシュしない仕様だとか、リクエストに Authorization ヘッダが付いていてもキャッシュしたいとかでなければ public は付けないほうがいい
Slide 43
Slide 43 text
まとめ ● キャッシュは思ってるより身近なもの ● 動的コンテンツでも工夫次第ではキャッシュできるよ ● キャッシュと仲良くしてエコなアプリケーション開発をしよう 参考文献 田中 祥平 『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