Fastly の Surrogate-Key を使用してキャッシュをまとめて削除


# 最初に

Fastly CDN の特徴のひとつにネットワーク上にキャッシュされたオブジェクトを削除(Purge)する速度が非常に高速(約150ms)というものがありますが、Purge する単位として以下の３種類があります。

- **Purge by URL**
指定した URL(クエリストリングを含む）でキャッシュされたオブジェクトを削除

- **Purge All**
指定したサービス（配信設定）でキャッシュされたすべてのオブジェクトを削除

- **Purge by Surrogate-key**
指定した Surrogate-Key を持つすべてのオブジェクトを削除

この記事ではこの中でも Fastly 独自の機能である Surrogate-key 機能を利用した効率的なキャッシュの削除方法と、Surrogate-Key を Fastly の VCL で追加するサンプルコードをいくつかご紹介します。




# Surrogate-key とは

Fastly ではオリジンサーバーから取得したオブジェクトをキャッシュする際に Surrogate-key を付与することができます。キャッシュオブジェクトに付与するタグのようなものと考えると分かりやすいです。

Fastly CDN 上にキャッシュされたオブジェクトを削除する際に、この Surrogate-Key の Key を指定することで、指定された Key をもつオブジェクトをまとめて削除することが出来ます。

例えば以下のようなコンテンツに Surrogate-Key を設定しておくことで、Purge が必要になった場合、関連する複数のキャッシュされたオブジェクトを一回の Purge リクエストでまとめて削除することが出来ます。

- 特定の商品に関連する画像やページのファイル
- 特定の動画に関連するマニフェストファイルやチャンクファイル、サムネイルファイルなど
- 特定のユーザーがアップロードした画像や情報のコンテンツ
- 特定の日時に生成されたコンテンツ


この他にもサービスの特性に合わせて運用上まとめて削除する可能性があるグループに Surrogate-Key を設定することで、キャッシュ削除作業を効率化することが出来ます。

Surrogate-Key はオリジンサーバーからのレスポンスヘッダーに含めることで設定しますが、Fastly の VCL を利用して Fastly サーバー側で設定することも可能です。

Surrogate-Key の詳細については[こちら](https://docs.fastly.com/ja/guides/working-with-surrogate-keys)のページをご参照下さい。



# Surrogate-Key の設定方法

前述の通り Surrogate-Key はオリジンサーバーからのレスポンスヘッダー、Fastly の VCL のいずれかで設定します。

### オリジンサーバーからのレスポンスヘッダー

オリジンサーバーからのオブジェクトを取得した際のレスポンスに Surrogate-Key ヘッダーが含まれていると、含まれていた値を Surrogate-Key として設定します。スペース区切りで複数の Key を設定することが可能です。

`Surrogate-Key: veggie seasonal central-mexico`

上記のヘッダーをオリジンサーバー側でレスポンスヘッダーとして設定した場合、`veggie` `seasonal` `central-mexico` という3つの値が Surrogate-Key として設定されます。

### VCL を利用して設定

VCL を利用して、リクエストやレスポンスに含まれる情報をもとに Fastly サーバー側で Surrogate-Key を追加することも可能です。オリジンサーバーの設定を変更する必要はありませんし、必要な情報を自由に追加することが可能です。

# 設定された Surrogate-Key の確認方法

Surrogate-Key はレスポンスヘッダーとして設定されますが、デフォルトの挙動ではエンドユーザーにレスポンスを送信する際にヘッダーは削除されています。

設定された Surrogate-Key ヘッダーの内容を確認したい場合、リクエストを送る際に `fastly-debug` ヘッダーを付与すると、レスポンスに Surrogate-Key ヘッダーの値が含まれるようになります。fastly-debug ヘッダーの値はなんでも大丈夫です。
curl コマンドを利用して実行する場合は以下のようになります。


`$ curl -svo /dev/null -H 'fastly-debug:1' 'https://<対象のURL>'`

こうすることで Fastly サーバーからのレスポンスヘッダーに Surrogate-Key ヘッダーが含まれ、設定されている内容を確認することが出来ます。

Fastly-Debug ヘッダーについては[こちらのページ](https://qiita.com/AtsushiFukuda/items/fb20f8a410b47396d83a#fastly-debug-%E3%83%98%E3%83%83%E3%83%80%E3%83%BC%E3%81%AE%E6%A6%82%E8%A6%81)で説明しています。

# Surrogate-Key の制約
Surrogate-Key は複数設定可能ですが、以下のような制約があります。

Key のサイズ: 1KB
Surrogate Key の値は 1024 bytes 以下である必要があります。このサイズを超える Key を設定しようとすると、Key は設定されずに無視されます。

Surrogate-Key ヘッダーサイズ: 16KB
ヘッダーサイズの合計は16KBまでです。これを超えて key を設定した場合、設定しようとした key は無視されます。

制約の詳細は [Network Service Resource Limits](https://docs.fastly.com/products/network-services-resource-limits) で確認可能です。

また、**Surrogate-Key はキャッシュを行うタイミングで設定されるため、Key をキャッシュされているオブジェクトに後から追加することは出来ません。** 後から追加するように設定した Key を指定して Purge を行っても Key が設定されていないオブジェクトは削除されませんのでご注意ください。


# Surrogate-Key を設定するコードサンプル

Surrogate-Key の追加は VCL の vcl_fetch サブルーチンで行います。以下は一般的に Surrogate-Key として使用されることが多い情報を VCL を使って追加するサンプルコードです。

VCL Snippet、または Custom VCL にコードを追加することで利用することが出来ます。

### 拡張子を設定
拡張子を Surrogate-Key として設定します。

```:vcl_fetch
set beresp.http.Surrogate-Key = req.url.ext;
```
なお、オリジンからのレスポンスや他のコードで既に Surrogate-Key ヘッダーが存在する場合に `set` すると上書きとなってしまい、先に設定されていた値がなくなってしまいます。

すでにヘッダーが存在する可能性がある場合は以下のようなコードで Surrogate-Key を追加することが出来ます。

```:vcl_fetch
set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " req.url.ext, req.url.ext);
```


### 日付や日時を設定
日付や、日付と時間(JST)を Surrogate-Key として設定します。
何らかの問題で一時的に不正なコンテンツが返却されキャッシュされてしまった場合などに、特定日時にキャッシュされたオブジェクトだけをまとめて削除することが可能になります。

```:vcl_fetch
set beresp.http.Surrogate-Key = strftime({"%Y%m%d"},time.add(now,9h)) " " strftime({"%Y%m%d-%H"},time.add(now,9h));

# 追記の場合
set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " strftime({"%Y%m%d"},time.add(now,9h)) " " strftime({"%Y%m%d-%H"},time.add(now,9h)), strftime({"%Y%m%d"},time.add(now,9h)) " " strftime({"%Y%m%d-%H"},time.add(now,9h)));
```


## ランダムな数字を設定
1から10のランダムな数字を設定します。すべてのキャッシュ削除が必要になったような場合、Purge All を行うとオリジン側の負荷が急増してしまいますが、ランダムな数字を Surrogate-Key として設定しておき、順番に Surrogate-Key Purge を行うことでオリジンにかかる負荷をコントロールしながら Purge All と同じような結果を実現することが出来ます。
ただし、Purge 後にオリジンから取得したオブジェクトに対してもランダムに数字を割り振るので、割り振られた番号次第では新たにキャッシュしたオブジェクトに対して再度 Purge が行われる可能性はあります。


```:vcl_fetch
set beresp.http.Surrogate-Key = randomint(1, 10);

# 追記の場合
set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " randomint(1, 10), randomint(1, 10));
```


## レスポンスヘッダーに含まれる値を設定
指定したレスポンスヘッダーの値をSurrogate-Key として設定します。コンテンツをアップロードしたユーザーや商品の ID などがレスポンスヘッダーに含まれる場合、その値を Surrogate-Key として利用することで特定のユーザーがアップロードしたコンテンツや商品に関するオブジェクトをまとめて削除することが出来ます。

```:vcl_fetch
set beresp.http.Surrogate-Key = beresp.http.content-type;

# 追記の場合
set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " beresp.http.content-type, beresp.http.content-type);
```

## 特定のクエリパラメタの値を設定
クエリパラメータから特定の値を取得して Surrogate-Key として設定します。
```:vcl_fetch
set beresp.http.Surrogate-Key = querystring.get(req.url, "foo");

# 追記の場合
set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " querystring.get(req.url, "foo"), querystring.get(req.url, "foo"));

```

## パスの値を設定
パスから情報を取得して Surrogate-Key に設定することも可能です。
例えばパスの二つ目の階層を Surrogate-Key として設定したい場合は以下のようなコードで実現できます。

正規表現を変更することで第一階層でも第三階層でも必要な場所から Key を設定することが可能です。

```:vcl_fetch
if (req.url.path ~ "^/[^/]*/([^/]*).*"){
  set beresp.http.surrogate-key = re.group.1;
}

# 追記の場合
if (req.url.path ~ "^/[^/]*/([^/]*).*"){
  set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " re.group.1, re.group.1);
}
```

## すべてのパス情報を設定 - ワイルドカードパージ
上の例では特定の階層のパス情報を Surrogate-Key として設定しましたが、すべてのパスの情報を設定することでいわゆるワイルドカードパージ（指定したパス配下のすべてのコンテンツを削除）を実現することが出来ます。

すべてのパス情報を Surrogate-Key として設定する方法はいくつかありますが、Fastly の Code Example でも紹介されている [vcl_init に作成した function を vcl_fetch から呼び出す方法](https://www.fastly.com/documentation/solutions/examples/purge-everything-under-a-url-path-prefix/)をご紹介します。

VCL Snippet で実装する場合は vcl_init と vcl_fetch 二つの Snippet を設定します。

```:vcl_init
sub extract_path_segment {
  if (std.strlen(beresp.http.tmp-path) > 0) {
    set beresp.http.surrogate-key = if (beresp.http.surrogate-key, beresp.http.surrogate-key " " beresp.http.tmp-path, beresp.http.tmp-path);
    set beresp.http.tmp-path = regsub(beresp.http.tmp-path, "^(.*?)/[^/]*$", "\1");
  }
}
```

```:vcl_fetch
set beresp.http.tmp-path = req.url.path;

call extract_path_segment;
call extract_path_segment;
call extract_path_segment;
call extract_path_segment;
call extract_path_segment;
call extract_path_segment;
```

動作としては vcl_fetch で `beresp.http.tmp-path` にパス情報を設定した後に、`extract_path_segment` を呼び出しています。

`sub extract_path_segment` function では `beresp.http.tmp-path` を Surrogate-Key に設定し、最後の階層を削除して vcl_fetch に戻り、再度 `extract_path_segment` が呼び出される形になります。

VCL は for ロジックをサポートしていないため、想定されるパスの階層の最大の数だけ `call extract_path_segment;` する必要があります。 call する回数が足りない場合、上層のパス情報が Surrogate-Key に設定されないので注意して下さい。


# Surrogate-Key を指定した Purge 実行方法

Surrogate-Key を指定した Purge のリクエストは Fastly のコントロールパネルか、API 経由で実行することが出来ます。手順の詳細はそれぞれ以下のドキュメントをご参照下さい。

コントロールパネルからの Surrogate-Key Purge
https://docs.fastly.com/ja/guides/purging-with-surrogate-keys

API を利用した Surrogate-Key Purge
https://www.fastly.com/documentation/reference/api/purging/


# まとめ

この記事では Surrogate-Key の代表的な利用方法について紹介しました。機能そのものの詳細については [サロゲートキーの操作](https://docs.fastly.com/ja/guides/working-with-surrogate-keys) もご参照下さい。