1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Cloudflare の画像キャッシュで Vary ヘッダを活用

Posted at

はじめに

2021年にローンチされた Vary for images という機能を試しました。
当時試したメモがありましたが、雑すぎて、再度…

画像ファイルのオリジンが、リクエストされた画像フォーマットに応じてデータの出し分けをしているとき、CDN キャッシュでも同様に対応させる、というのが期待です。

下図は、一つの画像 URL に対し複数のフォーマットでファイルを用意したオリジンがいて、CDN を中間におきながら、リクエストに応じて最適なファイルを返している、というイメージになります。

Screenshot 2023-08-13 at 17.45.49.png

テスト

初期状態

オリジンの設定

  • 画像ファイル sample0.jpg を提供
  • リクエストの Accept ヘッダを参照し、 JPEG か WebP を選んで返す
  • Vary で Accept を返す

JPEG 欲しい Eyeball

JPEG をもらえます。(curl -v の出力から一部を抜粋します。以下同じ。)

> GET /sample0.jpg HTTP/1.1
> Accept: image/jpg

< Content-Type: image/jpeg
< Vary: Accept

WebP 欲しい Eyeball

WebP をもらえます。

> GET /sample0.jpg HTTP/1.1
> Accept: image/webp

< Content-Type: image/webp
< Vary: Accept

Cloudflare CDN のキャッシュを挟む

CDN デフォルト設定

  • Vary for images はデフォルト無効
  • 画像ファイルのキャッシュがない状態では、最初のリクエストでキャッシュが作られる
  • 後続のリクエストにはキャッシュされた画像が返される
  • リクエスト中の Accept: image/ は参照されない

JPEG 欲しい Eyeball

キャッシュのない状況で、Accept: image/jpg でリクエストします。
JPEG がもらえます。
cf-cache-status: MISSとあるので、このリクエストで CDN にキャッシュされた可能性があります。

> GET /sample0.jpg HTTP/2
> Accept: image/jpg

< content-type: image/jpeg
< vary: Accept
< cf-cache-status: MISS

WebP 欲しい Eyeball

次に、WebP でリクエストします。
残念ながら WebP はもらえず、キャッシュされた JPEG が届きます。
cf-cache-status: HIT となっていることから、それがわかります。

> GET /sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< vary: Accept
< cf-cache-status: HIT

CDN で画像フォーマットの出し分けを有効にする

JPEG と WebP の出し分けを期待し、Vary for images を有効化します。

Vary for images を有効にする設定

API: Cache Variants Setting で設定(PATCH)します。

~$ curl -s -X PATCH -H "Content-Type: application/json" -H "X-Auth-Email: $EMAIL" -H  "X-Auth-Key: $APIKEY" "https://api.cloudflare.com/client/v4/zones/$ZONEID/cache/variants" -d '{"value":{
      "jpeg": [
        "image/webp"
      ],
      "jpg": [
        "image/webp"
      ]
    }}'

再度リクエストを投げるまえに、キャッシュをパージします。

~$  curl -s -X POST -H "Content-Type: application/json" -H "X-Auth-Email: $EMAIL" -H  "X-Auth-Key: $APIKEY" "https://api.cloudflare.com/client/v4/zones/$ZONEID/purge_cache" --data '{ "files": [
 "https://$SOMEHOST/sample0.jpg"
 ]
 }'

JPEG 欲しい Eyeball

JPEG をもらえます。

> GET /sample0.jpg HTTP/2
> Accept: image/jpg

< content-type: image/jpeg
< content-length: 4149610
< vary: Accept
< cf-cache-status: MISS

WebP 欲しい Eyeball

無事に WebP をもらえます。

> GET /sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/webp
< content-length: 1297356
< vary: Accept
< cf-cache-status: MISS

キャッシュ状態

Vary for images 無効時と違い、画像フォーマットのリクエスト内容に応じたファイルをキャッシュから取得できています。

JPEG

> GET /sample0.jpg HTTP/2
> Accept: image/jpg

< content-type: image/jpeg
< content-length: 2480841
< vary: Accept
< cf-cache-status: HIT

WebP

> GET /sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/webp
< content-length: 1297356
< vary: Accept
< cf-cache-status: HIT

結果

ユーザのリクエストした画像フォーマットに合わせデータを返すという動作をキャッシュ経由で反映させることができました。

メモ

他の画像関連機能との共存

