こんにちは。takutaka と申します。最近良かったことはMOTHER3をクリアしたことです。
最近、画像配信を WebP に動的変換するという施策を担当したので、その話をします。
ペパボにおける画像配信
GMO ペパボでは、クリエイターさんがアップロードした画像でTシャツなどのステキなアイテムが販売できる SUZURI というサービスや、EC支援サービスであるカラーミーショップやハンドメイドマーケット minne など、様々なサービスを運営しており、それぞれに画像の扱いは特徴があります。
SUZURI では、クリエイターさんに提供いただいた画像をペパボで独自で合成し画像を作成していますが、対してカラーミーショップや minne では、ショップオーナーさんや作家さんがペパボのシステムアップロードした画像をエンドユーザーさんに配信しています。
施策を実施したサービス
今回はEC支援サービスであるカラーミーショップの配信する画像を対象として施策を実施しました。
カラーミーショップにおける画像
前述の通り、カラーミーショップではショップオーナーさんがアップロードした画像をショップに訪れたユーザーさんに閲覧してもらっています。
ショップオーナーさんが様々な仕様の画像をアップロードされるため、サービスの品質を維持するために、配信容量や表示速度をカラーミーショップでコントローラブルに保つ仕組みが必要となります。
従来、jpg の画像に対し動的変換を実施することにより配信環境の最適化を行っていました。
2023年、多くのメジャーなブラウザが WebP の表示に対応したため、更に圧縮効率や表示速度に優れる WebP への変換を実施することになりました。
作業内容
以下、どのようなことをやっていったかを解説します。
1. 効果の見積もり
WebP に変換することにより、良さがないとやる意味はないですよね。効果の見積もりは重要です。
カラーミーショップで運営されているショップのなかでも画像を多く使用しているショップから、実際に画像を取得し変換をすることにより効果を測定することにしました。
トップページから2階層回遊することを想定し画像URLを収集し、表示されやすい画像はその分重みを付けて変換前後の容量を推定しました。
結果、55% ほどの容量削減効果があることがわかりました。
ECサイトという特性上、画像の傾向はどのショップでも大きく変わることはありません。カラーミーショップ全体に適用しても55%の容量削減効果が期待できると結論付けました。
2. 実装方針の決定
ほとんどのブラウザが WebP を表示できるとはいえ、非互換ブラウザの考慮も行わなくてはなりません。
アクセスするブラウザが送信する Accept ヘッダを確認することで、画像の出し分けを行うこととしました。
先行事例が非常に参考になりました。ありがとうございます。
Webサービス上の画像変換とWebPの利用について - mercari engineering
さて、カラーミーショップでは、CDN で Amazon CloudFront を使用しています。
Accept ヘッダをオリジンまで到達させ、オリジンで画像を出し分けることで上記仕様を実現しました。
例えば https://example.com/cat.jpg というファイルがあるとします。この URL に対し WebP が表示できるブラウザからアクセスがあった場合、 /cat.jpg.webp という URL に 302 リダイレクトをすることで WebP をダウンロードするという仕様としました。
(その後この仕様が波紋を生むこととなりました…)
3. ショップオーナーさん向け機能追加
上記メルカリさんの記事にもありますが、WebP に変換することで見づらくなってしまう特性の画像が存在します。
そのような画像を多く使用するショップを運営されているオーナーさんにとってこの施策は売上減少といった不利益の要因となる可能性があります。
WebPに変換されることにより自身のショップがどのような見た目になるのか、ショップオーナーさんに確認してもらうことにしました。
どのような方針で確認してもらおうか悩んでいたところ、一緒に施策を担当していた windyakin さんが、特定の URL を踏むと切り替えボタンが出る実装を行い華麗に解決してくれました。
この環境はリリース後多くのショップオーナーさんに利用していただき、結果として WebP への変換を希望しないオーナーさんに対して、画像を変換対象から除外するといった対応を実施することができました。
4. 切り替え手段の検証
リリースする際に非常に重要なことは、切り戻しできるかどうかです。
念入りに検証した結果、Accept ヘッダをキャッシュキーに設定する、設定から外すという操作でリリースと切り戻しを行うことができることがわかりました。
また、キャッシュが再作成されるタイミングでオリジンの負荷が高くなりすぎないように、 URL に含まれる数字部分の末尾を利用することで10回に分けてリリースすることにしました。
これで、あとはリリースするだけです。
切り替えてみると…
いざリリースをしてみると、とある問題が発生しました。
「ショップを訪れる一部のユーザーの環境で画像が表示されない」というものでした。
ログを見ても5xxといった異常系のステータスは確認できません。どういうことだろう…と調査を行いました。
ブラウザキャッシュの問題だった(と思う)
調査を進めると、どうやらブラウザキャッシュの問題である可能性が浮上しました。
カラーミーの画像配信環境では、ブラウザからアクセスがあった際にレスポンスヘッダとして max-age が乗った Cache-Control と Last-Modified を返却します。
Last-Modified の値はユーザーがアップロードしたオリジナル画像を配信する S3 から返却されたものをそのまま利用しています。
ブラウザは Cache-Control ヘッダの max-age が切れた際に、その Last-Modified の値を If-Modified-Since ヘッダとしてリクエストに乗せ CloudFront に送信します。
そして、オリジンにリクエストを行いオリジンの内容に変化がなかった場合、オリジンはステータス304を返します。
304を受け取った CloudFront は RefreshHit としてキャッシュの内容を再度返します。
さて、前述した通り、画像の拡張子を .webp に変更する方式でリリースを行っています。 この一連のフローの中で、以下の事象が発生した可能性がありました。
- WebPリリース前に /cat.jpg にアクセスしたことがあるユーザーが、max-age が過ぎたタイミングで Last-Modified ヘッダを送りリクエストを送る
- ユーザーは /cat.jpg.webp にリダイレクトする 302 を受け取る
- ブラウザは再度 /cat.jpg.webp に対し「If-Modified-Since ヘッダを付けて」リクエストを送信する
- オリジンは304を返し、その際に「CloudFront が304を返すこと」が稀にある状態となった。
- ブラウザに /cat.jpg.webp に関するキャッシュは存在しないため、画像が表示されない状態となった
3, 4 について、本当にそんな挙動であるのか?という疑問がありますが、画像が表示されないユーザーがいることは事実です。
非常に厄介なのが、箇条書きの3のように、/cat.jpg.webp で304を受け取る事自体は正常である点でした。ブラウザにキャッシュがあれば問題なくブラウザキャッシュの画像が表示されます。
結果、影響範囲が特定できない、再現しない、挙動もつかめないというデバッグが非常に難しい事象であることがわかりました。これはつらい…
影響範囲が特定できない以上、事象が起こっている状態を長引かせるにはいきません。切り戻しを決断しました。
どうやったか
対応を考えました。しかし、シュッとできて検証が楽な方法が思いつきません。
藁を掴む思いで、あれこれ相談していた kenchan に相談しました。kenchan も一緒にあれこれ考え、以下の方法ならできる! と方針が決まりました。
それは、URL に拡張子を追加しリダイレクトすることをやめ、変換後の WebP 画像も /cat.jpg のURLで配信することです。
実は URL と拡張子に関する規定は存在せず、Content-Type レスポンスヘッダのみで制御されます。そのため、cat.jpg で WebP を返すことはブラウザにとってなんの問題もありません。
WebP リリース前にアクセスしたことのあるユーザーは引き続き jpg のブラウザキャッシュで画像が見え、それ以外のユーザーは WebP が正常に取得できます。
この方式で無事リリースすることができました。
やってよかったこと
長くなってしまいましたが、このリリースを通して、やっておいてよかったことをまとめます。
1. オーナーさんが変換後の画像を確認できる環境を用意したこと
検証により、画質に大きな変化は起こらないようにパラメータの調整しました。しかし、変化が起こるかどうかは画像により異なります。ショップオーナーさんにとって、リリースされるまでどう画像が変化するかわからないことは、不安に繋がります。
ショップオーナーさんを不安にさせるだけでなく、「念の為、WebPに変換しないでもらいたい」と変換設定を外すことを望むオーナーさんが増えることが懸念されました。
変換後の画像を確認できる環境を用意したことにより、オーナーさんが納得して変更を受け入れることができ、不必要なオプトアウトを回避することができました。
2. 切り戻しの検証を入念に実施したこと
今回、CDN とブラウザキャッシュの仕様への理解が不十分であったことで不具合を生んでしまいました。
しかし、最低限切り戻しをすることで影響を止められることをあらかじめ十分に確認していたため、リリース自体や切り戻し判断に対する安心感に繋がりました。
今後自分が関わる全ての変更も、できる限り切り戻しができる仕様を探そうと思います。切り戻し最高!!!
まとめ
今回、WebP 変換という施策を通じて、不思議なキャッシュの挙動とその対応について、また、ユーザーに対して直接影響を与える変更に対し、取ってよかったなあと思ったアプローチを紹介しました。
キャッシュの挙動に関しては裏が取れていないため、この記事を見て「こういうことなんじゃない?」と思った方はぜひ教えてください…