Fastly VCL
Fastlyは「Varnish」を利用して動作しているため、Varnish Configuration Language(以下、VCL)で設定をプログラマブルに記述できる特徴を持っています。VCLを記述することで、リクエストとレスポンスを加工することができるのです。これにより、エッジサーバーにおいて細かいカスタマイズが可能となります。これをリクルートでは以下のように利用しています。
- ユーザーエージェントからデバイスタイプを選択して出し分ける。
- A/Bテストパラメータを設定する。
- HTMLのキャッシュを行う。
例えば、PCとスマートフォンユーザーで描画するページを出し分けることがあります。FastlyのVCLを用いると、ユーザーエージェントからデバイスがPCかスマートフォンかを判別し、その結果をリクエストのヘッダに加えることが可能になります。そこで、Fastlyでリクエストを受信したときにリスト1のようなVCLを実行すれば、BFFへのリクエストヘッダ「X-UA-Device」にデバイスタイプの結果を載せることができます。
sub detect_device {
if (req.http.User-Agent ~ "(?i)ip(hone|od)" || ...) {
set req.http.X-UA-Device = "sp"
} else {
set req.http.X-UA-Device = "pc"
}
}
次に、描画するコンポーネントのA/Bテストを行う場合について説明します。CDNを置かない場合では、BFFはアクセスしてきたユーザーごとにランダムにA/Bテストパラメータを設定し、テストパラメータに応じて描画するコンポーネントを出し分けます。この時点のレスポンスをCookieに書き出しておくことで、次回以降も割り振られたA/Bテストパラメータで同じコンポーネントを描画し続けることができます。
単純にCDNを置いただけでは、最初にアクセスしたユーザーに割り振られたA/BパターンでHTMLのキャッシュが生成されてしまい、それ以降にほかのユーザーのアクセスがあっても初期にキャッシュしたパターンで描画され続けてしまいます。そのためBFFではなく、エッジサーバーでA/Bテストパラメータを算出する必要があります。
リスト2はエッジサーバーで、リクエストが届いたときにA/Bテストパラメータを設定する例です。Fastlyがランダムにブール(bool)値を返してくれるrandombool関数を持っているため、容易に実装が可能です。
sub set_abparam_into_header {
#in recv
if (!req.http.Cookie:TestParam) {
if (randombool(50,100)) {
set req.http.x-test-param = "a-pattern";
} else {
set req.http.x-test-param = "b-pattern";
}
} else {
set req.http.x-test-param = req.http.Cookie:TestParam;
}
}
Fastlyでは基本的にURL(リクエストのホストとURLパス)をキャッシュキーに利用しており、同一のキャッシュキーに対して該当するキャッシュを返却します。しかしPCとスマートフォンやA/Bテストのように、同じURLで異なるWebページが表示されうるケースがあります。このようなケースのためにFastlyでは、URLに加えてVaryヘッダをキャッシュキーに利用し、Varyヘッダに特定の値を追加することでキャッシュの出し分けを行うことができます。リスト3は、エッジサーバーがキャッシュを取得する際に、VaryヘッダにデバイスタイプとA/Bテストパラメータを載せる例です。
sub set_vary_header {
#in fetch
if (beresp.http.Vary) {
set beresp.http.Vary = beresp.http.Vary ", x-test-param, X-UA-Device";
} else {
set beresp.http.Vary = "x-test-param, X-UA-Device";
}
}
また再訪された際に前回と同じコンテンツを表示する必要があるので、ユーザーへのレスポンスでA/BテストパラメータをCookieに書き込んでもらう必要があります。リスト4は、ユーザーにレスポンスを返すときにA/BテストパラメータをCookieに書き込む例です。
sub set_abparam_into_cookie {
#in deliver
if (!req.http.Cookie:TestParam){
add resp.http.Set-Cookie = "TestParam=" req.http.x-test-param + "; Expires=" + time.add(now, 24w) + "; Path=/;";
}
}
以上の内容をエッジサーバーで行うことで、Fastlyが導入された構成でA/Bテストを行うことができるようになります。
FastlyとCI連携
こうしたVCLを記述することでエッジサーバーに機能を持たせることができる一方、開発時にVCLの修正のたびにFastlyへデプロイし、エンドポイントを更新するのは容易ではありません。そこでリクルートでは、CIと連携してFastlyのエンドポイントを自動的に立ち上げる仕組みを検討しています。Fastlyは「Terraform」向けのプロバイダが準備されているので、TerraformでCIと連携してFastlyのエンドポイントを立ち上げることができます。これを利用して、GitHub上の特定のブランチに修正したVCLをpush(プッシュ)したことをトリガーにしてFastlyのエンドポイントを新規に立ち上げ、Fastly導入時の動作確認もより容易に行うことが可能になります。
キャッシュ戦略
フロントエンドのパフォーマンス改善の文脈でキャッシュの利用は欠かせないものです。その中では、CDNの選択も今後必須の技術となっていくことが予想できます。それと同時に、今後のフロントエンドのWebサービス開発では設計段階からCDNを活用したキャッシュ戦略も検討する必要があるでしょう。
コンテンツの情報更新が頻繁にあり、表示結果が毎回変わるようなコンテンツの場合はキャッシュを保持できません。新しく更新された情報で正しくWebページを表示するためには、コンテンツ情報の更新があるたびにキャッシュをパージしないといけないからです。その点Fastlyではミリ秒単位の短い時間でパージが可能なため、パージされるまでの時間を気にする必要はほとんどありません。しかしパージするまでの間隔が短ければ短いほど、CDNキャッシュの恩恵は低くなります。そのため、ビジネス的に許容できる範囲でパージするまでの間隔をできる限り長くできれば、キャッシュを効率的に活用することができます。
そのほかには、HTMLページの中でキャッシュする箇所とキャッシュしない箇所を決め、キャッシュしない箇所に関しては後からJavaScriptで更新するといった戦略もあります。例えば、コンテンツ情報の更新に関係のある部分がオススメのコンテンツを表示するコンポーネントだけであった場合、この部分だけをCSRにしてそれ以外の部分をSSRで配信するようにします。こうすることにより、CDNにキャッシュされるHTMLはコンテンツ情報に関わらない部分しかなくなるので、コンテンツ情報の更新があってもキャッシュをパージする必要がありません。ただし、CSRにされる部分は初期表示されていないことが許容できる場合に限ります。
図7をご覧いただくと、CDNのキャッシュを残すことを前提としてシステム設計や描画するコンポーネントの設計を検討することの必要性を理解していただけると思います。CDNやクライアントサイドのキャッシュなど、キャッシュを多層化させると問題は複雑化していきます。そのため、アプリケーションの要件に応じて適切なキャッシュ戦略を決めていく必要があります。
まとめ
本稿では、今後取り組むリクルートのWeb技術として、リクルートテクノロジーズがR&Dとして取り組んでいるredux-plutoとFastlyの導入について紹介しました。redux-plutoはBFFでReact/Reduxを使ったSSRが可能なボイラープレートで、社内のサービスにおいて開発と運用を続けています。FastlyはA/Bテストを行うようなケースで技術検証しており、次期サービスへの導入を予定しています。
今後もフロントエンドの最新技術をキャッチアップしながら、さらなるWebサービスのパフォーマンス改善にチャレンジしていきます。
