CDN 導入時のキャッシュ設定の考え方

# この記事の内容について

サイトにCDNを導入する際のキャッシュ設定の考え方についてまとめてみたいと思います。


なお、このページに記載されている内容は標準的な設定の場合は問題なく動作しますが、複雑な挙動を設定されている場合は処理の実行順序などにより意図したとおりに動作しない可能性があります。
実際にブラウザなどで動作確認を行うとともに、Generated VCL から生成された VCL を確認して、意図した動作が正しく行われることを確認して下さい。




# PASS 設定と TTL の指定

最初に Fastly がどのようにキャッシュの可否や、キャッシュ対象の TTL を決定するかを改めて考えてみます。

Fastly（というかVarnish）ではキャッシュ対象外とすることを PASS といいます。基本的には "PASS" = "キャッシュしない" と考えて差し支えないです。
また、Fastly のデフォルトの挙動ではオリジンサーバーからのレスポンスヘッダに含まれる Cache-Control ヘッダの値などを元にキャッシュの可否や TTL を判断します。

特定のリクエストに対して明示的に PASS を指定したい場合は以下のふたつのサブルーチンの中で指定することが出来ます。

**vcl_recv: リクエストを受け付けたタイミング**
パスや拡張子など、リクエストから判断出来る内容で PASS する場合はここで設定します。

**vcl_fetch: オリジンからコンテンツを取得したタイミング**
ステータス、コンテンツタイプなど、レスポンスの内容に応じて PASS する場合はここで設定します。

PASS は基本的には可能な限り vcl_recv で指定するほうがよいです。処理の順番的にも vcl_recv が先に実行されますし、同時に発生したリクエストなどに無駄な待ち時間が発生することを防ぐことが出来ます。