JPEG の例を見ると cf-cache-status: MISS と HIT で、content-length が違っています。
HIT のデータサイズが小さいです(4割減)。

< content-length: 4149610
< cf-cache-status: MISS

⬇⬇⬇

< content-length: 2480841
< cf-cache-status: HIT
< cf-polished: degrade=85, origSize=4149610, status=vary_header_present

これは当該 URL に対して、画像最適化の Polish という機能が有効になっていた影響です。
下記の処理が並行して行われていました。

  • 一旦キャッシュされたオリジナル画像(JPEG)から、最適化したファイル(JPEG)が作成され、キャッシュ
  • 後者を以降の応答に利用
  • レスポンスに cf-polished ヘッダなどが追加され、変更内容を記録
> GET /sample0.jpg HTTP/2
> Accept: image/jpg

< content-type: image/jpeg
< content-length: 4149610
< cache-control: public, max-age=691200
< last-modified: Mon, 16 Aug 2021 11:03:18 GMT
< etag: "3f516a-5c9ab2610c598"
< vary: Accept
< cf-cache-status: MISS

⬇⬇⬇

> GET /sample0.jpg HTTP/2
> Accept: image/jpg

< content-type: image/jpeg
< content-length: 2480841
< cache-control: public, max-age=691200
< cf-bgj: imgq:85,h2pri
< cf-polished: degrade=85, origSize=4149610, status=vary_header_present
< etag: "3f516a-5c9ab2610c598"
< last-modified: Mon, 16 Aug 2021 11:03:18 GMT
< vary: Accept
< cf-cache-status: HIT

問題発生 - Polish の Webp 変換

Vary for images と Polish の併用が確認できましたが、Vary for images を有効化したことで、既存サービスの一つに問題が発生しました。

Polish には JPEG(GIF, PNG)を状況に応じて WebP に変換するというオプション機能がありますが、それを活用していた既存の URL で WebP 変換およびキャッシュがされなくなりました。

  • 対象のサービスのオリジンは JPEG 画像のみ配布(なので Vary も付けていない)
  • Eyeball から Accept: image/webp を要求されたら、Polish で WebP に変換
  • うまく動いていいたが、Vary for images を有効にしたことで、常時 Cache MISS
  • .jpg に対する Accept: image/webp を Polish で扱ってたところに、同じ条件を扱う Vary for images が割り込んできた…
Polish + WebP 変換: Vary for images 無効時の挙動

Vary for images 投入前、うまく動いていたときの状況です。

一発目(JPEG 画像が得られます)

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< content-length: 80629
< cache-control: public, max-age=691200
< last-modified: Mon, 14 Aug 2023 03:27:34 GMT
< cf-cache-status: MISS

二発目(Polish で作成された WebP 画像が得られます)

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/webp
< content-length: 28524
< cache-control: public, max-age=691200
< cf-bgj: imgq:85,h2pri
< cf-polished: qual=85, origFmt=jpeg, origSize=80629
< last-modified: Mon, 14 Aug 2023 03:27:34 GMT
< vary: Accept
< cf-cache-status: HIT
< age: 2
Polish + WebP 変換: Vary for images 有効時の挙動

Vary for images(拡張子 jpg, jpeg に対して image/webp を定義)を有効化すると、常に Cache Miss となりました。

  • CDN は sample0.jpg に対して Accept: image/webp でリクエスト
  • Vary for images は content-type: image/webp と vary: accept の応答を期待
  • オリジンからは content-type: image/jpeg かつ vary: accept ナシの状態で、期待外れ
  • 一方、同リクエストに対しては Polish の WebP オプションも定義されている

一発目

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< content-length: 80629
< cache-control: public, max-age=691200
< last-modified: Mon, 14 Aug 2023 03:31:30 GMT
< cf-cache-status: MISS

二発目

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< content-length: 80629
< cache-control: public, max-age=691200
< last-modified: Mon, 14 Aug 2023 03:31:30 GMT
< cf-cache-status: MISS
Polish(WebP 変換を無効化): Vary for images 有効時の挙動

Polish の Webp オプションを無効にすると、キャッシュされるようになり、JPEG → JPEG での最適化は実施されます。

一発目

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< content-length: 80629
< cache-control: public, max-age=691200
< last-modified: Mon, 14 Aug 2023 03:33:07 GMT
< cf-cache-status: MISS

