静的ファイルのキャッシュコントロールについて ISUCON7
※以前掲載していたブログが消えたので再掲です。
今回初めてISUCON7に参加させていただきました。(チーム名:元pyns)
今回のお題の一つ目の壁は、いかに画像ファイル(アバターアイコン)をキャッシュさせてサーバーからデータを返さないようにするかでした。
8時間の大部分をこの対応に費やしましたが解決は出来ませんでした。
原因はきっちり304を返すための基礎知識が足りていなかったことです。
ですのでこれを機に勉強しなおしてみました。
304 (Not Modified)
大前提ですが、304ステータスコードは
キャッシュの有効無効の確認付きリクエストに対して、有効である場合に返すステータスコード
です。
この場合サーバーはリソースデータ(ペイロード)を送信しません。
すなわち,サーバは、[ クライアントに格納済みの その表現を 200 (OK) 応答のペイロードであったかのように,用立ててもらう ]べく,クライアントをリダイレクトしている。
訳が険しいですが意味は十分わかると思います。
静的ファイルをキャッシュさせようとする場合、適切に304を返すことが重要になります。(isucon開始前の自分に言ってます)
HTTPのキャッシュに関係するヘッダ
キャッシュの振る舞いを制御するためのレスポンスヘッダにはこれらがあります。
- Cache-Control
- Expires
- ETag
- Last-Modified
Cache-Control
Cache-Controlはレスポンス/リクエストに対してキャッシュのふるまいを制御するための指示を指定するために利用されます。
レスポンスのCache-Controlに指定できるディレクティブはこれらです。
ディレクティブ | 意味 |
---|---|
must-revalidate | 期限切れのキャッシュに対してオリジンサーバーで現在でも有効かどうか確認しなければキャッシュとして使用できないことを指示する |
no-cache | キャッシュはさせるが、オリジンサーバーに現在でも有効かどうか確認しなければキャッシュとして使用できないことを指示する。 確認方法にはIf-None-Match またはIf-Modified-Since を用います。いかにも「キャッシュするな」ぽいのがややこしい |
no-store | リクエストもレスポンスも一切キャッシュしてはいけないことを指示する |
no-transform | 中継者(CDN,プロキシなど)に対してメディアの形式変換を禁止する |
public | 共有キャッシュに保持して良いことを指示する |
private | 単独の利用者の使用が意図されているものであり、共有キャッシュに格納されてはならいことを指示する |
proxy-revalidate | must-revalidateの中間キャッシュに対するもの |
max-age | 指定した時間を過ぎるとfreshではないとみなすことを指示する1分キャッシュの例:Cache-Control: max-age=60 |
s-maxage | max-ageがクライアントに対しての有効期限であるのに対して、こちらは共有キャッシュに対しての有効期限。max-age,s-maxage両方セットされている場合は、共有キャッシュに関してはs-maxageを上書きする |
静的コンテンツをキャッシュさせる場合はmax-ageとpublicに着目
max-age
max-ageが1以上の場合、その秒数クライアントはリクエストを行いません。
public
共有キャッシュに保持しても良いということですが、共有キャッシュとは複数のユーザーが通る中間点のことで、例えばCDNやプロキシなどです。
publicを指定するとこの図の左下の状態になります。
今回のisucon7では、publicを指定すると点数が跳ね上がったみたいですのでチェッカークライアントとサーバーの間に何かしらキャッシュ機構があったということでしょうか。
でもこれはpublicにして点数が上がること以外にどうやって察知したら良かったのでしょうか・・💧
チェッカーから直接飛んで来ていない可能性については微塵も思いつきませんでした・・w
Expires
意味的にはCache-Control max-age
と同じです。
ヘッダの記載の例はこうなります。
Expires: Thu, 01 Dec 1994 16:00:00 GMT
時刻が無効なフォーマットの場合はゼロ、つまりキャッシュの有効期間切れと判断します。
Cache-Control max-age
と同時に指定されている場合、クライアントはExpiresヘッダを無視します。
HTTP1.0時代の名残のようです。
Etag
めずらしくwikiの説明がわかりやすいのでwikiを見ていただければ良いのですが
HTTP ETag – Wikipedia
かいつまむと、リソースに対してフィンガープリントのような識別子を生成し、それをサーバー・クライアント間で連絡し合うことでリソースが変更されたかどうか検知させます。
レスポンスヘッダにEtagが含まれているとクライアントは次回のリクエストからヘッダにIf-None-Match
をつけます。
If-None-Match
を受けたサーバーは識別子が同一であれば304を返します。
異なれば新しいリソースとともに新しいEtag
ヘッダを返します。
レスポンスヘッダの例
ETag: "686897696a7c876b7e"←識別子
リクエストヘッダの例
If-None-Match: "686897696a7c876b7e"←識別子
識別子の生成ルール
またHTTPにおいて、Etagの識別子の生成に決まりはありません。
nginxでは[mtime size]を使って生成します。
etag生成箇所のソースを貼っつけてくれている方がいました。
Algorithm behind nginx etag generation - Server Fault
apacheではデフォルトで[inode mtime size]を使って生成します。
#FileETag Apache HTTP サーバ バージョン 2.4
httpd.confで以下のように指定することで変更できます。
//最終更新時刻とファイルサイズでEtagを生成 FileETag MTime Size // 生成しない FileEtag None
配信サーバーが2台以上ある場合
同一のコンテンツを配信するサーバーが2台以上ある場合は、気をつけないとEtagによるキャッシュ制御が意図したものになりません。 apacheのデフォルトではinodeを用いているので仮にファイルのmtimeとsizeが同じでもサーバーが違えばinodeが異なります。 またinodeを無視しても、mtimeがずれていると異なるEtagになるのでそれぞれ別でキャッシュしてしまいます。
Last-Modified
このヘッダはリソースの更新時刻をクライアントに返し、次回アクセス時にクライアントからその更新時刻を送信させます。 一致していれば304を返し、一致していなければ新しい(バージョンの)リソースと更新時刻を返します。
レスポンスヘッダの例
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
リクエストヘッダの例
If-Modified-Since: Tue, 15 Nov 1994 12:45:26 GMT
配信サーバーが2台以上ある場合 こちらもEtag同様で同じファイルでもサーバー間で更新時刻がずれていると個別でキャッシュされて非効率になります。
まとめ
ISUCON最高でした! 運営の皆さんにはこんな貴重な機会を提供していただき感謝しております。
参考
- RFC 7234 — HTTP/1.1: Caching (日本語訳)
- HTTP キャッシュ - HTTP | MDN
- HTTPヘッダーフィールド2 – s-kitaの日記
- CDN切り替え作業における、Web版メルカリの個人情報流出の原因につきまして - Mercari Engineering Blog
- Cache-Control: no-transform で各種メディアの変換(再圧縮等)を防ぐ
- クラウド環境でのApacheの設定 | cloudrop