なお、リクエストを vcl_fetch で PASS した場合「リクエストを PASS する」という情報がキャッシュされます。これにより続いて発生した同じオブジェクトに対するリクエストはサーバー上ですぐに PASS 対象として処理が行われます。
これを `Hit for Pass オブジェクト` と呼びます。 このオブジェクトにも TTL が設定されるので、vcl_fetch で PASS する場合は `Hit for Pass オブジェクト` への TTL も意識する必要があります。
このあたりの動作の詳細については [PASS アクションの挙動について](https://docs.fastly.com/ja/guides/understanding-the-different-pass-action-behaviors#%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E8%A8%AD%E5%AE%9A%E3%81%A7%E3%81%AE-pass) をご確認下さい。

その他、vcl_fetch は TTL を指定するのにも使われます。Fastly の TTL 選択ロジックについては[Fastlyサーバーの TTL の選択ロジック](https://qiita.com/AtsushiFukuda/items/b7b5ec31abd387814640)で説明していますが、デフォルトの挙動で選択された TTL を上書きする場合も vcl_fetch で設定します。

また、Generated VCL の vcl_fetch の箇所のコードをみると分かるのですが、レスポンスに `Set-Cookie` や `Cache-Control:private` が含まれる場合は PASS が設定されます。
つまり、vcl_recv で PASS を設定しなくてもオリジンからのレスポンスの内容によっては vcl_fetch で PASS されるケースもあります。


まとめると、vcl_recv で PASS するかどうかを制御、デフォルト以外の TTL を指定したい場合は vcl_fetch で明示的に指定、というのが基本的な設定の構成になります。


# キャッシュポリシー
続いてキャッシュポリシーについて考えてみます。CDN を導入する際のキャッシュポリシーとしては大きく分けると以下の4種類になるかと思います。

+ キャッシュ対象のオブジェクトを指定、それ以外はキャッシュしない
+ キャッシュ対象外のオブジェクトを指定、それ以外はキャッシュ
+ すべてのリクエストをキャッシュ
+ すべてのリクエストをキャッシュしない

それでは上記を踏まえた上で各キャッシュポリシーについてと設定方法をご紹介します。

# キャッシュ対象のオブジェクトを指定、それ以外はキャッシュしない
キャッシュしてもよいオブジェクト以外に PASS を設定します。指定したオブジェクト以外には PASS が適用されるので、意図していないコンテンツがキャッシュされてしまうといった問題を防ぐことが出来ます。

CDN を導入するサイトから様々な種類のコンテンツを配信していてサイトの全体構成が把握しきれていない場合や、出来るだけ安全に CDN を導入したい場合はこのアプローチがおすすめです。

この設定を行うには、リクエストを受けたタイミング(vcl_recv)でキャッシュ対象にしたいもの以外に PASS を設定します。設定は UI でも可能ですが Snippet や Custom VCL で設定するほうが簡単です。

例えば以下のようなコードで設定可能です。if で指定した条件に一致しないものを PASS (キャッシュしない) ようにしています。

```:vcl_recv
# 指定した拡張子のみをキャッシュ対象(passしない)に指定
if (req.url.ext ~ "(?i)^(gif|jpg|jepg|png|pdf|css|js)$") {
  # Do nothing
} else {
  return(pass);
}
```

```:vcl_recv
# 特定のパスのみをキャッシュ対象(passしない)に指定
if (req.url ~ "^/image|^/css") {
  # Do nothing
} else {
  return(pass);
}
```

if の条件を `!~` として else ではなく直接 retrun(pass) してもよいのですが、上記のコードのほうが直感的に分かりやすいかなと思います。


### vcl_fetch での TTL の明示的な指定
PASS しなかったリクエストに適用する TTL は vcl_fetch で制御されます。
デフォルトではオリジンからのレスポンスに含まれる Cache-contrtol ヘッダなどがの内容に基づいて TTL が設定されます。
デフォルトの挙動ではなく、TTL を明示的に指定したい場合は以下のようなコードで設定することができます。

```:vcl_fetch
if (req.url.ext ~ "(?i)^(gif|jpg|jepg|png)$") {
  set beresp.ttl = 1d;
  return(deliver);
} elseif(req.url.ext ~ "(?i)^(pdf|css|js)$") {
  set beresp.ttl = 1h;
  return(deliver);
}
```
上のコードでは条件に一致する（指定した拡張子）に対して `beresp.ttl(キャッシュのTTL）` を設定しています。

その後 return(deliver) で処理を vcl_deliver に移動しています。ここで条件にマッチしなかったリクエストについては、Fastly のデフォルトの挙動で TTL が設定(または条件によっては PASS）されます。

### return(deliver)する理由
最終的に生成される Generated VCL をみると分かるのですが、Fastly のデフォルト VCL では vcl_fetch 内で `Set-Cookie` や `Cache-Control:private` がある場合に PASS を設定したり、TTL を指定するヘッダーがない場合に デフォルト TTL を適用したりしています。
そのため、ここに return(deliver) が無いと、TTL を指定した後に vcl_fetch 内で処理が継続され、結果として PASS されるなど意図しない設定が適用される可能性があります。


# キャッシュ対象外のオブジェクトを指定、それ以外はキャッシュ

先程の設定と似ていますが、ここでは PASS 対象を vcl_recv で指定します。条件に `!` が無いので、指定した条件に一致する場合 PASS となります。

```:vcl_recv
# 特定の拡張子の場合のみを PASS に指定する場合
if (req.url.ext ~ "(?i)^(cgi|php)$") {
  return(pass);
}
```

```:vcl_recv
# 特定のパスのみ PASS とする場合
if (req.url ~ "^/dynamic|^/admin") {
  return(pass);
}
```

上記の条件に一致するリクエスト**以外**は vcl_fetch でオリジンからのレスポンスに応じてキャッシュの可否や TTL が判断されます。

TTL を Fastly 側で設定したい場合は以下のように指定が可能です。

```:vcl_fetch
# 全体に同じ TTL を適用する場合
  set beresp.ttl = 1h;
  return(deliver);
```
上のコードでは TTL を適用する条件をしていない(if で囲んでない)ので、vcl_recv で PASS しなかったすべてのリクエストにここで指定した TTL が適用されます。


```:vcl_fetch
# 適用する TTL を条件によって変えたい場合
if (req.url.ext ~ "(?i)^(gif|jpg|jepg|png)$") {
  set beresp.ttl = 30d;
  return(deliver);
} elseif(req.url.ext ~ "(?i)^(pdf|css|js)$") {
  set beresp.ttl = 12h;
  return(deliver);
} else {
  set beresp.ttl = 1h;
  return(deliver);
}
```

適用する TTL を条件によって変えたい場合のコードでは、指定した条件に一致しない場合は最後の else で指定した TTL が適用されます。

# すべてのリクエストをキャッシュ
とにかくすべてのオブジェクトをキャッシュします。画像サーバーや静的オブジェクト専用のサブドメインからオブジェクトを配信しているケースではこの設定がわかりやすいです。

vcl_recv には特にコードを追加する必要はありません。オリジンからのレスポンスに `Set-Cookie` や `Cache-Control:private` などのヘッダが含まれている場合もキャッシュをしたい場合は、Snippet を利用して以下のコードを vcl_fetch に追加して下さい。 

```:vcl_fetch
set beresp.ttl = 30d;
return(deliver);
```
デフォルトではキャッシュしないレスポンスコードのレスポンスなどもキャッシュさせたい場合は、上記のコードに加えて `set beresp.cacheable = true;` を設定する必要があります。



# すべてのリクエストをキャッシュしない

ロードバランサー、TLS ターミネーションなどの目的でキャッシュせずに Fastly を経由させたい場合に利用できる設定です。

```:vcl_recv
  return(pass);
```

デフォルトを PASS とする設定と似ていますが、無条件で PASS するため if による条件が指定されていません。この場合はすべてのリクエストの vcl_recv で PASS が適用され、キャッシュは行われません。


# まとめ
CDN を利用するにあたって基本となる4種類の設定について紹介させて頂きました。同じドメイン内で異なる挙動が必要なパスが存在するなどより複雑な設定が必要なケースもあるかとは思いますが、基本的にはここで紹介したような設定の組み合わせになります。
また、プロダクション環境への公開前には、生成された VCL のコードを確認し、実際にブラウザなどからアクセスして意図した通りの設定が行われていることや、サイトが正しく動作することを十分に検証しましょう。