二発目

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< content-length: 33470
< cache-control: public, max-age=691200
< cf-bgj: imgq:85,h2pri
< cf-polished: degrade=85, origSize=80629
< last-modified: Mon, 14 Aug 2023 03:33:07 GMT
< cf-cache-status: HIT
< age: 6
解決策

今のところ Vary for images はゾーン全体での設定になります。

同じゾーンの配下で、Polish の WebP 変換オプションを活用しているサービスがある場合は、上記のように WebP 変換を外すか、オリジンでの WebP 対応を検討するなど、そこに頼らない設計としたほうが良さそうです。

あるいはオリジンはシンプルにして、CDN 側での変換に寄せていく方針もあるでしょう。Image ResizingImages が用意されています。

Screenshot 2023-08-14 at 11.31.49.png

補足:オリジンが Vary をつけたとき

上記は Vary for images に指定したバリアント(accept: image/webp)でオリジンにリクエストしたにも関わらず、オリジンは期待に反して content-type: image/jpeg を返し、かつ、Vary: Accept も付けない、というケースでした。

そこで、Vary for images の要件に近づけるため、オリジンで Vary: Accept を付けてみると cf-cache-status は BYPASS になりました。
これは "If the origin server sends Vary: Accept but does not serve the set variant, the response is not cached and displays BYPASS in the cache status in the response header. " の動きです。オリジンが WebP サポートしてる想定なのに、違ったからバイパスしています。

> GET /images/polish/sample0.jpg HTTP/2
> Accept: image/webp

< content-type: image/jpeg
< content-length: 80629
< vary: Accept
< cache-control: public, max-age=691200
< cf-cache-status: BYPASS
Polish と Vary

一方、Polish と Vary ヘッダーについては下記の注意書きがあります。
"Polish may not be applied to origin responses that contain a Vary header. The only accepted Vary header is Vary: Accept-Encoding."
Vary for images との併用がどこまで想定されているかは未確認です。

パージ

キャッシュされたファイルがパージされると、バリアントも同様にパージされます。

ブラウザからのアクセス

ブラウザからもアクセスしてログを確認します。ダッシュボードの Instant Logs から抜粋します。

iOS Safari(Accept: */*;q=0.8)
--> JPEG でキャッシュされています

    "ClientDeviceType": "mobile",
    "ClientRequestUserAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
    "RequestHeaders": {
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
    },
    "ResponseHeaders": {
        "content-type": "image/jpeg",
        "vary": "Accept, Accept-Encoding"
    },

macOS Chrome(Accept: image/avif,image/webp,image/apng,*/*;q=0.8)
--> WebP でキャッシュされています

    "ClientDeviceType": "desktop",
    "ClientRequestUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
    "RequestHeaders": {
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
    },
    "ResponseHeaders": {
        "content-type": "image/webp",
        "vary": "Accept, Accept-Encoding"
    },
【便利】 ログのカスタムフィールド

見たいヘッダを追加しておけば Instant Logs にもサクッと反映されます。
Screenshot 2023-08-14 at 18.40.21.png

その他、参考情報

API

  • Vary for images を無効にする(DELETE)
~ $ curl -s -X DELETE -H "Content-Type: application/json" -H "X-Auth-Email: $EMAIL" -H  "X-Auth-Key: $APIKEY" "https://api.cloudflare.com/client/v4/zones/$ZONEID/cache/variants"
{
  "result": {
    "editable": true,
    "id": "variants",
    "modified_on": "2023-08-03T11:28:31.818839Z"
  },
  "success": true,
  "errors": [],
  "messages": []
}
  • Vary for images の現状を得る(GET)
    無効時の例
~ $ curl -s -X GET -H "Content-Type: application/json" -H "X-Auth-Email: $EMAIL" -H  "X-Auth-Key: $APIKEY" "https://api.cloudflare.com/client/v4/zones/$ZONEID/cache/variants"
{
  "result": null,
  "success": false,
  "errors": [
    {
      "code": 1142,
      "message": "Unable to retrieve variants setting value. The zone setting does not exist."
    }
  ],
  "messages": []
}

リンク

https://blog.cloudflare.com/vary-for-images-serve-the-correct-images-to-the-correct-browsers/
https://developers.cloudflare.com/api/operations/zone-cache-settings-change-variants-setting
https://developers.cloudflare.com/cache/advanced-configuration/vary-for-images/
https://developers.cloudflare.com/images/polish/compression/
https://developers.cloudflare.com/images/polish/cf-polished-statuses/
https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values#values_for_an_image

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?