TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

974

はじめに こんにちは、ブランドソリューション開発本部でWEAR by ZOZOのiOSアプリの開発を担当している山田( @gamegamega_329 )です。 2024年の5月、WEARはAIを活用したファッションジャンル診断などの新たな機能やコンテンツを導入し 「WEAR by ZOZO」 (以下、WEAR)としてリニューアルしました。私が入社してからすぐにWEARアプリのリニューアルに取り組んできました。 当時、私は業務経験の浅い新卒1年目であり、リニューアルにおいてどのようにチームに貢献できるか不安を抱えていました。しかし、業務経験が未熟な中でも、自分の力で挑戦できるタスクを見極め、それに取り組み解決することでチームに貢献しました。 本記事では、大規模リニューアルプロジェクトに参画する中で私が直面した課題と、その課題を解決するための取り組みをご紹介します。業務に対して不安を感じる新卒iOSエンジニアの参考になれば幸いです。 目次 はじめに 目次 リニューアルプロジェクト概要 プロジェクトの背景と目的 チーム構成 進め方 自身の担当範囲 課題と解決アプローチ 課題1.好みのジャンル傾向のグラフの開発工数が膨らむ スライド提案で仕様調整することで工数削減 実装可能な仕様に落とし込んで工数を削減 結果と学び 課題2.ビルドおよび配布プロセスにおけるエラーに気づきにくい Xcode Preview専用ターゲットのビルド失敗を検知する 配布失敗をSlackbotで通知する 結果と学び 課題3.スプレットシートで管理している職種間のタスクを把握しづらい Slackbotでタスクの締め切りを通知する 結果と学び 最後に リニューアルプロジェクト概要 プロジェクトの背景と目的 このプロジェクトは、 経営戦略 「MORE FASHION × FASHION TECH ~ ワクワクできる『似合う』を届ける ~」を掲げて進められました。WEARでは、ユーザーが自分に「似合う」ファッションコーディネートを見つけられるアプリとして、その価値を提供することを目的としています。 チーム構成 WEARはマトリックス型のチームで構成され、「ホーム」「探す」「メイク」の3つのチームに分かれています。マトリックス型のチームとは、組織内で複数の部門や専門分野のメンバーが集まって構成されるチームのことです。1つのチーム内に、PM、デザイナー、エンジニア(iOS / Android / バックエンド / QA)が所属しています。 私は「探す」チームに所属し、検索を中心とした似合うコーディネートを探せる機能の実装を担当しました。「探す」チームは4名のiOSエンジニアで開発を担当しました。 進め方 プロジェクトが開始してからリリースまでの主な流れは以下の通りです。 仕様のフィジビリティ調査 工数見積もり 仕様 / デザインの調整 設計 / 実装 デザインレビュー / バグ修正 リリース 自身の担当範囲 私が主に担当した範囲は以下の通りです。 探すタブのトップ画面 :ユーザーが探すタブを開いたときに表示されるトップ画面の設計と実装 好みのジャンル傾向のグラフ :ジャンルごとの傾向を視覚的に表現するグラフの作成 ジャンルで絞り込む機能 :ユーザーがジャンルで絞り込み検索をするためのコンポーネントの実装 CI/CDの運用改善やマトリックスチームの効率化 チームメンバーに難易度の高い機能に挑戦したいとお願いしたところ、「好みのジャンル傾向」のグラフを担当することになりました。好みのジャンルとは、好みに近いスタイルを見つけるために12種類に分類されたファッションのカテゴリのことです。例えば、ラフ、きれいめ、モードなどがあります。グラフのイメージは以下の通りです。 課題と解決アプローチ リニューアルプロジェクトが進む中で、私が直面した課題とその解決アプローチについてご紹介します。 課題1. 好みのジャンル傾向のグラフの開発工数が膨らむ 課題2. ビルドおよび配布プロセスにおけるエラーに気づきにくい 課題3. スプレットシートで管理している職種間のタスクを把握しづらい 課題1.好みのジャンル傾向のグラフの開発工数が膨らむ グラフの実装は、見積もりの段階で大きな工数がかかりました。要因は以下の通りです。 その1. ジャンルの割合を変更するデザインの難易度が高い その2. 12種類のラベルの位置の選定方法の難しさ リリース後には大型プロモーションを予定しており、関係各部署と足並みを揃える必要があったため、リリースまでの時間が限られていました。この状況を踏まえ、リリース日に全ての実装を完了させるには、仕様やデザインを調整し、工数を削減する必要がありました。 スライド提案で仕様調整することで工数削減 「その1. ジャンルの割合を変更するデザインの難易度が高い」に対して、開発の観点から工数がかかる要因と代替案をスライドでビジュアル化し、デザイナーに提案しました。デザイナーに別のデザインを考えてもらうためには、なぜ工数がかかるのかを理解してもらう必要がありました。しかし、文字だけで伝えるのは専門外の人には難しく、納得してもらうのも困難だと思いました。 そこで、図や表を使ったスライドを作成し、ビジュアルで伝えることで理解してもらうようにしました。工数削減のためにエンジニアの観点から提案し、どのようなコンポーネントであれば工数を削減できるか、デザイナーが判断しやすくなるような材料を提供しました。 仕様変更により初回リリースには含まれませんでしたが、実際に使用したスライドは以下の通りです。 実装可能な仕様に落とし込んで工数を削減 「その2. 12種類のラベルの位置の選定方法の難しさ」に対して、現実的な工数で実装可能な仕様に落とし込み、工数を削減しました。デザイン要件と現実的な工数のバランスの取れた仕様にするまでに、多くの試行錯誤がありました。ジャンルを示すラベルに対し、提示された基本的なデザイン要件は、以下の内容でした。 ジャンルは12種類 1行と2行の2パターン 円とラベルの間は適度なスペースを保つ この基本要件を保ちつつ、複数のジャンルが選択された状態では、以下の手法Aのような規則でのコンポーネントの配置が求められました。 手法A:ラベルの高さと円グラフのサイズを考慮して赤枠のサイズを調整する 一方で私は、保守性を重視するためには、手法Bでの設計をするのが望ましいと考えました。 手法B:ラベルを配置してぶつかったところで少しずらす デザイン要件が複雑であるため、手法Aや手法Bを採用しても、デザイン通りに実装することが難しい状況でした。納期を優先するために、チームリーダーと相談しながらデザイナーに別の手法を提案して、デザイン要件を擦り合わせることを考えました。 新たに考えた手法Cのイメージは以下の通りです。 手法Cは、4つの領域に分割し、白円の中心座標に対し、各領域のテキスト位置を決定するような方法です。 それぞれの手法を比較した結果、以下の通りです。 手法 実装コストが低いか? デザイン要件を満たすか? 変更に柔軟か? A × ◯ × B △ △ ◯ C ◯ ◯ △ 最終的に、納期を厳守しながら、デザイン要件も満たせる手法Cを選択し、実装しました。 結果と学び 結果として、ユーザ体験を損なうことなく現実的な仕様に落とし込み、無事にリリースできました。また、リリース後も致命的なクラッシュや表示のズレは発生せず、安全に実装できました。 保守性の観点からいくつかの課題があります。例えば、ジャンルの種類が増えた場合や多言語対応する際、文字が重なってしまう可能性があります。この点については、現在も設計の見直しとリファクタリングを進めて対応しています。 この経験を通じて、 優先すべきことを明確 にし、 トレードオフを理解しながら最適な落とし所を見つけること を学びました。プロジェクトの状況次第ですが、仕様通りに実装することだけにこだわらず、限られた時間の中で現実的でバランスの取れた解決策を見つけることも重要だと思いました。 課題2.ビルドおよび配布プロセスにおけるエラーに気づきにくい ビルドおよび配布プロセスにおけるエラーに気づきにくいことで2つの問題がありました。 1つ目. 他の開発者がXcode Previewを表示できない 2つ目. デザイナーとエンジニアに余分な確認作業が発生 1つ目に関して、WEAR iOSチームはビルド時間短縮のためXcode Preview専用ターゲットを作成しましたが、ファイルの追加忘れで他の開発者がXcode Previewを表示できない問題が多発していました。 2つ目に関して、デザイナー確認のために毎日ビルドを配布していましたが、配布が失敗して変更が反映されていないことに気づかないことがありました。 Xcode Preview専用ターゲットのビルド失敗を検知する 「1つ目. 他の開発者がXcode Previewを表示できない」に対して、CI/CDツールとして利用しているBitriseのワークフローを改善しました。 プルリクエストを親ブランチへマージする前、CI環境でXcode Previewターゲットをビルドし、失敗を事前に検知させました。 Xcode Previewターゲットをビルドするために、Bitriseが提供している「Xcode Build for Simulator」のステップ(特定のタスクを実行するためのスクリプト)を追加しました。bitrise.ymlの設定は以下の通りです。 // bitrise.yml - xcode-build-for-simulator@2 : inputs : - scheme : WEARPreview この設定により、プルリクエストを親ブランチへマージする前にビルドの失敗を検知できるようになりました。 配布失敗をSlackbotで通知する 「2つ目. デザイナーとエンジニアに余分な確認作業が発生」に対して、配布が失敗した時にBotを活用してSlackに通知を送りました。 Slack上で通知するために、Bitriseの「Send a Slack message」ステップを追加しました。この設定と一緒に、SlackのWebhook、APIトークン、およびSlackボットの設定が必要です。bitrise.ymlの設定は以下の通りです。 // bitrise.yml - slack@4 : title : Slack Notification failed deploy is_always_run : true run_if : ".IsBuildFailed" inputs : - webhook_url : "$SAMPLE_WEBHOOK_URL" 配布が失敗した時の検知は、 is_always_run: true 、 run_if: ".IsBuildFailed" を設定に加えることでビルドが失敗した時にステップを実行します。詳細は Running a Step only if the build failed をご確認ください。 Slackで通知された時のイメージは、以下の通りです。 結果と学び 結果として、チームの作業負担を減らせました。また、今回の解決策が他のチームにも役立てることができました。 今回の経験を通じて、 他のチームメンバーが開発しやすくなるように工夫することが重要 であると学びました。作業量自体は比較的少なかったものの、Swiftでのコーディングだけでなく、開発プロセス全体の改善にも取り組むことがチームに貢献するためには大切だと感じました。 課題3.スプレットシートで管理している職種間のタスクを把握しづらい 基本的にエンジニアはJiraを使ってタスクを管理していますが、PM、デザイナー、QAなど複数の職種が利用することを考慮し、スプレッドシートで主に仕様調整に関するタスクを管理していました。 しかし、締め切り、起票者、担当者、完了状況など多くの情報が混在していたため、タスクの把握が難しい状況でした。起票者と担当者を決め、締め切りまでに対応方針を確定する流れで進めていましたが、タスク量の増加と職種間のやり取りの多さから、タスクの把握がさらに難しくなっていました。 Slackbotでタスクの締め切りを通知する 他のチームで運用されていた締め切り通知Botを参考に、タスクの締め切りを通知するSlackbotを追加しました。締め切り当日のタスクをSlackbotを活用して通知します。 投稿の内容には、対応期日やタスクの内容、起票者、担当者を含めました。完了状況は週1回の全体ミーティングで確認するようにしました。スプレットシートで管理していたことから、Google App Scriptで実装しました。 結果と学び この取り組みの結果、タスクの締め切りを意識しやすくなり、タスクが遅れそうな場合は各自で調整するようになりました。チーム内で振り返りを行った際には、他のメンバーから感謝の言葉をもらいました。 この経験を通じて、 他チームのアプローチを参考にして自チームに適用すること を学びました。今回、私は初めてGoogle App Scriptを触りました。他のチームの参考コードを修正して自チームに適用することで、短時間で実装できるので非常に有用だと感じました。 最後に 本記事では、リニューアルにおけるいくつかの課題解決について紹介しました。業務経験が未熟な中でも、自分の力で挑戦できるタスクを見極め、それに取り組んで解決することで、微力ながらもチームに貢献しました。 未経験の実装や技術に挑戦できたのは、チームメンバーのサポートのおかげです。また、社内のiOSメンバーからも多くの協力と支援を受け、本当に感謝しています。ZOZOの人々の良さを改めて実感しました。業務に対して不安を感じる新卒iOSエンジニアの参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは。ZOZOTOWN開発本部フロントエンドの菊地( @hiro0218 )です。 現在、ZOZOTOWNではWebフロントエンド技術のリプレイスプロジェクトが進行しています。以前の記事ではCSS in JSの技術選定をした際の背景や課題について紹介しました。 techblog.zozo.com その後、「 ZOZO Tech Meetup - Web フロントエンド 」でおよそ1年後の状況を簡単に共有させて頂きました。 speakerdeck.com 今回はZOZO Tech Meetupでお話した内容に加えて、CSS in JS導入から2年後の現状を改めて紹介したいと思います。 CSS in JS導入後の運用状況 ZOZOTOWNの開発体制は、Webフロントエンドだけでも5つのチームが存在し、さらに外部の業務委託メンバーも加えると、開発に携わるメンバーは執筆時点でのべ50名を超えています。これだけの多人数で開発をすると、開発環境のオンボーディングのようなイニシャルコストや日々の開発に関する問い合わせ対応は膨大なコストがかかってしまうため、以下のような取り組みを実施しています。 開発用ドキュメントの整備 Stylelintによるコードスタイルの統一 コードの記述方法に迷いが生じないような仕組み 単純に開発ドキュメントだけ整備するのでは伝達や統制に限界がありますが、細かい指針や推奨事項は、仕組み化していくことで開発者のスキルや経験に依存しない高品質な水準の開発を担保しています。また、イニシャルコストの低減だけではなく、コードレビューの効率化やメンテナンス性の向上も実現できています。 これらの中から、いくつかCSS in JSの運用に関わるものを紹介します。 styledの記法 ZOZOTOWNではCSS in JSのライブラリとして Emotion を採用しています。 Emotionでstyledの記法は「タグ付きテンプレートリテラル記法 (以下、テンプレートリテラル記法) 」と「オブジェクトスタイル記法」の2つの記法を利用できます。ZOZOTOWNのフロントエンド開発においては 「テンプレートリテラル記法」を推奨 しています。 それぞれの記法については、以下の通りです。 // テンプレートリテラル記法 const Section = styled.section ` background-color: #333; color: #fff; ` ; // オブジェクトスタイル記法 const Section = styled . section ({ backgroundColor : "#333" , color : "#fff" , }) ; 「テンプレートリテラル記法」を推奨した理由としては以下の通りです。 従来の CSS(Sass)と同様の記述方法ができる 導入当初、CSS in JSを初めて利用するメンバーも多く、書き慣れた従来のCSSの記述にすることで参入コストを少しでも下げたかった オブジェクトスタイル記法に比べて、CSSに慣れ親しんだメンバーにとっては直感的であり可読性や保守性が高い リプレイス前のCSSの実装を移行しやすい利点がある スタイルのネストの記述がしやすい 親子関係のスタイルや複雑な構造を表現するためのネスト構造 (例: &:hover ) を直接記述できる 複雑なスタイリングの記述がしやすい メディアクエリや擬似クラス、擬似要素など、CSSの高度な機能をそのまま利用できる CSSの継承やカスケードのルールをそのまま適用できるため、スタイルの一貫性を保ちやすい リプレイス前の環境と共通設定の Stylelint のルールが利用でき一貫性を保てる properties-order 系のプラグインなどオブジェクトスタイル記法だと一部動かないものがあった Visual Studio Codeの拡張機能をレコメンドする Visual Studio Code を利用している開発者が多いため、 .vscode/extensions.json ファイルをリポジトリ内に用意して、拡張機能をレコメンドしています。 例えばテンプレートリテラル記法は、エディタが標準ではスタイルとしてシンタックスハイライトをサポートしていないため、 vscode-styled-components のような拡張を利用します。この拡張は styled-components の拡張機能として提供されていますが、同様のシンタックスを有しているEmotionでも問題なく利用できます。 .vscode/extensions.json へ以下のように記述をするだけで拡張機能を開発者へレコメンドできます。 # . vscode / extensions . json { " recommendations ": [ " styled-components.vscode-styled-components " ] } 情報格差(拡張機能を知っているか)で開発効率に差を生まないようにしたい意図があります。 必要な情報はコンテキストに含める ThemeProvider のコンテキストには、端末の判定情報やSassの @mixin や @function に相当する関数を渡しています。各コンポーネントごとで関数を呼び出さずにstyled内からアクセスできるようにしています。 以下は、 font-weight に指定する値を function 経由で取得している例です。 const Text = styled.span ` font-weight: ${ ( { theme } ) => theme.function.fontWeight( "bold" ) } ; ` ; // iOS の Hiragino Sans のウェイトには W3, W6, W8 が含まれるが、`font-weight: bold`を指定すると 700 (W7) と同義になり意図よりも太い表示になる。 // 上記の function を利用すると iOS の場合は`font-wight: 600`の指定になり、それ以外の端末は`font-weight: bold`になる。 スタイリングへの関心事を ThemeProvider に集めることで実装者の迷いを減らしています。 Linterで誘導する スタイル向けのLinterとして Stylelint を導入しています。 Stylelintはリプレイス以前から導入しており、リプレイス後の環境のルールセットは基本的にそのままリプレイス以前のものを移行しています。 独自のルールとして、先述の「 font-weight に bold を直接記述しない」というルールがあります。このルールは、開発に参加したてのメンバーだと気付きづらく、またレビューでも指摘が漏れたり、テスト時に発覚してしまったりするといったケースも考えられます。これを事前に気付けるように declaration-property-value-disallowed-list を利用して、 bold の直接指定をルール上は許可しないようにしています。 // stylelint.config.js { rules : { "declaration-property-value-disallowed-list" : { "font-weight" : "bold" , } , } , } 社内の開発ドキュメントでも bold 指定は function を経由するようにと記載していますが、読み飛ばしや解釈違いもあるため、Linterによって二重で防ぐような処置をしています。 開発者の満足度や意見 開発メンバーに対して実施したアンケートの結果と、直近で行ったヒアリングの内容を共有します。 過去に実施したアンケート結果 導入から1年近く経ったZOZO Tech Meetupに際して、「CSS in JSの使い心地」に関するアンケートを実施しました。 対象 : リプレイス後の環境でCSS in JSを利用して開発を実施した部署内のフロントエンドエンジニア。 設問 (一部抜粋): CSS in JSに変わったことで、作業効率はどの程度変わりましたか? CSS in JS導入による全体的な満足度を評価してください CSS in JSはZOZOTOWNに適していると思いますか? CSS in JSを利用して新規に実装されたスタイルの品質はどうですか? 社内ドキュメントやインターネット上の情報を利用して開発が滞りなく進められていますか? CSS in JS(Emotion)のAPIの使い勝手はどうですか? その他、ご意見ご感想があれば記入してください 1. CSS in JSに変わったことで、作業効率はどの程度変わりましたか? 大いに向上した: 28.6% やや向上した: 57.1% 変わらない: 14.3% やや低下した: 0% 大いに低下した: 0% 2. CSS in JS導入による全体的な満足度を評価してください 非常に満足: 14.3% やや満足: 71.4% 普通: 14.3% やや不満: 0% 非常に不満: 0% 3. CSS in JSはZOZOTOWNに適していると思いますか? 非常に適している: 28.6% やや適している: 28.6% 普通: 42.9% やや適していない: 0% 全く適していない: 0% 4. CSS in JSを利用して新規に実装されたスタイルの品質はどうですか? 非常に高い: 14.3% やや高い: 57.1% 普通: 28.6% やや低い: 0% 非常に低い: 0% 5. 社内ドキュメントやインターネット上の情報を利用して開発が滞りなく進められていますか? 非常に満足: 42.9% やや満足: 28.6% 普通: 28.6% やや不満: 0% 非常に不満: 0% 6. CSS in JS(Emotion)の API の使い勝手はどうですか? 非常に使いやすい: 14.3% やや使いやすい: 71.4% 普通: 14.3% やや使いづらい: 0% 非常に使いづらい: 0% 7. その他、ご意見ご感想があれば記入してください リプレイス以前の環境でスタイルを新規で書く場合は、webpack用のentryファイルを用意する必要があり、それに比べて初期設定が簡単になりスタイルの記述までの作業が減った CSS in JS(Emotion)の使い勝手は非常に良い CSS in JSに不慣れなメンバーでも参入が容易だった CSS in JSを利用することでクラス名管理の問題などが解消された (Emotionの)日本語解説が少ないため、高度な利用例 (例:ThemeProviderにおけるThemeのマージ) に関する情報が不足していると感じた。今後はそのような知見を蓄積しドキュメントを整備する必要がある アンケートの総括 全体的にポジティブな回答が得られました。 CSS in JSライブラリ(Emotion)に対する不満が見られないことから、以下のような理由が考えられました。 初めて触れるメンバーでも参入しやすかった(簡単) ライブラリの機能性に足りない点や問題点が少なかった ドキュメントや情報が不足していると感じるメンバーもいるものの、全体的には満足度が高い結果だったと言えます。しかし、今よりも対象になる開発メンバーが少なかった事もあり、当然ながら現状とは状況や結果が異なっている可能性があります。 直近で実施したヒアリング結果 先のアンケートから時間も経過しており、関係するメンバーも増えたため、直近で改めて使用感に関するヒアリングを実施しました。 以下のような回答を得られました。 CSS設計を考える手間が減った リプレイス以前の環境では、CSSのクラス名の命名ルールにMindBEMding(BEMの派生)を採用していました 1 。この方法は堅牢な命名を可能にする一方で、適切なクラス名を考える手間も必要でした。CSS in JSの導入により、この手間が大幅に軽減され、コンポーネントの命名を集中して考えられるようになりました。 techblog.zozo.com CSSの変更が意図しない箇所やページにまで影響を与えてしまうことがあり、そのリスクを軽減するために命名規則のルールを設けていました。CSS in JSによって自動的にCSSのスコープが制限され、影響範囲も限られることで、それらの問題が発生しにくくなりました。 コードの可読性が向上した コンポーネント定義とスタイル定義を同じファイル内に共存できます。これによって、コードが見やすくなり、コード間の移動が減り実装スピードも向上しました。また、レビューも同じ観点でしやすくなりました。 複数の状態を持つコンポーネントの場合でも、コードが整理しやすく、読みやすさが向上したと感じています。 JavaScriptとCSSの統合性が向上した CSS in JSの導入によって、JavaScriptとCSSで共通定義を利用できるようになりました。これにより、細かい設定の一貫性を保つことができました (例:ヘッダーの高さをスタイリング用とスクロール時の位置操作用で同じ値を利用できる) 。 スタイルを操作するロジックがJavaScriptで簡潔に記述できるようになり(動的なスタイリングの実装)、複雑なデザイン要件にも対応できるようになりました。 CSS in JSへの課題感 開発者からはポジティブな意見が多く寄せられていますが、現状維持を良しとしているわけではなく、課題感も持って動向を注視しています。 パフォーマンスの懸念 ランタイムCSS in JSを利用していることから、パフォーマンスに関する課題感を持っています。 しかしながら、現状ではパフォーマンス上の問題が顕在化したことはありません。これは、ZOZOTOWN全体におけるリプレイス済みの機能(CSS in JSを利用している箇所)の割合がまだ低いため、問題が表面化していないという可能性も考えられます。 Zero-runtime CSS in JSへのライブラリのリプレイスについては、現時点で具体的な移行計画はありません。技術選定時の評価では、Zero-runtime方式はZOZOTOWNの開発要件を満たせないと判断しており、要件を満たすライブラリが登場しない限りは採用できないと考えています 2 。 今後、負荷テストなども実施しながら継続的にパフォーマンスを注視していきたいと思います。 可読性の課題 複雑なUIを実装しているとstyledの中で条件分岐が多くなり可読性を下げてしまうケースがあります。 以下のように簡単な分岐でも、条件分岐の記述方法がいくつか考えられます。どのような記述方法にするのが良いのか議論がありました 3 。 // (1) const Div = styled.div ` height: ${ ( { theme } ) => theme.device.isPc ? "55px" : "calc(55 / 375 * 100vw)" } ; ` ; // (2) const Div = styled.div ` ${ ( { theme } ) => { return theme.device.isPc ? css ` height: 55px; ` : css ` height: calc(55 / 375 * 100vw); ` ; } } ` ; 上記の記述方法としては、どちらも正しくコーディング規約などで記述方法を制限するようなことはしていません。意見としては以下のようなものが出ており、このような観点も記述の判断材料の1つとすると良いという結論に至りました。 (1): 他のプロパティが併記されている際にStylelintのソート( properties-order )が有効に機能する (2): PCとSPで大きく定義が異なる場合は可読性が高い 記述方法に厳格な制限を設けていないため、コードの統一性が多少失われることもあります。しかし、過度に複雑な記述 (例:多重の条件分岐や特殊な関数の利用) は避けるようにしています。これは、将来的なライブラリのリプレイス時の困難を防ぐためです。そのため、コードレビューやメンバー間での意見交換を通じて、適切な記述方法の意思統一を図っています。 まとめ 本投稿では、CSS in JS導入後の運用について紹介しました。CSS in JSの導入によって、可読性やメンテナンス性の向上といった多くのメリットがもたらされています。 技術選定では「どのようなスキルセットの開発メンバーが関与するか」「選定ライブラリが参入障壁にならないか」を意識していたため、参入障壁になっていないことがアンケートなどの結果から確認でき安心しました。一方で、Runtime CSS in JSのパフォーマンスに関する課題などは存在しているため、今後も技術的な観点で動向を注視していく必要はあると考えています。 同様の技術選定に悩む開発者の参考になれば幸いです。 最後に ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com ITCSSを採用して共同開発しやすいCSS設計をZOZOTOWNに導入した話 - ITCSSと命名規則(MindBEMding + 接頭辞)の組み合わせで実現できること ↩ ZOZOTOWN Web フロントエンドリプレイスにおける CSS in JS の技術選定で Emotion を選定した話 - LinariaZero-runtime-CSS-in-JS を検証する ↩ ZOZOTOWN開発本部のフロントエンドエンジニア有志で「スタイル分科会」を発足しており、スタイル周りの技術共有や各チームからの相談を受ける場として活動している。 ↩
アバター
はじめに こんにちは。技術本部ECプラットフォーム部マイグレーションブロックの小原です。 本記事では、Spring Bootの ApplicationRunner インタフェースを活用したバッチアプリケーション(CLIアプリケーション)の構築方法について解説します。 バッチ処理の実装において、SpringフレームワークはSpring Batchという強力なツールを提供しています。しかし、比較的単純なバッチ処理の場合、Spring Batchの使用はオーバーエンジニアリングとなる可能性があります。 そこで、軽量なアプローチとして ApplicationRunner を利用した実装方法を説明します。この方法は、シンプルなバッチ処理に適しており、Spring Bootの機能を活用しつつ、必要最小限の実装で効率的なバッチアプリケーションを構築できます。 なお、本記事は下記の環境にて検証しました。 Java 21 (Eclipse Temurin) Spring Boot 3.3.1 目次 はじめに 目次 ApplicationRunnerを選択した背景 ApplicationRunnerとCommandLineRunnerの比較 ApplicationRunnerを利用したバッチアプリケーションの実装 エントリポイントのコード 依存関係にspring-boot-starter-webを含まない場合 依存関係にspring-boot-starter-webを含む場合 バッチを起動するコマンド ExitCodeGeneratorを利用した終了コードの制御 テスト実装 @SpringBootTestを利用する方法 @ContextConfigurationを利用する方法 まとめ さいごに ApplicationRunner を選択した背景 今回のバッチアプリケーションにてSpring Batchではなく ApplicationRunner を選択した主な理由は以下の通りです。 マイクロサービスアーキテクチャの採用 : バッチの処理対象となるドメインではマイクロサービスアーキテクチャを採用しており、データ操作のためのAPIが既に存在していました。 インフラ構成のシンプル化 : データベース操作はマイクロサービスAPIの責務とし、バッチアプリケーション自体はデータベースを直接操作しない設計としました。これにより、バッチアプリケーションのインフラ構成をシンプルに保つことができました。 Spring Batchのオーバースペック : 上記の理由から、バッチアプリケーションはデータベースを直接操作することがありません。そのため、Spring Batchは学習曲線が高く、機能的にもオーバースペックであり、導入するメリットが薄いと判断しました。 軽量性 : ApplicationRunner を使用することで、必要最小限の機能を持つ軽量なバッチアプリケーションを構築できます。 これらの背景を踏まえ、 ApplicationRunner を活用した軽量バッチアプリケーションの構築方法を以下で詳しく解説します。 ApplicationRunner と CommandLineRunner の比較 Spring Bootには、アプリケーション起動時に処理を実行するためのインタフェースとして ApplicationRunner と CommandLineRunner が用意されています。以下に詳細な比較を示します。 特徴 ApplicationRunner CommandLineRunner メソッド run(ApplicationArguments args) run(String... args) 引数の処理 Spring Bootが解析済みの引数を提供 独自に引数を解析する必要がある オプション引数の扱い --key=value 形式を簡単に扱える 独自でパースが必要 非オプション引数の扱い getNonOptionArgs() で取得可能 配列の要素として直接アクセス 実行順序の制御 @Order アノテーションで制御可能 @Order アノテーションで制御可能 ここで、オプション引数とは --key=value の形式で指定される引数を指し、非オプション引数とはそれ以外の単純な値として渡される引数を指します。 例えば、 java -jar app.jar --input=data file1 file2 というコマンドでは、以下の通りとなります。 オプション引数 : --input=data 非オプション引数 : file1 と file2 本記事では、引数の扱いやすさから ApplicationRunner を選択しています。 ApplicationRunner および CommandLineRunner の詳細は、以下のJavadocを参照してください。 Interface ApplicationRunnerのドキュメント Interface CommandLineRunnerのドキュメント ApplicationRunner を利用したバッチアプリケーションの実装 以下に、 ApplicationRunner を利用したバッチアプリケーションの実装例を示します。 import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; @Component public class BatchApplicationRunner implements ApplicationRunner { private final RestClient restClient; public BatchApplicationRunner(RestClient.Builder restClientBuilder) { this .restClient = restClientBuilder.baseUrl( "http://microservice/api" ).build(); } @Override public void run(ApplicationArguments args) throws Exception { // コマンドライン引数から特定のキーの値を取得 String inputData = args.getOptionValues( "input" ).get( 0 ); System.out.println( "Input data: " + inputData); // マイクロサービスのAPIを呼び出してデータ操作を行う String response = restClient.get() .uri( "/data?input=" + inputData) .retrieve() .body(String. class ); System.out.println( "API Response: " + response); // バッチ処理のロジックを実装 performBatchProcessing(response); } private void performBatchProcessing(String data) { // バッチ処理の実装 System.out.println( "Processing data: " + data); } } エントリポイントのコード バッチアプリケーションのエントリポイントとなるクラスを、依存関係に spring-boot-starter-web を含まない場合と含む場合で分けて示します。 依存関係に spring-boot-starter-web を含まない場合 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BatchApplication { public static void main(String[] args) { SpringApplication.run(BatchApplication. class , args); } } 依存関係に spring-boot-starter-web を含む場合 import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @SpringBootApplication public class BatchApplication { public static void main(String[] args) { new SpringApplicationBuilder(BatchApplication. class ) .web(WebApplicationType.NONE) .run(args); } } 上記のコードでは、 SpringApplicationBuilder を使用して明示的にWebアプリケーションタイプを NONE に設定しています。これは、 spring-boot-starter-web が依存関係に含まれていても、Webサーバーを起動させないようにするためです。 この方法により、 RestClient や RestTemplate などのWeb関連のユーティリティを使用しつつ、Webサーバーを起動せずにバッチ処理を実行できます。 バッチを起動するコマンド バッチアプリケーションを起動する際のコマンドは以下のようになります。 java -jar app.jar --input=somedata このコマンドを実行すると、 BatchApplicationRunner の run メソッドが呼び出され、指定したデータを使ってバッチ処理が実行されます。 ExitCodeGenerator を利用した終了コードの制御 バッチアプリケーションが任意の終了コードを返すために、 ExitCodeGenerator インタフェースを実行できます。以下に例を示します。 import org.springframework.boot.ExitCodeGenerator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class BatchApplication { public static void main(String[] args) { System.exit(SpringApplication.exit(SpringApplication.run(BatchApplication. class , args))); } @Bean public ExitCodeGenerator exitCodeGenerator() { return () -> { // ここで適切な終了コードを返す // 例: 0は成功、1は一般的なエラー、2は特定のエラーなど return 0 ; }; } } この例では、 ExitCodeGenerator を実装したBeanを定義しています。 exitCodeGenerator メソッド内で、バッチ処理の結果に応じて適切な終了コードを返すロジックを実装できます。 テスト実装 ApplicationRunner を利用したバッチアプリケーションのテストには、主に2つのアプローチがあります。それぞれの特徴と使用方法を説明します。 @SpringBootTest を利用する方法 @SpringBootTest アノテーションを使用すると、 @SpringBootApplication アノテーションが付与された main メソッドのエントリポイントが実行されます。 @SpringBootTest を利用して、 main に引数を渡す場合のサンプルコードは次のとおりです。なお、テストコードは Spock を利用していますが、JUnitにおいても同様です。 import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.ApplicationArguments import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification @SpringBootTest(args = [ "--input=somedata" ]) class BatchApplicationSpec extends Specification { @Autowired ApplicationArguments applicationArguments def "バッチ処理のテスト" () { expect : // 期待される結果を検証 applicationArguments.getOptionValues( "input" ) == [ "somedata" ] } } このアプローチの主なポイントは以下の通りです。 @SpringBootTest アノテーションの args パラメータを使用して、コマンドライン引数をテストに渡すことができます。上記の例では、 --input=somedata という引数を指定しています。 ApplicationArguments インタフェースを @Autowired でインジェクションすることで、テストメソッド内で渡されたコマンドライン引数を取得し検証できます。 applicationArguments.getOptionValues("input") を使用して、特定のオプション引数の値を取得できます。この方法は、 ApplicationRunner の実装でコマンドライン引数を処理する方法と同じです。 アプリケーション全体のコンテキストが起動するため、実際の動作環境に近い状態でテストできます。 ただし、テスト実行時にバッチ処理が自動的に起動してしまうため、この挙動が問題となる場合は @ContextConfiguration を利用することで解消できます。 @ContextConfiguration を利用する方法 @SpringBootTest を使用すると、 @SpringBootApplication のエントリポイントが実行されてバッチ処理が自動的に起動してしまいます。 @ContextConfiguration アノテーションと適切なコンポーネントスキャンによりバッチ処理の自動起動を防ぎ、より細かいテスト制御が可能になります。 import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer import org.springframework.test.context.ContextConfiguration import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.ComponentScan import spock.lang.Specification @ContextConfiguration( classes = [TestConfig], initializers = [ConfigDataApplicationContextInitializer] ) class BatchApplicationSpec extends Specification { @TestConfiguration @ComponentScan(basePackages = [ "com.example.batch" ], excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [BatchApplication])) static class TestConfig { // 必要であればテストに必要な設定を追加する } def "バッチ処理のテスト" () { expect : // 期待される結果を検証 } } このアプローチの主なポイントは以下の通りです。 TestConfig クラスで @ComponentScan アノテーションを使用し、バッチアプリケーションのコンポーネントをスキャンします。ただし、バッチのエントリーポイント( BatchApplication )を除外します。 @ContextConfiguration アノテーションにおいて、 TestConfig と ConfigDataApplicationContextInitializer を指定します。 Spring Bootの ApplicationContext をカスタム設定で初期化し、バッチ処理の自動起動を防ぎます。 ConfigDataApplicationContextInitializer を使用することで、 application.properties や application.yml の設定を読み込めます。 @SpringBootTest を利用した場合と同様の挙動です。 まとめ ApplicationRunner を利用することで、Spring Batchを使用せずに軽量で柔軟なバッチアプリケーションを構築できました。この方法は以下のような場合に適しています。 シンプルなバッチ処理 マイクロサービスアーキテクチャとの統合 データベースを直接参照しない処理 しかしながら、上記の状況に当てはまらないケースにおいてはSpring Batchの導入を検討することも視野に入れてください。プロダクトの要件に応じて適切なアプローチを選択しましょう。 さいごに ZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 ZOZOTOWNリプレイスでは、段階的にシステムを置き換えるというアプローチによってマイクロサービス化を進めています。第3回は、マイクロサービス化の要となったAPI Gatewayの自社開発の話を中心に紹介します。 目次 はじめに 目次 はじめに API Gateway自社開発の動機 マイクロサービス化に向けたアプローチ API Gatewayの自社開発を決めた理由 開発の柔軟性 動作の軽量さを重視した技術選定 ZOZO API Gatewayが担う機能 ルーティング ユーザー認証 クライアント認証 トレースIDの発行 Istio導入以前に必要だった機能 リトライ リクエストタイムアウト 加重ルーティング ZOZO API Gatewayの運用 Istioの導入 ZOZO API GatewayとIstioの責務の整理と機能分担 監視 ZOZO API Gateway本番稼働開始から3年を経て ストラングラーフィグパターンの功績 自社開発のコストメリット まとめ はじめに ZOZOTOWNは2004年から運営を開始し、オンプレミス環境で動くモノリシックなシステムとして10年以上の間アーキテクチャを変えずに拡大してきました。ビジネスが順調に成長する一方で、古いシステムはスケーラビリティの限界、モノリシックなシステムの保守コストの増大、使用技術の陳腐化など多くの課題に直面していました。それらの課題を抜本的に解決すべく、ZOZOでは2017年からZOZOTOWNのマイクロサービス化を進めています。 この記事は、シリーズ連載の第3回として、ZOZOTOWNにおける段階的なマイクロサービス化に向けたアプローチと、その戦略の要となるZOZO API Gatewayを自社開発した話をします。一般的なマイクロサービス化に関する説明は割愛します。 前提として、ZOZOTOWNの旧システムはオンプレミス環境、移行先の新システムはクラウド環境にあります。また、本記事において「ZOZO API Gateway」という表現は、ZOZOで自社開発しているシステムを指します。Amazon Web Servicesなど他社のプロダクトにAPI Gatewayという名称が含まれるプロダクトについては、異なるシステムもしくはサービスであることがわかるよう、正式名称で記載します。 API Gateway自社開発の動機 マイクロサービス化に向けたアプローチ ZOZOTOWNのマイクロサービス化のためのアプローチとして、ZOZOではストラングラーフィグパターンを採用することを決めました。ストラングラーフィグパターンとは、リプレイス対象のシステムの機能を段階的に新しいマイクロサービスに置き換えていき、すべての機能を置き換え最終的に移行元のシステムを停止する戦略です。 ストラングラーフィグパターンには、フロントエンドにインターフェースを提供し、バックエンドの状態を隠蔽するファサードが必要です。ファサードが各マイクロサービスへの動的なルーティング機能を持ち、リクエストの送信先を徐々に新システムに切り替えていくことで、段階的なシステム移行を実現します。ZOZOTOWNの規模を考えると、マイクロサービスに移行して完全に旧システムを停止できるまでに、数年もしくはそれ以上の時間が必要と考えられました。その間に断続的に行われる置き換え作業をシームレスに実現するためのファサードとして、ZOZO API Gatewayを開発することを決定しました。リプレイス初期のシステム構成を簡略化すると、図1のようになります。 図1 ストラングラーフィグパターンによる旧システムから新システムへの処理の切り替え また、ZOZO API Gatewayはファサードであると同時に、API Gatewayパターンで定義されるAPI Gatewayの役割の一部も担います。フロントエンドに単一エントリポイントを提供することに加え、バックエンドに対してサービス横断的な機能も提供します。API GatewayパターンではAPI GatewayはBFF(Backends for Frontends)として振る舞う前提で語られることがありますが、ZOZOではそれらを別のシステムとして開発しています。BFFは複数のマイクロサービスからのレスポンスの集約や、ビジネスロジックに関するサービス横断的な機能の実現を担当しており、連載第6回で紹介予定です。 リプレイスが完了した時点でのシステム構成は図2のようになることを想定しています。 図2 旧システムからの切り替え後の理想的なシステム構成 API Gatewayの自社開発を決めた理由 ZOZOでは、API Gatewayをスクラッチで開発することを選択しました。自社開発という意思決定に至った決定的なメリットとして、開発の柔軟性と、動作の軽量さを重視した技術選定ができたことが挙げられます。 開発の柔軟性 マイクロサービス化を決定した当初、巨大化していたZOZOTOWNの機能や仕様をすべて洗い出すことは困難でした。そのため、リプレイスの具体的な進め方やサービス横断的に必要となる機能の要件が固まりきっていない状態でリプレイスプロジェクトをスタートせざるを得ませんでした。API Gatewayの仕様追加・変更に手間がかかる状態だと、バックエンドの開発を進められない状況を作り出し、API Gatewayがプロジェクト全体のボトルネックになるリスクがありました。 そこで、開発をすばやく柔軟に進められる状態にしておくために、自社開発が妥当と判断しました。既存のOSSやAPI Gatewayの機能を提供するクラウドサービスの使用も検討したものの、機能拡張のハードルの高さから開発スピードの低下が懸念されました。実際にプロジェクト開始後には、技術スタックやアーキテクチャがまったく異なる新旧両システムを連携させる役割をZOZO API Gatewayが担うことになり、会社特有の事情を考慮した開発も発生しました。結果として自社開発という選択は、開発コストとシステム保守の負担の軽減につながったと感じています。 動作の軽量さを重視した技術選定 ZOZO API Gatewayは、リプレイス完了時にはZOZOTOWNのほぼすべてのリクエストを通すことになります。そのため、レイテンシが低いこと、また障害発生時にも早急に復旧できる必要がありました。したがって、OSSやマネージドなクラウドサービスを使用して充実した多くの機能を付与できることよりも、軽量かつ起動が容易で、チューニングしやすいアプリケーションを作るメリットのほうが大きいと判断しました。ZOZOではサーバサイドのアプリケーション開発にJavaもしくはGoの採用を推奨しており、とくにGoは上記の条件を満たしていました。 ZOZO API Gatewayが担う機能 ZOZO API Gatewayの主な機能は次のとおりです。 ルーティング ユーザー認証 クライアント認証 トレースIDの発行 リトライ リクエストタイムアウト 加重ルーティング レートリミット 稼働を開始した当初は上記の機能をすべて提供していましたが、Istioを導入するにあたって責務の分担をし、現在はリクエストタイムアウト、リトライ、加重ルーティング、レートリミットの機能をIstioが担っています。この節ではZOZO API Gatewayの機能を説明し、Istioについての詳細は次節で説明します。 ルーティング ルーティングは、受け取ったリクエストを検証し、設定されたマイクロサービスもしくはオンプレミスシステムに転送する機能です。マイクロサービスの増加に伴いルーティングの設定も増えるため、ZOZO API Gatewayのアプリケーションとは切り離し、YAMLファイルで宣言的に設定をしています。 ルーティングを設定するためのYAMLファイルは routes.yaml (リスト1)と target_groups.yaml (リスト2)の2つがあります。この例では、リクエストのパスが正規表現で ^/sample/(.+)$ に一致した場合、targetAに転送します。また転送時には、 $1 がキャプチャグループのマッチした部分に置き換えられます。この設定を必要に応じてYAMLに記述し、ルーティングを実現しています。 - from : path : ^/sample/(.+)$ to : destinations : - target_group : targetA path : /$1 リスト1 routes.yaml targetA : targets : - host : sample.example.com port : 8080” リスト2 target_groups.yaml ユーザー認証 ユーザー認証は、トークンを用いてリクエスト送信者が誰であるかを確認する機能です。ただしログイン時に認証するのはマイクロサービスの1つであるID基盤サービスで、このサービスでログイン時にトークンを発行します。ZOZO API GatewayではID基盤APIを用いてリクエストに付与されたトークンを検証し、ユーザーの認証情報が正しいことを確認します。 クライアント認証 ZOZOTOWNのAPIは一般公開していません。そのためリクエストできるクライアントを制限しています。リクエストする際はトークンをヘッダに付与してもらうことで、ZOZOTOWNのネイティブアプリや社内サービスなどのクライアントを識別し、クライアントを認証します。クライアント認証後は、クライアントを識別できるヘッダを伝搬し、バックエンドの処理で認可に利用しています。 リクエストを許可するクライアントは、 routes.yaml のclientsで設定できます。リスト3の設定では、targetAにリクエストできるクライアントはserviceAだけに制限できます。トークンの詳しい設定方法については、セキュリティ上の理由から割愛します。 - from : path : ^/sample/(.+)$ clients : - serviceA to : destinations : - target_group : targetA path : /$1” リスト3 clientsを指定した routes.yaml トレースIDの発行 ストラングラーフィグパターンによりリクエストが新旧システムに振り分けられ、さらに複数のマイクロサービスを通る場合もあるため、リクエストの追跡が困難になりました。そこでZOZO API Gatewayで独自のトレースIDを発行し、転送先へ伝搬しました。新旧システムで受け取ったトレースIDをログに出力することで、サービスを横断したリクエストを追跡できます。 Istio導入以前に必要だった機能 リトライ、リクエストタイムアウト、加重ルーティングの機能は現在Istioに置き換えられており、ZOZO API Gatewayではすでに使われていないため、簡潔に紹介します。いずれも target_groups.yaml で設定しました。 リトライ リトライは、リクエストが何かしらのエラーで失敗した場合に再試行する機能です。次のパラメータを用いて、転送先ごとに設定できます。 max_try_count:リクエストを試行する最大回数 retry_cases:リトライする条件(サーバエラーやリクエストタイムアウト時など) retry_non_idempotent:冪等でないHTTPメソッド(POST、PATCH)に対するリトライ設定 retry_to:リトライ先の指定 リクエストタイムアウト リクエストに対するタイムアウトの設定も、リトライと同じく、転送先ごとに設定できます。 connect_timeout:1リクエストあたりのTCPコネクション確立までの間のリクエストタイムアウト値(ミリ秒単位) read_timeout:1リクエストあたりのリクエスト開始からレスポンスボディを読み込み終わるまでの間のリクエストタイムアウト値(ミリ秒単位) idle_conn_timeout:データが送受信されなかった場合にコネクションを維持する時間(ミリ秒単位) max_idle_conns_per_host:1ホストあたりに保持するアイドル状態のコネクションの最大数 加重ルーティング 新旧システムへのリクエスト比率の重みを設定できます。この比率を調整することで、旧システムから新システムへの移行の際にカナリアリリースを実現しました。リスト4の設定では、old-systemへ80%、new-systemへ20%の割合でリクエストされます。 targetA : targets : - host : old-system port : 8080 weight : 4 - host : new-system port : 8081 weight : 1” リスト4 加重ルーティングを適用した target_groups.yaml ZOZO API Gatewayの運用 Istioの導入 連載第2回(本誌2024年6月号) でお伝えしたとおり、ZOZOTOWNのマイクロサービスが稼働するKubernetesクラスタ「プラットフォーム基盤」では、オープンソースのサービスメッシュであるIstioを導入しています。導入の背景は、新たなトラフィック制御の要件です。リプレイスが進み、新しくマイクロサービスが他サービス(クラスタ外のサービスを含む)を呼び出す通信要件が発生しました。当時マイクロサービス起点のZOZO API Gatewayを経由しない通信では、一貫したトラフィック制御機能がありませんでした。そのため、各マイクロサービスは必要に応じて独自に機能を追加する非効率的な状況に陥っていました。そこで、プラットフォーム基盤で一貫したトラフィック制御機能の提供が課題となりました。この課題に対し、ZOZOでは3つの案を検討しました。 案1:マイクロサービス起点の通信でもZOZO API Gatewayを介し、ZOZO API Gatewayのトラフィック制御機能を使う 案2:トラフィック制御機能を提供する共通ライブラリを作成し各マイクロサービスに組み込む 案3:サービスメッシュを導入し、マイクロサービスを変更することなくサイドカーパターンでプロキシを注入して透過的にトラフィック制御機能を追加する 案1はZOZO API Gatewayへの負荷が大きく、スケールによるコスト増加が現実的ではありませんでした。また、案2はライブラリの作成やそのメンテナンス、ライブラリをマイクロサービスに組み込み逐次更新するといった手間が発生します。さらに、その作業の担当をどうするか、といった課題も挙がりました。最終的に、案3のサービスメッシュが最も現実的であるという結論に至りました。ZOZOでは次のような理由からIstioを選定し、2020年後半から検証を進め、導入を決めました。 サービスメッシュを実現するOSSの中で利用実績が多い 分散トレーシングに利用しているDatadogが、Istioとのインテグレーションをサポートしている Istioの導入により、マイクロサービスのPodにはistio-proxyというサイドカーコンテナが注入されます。マイクロサービス起点の通信はすべてistio-proxyをプロキシとして経由することになり、ZOZO API Gatewayを経由せずにIstioの設定による一貫したトラフィック制御ができるようになりました(図3)。 図3 Istioによるトラフィック制御 ZOZO API GatewayとIstioの責務の整理と機能分担 ZOZO API Gatewayはネットワークの入口でのみトラフィック制御機能を提供します。一方Istioは、メッシュネットワーク全体にトラフィック制御機能を提供します。IstioではVirtualServiceというカスタムリソースを用いて、各マイクロサービスでトラフィック制御をリスト5のように定義します。 apiVersion : networking.istio.io/v1beta1 kind : VirtualService metadata : name : virtualservice spec : hosts : - microservice http : - route : - destination : host : microservice.ns.svc.cluster.local subset : subset retries : attempts : 1 perTryTimeout” perTryTimeout : 3s retryOn : 5xx timeout : 4s リスト5 virtualservice.yaml リスト5の場合、メッシュネットワーク内でマイクロサービスへHTTPリクエストを行ったとき、3秒でリクエストタイムアウトとなります。HTTPステータスコードで5xx(リクエストタイムアウト504を含む)が返却された場合、1回リトライします。リトライを含むリクエスト全体で4秒経過するとクライアントにリクエストタイムアウトが返ります。ZOZO API Gatewayをメッシュネットワークに追加する際、トラフィック制御を両方設定しているとトラフィック制御が二重で行われてしまう状態となります。その場合、意図しないトラフィック制御により正常なリクエストができずZOZOTOWNの画面が表示されないなどの事象を引き起こす可能性があります。この課題に対し、ZOZOでは次の方針で重複する機能の責務を整理しました。 一貫した機能提供のため、メッシュネットワーク全体に関わる機能はIstioの責務とする 入口のGatewayレイヤーでのみ必要となる機能はZOZO API Gatewayの責務とする この責務に従い、各機能を表1のとおり分担することにしました。 機能 ZOZO API Gateway Istio ルーティング ○ ユーザー認証 ○ クライアント認証 ○ トレースIDの発行 ○ リトライ ○ リクエストタイムアウト ○ 加重ルーティング ○ レートリミット ○ 表1 責務整理後における機能の分担結果 ZOZO API Gatewayで不要となる機能はサービスメッシュへの追加と併せて削除していきました。結果、意図しないトラフィック制御などの問題が発生することなく、プラットフォーム基盤全体にIstioを導入できました。このように、Istioの導入でZOZO API Gatewayは責務を変え、Gatewayレイヤーで必要な機能のみ提供する形になりました。 監視 リプレイスプロジェクトではDatadog APMを使用し、分散トレーシングを実現しています。ZOZO API Gatewayでは、traceのspanタグにクライアント識別子を追加することでリクエスト元を判断できるようにしており、エラー発生時の可観測性を向上させています。ZOZO API GatewayはZOZOTOWNのリクエストのほぼすべてを処理する前提で開発していることから、このようなしくみによってすべてのサービスのエラーを検知し、分析できます。 監視の運用については特有の難しさもあります。どのサービスが発したエラーであれ、システム異常の可能性があることに変わりはないため、ZOZO API Gateway開発チームでは、検知したエラーにはほぼすべて対応します。チームはアラートを受けて、エラーが発生したサービスの通知が流れるSlackチャンネルを確認したり、担当チームにヒアリングを行ったり、事情を考慮してZOZO API Gateway側でのエラー通知の基準を調整したりなどの意思決定をします。現状、マイクロサービスが障害を起こす頻度は低く、監視で対応するケースは激しいスパイクアクセスを処理する機能を提供するサービスに起因した避けられないエラーの通知がほとんどです。しかし、安全な運用のために、ZOZO API Gatewayを開発するチームは関連するマイクロサービスの性質についてもある程度幅広く知っておく必要があります。 ZOZO API Gateway本番稼働開始から3年を経て ZOZO API Gatewayは2020年4月に稼働を開始し、2024年時点で3年以上の間本番環境で動いています。本記事の締めくくりとして、この節ではリプレイスプロジェクトにおける具体的な成果を紹介します。以降の説明で使用されるデータは、すべて2024年4月上旬時点のものです。 ストラングラーフィグパターンの功績 ファサードが存在することにより、バックエンドの開発が完全にフロントエンドから切り離され、リプレイスの戦略に大きな柔軟性を生みました。フロントエンドはAPIのホストが新旧システムのどちらであるかを意識する必要はなく、ZOZO API Gatewayの設定を変更するだけで新システムでの処理に切り替えることができます。これは、たとえばオンプレミスDBからマイクロサービスDBへのデータ移行時に役立ちます。リプレイスプロジェクトでは、ZOZOTOWNの稼働を止めずにデータを移行するために、書き込みを行うAPIとデータ取得のAPIを異なるタイミングでリリースします。ファサードがなければリクエストの切り替えに都度フロントエンドの修正が必要ですが、ストラングラーフィグパターンではフロントエンドのチームがバックエンドの事情を考慮する必要はありません。データ移行戦略の詳細についてはテックブログ *1 をご覧ください。 プロジェクト全体としても、マイクロサービス開発組織のスケールを実現できました。現在20以上のサービスの開発が同時に進んでおり、ZOZOTOWNを止めることなくリプレイスを進めています。エンジニアの採用も積極的に行い、リプレイスプロジェクトに携わるエンジニアの数は150人程度に増えました。 自社開発のコストメリット ZOZOTOWNのアクセス量の多さから、Amazon API Gatewayのような従量課金の料金体系のサービスを使用するよりも、自社でサービスを開発したほうが安くなるのではないかというもくろみが開発当初からありました。実際にコストメリットがあるのか、試算してみます。ここでは、Amazon API Gatewayサービスと、ZOZO API Gatewayが稼働しているAmazon EC2(以下EC2)の料金を用います。 執筆時点のAmazon API Gatewayの料金体系では、最初の3億リクエストは100万件当たり1.29USD、それを超えた分は100万件当たり1.18USDかかります。一方、ZOZO API GatewayはAmazon Elastic Kubernetes Service上で稼働しているため、そのEC2ノードにかかるコストが主な利用料金となります。リプレイス完了後にZOZO API Gatewayがどの程度のアクセスを処理することになるかは現時点では明言できませんが、たとえば月間のリクエスト数を10億とした場合、それぞれの概算コストは表2のようになります。リクエスト数はアプリケーションの設計にもよりますが、試算のとおり、月間10億リクエスト程度であれば、ZOZO API GatewayはAmazon API Gatewayと比べて60%程度のコストでアクセスをさばくことができると言えます。 Amazon API Gateway ZOZO API Gateway 試算 3億✕$1.29/100万 + (10億-3億)✕$1.18/100万 省略 (弊社実績とEC2の料金体系からの概算) コスト(月額) $1,213 $734 表2 Amazon API GatewayとZOZO API Gatewayの概算コスト比較 実際にZOZO API Gatewayが処理するアクセス数はより多く、よりZOZO API Gatewayにコストメリットがある状態です。またZOZO API GatewayはEC2のリザーブドインスタンスを契約するなどの工夫により、試算した金額感よりもさらにコストを抑えることができています。そのため、当初のもくろみどおりZOZO API Gatewayの自社開発はマネージドサービスの従量課金と比較してコストメリットがあることがわかります。 一方で、Amazon API GatewayにはAPIの作成、保守運用などに必要な豊富な機能が備わっています。ZOZOTOWNでのユースケースでは自社開発が結果的にコスト抑制にもつながりましたが、初期開発コストを抑えてより一般的な機能を実現したい場合や、アプリケーションの要件によっては、Amazon API Gatewayを採用するほうが妥当な可能性があります。 まとめ ZOZOTOWNではストラングラーフィグパターンによるマイクロサービス化を進めており、その戦略の要となるファサードとしてZOZO API Gatewayを開発しました。ZOZO API Gatewayは一般的なルーティングなどの役割を担い、Istioの導入によってリクエストタイムアウトやリトライなどの一部機能を分担し、開発規模は比較的小さく保っています。プロジェクトの進行に伴い、一部新旧システムの仕様の差を吸収する役割を担うことになったものの、スクラッチから開発したことにより柔軟に開発を進めることができました。ZOZOTOWNのケースでは、API Gatewayの自社開発はリプレイスプロジェクトのスムーズな進行に大きく寄与し、コスト低減にも役立っています。 本記事は、技術本部 ECプラットフォーム部 会員基盤ブロックの富永 良子、吉江 守弘と、技術本部 SRE部 プラットフォームSREブロック ブロック長の亀井 宏幸によって執筆されました。 本記事の初出は、 Software Design 2024年7月号 連載「レガシーシステム攻略のプロセス」の第3回「API Gatewayとサービスメッシュによるリクエスト制御」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com *1 : サービス無停止を実現するデータ移行戦略
アバター
こんにちは、SRE部カート決済SREブロックの飯島です。普段はZOZOTOWNのカート決済機能のリプレイス・運用・保守に携わっています。またSplunkの管理者としても活動しています。 本記事ではSplunk CloudにおけるInfrastructure as Code(IaC)についてご紹介します。 背景 Splunkに対して感じていた課題 Splunk CloudのIaC化検討 Splunk Appを使ったIaC App API IaC対象のSplunkリソース 既存のリソースのエクスポート IaCの全体像 Appのディレクトリ構成 CI/CDパイプライン 検証の詳細 デプロイの詳細 効果、メリット 終わりに 背景 弊社では様々な用途でSplunkを活用しています。過去にいくつかテックブログを公開していますので、興味のある方はぜひご覧ください。 techblog.zozo.com techblog.zozo.com Splunkには、オンプレミスやクラウドプロバイダーにインストールして運用するSplunk Enterpriseと、SaaSとして提供されるSplunk Cloudの2種類があります。弊社では後者のSplunk Cloudを利用しています。 Splunkに対して感じていた課題 冒頭でも説明した通り、弊社ではSplunkを様々な用途で活用しており、その重要性は導入時よりも高まっています。一方で以下の課題が生じていました。 SPLの習得難易度が高く新規利用の開始までのハードルが高い コード管理されていないためダッシュボードなどがいつの間にか変更されていることがある 何の用途で作成したか不明なリソースが存在する IaCを導入すると以下のメリットが生まれ、上記の課題を解決できます。 Splunkが不慣れでも他のコードを参考にして効率よくリソースの作成、変更が可能 コード管理されているとSplunkを触ったことがない人にも勧めやすい コード管理ツール(例えばGitHub)に変更履歴が残る コード管理ツールでレビューできる/レビューが受けられる Splunk CloudのIaC化検討 上述した課題を解決するためにSplunk Cloudをコード管理する方法を検討しました。 社内でインフラ構成管理ツールとしてTerraformを利用しているケースが多かったため、まずはTerraformでの管理を検討しました。しかしSplunk CloudではTerraformで管理できるリソースが一部に限られており、ダッシュボードなどのコード管理に対応できないことから課題解決に至らないと判断しました。 補足としてSplunk Enterpriseはほぼ全てのリソースがTerraformに対応しているため、Splunk Cloudについても今後対応リソースが増えていく可能性は十分あります。 github.com Splunk Appを使ったIaC そこで今回Splunk App(以下、App)と、Splunk社から提供されるApp用の2つのAPIを使ってIaCを実現しました。 App Appとは、Splunkの設定を集約して管理するための単位です。デフォルトではSearch & ReportingというAppがあらかじめ利用可能になっています。ユーザー自身でAppを新規作成したり、 Splunkbase からAppをインストールしたりもできます。 今回Search & Reportingの既存リソースを、新規作成したAppに移行してコード管理する方針としました。 API 以下が使用した2つのAPIです。 AppInspect API Appの品質と準拠性を検証するためのAPI(ref. Validate your private app ) Admin Config Service (ACS) API AppをSplunk Cloudにデプロイすることが可能(ref. Install an app ) デプロイするにはAppInspect APIの検証に合格する必要がある IaC対象のSplunkリソース 弊社でよく利用されているダッシュボードとアラート・レポートを対象としました。 Appを使ったIaCでは、Terraformのようにリソースを インポート して既存リソースをコード管理下に置く手段がありません。リソースをコードで定義すると、新規リソースとして作成されます。そのため、既存リソースを新規リソースに置き換えることが難しいもの(例えばIndex)は対象外としました。 既存のリソースのエクスポート 既存リソースの構成ファイルはエクスポート可能です。Splunk Enterpriseでは利用者自身でエクスポート可能ですが、Splunk Cloudではこの機能が存在せずSplunkサポートに依頼する必要があります。 今回既存のリソースが集約されているSearch & Reporting Appをエクスポートし、IaC対象のダッシュボードとアラート・レポートの構成ファイルを入手しました。 IaCの全体像 Appの構成ファイルはGitHubで管理しています。また、AppInspect APIとACS APIを使用し、GitHub ActionsのWorkflowを利用して、アプリの検証とデプロイを行うCI/CDパイプラインを構築しました。 Appのディレクトリ構成 Appは 開発者ガイド にある通り、決められたディレクトリ構成に準拠する必要があります。もし準拠できていない場合、AppInspect APIの検証に失敗します。 以下はAppの必要最低限のディレクトリ構成に、ダッシュボードとアラート・レポートを追加した構成です。 . ├── bin │ └── README ├── default │ ├── app.conf │ ├── data │ │ └── ui │ │ ├── nav │ │ │ └── default.xml │ │ └── views │ │ └── README │ │ └── sample_dashboard.xml │ └── savedsearches.conf ├── metadata │ └── default.meta └── static ├── appIcon.png ├── appIconAlt.png ├── appIconAlt_2x.png └── appIcon_2x.png 以下は上記の構成の主なファイルの説明です。 ファイル 説明 ./default/app.conf Appの設定 ./default/savedsearches.conf アラート・レポートの設定 ./default/data/ui/views/dashboard.xml ダッシュボードの設定 ./metadata/default.meta 各種リソースのアクセス権限の設定 CI/CDパイプライン GitHub上でPRを作成すると、ダッシュボードのXMLファイルのLintと、AppInspect APIを使用したAppの検証が実行されます(CI)。マージすると再度CIが実行され、その後ACS APIを使用したAppのデプロイと(CD)、CI/CDの結果のSlack通知が実行されます。 以後、Appの検証とデプロイの処理について説明します。 検証の詳細 以下がAppの検証処理の流れです。 splunk.comにログインしてアクセストークンを取得 -(1) Appのコードをtarコマンドでgzip圧縮 -(2) (1)と(2)を添付してAppInspect APIを実行 AppInspectの結果から検証の合否を判定 以下サンプルコードです。 # splunk.comにログインしてアクセストークンを取得 loginstatus = $( curl -u " $username : $password " --url " https://api.splunk.com/2.0/rest/login/splunk " ) access_token = $( echo " ${loginstatus} " | jq .data.token | tr -d ' " ' ) # Appのコードをtarコマンドでgzip圧縮 COPYFILE_DISABLE = 1 tar --format ustar -cvzf " ${appname} " .tar.gz " ${appname} " tarfile_path = $( realpath " ${appname} .tar.gz " ) # AppInspect API実行 request = $( curl -X POST \ -H " Authorization: bearer ${access_token} " \ -H " Cache-Control: no-cache " \ -F " app_package=@ ${tarfile_path} " \ -F " included_tags=cloud " \ --url " https://appinspect.splunk.com/v1/app/validate " ) # AppInspectの進捗を確認 request_id = $( echo " ${request} " | jq .request_id | tr -d ' " ' ) appinspect_status = " PROCESSING " while [ " ${appinspect_status} " = "PROCESSING" ]; do echo " waiting the app inspect reviewing " echo " fetch to check the status of inspecting from https://appinspect.splunk.com/v1/app/validate/status/ ${request_id} " sleep 10 status_request = $( curl -s -X GET \ -H " Authorization: bearer ${token} " \ --url " https://appinspect.splunk.com/v1/app/validate/status/ ${request_id} " ) echo ""; echo " -------------- App Inspect Status -------------- " echo " ${status_request} " | jq . appinspect_status = $( echo " ${status_request} " | jq -r .status ) done # AppInspectの成否の判定 errors = $( echo " ${status_request} " | jq -r .info.error ) failures = $( echo " ${status_request} " | jq -r .info.failure ) result = " failed " if [ " ${errors} " -eq 0 ] && [ " ${failures} " -eq 0 ]; then result = " success " fi AppInspectの結果(上記サンプルコードの status_request )には以下の項目が含まれます。errorとfailureが0であれば検証は合格です。 " status ": " SUCCESS ", " info ": { " error ": 0 , " failure ": 0 , " skipped ": 0 , " manual_check ": 0 , " not_applicable ": 0 , " warning ": 0 , " success ": 1 } AppInspectの結果の詳細はレポートで確認できます。レポートにはJSON形式とHTML形式があり、Content-Typeでファイル形式を選択できます。 # JSON形式 curl -X GET \ -H " Authorization: bearer ${access_token} " \ -H " Cache-Control: no-cache " \ -H " Content-Type: application/json " \ --url " https://appinspect.splunk.com/v1/app/report/ ${request_id} " # HTML形式 curl -X GET \ -H " Authorization: bearer ${access_token} " \ -H " Cache-Control: no-cache " \ -H " Content-Type: text/html " \ --url " https://appinspect.splunk.com/v1/app/report/ ${request_id} " デプロイの詳細 以下がACS APIを実行してSplunk CloudにAppをデプロイする処理です。 curl -X POST " https://admin.splunk.com/ ${STACK} /adminconfig/v2/apps/victoria " \ --header " X-Splunk-Authorization: ${access_token} " \ --header " Authorization: Bearer ${json_web_token} " \ --header " ACS-Legal-Ack: Y " \ --data-binary " @ ${tarfile_path} " | jq . CIで取得したアクセストークンとtarコマンドでgzip圧縮したAppのコードを添付します。またJSON Web Tokenが必要になるため、事前にSplunk CloudのUIもしくはACS APIで作成します。 効果、メリット IaC化により、他のコードを参考にしてリソースの作成や変更が可能となりました。これにより、Splunkに不慣れな場合でも効率的にリソースの作成や変更ができ、Splunkに触っていない人にもSplunkの利用を勧めやすくなりました。またGitHub上のPRで変更内容をレビューでき、その変更内容が履歴に残るメリットも得られました。 終わりに 弊社と同様にSplunk Cloudを使い、IaC化を検討している方の参考になれば幸いです。 今回Splunkのカスタマーサクセスチームの皆様には多大なご協力をいただきました。この場を借りて、心から感謝申し上げます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、データシステム部推薦基盤ブロックの寺崎( @f6wbl6 )です。 私たちのチームではZOZOTOWNにおけるパーソナライズ機能と推薦システムを開発しており、2022年6月のテックブログではZOZOTOWNのホーム画面をパーソナライズした事例の1つを紹介しました。 techblog.zozo.com 今回は上記記事の続編として、ホーム画面での商品訴求の単位である「モジュール」の並び順をパーソナライズした取り組みをご紹介します。本記事がパーソナライズ機能や推薦システムを開発している方の参考になれば幸いです。 目次 はじめに 目次 ZOZOTOWNのホーム画面における「モジュール」について パーソナライズ施策の比較 パーソナライズの方針 モジュール並び順パーソナライズの要件 パーソナライズ方法 概要 モデル構成 モジュールのスコア算出方法 システム構成 1.Two-Towerモデル学習パイプライン 2.商品ベクトル化パイプライン 3.モジュール並び順予測パイプライン A/Bテスト 概要 結果 A/Bテストを経て挙がった課題 定性的な課題 定量的な課題 最後に ZOZOTOWNのホーム画面における「モジュール」について まずはじめに、前回記事のおさらいとしてZOZOTOWNのホーム画面における「モジュール」について簡単に説明します。 モジュールは商品やブランド、ショップなどのコンテンツを表示させるための枠の総称で、ZOZOTOWNのホーム画面はこれらのモジュールによって構成されています。 2024年7月19日時点でのZOZOTOWNホーム画面 モジュールは訴求内容や更新頻度に応じて「定常モジュール」と「企画モジュール」の2種類に大別されます。各モジュールの違いは下表の通りです。 運用方法 更新頻度 定常モジュール 常に同じ訴求内容のコンテンツを掲載する ほぼ更新なし 企画モジュール ビジネスサイドが自由に訴求内容を企画・管理する 1~2週ごとに更新 定常モジュールは「チェックしたアイテム」や「世代別アイテムランキング」など、ファッショントレンドなどの外的要因によらず継続的に特定の内容を訴求するものです。一方、企画モジュールはビジネスサイドの企画チームがトレンドやキャンペーンに応じて柔軟に訴求内容を変更するもので、定期的に更新される点が特徴です。 企画モジュールには商品の検索条件が紐づいており、この検索条件を元に商品検索用のAPI(検索API)から取得した商品をモジュールに表示する仕組みとなっています。そのため企画チームは商品の検索条件を設定するだけで多様な訴求軸のモジュールを作成できます。 パーソナライズ施策の比較 「ホーム画面のモジュールをパーソナライズする」と一口に言ってもパーソナライズのパターンがいくつかあるため、ここではそのパターンを列挙しつつ過去の施策と今回の施策がどのパターンに該当するかを説明します。 モジュール内に表示される商品の表示順序を変える モジュール内に表示される商品の検索条件を変える 表示されるモジュールの順序を並び替える 過去のテックブログ で紹介したパーソナライズ施策は上記のうち「 2.モジュール内に表示される商品の検索条件を変える 」に該当します。前述の通りモジュールには特定の検索条件で取得した結果を表示しているため、ユーザーごとにパーソナライズしたブランドやカテゴリを検索条件に反映させることでモジュール内の商品をパーソナライズしていました。 過去のパーソナライズ施策のイメージ 今回のパーソナライズ施策は「 3.表示されるモジュールの順序を並び替える 」に該当します。モジュールそのものの並び順をユーザーごとに変化させ、ユーザーが興味を持つと考えられるモジュールを上位に表示することを目指しています。 今回のパーソナライズ施策のイメージ また、パーソナライズの対象となるモジュールは前述した「 企画モジュール 」に限定しています。定常モジュールはいわゆるマス向けの訴求軸であるため、パーソナライズの効果はそこまで期待できません。一方で企画モジュールは企画チームが多様な訴求軸のモジュールを作成できるため、パーソナライズによる効果が大きいと考えられます。 パーソナライズの方針 過去の施策ではパーソナライズの目的として「 既存購入者の来訪頻度・購入頻度の増大 」を掲げていましたが、今回の施策ではまず「 より良いモジュールの並び順とはどのような状態か 」を定義することから始めました。 ユーザーにとって最適な並び順でモジュールを表示することは長期的には購入頻度や受注金額の増大に繋がると考えられますが、短期的には興味のあるモジュールをクリックする頻度が増えるはずです。ここから「良いモジュールの並び順」を「ユーザーが興味を持ってクリックするモジュールが上から順に並んでいる状態」と定義し、まずは「 モジュールのクリック数増大 」を目指すこととしました。 この目的を元に、「ユーザーにクリックされるモジュールを優先的に表示する」という方針でパーソナライズシステムを開発しました。以降、モジュールの並び順のことを「モジュール並び順」と記述します。 モジュール並び順パーソナライズの要件 今回のプロジェクトで挙げられた要件をいくつかピックアップします。 会員ごとにパーソナライズされた並び順でモジュールを表示する パーソナライズされた並び順が定期的に更新される パーソナライズロジックのA/Bテストを実施できる モジュール並び順のパーソナライズができないユーザーに対してデフォルトの並び順でモジュールを表示できる 任意のモジュールを特定の位置に固定表示できる 1〜3番目の要件については特に違和感はないと思います。 4番目の要件は、ユーザーがパーソナライズに必要な行動ログを持たない場合やパーソナライズシステムが障害等で稼働できない状況を想定したものです。パーソナライズ機能に依存してZOZOTOWNのホーム画面にモジュールが表示されなくなる、という状況を避けるためにデフォルトの並び順を表示する要件を設けています。 5番目の要件はビジネス要求に基づくものです。ZOZOTOWNでは特定のブランド・ショップの商品の訴求を強めたり、セール期間中にセール商品の訴求を強めたりするケースがあり、そういった際に一部のモジュールを特定の位置に固定表示します。また広告系のモジュールやパーソナライズ以前から訴求効果が高かった「チェックしたアイテム」モジュールなど、表示位置を迂闊に変えるべきでないモジュールもいくつかあります。今回のプロジェクトではこれらのモジュールはパーソナライズの対象外として特定の位置に固定表示しています。 パーソナライズ方法 ここからはメイントピックであるモジュール並び順のパーソナライズ方法について説明します。 概要 モジュール並び順のパーソナライズにはユーザーとモジュールの相性を表すスコアを利用しており、このスコアの降順にモジュールを配置することでパーソナライズを実現しています。モジュールのスコアを計算する方法の概念図を以下に示します。 パーソナライズ方法の概念図 ユーザーを表現するベクトルはZOZOTOWN内での回遊行動をもとに作成し、モジュールを表現するベクトルはモジュールに表示される商品の情報をもとに作成しています。モジュールのベクトルはそのモジュールに表示される商品ベクトルの集合と考えられるため、商品ベクトルの集合とユーザーベクトルを用いてモジュールに対するスコアを計算します。 このパーソナライズ方法を実現するには ユーザー情報と商品情報をベクトルにするための仕組み が必要になります。次節ではこのベクトル化に必要な機械学習モデルとシステム構成について説明します。 モデル構成 ユーザー情報や商品情報をベクトル化する手法は多々ありますが、今回は弊社の別のパーソナライズシステムでも利用されている Two-Towerモデル を採用しました。Two-Towerモデルはドメインが異なるデータを同一のベクトル空間にマッピングする2つのエンコーダーから成っています。これらのエンコーダーを使ってユーザーと商品それぞれのベクトルを獲得します。 Two-Towerモデルの概念図 モデルの学習方法や使用したライブラリについては以下の通りです。 項目 内容 学習の方針 商品閲覧を最適化 モデル構築に使用したライブラリ TensorFlow Recommenders 近似最近傍探索の手法 ScaNN モジュールのスコア算出方法 ユーザーベクトルと商品ベクトルを使ってモジュールのスコアを決めますが、スコアの算出方法はパーソナライズ結果に大きく影響を与えるため、オフラインでの実験時にいくつかのパターンを試しました。 全商品ベクトルの平均をモジュールのベクトルとみなし、 ユーザーベクトルとのコサイン類似度をモジュールのスコアとする 全商品ベクトルの各次元でmaxをとったベクトルをモジュールのベクトルとみなし、 ユーザーベクトルとのコサイン類似度をモジュールのスコアとする ユーザーベクトルと各商品ベクトルのコサイン類似度を算出し、 得られたコサイン類似度の平均値をモジュールのスコアとする ユーザーベクトルと各商品ベクトルのコサイン類似度を算出し、 得られたコサイン類似度の最大値をモジュールのスコアとする 上記のスコア算出方法に対して定量・定性評価を実施した結果、案4の算出方法が定量的・定性的に優れていたことから案4を採用しました。 設計時は案3を採用する想定でしたが、定性評価をしてみると意図しない・もしくは意図がわからない推薦をしているものが多い結果となっていました。これはモジュールに掲載される商品では必ずしも統一感があるわけではなく、そうした商品のスコアの平均を取るとスコアが平滑化されてしまうことが原因と考えています。 商品スコアの最大値を取ることで「ユーザーの興味を惹く商品が少なくとも1つ以上ある」という条件を満たすモジュールを優先的に表示することになります。しかし、必ずしもそうした商品がモジュールの先頭に表示されているわけではないので、商品の表示位置を考慮した重み付けなど改善の余地があると考えています。 システム構成 Two-Towerモデルの学習から予測結果のサービングまでを含めた全体のシステム構成は以下のようになっています。 パーソナライズシステムの構成図 モジュール並び順を予測するためにVertex AI Pipelinesを用いたバッチシステムが3つ存在します。以下ではそれぞれのシステムの役割を説明していきます。 1.Two-Towerモデル学習パイプライン このパイプラインは定期的にTwo-Towerモデルを学習し、学習済みモデルをVertex AI Model Registryに登録することを目的としています。先述した通りTwo-Towerモデルからはユーザーと商品それぞれをベクトル化するエンコーダーが得られるため、後続システムではこれらのエンコーダーを使ってベクトル化を行います。図中ではユーザーと商品それぞれのエンコーダーを User tower , Item tower と記述しています。 2.商品ベクトル化パイプライン このパイプラインは、モジュールに表示される商品のベクトル化を目的としています。モジュールに表示される商品の情報は商品検索用のAPIから取得し、Item towerを使ってこれらの商品情報をベクトル化します。ベクトル化された商品情報はBigQueryに保存され、後続システムであるモジュール並び順予測パイプラインで使用されます。 運用上の理由から、モジュールに表示される商品リストは1日に複数回更新されることがあります。そのためこのパイプラインは1日に数回実行し、モジュールに紐づく商品ベクトルが最新の状態となるようにしています。 3.モジュール並び順予測パイプライン モジュールの並び順を予測するためのパイプラインです。Vertex AI Model Registryに格納されている学習済みのUser towerを使用し、BigQueryから取得したユーザー情報をベクトル化します。またBigQueryにはモジュールに紐づく商品のベクトルも格納されているため、これらのベクトルを使ってモジュールのスコアを計算し、モジュールの並び順を決定しています。 予測結果はBigtableに保存され、リアルタイムにリクエストを処理する推薦APIが会員IDごとのモジュール並び順を取得する構成となっています。 A/Bテスト 概要 モジュール並び順のパーソナライズによる効果を評価するために、ZOZOTOWN会員を対象として約1か月のA/Bテストを実施しました。Control群とTreatment群を比較すると以下のような変化が生じます。 今回の施策ではすべてのモジュールがパーソナライズされるわけではなく、ビジネス的な要望から常に特定の位置に固定表示されるモジュールがある点に留意ください。例えば上図の「モジュールA(固定)」と記載されているモジュールはA/Bの割り振り関係なしで上から2番目に固定表示されています。 結果 A/Bテストの結果サマリを以下に示します。 指標 備考 結果(T/C比) ホーム画面訪問者の受注金額 ホーム画面にランディングしたユーザーの合計受注金額 100.1 (%) モジュール経由の受注金額 モジュールに表示されている商品の合計受注金額 100.1 (%) モジュール経由の受注点数 モジュールに表示されている商品の合計受注商品点数 100.4 (%) モジュール掲載商品のインプレッション数 モジュールに表示されている商品の合計インプレッション数 105.2 (%) モジュール掲載商品のクリック数 モジュールに表示されている商品の合計クリック数 103.2 (%) 受注系の指標はControlと比較して大きなアップリフトはありませんでしたが、 モジュールに掲載されている商品のインプレッションや閲覧に関する指標は大きく改善している ことがわかります。このことから、ユーザーはモジュールに興味を持ってホーム画面を回遊してより多くのモジュールを閲覧し、モジュールから商品ページに遷移する頻度が増えたと考えられます。 一方で受注系の指標に特段の変化がなかったことから、購入するほどの商品は提示できていなかったか、1か月という期間ではユーザーの購入体験を変えるほどの効果は計測できなかったと考えられます。 受注系の指標まで改善すれば理想的な結果でしたが、まずはプロジェクトの目的である「 モジュールのクリック数増大 」は達成できたと言える結果となりました。 A/Bテストを経て挙がった課題 上述した課題点も含め、A/Bテストを経て挙がった定性・定量的な課題と改善案について簡単に紹介します。 定性的な課題 「訴求軸は同じだが並んでいる商品が微妙に異なるモジュール」が連続して表示される 見たことのないカテゴリの商品が表示されにくい どちらの課題もモジュールのスコア算出で使用しているTwo-Towerモデルの最適化方針に原因があると考えられます。Two-Towerモデルは「次に閲覧する商品を予測する」という問題設定で学習しているため、ユーザーが直近で閲覧しそうな商品を提示することには成功しています。しかし、 その結果モジュールに掲載される商品が同じカテゴリやブランドに偏ってしまう という傾向が確認されました。 極端な例では以下のように全く同じ商品がファーストビューに並んでいるケースを確認しています。 この課題はアルゴリズムの設計時点で考慮事項として挙げられていたものの、日々の運用でカバーできるものと判断して対策をしていませんでした。 改善案として同じ商品が並んでいるモジュールを除外するルールベースのアプローチや、 MMR(Maximal Marginal Relevance) といったナイーブなリランキング方法の導入が考えられます。また今回はパーソナライズ方法としてモジュールのスコアを降順に並べただけのシンプルな方法を採用していますが、機械学習モデルでリランキングするなどの方法も考えられ、まだまだ改善の余地を残しています。 定量的な課題 受注系の指標がアップリフトしていない 特定カテゴリで各種指標が低下している 受注系の指標がアップリフトしていない原因として、前述の通りモジュールに表示される商品がユーザーにとって購入意欲を喚起するほどのものではなかったことが挙げられます。この課題の対処法としてパーソナライズロジック自体を改善するのはもちろんですが、 パーソナライズに使用されるモジュールの訴求軸を多様にする というアプローチが考えられます。 今までは全てのユーザーに同じモジュールを表示する都合上マス向けの訴求に偏る傾向がありましたが、これからは多種多様な訴求軸でユーザーに合ったモジュールを提示できます。この「多種多様なモジュール」を企画するのはビジネスサイドの企画チームであるため、モジュール並び順のパーソナライズを改善するには企画チームとの密な連携が欠かせません。 2つ目の課題はユーザーに閲覧される頻度が多くないカテゴリに関して閲覧や受注系の指標が悪化している、というものです。今まではモジュールの企画チームがその時訴求したい内容をモジュールで表現してユーザーに提示することで、カテゴリごとの閲覧や受注のばらつきが少ない状態でした。モジュール並び順がパーソナライズされた後はそうしたカテゴリごとのばらつきなどが意識されていないため、特定の期間で特定カテゴリの訴求を強める仕組みなどを検討しています。 最後に 本記事ではZOZOTOWNのホーム画面に表示するモジュールの並び順をパーソナライズするシステムとその効果について紹介しました。今回取り上げた部分以外にも改善すべき箇所が大量にあるので、これからも1つずつ改善していくことでユーザーにとってより良いZOZOTOWNを提供できるよう邁進していきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
こんにちは、技術本部 SRE部 基幹プラットフォームSREチームの斉藤です。普段はZOZOの持っている倉庫システムやブランド様が触る管理ページなどのサービスのオンプレミスとクラウドの構築・運用に携わっています。またDBREとしてZOZOTOWNのデータベース全般の運用・保守も兼務しております。 7月11日、12日に行われた「 db tech showcase 2024 」に、DBREから5名のエンジニアが参加しました。この記事では会場の様子と印象に残ったセッションについてご紹介します! db tech showcaseとは 会場の様子 セッションレポート おわりに db tech showcaseとは 国内最大規模のデータとデータベース関連のカンファレンスです。このイベントでは、データベースの専門家やエンジニア、IT業界のリーダーたちが一堂に会し、新しい技術やソリューション、事例、ノウハウを共有します。 db tech showcaseの主な内容には以下のようなものがあります。 講演:データベース技術の最新トレンド、事例や実践的なアプローチについての講演が行われます。 展示:企業やベンダーが最新の製品やサービスを展示し、デモンストレーションします。 ネットワーキング:業界の専門家や同僚と交流する機会が提供され、情報共有やビジネスの機会が広がります。 www.youtube.com 会場の様子 入口 db tech showcase 2024はTKP市ヶ谷カンファレンスセンターで開催されました。受付を終えるとパスが発行され、イベントのパンフレットが配布されました。 EXPO データベースに関連する企業のブースが並んでいました。セッションの間で立ち寄ったOracle社のブースではHeatWaveの話題で盛り上がり、セッションさながらの事例や最新情報を熱弁頂きました。他にも、休憩スペースでセッションの感想で盛り上がってる人達がいたり、ミニセッションも開催されていたりしました。EXPOエリアではこうした交流が盛んに行われいて会場全体に活気がありました。 セッション 2日間で75以上のセッションが行われました。 db tech showcase 2024セッションスケジュール その他 企業の各ブースでは魅力的なグッズを配布していました。 セッションレポート DBREのメンバーが気になったセッションを紹介します。 MySQLアプリケーション開発を爆速にする最新手法 Amazon Bedrockで検証!データベース移行時のクエリ修正を簡素化するLLMの活用法 爆速成長中のFindyがぶつかった課題と解決に向けた実践手法 【出光興産様事例紹介】SAP DB運用の課題と今後の計画 SQL Server 技術支援のための動作の調査 - dbts2024 版 - MySQLアプリケーション開発を爆速にする最新手法 基幹プラットフォームSREチームの斉藤です。ZOZOのメインデータベースはSQL Serverを使用していますが、マイクロサービス化が進んでいるシステムではAurora MySQLを採用しています。MySQLの最新情報や運用に使用できるツールの事例などをキャッチアップすべく本セッションに参加させていただきました。 MySQLとVS Codeの統合 VS Codeで利用できるオープンソースの拡張機能として、MySQL Shell for VS Codeが提供されています。MySQL WorkbenchのEOLが迫っている中で後継となるもので、MySQLの運用には欠かせない機能になっていくのではないだろうかと思います。MySQL WorkbenchからはSQLエディタやスキーマの確認・データの検索結果の表示など主要な機能は全て継承されていると思われます。さらに「MySQL Shell for VS Code」はただのMySQL Workbenchの代わりではなく、VS Codeを使用してきた開発者とMySQL Workbenchを使用してきた管理者が共通して使用できるIDEとして設計されています。NotebookというエディタからはSQLだけではなくJavaScriptやTypeScriptを実行し結果をグラフで表示できます。これらの機能を使用してシステム開発が効率化されていると感じました。 MySQL Shell for VS Codeついては公式ドキュメント「 MySQL Shell GUI / MySQL Shell for VS Code 」を参照してください。 MySQLのJavaScriptサポート MySQL Enterprise Editionの機能でストアドプロシージャや関数をJavaScriptで作成し実行できます。そのストアドプロシージャや関数は実行するSQL上でも呼び出すことができ、処理の幅が広くなりました。 MySQLでJavaScriptを使用するできるメリットは次の通りです。 高い表現力 JavaScriptアプリケーション開発者向けの大規模なツールのエコシステム サードパーティライブラリの充実 人気のある開発言語の1つで、1300万人もの開発者が存在する MySQLのJavaScriptサポートついての情報はThe Oracle MySQL Japan Blogの「 MySQLのJavaScriptサポートについて 」で詳しく紹介されております。 MySQL REST Service MySQL REST ServiceはMySQL Routerの拡張機能として提供されています。RESTを使ってHTTP経由でMySQLのデータを参照、更新ができます。MySQL Shell for VS Code上から設定し運用します。 基本的なアーキテクチャ 画像引用元: The Oracle MySQL Japan Blog「 MySQL REST Serviceの紹介 」 MySQL REST Serviceついての情報はThe Oracle MySQL Japan Blogの「 MySQL REST Serviceの紹介 」で詳しく紹介されております。 感想 MySQLは「MySQL Shell for VS Code」やJavaScriptサポート、「MySQL REST Service」などの最新機能により、開発効率と運用性が大幅に向上し、現代のアプリケーション開発に重要なソリューションへと進化を続けていると感じました。 Amazon Bedrockで検証!データベース移行時のクエリ修正を簡素化するLLMの活用法 カート決済SREの遠藤です。 DBのバージョンアップや異なるDBへリプレースする際、クエリの互換性が課題になることがあります。この課題に対し、昨今注目されるLLM(大規模言語モデル)を活用してクエリの修正工数を減らせるかの検証デモが行われました。 現在、ZOZOTOWNで利用しているSQL Serverのサーバー分離とバージョンアップを担当しており、LLMを使ったクエリ修正に興味がありこのセッションに参加しました。 内容 異なるDBMS製品への移行や、バージョンアップに伴うクエリ修正の例が5つほど紹介されました。まず、何も工夫をせずLLMに対してクエリ修正を指示した場合、半分以上が間違った回答でした。こうしたハルシネーション(生成AIの嘘)を防止するために、よくある工夫をプロンプトに取り入れることで精度が向上しました。 クエリ互換性に関する内容であることの記述 専門家としての視点の強調 思考の連鎖を促す指示(step by step) 事例の提供(Few-Shotプロンプティング) また、RAG(検索拡張生成)としてAmazon Bedrockを使い、DBMS製品の公式リリースノートをナレッジベースとすることでさらに精度が向上しました。 Bedrockの場合、ナレッジベースをS3に格納するのですが、以下のような加工をすることでさらに精度が向上しました。 リリースノートからSQLの互換性に関係のない記述を削除 Markdown形式に変換 最終的に、5つのデモ全てにおいてクエリを正しく修正できていました。100%の精度は保証されないものの、LLMはSQL変換タスクにも有用であり、修正工数を大幅に減らすことができそうです。 感想 ChatGPTをはじめとしたLLMはすでに業務で活用していますが、ここまで具体的にDB移行業務に活用できる例を見ることができ、新たな発見がありました。LLMでは100%正しい回答が得られるとは限らないため、人力でのチェックは必須です。そこでRAGを利用することにより、ナレッジベースを元に回答の根拠も示してくれるため、ハルシネーションが発生しても比較的簡単に正当性を判断できそうです。 今回のセッションではOracleDBからPostgreSQLへの移行でしたが、弊社ではSQL ServerやMySQLを採用することが多いためそちらでも試してみたいと思いました。また、弊社ではテーブル設計ガイドラインを作成し、日常的にDBREがテーブル設計レビューを行っています。ガイドラインをナレッジベース化することで、テーブルのCREATE文を元に一次レビューを自動化できるかもしれません。 アプリケーション開発ではGitHub CopilotをはじめとしたLLMによる開発のサポートが既に充実しています。しかし、今後SQLの開発にも同様のことが行えるようになり、DBA/DBREの働き方も大きく変わってくると感じました。 爆速成長中のFindyがぶつかった課題と解決に向けた実践手法 基幹システム本部・物流開発部の黒岩です。 普段はZOZO社内で使用している基幹システムのリプレイスをする傍ら、DBREとしても活動しています。db tech showcaseは今回初めての参加でしたが、どの発表も内容が幅広く、興味深いセッションが多かったです。 私はファンディ株式会社 @gessy0129 げっしーさんの「爆速成長中のFindyがぶつかった課題と解決に向けた実践手法」というセッションについて紹介します。 DB面での課題と解決方法 サービスが急成長し、多くのユーザーに使われていく中で直面したDB面での課題とその解決方法が紹介されました。 .tal{text-align:left !important;} 性能問題 解決方法 DB負荷が高まり、CPU使用率が上昇 RDSからAuroraに移行してリソース利用を効率化 AuroraをARMベースのインスタンスに変更し、同じ性能をより低コストで実現 SQLのレスポンスが悪化 Performance Insighsを活用してクエリパフォーマンスを把握し、ボトルネック箇所を可視化 クエリの実行計画を分析して足りないインデックスを特定、インデックスを追加 インデックスが使われないクエリの形を修正(例:IN句の中に大量のデータが入っている、LIMITの数が適切でない場合など) DBエンジニアとアプリケーションエンジニアの協力 セッションで印象に残ったのは、スピーカーのげっしーさんが言った「性能試験は総合格闘技」という言葉でした。この言葉通り、DBエンジニアだけで解決するのではなく、アプリケーションエンジニアと密に連携して問題解決に取り組んだ例が紹介されていました。具体的には、以下2つの取り組みが印象的でした。 ReaderとWriterのインスタンスノードの振り分け AuroraのReaderインスタンスが有効に活用されていない課題に対して、DBエンジニアとアプリケーションエンジニアがペアプロしながら解決した事例です。DBエンジニアがReaderインスタンスの役割や性能向上の仕組みを説明し、アプリケーションエンジニアがその知識をもとにアプリケーションコードを改修して、書き込み操作時にはWriterインスタンス、読み込み操作時にはReaderインスタンスへ分けるようにしたそうです。これにより、読み込み処理時にReaderインスタンスが使われるようになり、効率的な負荷分散を実現したとのことでした。この取り組みでは、両者がそれぞれの専門知識を共有し、リアルタイムでコードの変更とその効果を確認しながら進めたことが成功の鍵となったそうです。 アプリケーションコード内のクエリ最適化 もう1つの課題として、より複雑な処理をクエリで一括処理するか、データを分割して取得した上で、アプリケーション側で処理するかという課題があったそうです。ここでも相互にペアプロなどで検証しながら性能を検証し、例えば「MySQLが苦手とする集計処理」について、クエリで一括処理していた部分を一部アプリケーション側で処理することで、DBの負荷を軽減した例が紹介されていました。 感想と今後の取り組み このセッションを通じて、DBエンジニアとアプリケーションエンジニアの協力がDBの負荷軽減において非常に重要であることを再認識しました。現在、弊社でも基幹DBの負荷軽減を図るためにクエリチューニングを進めていますが、この取り組みはまだ効果が限定的です。今回のセッションで学んだように、DBの性能課題に真摯に向き合い、基幹システムに関わるチーム全体で性能問題解決に取り組む姿勢を大切にし、今後もさらなる協力と改善を進めていきたいと思います。 【出光興産様事例紹介】SAP DB運用の課題と今後の計画 基幹システム本部・データ連携ブロックの馬場です。現在ZOZOTOWNの基幹システムで使用しているデータベースの性能改善を課題としています。 社内システム統合時に発生したデータベースのスローダウンに対し、どのようなツールを利用し課題対応しているか、という事例紹介のセッションに参加しました。 MaxGauge スローダウンが発生しているボトルネッククエリを調査すべく、データベース監視ツールである「MaxGauge」が紹介されました。「MaxGauge」はデータベースサーバにエージェントを導入することで、リアルタイムにSQLのトラッキングを行い、蓄積されたトラッキングデータにより、パフォーマンス低下対象の事後分析が可能です。 弊社ではDPMツールとして「Datadog」を導入していますが、情報収集に掛かるデータベース側への負荷や、クエリの取得間隔等を比較してみたいです。 MAJESTY 次にデータベース開発支援ツールの「MAJESTY」の紹介です。パフォーマンスが低下しているクエリに対し、最適なインデックス設計をアドバイスしてくれることで効率的に性能分析・改善が可能です。最大の特徴は「アクセスパターン分析」を実装しており、テーブルへのアクセスをパターン化し、最適なアクセスパスを分析することです。これにより、SQL単体を分析する手法よりも大幅にコストを抑えた性能改善が実現可能です。 感想 今回の事例ではSQL Serverを利用し、基幹システムに性能問題を抱えている境遇が弊社と似ているため参考になるセッションでした。 チューニングを行うにはデータベースに対し一定の習熟度が必要になりますが、社内のエンジニア全員が同じ習熟度を保つことは難しいです。本セッションで紹介されたツールを導入することにより、誰もが同じ視点で性能改善を実施し、日々の性能改善コストが大幅に削減できる環境が作れると良いと思います。 SQL Server 技術支援のための動作の調査 - dbts2024 版 - 技術本部SRE部カート決済SREブロックの伊藤です。 こちらのセッションはMicrosoft MVPの小澤さんが、SQL Serverの挙動について、実演を交えて調査方法を紹介してくれるセッションとなっていました。例えば、クエリストアや拡張イベントを有効化する場合にクエリのスループットにどの程度影響が出るかをどのように調べるか、といった内容です。 内容が濃密なこともあり、事例全てを紹介していただくことは時間の都合上できませんでしたが、会場を巻き込んで挙手制でアンケートを取りながら紹介事例を選ぶ姿は、オフラインイベント感を感じる一幕でした。 事例の中で私が最も印象に残ったのは「オンラインでのインデックス再構築による同時実行性の低下の有無」です。 オンライン再構築による同時実行性の低下の有無 SQL Serverではオンラインでのインデックス操作に対応しています。 ONLINE オプションは、このようなインデックス操作中に基になるテーブルまたはクラスター化インデックス データ、ならびに関連付けられた任意の非クラスター化インデックスへ同時ユーザー アクセスを可能にします。 たとえば、あるユーザーがクラスター化インデックスを再構築している最中に、そのユーザーと他のユーザーが基になるデータの更新やクエリを続行できます。 引用元: オンラインでのインデックス操作の実行 この時に、ロックは一切かからないのかを解説交えながら検証の実演が行われました。具体的には以下のような手順です。 長時間かかるようなSELECT文を実行する SSMSから実行するときには結果を出力しないようにクエリオプションで破棄する設定にしておく この時 sys.dm_tran_locks などでロックの状況を確認すると共有ロックがかかっている状態が見られる バックグラウンドでSELECTを大量に実行しておく Ostress を使うことで並列に大量のクエリを流し続けておくことができる REBUILD INDEX WITH(ONLINE=ON)を実行する 最終段階でSch-Mのロックが必要になるため1の処理と競合してロックの解放待ち状態となる この時に2で実行していたクエリをブロックする可能性がある 発生の有無はロックパーティションの状態次第 CPUのコア数で挙動が変わる部分でもある ロックタイムアウトの活用でブロッキングの連鎖は防ぐことができる 後日実際に自分の環境でも試してみた 上記の内容を改めて実際に試してみました。これは過去にオンラインでインデックス作成が行われた際にブロッキングの発生を観測したことがあり、ユーザーに影響するものなので対策含めて運用ドキュメントに取り入れていきたいという思いがあったからです。 実際に流したクエリは以下の通りです。 -- 長時間かかるSELECT文 SELECT a.SampleColumn FROM _testTable AS a CROSS JOIN _testTable AS b -- Ostressから実行したバックグラウンドで動かすSELECT文 SELECT TOP( 1 ) * FROM [dbo].[_TestTable] tablesample( 1 percent) WITH (NOLOCK) -- インデックスのリビルド ALTER INDEX [IX__TestTable_SampleIndex] ON [dbo].[_TestTable] REBUILD WITH ( ONLINE = ON ) GO 上記のクエリをそれぞれ流したところ、インデックスリビルドの最終段階でSch-Mのロック要求が発生していることを確認できました。 sys.dm_tran_locksから確認したロック情報 Datadogが入っている検証環境ということもあり、Datadogからも確認したところ、ブロッキングが連鎖してOstressで流していたクエリも巻き込まれていることが確認できました。 Datadog Database MonitoringのBlocking Queriesからのキャプチャ これに対し、インデックスリビルド時に SET LOCK_TIMEOUT を付けて同様に再現したところロック要求のタイムアウトの発生を確認できました。インデックスのリビルドは失敗するため、改めて実施する必要はありますが、これでユーザー影響を抑えることができます。 -- 1秒ロックが取れなかったらインデックスのリビルドを中断する SET LOCK_TIMEOUT 1000 ; ALTER INDEX [IX__TestTable_SampleIndex] ON [dbo].[_TestTable] REBUILD WITH ( ONLINE = ON ) GO エラーメッセージが出力されて処理が中断 実演を見た上で自分でも試してみて、インデックス操作時の挙動及び調査方法への理解を深められました。インデックス操作時の SET LOCK_TIMEOUT 指定の必須化などは弊社内での運用にも取り入れていこうと思います。 おわりに 今回のセッションを通じて多くの知見を得ることができました。セッション以外にも、企業ブースや他社のエンジニアとの交流を通じて、多くの学びと刺激を受けました。実際に現地に足を運ぶことで得られるものは非常に大きかったです。 db tech showcase 2024で学んだことをZOZOのデータベースシステムの向上に活かしていこうと思います。 ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
はじめに こんにちは。WEARバックエンド部SREブロックの 春日 です。普段は WEAR というサービスのSREとして開発・運用に携わっています。本記事では、約60%のコスト削減に成功した NATゲートウェイ の通信内容の調査方法と通信量の削減方法についてご紹介します。 目次 はじめに 目次 背景 コストの把握 NATゲートウェイの通信内容の把握 CloudWatchメトリクスでの確認 VPCフローログでの確認 リゾルバーでのクエリログでの確認 調査結果をもとにNATゲートウェイ経由での通信量を削減する AWSサービスとの通信 Datadogとの通信 WEARのAPIとの通信 ECRパブリックリポジトリとの通信 結果 まとめ 背景 ZOZOではより効果的な成長を目指してコストの最適化を進めています。コストの増大はサービスの拡大を鈍化させる原因となるため、常に最適な状態に保つことが必要です。WEARでも不要なコストを可能な限り削減し適正化すべく、コスト把握と対応を続けています。 コストの把握 まずはコストを把握します。AWSのコストは AWS Cost Explorer(以下、Cost Explorer) で確認できます。WEARでは『Elastic Container Service for Kubernetes』『S3』『CloudFront』に次いで『EC2その他』に料金がかかっていました。『EC2その他』の料金がそこまで高いことは想定外だったため、『EC2その他』の内訳を確認します。フィルターのサービスを『EC2 - Other』、グループ化の条件のディメンションを『APIオペレーション』とすることで、API単位で料金を確認できます。結果は以下のグラフの通りです 1 。 内訳を確認すると、コストの3分の2ほどがNATゲートウェイのコストでした。さらに詳細に内容を確認します。先ほどまでのレポートパラメータを解除し、フィルターの『APIオペレーション』を『NatGateway』、グループ化の条件のディメンションを『使用タイプ』にします。 NATゲートウェイの料金に関するドキュメント を確認すると、『NATゲートウェイあたりの料金(USD/時)』と『処理データ1GBあたりの料金 (USD)』で構成されています。グラフを見ると、NATゲートウェイの起動時間による料金よりも、NATゲートウェイのデータ処理に関する料金が圧倒的に高いことがわかります。つまり、NATゲートウェイを経由して大量の通信が行われているということが読み取れます。 次章では、NATゲートウェイを経由する通信を詳しく調査します。 NATゲートウェイの通信内容の把握 CloudWatchメトリクスでの確認 まずは Amazon CloudWatch(以下、CloudWatch) でNATゲートウェイの CloudWatchメトリクス を確認します。メトリクスの詳細は 公式ドキュメント に記載があります。 WEARでは、 BytesOutToDestination が BytesInFromDestination の2倍ほどの量でした。これは、NATゲートウェイを経由した外向きの通信量が、NATゲートウェイを経由した内向きの通信量の2倍あることを示します。 VPCフローログでの確認 次に、 VPCフローログ を用いて、より詳細な通信内容を確認します。VPCフローログにはVPC内部のネットワークインターフェイス間の通信内容が記録されています。 Amazon S3(以下、S3) バケットに出力されたVPCフローログを Amazon Athena(以下、Athena) でクエリして通信内容を確認します 2 。 VPCフローログのAthenaテーブルは以下の内容で作成したことを前提に説明します。 VPCフローログテーブル作成クエリ CREATE EXTERNAL TABLE `vpc_flow_logs_table`( `version` int COMMENT '' , `account_id` string COMMENT '' , `interface_id` string COMMENT '' , `srcaddr` string COMMENT '' , `dstaddr` string COMMENT '' , `srcport` int COMMENT '' , `dstport` int COMMENT '' , `protocol` bigint COMMENT '' , `packets` bigint COMMENT '' , `bytes` bigint COMMENT '' , ` start ` bigint COMMENT '' , ` end ` bigint COMMENT '' , `action` string COMMENT '' , `log_status` string COMMENT '' , `vpc_id` string COMMENT '' , `subnet_id` string COMMENT '' , `instance_id` string COMMENT '' , `tcp_flags` int COMMENT '' , ` type ` string COMMENT '' , `pkt_srcaddr` string COMMENT '' , `pkt_dstaddr` string COMMENT '' , `region` string COMMENT '' , `az_id` string COMMENT '' , `sublocation_type` string COMMENT '' , `sublocation_id` string COMMENT '' , `pkt_src_aws_service` string COMMENT '' , `pkt_dst_aws_service` string COMMENT '' , `flow_direction` string COMMENT '' , `traffic_path` int COMMENT '' ) PARTITIONED BY ( `logdate` string COMMENT '' ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ' ' STORED AS INPUTFORMAT ' org.apache.hadoop.mapred.TextInputFormat ' OUTPUTFORMAT ' org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat ' LOCATION ' $LOCATION_OF_LOGS ' TBLPROPERTIES ( ' projection.enabled ' = ' true ' , ' projection.logdate.format ' = ' yyyy/MM/dd/HH ' , ' projection.logdate.interval ' = ' 1 ' , ' projection.logdate.interval.unit ' = ' HOURS ' , ' projection.logdate.range ' = ' 2022/01/01/00,NOW ' , ' projection.logdate.type ' = ' date ' , ' skip.header.line.count ' = ' 1 ' , ' storage.location.template ' = ' $LOCATION_OF_LOGS/${logdate} ' , ' typeOfData ' = ' file ' ) $LOCATION_OF_LOGS はVPCフローログの出力先S3パスに読み替えてください。 WEARではAthenaテーブルの作成を Terraform の aws_glue_catalog_table で行っています。上記のクエリは aws_glue_catalog_table から作成されたテーブルを SHOW CREATE TABLE で出力したクエリのため、公式ドキュメントとは表現が一部異なっています。 次のようなクエリ 3 でVPC内部からNATゲートウェイを経由した外向きの通信を確認します。SQL内にコメントしてある箇所は適宜自分の環境に読み替えてください。 SELECT pkt_dst_aws_service, SUM (bytes) AS bytesTransferred FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 調査対象の日付(UTC) GROUP BY pkt_dst_aws_service ORDER BY bytesTransferred DESC このクエリでは、特定の日付でVPC内部からNATゲートウェイ経由で送信したトラフィック量をAWSサービス 4 ごとに確認しています。この例では1日で調査していますが、Athenaはスキャンしたデータ量に料金が比例します 5 。サービスの規模によってはまず時間単位でクエリし、スキャン量が許容できることを確認してください。 結果の例を以下の表に記載します。 pkt_dst_aws_service が - である箇所はAWSが管理していないIPに向けた通信です。表の数値はWEARの実際の値ではなくダミーデータです。今後出てくるAthenaのクエリ結果はすべて実際の値ではなく、ダミーデータを記載します。 pkt_dst_aws_service bytesTransferred AMAZON 106000 EC2 4500 - 2000 DYNAMODB 1000 CLOUDFRONT 3 GLOBALACCELERATOR 1 続いて、NATゲートウェイを経由してVPC内部で受信するトラフィックも確認します。 SELECT pkt_src_aws_service, SUM (bytes) AS bytesTransferred FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr NOT LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 調査対象の日付(UTC) GROUP BY pkt_src_aws_service ORDER BY bytesTransferred DESC pkt_src_aws_service bytesTransferred EC2 21000 AMAZON 6000 - 5000 CLOUDFRONT 1500 DYNAMODB 1000 GLOBALACCELERATOR 1 この時点で Amazon DynamoDB(以下、DynamoDB) の ゲートウェイエンドポイント が不足しており、NATゲートウェイを経由して通信してしまっていることが読み取れます。 Datadog のように、 IPアドレス範囲を公開 しているサービスを利用している場合、VPCフローログの pkt_dstaddr (送信先IPアドレス)だけで把握も可能です 6 。しかし、VPCフローログのみでは AMAZON の内訳など、具体的にどのエンドポイントに向けて通信しているかがわかりません。これらを把握するため、VPC内部の名前解決の際のクエリログを活用してより詳細に調査します。 リゾルバーでのクエリログでの確認 リゾルバーでのクエリのログ記録 を設定すると、VPC内部で行われた名前解決のクエリログをS3に保存できます。VPCフローログで確認できる送信先のIPアドレスとクエリログの名前解決の結果を突き合わせることで、NATゲートウェイを経由して行われた通信先のドメインが把握できます。 ドキュメント に従ってクエリログのAthenaテーブルを作成します。 リゾルバーのクエリログのAthenaテーブルは以下の内容で作成したことを前提に説明します。 リゾルバーのクエリログテーブル作成クエリ CREATE EXTERNAL TABLE `vpc_dns_query_logs_table`( `version` string COMMENT '' , `account_id` string COMMENT '' , `region` string COMMENT '' , `vpc_id` string COMMENT '' , `query_timestamp` string COMMENT '' , `query_name` string COMMENT '' , `query_type` string COMMENT '' , `query_class` string COMMENT '' , `rcode` string COMMENT '' , `answers` array<struct<rdata:string, type :string,class:string>> COMMENT '' , `srcaddr` string COMMENT '' , `srcport` int COMMENT '' , `transport` string COMMENT '' , `srcids` struct<instance:string,resolver_endpoint:string> COMMENT '' , `firewall_rule_action` string COMMENT '' , `firewall_rule_group_id` string COMMENT '' , `firewall_domain_list_id` string COMMENT '' ) PARTITIONED BY ( `logdate` string COMMENT '' ) ROW FORMAT SERDE ' org.openx.data.jsonserde.JsonSerDe ' STORED AS INPUTFORMAT ' org.apache.hadoop.mapred.TextInputFormat ' OUTPUTFORMAT ' org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat ' LOCATION ' $LOCATION_OF_LOGS ' TBLPROPERTIES ( ' projection.enabled ' = ' true ' , ' projection.logdate.format ' = ' yyyy/MM/dd ' , ' projection.logdate.interval ' = ' 1 ' , ' projection.logdate.interval.unit ' = ' DAYS ' , ' projection.logdate.range ' = ' 2022/01/01,NOW ' , ' projection.logdate.type ' = ' date ' , ' storage.location.template ' = ' $LOCATION_OF_LOGS/$VPC_ID/${logdate} ' , ' typeOfData ' = ' file ' ) $LOCATION_OF_LOGS はリゾルバのクエリログの出力先S3パス、 $VPC_ID はログを記録しているVPCのIDに読み替えてください。 VPCフローログテーブルと同様にAthenaテーブルの作成をTerraformで行っており、上記は SHOW CREATE TABLE で出力したクエリです。そのため、公式ドキュメントとは表現が一部異なっています。 次のようなクエリでVPCフローログとリゾルバーのクエリログを突き合わせます。 SELECT R.query_name, SUM (F.bytesTransferred) AS bytes_day, ROUND ( SUM (F.bytesTransferred) * 30.0 / ( 1000 * 1000 * 1000 ), 2 ) AS GB_months FROM ( SELECT pkt_dstaddr, SUM (bytes) AS bytesTransferred FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 調査対象の日付(UTC) AND pkt_dst_aws_service = ' AMAZON ' GROUP BY pkt_dstaddr ) F LEFT JOIN ( SELECT DISTINCT query_name, answer.rdata FROM " vpc_dns_query_logs_database " . " vpc_dns_query_logs_table " -- リゾルバーのクエリログテーブルが存在するデータベース.リゾルバーのクエリログテーブル CROSS JOIN UNNEST(answers) as st(answer) WHERE answer. type = ' A ' AND logdate = ' YYYY/MM/dd ' -- 調査対象の日付(UTC) ) R ON F.pkt_dstaddr = R.rdata GROUP BY R.query_name ORDER BY bytes_day DESC このクエリでは、大きく分けて3つのことを行っています。 VPCフローログテーブルからNATゲートウェイを経由するトラフィックの送信先IPアドレスを取得(クエリの F 部分) リゾルバーのクエリログテーブルからドメインとIPアドレスの対応表を作成(クエリの R 部分) 1と2をIPアドレスで結合し、送信先ドメインごとのトラフィック量を取得 クエリの結果を確認すると、 firehose.ap-northeast-1.amazonaws.com. と sqs.ap-northeast-1.amazonaws.com. に対するトラフィック量が多いことを確認できました。WEARでは、アプリケーションのログを aws-for-fluent-bit を用いて Amazon Data Firehose(以下、Firehose) に送信しています。このサービスに対する送信がほとんどであり、 インターフェイスVPCエンドポイント が不足していることに気付けました。 Amazon Simple Queue Service(以下、SQS) も同様に、VPCエンドポイントが不足していることが判明しました。 他の箇所についても同様に調査すべく、 pkt_dst_aws_service = 'EC2' などに変更しながらトラフィック量を確認していきます 7 。 その結果、以下のドメインに対しての通信量が多いことを確認できました。 d5l0dvt14r5h8.cloudfront.net. に見覚えはありませんでしたが、AWSサポートに確認したところ、 Amazon ECR パブリックリポジトリ(以下、ECRパブリックリポジトリ) であることが判明しました(2024年7月現在)。 *.datadoghq.com WEARのAPI d5l0dvt14r5h8.cloudfront.net. これで通信内容が判明しました。NATゲートウェイ経由で大量に通信しており、削減効果が見込めそうな宛先は以下の通りです。 AWSサービス Datadog WEARのAPI ECRパブリックリポジトリ 次章からはこの結果を元に、可能な限りNATゲートウェイを経由せずに済むように対応します。 調査結果をもとにNATゲートウェイ経由での通信量を削減する AWSサービスとの通信 AWSサービスをNATゲートウェイ経由で通信させないためには、VPCエンドポイントが必要です。ただし、通信するすべてのAWSサービスに対してVPCエンドポイントを用意すればいいとは限りません。VPCエンドポイントにも起動時間による料金と、データ処理による料金が発生するためです。 インターフェイスエンドポイントの料金 を確認し、 NATゲートウェイで通信する場合の料金 との損益分岐点を確認します。 NATゲートウェイはすでに存在し、外部への通信に利用しています。NATゲートウェイは削除できないため、NATゲートウェイの起動時間に関するコストは考慮しないことにします。また、インターフェイスエンドポイントはAZごとに起動時間の料金がかかります。WEARでは3AZを利用しているため、3つとして計算します。 ap-northeast-1 リージョンの料金は以下のようになっています(2024年7月現在)。 NATゲートウェイの料金表 NAT ゲートウェイあたりの料金 (USD/時) 処理データ 1 GB あたりの料金 (USD) USD 0.062 USD 0.062 インターフェイスエンドポイントの料金表 各 AZ の VPC エンドポイント 1 つあたりの料金 (USD/時間) USD 0.014   AWS リージョンで 1 か月に処理されるデータ 処理データ 1 GB あたりの料金 (USD) 最初の 1 PB 0.01 USD 次の 4 PB 0.006 USD 5 PB以上のもの 0.004 USD 1か月あたりの通信量(GB)を とし、以下の式を満たす を計算します。VPCエンドポイントの起動時間は 24時間*30日*3AZ で計算しています。また、調査時に概算した結果、1ヶ月に1PB以上は使っていないため、最初の1PBの料金で計算します。 すると となるため、月に581GB以上通信するのであればNATゲートウェイ経由よりもVPCエンドポイント経由の方が安いということが導けます。 そのため、WEARではFirehoseとSQSのインターフェイスエンドポイントを追加で作成することにしました。ゲートウェイタイプのVPCエンドポイントの場合は追加料金なしで利用できる 8 ため、DynamoDBのVPCエンドポイントも作成します。 Datadogとの通信 Datadogは AWS PrivateLink(以下、PrivateLink) を経由して通信する方法を提供しています 9 。ただし、調査の過程でWEARではこの方法は断念しました。 Datadogには サイト という概念があります。各サイトは完全に独立しており、サイト間でデータの共有はできません。WEARでは Amazon Elastic Kubernetes Service(以下、EKS) のリージョンと使用しているDatadogサイトの場所が異なっていました。その場合、『VPCピアリングを使用した他のリージョンからの接続』ですが、リージョン間の通信は、2024年7月時点で 1GBあたり$0.09 かかってしまいます。そのため、WEARではDatadogへの通信に関してはNATゲートウェイを経由することを許容しました。ご利用中のDatadogサイトのPrivateLinkとデータ送信元が同一リージョンにある場合はPrivateLinkを用いる方法を検討してみてください。 WEARのAPIとの通信 調査結果から、WEARのWebアプリケーションからAPIへの通信がNATゲートウェイを経由して行われていることがわかりました 10 。通信経路の概略は以下の図の通りです。AWS間の通信のためインターネットには出ていませんが、NATゲートウェイを経由して通信してしまっています。 これらは同一VPCに存在しているため、VPC内部のみで通信を完結させたいところです。WEARのEKS内のPodは Application Load Balancer(以下、ALB) の配下に存在します。調査時点ではインターネット向けのALBのみ存在しましたが内部向けのALBも作成し、VPC内部からの通信は内部向けのALBに対して行うようにします。 WEARでは、ALBとALBのエイリアスレコードの作成を AWS Load Balancer Controller と ExternalDNS を用いてIngressに専用のアノテーションを付与することで行っています。 既存のIngressを踏襲し、新たに内部向けALB用のIngressを作成します。 alb.ingress.kubernetes.io/scheme アノテーションのデフォルト値は internal ですが、後述する理由により明示的に internal を設定しておきます。 以下に内部向けALB作成のサンプルIngressを記載します。また、内部向けALBを作成するプライベートサブネットに自動検出用のタグ 11 が付与されていることを確認してください。 apiVersion : networking.k8s.io/v1 kind : Ingress metadata : name : api-internal namespace : api annotations : kubernetes.io/ingress.class : alb alb.ingress.kubernetes.io/scheme : internal external-dns.alpha.kubernetes.io/hostname : api.wear.jp # インターネット向けIngressのものと同じ # (以下略) spec : rules : - http : paths : - path : / pathType : Prefix backend : service : name : api port : number : 80 次に、作成する内部向けALBのエイリアスレコードを登録するためのプライベートホストゾーンと、プライベートホストゾーン用のExternalDNSを新たに作成します。プライベートホストゾーン名はエイリアスレコードのレコード名と一致させます。 ここで1つ注意しなければならないことがあります。 すでに通信が行われているドメインに対して新たにプライベートホストゾーンを作成する場合は、エイリアスレコードを作成するまではEKSのVPCにプライベートホストゾーンを関連付けてはいけない ということです。 プライベートホストゾーンを作成した時点で、関連付けされているVPC内部の通信はそのプライベートホストゾーンで名前解決を試みます。しかし、プライベートホストゾーンの作成とエイリアスレコードの作成は同時にできないため、名前解決に失敗してしまいます。プライベートホストゾーンは作成時に必ず1つ以上のVPCを関連付けなければならないため、使用していないVPCのみを一時的に関連付けておきます。 以下に Terraform を用いたサンプルコードを記載します。ここでは、使用していないデフォルトのVPCを一時的にプライベートホストゾーンに関連付けしています。 resource " aws_route53_zone " " private_api " { name = " api.wear.jp " vpc { # 一時的にdefault VPCを指定。 vpc_id = data.aws_vpc.default.id vpc_region = " ap-northeast-1 " } force_destroy = false } # 一時的にdefault VPCを指定するためのデータソース data " aws_vpc " " default " { default = true } 同じドメインでパブリックホストゾーンとプライベートホストゾーンを出し分けるためにプライベートホストゾーン用のExternalDNSを新たに作成します 12 。以下のオプションで起動します。 --aws-zone-type=private --annotation-filter=alb.ingress.kubernetes.io/scheme=internal --domain-filter=${プライベートホストゾーン名} 内部向けALBのためのアノテーションがついているリソースのみを対象に設定しています。これが、デフォルト値にもかかわらず明示的にIngressにアノテーションを設定する理由です。 元々起動していたパブリックホストゾーン用のExternalDNSには --annotation-filter=alb.ingress.kubernetes.io/scheme=internet-facing を用いて再起動し、インターネット向けALB用のリソースのみを対象にします。 ExternalDNSの準備ができたら内部向け用IngressをEKS内に作成し、内部向けALBとALBのエイリアスレコードが作成されていることを確認します。一時的に関連付けておいたVPC内部からdigコマンド等で名前解決し、プライベートアドレスに解決されることも確認しておきます。確認後、プライベートホストゾーンをEKSのVPCに関連付けし、一時的なVPCの関連付けは解除します。 以下にサンプルコードを記載します。ここでは、プライベートホストゾーンに関連付けされているVPCを、使用していないVPCからvariablesに設定されたEKSのVPCに変更しています。 variable " vpc_id " { type = string description = " EKSのVPC ID " } resource " aws_route53_zone " " private_api " { name = " api.wear.jp " vpc { vpc_id = var.vpc_id vpc_region = " ap-northeast-1 " } force_destroy = false } 最終的な通信経路の概略は以下の図の通りです。これで、WEARのWebアプリケーションからAPIへの通信がVPC内部で完結するように設定できました。 ECRパブリックリポジトリとの通信 WEARでは、aws-for-fluent-bitを初めとする、複数のコンテナイメージでECRパブリックリポジトリのものを多用しています。 ドキュメント には以下のような記載があります。 現在、VPC エンドポイントは Amazon ECR パブリックリポジトリをサポートしていません。プルスルーキャッシュルールを使用して、VPC エンドポイントと同じリージョンにあるプライベートリポジトリでパブリックイメージをホストすることを検討してください。 上記の案内通り、プルスルーキャッシュルールを使用することにします。プルスルーキャッシュルールを使用すると、DockerHubやECRパブリックなどにあるリポジトリを自分のAWSアカウントのプライベートリポジトリにキャッシュしておくことができます。あらかじめ手動でイメージをプッシュしておく必要はなく、自分のリポジトリからプルしようとした際にイメージが存在しなければ、自動的に設定先のリポジトリからプルしてイメージを格納しておいてくれます。 詳細は ドキュメント をご参照ください。また、WEARでは Amazon Elastic Container Registry(以下、ECR) 用のVPCエンドポイントはすでに作成してあったため、新たに作成する必要はありませんでした。 以下にTerraformを用いたサンプルコードを記載します。 resource " aws_ecr_pull_through_cache_rule " " ecr_public " { ecr_repository_prefix = " ecr-public " upstream_registry_url = " public.ecr.aws " } 設定完了後、マニフェストのイメージを以下のように書き換えます。 $ACCOUNT_ID はプライベートリポジトリが存在するAWSアカウントのIDです。また、WEARでは元々DatadogのコンテナイメージをHelmのデフォルト値である gcr.io/datadoghq からプルしていましたが、このタイミングでECRに切り替えました 13 。 - image: public.ecr.aws/aws-observability/aws-for-fluent-bit + image: $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-public/aws-observability/aws-for-fluent-bit これで、初回プル時にはNATゲートウェイを経由してイメージがプルされますが、その後はVPCエンドポイント経由でプライベートリポジトリからプルされるようになりました。 結果 対応完了後、NATゲートウェイを経由する通信量がどのくらい減ったのかを確認します。まずは、以下のクエリでVPC内部からNATゲートウェイを経由した外向きの通信に対して対応前後の削減量を確認します 14 。 SELECT B.pkt_dst_aws_service AS pkt_dst_aws_service, ROUND ( CAST (B.bytes_day- COALESCE (A.bytes_day, 0 ) AS double)/B.bytes_day* 100 , 2 ) AS Reduction_percentage FROM ( SELECT pkt_dst_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応前の日付(UTC) GROUP BY pkt_dst_aws_service ) B LEFT JOIN ( SELECT pkt_dst_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応後の日付(UTC) GROUP BY pkt_dst_aws_service ) A ON B.pkt_dst_aws_service = A.pkt_dst_aws_service ORDER BY B.bytes_day DESC pkt_dst_aws_service Reduction_percentage AMAZON 99.91 EC2 -2.82 - -2.14 DYNAMODB 100.0 CLOUDFRONT 93.75 GLOBALACCELERATOR 100.0 結果を確認すると、 AMAZON への通信量が99.91%、 CLOUDFRONT が93.75%、 DYNAMODB への通信量が100.0%削減できていることがわかりました。VPCエンドポイントがうまく作用しているようです。増えている箇所もありますが、通信量は日によって誤差があるため対応によるものではありません。NATゲートウェイを経由してVPC内部に受信する通信に関しても確認します。 NATゲートウェイを経由する内向き通信の削減量確認クエリ SELECT B.pkt_src_aws_service AS pkt_src_aws_service, ROUND ( CAST (B.bytes_day- COALESCE (A.bytes_day, 0 ) AS double)/B.bytes_day* 100 , 2 ) AS Reduction_percentage FROM ( SELECT pkt_src_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr NOT LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応前の日付(UTC) GROUP BY pkt_src_aws_service ) B LEFT JOIN ( SELECT pkt_src_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr NOT LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応後の日付(UTC) GROUP BY pkt_src_aws_service ) A ON B.pkt_src_aws_service = A.pkt_src_aws_service ORDER BY B.bytes_day DESC 結果は以下の通りです。WEARのAPI( EC2 )への通信がVPC内部で完結したこと、プルスルーキャッシュルールによって CLOUDFRONT や外部サービスへの通信回数が減ったことで通信量が大幅に減っています。 pkt_src_aws_service Reduction_percentage EC2 89.15 AMAZON 98.22 - 63.65 CLOUDFRONT 99.14 DYNAMODB 100.0 GLOBALACCELERATOR 100.0 Cost Explorerで対応前後の1日毎のグラフを確認します。最終的に対応が完了したのは6/6頃です。グラフの通り、大幅にコストを減らせました。NATゲートウェイのコストだけで言うと、80%ほど削減できました。 しかし、NATゲートウェイを経由しなくなった分、VPCエンドポイントのコストが増えているはずです。そちらも確認します。APIオペレーションに『VpcEndpoint』も追加し、グラフを確認します。 VPCエンドポイントの通信コストを加味しても、コストを大幅に削減できています。対応前のNATゲートウェイとVPCエンドポイントの総額で計算すると、最終的には60%ほど削減できました。 まとめ 本記事ではNATゲートウェイの通信内容の調査と通信量の削減方法について紹介しました。VPCフローログとリゾルバーのクエリログを確認することで詳細な通信内容を把握できました。通信内容に応じて適切な対応をした結果、約60%のコストを削減できました。NATゲートウェイのコスト削減を検討している方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 結果画像のy軸はマスク処理を施してあります。今後出てくるCost Explorerの画像はすべてy軸がマスク処理済みのものです。 ↩ Amazon VPC フローログのクエリ ↩ 参考: サンプルクエリ - Amazon CloudWatch Logs ↩ ここでのAWSサービスはすべてのサービス名ではなく、VPCフローログの pkt-src-aws-service フィールドの値で表示されるもの(参考: VPC フローログを使用した IP トラフィックのログ記録 - Amazon Virtual Private Cloud ) ↩ Amazon Athena の料金 ↩ Datadogへの通信は、ほとんどが pkt_src_aws_service = 'EC2' に内包されています。 ↩ わかりやすさのため pkt_dst_aws_service ごとにクエリを実行していますが、このカラムにはパーティションが設定されていないため、この条件句によってスキャン量を減らすことはできません。Athenaのスキャン量による料金を減らしたい場合、 pkt_dst_aws_service にパーティションを設定することを検討するか、この条件を削除し、1回のクエリですべてを出力してください。 ↩ ゲートウェイエンドポイント - Amazon Virtual Private Cloud ↩ AWS PrivateLink を介して Datadog に接続する ↩ 背景: WEAR Webフロントエンドリプレイスのアーキテクチャ選定とNext.jsへの移行 ↩ キー名: kubernetes.io/role/internal-elb 、値:1のタグが必要(参考: Amazon EKS でのアプリケーション負荷分散 ) ↩ 参考: ExternalDNSでPrivate Hosted ZoneとPublic Hosted Zoneにレコードを出し分ける | DevelopersIO ↩ Docker 環境のコンテナイメージ ↩ わかりやすさのために1つのクエリにしていますが、対応前の結果は最初にクエリした際どこかにメモしておき、対応後の日付だけクエリして手動で比較する方がAthenaの料金上良いと思います。 ↩
アバター
こんにちは、ZOZOTOWN開発本部でZOZOTOWN iOSの開発を担当している らぷらぷ です。 今年のWWDCもワクワクする情報が目白押しでしたね。個人的にはApple Intelligenceが今後どんな進化を果たし、日常生活をどう変えていくのかが楽しみです。 本記事では、ZOZOのiOSエンジニアが注目したセッションや、参加したラボで得た知見を紹介します。 現地参加されたメンバーによるレポートもありますので、ぜひ御覧ください。Appleのスタッフや各国の開発者との交流や、現地に行く人向けのアドバイスなどまとまっております。 techblog.zozo.com オンライン組メンバーのキャッチアップ Sessions UI updates Translation API Apple Vision Pro Labs Xcodeによって実行されるResolve Packagesの時間について SPM移行で遭遇したバグや不明点を聞いてみた メモリ関連で気になることをひたすら聞いてみた まとめ オンライン組メンバーのキャッチアップ 現地に参加しなかったオンライン組メンバーは、各自キャッチアップした情報をMiroに集め、毎日30分から1時間ほどビデオ通話でシェアし合いました。このブログに書きたいセッション、ラボで聞きたいこと、社内の他のチームにも共有したいこと、いち開発者として気になることなど、思い思いに話しました。 特にセッションは「可能な限り全部見たい!」という気持ちになりますが、KeynoteとPlatforms State of the Union、Recapを除くと全117セッションあります。とはいえ、見るべきセッションを選ぶのはなかなか難しいです。最初の2、3分を見て判断しようかな…と見始めたら最後まで見てしまったこともあります。 セッションの要約があればじっくり見るべきか判断しやすいかも、ということで今年はAIチャットボットにセッションの字幕を要約してもらってから内容を深ぼるか判断しました。例として「 Bring your app to Siri 」を挙げましょう。概要は以下のように記載されています。 Learn how to use SiriKit and App Intents to expose your app's functionality to Siri and Apple Intelligence. Discover which intents are already available for your use, and how to adopt App Intent domains to integrate actions from your app into the system. Find out what metadata to provide, how to make your entities searchable with Spotlight, how to integrate your app with in-app search, and much more. 以下が、こちらのセッションの字幕をAIチャットボットに要約してもらった結果です。 .typesquare_tags ol {width: calc(100% - 1em) !important; margin-left:0 !important;} .typesquare_tags ul {margin-left:1em !important; width: calc(100% - 3em) !important;} Apple IntelligenceとAssistant Schemasの導入により、Siriとアプリケーションのインテグレーションがより強力かつ柔軟になりました。開発者は新しいAPIを活用することで、より自然で個人化されたユーザー体験を提供できるようになります。この新しい機能は、iOS 18以降で順次展開される予定です。 既存のフレームワーク: SiriKit: iOS 10で導入され、システムが提供するインテントを使用します。例えば、音楽の再生やテキストメッセージの送信などの一般的なアクションに適しています。 AppIntents: iOS 16で導入され、より柔軟なアプリ統合を可能にします。SiriKitのドメインと重複しない機能に適しています。 Apple Intelligenceによる改善: 新しい大規模言語モデルにより、以下の改善がなされています: より自然な会話 文脈理解の向上 個人化された体験 画面上の情報の理解と活用 新しいApp Intent domains: 12の新しいドメイン(メール、写真、カメラ、スプレッドシートなど)が導入され、100以上の新しいアクションをサポートします Assistant Schemas: 事前定義された「スキーマ」に基づいてインテントを実装します。これにより、コードが簡素化され、コンパイル時の検証が可能になります 個人コンテキスト機能: アプリ内検索: ShowInAppSearchResultsIntentを拡張し、Siriがアプリの検索機能を直接利用できるようになります。 セマンティック検索: 新しいIndexedEntityAPIを使用して、アプリのコンテンツをSiriの意味検索に利用可能にします。 テストと実行: ショートカットアプリを使用して、新しいインテントをテストできます。 将来的には、これらのインテントが自動的にSiriと連携するようになります。 これだけ情報が揃っていると、動画を見てさらに掘り下げるかどうかを判断しやすくなります。掘り下げる場合、このようにセッション動画の構成と詳細が分かっていると、集中して視聴すべきパートを絞ることができます。さらに、要約文内の気になるキーワードについてAIチャットボットに質問しておくことで理解を深められます。 今回この方法を試したことで、動画中のスライドによる解説を理解する時間をグッと短縮できました。 Sessions UI updates ZOZOTOWNのiOSアプリの開発を担当している荻野と、WEARのiOSアプリの開発を担当している山田です。 今回の発表でも、SwiftUIの幅広いアップデートがありました。これまでは、SwiftUIの機能が少ないため社内導入を見送っていた人もいると思います。しかし、SwiftUIはますます使いやすくなっており、もう見逃すことはできなくなってきているのではないでしょうか。まだセッションを見ていない方、まずは「 What’s new in SwiftUI 」からご覧ください。 特に注目すべきは、ズームトランジションのアニメーションが新たに加わったことです。ズームトランジションとは、別の画面へ遷移する際に画面がズームするようなアニメーションです。ZOZOTOWNやWEARでは以前から独自にズームトランジションを実装していましたが、今回のアップデートで標準のズームトランジションが利用できるようになったのは大きな進歩です。さらに、SwiftUIだけでなくUIKitでも対応しているのは非常に嬉しい発表でした。詳細は「 Enhance your UI animations and transitions 」のセッションで確認でき、ドキュメントベースでは「 Enhancing your app with fluid transitions 」で、Viewの状態変化を図を用いてわかりやすく説明しています。ZOZOTOWNのiOSアプリでもズームのアニメーションを行なっている部分があるので、今後の改善に活かしていきたいです。 また、「 Add personality to your app through UX writing 」で紹介されたアプリに個性を出すためのUXライティングにも注目しました。このセッションは、アプリ内の文章をデザインするときに考える声・トーンの一貫性、いわゆるトンマナをAppleのHuman Interface Designチームがどう合わせているのかを解説しています。 具体的には、アプリを人に見立てたとき、どんな性格になるか形容詞を考え、それらをグルーピングします。これがアプリの声の定義となり、声の定義を満たすように文章を作ります。例えば、お金を貯めることが目的のアプリでも、子供向けの貯金アプリか長期的な投資を支援するアプリかどうかで、表現するべき言葉はまったく異なります。 ZOZOTOWNやWEARでも、それぞれのアプリでお客様が洋服を買う・コーディネートを投稿するモチベーションを最大化できるような文章を作るように心がけています。セッションを通してまだまだ改善できる部分があると感じたので、デザイナーやビジネスサイドにもこのセッションを共有しようと思います。 Translation API こんにちは、FAANS部iOSチームの加藤です。今年のWWDCでは英語が苦手な私にとって、とても嬉しいAPIの紹介がありました。そうです! Translation APIです! Translation APIに関するセッションは「 Meet the Translation API 」です。ぜひご覧ください。 Translation APIはAppleの新しいAPIであり、Swiftで作られたアプリに強力な翻訳機能を簡単に組み込むことができます。Translation APIは、Appleの翻訳アプリにおいて翻訳対象である20言語に対応しており、入力された言語の種類を自動で認識して、任意の言語に翻訳できます。また、翻訳したい言語の翻訳モデルを事前にダウンロードしておくことで、オフラインの環境でも翻訳が行える点も非常に便利です。 ZOZOの開発するアプリでは、アイテムの詳細説明や、アイテムに対するレビューを閲覧できる機能が備わっています。日本語を読めないユーザーがZOZOのアプリを使用する場合には、アイテムの詳細説明やレビューを理解することが難しく、アプリに掲載されているアイテムの良さが伝わらないかもしれません。こういった問題も、Translation APIを用いた翻訳機能を導入することで解決でき、より多くのユーザーがZOZOのアプリを使ってくれるきっかけになるかもしれません。 私も英語が苦手で英語との間に分厚い壁を感じていたので、Translation APIを組み込んだアプリがこの壁を打ち破ってくれることをとても楽しみにしています。 Apple Vision Pro ARやVRといったXR領域のリサーチや検証などを担当している創造開発ブロックの @ikkou です。頭はひとつしかないのにVRヘッドマウントディスプレイやARグラスはたくさん持っています。昨年のWWDC23で発表されたApple Vision Proがいよいよ日本でも発売されました。 去年のWWDC23レポート記事 で私は『決してお安いお買い物ではありませんが、アーリーアダプター気質のある開発者は間違いなく買うでしょう。もちろん私も買います。』と書きましたが、予告通りApple Vision Proを買いました。当時は約50万円想定だったものが円安の影響で約60万円になったのは想定外の出費となりましたが、今も毎日使っています。家族の一瞬を立体的に残せる空間ビデオは最高ですし、この文章もApple Vision ProをMacの仮想ディスプレイとして利用して書いています。サンクコスト効果も働いていますが、少なくとも現時点では “装着する” というひと手間をかけるだけの価値があるデバイスだと感じています。 そんなApple Vision Proですが、Appleが力を入れていることはWWDC24のvisionOSに関連するセッション数の多さからも伝わってきます。非常に多くのvisionOS関連セッションが用意されていますが、中でも「 Design great visionOS apps 」は必見です。Appleのプラットフォームにとって初めての空間コンピューティングであるApple Vision Proに最適化したアプリを作るための重要なリファレンスとなるセッションです。 また、空間コンピューティングが当たり前になった世界では、ブラウザの世界も2Dから3Dに変容を遂げます。つまりvisionOS対応アプリをリリースせずとも、空間Webへの対応が当たり前になる世界線も十分に考えられます。そういった観点から「 Optimize for the spatial web 」セッションも非常に重要です。このセッションはiOSエンジニアだけでなくWebフロントエンドエンジニアも認識しておくべき内容に溢れています。 Apple Vision Proは非常に高価なデバイスであるため、キャズムを超えるまで時間がかかると考えています。そういった状況で今すぐvisionOS対応アプリを開発するべきか悩ましいかもしれませんが、開発者視点だけで言えば着手するべきだと考えています。盛り上がりを形作る今が特に楽しい時期です。もちろんビジネス的な側面で言えば、まだまだ母数が少ないであろうApple Vision Proに開発リソースを割くのであれば、別のところに割くべき、という考え方もあります。しかし、長らくXRを推している身としては、今こそ機運が高まって欲しいと願わずにはいられません。 Labs Xcodeによって実行されるResolve Packagesの時間について ZOZOTOWN開発本部のとしです。今回はXcodeによって実行されるSPMのResolve Packagesに毎回かなりの時間がかかっていることが、開発をしていく中でネックになっていたので、この問題について相談しました。 結論から言うとこのLabを通じて、クリティカルな解決方法が見つかったわけではありません。しかし、画面を共有することでAppleのエンジニアが意図していない挙動であること、ワークアラウンドを発見できたこと、逆に自分たちが考えていた手法は実行できないということなどが分かりました。このLabでの結果によって今後の調査のアプローチの方向性がわかったので意義のあるLabになりました。 やはり、Apple側としてもその不具合を再現するプロジェクトとFeedback Assistantを使用したBug Reportingがあるとかなり助かるようでした。 SPM移行で遭遇したバグや不明点を聞いてみた 計測プラットフォーム開発本部の中岡です。私は以前執筆した計測フレームワークをCocoaPodsからSPMに移行する作業の中で遭遇したバグや不明点について2つ質問しました。 techblog.zozo.com 1つ目がObjective-CのSwift Packageのtarget.nameに”-”が含まれている場合、リソースバンドルのアクセスに失敗する問題です。ラボの前に自分なりにOSSのSwift Package Managerを調査して原因となっているであろう箇所を 修正するPR を提出していました。そしてこれについて議論をしたのですが、結果としてSwift Pacakge ManagerではなくXcode側のバグだろうとのことでした。そのためLabsが終わった後にバグを再現させるプロジェクトを作りフィードバックを送りました。 2つ目がswiftSettingsについてです。計測フレームワークにはswiftSettingsを使った分岐処理があり「これを利用する側から設定を変更できないか」という相談をしました。結果として、現状Swift Packageにはそのような機能はありませんでした。しかし以下のforumsでPackage traitsという機能が議論されていることを教えていただきました。まさに求めていた機能なので今後に期待です。 forums.swift.org メモリ関連で気になることをひたすら聞いてみた ZOZOTOWN開発本部のらぷらぷです。最近メモリ効率を上げるための調査や対応に興味があります。Appleのエンジニアはどんな関心を持ってメモリ効率について学んでいるのかが気になり、Performance, power, and stability consultationにてお聞きしました。 お話ししたエンジニアの方曰く、まずはメモリを観察することを好きになり、毎日メモリ使用量を観察して妥当な使用量を見極め、異常値が発生してから分析することを続けましょうとのことでした。よく考えればiOSに限らないパフォーマンスチューニングの観点と一緒ですね。 その他、Memory graphの生成の詳細を解説してくれたり、「 Analyze heap memory 」を例にメモリー安全なコードを紹介してくれたりしました。メモリー安全の話からSwift Concurrencyの話に移り、InstrumentsでSwift Concurrencyをデバッグする方法も教えていただきました。 次々と会話が進み、30分では足りない時間を過ごしました。 具体的なコードの課題をもっていなくても、こうしてAppleのエンジニアの考えていることを聞けて、そこからセッションの視聴を提案されるのもラボの魅力のひとつです。 まとめ 本記事では、WWDC24における弊社iOSエンジニアの取り組み、注目したセッション、ラボから得た知見をお伝えしました。今年も数多くのセッションやラボから学びを得ることができました。これらをZOZOのサービスの進化にどう活かすか、今後の課題として取り組んでいきます。 ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは、推薦基盤ブロック、新卒1年目の 住安宏介 です。普段は推薦システムの開発・運用を担当しています。 2024年6月に開催されたコンピュータビジョン・パターン認識分野において世界最高峰の国際会議の1つであるCVPR(Conference on Computer Vision and Pattern Recognition)2024に参加しました。参加レポートとして発表内容や参加した感想を紹介いたします。また、最後にZOZO NEXTが行っているワークショップのスポンサー活動についてZOZO Researchの清水から紹介いたします。 目次 はじめに 目次 CVPR とは 開催地のシアトルについて 学会のスケジュール 企業展示ブースの様子 ポスターセッションの雰囲気 採択数増加に伴うポスターセッションの懸念とその実際 特に、印象に残った研究発表 SLICE: Stabilized LIME for Consistent Explanations for Image Classification [Highlight] CAM Back Again: Large Kernel CNNs from a Weakly Supervised Object Localization Perspective Comparing the Decision-Making Mechanisms by Transformers and CNNs via Explanation Methods [Award Candidate] Learning to Rank Patches for Unbiased Image Redundancy Reduction Identifying Important Group of Pixels using Interactions CVFAD におけるスポンサーの取り組み さいごに CVPR とは 先述の通り、 CVPR はコンピュータビジョン・パターン認識分野における世界最高峰の国際会議の1つです。近年、CVPRはそのプレゼンスを大きく高めており、採択された論文の影響力も著しく向上しています。Google Scholarのh5-indexによる全分野の学術誌・国際会議のランキングで、Nature、New England Journal of Medicine、Scienceに次いで4位にランクインしています。このことは、CVPRにおける研究成果が、量だけでなく質においても非常に高く評価されていることを示しています。CVPRは、そのプレゼンスの高まりとともに、コンピュータビジョンとパターン認識の分野における最新の研究成果を発表する場として、世界中の研究者や開発者から高い注目を集めています。 開催地のシアトルについて 開催地となったシアトルは、美しい自然環境に恵まれた都市として有名です。例えば、日本で人気のコーヒーの名前の由来となったマウント・レーニアや、ユネスコ世界遺産に登録されているオリンピック国立公園があります。また、シアトルは世界的に有名なテック企業が集まる都市でもあります。そのため、CVPRに参加された研究者の間では、これらの企業のオフィスツアーに参加する動きも見られ、活発な交流が行われていました。 シアトルの街並み 会場となったシアトルコンベンションセンター 学会のスケジュール 学会の スケジュール は以下のように構成されています。 1日目から2日目 ワークショップ チュートリアル 3日目から5日目(本会議) キーノート パネルディスカッション オーラル発表 ポスター発表 企業展示 AIアートギャラリー この中で特に興味深かった企業展示ブースの様子、ポスターセッションの雰囲気、そして私が予期していた採択数増加に伴うポスターセッションの懸念とその実際について紹介します。 企業展示ブースの様子 企業展示ブースでは、各会社が最新の研究成果や実際の活用事例を紹介していました。50を超える企業が参加しており、どのブースも大変賑わっていました。また、展示内容も非常に興味深くワクワクするものばかりで、とても楽しい時間を過ごせました。特に興味深かったのは Zoox の無人運転車両です。この車両は運転席が不要であるため、興味深いことに座席が対面で配置されていました。私はペーパードライバーであるため、導入されることを楽しみに待っています。 企業デモの様子 Zooxの無人運転車両 ポスターセッションの雰囲気 ポスターセッションとは、発表者が研究内容をポスター形式で展示し、聴講者と直接対話しながら説明と議論をする発表形式です。最も権威のある国際会議の1つであるため、参加者の熱気がとても感じられました。また、発表された研究の多くが魅力的であり、一人の発表者に多くの聴講者が集まる場面も多々見られました。多数の聴講者に対応するため、チームで発表するケースも見られました。 国内の学会では最初から最後まで説明し終えた後に質問を受ける流れが多いですが、今回は概要を話してから適宜聴講者の質問を元に会話するという形が主流でした。私もこの形で大学院時代の研究内容を発表しました。 実際のポスターセッションの様子1 採択数増加に伴うポスターセッションの懸念とその実際 本会議での論文採択数は年々増加しており、すべての興味のある論文を把握しきれない、またはすべての興味のあるポスターを聴講できない可能性が懸念されました。今年のポスターセッションは各90分で、約450件のポスターが展示されていたため、単純計算で1つのポスターにかけられる時間は約12秒しかありません。このため、事前に聴講するポスターを決めておくなどの工夫が必要でした。 しかし、この懸念にもかかわらず、ポスターセッションは予想以上に効率よく回れました。その理由の1つに、CVPRが提供する採択論文の推薦システムを活用することで、事前に興味のある論文を特定できたことが挙げられます。このシステムにより、興味のある論文を事前にチェックするのが非常に容易になりました。 また、興味のあるポスターが会場内で分散していると移動や探索が大変になる可能性がありました。ですが、分野ごとにポスターが配置されていたため、関連する研究を効率的に回れ、思ったよりも負担ではありませんでした。実際に、私はセッション内で大体6から9件ほどの研究を聴講でき、満足感が高かったです。 実際のポスターセッションの様子2 www.scholar-inbox.com 特に、印象に残った研究発表 以上では、会議中の様子を紹介しました。ここからは、私が興味を持っているコンピュータビジョン分野の深層学習モデルの説明可能性について紹介いたします。 SLICE: Stabilized LIME for Consistent Explanations for Image Classification [Highlight] Revoti Prasad Bora、Kiran Raja、Philipp Terhörst、Raymond Veldhuis、Raghavendra Ramachandra 深層学習モデルの説明可能性を高めるための有名な手法の1つとして、LIME(Local Interpretable Model-agnostic Explanations)という手法があります。LIMEとは、Deep Neural Networkなどの複雑なモデルを線形モデルのような簡単なモデルで局所的に近似することによって、各特徴量の貢献度を測定します。しかし、LIMEの説明には一貫性がないという問題があります。例えば、同じ入力データに対してLIMEを複数回適用した場合でも、適用ごとに特徴量の貢献度のランキングが異なったり、貢献度の符号の正負が逆転したりすることがあります。 この論文では、LIMEの手法を改善し、一貫した説明を提供できるように、SLICE(Stabilized LIME for Consistent Explanations)という手法を提案しました。この手法では、LIMEの説明において貢献度が正と負に変動する特徴を除去することにより、説明の安定性を向上させています。具体的には、符号エントロピーに基づいて不安定な特徴を特定し、それらを除去することで、一貫性のある貢献度を求めています。 結果を見ると、このSLICEという手法は平均実行時間が通常のLIMEの約4倍かかります。LIMEより安定した説明ができるため、今後の説明手法の選択肢の1つになると考えています。 CAM Back Again: Large Kernel CNNs from a Weakly Supervised Object Localization Perspective Shunsuke Yasuki、Masato Taki 近年、CNN(Convolutional Neural Network)の性能向上を目指してカーネルサイズを拡大したラージカーネルCNNが注目されています。先行研究による分析では、性能向上の理由としてCNNのカーネルサイズの拡大により、CNNが広範囲の特徴から情報を集めるようになり、そのような有効受容野の拡大が原因だと述べられていました。 この研究では、実際にラージカーネルCNNの性能向上の理由が有効受容野の拡大であるのか検証し、その妥当性を弱教師ありオブジェクトローカリゼーション(WSOL)の観点から網羅的に評価しました。実験結果から、ラージカーネルCNNの精度向上の理由が有効受容野の拡大だけで説明することは難しいことがわかりました。代わりに、著者らはその理由として、特徴マップの改善が原因であることを示唆しました。具体的には、古典的なモデルの判断根拠の可視化手法のCAM(Class Activation Mapping)を使用して通常のCNNとラージカーネルCNNを比較していました。その結果、通常のCNNは局所領域のみを活性化するのに対し、ラージカーネルCNNはオブジェクト全体を活性化することがわかりました。さらに、この傾向を活かしラージカーネルCNNとCAMを単純に組み合わせた手法でWSOLの精度を検証した結果、複雑な構成を持つ最先端のCNNベースのWSOL手法に匹敵する性能を持つことを示しました。 個人的には、古典的な可視化手法であるCAMが、最新のCNNの性能向上の理由を説明していることから、同様に他の説明手法を最新のCNNやViT(Vision Transformer)に対して適用した際に新たな説明が得られるのかが気になります。 Comparing the Decision-Making Mechanisms by Transformers and CNNs via Explanation Methods [Award Candidate] Mingqi Jiang、Saeed Khorram、Li Fuxin 多くの研究で、TransformerとCNNの判断根拠の違いが議論されています。特に、TransformerはCNNよりも形状に着目して判断するという知見があります。この研究では、そのような新たな知見を得るために「sub-explanation counting」と「cross-testing」と呼ばれる評価指標を提案しました。 1つ目の指標のsub-explanation countingは、モデルが画像内の複数の画像パッチを構成的に見て判断しているのか、それとも一部の重要な画像パッチに強く依存しているのかを調べる手法です。まず、MSEs(Minimally Sufficient Explanations)と呼ばれる、完全な画像と同程度の予測確率を持つ最小限の画像パッチセットを取得します。次にそのMSEsの一部の画像パッチを削除して予測確率を計算し、完全な画像の予測確率との尤度比を求めます。もし、尤度比が高ければ、その画像パッチの組み合わせが重要であり、モデルは複数の画像パッチを同時に着目しているため「構成的」と判断されます。逆に、重要な画像パッチを削除すると予測結果が大きく変わるものを「分離的」と判断されます。この手法によると、Transformerは構成的であり、CNNは分離的であることが示されていました。 2つ目の指標のcross-testingは、モデル間の判断根拠の類似性を評価するための指標です。もし、2つのモデルが同様の視覚的特徴に依存している場合、cross-testingの高いスコアを獲得します。その結果、類似したタイプのアーキテクチャや学習法のモデルでは、予測に使用する特徴が似ていることが確かめられていました。 個人的には、実際にアプリケーションに応用する際には、どちらかのアーキテクチャのモデルがベースとなるのか、それともケースバイケースでアーキテクチャを使い分けるべきかが気になります。 Learning to Rank Patches for Unbiased Image Redundancy Reduction Yang Luo、Zhineng Chen、Peng Zhou、Zuxuan Wu、Xieping Gao、Yu-Gang Jiang 深層学習モデルの説明可能性の文脈ではないのですが、類似した研究の方向性として冗長な情報の削減というものがあり、興味深い考え方をしていたので紹介いたします。画像において、近くにあるピクセルがしばしば似た色や明るさを持つことがあり、このような類似した情報を冗長な情報といいます。推論の高速化や効果的な伝送のためには、この冗長性を削減することが重要です。 この研究は、冗長な情報を削減するためにMAE(Masked Auto Encoder)を用いた自己教師あり学習フレームワークLTRP(Learning to Rank Patches)を提案しました。MAEとは、画像パッチをマスクした入力に対して元の入力画像を出力するように再構成することで特徴表現を学習する手法です。この手法は、画像再構成の難易度に差を持つことを知られています。例えば、犬の胴体をマスクしても元々の犬とほとんど同様な再構成は容易ですが、尻尾をマスクすると再構成は難しくなることがあります。著者らはこのような難易度の差に目をつけ、その画像パッチの情報量を測定する指標として再構成の難易度を使用しました。つまり、再構成が難しい画像パッチは重要な情報を含むと考えられ、再構成が容易な画像パッチは冗長な情報と見なすということです。LTRPの具体的な手法は、まず事前学習されたMAEを利用して画像パッチの擬似スコア(再構成画像の崩れ具合のスコア)を生成します。そして、このスコアを疑似ラベルとした教師データにより、各パッチの重要度のランク付けを推論する代理モデルを学習します。この代理モデルにより、任意の入力画像に対し重要な画像パッチを特定でき、特定されなかった画像パッチは冗長な情報として削減できます。 再構成が難しいというネガティブな要素に直面した場合、それを改善する方向で研究が行われると思うのですが、それに情報量を結びつけることでポジティブな効果を生み出そうとした発想がとても興味深いと思いました。 Identifying Important Group of Pixels using Interactions Kosuke Sumiyasu、Kazuhiko Kawamoto、Hiroshi Kera 私が発表した論文を簡単に紹介したいと思います。 画像分類モデルの振る舞いを理解するために、画像内のピクセルが推論に与える影響を貢献度として測定し、可視化することがよく行われています。既存の手法であるGradCAMやAttention rolloutでは、画像内の各ピクセルが推論に与える影響を測定して可視化しています。しかし、貢献度の高いピクセルを集めたピクセル群が必ずしも高い貢献度を持たないことがあります。例えば、海辺にいる鴨の画像分類を考えると、単純には主に鴨の部分にフォーカスすれば良いと考えられます。ですが、鴨は海の上に生息する傾向があるため、背景に写っている海辺の情報を鳥の情報と一緒にモデルに入力した方が分類を補助するために役立つ可能性が高いことが考えられます。実際に従来手法では、鴨の体や嘴といった情報のみを集めていたのですが、確信度が低く、一方で鴨の体だけでなく海などの情報も含めた方がより高い確信度を持つことがわかりました。 この研究では、モデルにピクセル群を入力したときに推論へ大きく影響を与えるピクセル群を特定する手法MoXI(Model eXplanation by Interactions)を提案しています。具体的には、ゲーム理論で用いられるShapley Valueと相互作用値という2つの量を計算することで、個々のピクセルの貢献度に加えて、ピクセル間の協調による貢献度を考慮できる手法となっています。 CVFAD におけるスポンサーの取り組み ZOZO Researchの清水良太郎です。最後に、CVPRで毎年開催されているファッションやアートに関するワークショップ Workshop on Computer Vision for Fashion, Art, and Design (CVFAD)について紹介いたします。CVFADは今年で7回目の開催であり、毎年様々な大学や企業からファッション関連の最新の研究が発表されています。 ファッション関連のAI技術の研究者にとっては毎年欠かすことのできないイベントであり、ZOZO NEXTも今年はスポンサーとして関わりました。今年の採択率は38%と、ワークショップながら厳しい査読プロセスを経て、8件の研究が発表されました。なお、採択された論文は、毎年CVPR WorkshopsのProceedingsに掲載されています。 私個人としても、今年は「A Fashion Item Recommendation Model in Hyperbolic Space」が採択され、現地で発表してきました。この研究は、画像情報を用いた推薦モデルの学習に、双曲空間における距離尺度を導入し、優れた精度を達成したという内容です。双曲空間は、我々の生活しているユークリッド空間と呼ばれる平坦な空間と異なり、曲がった空間の一種です。原点から離れるほど空間が広くなるという特徴から、階層的な構造を有したデータの埋め込みと相性が良いとされています。推薦のデータにおいては、わかりやすい階層構造だけでも、例えばユーザとアイテム。また、アイテム間の人気度から生じる階層構造が存在します。 ファッション画像推薦モデルの学習によって得られる双曲空間のイメージ 本研究では、双曲空間においてファッション画像特徴量を用いた推薦モデルを学習し、その精度や学習されたユーザ・アイテム空間に関する多角的な考察をしました。詳しくは、 論文 をご一読いただけると幸いです。 CVPRはコンピュータビジョン・パターン認識分野において世界最高峰の国際会議であるため、そのワークショップに対する採択の難易度も年々向上しております。特に、一部のワークショップは、一流の国際会議やジャーナルに匹敵するような評価を受けています。実際にGoogle Scholarが発表しているh5-indexを基準にしたランキングでは、(2024年7月5日現在)Computer Vision & Pattern Recognition分野の全学術誌・国際会議を通して7番目の評価をされています。会社の企業理念である「世界中をカッコよく、世界中に笑顔を」を叶えるべく、世界中のファッションに関わるAI研究者や技術者から認知をしてもらうことはとても重要な一歩です。このような場で継続的に研究成果を発表するだけでなく、スポンサー企業として積極的に参加することにより、ZOZO/ZOZO NEXTのプレゼンスの向上に大きく貢献すると考えています。 さいごに 今回は、CVPRに参加した感想や内容の一片をお伝えしました。特に経験して良かったことは、トップ層の研究者や開発者たちが持つ知的好奇心の強さと行動力を直接感じることができたことです。このような熱意に触れられたことは、将来の糧になると感じています。ここで得られた知見や経験を今後の開発に取り入れ、より良いサービス開発をしていきたいと思っています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com hrmos.co
アバター
はじめに こんにちは、CISO部の 兵藤 です。日々ZOZOの安全を守るためSOC業務に取り組んでいます。 本記事ではMicrosoftのDefender for Endpointを用いてAppleのmacOSに対してセキュリティ対策するTipsについて紹介します。 また、CISO部ではその他にもZOZOを守るための取り組みを行っています。詳細については以下の「OpenCTIをSentinelに食わせてみた」をご覧いただければと思います。 techblog.zozo.com 目次 はじめに 目次 背景と概要 前提知識 Microsoft Defender for Endpoint導入方法 macOSとWindowsでのDefender for Endpointの機能差分 macOSへのLive Response機能 Live ResponseのBashスクリプトの登録と実行 スクリプトのTips デバイス分離の際の通知スクリプト 子プロセスまで切るスクリプト フォレンジックアーティファクト取得スクリプト まとめ おわりに 背景と概要 ZOZOではmacOSのエンドポイントの保護にMicrosoft Defender for Endpointを利用しています。Microsoft Defender for Endpointは、macOSに対してもエンドポイント保護ができます。 ですが、Windows端末に対して行うことができるインシデント対応時のエンドポイトの機能とmacOSの機能には差分があるため、独自でmacOSに対して分析調査を行う必要があります。そのための簡易なスクリプトを用意し、インシデント対応に活用しています。その事例について本記事で紹介します。 前提知識 Microsoft Defender for Endpoint導入方法 ZOZOではユーザ端末を管理するために、Microsoft Intuneを利用しています。Microsoft Intuneは、Windows端末だけでなくmacOSにもFirewallやGateKeeperなどセキュリティの設定プロファイルやアプリを配ることができます。Microsoftのライセンスを購入している組織はこういった形でmacOSに対してもIntuneで管理することがあるかと思います。 Microsoft Defender for EndpointもIntuneによって配ることが可能です。詳しくは 公式ドキュメント をご覧ください。構成プロファイルが多いですが、必要な構成プロファイルとなるためIntuneで配布する必要があります。 macOSとWindowsでのDefender for Endpointの機能差分 Defender for Endpointを使ったインシデント対応では、UI上の右上の3点リーダーから表示される以下の機能群を使うことが多いと思います。 Windowsでの機能 macOSでの機能 左図はWindowsでのDefender for Endpointの機能群で、右図はmacOSでの機能群です。 大きな違いは「分離スクリプトから強制解放をダウンロード」と「自動調査の開始」です。「分離スクリプトから強制解放をダウンロード」についてはWindows用のバッチファイルなのでmacOSでは使えません。「 自動調査 の開始」についてはDefender for Endpointが、FileやProcess、Driver、通信先、永続化の有無(レジストリ)などを自動で調査してくれる機能です。普段はアラームトリガーで動くのですが、手動でも実行できます。Windows特有の調査項目もあるので、これもmacOSでは使えません。 macOSへのLive Response機能 Defender for Endpointは端末に対してリモートからコマンドベースの操作が可能です。リアルタイムで端末の情報を遠隔から取得できたり、ファイルの取得や削除が可能で、インシデント対応においては何かと便利です。 macOSでもこのLive Response機能を使うことができます。macOSで利用できるコマンドは 公式ドキュメント をご覧ください。Windowsと違ってmacOSでは分離機能、アンチウィルススキャンなどがコマンドで利用可能です。また、 run コマンドについてはmacOS上でbashスクリプトの起動が可能です。 Live ResponseのBashスクリプトの登録と実行 macOSではLive Response機能を利用し端末上でBashスクリプトを利用できます。この機能で用意しておいた便利スクリプトを起動し、SOC対応に活用しています。 このスクリプトはDefender for EndpointのLive Response起動時の画面から登録できます。まずは、右上の「ライブラリへのファイルのアップロード」を選択します。 次にアップロードするファイルを選択し、アップロードします。 アップロード完了後は、Live Responseの library コマンドでアップロードしたスクリプトを確認できます。このLlibraryに保存されているスクリプトは library delete コマンドで消さない限り残るので、一度登録しておけばSOC対応中に何度も呼び出すことが可能です。 この library に存在するスクリプトの起動は以下のコマンドで行います。 run < bash-file in library > ここまでのオペレーションで、macOS上でカスタムしたBashスクリプトを実行できます。 スクリプトのTips スクリプトを侵害された可能性のある端末で回すことになるので、侵害の度合いや対応フェーズによってスクリプトを回す、回さないの判断があると思います。それについてはアラームの出方によって都度判断することをお勧めします。 デバイス分離の際の通知スクリプト Defender for Endpointではデバイスのネットワーク(以降NW)上からの分離が可能です。この機能を使うとデバイスのユーザーはネットワーク通信を用いた操作ができなくなるので、業務が止まってしまいます。出社している場合は同フロアや同僚の助けを借りて対処が可能ですが、リモートワークが主体の弊社ではSOCの対応なのか、NWでの不具合なのか判断がつきません。 Windows環境の場合は、デバイス分離の際にユーザーへ通知する機能 1 が存在します。macOSではこのような機能はありません。 Windowsでのデバイス分離の通知 macOSでもユーザが判断するために、デバイス分離の際にカスタムスクリプトを回すことによって、端末のユーザに通知できます。以下にスクリプトの例を示します。 script_message = ' display dialog "コンピュータウィルスの感染の疑いがあるため、SOCにてPCを隔離いたしました。\n現状の詳細について、緊急のお問い合わせはこちら\n\n<電話番号を記載>" with title "SOC_Alert" with icon caution buttons {"OK"} ' /usr/bin/osascript -e " ${script_message} " 社内の緊急時の連絡先や対応フローに沿って手順を記載すると、ユーザも安心して今後の対応に進むことができると思います。 子プロセスまで切るスクリプト Defender for EndpointのLive Response機能では remediate と言ったコマンドがあります。このコマンドについては以下のように公式ドキュメント 2 に記載されています。 デバイス上のエンティティを修復します。 修復アクションは、エンティティの種類によって異なります。 ファイル: 削除 プロセス: イメージ ファイルを停止、削除する サービス: イメージ ファイルを停止、削除する レジストリ エントリ: 削除 スケジュールされたタスク: 削除 スタートアップ フォルダー項目: ファイルを削除する このコマンドによってプロセスを遠隔から切ることができるのですが、子プロセスまでまとめて切ることができません。 そこでmacOSに存在する便利なコマンド pkill を用いたスクリプトで、親から子プロセスまで切ることができます。 # pkill.bash /usr/bin/pkill -P " $1 " このスクリプトをLive Responseで実行する際には以下のようにします。 run pkill.bash < 親のPID > 不審なプロセスから作成される子プロセスをまとめて切ることができるので便利です。 フォレンジックアーティファクト取得スクリプト Defender for Endpointでは「調査パッケージの収集」の機能を利用すると、フォレンジックに役立つ情報をリアルタイムで収集できます。 lsof や kextstat コマンド出力、 .zsh_history ファイルなど多彩なログを収集可能です。収集できる情報については 公式ドキュメント を参照してください。 この「調査パッケージの収集」機能で目ぼしい情報は収集できます。ですが、各インシデントによっては別途ブラウザのログや、ASL(Apple System Log)、SFLファイルを確認したいことがあります。 これらのアーカイブファイルがどんなログなのか確認したい場合は、JSAC 2022で行われた An Introduction to macOS Forensics with Open Source Software のスライドを参考にしてみるとわかりやすいです。 どういったログなのか理解していないと思わぬ情報の見落としに繋がる可能性があります。そこで、事前に取得できるアーティファクトを確認しておくことが重要です。 例えば「 com.apple.LaunchServices.QuarantineEventsV2 のログはデフォルトでは wget などのダウンロードでは com.apple.quarantine 拡張属性がつかないので記録されない」などです。 これらのmacOSの各種アーティファクトを追加で取得する際に便利なツール AutoMacTC を用いてスクリプト作成しました。 AutoMacTCはセキュリティベンダーであるCrowdStrikeが公開しているmacOSのフォレンジックツールです。このツールを使うことで、macOSの各種ログや設定ファイルを取得できます。 その他のフォレンジックツールはmacOSの性質上、再起動が必要です。そこで、リアルタイムフォレンジックを行えるツールで、各種ログを取得できるAutoMacTCを選定しました。 また、AutoMacTCはmacOSにデフォルトであるPython 3で動作し、追加のパッケージも pip などでインストールしないため、迅速にアーティファクトを取得できます。最近はメンテナンスされていないのが難点ではあるので、その点は注意が必要です。こちらの Issue などのように収集対象が変わっている場合があります。 このツールをDefender for EndpointのLive Responseで使うためには、諸々の準備が必要です。 まずAutoMacTCで利用するmodulesをzip圧縮しておきます。 zip -r modules.zip modules この modules.zip ファイルと automactc.py を事前にLive Responseのlibraryに登録しておきます。そして以下のカスタムスクリプトもlibraryに登録しておきます。 # automactc.bash folder = " /Library/Application Support/Microsoft/Defender/response/ " #Live Responseがputfileコマンドでファイルを置くディレクトリ modulefile = " modules.zip " pyfile = " automactc.py " /usr/bin/unzip -o " ${folder}${modulefile} " -d " ${folder} " /usr/bin/python3 " ${folder}${pyfile} " -m all -o " ${folder} " 登録した後はzipファイルやPythonスクリプトを putfile コマンドで展開し、カスタムスクリプトを run するだけでAutoMacTCが動作します。動作後のアーティファクトは getfile コマンドで取得できます。 追加で様々なアーティファクトを取得でき便利です。 まとめ Defender for EndpointをMacで活用するためのTipsを紹介しました。macOSでのインシデント対応においては、Windowsとは異なる機能差分があるため、独自のスクリプトを用意しておくと便利です。 ZOZOではこれからもSOCの運用体制を整備し、ZOZOの安全性の向上を図っていきたいと考えています。 おわりに ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクから是非ご応募ください! corp.zozo.com デバイス分離 ↩ ライブ応答を使用してデバイス上のエンティティを調査する ↩
アバター
はじめに こんにちは。DevRelブロックの @ikkou です。6月26日に「 WWDC24 報告会 at LINEヤフー, ZOZO 」を開催しました。WWDCに参加したLINEヤフーとZOZOのエンジニアが新しく発表された技術や得た知見・情報などを共有するイベントです。今年はオフラインのみで開催しました。 登壇内容まとめ LINEヤフーとZOZOの社員によるLTとゲストを招いてのパネルディスカッションを行い、その後は交流会で盛り上がりました。 なお、本イベントはAppleがNDAを締結した開発者にのみ公表している情報を取り扱っており、参加はApple Developer Programに加入している方に限定して実施しました。本レポートもLTの詳細は割愛し、雰囲気をお伝えできればと思います。 コンテンツ 登壇者 Essential Highlights: SwiftUI updates たなたつ / LINEヤフー What’s new in controls Fuya / ZOZO AccessorySetupKit 鎌倉和弘 / LINEヤフー Swift Charts: Vectorized and function plots yamaken / LINEヤフー What's new in privacy 松原良和 / LINEヤフー Introduction to Swift Testing! shoma / ZOZO What's new in SwiftData 大岡湧汰 / LINEヤフー Panel Discussion freddi / LINEヤフー jollyjoester / メルカリ akkie76 / DeNA 交流会 発表風景 LINEヤフーのたなたつさんによるLT LINEヤフーの松原さんによるLT ZOZOのshomaさんによるLT 発表資料 一部の登壇者については、イベント当日に投影していた資料からNDAに抵触する箇所を省き、公開情報のみで構成した資料を公開しています。 speakerdeck.com Panel Discussion 乾杯で始まり和やかな感じで進行したパネルディスカッション 名物“乾杯er”である jollyjoester さんの「カー→ンパ↑ーイ!」とともにカジュアルな雰囲気でパネルディスカッションがはじまりました。 今回のパネルディスカッションは、LINEヤフーの freddi さんの他、グループ外からのゲストとしてメルカリの jollyjoester さんとDeNAの akkie76 さんの3名で進行しました。私たちのWWDC報告会として社外ゲストをお招きしての実施は初めての試みでしたが、それぞれがWWDC24の現地で感じたことを中心にWWDC24を振り返りました。 交流会 パネルディスカッションの後、交流会を実施しました。昨年に続きApple JapanからTechnology Evangelistの豊田さまにご参加いただき、参加者からのさまざまな質問に答えていただきました。 最後に みなさまご参加ありがとうございました。WWDCの報告会は毎年の恒例イベントです。また来年も開催できることを楽しみにしています! ZOZOでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに はじめまして。2024年に新卒として株式会社ZOZOに入社しました。 佐藤仁 と申します。 この記事は4か月間にわたる内定者アルバイトの体験記です。アルバイトの概要、チームの文化、実際に行ったタスク、反省点、フィードバックをご紹介します。 目次 はじめに 目次 内定者アルバイトについて 内定者アルバイトでの働き方 実際にアルバイトをした部署の概要 ZOZOTOWN開発1部フロントエンドブロックの取り組み 週一回の輪読会 Findy Team+による開発運用の改善 実際に行ったタスク 似合うアノテーション 背景 開発体制 実装箇所 得られた知見 フィードバックと反省点 最後に 内定者アルバイトについて 内定者アルバイトとは内定承諾から入社までの期間にアルバイトの雇用契約を結び、内定先で就業できるものです。 選考時に志望していた職種以外の求人に応募できます。同期を見ているとフロントエンドを志望したけど内定者アルバイトではSREに参画する人もいました。なので専門外の技術を学ぶには良い機会になりそうですね。 内定者アルバイトでの働き方 私は2023年1月末に内々定をいただき、同年12月から翌年3月の大学卒業までの4か月アルバイトを行いました。 ZOZOTOWN開発部門の社員にはフルフレックス制度が導入されていますが、内定者アルバイトはシフト制なので、私は10時から17時、1日6時間勤務を週3日、フルリモートで働きました。 メンターとは朝に相談会の時間を設け、業務での悩み事を共有しました。業務の事以外にも一人暮らしに向けた引っ越しの相談なども付き合ってもらえました。MTGの時間を定期的に用意してもらえたのでフルリモートでも問題なく働けました。 私は新潟にいたので、オフィスへ出勤するのが難しかったのですが、千葉にいる内定者アルバイトの同期は出社して働く人もいました。 実際にアルバイトをした部署の概要 私が参画した部署はZOZOTOWN開発本部ZOZOTOWN開発1部Webフロントエンドブロックです。 受け持つ案件は大規模・中規模案件がメインで、サービスに関わる新機能の実装を主に担当します。2023年12月時点ではスタッフは10名程度で構成されていました。 チームのコミュニケーションはSlackとGoogle Meet、デザインツールはFigma、仕様に関してはConfluenceとMiro、タスク管理にはGitHub Projectsを使っています。 チームで印象的だった取り組みを2つ紹介します。 ZOZOTOWN開発1部フロントエンドブロックの取り組み 週一回の輪読会 毎週金曜日に1時間読書する時間を設け、その後1時間読んだ内容について議論し、チームに導入できることはないか相談します。読む本は皆で相談しながら、今のチームにとって必要な本を選んでいます。 内定者アルバイトの期間では「 フロントエンド開発のためのテスト入門 今からでも知っておきたい自動テスト戦略の必須知識 」を読みました。開発1部では新規機能の開発に追われテストをしっかり書く機会がないこともありましたが、テスト文化を築く良いきっかけになりました。 本以外にも社員の方に翻訳してもらった Google Engineering Practices Documentation を読みました。PRの書き方や小さなPRを作る重要性、コメントをする上での心構えをチームで共有できました。 Findy Team+による開発運用の改善 Findy Team+は1PRあたりの平均マージ・クローズ時間や変更行数などの数値を分析できるツールです。開発1部ではこれを用いて週に1回、PRのオープンからマージまでのサイクルタイムやマージされたPRの数、1PRあたりの変更行数を振り返ります。 Google Engineering Practices Documentation を輪読会で読んでから、PRの粒度が小さくなり、PRをオープンしてからマージされるまでの平均時間が改善されました。 別の部署での取り組みがブログにあるので興味のある方は下記リンクからご覧下さい。 techblog.zozo.com 実際に行ったタスク 4か月の間、色々なタスクを担当しましたが、開発期間が長かったものを1つ紹介します。 似合うアノテーション ZOZOTOWN会員にどちらのコーデ画像の方が似合っているかアノテーションをしてもらう機能です。 背景 ZOZOTOWNは創業25年目を迎え、 経営戦略 として新たに「ワクワクできる『似合う』を届ける」が追加されました。 服と服、人と服の似合うデータをもとにモデルを開発したい。そのために「似合う」に関する大量のデータが必要だが現在大量にデータ取得できる経路がない。という経緯で、似合うに関するデータを大量に収集するためのアンケート機能を実装する必要がありました。 開発体制 部署内のテックリード、メンターと自分の3人でフロントの部分を実装しました。issueはGitHubのProjectsに案件ごとでまとめられており、issueと紐付けてPRを出していきました。 検証に上がった後は結合テストで開発者たちによる実機検証をしました。その後はQAエンジニアに動作確認をしてもらい、リリースという流れです。週に1回バックエンドのチームと共有会もあり、進捗や疑問点を共有しました。 実装箇所 APIの部分はメンターの方が既に実装していたので、私はそのAPIを使ったhooksやコンポーネントの実装を担当しました。 シンプルなUIで内定者アルバイトとして適切な難易度だったと思います。ただ、ZOZOTOWNは歴史あるプロダクトなだけあって普段のフロントエンド開発では見ないディレクトリ構成をしていたり、社内独自のライブラリがあったりしたので慣れるまで時間がかかりました。 一番難しかったのは途中離脱を防止するアラートの実装です。このページではアンケートを回答すると画面が変わりますが、URLのパスが変わるわけではありません。そのため、ブラウザバックをする時にアンケート開始前のホーム画面に戻ってしまうので、前の回答ページには戻れないと伝える必要がありました。 JavaScriptにはHistoryAPIがあります。このHistoryAPIはブラウザ上の閲覧履歴を操作できるものです。また、ブラウザバックのハンドリングには popstate イベントを使いました。 このAPIを利用して、アンケートに回答すると最初の1回だけ履歴を追加します。その後、ブラウザバックをした時にpopstateイベントを検知してアラートを表示していました。 しかし、iOSのsafariでは履歴の追加後にブラウザバックをするとアラートが出なくなる挙動を発見しました。この問題を解決できず、最終的にはアラート機能を外す結果となりました。 得られた知見 GitHub Projects内で管理されていたissueが細分化されていました。一見issueの量が多く見えて大変と感じるかもしれませんが、1つのissueが小さいことからPRの粒度も小さくなり、Reviewerの負担が減りました。このようなことからタスクをしっかり細分化することで開発サイクルを早められると気付けました。 ブラウザ・バージョンの互換性について意識するようになりました。開発者としては当たり前な部分だと思いますが、今までやってきた業務ではおざなりにしていたんだなと実感できました。エラーが起きた時、もしくはエラーはないが想定通りの動作が確認できない時、別のブラウザで同様のエラーが見られるか確認するようになりました。ブラウザ依存のAPIは互換性を意識する必要があると学べました。 セマンティックなマークアップ、アクセシビリティに対する意識も上がりました。私は内定者アルバイトを始める前、ある程度UIの実装ならできると慢心していましたが、レビューを受ける中で自分がいかに無知か再確認できました。小さなコンポーネント1つでもアクセシビリティのことを考慮すると改善の余地がたくさん出てきました。Reviewer方の知見の広さに感嘆するばかりでした。 フィードバックと反省点 内定者アルバイト最終日にMTGがあり、いくつかフィードバックをいただきました。 作業スピード、スキルは申し分ない 自分で仕事をガツガツ進めていく姿勢が良かった チームの輪読会・朝会で積極的に話せている などありがたいお言葉をいただきました。作業スピードが早いのは良いことですが、その反面PRの書き方が雑でレビュアに負荷を与えていたという反省点があります。動作確認の手順が書かれていない、概要の情報量が不足している、仕様の抜け漏れがある、メンバーが前提知識で知っていることをわざわざ書いていて冗長などの指摘をいただきました。 また、指摘箇所を修正した後連絡がないなどチーム開発をする中でもメンバーに寄り添った立ち回りができていなかったと思います。メンターからも言われたことですが多少時間がかかっても良いから抜け漏れのないPRを出して周囲に気遣いができるようになろうと思いました。 もう1点、レビューにもっと参加して欲しかったというフィードバックもいただきました。これは私も同じことを感じていて、せっかくZOZOのエンジニアたちのコードを読めるというのにコードリーディングにしっかり時間を割けず、勿体ない事をしたなと反省しています。配属後は1日の中で必ず一度は他の人のPRを見てレビューする習慣をつけようと考えています。 最後に ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 本連載では、ZOZOTOWNリプレイスプロジェクトについて紹介します。2020年に再始動したZOZOTOWNリプレイスでは、「マイクロサービス化」が大きなカギとなりました。今回は、SRE部が行った、リプレイス方針の決定から導入ツールの選定、マイクロサービスのリリース方法の改善までを紹介していきます。 目次 はじめに 目次 ZOZOTOWNリプレイスにおけるSRE部の方針 IaCの導入 IaCとは プラットフォーム基盤におけるIaC CI/CDの導入 CI/CDとは GitHub Actions 変更のあるインフラリソースのみをCIの対象とする工夫 Canary Releaseの導入 Canary Releaseとは ZOZO API GatewayとALBによるCanary Release IstioによるCanary Release Progressive Deliveryの導入 Progressive Deliveryとは Progressive Deliveryツールの選定 Flagger GitOpsの導入 GitOpsとは GitOpsツールの選定 Flux2 マイクロサービスのリリース方法の改善 改善前の課題 課題を解決したCDパイプライン Required reviewers OCIRepositoryに変更 改善の結果 おわりに こんにちは。株式会社ZOZOの技術本部 SRE部の籏野( @gold_kou )と堀口( @makocchi6 )と申します。第2回は、ZOZOTOWNリプレイスにおける、SRE部によるIaC(Infrastructureas Code)やCI/CD関連の取り組みを中心にまとめます。 ZOZOTOWNリプレイスにおけるSRE部の方針 ZOZOTOWNの開発に従事するSite Reliability Engineerは、技術本部のSRE部という組織に所属しています。2020年にZOZOTOWNのリプレイスを本格的に再開した際に、SRE部としていくつかの方針を決定しました。 まずは、AWSの導入です。ZOZOTOWNはもともと、オンプレミスのインフラ構成になっており、スケールアウトに課題がありました。たとえば、大規模なセールでは平常時の3倍以上のリクエストを受けますが、そのピークトラフィックを捌さばくために相応のインフラ設備を購入する必要があります。これには数ヵ月といったリードタイムが発生するため、準備も入念に行う必要があるうえ、スケールアウトが不十分でシステムエラーが多発するケースもありました。AWSを導入することでその問題を解決することにしました。 また、マイクロサービスはコンテナで管理することにしました。コンテナ技術を利用することで、処理速度および起動速度が速い、Amazon ECR(以下、ECR)などのイメージレジストリを利用できる、などさまざまなメリットを享受できます。 そして、コンテナ化されたマイクロサービスはAmazon EKSのKubernetes(以下、K8s)クラスター上で稼働させることにしました。ZOZOでは、このマイクロサービスが稼働するK8sクラスターを「プラットフォーム基盤」と呼んでいます。プラットフォーム基盤は、マルチテナンシー方式で、複数のマイクロサービスをnamespace単位で区切り、単一のK8sクラスター上で稼働させています。マルチテナンシー方式を採用した理由は、管理のしやすさやリソース効率の良さなどに加えて、リプレイスの速度を上げるためです。当時のZOZOの状況では、マルチテナンシー方式を採用することで、マイクロサービスの構築をパターン化して量産する体制を整えやすいと判断しました。 ほかにもこの時点で、IaC、CI/CD、Canary Release、Progressive Deliveryの導入を決定しました。また、それらを導入する中で、GitOpsの導入やマイクロサービスのリリース方法を改善するようになりました。本記事では、これらの取り組みを紹介します。 IaCの導入 IaCとは IaCは、インフラ構成をコード化して、そのプロビジョニングを自動化する手法です。次の効果が期待できます。 Gitによるバージョン管理とレビュー環境の提供 構築作業におけるヒューマンエラーの削減 新規メンバーのキャッチアップが容易 自動化による開発効率とリリーススピードの向上 コードの再利用 インフラへのCI/CD導入 プラットフォーム基盤におけるIaC プラットフォーム基盤のインフラはほとんどがIaC化されています。具体的には、AWSリソースはAWS CloudFormation(以下、CloudFormation)、K8sリソースはK8sのマニフェスト、DatadogリソースとPagerDutyリソースとSentryリソースはTerraformによりIaC化されています。 CI/CDの導入 CI/CDとは CI/CDは、CI(継続的インテグレーション)とCD(継続的デリバリー)の組み合わせです。CIはコードの変更を起点にコードの静的分析、ビルド、テスト、成果物の生成などの実行を自動化する手法です。一方、CDはCIで検証・テストされたコードや成果物を目的の環境に自動でデプロイする手法です。CI/CDを導入することで、本来は人が行う必要のない作業が自動化され、結果的に空いた時間で本質的な作業に人が取り組めます。これは、メンバーのモチベーション向上と、組織全体の生産性向上につながります。 プラットフォーム基盤におけるインフラのCI/CDでは、AWSリソースとK8sリソース、Terraformリソースの変更を検知して、リソースを更新します(図1)。前提として、IaCが導入されている必要があります。 図1 プラットフォーム基盤におけるインフラのCI/CD GitHub Actions CI/CDのツールはGitHub Actions(GHA)を採用しました。2020年当時は、GHAがまだサービスとしてリリースされて間もなく、マトリックスビルドができるなどの利点はあるものの、機能的に他のツールと比べて大きな優位性はないという社内評価でした。しかしながら、GitHubとの統合性の高さや将来性をふまえGHAを採用しました。 変更のあるインフラリソースのみをCIの対象とする工夫 前提として、プラットフォーム基盤のインフラを管理するGitHubリポジトリは、直下に「cloudformation」「k8s」「terraform」というそれぞれのIaCのディレクトリが存在する構成になっています(図2)。そしてそれぞれの配下に、環境(dev、stg、prdなど)やマイクロサービスなどに応じたディレクトリが存在します。 図2 プラットフォーム基盤のインフラに関する GitHubリポジトリの中身 この工夫では、OSSの tj-actions/changedfiles を利用し、Pull Request(以下、PR)で変更のあったディレクトリ情報のみを取得して、その情報を後続のJobへ渡すようにしました。後続のJobでは、変更のあったディレクトリに関するインフラリソースに関してのみ処理されます。結果として、すべてのインフラリソースに対してCIを実行していたころよりも、大幅にCIの時間を短縮できました。また、マイクロサービスがどれだけ増えても実行時間が長期化することはなくなりました。 Canary Releaseの導入 Canary Releaseとは Canary Releaseは、新しいバージョンのアプリケーションを段階的にデプロイする手法です。段階的にリリースすることで、新しいアプリケーションにバグがあった場合でもユーザーへの影響を最小限に抑えられます。また、リリース作業をする人(以下、リリーサー)の心理的負担も軽減できます。 ZOZO API GatewayとALBによるCanary Release 最初に、内製のZOZO API GatewayとAWSのApplication Load Balancer(以下、ALB)によるCanary Releaseをプラットフォーム基盤に導入しました。 ZOZO API Gatewayの加重ルーティング機能を利用して、各マイクロサービスのCanary Releaseを実現しました *1 。 そして、ALBの加重ルーティング機能を利用して、ZOZO APIGateway自体のCanary Releaseを実現しました。ZOZO API Gatewayの前段にはIngressリソースであるALBが存在します。ZOZO API GatewayのPrimaryリソースとCanaryリソースをそれぞれ用意し、それらをターゲットグループとして任意の加重率を設定します。加重率の設定は、リスト1のようにIngressリソースのannotationで行います。この設定を適用すると、AWS Load BalancerControllerはIngressリソースの変更を検知し、ALBのListener Ruleを更新します。 alb.ingress.kubernetes.io/actions.forward-external-traffic : | { "Type" : "forward" , "ForwardConfig" :{ "TargetGroups" :[ { "ServiceName" : "zozo-api-gateway-primary" , "ServicePort" : "80" , "Weight" : 90 } , { "ServiceName" : "zozo-api-gateway-canary" , "ServicePort" : "80" "Weight" : 10 } ] } } リスト1 ALBの加重ルーティング設定例 IstioによるCanary Release 前述したZOZO API GatewayとALBによるCanary Releaseを導入した後に、プラットフォーム基盤にIstioを導入しました。マイクロサービス間通信およびプラットフォーム基盤外への通信において、一貫した通信制御を提供するサービスメッシュが必要だったためです。 Istioの導入に伴い、Canary Releaseの手法を、IstioによるCanary Releaseに変えました。リスト2は、Istioの DestinationRule と Virtual Serviceにより、ZOZO API GatewayをCanary Releaseする設定例です。DestinationRuleでは、hostやsubsetsのprimaryとcanaryを定義します。VirtualServiceでは、destination ごとにDestinationRuleで定義したhostとsubsetを指定し、weightで加重率を設定します。この設定を適用すると、istiodにより自動的にistioproxyのconfigが更新され、ZOZO API Gatewayへのトラフィック加重率が変更されます。 apiVersion : networking.istio.io/v1alpha3 kind : DestinationRule metadata : name : destinationrule spec : host : zozo-api-gateway.ns.svc.cluster.local subsets : - name : primary labels : version : zozo-api-gateway - name : canary labels : version : zozo-api-gateway-canary --- apiVersion : networking.istio.io/v1alpha3 kind : VirtualService metadata : name : virtualservice spec : hosts : - zozo-api-gateway.example.com // 省略 http : - route : - destination : host : zozo-api-gateway.ns.svc.cluster.local subset : primary weight : 90 - destination : host : zozo-api-gateway.ns.svc.cluster.local subset : canary weight : 10 // 省略 リスト2 IstioによるCanary Releaseの設定例 ZOZO API Gatewayだけでなく、ほかのマイクロサービスも同様の方法で Canary Releaseできます。 Progressive Deliveryの導入 Progressive Deliveryとは Progressive Deliveryは、Canary Releaseも含め、より広範に新しいバージョンのアプリケーションを安全にリリースするための概念です。そこには、Blue/GreenデプロイメントやA/Bテストなどに加えて、Canary Releaseの自動化も含まれます。 前述のとおり、プラットフォーム基盤には IstioによるCanary Releaseを導入しました。しかしながら、Canary Releaseの進行における判断コストや加重ルーティングの進行、切り戻しなどの作業に関して運用コストが高いという課題がありました。そこで、Progressive DeliveryによるCanary Releaseの自動化を導入しました。 Progressive Deliveryツールの選定 Progressive Deliveryツールの候補としてArgo Rollouts、Spinnakerなどがありましたが、ZOZOのプラットフォーム基盤では次の理由からFlaggerを採用しました。 Istio との連携をサポートしており、自動でVirtualServiceの加重を変更できるため Datadogのメトリクス取得をサポートしており、判断基準に使用できるため いずれも、もともとは人が実施していた作業を自動化するだけなので、導入イメージが湧きやすかったという点が大きかったです。 Flagger Flaggerは、Progressive Deliveryを実現するKubernetes Operatorです。Flaggerを導入する主なメリットは2つです。 1つ目は、Canary Release作業の工数削減です。Flaggerはメトリクスの取得・分析、判断、加重率の変更作業などをすべて自動化してくれます。 2つ目は、Canaryリソースのコスト削減です。K8sのHorizontalPodAutoscalerの仕様上、min Replicasを0にはできません。したがって、Canary Release時以外でもCanaryのPodを最低1つは常時起動しておく必要がありました。しかし、Flaggerを導入すればそれが必要なくなります。 なお、プラットフォーム基盤では、Flaggerは次のように動作します(図3) *2 。 図3 プラットフォーム基盤におけるFlaggerの動作 加重変更 : VirtualServiceのweightを変更 スケールアウト/イン : K8sの設定を変更 メトリクス取得 : Datadogにクエリを発行 通知・アラート : Slackに通知 GitOpsの導入 GitOpsとは GitOpsは、Gitリポジトリを唯一の真実の情報源(Single Source of Truth)とし、K8sクラスターが自身の状態をGitリポジトリと同期するCD方式です。いわゆるPull型と呼ばれ、定期的にGitリポジトリをチェックし、変更があった場合はその変更を自動的に反映します。 一方、これまでプラットフォーム基盤にはCIOpsと呼ばれる、Push型のCD方式を導入していました。GitHubのPRのマージをトリガーにCIが走り、K8sクラスターへGHAがapplyをしていました。つまり、GHAにはapplyを実行するための強い権限が付与されていました。 CIOpsからGitOpsにすることで、CIはGHAの責務、CDはGitOpsツールの責務として分離し、GHAから強い権限を剥がせます。また、ワークフローのシンプル化や高速化も期待できます。 GitOpsツールの選定 ZOZOのプラットフォーム基盤ではFlux2を採用しました。類似のOSSとしてArgoCDが挙げられます。Flux2を採用した理由は、すでにプラットフォーム基盤で利用している FlaggerがFlux2と同じFlux Projectに所属しており、親和性の高さを期待できたからです。 Flux2 Flux2は、GitOps Toolkitと呼ばれるいくつかのコンポーネントにより動作します。たとえばSource ControllerやKustomize Controllerです。 Source Controllerは、Gitリポジトリ、Helmリポジトリ、バケットなどからアーティファクトを取得します。プラットフォーム基盤では、GitRepositoryというカスタムリソースを使用して、プラットフォーム基盤のインフラを管理しているGitリポジトリからK8sマニフェストを取得しています。 Kustomize Controllerは、Source Controllerによって取得したマニフェストをfetchし、それらをクラスターに適用します。Kustomizeという機能を利用して、マニフェストのカスタマイズやパッチ適用も行えます。Kustomizationというカスタムリソースを管理します。 リスト3は zozo-web-gateway というマイクロサービスのGitRepositoryとKustomizationの例です。GitRepositoryは、プラットフォー ム基盤のインフラを管理するGitHubリポジトリのreleaseブランチに対して、secretに保存されているSSH鍵を使って1分間隔でfetchする設定です。Kustomizationは、そのGit Repositoryの k8s/prd/zozo-web-gateway というディレクトリに存在するK8sマニフェストを1分間隔でfetchして、クラスターに展開する設定です *3 。 apiVersion : source.toolkit.fluxcd.io/v1 kind : GitRepository metadata : name : zozo-web-gateway namespace : zozo-web-gateway spec : interval : 1m0s ref : branch : release secretRef : name : zozo-web-gateway-flux-secrets-202307120000 url : ssh://git@github.com/xxx --- apiVersion : kustomize.toolkit.fluxcd.io/v1 kind : Kustomization metadata : name : zozo-web-gateway namespace : zozo-web-gateway spec : interval : 1m0s path : ./k8s/prd/zozo-web-gateway prune : false sourceRef : kind : GitRepository name : zozo-web-gateway suspend : false リスト3 GitRepositoryとKustomizationのYAML設定例 マイクロサービスのリリース方法の改善 改善前の課題 プラットフォーム基盤の構築当初は、masterブランチからreleaseブランチ宛のPR(以下、Release PR)を作成すると本番環境のCIパイプラインが動作し、PRをマージするとCDパイプラインが動作するという方法でリリースしていました。この手法のメリットは、リリース前の動作確認が可能で、リリース手順も簡単であることでした。しかし、1つのRelease PRで複数のチームの複数のマイクロサービスのリリースを管理することになるため、リリース時のチーム間での調整コストが発生したり、リリーサーを制限できなかったりなどの課題がありました。 課題を解決したCDパイプライン Required reviewers GitHubのEnvironmentsに、Deployment protection rulesという機能が存在します。たとえば、Required reviewersというruleを使用すると、そのEnvironmentに関して特定のチームや個人による承認を必須とします。図4は、pf_infra_sreというEnvironmentの例です。Web UI上で設定と承認ができます。リスト4のように、Required reviewsが設定されたEnvironmentをGHAのjobで指定すると、そのjobは承認されるまで実行されなくなります。 図4 Required reviewersの設定例 release-approval : runs-on : ubuntu-latest environment : name : pf_infra_sre リスト4 Environmentを使用したjobの例 このjobをデプロイ関連のjobのneedsに指定すれば、結果的にリリーサーを特定のチームや個人に絞れます。 OCIRepositoryに変更 Flux2のSource ControllerをGitRepositoryからOCIRepositoryに変更しました。OCI Repositoryは、GitRepositoryと同じくSource Controllerが管理するカスタムリソースの1つです。OCI(Open Container Initiative)互換のレジストリを参照します。プラットフォーム基盤にFlux2を導入した当初はOCIRepositoryという選択肢がありませんでしたが、この時点では利用できるようになっていました。 GitRepositoryのままだと、PRをマージしたタイミングでGitHubリポジトリを参照してデプロイされてしまいます。これでは、そのGitHubリポジトリの権限を持つスタッフは誰でもPRをマージできてしまうので、Required reviewersでリリーサーを制限しようとしても意味がありません。 そこで、Required reviewersでの承認後にOCIRepositoryへKustomizeマニフェストをpushするGHAのjobを構築しました。OCI RepositoryにはECRを利用し、KustomizationがECR上で管理されているマニフェストを取得して、クラスターへ適用します。また、OCI Repositoryを採用することで、SSH鍵の管理が不要になりました。 改善の結果 以上の改善により、リリーサーを制限できるようになりました。また、Release PRを使う必要がなくなったので、マイクロサービスのリリースにおけるこれまで発生していたチーム間での調整作業が不要になりました *4 。 おわりに 第2回では、ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組みを中心に紹介しました。ZOZOTOWNのリプレイスにより、「柔軟なシステム」「技術のモダン化」「開発生産性の向上」「採用強化」という4つの効果が期待できます。今回の取り組みの紹介を例に挙げると、AWSやK8sなどのクラウド化により、スケーリングなどの観点で柔軟なシステムの構築が可能になりました。IaCやCI/CDに関する複数のモダンな技術を取り入れ、開発生産性が向上しました。そして、これまでの取り組みをテックブログで公開したり、イベントで発表したりしました。その結果、テック業界におけるZOZOの技術プレゼンスが向上し、採用強化につながりました。リプレイス前は10名以下だったインフラチームには、現在SRE部として30名以上が所属しています。 SRE部はZOZOTOWNの成長のため、今回紹介した事例に限らず、さまざまな課題の解決や取り組みをしてきました。今後もZOZOTOWNのリプレイスを進めていきます。 本記事は、技術本部 SRE部 ECプラットフォーム基盤SREブロックの籏野 光輝と、技術本部 SRE部 商品基盤SREブロック ブロック長の堀口 真によって執筆されました。 本記事の初出は、 Software Design 2024年6月号 連載「レガシーシステム攻略のプロセス」の第2回「ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組み」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com *1 : 【ZOZOTOWNマイクロサービス化】API Gatewayを自社開発したノウハウ大公開! *2 : カナリアリリースを自動化!Flaggerでプログレッシブデリバリーを実現した話 *3 : CIOpsからGitOpsへ。Flux2でマイクロサービスのデプロイを爆速にした話 *4 : ついに最強のCI/CDが完成した 〜巨大リポジトリで各チームが独立して・安全に・高速にリリースする〜
アバター
はじめに こんにちは。DevRelブロックの @wiroha です。6月25日に「 ZOZO物流システムリプレイスの旅〜序章〜これまでとこれから 」を開催しました。ZOZOのエンジニアが物流システムリプレイス開発事例を紹介するイベントです。 登壇内容まとめ 弊社から次の3名が登壇しました。各発表はYouTubeにアップロードしてありますので、見逃した方やもう一度見たい方はぜひご覧ください。 コンテンツ 登壇者 分散メッセージングシステムを用いた新発送サービスのアーキテクチャ 作田 航平 ここがつらいよ 物流マイクロサービス 真田 洋輝 リプレイスを安心安全に 〜段階的リプレイスと等価比較〜 上原 駿 分散メッセージングシステムを用いた新発送サービスのアーキテクチャ www.youtube.com speakerdeck.com 作田からは発送サービスのアーキテクチャについて、リプレイス前後の比較やメリット・課題などを発表しました。分散メッセージングシステムを使用することで、基幹システムで障害が発生しても発送業務が継続できるように疎結合な新システムが構築できました。一方すべての課題を解決できたわけではなく、今後の課題も紹介されました。 ここがつらいよ 物流マイクロサービス www.youtube.com speakerdeck.com 真田からは物流マイクロサービスのつらいポイントにフォーカスした発表を行いました。実際に遭遇した課題とその対策を紹介し、Xでも「ありそう」「リアル」といった反響をいただきました。イレギュラーフローも含めた現場運用の理解やバランス感覚が必要で、物流システムの特性を感じました。 当日はたくさんの質問をいただきました。その中で回答できなかったものについて、回答を掲載します。 Q:ダウンフラグONはサーキットブレイカーのようなものですか? A:ダウンフラグ(サーキットブレーカーでいう状態)を自動で更新するか手動で更新するかの違いはありますが、同じようなものです。障害発生時にサーキットブレーカーのように通信自体を遮断するかどうかについてはまだ検討中ですが、障害発生時用の挙動に切り替わる点は同じです。 リプレイスを安心安全に 〜段階的リプレイスと等価比較〜 www.youtube.com speakerdeck.com 上原からは入荷リプレイスの概要と進め方について発表しました。入荷はもし止まってしまうとさまざまな業務に影響が出る重要な部分です。既存課題の解決をしながら安全にリプレイスするため、フェーズを分けて段階的に進めることにしました。旧実装と新実装後で等価比較する仕組みにより、安全にリプレイスを進めることができていました。 こちらの発表に関しても、当日いただいた質問への補足回答をいたします。 Q:モジュラモノリスのアプリケーションを複数人で開発する中で苦労されたエピソードがあれば教えてください。 A:モジュラーモノリス内には、発送と入荷があり、テストデータの準備に苦労しました。というのも発送と入荷の開発は、それぞれのチームに分かれており、お互いの開発状況をあまり把握しておらず、入荷用にデータを修正したり、逆に発送用にデータを修正したりしなくてはならないなどがありました。 Q:具体的にどういう部分(テーブルなど)が原因で基幹DBから分離ができないのでしょうか? A:具体的には在庫などでしょうか。在庫増減などは複雑な部分(ZOZOTOWNや発送などが関わる)なので、分離は難しいと考えました。 最後に 今回はたくさんの方にご参加いただき、ありがとうございました。質問も多数寄せられ、関心の高さが伺えました。今後もさまざまな分野でイベントを開催していきますので、ぜひご参加ください。 ZOZOでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
目次 目次 はじめに 企画LPの儚さ 企画LPをアーカイブする! 仕様 開発について 技術選定 ちょっと技術の話 スクラム開発 ユーザーの反応 おわりに はじめに こんにちは! ZOZOTOWN企画開発部・企画フロントエンド1ブロックの秋山です。ZOZOTOWNトップでは、セール訴求や新作アイテム訴求、未出店ブランドの期間限定ポップアップ、著名人コラボなどの企画イベントが毎日何かしら打ち出されています。私はそのプラットフォームとなる企画LPをメインに実装するチームに在籍しています。 チーム特性としては以下のようなものがあります! クリエイティブコーディング寄りの実装をする機会に恵まれやすい 普段のLP案件内では、エンジニアよりも他職種(デザイナー、PM、ビジネス職)と連携することが多い 企画LPの儚さ 定常的なページと違って、開催期間が定まっている企画LPの命は儚いものです。月に20本ほどの企画LPがローンチされては、人知れずクローズしていきます。 制作陣一同、タイトなスケジュールのなか心を込めてつくりますが、目に見えやすいかたちで実績が残りません。もちろん、GitHubリポジトリ上のコードやデザインラフとしてはある程度残っていきますが、色々と容量を圧迫するものは削除していきたい所存です。そうすると、成果物は段階的に消滅することになります。 削除されないうちにも、企画量が膨大すぎて過去事例を掘り当てるのに苦労しがち、という問題もありました。「あの企画のとき、どう対応してたっけ?」と聞かれても、咄嗟に目当ての情報を見つけられなかったりします。 企画LPをアーカイブする! ちょうどデザイナーチームでも似たような課題感を抱えており、この機会にきちんとプロジェクト化して「ZOZOTOWN LP ARCHIVE」という社内ツールをつくることにしました! まず、普段から親交のあるデザイナーと2人で構想・仕様の大枠を練ったあと、PMにもジョインしてもらい、ビジネス側にもヒアリングしながらプロジェクトを進めました。この起案メンバーでは週次定例を行い、全体の仕様や運用方法を詰める作業や、各部の進捗確認をしていきました。 仕様 リアルタイム検索(フリーワード、日付、タグ検索) LP画面のキャプチャ閲覧・DL LP概要閲覧 各種ドキュメントへのリンク 上記の機能をミニマムで盛り込み、デザイナー・PM・事業部・エンジニア等、企画に関わる多方面のスタッフにとって嬉しいツールを目指します。 これはZOZOTOWNの企画コンテンツ全体としてのポートフォリオでもあります。前提として新規LPは全てアーカイブされますが、せっかくなので過去1年程度の企画はがんばってデータを集め、反映することにしました! これだけでも、200本ほどの量になります。 開発について さらに! 開発メンバーとして、元気な新卒2年生とフロントエンド道25年の大ベテランの方に入っていただけることとなり、プロジェクトが本格始動しました! メンバーそれぞれが事業案件を持ちながらの実装且つ、SRE等の他部署と連携が必要だったので、空白の期間も挟みつつ進みました。全ての工程を完了するには2か月くらいかかりました。 技術選定 社内ツールということもあり、個人的に試してみたいものを自由に使うことができました! 会社のプロダクトで技術選定を自由に行える機会はそう多くないので、わくわくします。 採用したのは、Next.js(App Router)、Panda CSSなどです。Recoil、dayjs、js-file-downloadなどのライブラリも、適宜相談・検討しながら導入しました。ヘッドレスCMSはmicroCMSを利用しました。 ちょっと技術の話 工夫した点などを開発メンバーに聞いてみましょう! こんにちは。大ベテランということで技術選定や困ったことがあったときなどのフォローとアドバイスを担当した竹口です。大ベテランということで紹介されましたが、ZOZOへは入社したてといった状態でした。ということでメンバーの技術レベルや社内の開発環境はどういったものかを把握するところからはじめました。 本記事を読んでいただくとわかると思いますが、意欲高めのメンバーが揃っていたということもありNext.jsのApp RouterやPanda CSSといったZOZOTOWN開発本部内で採用実績のない技術を使っています。社内ツールということもあって積極的にやりたいことを自分たちで選択していきました。 今までのNext.jsのルーティング方式であるPages Routerと、比較的新しいApp Routerになってから変更されたところを調べながら、メンバーでああでもない。こうでもない。といった感じで進めました。最終的には社内のホスティング環境がサーバーでレンダリングできないのがわかり、急遽SSG化してGitHub Actionsでビルドしてデプロイといった大きな変更も。柔軟に対応できるメンバーだったのは日頃から密に対話して進めていたからできたことかと思います。 工夫したのはNext.jsによる開発経験が高めのチームではなかったためPull Requestに対してファイルや変数の命名、コンポーネントの粒度など細かく事例を示したり代替案を出したりしながらレビューしたところです。 入社したてで良いメンバーと楽しく進めることができたのは良い経験となりました。 こんにちは。LPアーカイブのホーム画面実装を担当したゾイです。ホーム画面のメイン機能はリアルタイム検索でしたので、私からはリアルタイム検索の機能実装について工夫した点を話したいと思います! リアルタイム検索には企画タイトルや企画の開催期間のような基本的な情報はもちろん、技術・デザイン・企画種などのタグ検索機能を実装する必要もありました。 SSRで検索機能を実装することも検討しましたが、以下の3点を踏まえCSRで検索機能を実装することにしました。 ヘッドレスCMSにリクエストを送りすぎると負担が重い SSRの場合ロード時間が長くなりUX的に良くない LPアーカイブサイトはSPAっぽくサクサク遷移させたい そのため、状態を共有するコンポーネントが多くてもパフォーマンス的に問題ない設計を工夫する必要があり、状態管理ライブラリーの導入を検討しました。 結果から言うと、今後のメンテナンスしやすさを優先し可読性が良いと思われる Recoil を導入することになりました。 Recoilを導入した理由は次の通りです。 Recoilは atom が更新されたら該当のatomを利用するコンポーネントのみ再レンダリングするため、パフォーマンスが良い。 Recoilは Redux のような他の状態管理ライブラリーに比べて可読性が良く、今後のメンテナンスがしやすい。 新しい状態を追加することが必要になった場合Recoilだと RecoilRoot の配下に新しいatomを追加するだけで済むので、Contextのように親コンポーネントを再度ネストする必要がない。 RecoilはSSR時にメモリーリークが発生している問題があるようです。しかし、LPアーカイブでは以下の点を踏まえ、Recoilを利用しても問題ないと思いました。 LPアーカイブでは主にCSRを利用している 社内ツールなのでのスケールも比較的に小さい それでは、具体的にRecoilを利用してリアルタイム検索機能をどう実装したかを話したいと思います。 1. RecoilRootの配下に親コンポーネントを包む <RecoilRoot > < Home /> < / RecoilRoot> 2. SSR時にヘッドレスCMSから取得したデータをatomに保存する // 全データ保存用のatom const [ result , setResult ] = useRecoilState ( resultAtom ) // ヘッドレスCMSからのレスポンスが更新された場合のみ状態を更新する useEffect (() => { if ( result ! == data ) setResult ( data ) } , [ data , result , setResult ]) 3. 各々のフィルターコンポーネントの入力状態を(2)とは別のatomに保存 // 企画タイトルでフィルターするコンポーネントの例 export const TitleSearch = () => { const [ _ , setSearchInput ] = useRecoilState ( searchInputAtom ) const handleChange = ( e: React . ChangeEvent < HTMLInputElement > ) => { setSearchInput (( prev ) => ({ ... prev , title : e . target . value , })) } return ( < input type = "text" placeholder = "例)ZOZOWEEK" onChange = { handleChange } /> ) } 4. ユーザーのインプットによりフィルターコンポーネントの値が更新されたら(2)のデータをフィルターし、新しい結果を表示する import { useRecoilValue } from 'recoil' import { searchInputAtom } from '@/recoil/atom/searchInputAtom' import { resultAtom } from '@/recoil/atom/resultAtom' // (2)で取得したデータ const result = useRecoilValue ( resultAtom ) const [ filteredResult , setFilteredResult ] = useState ( result ) // (3)で更新したフィルターのデータ const searchInput = useRecoilValue ( searchInputAtom ) useMemo (() => { const filters = [ // LPのタイトルでフィルターする場合の例 const lpTitleRegex = new RegExp ( searchInput . title || '' , 'i' ) ( item: ZozoLpArchiveContent ) => lpTitleRegex . test ( item . lpTitle ) ,   // 開催期間など、その他のフィルター(省略) ] const filteredResult = result . filter (( item ) => filters . every (( filter ) => filter ( item )) ) setFilteredResult ( filteredResult ) } , [ result , searchInput ]) 秋山の方からは、CSS in JSライブラリPanda CSSの使用感の紹介です。 ZOZOTOWNのフロントエンドリプレイス環境ではNext.jsのPages Routerを採用しており、CSS in JSライブラリにはEmotionを利用しています。 今回はNext.jsのApp Routerを使いたかったので、React Server Componentがデフォルトということになります。Emotionは'use client'したコンポーネントでしか利用できない(実装を開始した2023年末時点)ため、App Routerのよさを生かすにはEmotion以外の選択肢を探す必要がありました。 今後他のメンバーが参画する可能性もあり、ZOZOTOWNフロントエンドリプレイス環境と似た書き方をできる方がいい 公式ドキュメントが丁寧 Tailwindなども検討してみたのですが、上記の理由から、Panda CSSを採用しました! Panda CSSにはいろいろな機能が搭載されています。コンポーネント内で何度も使う基本のスタイルと、マイナーチェンジしたスタイルを使い分けて書くことができる“レシピ”という機能がとても便利でした! 基本のスタイルはbaseの中に書いていきます。variants内にはbaseから変化をつけたいスタイルを書いていきます。 const Detail = styled ( 'div' , { base : { width : '620px' , padding : '20px 15px' , } , variants : { isCatch : { true : { padding : '10px' , } , } , } , }) < Detail isCatch = { true } > 略 < / Detail> 今回は利用しませんでしたが、“パターン”という機能もあり、Box、Flex、Wrap、Aspect Ratioなど便利なレイアウトパターンが用意されています。これらは適宜importして使います。 Panda CSSを使っていて個人的に感じたデメリットとしては、CSSプロパティをキャメルケースで書かないといけない&値を’ ’で囲わないといけないのがちょっと面倒なことくらいです。 const Capture = styled ( 'li' , { base : { marginRight : '20px' , } , }) 今回はEmotion、styled-componentsっぽい書き方をしてみました。Tailwind風に書くことも可能で、導入にあたって他のCSS in JSからPandaに変更する場合の学習コストはかなり低そうです。ただし自由に書ける反面、規約などで書き方をチームで統一するなどしないと破綻しそうにも思いました。 あとこれは余談ですが、⌘+Sするたび、ターミナルに🐼が出現して地味に癒されます。 スクラム開発 開発佳境の時期にはデイリースクラムを行い、毎日アイデアを出し合ったり、疑問点を都度解消したりしていました。 デイリースクラムにはGitHubのProject機能を利用しました。タスク管理はGitHubのIssueで行いました。Issue・PRは細かめに分けて、実装・レビュー共にさくさく進められたと思います。また、Slackで専用チャンネルを作り、スクラム以外の時間でも気軽に相談できる環境となっていました。 ユーザーの反応 当初想定していたユーザー層は企画LP関係のスタッフに限られており、使われ方としては以下のようなものがありました。 ビジネス職が企画立案時や営業時の参考にする PMが効果検証や要件策定時の参考にする デザイナーがアイデア帳、過去施策との比較、振り返り用として利用する フロントエンドエンジニアが過去の実装例を掘り当てる ZOZOTOWN全体の企画LPポートフォリオとして活用する いざローンチしてみると、以下のような予想外の恩恵もありました。 バックエンドエンジニアが検索リクエスト数を振り返るのにも使える 新メンバーなどは見てるだけでも勉強になりそう デザインかっこよ 眺めているだけでも楽しい UIがよくてわかりやすい ZOZOTOWNサイトと似たUIなので、馴染みのある操作方法で違和感なく説明なくてもすぐ利用できた といった、デザインを絶賛する声も。 多方面から嬉しいリアクションをもらえたのも、横のつながりを駆使して、職種の垣根を超えて力を合わせた成果です! おわりに 普段の業務での成果物はトップダウンの事業案件にまつわるものがほとんどです。しかし、今回の「ZOZOTOWN LP ARCHIVE」は現場メンバーの「こんなものを作って便利にしよう!」というアイデアから生まれました! いろいろな立場のスタッフの協力を得て、無事に成果物のローンチ・運用まで漕ぎ着けられました! それにしても、発案・構想・設計・実装・運用フロー作成と、最初から最後まで育てた成果物には愛着もひとしおです!「自分たちの城」感がすごいですね! 尚且つ、色々な部署にとって、小規模だけど有益なプロダクトである(自己満じゃない)というのがまた良かったです。 株式会社ZOZOでは、アイデア次第でこんなふうに自由度の高い開発経験をできる環境が整っています! ご興味のある方はぜひ、ご応募お待ちしております! corp.zozo.com
アバター
こんにちは。SRE部プラットフォームSREブロックの高塚です。 6月20日、21日の2日間に渡って幕張メッセで開催された AWS Summit Japan に、SRE部から10名以上のエンジニアが参加しました。この記事では熱気あふれる会場の様子と面白かったセッションについてご紹介します! AWS Summit Japanとは 会場の様子 セッションレポート おわりに AWS Summit Japanとは www.youtube.com AWS Summit Japanは延べ3万人以上が参加する日本最大の「AWSを学ぶイベント」です。今年は昨年に引き続き幕張メッセで2日間にわたり開催されました。ライブ配信も行われたほか、2024年7月5日まではオンデマンド配信を視聴できます。 aws.amazon.com ちなみに2023年と2019年以前はAWS Summit Tokyo、2020年から2022年まではAWS Summit Onlineという名称でした。 会場の様子 入口 朝から大賑わいの幕張メッセ。 撮影:花房 撮影:花房 EXPO 250以上のブースが並びます。 撮影:花房 撮影:高塚 撮影:高塚 基調講演 今年は座席指定券が配布されました。 撮影:鈴木 撮影:鈴木 セッション 2日間で150以上のセッションが行われます。 AWS Summit Japan 2024公式サイトのスケジュール より引用 撮影:高塚 撮影:高塚 お弁当 両日先着4,000名にはお弁当が配られました。 撮影:鈴木 撮影:鈴木 AWS認定者ラウンジ AWS 認定 の保有者が使える休憩スペースです。 撮影:高塚 保有資格に応じたSWAGがもらえます。写真は全冠の江島のもの。 撮影:江島 その他 自由に描けるボード。 撮影:江島 AWS Deep Racer の日本一を決める戦い。 撮影:江島 QuizKnockによるクイズ大会は大盛況でした。 撮影:鈴木 セッションレポート ここからはSRE部のメンバーが気になったセッションを紹介します。 AWS 環境におけるセキュリティ調査の高度化と生成 AI 活用(AWS-18) AWSコスト管理の最前線(AWS-05) Amazon Aurora Limitless Database 内部アーキテクチャ詳解 ~ スケーラビリティと高可用性の秘密 ~(AWS-40) アーキテクチャ道場 2024!(AWS-59) ゼンリンデータコム様のAMDインスタンス導入による効率化事例(AP-24) AWS 環境におけるセキュリティ調査の高度化と生成 AI 活用(AWS-18) 基幹プラットフォームSREブロックの鈴木です。普段はZOZOの持っている倉庫システムやブランド様が触る管理ページなどのサービスのSREとして活動しつつ、社内のAWS管理者としてGuardDutyやOrganizationsの対応を担っています。 内容のまとめ セキュリティインシデントが発生した際の対応について、AWSの「高い可視性」と「柔軟かつ多様な自動化と機能連携」を活かして、セキュリティ調査の高度化を図ることができます。 これまではセキュリティの調査においてログを分析し、頭の中で攻撃の流れをイメージしながら分析していく必要がありました。生成AIの活用によって、このログ分析の際に発生する調査の本質ではない「クエリの作成」がAWS Config、CloudWatch Logs Insightにおいてサポートされました。 またAmazon Detectiveがあらゆるサービスを統合し、いままで分析者の頭の中で構築する必要があった攻撃の流れを構築し、調査をサポートしてくれるようになりました。この調査に関しても生成AIによって内容を要約して担当者の理解をサポートしてくれるため、セキュリティ調査の高度化が図れるようになりました。 感想 ログの分析において、自身がクエリの作成に時間を費やしてしまうことがあったため、生成AIによってこの部分をサポートしてくれるのは非常に有用だと感じました。Athenaのサポートも早く来てほしいと思います。 インシデントの対応はかなり専門性が高く、専任のチームではなく社内でAWSの管理をサービス運用の片手間で行っている弊社においては「どのように知見を共有していくか」「有事の際、いかにラクに調査ができる状態にするか」などが課題だと感じていました。 サービスの進化と生成AIによって、これらの課題に対して一歩解決に近づいたことを感じることができました。Detectiveに関しては正直費用もかかるものとなるのでこれから検討していくことになりますが、セキュリティの重要性を考えると検討する価値はあると感じます。 もしものときのためにどれだけ備えられるかがこの分野では重要だと考えているので、現在AWSが提供する備えについて幅広くどのような連携があるかを知れた点でよいセッションでした。 AWSコスト管理の最前線(AWS-05) カート決済SREブロックの伊藤です。私はコスト最適化に興味があったため、コスト管理についてのセッションに参加しました。こちらはCloud Financial Management(CFM: クラウド財務管理)を実現するにあたり、どのようなツールを使えば良いのかをユースケースを基にして紹介するセッションです。 クラウド財務管理(CFM、財務業務、FinOps とも呼ばれる)とは、クラウドリソースのプロビジョニング、デプロイ、モニタリングに対するコスト意識を浸透させ、説明責任を推進するための一連の原則や実践を指します。 S&P Global 2022年11月 Discovery Report P.5 より引用。 具体的には「5月分の利用料金が上昇した原因調査とコスト最適化」を題材として、以下のようにして各問題の提起と解決方法について紹介しています。 カテゴリ 問題提起 ツール 結果例 可視化 コストが増えた要因は何か? AWS Cost Explorer 全体でどのサービスの費用が増加しているかがわかる EC2インスタンスの費用が増加していることがわかったが、管理しているのはどこなのか? コスト配分タグ ○○というアプリによる利用料金が増えていることがわかる もっと細かく見るには? AWS Cost and Usage Reports リソース単位や時間単位での分析が可能となる 最適化 コスト最適化をどのように行えばいいか? EventBridgeスケジューラ など 土日、夜間の開発環境停止によるコストの削減 コスト最適化ハブ インスタンスタイプの見直しやストレージの見直しによるコスト削減 予測・計画 もっと早く気づくにはどうすればよかったか? 予算 の設定・ 予算アラート ・ 異常検知アラート の設定 コストが想定以上にかかっている場合に気付けるようになる この中で私が本セッションを通じて初めて知ることができたのはコスト最適化ハブです。無料で使えるということもあり、早速、開発環境用アカウントで有効化してみました。有効化後、データの収集が完了してから(24時間以上が経過後)、確認した結果が次の画像です。 実際に試したコスト最適化ハブのスクリーンショット それぞれのリソースに対してどのようなアクションが取れるかと、それによるコスト削減率、実装作業負担の高さやリソースの再起動が必要になるかなどを一覧で見ることができました。 今まで認識できていない部分もあったのでこれらを活用してコスト削減を加速させていきたいと思います。 Amazon Aurora Limitless Database 内部アーキテクチャ詳解 ~ スケーラビリティと高可用性の秘密 ~(AWS-40) 商品基盤ブロックの佐藤です。私はAmazon Aurora Limitless Databaseの背景と内部アーキテクチャについて解説するセッションに参加しました。 まず興味深かったのは、GroverとCaspianについての説明です。Aurora専用ストレージであるGroverは「The log is the Database」のコンセプトに基づき、データベースの更新ログを集め処理することでディスクI/Oを80%削減しました。Caspianはハードウェア上でオーバーサブスクリプションによりインスタンスを起動し、負荷状況に応じてリソースをダウンタイムなしでミリ秒単位で割り当てることができます。この処理中、データベースのワークロードには一切影響を与えず、さらにキャパシティが枯渇した場合は別のハードウェアへライブマイグレーションを行う機能もあります。 配布資料(PDF) のP.9より引用 配布資料(PDF) のP.10より引用 Amazon Aurora Limitless Databaseはマネージドシャーディングによりライトスケーラビリティとストレージサイズを拡張できるサービスです。アプリケーション側での開発は不要で、Limitless Database用のエンドポイントにクエリを投げるだけで透過的にシャーディングを行うことができます。数百万件のトランザクションを処理してペタバイトクラスのストレージを使用でき、複数のシャードをまたいだ分散トランザクション、シャードスケールの自動調整など、そのすべてがAuroraのプラットフォームとして提供され、既存のAuroraの機能も使うことができます。 配布資料(PDF) のP.19より引用 Limitless Databaseには2つのテーブルタイプがあります。1つはシャードキーをもとにして各シャードにテーブルが分配されるシャードテーブルです。同じシャードキーを持つ関連テーブルは同じシャードに格納され、ネットワークのスループットを減らすことでパフォーマンスを最適化します(コロケーション)。もう1つはすべてのシャードにコピーが作られるリファレンステーブルです。更新が少なくテーブルサイズも小さいテーブルを設定します。この2つのテーブルタイプを活用することでシングルシャードクエリが実行しやすくなり、パフォーマンスが向上します。 配布資料(PDF) のP.25より引用 Limitless Databaseの内部アーキテクチャは2つのレイヤーで構成されています。1つは分散トランザクションを制御するルーターで、どのシャードにどのデータがあるかというメタデータを管理し、各シャードから返ってきたデータを集約してアプリケーションに返します。もう1つはデータアクセスシャードレイヤーです。ここもメタデータだけを保持し、最終的な実行プランを立てて自身のシャードに対してクエリを実行し、結果をルーターに返します。シングルシャードトランザクションでは、コミットやロールバックの処理もこのレイヤーで行います。これにより、複数のシャードで並列クエリ実行が可能になり、スループットを向上させます。このように、いかにシングルシャードトランザクション構成にできるかがチューニングポイントのようです。 配布資料(PDF) のP.34より引用 配布資料(PDF) のP.35より引用 水平スケール(シャードスプリット)はユーザー側で設定することも、Aurora側に任せることもできます。Groverによりデータのクローンは高速に行われるので、ルーターやデータアクセスシャードは軽量なメタデータのやり取りだけで完結します。そのため高速なスプリットが可能になります。普段の運用でインスタンスのスケール変更を頻繁に行う自分としては非常に関心を引いた話でした。 シャード間の整合性を取る仕組みとして、EC2 TimeSync ServiceとPostgreSQLのアーキテクチャを組み合わせたバウンディッドクロックが紹介されました。EC2 TimeSync Serviceは現在時刻の他にEarliest Possible TimeとLatest Possible Timeという時間情報の信頼区間も配信しています。あるタイムスタンプでコミットしたというルーターからの情報があっても、クエリが影響を及ぼすすべてのシャードのEarliest Possible TimeとLatest Possible Timeの信頼区間内でなければコミットは待機します。これをマイクロ秒単位で処理することで、高速にトランザクション分離レベルで制御します。 配布資料(PDF) のP.48より引用 全体を通しての感想として、導入と運用のコストが低く、煩雑なタスクを減らしマネージャビリティ(管理・運用性)を向上させるマネージドデータベースの基本コンセプトに沿った魅力的なプロダクトだと思いました。ただし、料金コストがどれくらいになるか気になりました。今後、書き込みを多く行うプロダクトでのPoCを検討したいと思います。 アーキテクチャ道場 2024!(AWS-59) フロントSREブロックの江島です。ZOZOTOWNのエンドユーザーに近い部分(フロントエンド、BFF等)を担当領域としています。私は「アーキテクチャ道場 2024!」というセッションについて紹介します。 アーキテクチャ道場について 撮影:江島 SAの方が実際に設計したアーキテクチャを題材に、チーフテクノロジストの内海さんが聴講者の前でレビューするセッションです。今回のテーマはレジリエンスでした。2つのお題に対してレジリエンスを高めるためのアーキテクチャ改善を検討します。 セッションの内容(1つ目のお題) 撮影:江島 1つ目のお題は、マルチAZ構成のシステムにおいてグレー障害やブラウンアウトが発生した場合にユーザ影響を最小化することでした。AZ障害を検知した場合にはワークロードから当該AZを切り離すという方針で検討されました。また、それを実現するためにAZ毎の独立性を高めるように設計がなされました。 セッションの内容(2つ目のお題) 撮影:江島 2つ目のお題は、信頼性の低いサードパーティサービスへ依存したアプリケーションにおいて、依存度を緩和させることにより可用性を高めることでした。両者の間に追加の緩衝機構を設ける方針で検討されました。緩衝機構は、レートリミット、リトライ、サーキットブレーカー、キャッシュや非同期処理などの機能を組み合わせる形で設計がなされました。 2つのセッションを通じて まとめとして、クラウドにおけるレジリエンスの基本的なアプローチは障害の「原因」ではなく「影響」をコントロールすることだと説明がありました。その手段として「障害の範囲を限定するための隔壁(バルクヘッド)を設けること」と「障害の波及を遮断するための緩衝機構を設けること」が推奨されました。 感想 エキサイティングで学びの多いセッションでした。深く考え抜かれたアーキテクチャに対して、様々な視点から質疑が繰り広げられる光景に心を奪われました。私自身、マルチAZでの負荷分散を前提とした構成は幾度となく見てきましたし、それがベストだと思っていました。これをあえて崩すことによりAZ毎の独立性を高めるというアプローチには驚きを覚えました。また、緩衝機構に相当する仕組みは弊社でも導入しておりますが、その必要性や重要性を再認識する良い機会となりました。本サミットで学んだことを生かして、ZOZOTOWNのレジリエンスをより高めていきたいと思います。 ゼンリンデータコム様のAMDインスタンス導入による効率化事例(AP-24) 検索基盤SREブロックの花房です。普段はZOZOTOWNの検索関連マイクロサービスにおけるQCD改善やインフラ運用を担当しています。 現在、SRE部ではコスト削減に注力しています。その中でインスタンスタイプ変更によるリソース最適化は有用な手段の1つです。システムの用途に合わないインスタンスタイプを利用しているとコストパフォーマンスが悪いまま、無駄なコストを払い続けることになります。EC2には多様なインスタンスタイプが用意されていますが、今回は特にコストパフォーマンスに優れたM7aおよびM6aインスタンスを比較・導入した事例について学びました。そのセッションについて紹介します。 M7a・M6aインスタンスタイプ 本セッションでは、先に日本AMD社からM7aとM6aについて説明がありました。M7aとM6aにはそれぞれ下記のAMD CPUが搭載されています。 インスタンスタイプ CPU M7a 第4世代 AMD EPYC "Genoa" M6a 第3世代 AMD EPYC "Milan" それぞれのインスタンスタイプに搭載されているCPUのスペックと特徴を下記に示します。 配布資料(PDF) のP.9より引用 第4世代のCPUはハイパースレッディングをオフにしているためスレッドの記載はありません。第4世代のCPUの処理速度はスレッドを使用しない方が速くなるそうです。 コストパフォーマンスについて、M6iと比較すると下記のような差があります。 配布資料(PDF) のP.7より引用 M7aの単価はIntel CPUを搭載したインスタンスタイプよりも高くなったようです。しかし、CPU性能の向上に伴い必要な台数を少なくできるため結果的にコストを抑えられます。 M7aとM6aにおいて、最適なリソースを選択するには下記の図が役に立ちます。 配布資料(PDF) のP.11より引用 M7a・M6aインスタンス導入事例 次にゼンリンデータコム社からの事例紹介がありました。 ゼンリンデータコム社では、インフラ沿革の中でコストが課題になっており、コストを抑制するための取り組みとしてリソース最適化を実施しました。パフォーマンスとコストに優れるAMDインスタンスへの変更を検討し、実際に試してみた結果、M7aとM6aの導入に至ったようです。下記にコスト抑制の取り組みの図を示します。 配布資料(PDF) のP.24より引用 AMDインスタンスを導入する際の考慮点について下記が挙げられていました。 手軽さ:簡単に導入できるか 金額面:利用料金が妥当か 性能面:処理能力に問題がないか それぞれについて詳しく見ていきます。 まずは1つ目の「手軽さ」についてです。使用中のインスタンスタイプのCPUアーキテクチャがx86互換であれば、AMDインスタンスを簡単に試すことができます。プログラムの改修なしでインスタンスタイプのみ変更すればよいためです。下記に図を示します。 配布資料(PDF) のP.27より引用 次に2つ目の「金額面」についてです。SavingPlansを使用した場合、x86互換のインスタンスタイプの利用料金は下記のようになります。 配布資料(PDF) のP.28より引用 スライドにも記載されているようにユースケースに応じて使い分けることが重要です。リソース最適なインスタンスタイプを選択すればコストは抑えられますが、そうでない場合は反対にコストが上がる可能性もあります。 最後に3つ目の「性能面」についてです。性能比較では下記の内容を実施したようです。 配布資料(PDF) のP.29より引用 性能比較の詳細を記載すると本レポートに収まらないため記載しません。性能比較の結果は、AMDインスタンスのスコアが一番高くなり、新しい世代の方が古い世代よりスコアが向上したようです。 APIサーバについてはM6aからM7aに変更したことにより、約40%の台数削減を実現したようです。性能向上に伴う台数削減により、単価が高いM7aの方がコストパフォーマンスを改善できるケースとなりました。 一方、BatchサーバについてはCPUが高負荷になる処理が少ないため、Batch処理時間は大きく変化せず、M6aの方が良いコストパフォーマンスを見込めるケースになりました。 まとめ セッションを通して、M7aとM6aのAMD CPUについて学ぶことができました。これらのCPUはx86互換であるため、同じx86系のインスタンスを使用していればAMDインスタンスを簡単に試すことができます。 ゼンリンデータコム社のシステムを使用した性能比較においても、AMDインスタンスはコストパフォーマンスに優れていることが分かりました。性能が向上するインスタンスタイプを選択した場合、本当に性能が向上するのか試すことは大事だと感じました。また、性能が向上したとしても、コスト面でのメリットが得られるかを確認することも重要です。今回学んだことを考慮して、今後のインスタンスタイプ変更によるコスト削減に取り組んでいきたいと思います。 おわりに セッションや展示ブースで多くのことを学べるのはもちろん、AWSのエキスパートや他社のエンジニアの方々と交流し、多くの刺激を受けられるのが現地参加の醍醐味です。 今回得た知見を社内外に共有しながら、これからもAWSを活用してプロダクトとビジネスの成長に貢献していきます。 ZOZOではAWSが大好きなエンジニアを募集しています。奮ってご応募ください! corp.zozo.com
アバター
はじめに こんにちは、CISO部の 兵藤 です。日々ZOZOの安全のためにSOC対応を行なっています。普段のSOCの取り組みについては以下「フィッシングハントの始め方」等をご参照ください。 techblog.zozo.com 6/10〜6/12にアメリカのフィラデルフィアで開催されたAWS re:Inforce 2024に参加してきました。この記事ではその参加レポートをお届けします。 フィラデルフィア現地会場前の様子 目次 はじめに 目次 AWS re:Inforce 2024とは セッションの紹介 Builder's Session TDR355 Detecting ransomware and suspicious activity in Amazon RDS TDR352 How to automate containment and forensics for Amazon EC2 Breakout Session TDR305 Cyber threat intelligence sharing on AWS おわりに AWS re:Inforce 2024とは re:Inforce はAmazon Web Services(AWS)が主催する大規模な技術カンファレンスであり、特にセキュリティへフォーカスしたものとなっています。 セキュリティに関する最新のAWSでの動向や、サービスのアップデートなどがセッションやワークショップを通じて紹介されます。 ZOZOのサービスをよりセキュアにするヒントを得るために、本カンファレンスに参加しました。 今年のre:Inforceは生成AIが流行していたこともあり、そのAIに対するセキュリティをどう担保していくかといったテーマが多く取り上げられていました。 re:Inforce 2024 会場 また以下のようなGuardDutyのS3対応や、IAMのPasskey対応など新機能の発表もあり、現地でも大きな話題となっていました。 aws.amazon.com aws.amazon.com 最後にClosing Receptionとして、パーティが開催されました。 Closing Receptionの様子 セッションの紹介 re:Inforceでは多くのセッション、ワークショップなどが開催されており、その中からいくつか気になったものをピックアップして紹介します。 自分は脅威情報やインシデントレスポンスに関わるセッション、ワークショップを見ることが多かったのでそちらに偏りがちな内容ですが、ご了承ください。 Builder's Session TDR355 Detecting ransomware and suspicious activity in Amazon RDS TDR352 How to automate containment and forensics for Amazon EC2 Breakout Session TDR305 Cyber threat intelligence sharing on AWS Builder's Session TDR355 Detecting ransomware and suspicious activity in Amazon RDS このBuilder's Sessionの概要は以下の通りです。 In this builders’ session, acquire skills that can help you detect and respond to threats targeting AWS databases. Using services such as AWS Cloud9 and AWS CloudFormation, simulate real-world intrusions on Amazon RDS and Amazon Aurora and use Amazon Athena to detect unauthorized activities. The session also covers strategies from the AWS Customer Incident Response Team (CIRT) for rapid incident response and configuring essential security settings to enhance your database defenses. The session provides practical experience in configuring audit logging and enabling termination protection to ensure robust database security measures. You must bring your laptop to participate. 本セッションでは AWSが公開しているWorkshop を活用してRDSに対するランサムウェア攻撃のシミュレーションとインシデント対応を実施しました。具体的には以下のような手順で実施しました。 AWS Cloud9を利用してシミュレーション環境を構築 RDSの構築 攻撃のシミュレーション ログ分析 分析に関しては攻撃者による「AWSアカウントの列挙」「データベースの列挙」「データベースの操作」などをAmazon Athenaを利用して調査しました。 Amazon Athenaは使用したことがなく、クエリベースでログ調査ができるのでとても面白いと感じました。 ランサムノートを確認した画面 TDR352 How to automate containment and forensics for Amazon EC2 このBuilder's Sessionの概要は以下の通りです。 Automated Forensics Orchestrator for Amazon EC2 deploys a mechanism that uses AWS services to orchestrate and automate key digital forensics processes and activities for Amazon EC2 instances in the event of a potential security issue being detected. In this builders’ session, learn how to deploy and scale this self-service AWS solution. Explore the prerequisites, learn how to customize it for your environment, and experience forensic analysis on live artifacts to identify what potential unauthorized users could do in your environment. You must bring your laptop to participate. 本セッションでは AWSのドキュメント のソリューションを活用します。以下のようなアーキテクチャの構成はすでにセッションでは構築されていました。 ソリューションのアーキテクチャ図 この 自動フォレンジックオーケストレーター を利用してEC2でのインシデントレスポンス対応におけるフォレンジックを行いました。 フォレンジック対応としては、ディスクのスナップショットとメモリダンプの取得になります。AWSの機能ではEBSスナップショットなどディスクデータを取得できますが、メモリダンプの取得は提供されていないため、このオーケストレーターではメモリダンプを LiME を利用して取得していました。取得したダンプはS3の方に飛ばしていることが コード からわかります。 では実際にセッションで実行した内容を紹介します。 インシデントのAlertはSecurity Hubからキャッチし、Security Hubのカスタムアクションを利用して、フォレンジック対応をスタートします。以下の画像で「Forensic Triage」を実行しました。 Security Hubでのカスタムアクション そこからEvent Bridge経由でStep Functionが起動されます。最初のFunctionは以下のように起動されます。 最初のStep Function このStep Functionを実行した際にはメモリダンプやディスク取得しませんでした。インスタンスTagを判断して次のStep Functionを実行するかしないかを判断しているようです。 次の2つのStep Functionでは以下のようにディスクスナップショットとメモリダンプを取得しています。 ディスクフォレンジックのStep Function メモリフォレンジックのStep Function メモリフォレンジックのStep Functionの中にEC2のネットワーク分離のStep Functionがあります。これは コード を確認するとセキュリティイベントタイプによって実行するのか判断しているようです。 本番環境における分離の実行は慎重に行う必要があると思うので、この部分は組織によって要検討の箇所だと考えます。 セッション中はここまでしか時間がなかったのですが、実際のフォレンジックで何をみているかというのはSSMドキュメントを参照すればある程度わかりました。 例えば メモリフォレンジックの内容 を覗いてみると、volatility2を利用していくつかコマンドを打っていることがわかります。 また、メモリダンプも取得できているので自前でフォレンジックも可能だと思います。Meterpreterなどファイルレスマルウェアの抽出だったりはこのダンプがあれば可能となります。あれば嬉しいメモリダンプですね。 EC2のフォレンジック対応は初めてだったのでとても学びの多いセッションでした。 Breakout Session TDR305 Cyber threat intelligence sharing on AWS このBreakout Sessionの概要は以下の通りです。 Real-time, contextual, and comprehensive visibility into security issues is essential for resilience in any organization. Join this session to learn about cyber threat intelligence informed security, including lessons learned from the Australian Cyber Security Centre (ACSC) Cyber Threat Intelligence Sharing (CTIS) program, built on AWS. With the aim to improve the cyber resilience of the Australian community and help make Australia the most secure place to connect online, the ACSC protects Australia from thousands of threats every day. Learn the technical fundamentals that can help you apply best practices for real-time, bidirectional sharing of threat intelligence across all sectors. このセッションでは、オーストラリアサイバーセキュリティセンター(ACSC)のサイバー脅威インテリジェンス共有(CTIS)プログラムについて紹介。そして、AWS上でどのように脅威情報を活用しているかを紹介していました。 どのように脅威情報を扱って攻撃を予防、検知しているかは以下スライドのように概要を説明されていました。AWSというより、一般的な脅威情報の扱い方の紹介ですね。真ん中のTrust CommunityがACSCで、MemberがASD 1 (Australian Signals Directorate)partnerです。JPCERTと似た雰囲気を感じました。 具体的に脅威インテリジェンス(Tactical相当)をAWSでどう活用していくかは以下のように説明されていました。 上記は脅威インテリジェンスをAWSのセキュリティサービスにどのように活用しているかを示すアーキテクチャ図です。ただAWSは公開サービスで利用していることが多いと思うので、鮮度がイマイチな情報をそのまま投入すると過検知が多くなる懸念はあります。 次は脅威インテリジェンスを利用した脅威ハンティングといったところです。利用する組織はこういった使い方が多いのではないでしょうか? これは脅威インテリジェンスをシェアする場合です。 脅威インテリジェンスをAWSで活用していくヒントを得たセッションで、とても参考になりました。 おわりに 私自身、参加当時はAWS初心者レベルでありビクビクしながら参加したre:Inforceでしたが、面白く、学びが多いセッションばかりでいい経験になりました。 本カンファレンスの経験を生かして、社内でのセキュリティ強化をより一層実施していければと思います。 ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com ASD ↩
アバター
こんにちは、ブランドソリューション開発本部で WEAR by ZOZO のiOSアプリの開発を担当している新卒2年目の山田( @gamegamega_329 )です。 今年もWWDCはオフラインとオンラインの同時開催となり、私は昨年に引き続き、2年連続で現地参加しました。昨年の現地レポートは下記の記事をご覧ください。 techblog.zozo.com 本記事では、現地ならではのイベント内容や雰囲気などを中心に、可能な範囲でご紹介します。現地参加の魅力や、実際に体験した貴重な瞬間をお伝えできればと思います。 WWDCとは? スケジュール Day 0 - 6月9日 Welcome Reception 今年のノベルティ 受付を終えた後 デベロッパーとの交流 Apple Design Awards Day 1 - 6月10日 Keynote Platforms State of the Union Discover Apple Park In-person Labs 提供されたご飯たち Day 2 - 6月11日 Evening Session Appleスタッフ&デベロッパーとの交流 その他 現地での過ごしやすさ 日本の開発者たちとの交流 現地参加するなら用意・準備しておくと良いこと 最後に 「WWDC24 報告会 at LINEヤフー, ZOZO」を開催します WWDCとは? WWDC(Worldwide Developers Conference)は、Appleが毎年開催している開発者向けの重要なカンファレンスです。このイベントでは、OSのアップデートや新しい開発ツールが発表され、開発者にとって大変有益な情報が提供されます。昨年に引き続き、今年もオフラインとオンラインのハイブリッド形式で開催されました。 スケジュール 日付 時間(Pacific Time) コンテンツ 場所 6/9 3:00 PM ・Early Check-in ・Design Award Infinite Loop 6/10 8:00 AM ・Check-in Apple Park Visitor Center 10:00 AM ・Keynote Apple Park 1:30 PM ・Platforms State of the Union Apple Park 2:00 PM ・Discover Apple Park Apple Park Inner Ring 2:00 PM ・In-person labs Apple Park Caffè Macs 6/11 6:00 PM ・Evening Session Developer Center Day 0 - 6月9日 Welcome Reception 今年もイベントの前日にInfinite LoopでWelcome Receptionが開催されました。 Infinite Loopの入口に立つ筆者 このイベントでは、事前にチェックインができるため、翌日の入場がスムーズになります。さらに、早めにイベントの雰囲気を楽しめるだけでなく、ノベルティも一足先にゲットできます。 Infinite Loop内にあったWWDC24のオブジェ 今年のノベルティ 今年のノベルティはピンバッジ、バッグ、水筒、レジャーシートでした。これから日本は暑くなるので、これらはBBQなどで大いに活躍しそうです。さらに、Apple Vision Proのピンバッジを手に入れられ感動しました。帰国したらバッグにつけようと思います。 WWDC24のノベルティはピンバッジ、バッグ、水筒、レジャーシートの4種類 受付を終えた後 軽食やドリンクを楽しみながら、参加者同士での会話を楽しみました。提供された食べ物や飲み物は、唐揚げ・春巻き・アイスクリーム・コーラなど多岐にわたり、食べ放題なので時間いっぱい楽しめます。ただし、次の日の胃もたれや顔のむくみには注意が必要です。 軽食コーナー ドリンクステーション 暑さを和らげてくれたアイスクリーム デベロッパーとの交流 アメリカ・インド・中国・韓国・ブラジルなど、多くの国籍のデベロッパーやデザイナーと交流しました。特に私は個人でリリースしたアプリの話で盛り上がり、ヘルスケアアプリや大学案内アプリ、ジェスチャー認識アプリなど、様々なアプリについて語り合いました。 現地で出会ったデベロッパーとの記念写真 私が開発した「腹筋ローラーアプリ」を紹介すると、多くの人が「Amazing」と称賛してくれました。この日に話した多くの人が個人でアプリをリリースしていて、熱い想いを共有できたのは忘れられない思い出になりました。 Apple Design Awards Infinite Loopの食堂内には、 Apple Design Awards のトロフィーが展示されていて、このトロフィーには受賞者の名前が刻まれていました。サンプル品を手に取ると、見た目よりも重量感があったので驚きました。Welcome Receptionが終わった後、表彰者のみで表彰式が行われるとのことでした。 Apple Design Awardsの受賞者に贈られるトロフィーのサンプル 気づけば7:00 pmを過ぎ、気温が下がって羽織ものがないと寒いくらいになっていました。こうしてDay 0が終了しました。 Day 1 - 6月10日 開場は8:00 amからですが、最前列を確保するために6:30 amから並び始めました。グループ会社であるLINEヤフーのエンジニアをはじめとする日本のデベロッパーたちと一緒に並びました。早朝なので少し肌寒く感じました。 待ち時間には、ポンデリングのようなモチモチのドーナツや、香り高いコーヒーが提供されました。おいしいスナックを楽しみながら、開場を心待ちにする時間はとても充実していました。 開場するまでの待ち時間にはドーナツやコーヒーが提供された 昨年は最前列に座っていましたが、今年は最前列から3列目までがEnterprise席として確保されていたため、私の席はそのすぐ後ろの4列目となりました。実質的には最前列に近い席で、安心しました。 昨年と異なり前から3列目まではEnterprise席だったので4列目に着席 席を取った後、Keynoteが始まるまでの間に用意されていた朝食を楽しみました。WWDCに参加すると、朝から夜まで食事が提供されます。どんな食事が提供されたのかについては、最後にご紹介します。 Keynoteが始まるまでの時間は周りを散策して過ごしました。OpenAI社のCEOであるサム・アルトマン氏にも遭遇しました。この日の最高気温は34度で、Keynoteが始まる直前には日差しが強くなったため、サングラスをかけて待機していました。 Keynote www.youtube.com CEOのティム・クック氏とSenior Vice Presidentのクレイグ・フェデリギ氏が壇上に立ち、短い時間トークしました。その後、Keynote本編のビデオが会場内のスクリーンで再生されました。 CEOのティム・クック氏とSenior Vice Presidentのクレイグ・フェデリギ氏 今回の目玉は、発表前から噂されていたAI「Apple Intelligence」でした。 www.apple.com 発表の瞬間、会場は拍手喝采で大いに盛り上がりました。このAIは独自のモデルであり、ChatGPTとの連携も可能ですが、単独でも機能するのが特徴です。その他にも、コントロールのカスタマイズやMath Notesなど、様々な新機能に胸を躍らせました。 会場が沸いたApple Intelligenceの発表 このアップデートによって、アプリ内だけでなく、Apple Intelligenceや他のアプリとの連携を含めて、どのように自社サービスを活用してもらうかを考える必要があると感じました。 Platforms State of the Union www.youtube.com Keynoteに続くPlatforms State of the Unionは、強い日差しを避けるために別の席から観ました。 Platforms State of the Unionは室内で視聴 私が特に注目したのはXcodeの新機能「Swift Assist」でした。この発表でも大きな歓声が上がり、会場は大いに盛り上がりました。GitHub Copilotが発表されて以来、iOSエンジニアたちはこの機能を待ち望んでいたことでしょう。 他にも、 Swift Testing や SwiftDataのアップデート など、デベロッパーにとって魅力的な発表が多く、非常にワクワクする内容でした。 Discover Apple Park セッションが終わった後には、Appleのエンジニアやデザイナーに直接質問できるラボの時間や、Apple Park内を自由に散策できる時間がありました。私はすぐにApple Parkを散策することにしました。その魅力的な見どころをいくつか紹介します。 まず、目を引くのは巨大な虹のオブジェです。まるで夢の中にいるかのようなこの場所で、私はジャンプして記念写真を撮りました。 巨大な虹のオブジェの前でジャンプして記念撮影 次に訪れたのは、まるで本物のような人工池です。波の音が心地よく流れていて、最初は本物の池だと勘違いしてしまいました。波の音も実はスピーカーが巧妙に配置されていて、そこから音が流れているとのことでしたが、スピーカーの場所は全く見つけられませんでした。 本物の池だと勘違いした人工池 さらに、この探索中にバナナチョコレート味のアイスクリームを配っていて、思わず手に取ってしまいました。暑い日にはぴったりのご褒美でした。 また、Download Stationでは、有線接続で最新のXcodeを高速ダウンロードできました。開発者にとっては嬉しい限りです。早速、私も最新のXcodeをその場でインストールしました。 高速な回線が用意されていたDownload Station In-person Labs 散策を終えた後は、Appleの社員に質問できる時間を存分に楽しみました。 Caffè Macsでは、各エリアに担当者が配置されており、その分野のスペシャリストに直接質問できます。オレンジエリアは3階、紫エリアは外で行われていました。 In-person Labsの配置図 スタッフの役割は着ているTシャツの色で分かります。詳細な技術に詳しい担当者や、広範囲にわたってメンバーの役割を把握している案内スタッフなどがいました。 Tシャツの色分けでスタッフの役割がわかりやすくなっていた 私は、セッションビデオで興味を持ったApple Intelligence、SwiftUI、Xcodeに関連する質問を投げかけました。特に、どのような仕組みで動作しているのかや、開発チームの規模について詳しく聞きました。 また、予約制のDesign Labもあり、自分のアプリをデザイナーに見せて直接フィードバックや感想をもらえます。細かなニュアンスなどの説明が難しかったので、翻訳アプリを使ってやりとりしましたが、スタッフは快く対応してくれました。 私は、自分が担当しているリニューアルしたWEARアプリを見せてフィードバックを受けました。良い点や改善点、全体的な所感について話し合いました。デザイナーによっては、絵を描いて提案してくれることもあり、ワイヤーフレームが印刷された用紙を持っているデザイナーもいました。 提供されたご飯たち 朝食には、クロワッサンやパイのような食感のエンパナーダ、フルーツの盛り合わせ、オーバーナイトオーツなど、普段食べることの多くない珍しいメニューが並びました。 朝食 昼食はたくさんメニューがありましたが、その中でも、グリルチキン&キノアサラダ、ティラミスを選びました。 昼食 昼食と同様、夕食も多くのメニューがありました。その中でも、私はクリスピーチキンワッフル、フルーツタルトとチョコレートキャラメルバー、プラリネクリームのシュークリームを選びました。 夕食 食事の時間以外でもスナックやドリンクが配られていました。 豊富に用意されていたドリンク おやつのドライフルーツ Day 2 - 6月11日 この日は予約制のセッションに参加しました。セッションは「Morning」「Afternoon」「Evening」の3つの時間帯から選べます。ただし、予約数に限りがあるため、早めに好きな時間帯を選ぶ必要があります。私は「Evening」のセッションのみ予約できました。 Evening Session 会場はDeveloper Centerで、生のプレゼンテーションを体験できる貴重な機会でした。プレゼンテーションはとてもカッコよく、迫力満点で、いつか自分もこんな発表ができるようになりたいと感銘を受けました。 参加したEvening Sessionの様子 ライブコーディングやデモなど、予約制ならではの特別な内容が盛りだくさんで、参加した甲斐がありました。 Appleスタッフ&デベロッパーとの交流 セッションが終わった後は、軽食を楽しみながら、セッションのプレゼンターやAppleのデベロッパー、さらには各国のデベロッパーたちと交流しました。 Evening Session後の交流会で提供されていた軽食 この時間をフル活用して、初日のIn-person Labsで聞き逃してしまった質問や、セッションを通じて気になったことを、セッションのプレゼンターやAppleのデベロッパーに直接聞きました。それだけでなく、ブラジルの学生とも仲良くなり、セッションの感想を語り合ったり、雑談を楽しんだりしました。 その他 現地での過ごしやすさ WWDC24の会期中は全て快晴に恵まれましたが、一日の寒暖差が激しいものでした。日中は暑く、気温は28〜31度に達しましたが、夜18時頃から急激に冷え込み、20度前後まで下がりました。日中の服装としてはTシャツと長ズボンが適していましたが、夜間の急な冷え込みに対応するため、羽織ものを持参するべきでした。 日本の開発者たちとの交流 iOS界隈で著名な方々が多く参加している印象を受けました。会うたびに情報交換することが多く、有益な時間を過ごせました。また、 Swift Student Challenge の参加者とも交流する機会があり、地域活性化のアプリを開発している方とお互いのアプリを紹介し合う場面もありました。さらに、日本のAppleエバンジェリストの方が親切に案内してくれたので、非常に助かりました。 現地参加するなら用意・準備しておくと良いこと 日本コミュニティに参加して、事前に情報収集しておくこと(例:try! SwiftのDiscord) 日差しが強いので、日焼け止めやサングラスを持っていくこと(私は現地のゴルフショップでサングラスを購入しました) 基本的に現金を持たずにクレジットカードで支払いできるが、紛失や盗難の対策を講じておくとなお良い 自社サービスや個人で開発しているアプリがあれば、アピールできる資料やノベルティなどを準備すると良い 英語が不慣れな場合でも、1か月間英会話の学習を頑張れば、翻訳アプリを活用しながら通用するレベルになる ただし、ラボなどでの技術的な会話は難しいので、事前準備は必須 最後に 今回、ZOZOからは私一人が参加し、2年連続でのWWDC現地参加となりました。昨年にはなかった要素や新たな発見、学びが多く、とても充実した日々を過ごせました。 決して英語が得意とは言えない私でも、Appleスタッフ、日本のエンジニア、そして気さくに話しかけてくれた多国籍のデベロッパーたちのサポートのおかげで、WWDCを存分に楽しめました。Appleコミュニティの素晴らしさを改めて実感しました。 次回以降のWWDCで現地参加することになった方々にとって、この記事が少しでもお役に立てば幸いです。 「WWDC24 報告会 at LINEヤフー, ZOZO」を開催します 6月26日(水)に、WWDCに参加したLINEヤフーとZOZOのエンジニアが、それぞれが興味のある分野について、新しく発表された技術や得た知見、情報などを共有する「WWDC24 報告会」を開催します。昨年に続き私も登壇予定です。ご興味のある方はぜひご参加ください。 lycorptech-jp.connpass.com ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
こんにちは。検索基盤部の橘です。ZOZOTOWNでは、商品検索エンジンとしてElasticsearchを利用し、大規模なデータに対して高速な全文検索を実現しています。 Elasticsearchに関する取り組みは以下の記事をご覧ください。 techblog.zozo.com 検索基盤部では、ZOZOTOWNの検索結果の品質向上を目指し、新しい検索手法の導入を検討しています。本記事ではベクトル検索と呼ばれる検索手法に関して得た知見を紹介します。 ※本記事はElasticsearchバージョン8.9に関する内容となっています。 目次 目次 ベクトル検索とは ベクトル検索に期待すること Elasticsearchを使用したベクトル検索の導入 導入の簡略化 デプロイ可能な埋め込みモデル ベクトル検索のクエリ ハイブリッド検索とは Elasticsearchを用いたハイブリッド検索 RRF(Reciprocal rank fusion)を使うパターン rescoreを使うパターン ベクトル検索の定性評価 ベクトル検索の導入に伴う課題 類似度の閾値設定 モデルの埋め込み精度の問題 クエリごとの類似度のばらつき モデルの変化による類似度の分布の変化 まとめ おわりに ベクトル検索とは ベクトル検索とは、データを高次元のベクトル空間にマッピングし、類似性に基づいて情報を検索する技術です。 一般的なベクトル検索の方法は、ユーザーの検索クエリをベクトル化し、同じベクトル空間にマッピングされた商品データとの類似度を計算します。類似度が高い商品から順に検索結果としてユーザーに表示します。 下図において商品Aは商品Bより検索クエリとの角度が小さく、類似度が高いため、検索結果では商品Aの方が上位に表示されます。 検索クエリや商品データのベクトル化には埋め込み(Embedding)モデルを利用する方法が一般的です。 CLIP などのマルチモーダルモデルを使うことで文字列データに加え画像データも同じ空間にベクトル化できます。 ベクトル検索に期待すること ベクトル検索で特に期待できる点は、曖昧なクエリに対してより良い結果を出力できることです。 例えば、ZOZOTOWNでは次のような曖昧な検索クエリが見られます。 ベビー用品 きれいめワンピース フォーマルなスーツ 例えば「ベビー用品」という検索クエリの場合、「用品」という単語は広い意味を持ちます。全文検索では「用品」という文字が商品情報と完全に一致する必要があるため、検索結果が限定されます。 一方、ベクトル検索では「ベビー用品」という検索クエリで次のような商品を検索できます。 ベビー服 ベビー用おもちゃ ベビーカー その他ベクトル検索に期待できる点は以下です。 テキスト以外のデータでの検索:画像などのデータも検索に利用可能 表記揺れクエリに対して堅牢:検索クエリ「バック」で「バッグ」を検索可能 多言語への対応:検索クエリ「Tshirt」で「Tシャツ」を検索可能 Elasticsearchを使用したベクトル検索の導入 Elasticsearchは上述のベクトル検索をサポートしており、Elasticsearchでのベクトル検索導入には次のメリットがあります。 スケーラビリティ:Elasticsearchは分散アーキテクチャを採用しており、大規模なデータに対しても効率的にスケールアップできます。 高速な検索性能:ベクトル検索を含む大量のデータに対する複雑な検索を高速で処理できます。特にZOZOTOWNのように検索対象の商品数が多い場合の使用に適しています。Elasticsearch8.0以降では HSNW をベースとした近傍検索による高速な検索が可能です。 統合された検索環境:Elasticsearchは全文検索とベクトル検索の両方をサポートしています。全文検索とベクトル検索の組み合わせで検索精度の向上が期待できます。 導入の簡略化:埋め込みモデルをElasticsearchにデプロイし、ベクトル検索導入を簡略化できます。 導入の簡略化 ベクトル検索を実現するためには検索クエリと商品データを埋め込みモデルによりベクトル化する必要があります。これらのベクトル化処理のためのバッチやAPIの開発は、ベクトル検索導入の際のハードルとなります。 Elasticsearchでは、埋め込みモデルをElasticsearchにデプロイする方法が用意されており、これによりベクトル検索の導入を簡略化できます。 詳しい導入手順は以下の記事をご覧ください。 Elasticsearchで日本語NLPモデルを利用してセマンティック検索を実現する | Elastic Blog 簡単な手順としては次の通りです。 Elasticが提供するPythonのElasticsearchクライアントライブラリ eland を使用して、 Hugging Face 上のモデルをアップロードしデプロイ インジェストパイプライン を作成し、 推論プロセッサー でフィールドのデータをベクトル化しインデックスを作成 knnクエリ(後述)の query_vector_builder オプションを使うことで検索クエリをベクトルに変換し、類似度の高い順の検索結果のレスポンスを取得 このように、埋め込みモデルをElasticsearchにデプロイすることで、ベクトル化処理のためのバッチやAPIの開発を省略してベクトル検索を導入できます。 デプロイ可能な埋め込みモデル ベクトル検索に使うモデルはHugging Faceに登録してあるモデルの中から Feature Extraction のタスクに対応しているモデルを選ぶ必要があります。例えば、 multilingual-e5-large が該当します。 Hugging Face上にある事前学習済みモデルをファインチューニングしたモデルも使用できます。その場合、ファインチューニングしたモデルをHugging Face上のリポジトリにアップロードし、elandを使ってElasticsearchにアップロードします。 ベクトル検索のクエリ knnクエリ を使うことにより、Elasticsearchでベクトル検索できます。 クエリ例は以下の通りです。 { " knn ": { " field ": " text_embedding.predicted_value ", " query_vector_builder ": { " text_embedding ": { " model_id ": " cl-tohoku__bert-base-japanese-v2 ", " model_text ": " shoes " } } , " k ": 10 , " num_candidates ": 100 , " similarity ": 0.8 , " boost ": 1 , " filter ": { フィルタリング条件を記載 } } } knnクエリのパラメータは次の通りです。 パラメータ名 説明 field 検索対象のフィールド query_vector_builder ベクトル化に使うモデルidとベクトル化するテキスト k 結果出力数 num_candidates 各シャードから抽出する候補数 similarity 類似度の下限閾値 boost 類似度(スコア)に掛ける重み filter フィルタリング条件 ハイブリッド検索とは 上記でベクトル検索の利点を紹介しましたが、全文検索にもいくつか利点があります。 実装や導入が比較的容易 大規模な情報に対して高速な検索が可能 検索クエリとドキュメントのマッチ方法が明確で、結果の解釈が容易 全文検索とベクトル検索はそれぞれ利点が異なります。両方を組み合わせることで、それぞれの利点をうまく活かし、より高い検索精度を期待できます。全文検索とベクトル検索を組み合わせる検索手法は「ハイブリッド検索」と呼ばれます。 Elasticsearchにはハイブリッド検索を実現する機能が用意されています。 Elasticsearchを用いたハイブリッド検索 ここではElasticsearchを使ったハイプリッド検索のパターンをいくつか紹介します。 RRF(Reciprocal rank fusion)を使うパターン RRF は異なる検索結果のランキングを統合し、ランキングを生成するアルゴリズムです。 RRFは以下の数式で表されます。 パラメータ名 説明 統合するランキングの数 ドキュメントのランキング順位 調整定数 ランキング対象となるドキュメントの集合 RRFを利用し、全文検索のランキングとベクトル検索のランキングを統合するクエリ例は以下の通りです。ベクトル検索と全文検索のランキングスコアのスケールは異なりますが、RRFを使うことでスコアのスケールを合わせる必要なくランキングを統合できます。 { " size ": 10 , " query ": { " term ": { " text ": " shoes " } } , " knn ": { " field ": " text_embedding.predicted_value ", " k ": 10 , " num_candidates ": 100 , " query_vector_builder ": { " text_embedding ": { " model_id ": " cl-tohoku__bert-base-japanese-v2 ", " model_text ": " shoes " } } } , " rank ": { " rrf ": { " window_size ": 10 , " rank_constant ": 1 } } } 上記のクエリでは、まずshoesという用語に一致するドキュメントを全文検索で取得し、次に同じshoesを基にベクトル検索で最も近いドキュメント上位10件を取得します。その後、全文検索とベクトル検索の結果を統合し、RRFを適用して最終的なランキングを生成しています。 RRFクエリのパラメータは次の通りです。 パラメータ名 説明 window_size 各ランキングにおいてRRFアルゴリズムを適用する上位N件 rank_constant 各ランキング内のドキュメントが最終的なランキングにどれだけ影響を与えるかを決定する調整定数(値が高いほど、低ランクのドキュメントがより大きな影響を与える) rescoreを使うパターン rescore クエリによって全文検索とベクトル検索の結果をリランキングし統合できます。 rescoreを利用し、全文検索のランキングとベクトル検索のランキングを統合するクエリ例は以下の通りです。 { " size ": 10 , " query ": { " term ": { " text ": " shoes " } } , " knn ": { " field ": " text_embedding.predicted_value ", " k ": 10 , " num_candidates ": 100 , " query_vector_builder ": { " text_embedding ": { " model_id ": " cl-tohoku__bert-base-japanese-v2 ", " model_text ": " shoes " } , " boost ": 0.0001 } } , " rescore ": [ { " window_size ": 10 , " query ": { " rescore_query ": { リスコアのロジックを記載 } , " query_weight ": 0 , " rescore_query_weight ": 1.0 , " score_mode ": " total " } } ] } 上記のクエリでは、全文検索の結果とベクトル検索の結果を組み合わせてリスコアを行い、最終的に上位10件の結果を取得しています。また、knnクエリのboostの値を低く設定することで、全文検索の結果が優先してリスコアされるようにしています。 クエリ内でRRFとrescoreクエリは併用できないので注意が必要です。 ベクトル検索の定性評価 社内で全文検索の結果とベクトル検索の結果をオフライン定性評価しました。定性評価では、被験者がいくつかの評価用クエリに対する全文検索の結果とベクトル検索の結果を見比べて、どちらの検索結果が良いかを評価します。 ZOZOTOWNで実施している定性評価の詳細は以下の記事をご覧ください。 techblog.zozo.com 以下は、オフライン定性評価でベクトル検索が良い結果を出したクエリと悪い結果を出したクエリの例です。 ベクトル検索が良い結果となったクエリ ベクトル検索が悪い結果となったクエリ ペット用品 きれいめドレス 小さめリュック スポーツウェアー いちご柄 卒業式 女の子 小学生 フォーマル ブランド名全般 キャラクター名全般 これらのクエリと検索結果を考察し、ベクトル検索が適用可能だった語と適用困難だった語は次の通りでした。 ベクトル検索が適用可能だった語 説明 曖昧な語 「用品」「きれいめ」など曖昧なクエリに対しては近い意味の商品を検索可能 多言語 「小さめ」のクエリに対して「small」という語を含んだ商品を検索可能 表記揺れ 「ウェアー」のクエリに対して「ウエア」「ウェア」という語を含んだ商品を検索可能 ベクトル検索が適用困難だった語 説明 明確な語 「いちご柄」のクエリに対してドット柄のような商品を誤って検索 複雑な語 「卒業式 女の子 小学生 フォーマル」のような様々な意味を含む複雑なクエリに対して「ネクタイ」を誤って検索 ドメイン性が強い語 ブランド名やキャラクター名などドメイン性が強いクエリに対して似たブランドやキャラクターの商品を検索することは不可能 このようにベクトル検索が適用可能な語と適用困難な語を評価することで、どのような検索クエリに対してベクトル検索が効果的かを理解できました。 ベクトル検索の導入に伴う課題 ベクトル検索の導入を検討するにあたり、いくつかの課題がありました。代表的な内容をいくつか紹介します。 類似度の閾値設定 類似度の低い商品はクエリとは無関係である可能性があります。クエリとは無関係な商品が含まれることを防ぐために、適切な類似度の閾値を設定する必要があります。 この閾値の設定方法はいくつか考えられますが、1つの方法としてアノテーションによる閾値設定の方法が考えられます。 以下はアノテーションの例で、アノテーション結果にはクエリと商品の関連性がある場合に正解、関連性がない場合に不正解を付与します。 クエリ 商品 類似度 アノテーション結果 きれいめワンピース 商品A 0.94 正解 きれいめワンピース 商品B 0.85 正解 きれいめワンピース 商品C 0.65 不正解 この結果を基に、適切な類似度を設定することが可能になります。 モデルの埋め込み精度の問題 以下の表はある事前学習済みモデルを用いて、検索クエリ「ダウン」と商品名のcosine類似度を算出した結果です。 商品名 類似度 ダウンコート 0.67 下駄 0.64 ダウン ティペット 0.61 「ダウン」と検索された際に、「ダウンコート」と「ダウン ティペット」が検索結果に表示されるのは適当ですが、「下駄」が表示されることは望ましくありません。この場合、類似度の閾値を0.65に設定し「ダウンコート」のみを検索結果に表示するか、閾値を下げて「下駄」を含めすべて検索結果に表示するかの判断が必要です。 モデルの埋め込みに問題がある場合、検索結果に意図しない商品が含まれてしまいます。この問題を解決するには、モデルのファインチューニングなどにより出力ベクトルを最適化する必要があります。 クエリごとの類似度のばらつき 以下の図は、いくつかのクエリに対してcosine類似度が高いZOZOTOWNの商品TOP100を示しています。 クエリごとにcosine類似度の分布にばらつきが見られます。例えば「スウェット」のクエリに対してcosine類似度の高いTop100の商品は全て類似度が0.91以上であるのに対し、「きれいめ」のクエリに対してはTop100商品すべて0.90以下になっています。 この場合、類似度の閾値の設定が難しいです。例えばcosine類似度の閾値を0.88にした場合、「きれいめ」というクエリはベクトル検索で殆ど対応できないことになります。cosine類似度の閾値をより低く設定すると、無関係な商品が検索結果に含まれる可能性が高くなります。 このように、クエリと商品によっては類似度にばらつきが発生し、類似度の閾値を一意に決めることが難しくなることに注意が必要です。この閾値を動的に変える場合は、サービス側でクエリに応じて変更する必要があります。 モデルの変化による類似度の分布の変化 利用しているモデルのパラメータを変更すると、検索クエリと商品の類似度の分布が変わります。 次の図は、 e5-small モデルと universal-sentence-encoder モデルでcosine類似度が高いZOZOTOWN商品TOP100をプロットした図です。 モデルによってcosine類似度の分布が異なります。モデルの変更に応じて類似度の閾値のパラメータを調整する必要があります。 よって、モデルの変更に応じて類似度の閾値のパラメータを調整できるようにしておく必要があります。 まとめ 本記事ではベクトル検索の導入において得た知見をご紹介しました。ベクトル検索の導入に関しては本記事で取り上げた課題があり、現状導入には至っていませんが、引き続き検討を重ねていく予定です。 おわりに ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター