🐶

300万以上のURLをキャッシュを活かして効率よくリダイレクトさせた話

2024/07/03に公開

こんにちは。株式会社スペースマーケットでフロントエンドエンジニアをしておりますwado63です。
少し前ですが、SEOの施策として300万以上のURLを新ページにリダイレクトさせるという対応を行いました。
SEOの話はSearch Centralのドキュメントに書いてあること以上の話は正確な答えがない分野なので、ここではリダイレクトに使用した技術に絞って書きたいと思います。

今回取り組んだ課題

スペースマーケットでは

  • /features/tv/cities/fujisawa-shi--kanagawa/stations/1130108/
  • /search/space_types/rental_studio/areas/kanagawa/cities/fujisawa-shi/stations/9932005/
    のようなパターンのURLが300万以上存在していました。

これらのURLを以下の要件に沿って/lists/${id}/にリダイレクトさせます。

  • リダイレクト後のページ表示の時間も含めて平均1000ms以内に表示できること
  • 300万以上の各URLに対して個別にリダイレクト先を設定できること
  • 特定のクエリパラメーターがある場合、そのクエリパラメーターを削除してリダイレクトできること
  • 特定のクエリパラメーターがある場合orリダイレクト先がない場合は、検索ページにリダイレクトすること

アーキテクチャ

リダイレクトの仕組みは以下のようになっています。
Fastlyおよび、AWSのALB、Lambda、DynamoDBを利用しています。

リダイレクトアーキテクチャ

  1. ユーザーからのリクエストをFastlyを経由してALBに流す
  2. ALBは特定のパスにリクエストが来た場合、リダイレクト用のAWS Lambdaにリクエストを流す
  3. LambdaはリクエクトされたURLを元にDynamoDBに保存してある、リダイレクト先のURLを取得しリダイレクトのレスポンスを返す
    または特定のクエリパラメーターがある場合、要件に合わせてリダイレクトのレスポンスを返す
  4. Fastlyでリダイレクトのレスポンスをキャッシュし、次回以降のリクエストに対してはFastlyからレスポンスを返す

スペースマーケットはもとよりFastlyとALBを利用しているため、今回は主にリダイレクト用のLambdaを追加とFastlyのキャッシュヘッダーを調整することで対応できました。

こだわった点

  • Lambdaのコールドスタートを避けるため、Provisioned Concurrencyを利用して起動時間を短縮
  • Go言語を利用して軽量なLambdaを作成
  • Fastlyを活かして2回目以降のリクエストはキャッシュから返す
  • Varyヘッダーを利用したフューチャーフラグで安全なリリース
  • 事前にリダイレクトレスポンスをキャッシュし、キャッシュヒット率を上げる

Lambdaのコールドスタートを避けるため、Provisioned Concurrencyを利用して起動時間を短縮

Lambdaはコールドスタートが発生するため、初回のリクエストに対しては起動時間がかかります。
Provisioned Concurrencyを利用することで事前にLambdaを起動しておくことができ、コールドスタートを回避できます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/provisioned-concurrency.html

Go言語を利用して軽量なLambdaを作成

私はフロントエンドエンジニアなので、普段はNode.jsで書いてしまうことが多いですが、今回はできる限りパフォーマンスを意譳してGo言語を利用しました。
DynamoDBへのアクセスとクエリパラメーターを見て分岐してHTTPのレスポンスを返すだけなので、テストコードで動作確認しながらすぐに作成できました。
パフォーマンスに関しては以下の記事を参考にしています。

https://filia-aleks.medium.com/aws-lambda-battle-2021-performance-comparison-for-all-languages-c1b441005fd1

Fastlyを活かして2回目以降のリクエストはキャッシュから返す

Fastlyはデフォルトで301のステータスコードをキャッシュするので、max-age, surrogate-controlなどでキャッシュの期限を設定するだけでキャッシュすることができました。
Next.jsのリダイレクトに使われる308などはキャッシュされないので、どのステータスコードがキャッシュされるかどうかは普段から意識しておきたいですね。

https://docs.fastly.com/ja/guides/caching-best-practices

Varyヘッダーを利用したフューチャーフラグで安全なリリース

FastlyではVaryヘッダーを利用することで、特定のリクエストヘッダーが異なる場合に異なるキャッシュを返すことができます。

https://www.fastly.com/jp/blog/getting-most-out-vary-fastly/

先ほど示したアーキテクトのALBの箇所は/search/*などのパスだけではなくsome-redirect-flagというようなフィーチャーフラグ用のヘッダーに応じてもリダイレクトを行うかどうかを判定しています。

リダイレクトアーキテクチャ

既存ページとリダイレクトのレスポンスはVary: some-redirect-flagのようなヘッダーを付与しているため、リクエストヘッダーのsome-redirect-flagに応じてキャッシュが分かれるようになっています。
リダイレクト機能をユーザー公開する前は、Chromeの拡張機能など使ってsome-redirect-flagを付与し本番環境でのリダイレクトの挙動を確認していました。
実際の本番環境を使用して検証するため、ユーザー公開した際の差分を最小限に抑えることができました。

実際のユーザー公開はFastly側の設定でリクエスト時にsome-redirect-flagのリクエストヘッダーを付与することで切り替えられます。万が一障害が発生した場合は、Fastlyの設定を元に戻すだけでリダイレクト機能をオフにすることができるため安全なリリースができました。

事前にリダイレクトレスポンスをキャッシュし、キャッシュヒット率を上げる

Provisioned Concurrencyの利用や、Goを使ったLambdaは確かに早いですがオリジンにリクエストが到達してしまうためレスポンスが遅くなります。
なので事前にキャッシュを作成しておくことでFastlyのキャッシュヒット率を上げることにしました。

Fastlyではオリジンシールドという設定があり、これは世界各地のpoints of presence (POP)という配信拠点にキャッシュがない場合、オリジンサーバーにリクエストを送る前に指定したPOPを経由してキャッシュを取得できます。
https://docs.fastly.com/ja/guides/shielding

Lambdaは負荷にかなり強い作りになっているので、ユーザー公開の前に300万以上のURLに対しリクエストを送ることでキャッシュを事前作成しました。
実際には300万のURLが書かれたテキストファイルを元にひたすらcurlを叩くシンプルな処理で対応しています。

結果

リダイレクト単体で見た場合キャッシュがある場合は50ms前後、キャッシュがない場合は100ms前後と比較的高速にリダイレクトができるようになりました。
速度早いのももちろんですが、仕組みとしてかなりシンプルなものだったので開発もかなりしやすかったです。

実のところリダイレクトの処理というよりも、既存アプリケーションの実装を読み解いてリダイレクト先のURL・ページを作るというところが本当に大変でした。それはまた機会があれば書きたいと思います。

宣伝

スペースマーケットでは現在エンジニアを募集しております!
ちょっと話を聞いてみたいといったようなカジュアルな面談でも構いませんので、ご興味のある方は是非ご応募お待ちしております!

スペースマーケット Engineer Blog

Discussion