有名テック企業の技術ブログを、ひとつのフィードで。
フィード
35件
はじめに こんにちは。WEARバックエンド部SREブロックの春日です。普段はWEARというサービスのSREとして開発・運用に携わっています。 本記事では、WEARのハイブリッド検索のリリースに伴い刷新した検索インデクシングシステム(以下、インデクサー)について、OpenSearch Ingestionを採用しようとした際にハマったポイントや、ベクトル検索のためのインデクサーを設計する上で工夫した点を中心に紹介します。 目次 はじめに 目次 背景 既存のインデクサーと刷新の動機 ベクトルデータの保持方法の検討 インデクサーの構成方針 BigQuery → S3 のデータ連携 日次更新の設計 差分更新の設計 初期設計:OpenSearch Ingestion+Lambdaプロセッサでのベクトル化とインデクシング 1万件の差分更新で表面化した問題 再設計:ベクトル化Lambdaを前段に出す 最終設計:S3+SQS+Lambdaで非同期にベクトル化とインデクシング ベクトル化Lambdaでの工夫 Bedrockのリージョン分散 1ファイル単位の処理量を制御する 出力形式と後処理 OpenSearch投入Lambdaでの工夫 external versionで古いデータで新しいデータを上書きすることを防止 処理完了後のファイル削除 Lambdaエラー時のファイル退避 非同期処理の完了待機 既存データに対する初回ベクトル化 結果 まとめ 背景 WEARでは、検索基盤としてAmazon OpenSearch Service(以下、OpenSearch)を利用しています1。これまでフリーワード検索ではタグマッチングを主軸としていましたが、タグが付与されていない検索ワードに対する検索結果の質と量に課題がありました。 これを改善するため、ベクトル検索と全文検索を組み合わせたハイブリッド検索(WEARではあいまい検索と呼んでいるため、以下「あいまい検索」と表記)をリリースすることになりました2。 あいまい検索のためにベクトル検索を導入するには、検索対象の各documentに対して、タイトル・説明文・タグなどを連結したテキストをベクトル化したフィールドを持たせる必要があります。しかしながら、既存のインデクサーでこのフローを実現するのは難しく、インデクサー自体を刷新することになりました。本記事ではその刷新の過程と、設計時に行った工夫を紹介します。 既存のインデクサーと刷新の動機 WEARではOpenSearchへのインデクシングをEmbulkを用いて行っていました。 embulk-input-bigqueryとembulk-output-elasticsearchなどを組み合わせ、BigQueryからOpenSearchへデータを連携する構成です。EmbulkのジョブはDigdagのworkflowで管理し、Amazon EKS(以下、EKS)上のJobとして実行していました3。インデクサーには差分更新と日次更新の2種類があり、それぞれ次の役割を持っていました。 差分更新:10分間隔で実行。直近で新規投稿・更新documentをインデクシングし、削除された投稿をindexから削除 日次更新:1日1回、新しいindexを作成して全件をインデクシングし、Blue/Greenでエイリアスを切り替える形で全件更新する。統計データなどの日次で更新すべき値はこのタイミングで反映 しかし、ベクトル検索の導入を検討するにあたり、この構成にはいくつかの課題がありました。 WEARで一番大きいコーディネートのindexは大量のdocumentを持っており、これらを毎日ベクトル化するのはコストと処理時間の両面で非現実的 BigQueryからOpenSearchへの連携中にベクトル化の処理を挟むのが困難 本対応の検討時点でEmbulkはすでにメンテナンスがされていない状態であり、長期的な保守性に不安 これらを踏まえ、ベクトル検索対応に必要な機能と、長期的な保守性の両方を満たす構成へとインデクサーを刷新する方針を決めました。 ベクトルデータの保持方法の検討 最初に取り組んだのが、ベクトルデータをどこに、どのタイミングで持たせるかという検討です。 既存の日次更新では新しいindexを毎日作成して全件インデクシングしていましたが、大量のデータを毎日全件ベクトル化するのは非現実的なため、ベクトルデータを別ストレージに保存しておく案を検討しました。しかし、ベクトル取得時のパフォーマンスやコスト面で見合わないと判断し、最終的には日次での全件更新そのものを廃止する方針を取りました。 毎日indexを全件更新するメリットの1つとして、indexの不整合が発生した場合に、日次での全件更新によって整合性を保つことができるという点がありました。これは例として、差分更新の失敗時のリトライで、古いデータで新しいデータが上書きされてしまうといった状況が挙げられます。全件更新を廃止するにあたり、この点をどう担保するのかが課題でしたが、後述する方法でdocumentのバージョニングを行うことで、不整合が発生しないようにしました。 新しい設計では、ベクトル化は差分更新のみで行い、日次更新では同じindexに対して日次で更新すべき値のみを上書きするように責務を分けました。これにより、ベクトル化を投稿の追加・更新時のみに限定でき、ベクトル化コストと処理時間の問題を回避できるようになりました。 インデクサーの構成方針 インデクサー刷新にあたって、ベクトル化を含む新しい構成として複数の選択肢を検討しましたが、OpenSearch Ingestionを軸とする構成を採用しました。判断のポイントは以下の通りです。 自前で運用する外部ツールは最小限にしたい(Embulkのように追加でメンテナンスが必要なツールを増やしたくない) データ抽出のSQLはバックエンドエンジニア、インデクサーのインフラ構築・運用はSREという責務分離を維持し、両者を疎結合にしたい AWS公式のLambdaプロセッサでベクトル化するパターンを参考にすれば、ベクトル化部分をLambdaへ切り出して柔軟に構成できそう これらを総合的に考慮し、Amazon S3(以下、S3)を起点としたAWS Lambda(以下、Lambda)の構成を方針として進めることになりました。 BigQuery → S3 のデータ連携 WEARではMicrosoft SQL ServerからBigQueryへリアルタイム連携をしており、インデクサー側もBigQueryからデータを取得しています。前述の通り、インデクサーはS3を起点としてデータを処理する設計を取っているため、BigQueryから取得したデータをS3に連携する必要があります。BigQueryから直接S3へ出力する機能はないため、いったんCloud Storage(以下、GCS)へ出力してからS3へ転送する形を取りました。 GCSへの出力にはBigQueryのEXPORT DATA文を利用しています。差分更新・日次更新いずれもJSON Lines形式でGCSへ出力するように記述しており、以下に差分更新を例にしたものを記載します。 EXPORT DATA OPTIONS( uri='gs://GCS_BUCKET/coordinates/diff/raw-data/YYYY/MM/DD/HH/mm/data_*.jsonl', format='JSON', overwrite=true ) AS -- 対象データを取得するクエリ ... uriにワイルドカード(*)を含めることで、BigQueryが出力サイズに応じて自動的に複数ファイルへ分割します。出力フォーマットはJSONを指定するとJSON Lines形式になります。 GCSからS3への転送方法は、差分更新と日次更新で異なるツールを使い分けています。 差分更新:rclone 日次更新:AWS DataSync(以下、DataSync) DataSyncは大量データの高速転送に適していますが、タスクの起動・実行に約5分かかります。差分更新は10分間隔で実行する上、ベクトル化のような時間のかかる処理も挟まるため、起動に時間のかかるDataSyncは許容できませんでした。差分更新ではデータ量がそこまで多くないこともあり、rcloneを採用しています。 日次更新の設計 日次更新では、もともとEmbulkで実装されていた全件更新を廃止し、差分更新と同じindexに対して統計データなどの日次で更新すべき値のみを上書きする方式に変更しました。日次更新の構成は以下の通りです。 日次更新はOpenSearch Ingestionを採用しており、S3に格納された全件データに対してS3 scanでOpenSearchへbulkでupsertしています。OpenSearch Ingestionのパイプライン定義の例は以下のとおりです。 version: 2 coordinates-daily-indexer: source: s3: acknowledgments: true delete_s3_objects_on_read: true scan: buckets: - bucket: name: ${BUCKET_NAME} filter: include_prefix: ["coordinates/daily/raw-data/"] aws: region: ap-northeast-1 sts_role_arn: ${STS_ROLE_ARN} codec: ndjson: {} processor: - delete_entries: with_keys: <s
ZOZO開発組織の2026年4月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2026年4月は、前月のMonthly Tech Reportを含む計11本の記事を公開しました。中でもWEARバックエンドの無停止移行に関する記事は詳細に記されています。ぜひご覧ください。 techblog.zozo.com 登壇 JAWS-UG山梨 【第11回】勉強会 4月4日に開催された「JAWS-UG山梨 【第11回】勉強会」に、SRE部の姫野が登壇しました。 AWS Data & AI イノベーションフォーラム:顧客成功事例から学ぶデータ活用の最前線 DAY1 4月9日に開催された「AWS Data & AI イノベーションフォーラム:顧客成功事例から学ぶデータ活用の最前線 DAY1」に、SRE部の伊藤が登壇しました。 Claude Code Skills実践! - 業務を効率化する活用事例 4月9日に開催された「Claude Code Skills実践! - 業務を効率化する活用事例」に、ZOZOTOWN開発3部の平林(@bayacollector)が登壇しました。 try! Swift Tokyo 2026 4月12-13日に開催された「try! Swift Tokyo 2026」に、ZOZOTOWN開発2部の續橋(@tsuzuki817)が登壇しました。 【ZOZO x Mercari x LayerX】企業R&D勉強会 〜 研究と実用化のリアル〜 4月24日に開催された「【ZOZO x Mercari x LayerX】企業R&D勉強会 〜 研究と実用化のリアル〜」に、ZOZO研究所の清水が登壇しました。 協賛 RubyKaigi 2026 2026年4月22日から24日の3日間にわたり函館で開催された「RubyKaigi 2026」にプラチナスポンサーとして協賛しました。 technote.zozo.com techblog.zozo.com 掲載 ZOZO独自のAI活用指標「All ZOZO AI Readiness Score(AZARS)」 4月8日にZOZO独自のAI活用指標である「All ZOZO AI Readiness Score(AZARS)」の導入を発表しました。 AZARS(アザース)は「組織AI活用レベル」と「個人AI活用レベル」の2つで構成され、生成AIを含むAI活用において、業務上期待される能力と状態をそれぞれ4段階で定義した指標です。本指標の導入により、主観的になりがちなAI活用度を全社統一の基準で可視化・評価することが可能になります。またAZARSは、エンジニアなどの開発者と、事業・コーポレート部門といった非開発者の双方に共通する指標を定めている点に特徴があります。これにより、職種にかかわらず、同一の基準でAI活用を推進することが可能になります。 corp.zozo.com この「AZARS」導入に関連した記事が複数のメディアに掲載されました。 www.itmedia.co.jp www.nikkei.com AI活用はどう可視化して推進する? ZOZOが導入した全職種共通のAI活用指標「All ZOZO AI Readiness Score(AZARS)」とは | ネットショップ担当者フォーラム ZOZOの似合うコーデAI ラボくん 4月27日に対話で日常の服選びをサポートするLINE公式アカウント「ZOZOの似合うコーデAI ラボくん」の開設を発表しました。 「ZOZOの似合うコーデAI ラボくん」は、ユーザーの言語化しにくいファッションの好みやニーズを「ラボくん」との会話によって引き出し、要望を具体化することで、ユーザーの嗜好や利用シーンに応じたコーディネートを提案します。多くの方が利用するLINEという日常的な接点をチャネルとすることで、ファッションの情報探索や相談をより身近で手軽なものにし、日常の服選びをサポートします。 corp.zozo.com この「ZOZOの似合うコーデAI ラボくん」開設に関連した記事が複数のメディアに掲載されました。 netkeizai.com www.ryutsuu.biz ZOZOがLINEでコーデ相談AI ファッションECの「検索から対話」の試金石 - WWDJAPAN 日経BOOKプラス 日経BOOKプラスのゼロから創らない戦略に、ZOZOのビジネスモデルに関する記事が掲載されました。 bookplus.nikkei.com その他 香りの総合プラットフォーム「カラリア」を運営する株式会社High Linkを完全子会社化 4月30日に株式会社High Linkの全株式を取得し完全子会社化 したことを発表しました。 当社は、今後の戦略の一つとして「Near Fashion領域」での成長を掲げ、ファッションの周辺領域における事業推進と利益創出を目指しています。ファッションと親和性が高い香水を中心にした事業を手掛けるHigh LinkをZOZOグループに迎えることで、当社グループはフレグランス市場への領域拡大を図るとともに、サブスクリプションサービスをはじめとする販売手法を取り入れ、ファッション周辺領域における事業展開を加速します。今後は当社の顧客基盤を活用したHigh Linkの各サービスへの送客や、当社のEC運営ノウハウおよびデータを活用し、香水との新たな出会いを促すディスカバリー体験の提供などにも取り組む予定です。 corp.zozo.com 2026年3月期 通期決算発表 4月30日に2026年3月期 通期決算発表を開示しました。詳細は以下のリンクにある開示資料をご確認ください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%
はじめに こんにちは、ブランドソリューション開発本部ZOZOMO部FBZブロックの池上 寛登です。2026年3月にZOZOへ入社し、Fulfillment by ZOZO(以下、FBZ)のバックエンド開発を担当しています。 FBZに参画してまず直面したのは、ドメイン知識の壁でした。中でも強く実感したのが、コードレビューの場面です。Pull Request(以下、PR)のレビューには、判断の根拠がドキュメントに載っていない「暗黙知の壁」がありました。既存メンバーの指摘は的確ですが、新規参画者の自分には同じ品質でレビューする難しさがありました。 この課題を解決するために、暗黙知を形式知としてガイドライン化し、Claude Code SkillsとAmazon Bedrockに組み込んだPR自動レビュー基盤を作成しました。本記事では、その仕組みと設計判断を紹介します。 目次 はじめに 目次 背景:FBZにおけるPRレビューの課題 アプローチの全体像 ガイドラインの設計 暗黙知の収集 レイヤー別ガイドラインの作成 変更パスに応じた参照ルールの選択 実行基盤の構成 実行環境 PRサイズに応じたモデル選択 2段レビューによる誤検知抑制 Confidence(確信度)閾値と Severity(重要度)ラベル ai-reviewedラベルによる再レビュー ガイドライン更新の自動提案 効果 ドメイン固有のアンチパターン検出 実装品質の向上 新規参画者にとってのドメインリファレンス まとめ 背景:FBZにおけるPRレビューの課題 FBZはZOZOTOWNの倉庫リソースを活用し、外部のブランドが運営する自社ECへ物流・決済・返金などの機能を提供するフルフィルメントサービスです。在庫同期・注文管理など、扱うドメインは多岐にわたります。 さらにFBZは複数のリポジトリで構成されており、リポジトリごとに採用言語やアーキテクチャが異なります。PRレビューで見るべき観点もリポジトリごとに大きく変わるため、レビュアーには横断的な知識が求められます。 実装やレビューの参考になるようなガイドラインは存在したものの、リポジトリごとに保存場所が異なり、内容の鮮度や粒度も統一されていませんでした。結果として、判断はレビュアー個人の経験知に大きく依存し、次の3つの課題が顕在化していました。 レビュアーごとに観点が異なり、レビュー品質にばらつきが出る 新規参画者がドメイン知識をキャッチアップするまでに時間を要する 同じアンチパターンの指摘が、異なるPRで繰り返し発生する アプローチの全体像 これらの課題に対し、ガイドラインを中心に据えたPR自動レビュー基盤を構築しました。 取り組みは大きく次の3層に分かれます。 ガイドラインの設計:暗黙知を形式知へ落とし込み、レイヤー別ファイルとNG/OKペアで定義する 実行基盤の構成:Claude Code Actionを実行し、関連するガイドラインを読み込んでPRをレビューする ガイドライン更新の自動提案:過去のレビューコメントからガイドラインの更新提案を自動生成し、ルールの陳腐化を防ぐ それぞれを順に説明します。 ガイドラインの設計 暗黙知の収集 ガイドラインに落とし込む暗黙知は、PRのレビューコメントから収集しました。当初はSlack(コミュニケーションツール)の議論やConfluence(社内Wiki、ADR)の設計メモからも抽出を試みました。しかしFBZの場合は設計判断や指摘の根拠の多くがPRのレビュー会話に蓄積されていたため、最終的にPRを主な情報源とすることにしました。 過去のPRレビューを横断的に分析し、次の観点に該当する指摘をルール化しました。 同種の指摘が繰り返し発生している 既存のドキュメントではカバーされていない チーム全体で共有すべき設計判断が含まれている レイヤー別ガイドラインの作成 収集した暗黙知をルール化するため、ドメインで頻出する論点を抽出し、アーキテクチャレイヤーごとのファイルへ分割しました。具体的には、レイヤー責務・データ整合性・エラー設計・テスト/コーディング規約・インフラ/セキュリティといった観点で章を分け、全章で前提となる共通ファイルを別途用意しています。 各ルールはNG/OKペアの形式で記述しています。形式を統一することで、LLMがルールの境界を判定しやすくなります。 # NG: 呼び出し元で税率や端数処理をハードコードしている total = round(price * 1.1) # OK: ドメイン共通の計算ロジックを通し、税率や端数ルールを一元化できている total = PriceCalculator.with_tax(price) 変更パスに応じた参照ルールの選択 レビュー時にガイドラインを全て読み込むと、コンテキストが肥大化して精度も落ちます。そこで、変更ファイルのパスから参照すべきガイドラインを対応付ける表を定義しました。 この対応表は SKILL.md に記述しており、LLMが変更ファイル一覧と対応表を照らし合わせて、該当する章だけを動的にロードすることを実現しています。 'app/service/**': layer-rules/layer-responsibility.md 'app/dataaccess/**': layer-rules/data-integrity.md 'tests/**': layer-rules/test-and-coding.md 実行基盤の構成 実行基盤の全体像は以下のとおりです。図中の各要素については、次節以降で順に解説していきます。 実行環境 基盤としてはGitHub Actions上で動作するClaude Code Actionを採用し、モデル呼び出し先にはAmazon Bedrockを選びました。 ガバナンス要件として、社外のサービスへソースコードを送信せず、社内AWSアカウントに閉じた状態でモデルを利用する必要があったためです。GitHub ActionsからはOIDCでAWSにAssumeRoleし、Bedrockの推論プロファイル経由でClaudeモデルを呼び出します。これによりIAM・ログ・モデル呼出のすべてを社内環境に閉じた構成で運用できます。 PRサイズに応じたモデル選択 PRのサイズに応じて、レビューに使うモデルを動的に切り替えています。PRサイズは、差分の行数と変更ファイル数の組み合わせで判定しています。Opusは検証段階で精度向上の幅がコストに見合わなかったため、採用していません。SonnetとHaikuの使い分けで、精度・コスト・レイテンシをバランス良く確保できました。 PRサイズ モデル 設計判断 小規模・単一ファイル Claude Haiku 軽量な判定で十分な品質を確保できるため、コストとレイテンシを優先する 中〜大規模 Claude Sonnet レビュー本処理の標準モデル。大規模PRでは影響範囲の調査で往復回数が増えるため、ターン上限(LLMがツール呼び出しを連続で行える回数の上限)を引き上げて対応する 2段レビューによる誤検知抑制 LLMによる自動レビューで課題となるのが、誤検知(False Positive)と見逃し(False Negative)です。誤検知が多ければBotの指摘は信頼されなくなり、見逃しが多ければ自動レビュー自体の意義が失われます。 この課題に対し、単一セッション内でペルソナを切り替えながら2段でレビューする仕組みを取り入れました。 Reviewer Passは「網羅的に拾うペルソナ」として候補を出し切り、Validator Passは「批判的に再検証するペルソナ」として根拠を再確認します。役割を意識的に分離することで、互いに独立した判断が成立します。検証段階で複数のレビュー方式を比較したところ、この方式が最も誤検知を抑えられました。 Confidence(確信度)閾値と Severity(重要度)ラベル Validator Passでは、各指摘に対してチェック項目を採点し、合計値をその指摘のConfidenceとして算出します。チェック項目は「根拠コードを再読み込みしたか」「PR説明文を踏まえているか」「影響範囲を確認したか」などです。 指摘にはImportant・Pre-existing・Nitの3種類のSeverityを付与し、それぞれに投稿するためのConfidence閾値を設けています。Importantは厳しめ、Nitは緩めの閾値です。閾値を満たさない候補は、たとえReviewer Passで挙がっていても投稿しません。 Severityはコメント先頭に[Important]のようなテキストラベルとして付与します。PR作成者はラベルを見て対応の優先度を判断できます。 ai-reviewedラベルによる再レビュー 不要な再実行を避けてコストを抑えるため、レビュー完了時にPRへai-reviewedラベルを付与しています。PR作成者は修正後にラベルを外すだけで、必要なときだけ再レビューを起動できます。 また再レビュー時はノイズ抑制のため、ブロッキング相当の指摘であるImportantとPre-existingのコメントのみを投稿するよう制御しています。 ガイドライン更新の自動提案 ガイドラインは、コードベースや設計判断の変化に追従できないため、時間の経過とともに陳腐化します。これを避けるため、直近のレビューコメントと既存ガイドラインを照らし合わせ、追加・修正・削除を含む更新提案を起票するワークフローを毎月実行するようにしました。 また、ガイドライン更新時の判断基準を統一するため、追記場所・文体・重複チェック手順をまとめたメタガイドラインを別ファイルで用意しています。これにより、ガイドラインの一貫性を保つことができます。 効果 運用開始からまだ日が浅いですが、現時点で見えている効果は次のとおりです。 ドメイン固有のアンチパターン検出 ガイドラインに沿ったレビューが行われるため、レイヤー責務・データ整合性・エラー設計といったドメイン色の強い論点も、自動レビューで捕捉できるようになりました。 実装品質の向上 今回の対応でプロジェクトにおける暗黙知を形式知化できました。例えばこのガイドラインを .claude/rules/ に配置することで、開発時のClaude Codeも同じルールを参照する効果を期待できます。 新規参画者にとってのドメインリファレンス 副次効果として、ガイドラインは新規参画者の学習リファレンスとしても機能しています。私自身もこのガイドラインで「FBZのService層はどう書くのが正解か」を確認しながら実装を進めてきました。 まとめ 本記事では、Claude Code SkillsとAmazon Bedrockを組み合わせてFBZドメインに特化したPR自動レビュー基盤を構築した取り組みを紹介しました。 ドメイン知識を抱えるチームでPRレビューの自動化を検討している方は、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは、WEAR開発部SREブロックの木内です。普段はWEARのSREとして開発、運用に携わっています。 WEARは2013年にサービスを開始し長年オンプレミスで運用されてきましたが、過去にクラウド(AWS)へのシステムリプレイスを実施しています。その際にWebアプリのCDNとしてFastlyを採用し、オンプレミスからクラウドへの段階的な移行を実現しました。 採用の決め手は主に以下の点です。 パスベースのルーティングが可能(パスごとにオンプレミスとクラウドのオリジンを切り替えられる) ネイキッドドメインへの対応 設定変更の即時反映による迅速なロールバック リプレイスの詳細については、以下の記事をご参照ください。 techblog.zozo.com Fastlyを用いた構成はサービスを止めずに安全なリプレイスを実現するうえで大きく貢献しました。一方で、リプレイスが完了しFastlyを導入した当初の目的を達成した後も残り続けたことで、運用負荷やコストといった課題が顕在化してきました。 本記事では、過渡期の構成を整理し、CDNをFastlyからAmazon CloudFront(以下、CloudFront)へ移行してAWSに統一した取り組みを紹介します。 目次 はじめに 目次 移行の背景・課題 運用負荷 コスト 移行後の構成 移行方法 1. LP・アセットの切り替え 2. 動的コンテンツ・認証処理の切り替え 3. VCLの処理をCloudFront Functionsへ移行 4. DNS切り替え WAFの移行タイミング 設計のポイント 移行期間中のリクエスト CloudFront Functionsを用いたリダイレクト・リライト カスタムエラーレスポンス 得られた成果 運用のシンプル化 インフラコストの削減 まとめ 移行の背景・課題 移行前の構成は以下の通りです。 Fastlyをフロントに置き、バックエンドにALBとS3が2つずつ、計4つのオリジンを持つ構成です。 ALBは動的コンテンツの配信や認証処理を担っており、S3はLPやアセットなどの静的コンテンツを配信しています。S3の前段にはそれぞれ個別のCloudFrontを使用しています。 また、FastlyではVCL1を用いてCDN以外の多くの処理も担っていました。 パスごとのオリジン振り分け 各種ブロック(IP / ASN / リファラ など) Basic認証 メンテナンスモード リダイレクト / リライト 前述の通り、リプレイス時にFastlyを採用したことで安全にリプレイスを進めることができましたが、リプレイス完了後に以下のような課題が残りました。 運用負荷 AWSとFastlyの二重管理が運用負荷に繋がっていました。 WEARの大部分はすでにCloudFrontを使用しており、2つのCDNそれぞれでキャッチアップが必要だった AWSとFastlyそれぞれでユーザーやリソースの管理も必要だった 設定変更をWebチームとSREチームで担っていたため、Fastly専用のインフラリポジトリを別途設けており、CIの整備やレビューフローの維持など、リポジトリが増えることに伴う管理コストが発生していた コスト FastlyをAWSの前段に置く構成であるため、大きく分けて2種類のコストが発生していました。 Fastlyがユーザーへ配信するコスト AWSがFastlyへ配信するコスト また、LPやアセットはすでにCloudFrontを使用していたため、FastlyとCloudFront両方のCDNコストが発生していた点も見過ごせません。 これらの課題を解消するために、CDNをCloudFrontへ移行し、配信基盤をAWSへ統一する方針を取りました。 移行後の構成 移行後の構成は以下の通りです。 移行後は、ALB・S3など全てのバックエンドの前段に1つのCloudFrontを配置しています。 また、FastlyのVCLで実施していた処理はそれぞれ対応するAWSサービスへ移行しました。 処理 Fastly AWS CDN・配信 Fastly CDN CloudFront パスごとのオリジン振り分け VCL CloudFront(Cache Behavior) 各種ブロック(IP / ASN / リファラ など) VCL AWS WAF Basic認証 VCL AWS WAF メンテナンスモード VCL AWS WAF リダイレクト / リライト VCL CloudFront Functions Basic認証・メンテナンスモードはAWS WAF(以下、WAF)のカスタムレスポンス機能を利用して実装しています。WAFのルールにマッチしたリクエストに対して、任意のHTTPステータスコード・レスポンスヘッダー・レスポンスボディを返せる機能です。 セキュリティ関連の処理をWAFに集約することで一元管理できることに加え、CloudFront Functionsのコードサイズや実行時間の制限2を避けられる点も採用の理由です。 リダイレクト・リライトの実装にあたり、Lambda@EdgeとCloudFront Functionsを比較検討しました。Lambda@Edgeは複雑な処理や長時間実行に向いている一方、今回のような軽量なリダイレクト・リライト処理にはCloudFront Functionsが適しています3。加えてスケール量・リクエスト料金の面でも有利なため採用しました。 さらに、リダイレクト・リライトはルール数が多いため、jsをビルドしminify化しながらCloudFront Functionsのコードサイズ制限を超過しないよう工夫しています。CloudFront KeyValueStoreを使用したサイズ圧縮も検討しましたが、フロントとインフラ間の依存関係の簡素化や認知負荷の低減を優先したかったからです。 これらの設計を経て、FastlyのVCLに分散していたエッジ処理をAWS WAFとCloudFront Functionsに集約し、CDNレイヤーの機能をすべてAWS上で完結させる構成になりました。 移行方法 今回のCDN切り替えはフェーズを分けて段階的に実施しました。このCDNはPC・SP全体のトラフィックを受けているため、問題が発生すれば多くのユーザーへ影響が出てしまいます。そのため、いかにユーザー影響を出さず安全に移行するかが本プロジェクトの鍵でした。 具体的なフェーズは以下の通りです。Fastlyの後段にCloudFrontを配置し、影響範囲が小さいものから順にCloudFront経由へ切り替えていきました。 1. LP・アセットの切り替え 最初はS3で配信しているLPとアセットの切り替えです。静的コンテンツは影響範囲が限定的で切り戻しもしやすいため、最初の移行対象としました。 切り替えにあたっては全パスを網羅するURLリストを用意してスクリプトで動作確認を行い、移行前後の挙動に差異がないことを確認しながら実施しました。 2. 動的コンテンツ・認証処理の切り替え 次に動的コンテンツの配信や認証処理を担うALBの切り替えです。動的コンテンツの配信や認証処理を担っているため、CDN切り替えによるヘッダーやキャッシュの挙動の変化が配信や認証に影響を与えるリスクがありました。 影響範囲を抑えるためにALBは1台ずつ切り替え、バックエンドチームにも協力してもらい機能リグレッションがないことを確認しながら進めました。 加えて、ダークカナリアリリース(ユーザーには見えない形で一部のトラフィックを新しい構成に流し、本番環境で検証する手法)でCloudFront経由に流し、環境差異による不具合がないかも検証しました。 3. VCLの処理をCloudFront Functionsへ移行 続いてVCLのリダイレクト・リライト処理を移行しました。VCLの処理の中にはリクエスト元の国別判定をした上でリダイレクトをする処理もあり、障害につながりやすい部分であったため、切り戻しやすさを考慮して2段階に分けて対応しました。 動作確認はFastlyで実施していたリダイレクト・リライト処理を網羅的に検証できるスクリプトをWebチームが作成してくれており、そちらを活用し移行前後の挙動に差異がないことを確認しながら進めました。 スクリプトはDenoのテストフレームワークで書かれており、各パスへのリクエストに対してステータスコード・リダイレクト先・レスポンスヘッダーを検証します。さらにHTMLレスポンス内のJS・CSSアセットも正常に取得できるかまで確認しており、移行後の挙動を一通りカバーしています。 また、VCLの移行はオリジンの切り替えと異なり、パスごとに挙動が細かく異なります。そのため追加のスクリプトも作成し、リダイレクト・リライト対象外のパスへの影響がないか、Serverヘッダーでオリジンが意図した経路を通っているかを、1パスずつ地道に確認していきました。 4. DNS切り替え 最後にDNSを切り替えてFastlyからCloudFrontへ完全移行しました。 WEARはDNSにAkamaiを使用しています。今回、AkamaiのChange Listを活用し、ダウンタイムなしで切り替えを実現しました。 CloudFrontのIPアドレスは固定されていないため、FastlyのIPを登録していたAレコードからCloudFrontのドメインを指定するCNAMEへの変更が必要でした。しかしAレコードとCNAMEは共存できないため、削除と追加を別々に適用すると瞬断のリスクがありました。Change ListはDNSレコードの変更をまとめてアトミックに適用できる機能で、これを活用することでダウンタイムなしでの切り替えを可能にしています。切り替えは有事の際に素早く切り戻せるよう、事前にTTLを60秒に下げた上で実施しました。 また、次のセクションで詳しく説明しますが、ブロックやBasic認証等のWAF切り替えもこのタイミングで同時に実施しています。 WAFの移行タイミング WAFはIP・国・ASNなどの判定を、送信元IPまたはX-Forwarded-For(XFF)ヘッダーのIPアドレスを基に行います。 DNS切り替え前はFastlyがリクエストを受け取るため、AWS WAFから見るとアクセス元のIPがFastlyのIPになります。この状態でWAFを先に切り替えると、ブロックルールがFastlyのIPに対して適用され、意図しないブロックや、本来ブロックすべきリクエストが通過してしまうリスクがありました。 XFFを参照することでクライアントIPに基づく判定も可能ですが、以下の理由から採用しませんでした。 Fastlyの判定ロジックが送信元IPを利用しており、仕様を変えたくなかった DNS切り替え前後でXFFの内容が変わるため、切り替えのタイミングで挙動が変わることを避けたかった そのため、DNS切り替えまでの間はFastly側のブロック設定を残し、WAFの切り替えはDNS切り替えと同時に行いました。 なお、事前の動作確認はCloudFrontのFQDNに直接curlでアクセスして行いました。DNS切り替え前はFastlyがエンドポイントのため、CloudFrontのFQDNに対してアクセスする必要があります。CloudFrontはHTTPS接続時にHostヘッダーの値でSSL証明書を選択するため、Hostヘッダーにドメインを指定することで正しく証明書検証が行えます。 CloudFront FunctionsについてもDNS切り替え前はリクエストがFastlyを経由するため、リクエスト元の国別判定で同様の問題がありました。 こちらはWebチームが解決策を検討・実装してくれました。DNS切り替え前の移行期間中に限り、Fastly側でCloudFrontの仕様に準ずるヘッダーを付与しました。CloudFront Functions側でもそのヘッダーを参照することで、正しく地域判定が行える構成にしています。 設計のポイント ここまで移行の手順を紹介しました。次に、移行にあたって設計上特に考慮が必要だったCloudFrontのビヘイビア設計について紹介します。 CloudFrontでは、リクエストのURIパターンに応じて処理方法を定義