TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

937

.table-of-contents > li > ul, .table-of-contents > li:nth-child(2) { display: none; } はじめに こんにちは、ZOZOTOWN開発本部リプレイスバックエンドブロックのばやです。普段はZOZOTOWN BFFのリプレイス開発を担当しています。 システムリプレイスのプロジェクトでは、実装に入る前段階として 既存コードの調査 が必ず発生します。特にレガシーシステムの場合、ドキュメントが整備されていなかったり、仕様が暗黙知として埋もれていたりすることが多く、コードを読み解くことでしか仕様を把握できないケースも少なくありません。 一方で、この調査フェーズは成果物の形式や進め方が属人化しやすく、プロジェクト全体の生産性に大きな影響を与えるポイントでもあります。調査に時間がかかればプロジェクト全体のスケジュールに影響しますし、調査品質が低ければ後工程での手戻りにつながります。 本記事では、リプレイスにおける既存コード調査の課題に対し、調査業務をテンプレート化しその後、 Claude CodeでAgent Skillsに落とし込むことで効率化した取り組み をご紹介します。 目次 はじめに 目次 背景 リプレイスにおける既存コード調査の重要性 調査フェーズが抱えていた課題 人による改善:既存コード調査業務の標準化 調査対象コードのコピーによるスコープ明確化 調査テンプレートの設計 テンプレート化だけでは解決できなかった課題 経験者と未経験者の工数差 調査結果の粒度とレビュー工数のバランス AIによる改善 Agent Skillsとは なぜAgent Skillsを選んだのか 「Slash Command + Skills」を採用した理由 常時コンテキストを圧迫することを防ぐ Anthropic公式のSkills作成のSkillsが存在した 専門知識を一般化された状態で格納できる Agent Skillsの実装 ディレクトリ構成 実行フロー この構成にした理由 1. LSPがないレガシー言語への対応 2. コンテキストが膨大な処理の並列化 3. 中間ドキュメントによる精度向上 各Skillの役割 成果物 運用フロー 導入効果 定量的な効果 定性的な効果 見えてきた課題 コンテキストの欠落 ハルシネーションの見落とし 今後の展望 検証プロセスの強化 より人間がわかりやすいドキュメントを再作成する 新規実装やテストコードと旧コードのマッピング Agent Skillsの継続的な改善 他チームへの展開 まとめ 背景 リプレイスにおける既存コード調査の重要性 リプレイスでは、既存システムの仕様や振る舞いを正しく理解することが不可欠です。 特に以下のような情報は、後続の設計・実装の品質を大きく左右します。 実際に使われている機能・使われていない機能 暗黙的な仕様や例外的な処理 データ構造や依存関係 パフォーマンスや運用上の注意点 そのため、既存コード調査は「とりあえず読む」ではなく、 一定の品質を担保した成果物として残す必要 があります。 成果物として残すことで、レビューしプロジェクト全体で品質を担保できます。 調査フェーズが抱えていた課題 一方で、従来の調査フェーズには次のような課題がありました。 調査結果の形式がメンバーごとに異なる どこまで調査すれば十分なのか判断基準が曖昧 調査内容の抜け漏れがレビューまで発覚しない キャッチアップコストが高く、経験者と未経験者で工数差が大きい タスクの見積もりが人によって大きくブレる 人による改善:既存コード調査業務の標準化 これらの課題を解決するために、まず着手したのは 既存コード調査業務の標準化 です。具体的には、以下の2つを行いました。 調査対象コードのコピーによるスコープ明確化 調査対象のコードを、元のリポジトリから現開発環境のリポジトリにコピーします。 単純ではありますが、確認するコードを限定することで作業者とレビュアの双方にとって範囲が明確になり、作業効率が向上しました。 当時はAgent Skillsの導入を意識していたわけではありませんでしたが、結果的にこの運用がAgent Skillsへの移行をスムーズにしました。対象コードが分析用ディレクトリにまとまっていたことで、Agent Skillsが旧コードベースを読み込んで解析できる状態が既に整っていました。 調査テンプレートの設計 私たちのチームでは、BFFを開発する上で最低限必要となる情報の調査に特化したテンプレートを使用しています。 ## API名 ## 概要シーケンス ## リクエスト例 ## リクエストパラメータおよび変換処理 ## コード内での呼び出しAPI / 呼び出しテーブル ## エラーレスポンス ## レスポンス例 ## レスポンスパラメータおよび変換処理 テンプレートにより「何を調べるべきか」「何を書けばよいか」が明確になり、調査の抜け漏れを防げるようになりました。また、成果物の形式が統一されたことで、レビューもしやすくなりました。 テンプレート化だけでは解決できなかった課題 テンプレート化により「何を書けばよいか」は明確になりましたが、以下の課題は依然として残っていました。 経験者と未経験者の工数差 レガシーコードに慣れている経験者は効率的に調査を進められますが、未経験者は同じテンプレートを埋めるのにも時間がかかってしまいます。テンプレートは「何を書くか」を示してくれますが、「どうやって調べるか」までは教えてくれません。 調査結果の粒度とレビュー工数のバランス レビュアの既存コード調査レビューはそれなりに工数を伴います。リプレイスする機能は多岐に渡り、1つの機能がどのような機能かを詳細に把握することは大変です。 一方で一機能のリリースにおける、最序盤のタスクである既存調査に対してプロジェクトとしてはあまり工数をかけたくなく、既存調査の情報粒度は2〜3日程度で完成できる程度としていました。 必然的にレビュアの負担をかけることで、品質を担保するようにしていました。 これにより、レビューが重なったタイミングなどはレビュー遅延による待ちなどが発生して、プロジェクト全体の遅延が懸念されました。 これらの課題を解決するために、次にAgent Skillsの導入を検討しました。 AIによる改善 Agent Skillsとは Agent Skills は、Claudeの機能を拡張するモジュール型の機能です。指示、メタデータ、およびオプションのリソース(スクリプト、テンプレートなど)をパッケージ化し、Claudeが関連するタスクで自動的に使用できるようにします。 Agent Skillsを使う主な利点は以下の3点です。 専門知識を注入し、暗黙知を削減 : 既存知識をドキュメントに移譲することで、暗黙知となっていた業務知識を公開ドキュメントとできる 繰り返しを削減する : 一度作成すれば、複数の会話で同じガイダンスを繰り返す必要がなくなる 機能を組み合わせる : 複数のSkillsを組み合わせて複雑なワークフローを構築できる Agent Skillsは SKILL.md ファイルとして定義します。YAMLフロントマターでメタデータを記述し、本文に具体的な指示を記載します。Claude Codeでは、このファイルを .claude/skills/ ディレクトリに配置することで、Claudeが自動的に検出して使用します。 なぜAgent Skillsを選んだのか Claude Codeには、カスタマイズのための複数の仕組みがあります。今回の用途に適した方式を選ぶため、それぞれの特徴を比較しました。 方式 特徴 用途 CLAUDE.md 起動時に常に読み込まれる 常に必要な情報 Skills 必要に応じて読み込まれる 特定の作業タイミングでのみ必要な指示 Slash Commands ユーザーが明示的に呼び出す 定期的に実行したいタスクのショートカット Subagents 独立したタスクを別エージェントに委譲 調査過程が膨大になる場合の結果のみ取得 現状Agentの呼び出し方法には複数の選択肢があり、チームでは Slash Commands + Skills の組み合わせ を採用しました。 エントリーポイント : Slash Command 業務知識の保存 : Skills(詳細な調査手順とテンプレート) Slash Commandをエントリーポイントとしてのみの薄い層とし、実際の調査テンプレートや調査方法はSkillsに格納しました 1 。 「Slash Command + Skills」を採用した理由 常時コンテキストを圧迫することを防ぐ CLAUDE.mdへの記載は、常時コンテキストを圧迫することになるので避けました。 Anthropic公式のSkills作成のSkillsが存在した 我々のチームでは、CLAUDE.mdなどを読み込ませる際に、どのような文章構成がよりAgentの性能を上げてくれるのかが手探りの状態でした。 導入当時、Skillsは公式が作り方をサポートしてくれるSkillsを作成していました 2 。こちらに沿って作成すればスムーズにSkillsを作成でき、最適な形であることが保証できました。 Anthropic公式の マーケットプレイス も参照ください。 専門知識を一般化された状態で格納できる 作成した既存調査のSkillsは更新・改善を見込んでいます。このように階層に分けたドキュメント配置が公式から指定されていることで、我流のドキュメント配置を抑制できます。一般化された階層を誰もが実現でき、可読性が高い状態を維持できます。 なお、当時はSkillsを明示的に呼び出すことができなかったため、呼び出し部分だけSlash Commandで作成しました。結果的にSkills側に専門知識を寄せたことにより、現状のSlash CommandとSkillsの統合への変更が最小限で済みました。 Slash Command内で並列化することにより、いくつかのSkillsはSubagent的な役割を実現させています。これは個人的な意見にはなりますが、SubagentもいずれSkillsに統合され、Skillsを同期的に呼ぶか非同期で呼ぶかという観点のみが残るような気がしています。 Agent Skillsの実装 調査テンプレートをAgent Skillsとして定義しました。複数のSkillsを組み合わせ、以下の流れで調査を自動化しています。 ディレクトリ構成 {plugin-name}/ ├── commands/ # エントリーポイント │ └── analyze.md # 分析実行コマンド ├── skills/ # Skills本体(詳細な手順) │ ├── find-dependencies/ # 依存関係調査 │ ├── analyze-code/ # コード詳細分析 │ ├── extract-files/ # ファイル抽出 │ └── generate-readme/ # ドキュメント生成 │ └── templates/ # 出力テンプレート └── agents/ # Subagents定義(並列実行用) 実行フロー 1. /analyze を実行(Slash Command) ↓ 2. find-dependencies(依存関係調査) ↓ 3. 並列実行: ├─ analyze-code(コード詳細分析) └─ extract-files(ファイル抽出) ↓ 4. generate-readme(ドキュメント生成) ↓ 5. 成果物出力 この構成にした理由 1. LSPがないレガシー言語への対応 旧コードにはLSP(Language Server Protocol)が存在しないため、「定義へジャンプ」や「参照検索」といった機能が使えません。関数の検索方法や依存関係の追跡方法をドメイン知識としてSkillsに注入する必要がありました。 find-dependencies を最初に実行することで、後続の処理に必要な情報を確実に収集します。 2. コンテキストが膨大な処理の並列化 従来の手作業では、既存コードのコピーとテンプレートへの情報抽出を順番に行っていました。どちらもコードベース全体を読み込む必要があり、コンテキストが膨大になります。これらを analyze-code と extract-files として分離し、並列実行することで処理時間を短縮しています。 3. 中間ドキュメントによる精度向上 依存関係の調査結果を analyze-code で中間ドキュメント(markdown)として出力し、後続のSkillsが参照する構成にしています。一度にすべてを処理するのではなく、段階を踏むことでAIの出力精度が向上します。 各Skillの役割 Skill 役割 find-dependencies 関数検索、依存関数の追跡、グローバル変数の特定 analyze-code 入力パラメータ、出力データ、データアクセス、変換処理を抽出。旧フローのテンプレート化に相当 extract-files メイン関数、依存関数を個別ファイルに分離。旧フローのコードコピーに相当 generate-readme テンプレートに準拠した調査結果ドキュメントを生成。旧フローのテンプレート化に相当 成果物 実行後、以下の成果物が出力されます。 output/{対象関数名}/ ├── README.md # 調査結果(テンプレート準拠) ├── 01.{メイン関数}.ext # メイン関数 ├── 02.{依存関数1}.ext # 依存関数 └── ... 調査結果のREADME.mdは、先述のテンプレートに沿った形式で出力されます。依存関数も個別ファイルとして抽出されるため、レビュー時にコード全体を把握しやすくなります。 運用フロー この仕組みにより、調査担当者は以下の流れで作業できるようになりました。 1. 対象リポジトリやディレクトリを指定 2. Agent Skillsを実行 3. 出力された調査結果を確認・修正 4. レビューを実施 5. 必要に応じて追加調査 従来は「コードを読む → 理解する → まとめる」という一連の作業をすべて人間が行っていました。Agent Skillsを活用することで「AIが下書きを作成 → 人間が確認・修正」という流れに変わりました。 導入効果 定量的な効果 Agent Skillsの導入により、調査作業のリードタイムは大きく改善しました。 項目 従来 導入後 調査の実行時間 数時間〜数日 数分程度 全体のリードタイム 2〜5日 数時間 成果物の形式 バラバラ 統一 調査結果の確認・修正・レビューを含めても 数時間で完結 するようになりました。従来の「数日かかる調査タスク」と比較すると、大幅な短縮です。 また、レビュー体験も改善しました。成果物の形式が統一されているため、レビュアは「何がどこに書いてあるか」を把握しやすくなり、内容の精査へ集中できるようになりました。 特にリードタイムの短縮は顕著で、これまでボトルネックになっていた調査フェーズがスムーズに進むようになりました。 定性的な効果 定量的な効果に加えて、以下のような定性的な効果も得られました。 メンバー間で調査観点が揃い、レビューがしやすくなった 情報粒度が上がったので、細かい内容まで記載があって、レビューがしやすくなった 新しく参加したメンバーでも、同じ品質で調査できるようになった 特に、新規メンバーのキャッチアップコストが下がったことは大きな効果でした。従来は既存システムの知識がないと調査に時間がかかっていましたが、Agent Skillsを使うことで、知識の有無に関わらず一定品質の調査が可能になりました。 見えてきた課題 Agent Skillsの導入により多くの効果が得られた一方で、新たな課題も見えてきました。 コンテキストの欠落 従来は時間をかけて調査することで、各メンバーが暗黙的な知識を蓄積していました。「なぜこのような実装になっているのか」「過去にどのような経緯があったのか」といった背景知識は、コードを読み込む過程で自然と身についていました。 しかし、AIによる高速な調査では「調べた結果」は得られるものの、「理解」を伴わないケースもあります。具体的には以下のような問題に直面しました。 実装フェーズで想定外の考慮漏れが発生する 全体像を把握していないことによる手戻り 調査結果に書いてあることしか把握していない状態 この課題に対しては、調査結果を「読むだけ」で終わらせず、チーム内で共有・議論する時間を設けることが重要だと感じています。 AIが作成した調査結果をベースに、チームで同期的な理解の時間を設けることで後のフェーズである、設計・実装の部分でスムーズな進行ができると感じました。 ハルシネーションの見落とし AIが大量の情報を要約して出力するため、全項目を精査することが難しくなります。結果として、誤った情報がそのままレビューを通過してしまうリスクがあります。 これはLLMを使っている以上避けては通れないリスクではありますが、現状、根本的な解決策は見つかっていません。 レビュイー・レビュア双方による注意深い確認が重要だとされがちです。しかし、それでは業務負荷が作成からレビューに移行しただけで、AI駆動開発の恩恵を受けられません。 今後の展望 見えてきた課題を踏まえ、今後は以下の取り組みを進めていく予定です。 検証プロセスの強化 AIの出力をより効率的に検証するため、現在以下の取り組みを行っています。 より人間がわかりやすいドキュメントを再作成する 既存調査 → 新規設計 → 実装という形でフェーズが進行しており、レビュアが見やすい粒度・構成の設計書を模索しながら作成しています。 新規実装やテストコードと旧コードのマッピング レビュイー本人も漏れを防ぐため、リプレイス後のコードに旧コードのGitHub上での該当箇所URLをコメントすることで擬似チェックリストのようなものを再現しています。愚直ではありますが、これによって実装者が漏れを把握して未然にバグを防げたので、有効性は確認できています。 今後は以下の仕組みも検討しています。 調査結果と実際のコードを自動で照合するスクリプトの作成 重要度の高い項目(データ構造、エラーハンドリングなど)のチェックリスト化 実装フェーズでの差分検出と調査結果へのフィードバックループ Agent Skillsの継続的な改善 プロンプトの精度向上や、新たな観点の追加を継続的に行います。 調査対象のコードパターンに応じたプロンプトの分岐 よくある誤りパターンの収集と、それを防ぐための指示の追加 チームからのフィードバックに基づくテンプレートの更新 他チームへの展開 現在は特定のチームで運用していますが、他のリプレイスプロジェクトへの展開も視野に入れています。 Agent Skillsの汎用化と、プロジェクト固有の設定の分離 導入ガイドやベストプラクティスの整備 成功事例・失敗事例のナレッジ蓄積 まとめ 本記事では、リプレイスにおける既存コード調査の課題に対し、以下のアプローチで業務を効率化した事例を紹介しました。 調査業務をテンプレート化し、「何を調べるか」「何を書くか」を明確にする その手順をAgent Skillsに落とし込み、AIによる調査の自動化を実現する 結果として、調査タスクのリードタイムを大幅に短縮しつつ、品質の安定化も実現できました。 一方で、「コンテキストの欠落」や「ハルシネーションの見落とし」といった新たな課題も見えてきました。これらは、AIを活用した業務効率化において避けて通れない課題であり、運用の工夫や検証プロセスの改善によって対処していく必要があります。 リプレイスでは「調査」は避けて通れない工程だからこそ、人に依存しない形で品質とスピードを両立させることが重要だと考えています。AIの活用はその有力な手段の1つですが、AIの出力を鵜呑みにせず、人間による検証とチームでの知識共有を組み合わせることが成功の鍵です。 既存コード調査やリプレイスに課題を感じている方の参考になれば幸いです。 ZOZOTOWN開発本部リプレイスバックエンドブロックでは、大規模なシステムリプレイスを推進するエンジニアを募集しています。AIを活用した開発プロセスの改善や、レガシーシステムのリプレイスに興味のある方、ぜひご応募ください。 corp.zozo.com 2026/01/21現在、SkillsとSlash Commandは統合され、Skillsを明示的に呼び出せるようになりました。こちらの分割は現在不要となりましたが、実行役割を持つSkillsと知識をもつSkillsの分割はパターンとして未だに有効だと考えています。 ↩ Anthropicの skill-creator というSkillsを参考にしてください。 ↩
アバター
こんにちは、技術戦略部のikkouです。2025年12月16日に「 AI Engineering Summit Tokyo 2025 」が開催されました。ZOZOはGoldスポンサーとして協賛し、スポンサーブースを出展しました。 AI Engineering Summit Tokyo 2025 会場エントランスのスポンサーパネル technote.zozo.com 本記事ではZOZOから登壇したセッションの紹介と、ZOZOの協賛ブースの様子をまとめてお伝えします! セッションレポート ファッション×AI:「似合う」を届けるためのWEARのAI戦略 登壇中のブランドソリューション開発本部 本部長の増田 ブランドソリューション開発本部 本部長の増田が登壇し、ZOZOが「似合う」というテーマに取り組む背景から、それをWEARでどのように実現しているのかまでを紹介しました。 WEARにおけるAI活用の考え方や戦略について、具体的な事例を交えながら語られたセッションとなりました。登壇資料はSpeaker Deckで公開されています。 speakerdeck.com 見逃した方は、Findy Conferenceのアーカイブ動画もぜひご覧ください。 conference.findy-code.io セッション後のAsk the Speakerもとても盛り上がっていました。会場にお越しいただいた方、オンラインでご視聴いただいた方、ありがとうございました。 エンジニア組織として全社のAI活用をどうリードしていくのか パネルディスカッションに登壇中の執行役員 兼 CTOの瀬尾 続いて、執行役員 兼 CTOの瀬尾( @sonots )が「AI時代の開発組織の未来像 ― 5年後の姿をどう考えるか?」をテーマとしたパネルディスカッションに登壇しました。 本セッションは会場限定での実施となりましたが、アーカイブ動画が公開されています。このトピックに関心のある方は、ぜひご視聴ください。 conference.findy-code.io ZOZOブースレポート ZOZOの協賛ブースでは、当日登壇したセッション資料を含むAI関連の取り組み資料をスライドショー形式で紹介しました。 特に、瀬尾がパネルディスカッション内で言及していた「ZOZOグループで利用可能な代表的なAIツール」に関心を持たれる方が多く、ブースでも活発な交流が行われていました。 ZOZOグループで利用可能な代表的AIツール(2025年12月現在) ブース内では、「似合う」に関する取り組みを紹介するパネル展示も行いました。 ZOZOの独自性を生み出す「似合う4大要素」の開発サイクル 『ZOZOの独自性を生み出す「似合う4大要素」の開発サイクル』を解説する冨田 speakerdeck.com データサイエンス部 コーディネートサイエンスブロックの冨田が、ZOZOの独自性を支える「似合う4大要素」の開発サイクルについて解説しました。進行中のプロジェクトのため公開できる内容には限りがありましたが、プロジェクトに深く関わっている立場から、一人ひとりに丁寧な説明が行われました。 ファッションコーディネートアプリ「WEAR」におけるコーディネートレコメンドの動作インフラとその周辺技術 『ファッションコーディネートアプリ「WEAR」におけるコーディネートレコメンドの動作インフラとその周辺技術』を説明する岡本 ファッションコーディネートアプリ「WEAR」におけるコーディネートレコメンドの動作インフラとその周辺技術 もうひとつの展示では、ファッションコーディネートアプリ「WEAR」におけるコーディネートレコメンドの仕組みと、その周辺技術を紹介しました。データシステム部 MLOpsブロックの岡本が、関連するコーディネート情報を活用した推薦システムのアーキテクチャを、MLOpsの観点から解説しました。 前月に協賛してブースを出展したアーキテクチャConference 2025 でも感じたことですが、アーキテクチャやシステム構成に関心を持つ方は多く、こちらの展示も大きな盛り上がりを見せていました。 おわりに ZOZOから参加したメンバーの一部で記念撮影! 今回の協賛・出展を通して、改めてAIの活用や開発に対する関心の高まりを強く感じました。ZOZOのAIに対する考え方や取り組みが、少しでも来場者の皆さまに伝わっていれば嬉しいです。 ZOZOでは、今回ご紹介したWEARのエンジニアをはじめ、さまざまな分野でエンジニアを募集しています。ご興味のある方は、ぜひ採用ページをご覧ください。 corp.zozo.com
アバター
.table-of-contents > li:nth-child(2) { display: none; } はじめに こんにちは、2025年にiOSエンジニアとして新卒入社したZOZOTOWN開発1部iOSブロックの だーはま です。普段はZOZOTOWNのiOSアプリを開発しています。本記事では、新卒1年目の私がZOZOTOWNの画面へMVVM+UseCaseアーキテクチャを導入した過程と、工夫を紹介していきます。 目次 はじめに 目次 背景と目的 チーム配属から1か月でMVVM+UseCaseアーキテクチャ導入を担当した経緯 デバッグ画面について 課題 コードやドメインに対する知識不足 700行以上に及ぶFatViewController 課題を解決するための取り組み Claude CodeのSubagentsを使いキャッチアップを高速化 MVVM+UseCaseアーキテクチャ導入 結果 デバッグ画面のテストカバレッジ0→93.5% UI実装をStoryboardからSwiftUIへ完全移行 開発工数40%減少 MVVM+UseCaseアーキテクチャ導入を振り返って 最後に 背景と目的 チーム配属から1か月でMVVM+UseCaseアーキテクチャ導入を担当した経緯 ZOZOTOWN iOSでは、MVVMとUseCaseで構成されるチーム標準のアーキテクチャがあります(以後アーキテクチャという表記はチーム標準のアーキテクチャを指します)。現在、アプリ全体の保守性と開発効率を向上させるため、 チーム全体でこのアーキテクチャへの統一を進めるプロジェクトが進行しています 。 私自身、学生時代から設計への関心は高く、個人開発で同様の構成を取り入れた経験はありました。しかし、ZOZOTOWNのような歴史ある大規模なアプリの画面で、UIからビジネスロジックまでを含めて構成を見直した経験はありません。また、当時は入社して間もなく、経験したことのあるタスクはUIの修正やログ送信といった特定箇所の改修のみで、 ZOZOTOWNの大規模なコードの全体像も把握できていない状態 でした。 このような状況ではありましたが、iOSチームには以下のように アーキテクチャ導入を支える手厚いフォロー体制 1 が整っていました。 アーキテクチャの指針書が整備されている 各レイヤーの責務が言語化されている レイヤーごとにコードを使った実装例がまとめられている 設計段階でのレビューが義務化されており、実装後の手戻りを抑えられる これらの環境に背中を押され、アーキテクチャ導入がチームへの貢献と自己成長へ繋がると考えました。そのため、チームへ配属されて1か月目というタイミングではありましたが、 自ら手を挙げタスクオーナーとしてアーキテクチャ導入を進める ことになりました。 取り組む目的は以下の通りです。 技術的負債を抱えた画面のメンテナンスコストを下げること 疎結合でテスタブルなコードにする ZOZOTOWN iOSが採用しているアーキテクチャに慣れること ZOZOTOWNのiOSチームが採用しているアーキテクチャへの理解を深め、他画面のコードリーディングを高速化する テストコードの実装にも慣れるため、可能な限りテストを実装する 大規模なコードを高速にキャッチアップするための練習をする AIと協働して大規模なコードを高速にキャッチアップする方法を模索する 上記の目的を満たす画面をチーム内で議論し、社内向けでありユーザー影響のないデバッグ画面を選びました。 デバッグ画面について デバッグ画面は、ZOZOTOWNのデバッグ用にデータを編集する画面です。開発している案件に応じてサーバーの向き先を切り替えるなど、開発する上で便利な機能が複数用意されています。デバッグ画面を使うことでQAや案件ごとに実施される動作確認をスムーズに行えます。このデバッグ画面はiOSエンジニアだけでなく、QAチームやバックエンドを始めとした社内の様々なチームおよびメンバーが触れるため、 開発する上でとても重要な画面 です。 しかし、新規案件の実装を優先しておこなっていく中で、社内向けでありプロダクトコードではないことから保守に手をつけることができずにいました。その結果、UIからビジネスロジックまでの実装が1つのViewControllerに記述され、機能を追加するたびにメンテナンスコストが増大していました。 課題 アーキテクチャ導入にあたり解決すべき課題は以下の2つです。 コードやドメインに対する知識不足 700行以上に及ぶFatViewController コードやドメインに対する知識不足 先述したようにデバッグ画面は依存関係が複雑です。 また、導入にあたり以下のような課題がありました。どの課題にも共通して「キャッチアップするべき項目が多く、十分にできていなかった(本人は十分にできているつもりだった)」ということが挙げられます。 触れたことのないアーキテクチャの理解 学生時代に個人開発で触れていたアーキテクチャ(MVVM,TCAなど)とは思想やルール、実装する上での責任が異なるため、チームが採用しているアーキテクチャを理解して慣れるまで時間がかかる 既存実装の仕様を確認する難しさ プロパティの初期値やデータソースへのアクセス、データを読み書きするタイミングなど、既存実装には多くの仕様が存在しており、それらすべてを確認して実装を進めるのが困難 テストコード実装時に発覚した「隠れた密結合」 設計のレビューを通過し各レイヤーの責務を定義したはずが、いざ実装を進めるとデータソースの抽象化ができておらずユニットテストを書けない状態で手戻りが発生する 700行以上に及ぶFatViewController 既存の実装では1つのViewController内に画面の状態からデータソースへのアクセスまで全てのコードが記述されていました。責務分離がなされていないためクラスをDIできず、ユニットテストの記述が困難な状況です。また、StoryboardでUIを開発していたためメンテナンスコストが高くなっていました。実際にメンバーからは「デバッグ用のフラグを1つ追加したいだけなのに、Storyboardの修正や依存関係のあるコードの把握に時間がかかってしまう」という声が上がっていました。本来は開発を効率化するためのツールであるはずのデバッグ画面が、機能追加のたびに負債が溜まっていく画面になっていたのです。 課題を解決するための取り組み 前述した課題を解消するため2点取り組みました。 Claude CodeのSubagentsを使いキャッチアップを高速化 MVVM+UseCaseアーキテクチャ導入 Claude CodeのSubagentsを使いキャッチアップを高速化 1つ目の課題で挙げたコードやドメインに対する知識不足を解決するため、コードの分析からプランニングまでをサポートするClaude CodeのSubagentsを3つ作成しました。Subagentsで分析することで、自力で読み解くよりも高速なドメイン知識の獲得が可能です。また、分析結果をもとにドキュメントを作成してもらうことで、レビューの説明文にも活かせます。 作成したSubagentsと役割は以下の通りです。 spec-refactorer 既存コードを解析し、現状のロジックや仕様を整理 ios-architecture-engineer アーキテクチャ導入に向けた技術的な調査と、依存関係の整理 refact-planner 調査結果を元に、実装の優先順位や具体的な手順のプランニングを提案 3つのプロンプトを載せることは記事の都合上できないため、 spec-refactorer のみを紹介します。 spec-refactorer のプロンプトは以下の通りです。 ## 出力フォーマット に記載されているような画面の構成やデータフローなどをまとめます。 // 重要な部分のみ抜粋 ## 目的 * 画面を開いてからの **データフロー**(依存解決→API→変換→State更新→描画)と、ユーザー操作(例: **Aというコンポーネントをタップ**)時の **処理・I/O・状態/遷移** を一目で把握できるようにする。 ## 出力フォーマット 1. TL;DR(初回ロードと主要アクションのI/Oを箇条書き) 2. 画面の構成(UI/State/Lifecycleの表) 3. データフロー(画面表示→初回ロードのシーケンス図) 4. UIコンポーネント×アクション×副作用の対応表(Cell/ボタン/トグル/Pull-to-Refresh/ページネーション/セル内ボタン含む) 5. 主アクション毎のシーケンス図(最低A=主ボタン, Refresh, セル選択) 6. ネットワークI/O一覧(Method/Path/Auth/キャッシュ/失敗時/呼出根拠) 7. 状態管理(公開State, アクション(必ず網羅すること。アクションを過不足なく網羅できるかがUXに直結する), 非同期/キャンセル) 8. DI(依存解決の流れ)/ナビゲーション 9. エラー/ローディング/空状態 10. イベントログ送信箇所(ない場合は無しと記載) 11. 分析イベント/フラグ 12. リスク・改善 13. Reference ## 探索範囲 * **関連するファイルは可能な限り探索・調査** する(ViewModel/Reducer/Repository/API/Router/DI/テスト/拡張/ユーティリティを横断参照)。 ## 網羅してほしい操作例(該当するもののみ) * 画面表示(初回ロード) * 主要ボタン(例: AddToCart/Favorite/Buy)タップ * セル選択(詳細遷移) * Pull-to-Refresh / ページネーション * 失敗時のエラーハンドリング ## 出力スタイル * 判断根拠となるコードは丁寧に過不足なく提示してください。 * 技術構成を図に描く場合は、左側にUI層で右に行くほどDomain,Data層になるようにしてください。 * 出力は日本語でお願いします。 以下の点が整理されるようにSubagentsを設計しました。これによってハルシネーションや考慮漏れを抑えた出力を得られるようになります。 判断根拠となるコードをドキュメントの最後に記載させる ユーザーアクションを軸にしてデータフローを整理させる 見落としがちなポイントも確認させる エラーやローディング時の挙動 ログ送信の有無 外部へ公開しているプロパティ(画面の状態)は何か MVVM+UseCaseアーキテクチャ導入 2つ目の課題で挙げられていた「Fat」な実装を解消するため、MVVM+UseCaseアーキテクチャを導入しました。MVVM+UseCaseアーキテクチャは Android Architecture Components を参考にしたアーキテクチャであり、以下のように役割を分担させます。 View / ViewController UIレイアウトや画面遷移 ViewModel イベントハンドリングやViewに最適化したデータ整形などのプレゼンテーションロジック UseCase キャッシュ管理などの特定のViewに依存しないビジネスロジックをカプセルし、原則として状態を持たない Translator DataSourceで取得したデータをUseCaseで扱える型へ変換することで、UseCaseにAPI等の知識が入り込むことを防ぐ DataSource データソース(APIやUserDefaultsなど)へアクセス ViewModel、UseCase、DataSourceはprotocolで抽象化しDIを可能にしました。これにより、UseCase、DataSourceのモックを作ることで、依存関係のあるクラスのテストを書けるようになります。 アーキテクチャ導入によりプレゼンテーションとドメインが疎結合となり、UI層のコードの変更が容易になったため、SwiftUIへの移行がスムーズに行えました。 結果 取り組みによって得られた結果は以下の通りです。 デバッグ画面のテストカバレッジ0→93.5% UI実装をStoryboardからSwiftUIへ完全移行 開発工数40%減少 デバッグ画面のテストカバレッジ0→93.5% 2つ目の課題で挙げたように既存実装は密結合が原因でテストを書けませんでしたが、ViewModel、UseCase、Translatorに対してユニットテストを書けるようになりました。 カバレッジを計測するとデバッグ画面に関連するテストの平均が93.5%でした。残りの6.5%はテスト不要なコードが対象となっているため、実質100%となります。 UI実装をStoryboardからSwiftUIへ完全移行 画面の実装をStoryboardからSwiftUIへ完全に移行させました。既存実装ではカスタムコンポーネントを使っていました。しかし、これらはOSのアップデートに伴うデザインシステムの変更や仕様変更の影響を受けやすく、その都度レイアウトの調整や挙動の修正が必要になるため、メンテナンスコストが増大していました。このメンテナンスコストを抑えるため、カスタムコンポーネントをSwiftUI化に合わせて廃止し、 Apple標準のコンポーネントのみ で画面を構成しました。 開発工数40%減少 既存実装に合わせたViewModelのリファクタリングや網羅的なユニットテスト実装など、Claude Codeに実装をサポートしてもらいました。 結果、Claude Code導入前に(先輩社員と相談し)見積もっていた 工数35日が、Claude Codeを使うと20日で完了 しました。約40%の工数削減となります。また、コードを書く時間が減り設計や実装方針を考える時間が増えたためClaude Codeなしに比べきれいなコードを書けました。 MVVM+UseCaseアーキテクチャ導入を振り返って 振り返ると、序盤は以下のような不安を抱えていました。 デバッグ画面のキャッチアップに時間がかかりすぎて、他画面のキャッチアップに時間を使えないのではないか 既存仕様を確認できておらずリグレッションを発生させてしまうのではないか (今後アサインされるであろう)他案件と折り合いがつかず中途半端に終わらせてしまうのではないか 冒頭でもお話ししたように、これらの不安の根本は「広い範囲のキャッチアップをうまく行えない」ことです。原因に序盤で気づき、Claude CodeのSubagentsを使ったキャッチアップ方法を模索するようになりました。初めは欲しい情報を得られずプロンプトを何度もチューニングしていましたが、今では開発に活きる情報(画面の状態やデータフローなど)が得られるようになっています。このように試行錯誤しながら作り上げたSubagentsのおかげでスピード感を持ってキャッチアップを進められました。また、Subagentsに調査結果をドキュメント化してもらうことで、ドキュメント生成のスピードも格段に上がりました。 これらの実績をまとめ、2025年10月には社内の全エンジニアを対象とした技術共有会で紹介しました。紹介をきっかけにiOSチーム以外の方にも活動を認知してもらい、「デバッグ画面を使いやすくなって作業が楽になりました!」のようなポジティブなフィードバックを多くいただきました。嬉しかったです。 アーキテクチャ導入を担当させてくれてサポートまでしてくれた上長やチームメンバーに感謝でいっぱいです。 最後に 本記事では、新卒1年目の私がAIを使ってデバッグ画面にアーキテクチャを導入した際の取り組みを紹介しました。現在は、その経験を活かして、より難易度の高い別画面への導入を進めています。試行錯誤の日々ですが、今回作成したSubagentsのおかげでコードリーディングの負担は格段に減り、開発スピードも上がったように感じています。本記事が、私と同じくキャッチアップの速度に課題を感じている方に届き、開発の生産性向上に少しでも貢献できれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com アーキテクチャの設計およびレビュー体制は「 ZOZOTOWN iOSアプリでのFatViewController解消への取り組み 」をご覧ください ↩
アバター
.table-of-contents > li > ul > li > ul > li { display: none; } はじめに こんにちは、データシステム部MA推薦ブロックの佐藤( @rayuron )です。私たちは、主にZOZOTOWNのメール配信のパーソナライズなど、マーケティングオートメーションに関するレコメンドシステムを開発・運用しています。本記事では、 GitHub Projects 、 BigQuery 、 Looker Studio を組み合わせて作業工数を可視化し、改善サイクルを回すための仕組みを構築した取り組みについてご紹介します。 はじめに 背景と課題 1. ボトルネックの特定に手間がかかり改善に着手しにくい 2. 工数に対する事業価値を把握できていない 3. AI活用の効果測定とナレッジの蓄積ができていない 解決策 1. GitHub Projectsの運用整備 カスタムフィールドの作成 自動入力の仕組み 2. データ収集の自動化 BigQueryへのデータ保存 GitHub Actionsで日次エクスポート 3. ダッシュボードと振り返り会の運用 週次振り返り 施策完了時の振り返り 半期振り返り 工数入力 効果 課題に対する効果 1. ボトルネックの可視化と改善 2. 工数と事業価値の関連付け 3. AI活用の効果測定とナレッジ蓄積 副次的な効果 今後の展望 フィールド入力の自動化 施策の優先度決めでの活用 AI活用のさらなる促進 おわりに 背景と課題 MA推薦ブロックはこれまでGitHub Projectsでタスクを管理してきましたが、担当者やリポジトリごとに記入フォーマットが異なり、運用ルールが統一されていませんでした。そのためGitHub Projectsによる進捗確認はできるものの、工数の集計や振り返りなどに活かせていませんでした。具体的には以下の3つの課題がありました。 1. ボトルネックの特定に手間がかかり改善に着手しにくい プロジェクトを進める中で「なんとなく時間がかかった」という感覚はあっても、どの工程に工数が多くかかっているのか、予想と実際の工数の差が大きい箇所はどこなのかを把握できていませんでした。また、当時の状況やドキュメントがSlack、Confluence、GitHubに点在しており、ログが散逸していたためボトルネックの特定に時間がかかり改善に着手しにくい状況でした。 2. 工数に対する事業価値を把握できていない プロジェクトの事業価値や投資対効果を把握するために必要となる、過去のプロジェクト工数と事業KPIの変化を紐付けて記録できていませんでした。 3. AI活用の効果測定とナレッジの蓄積ができていない あるタスクに対してどのようなAI活用が効果的だったかというナレッジを蓄積・共有する仕組みがありませんでした。そのため人によっては効率的にAIを活用している一方で活用が進んでいないメンバーもおり、チーム全体でのAI活用の波及が進んでいない状況でした。 解決策 これらの課題に対して以下の解決策を考えました。 課題 解決策 1 ボトルネックの特定に手間がかかり改善に着手しにくい 施策の工数予実を可視化し振り返ることでボトルネックを特定し改善する仕組みを作る 2 工数に対する事業価値を把握できていない プロジェクトごとの工数と事業KPIの変化を関連付けて振り返る仕組みを作る 3 AI活用の効果測定とナレッジの蓄積ができていない タスクごとにAI活用のナレッジを蓄積・共有する仕組みを作る 上記の解決策を実現するため、GitHub Projects + BigQuery + Looker Studioを組み合わせた仕組みと、振り返り会を導入しました。 1. GitHub Projectsの運用整備 まず初めに、タスクに紐づく工数予実などのデータを取得する必要がありましたが、これには引き続きGitHub Projectsを活用することにしました。GitHub Projectsではカスタムフィールドを活用することで、タスクに任意のメタデータを付与できます。さらにGitHub Actionsとの連携により、様々なオートメーションの構築も容易です。社内では既に Findy Team+ が導入されており主にPRベースで定量的な工数集計と可視化が可能ですが、PRに紐づかないタスクの工数集計や拡張性の観点で最終的にGitHub Projectsを選択しました。 カスタムフィールドの作成 GitHub Projectsにカスタムフィールドを設定しIssueに紐付けます。先述の工数やAI活用度合いを計測するため、以下のカスタムフィールドを用意しました。 フィールド名 種類 説明 入力 案件 単一選択 どの案件に紐づくタスクかを選択 必須 作業内容 単一選択 どの工程のタスクかを選択 必須 予定開始日 日付 タスクの開始予定日 必須 予定完了日 日付 タスクの完了予定日 必須 開始日 日付 タスクの実際の開始日 必須 完了日 日付 タスクの実際の完了日 必須 AI活用 単一選択 AI活用方法を記録 任意 AI削減時間 数値 AI活用による削減時間 任意 工数予実を記録するために開始予定日と完了予定日、開始日と完了日の入力を必須としています。また、作業内容と工数の紐付けを行うため、案件と作業内容の入力も必須としました。さらに、Issueのテンプレート機能を活用し、概要と完了条件を必須項目として設定し最低限のコンテキストがIssueの中で整理されるようにしました。 自動入力の仕組み 上記のように多くのフィールドを用意したことで手動での運用負荷が増加します。これを軽減するためGitHub Projectsに紐づくステータスやIssueに紐づくイベントと GitHub Actions を連携させることで解決を図っています。具体的には以下の自動入力の仕組みを作成しています。 Issueをオープンした時に、オープンした人をassigneeに自動設定する Issueのステータスを「進行中」に変更すると「開始日」が自動入力される Issueをクローズすると「完了日」が自動入力される Issueのステータス変更時にin-progress, in-reviewなどのラベルが自動付与される Issueに紐づくPRでレビューリクエストをするとIssueのassigneeとステータスが自動更新される Issueに紐づくPRをApproveするとIssueのassigneeとステータスが自動更新される 上記に加え、Issue作成のための Claude Code の Skills を作成して運用しています。Skillsでは、 GitHub CLI とClaude CodeのAskUserQuestion Toolを組み合わせています。Issueの作成や編集を選択式で行えるため、コンテキストの整理や運用負荷の軽減につながっています。 2. データ収集の自動化 上記で紹介したGitHub Projectsの情報は GitHub GraphQL API を通して取得可能なため、BigQueryに自動的に保存する仕組みを作成しました。 BigQueryへのデータ保存 まず初めにGitHub GraphQL APIを使用して、GitHub Projectsのデータを取得するスクリプトを作成しました。GitHub Projectsから取得したデータは社内でよく使用するBigQueryに以下のようなカラム構成で保存しています。この時、簡単な集計や営業日計算もスクリプト内で実装しています。 カラム名 説明 url IssueのURL title Issueのタイトル body Issueの本文 comments Issueのコメント assignees 担当者 labels Issueのラベル in_review_label_duration_days レビュー中ラベルが付与されている日数 in_progress_label_duration_days 進行中ラベルが付与されている日数 review_started_at_jst レビュー開始日時 review_ended_at_jst レビュー終了日時 progress_started_at_jst 進行開始日時 progress_ended_at_jst 進行終了日時 created_at Issue作成日時 closed_at Issueクローズ日時 github_project GitHub Project名 status Issueのステータス project 案件名 planned_start_date 予定開始日 planned_end_date 予定完了日 actual_start_date 実際の開始日 actual_end_date 実際の完了日 work_type 作業内容 ai_success_failure AI活用方法を記録 ai_hours_reduction AI活用による削減時間 actual_days 実績日数 planned_days 予定日数 delay_days 予実差(日数) estimation_accuracy 見積もり精度(実績日数/予定日数) project_work_type 案件×作業内容の組み合わせ year_month 年月(YYYY-MM形式) FYH 会計年度と半期(例:FY24H1) GitHub Actionsで日次エクスポート 次に上記のスクリプトをGitHub Actionsで日次実行し、BigQueryにエクスポートする仕組みを構築しました。 name : Export GitHub Project to BigQuery on : schedule : - cron : '0 0 * * *' workflow_dispatch : jobs : export-to-bq : runs-on : ubuntu-latest permissions : contents : read id-token : write steps : - uses : actions/checkout@v4 - name : Install uv uses : astral-sh/setup-uv@v4 - name : Generate GitHub App Token id : generate_token uses : actions/create-github-app-token@v2 with : app-id : ${{ secrets.APP_ID }} private-key : ${{ secrets.APP_PRIVATE_KEY }} owner : your-org repositories : | target-repo-1 target-repo-2 - name : Authenticate to Google Cloud uses : google-github-actions/auth@v2 with : workload_identity_provider : ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} service_account : ${{ secrets.GCP_SERVICE_ACCOUNT }} - name : Set up Cloud SDK uses : google-github-actions/setup-gcloud@v2 - name : Export data to BigQuery env : GH_TOKEN : ${{ steps.generate_token.outputs.token }} GCP_PROJECT_ID : ${{ secrets.GCP_PROJECT_ID }} BQ_DATASET : github_aggregation BQ_TABLE : github_project_items GITHUB_ORG : your-org GITHUB_PROJECT_NUMBER : 123 run : uv run export_github_project_to_bigquery.py 3. ダッシュボードと振り返り会の運用 上記のようにして収集したデータをもとにLooker Studioでダッシュボードを作成し、以下の振り返り会で活用しています。 振り返り会 頻度 ダッシュボード 用途 週次振り返り 毎週20分 WBS、AI活用状況 タスク進捗確認、AI活用ナレッジ共有 施策完了時の振り返り 施策完了時 振り返り用 予実差の確認、ボトルネック特定 半期振り返り 半期ごと 半期振り返り用 プロジェクト横断での指標振り返り 工数入力 月初 工数管理システム入力用 工数割合の可視化 以下では、各振り返り会について説明します。 週次振り返り 週次振り返りでは毎週20分のミーティングで、WBSとAI活用状況を確認します。WBSはGitHub Projectsの予定開始日と予定完了日フィールドとRoadmap viewを活用して作成しています。 WBSを確認することで、メンバー同士がお互いのタスク進捗を把握でき、タスクが遅延している場合にその理由を知ることができます。さらに他メンバーの補助やタスクの分散を検討することで、遅延の早期解消につなげています。 次に、AI活用状況ダッシュボードを見て、AI活用の目標に対する進捗の確認とタスクごとのAI活用事例を共有します。その週最もAI活用が効果的だった事例をピックアップし、社内のピアボーナスアプリで表彰する運用をしています。 施策完了時の振り返り 施策が完了した際に、施策振り返り用ダッシュボードを参照して振り返りを行います。具体的には以下の指標を確認します。 作業内容別の予定日数と実績日数の比較 作業日数合計に対して各作業内容が占める割合 タスク一覧と予実差の確認 予定日数と実績日数の散布図による見積もり精度の確認 ※一部の情報はマスクしています。 上記のダッシュボードを参照しながら、以下の観点で振り返りを行います。 項目 説明 施策の効果 施策が事業KPIにどれだけ貢献したかを確認 開発工数 予定と実際の工数のずれを確認しボトルネックとなった工程を特定 具体的な原因の深掘りと次回の改善点を議論 施策KPT 継続すべき事、問題点、改善案を整理し、次回施策に向けたアクションを決定 半期振り返り 半期振り返り用ダッシュボードを使用して、半期の活動を振り返ります。施策振り返り用ダッシュボードが1案件単位での振り返りに特化しているのに対し、こちらはプロジェクト横断で同様の指標を振り返りできるようにしています。 上記のダッシュボードを参照しながら、以下の観点で振り返りを行います。 項目 説明 案件とタイムライン 半期に取り組んだ案件の一覧とスケジュールを振り返る 事業KPI 案件毎の事業KPIの変化を確認 事業KPI 以外での貢献 登壇、チームの仕組みづくりによる影響などKPI以外の価値を評価 コスト インフラコストの変化を把握 開発生産性 案件や作業内容の割合、予定日数と実績日数の差を分析 ブロックのテーマ チームとして注力したテーマを振り返る 半期KPT 継続すべき事、問題点、改善案を整理し、次期半期に向けたアクションを決定 工数入力 社内では工数管理システムへの工数登録が必要なため、入力支援用のダッシュボードを作成しています。案件×作業内容についての工数割合を円グラフで可視化しています。担当者と年月でフィルタリングをすると、各カテゴリの工数割合が一目でわかり入力の補助になります。 効果 これらの取り組みにより当初の課題が解決されただけでなく、副次的な効果も得られました。 課題に対する効果 1. ボトルネックの可視化と改善 施策振り返り時に、予定と実際の工数のずれや、工数がかかっている箇所が可視化されるようになりました。感覚的に捉えていたボトルネックをデータで把握できるようになりました。振り返り時にはIssueからその時のコンテキストを参照できるので、解像度の高い課題の特定と正確なネクストアクションの設定が可能です。これまで具体的に以下のようなアクションを設定できました。 要件の抜け漏れによる手戻りの発生に対して、テンプレートとなるドキュメントの更新 実装後にコストが想定外となり調整に時間がかかった課題に対して、コスト試算フローの追加 コードが複雑なので可読性が落ち理解に時間がかかったり、バグを埋め込み手戻りが発生したりするという課題に対して、コードの複雑度を自動計測する仕組みを導入 忘れかけていたボトルネックを振り返り会で再発見することもあり、改善に向けた具体的なアクションを設定できるようになりました。 2. 工数と事業価値の関連付け 施策振り返り時や半期の評価時に、施策ごとの工数と事業価値の関連を確認できるようになりました。特定の案件の工数とKPIの変化を紐付けて振り返ることで、投資対効果の高い施策を記録し説明できるようになりました。 3. AI活用の効果測定とナレッジ蓄積 AI活用フィールドにより、どのタスクでAIを活用したか、どの程度の工数削減につながったかを記録・可視化できるようになりました。IssueあたりのAI活用率は2025年4月の44%から12月には73%へと上昇しました。また、Issueの作成単位は作業内容により異なりますが、平均的な削減時間は分析・レポーティング(3.8h)、執筆・登壇業務(3.7h)、モデル開発(2.8h)、システム開発(2.7h)の順で大きい傾向があります。 さらに、AI活用事例がBigQueryに蓄積されているため、過去の成功パターンを簡単に参照できます。以下は実際に蓄積されたAI活用事例の一部です。 marimo のドキュメント調査をした。marimoはcurlで CLAUDE.md を取得可能で体験がよかった。分析手法の計画とクエリ作成、分析は全てClaude Codeに任せてその後自分で調整と確認をした Claude Codeを使ったドキュメントの推敲をした。目次と参考資料からドキュメントを生成してもらいそれを推敲した。画像はVS CodeのDraw.io Extensionでプレビューを確認しつつ、ローカル上でClaude Codeに作成してもらいそれを手動調整した 副次的な効果 当初の課題以外でも以下のような副次的な効果が得られました。 GitHub ProjectsとGoogle SpreadsheetでのWBSの二重管理が解消された SlackでのPRレビュー依頼がなくなり、必要のないコミュニケーションが減少した 過去のプロジェクトデータにBigQueryを通してアクセスできるようになり、深掘り分析が容易になった 週1の定例でプロジェクトの遅延を可視化でき、早期のフォローが可能になった AI活用の定量的な目標設定と振り返りが可能になった AI活用事例を他チームと共有しやすくなった 社内の工数管理システムへの入力が効率化された 今後の展望 フィールド入力の自動化 Issue作成には先述したオートメーションを活用しているものの、まだ運用負荷が高いためフィールド入力のさらなる自動化を検討しています。また、AI活用事例も現在は自己申告ベースのため、自動化や精緻な効果測定の仕組み作りを考える必要があります。 施策の優先度決めでの活用 工数と事業価値の関連付けと記録は可能になりましたが、それを用いた将来的な施策の優先度付けは感覚的な活用に留まっており十分ではありません。今後は、工数と事業価値の関連データを活用した施策の優先度決めの仕組み作りを進めていきたいと考えています。 AI活用のさらなる促進 また、Issueに「概要・完了条件」を構造化して記載する運用により GitHub Copilot やClaude CodeがIssueを読み取ってPRを作成するワークフローとの相性も良くなっています。今後はAIによるタスク自動達成ケースの分析・評価と仕組み作りを進め、AI活用のさらなる促進を目指します。 おわりに 本記事では、GitHub Projects、BigQuery、Looker Studioを組み合わせて作業工数を可視化し、改善サイクルを回すための仕組みを構築した取り組みについてご紹介しました。本仕組みにより、ボトルネックの可視化と改善、工数と事業価値の関連付け、AI活用の効果測定とナレッジ蓄積が可能となりました。 ZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味がある方は以下のリンクからぜひご応募ください! corp.zozo.com
アバター
.table-of-contents > li > ul > li > ul { display: none } p + ul { margin-top: -10px !important } img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20260114/20260114193736.png"] { width: 50% } img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20260114/20260114223243.png"] { width: 40% } img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20260118/20260118003257.jpg"] { width: 50% } .images-row { width: 100% !important } p.footnote { margin: 0 !important } こんにちは、XR × Fashion TechやXR × Beauty Tech領域の取り組みを推進している創造開発ブロックの @ikkou です。 2026年1月6日から9日の4日間にかけてラスベガスで開催された「 CES 2026 」に一般参加者として現地参加しました。個人としては7回目、ZOZO所属としては5回目の参加です。継続参加し、業界動向を定点観測しています。 CES 2025 現地レポート: XRとFashion Techの未来を探る CES 2024 参加レポート - コロナ禍以前の活況を取り戻した CES CES 2023 参加レポート - 3年ぶりの現地参加 CES 2020参加レポート: 現地参加3年目の目線で視た #CES2020 前半はCESの概要と関連する情報のアップデートを、後半は特に私が注目したトピックについてお伝えします。 CES 2026全体のトレンドについては、会期中に配布される「CES Daily」(デジタル版)の Day 1 / Day 2 / Day 3 などもあわせてご覧ください。 CES 2026の概況 CES 2026のメインテーマは「Innovators Show Up」 CES 2026の6つのトレンドと49のカテゴリー CES 2026におけるメガトレンド 出展社数と参加者数の推移 CES 参加バッジの価格 会場の概要 LVCC Campus Venetian Campus その他の会場 会場間の移動 Vegas Loop Zoox 注目のトピック:XR Tech XR関連企業の出展動向 出展ブースのXRデバイスから見るトレンド 中国企業の大攻勢 中国系の眼鏡型デバイスを一手に引き受けるMeta-Bounds 見かける機会の減ったHMD型デバイス 日本発企業が示したXRデバイス向け要素技術 Play For Dream Cellid Even Realities Meta 注目のトピック:Fashion TechとBeauty Tech Mode Wearablesの「ModeX Bomber Jacket」 デジタルカラーチェンジネイルの「iPolish」 カスタム3Dプリントシューズの「Fitasy」 ReFaの「ReFa AI color recipe PRO」 花王の「my Symmetry & THE CORE」 Perfectの「YouCam AI API」 注目のトピック:その他 NVIDIA Samsung Razer Robot, Robot and Robot... おわりに CES 2026の概況 今年のCESは「人が戻った」だけでなく、“会場の重心”がLVCC中心から周辺会場に少し移動したように感じました。 大手企業の出展の仕方が変わり、LVCCの景色が去年と違う 一方で来場者は増え、移動のボトルネックは相変わらない このあと前半で全体像、後半でXR Tech、Fashion Tech、Beauty Tech目線の“刺さった展示”をまとめます。 数年来の工事が完了して綺麗に整備されたLas Vegas Convention Center CES は CTA (Consumer Technology Association)が主催する、毎年1月にラスベガスで開催される世界最大級のテックイベントです。公式サイトでは「 The Most Powerful Tech Event in the World 」と表現されています。「セス」と呼ぶ方もいますが、正しくは「 シーイーエス 」です。CESはかつて『Consumer Electronics Show』と呼ばれていましたが、現在この表記は使用されていません。現在は「Consumer Electronics Show」の表記は前面に出されず、CESとしてブランド化されています。 CES 2026のメインテーマは「Innovators Show Up」 メインテーマが掲げられているVenetian 2Fのサイネージ CES 2024の「All ON」、CES 2025の「DIVE IN」に続くCES 2026のメインテーマは「 Innovators Show Up 」でした。 *1 "CES is where innovators show up, business accelerates, partnerships ignite, and technology transforms real-world challenges into bold opportunities" CTA CEOのGary Shapiro氏は上記のように、CESはイノベーターが集う場であることを強調しています。 CES 2026の6つのトレンドと49のカテゴリー CESには毎年CTAの打ち出すトレンドがあり、2026年のトップトレンドとして、次の6つが挙げられています。 *2 AI Digital Health Energy Enterprise Mobility Robotics 「AI」「Digital Health」「Mobility」は昨年に引き続きトップトレンドとして挙げられています。一方、昨年のトップトレンドであった「Energy Transition」「Quantum」「Sustainability」は外れ、新たに「Energy」と「Enterprise」が加わりました。 トップトレンドとは別に、次に挙げる49の技術領域が公式カテゴリーとして設定されています。 Accessibility, Accessories, Additive Manufacturing & 3D Printing, AgTech, Artificial Intelligence, Audio, Beauty Tech , Biotech, Blockchain & Digital Assets, Cloud Computing, Computing, Construction & Industrial Tech, Content & Entertainment, Cybersecurity, Digital Health, Drones, Education Tech, Energy Transition, Enterprise, Fashion Tech , Fintech, Fitness , Food Tech, Gaming & Esports , Home Entertainment & Office Hardware, Imaging, Investment & Venture Capital, IoT/Sensors, Lifestyle, Longevity, Marketing & Advertising, Next G, Pet & Animal Tech, Quantum, Retail/E-Commerce , Robotics, Smart Communities, Smart Home & Appliances, Sourcing & Manufacturing, Space Tech, Sports, Startups, Streaming, Supply & Logistics, Sustainability, Travel & Tourism, Vehicle Tech & Advanced Mobility, Video & Display, XR & Spatial Computing 私が特に注目しているカテゴリーは太字で示しています。CES 2025では「AR/VR/XR」がカテゴリーとして設けられていましたが、今年は「 XR & Spatial Computing 」と名称が変更されました。同様にいくつかのカテゴリー名が変更された一方で、「Cryptocurrency」「Metaverse」「NFT」などは外れています(それらのカテゴリーの出展が完全になくなったわけではありません)。 CES 2026におけるメガトレンド 先に発表された6つのトップトレンドとは別に、CTAはトレンド予測セッション「 Trends to Watch 」で2026年のメガトレンドとして次の3つを挙げました。 Intelligent Transformation Longevity Engineering Tomorrow なかでも「 Longevity (ロンジェビティ)」、つまり「長寿、健康」に関するAgeTech、LongevityセクターはCES 2026の会場内でも多く見かけました。健康寿命を延ばすことに関心が高まっていることを示しています。 このセッションはメディア向けセッションのため現地では聴講していませんが、 アーカイブ動画 と PDF資料 が公開されています。CTAの謳うテックトレンドに少しでも興味がある方は、一度は目を通すことをおすすめします。 出展社数と参加者数の推移 出展社数は過去最高タイだったCES 2025の4,500社以上から4,100社以上に減少しましたが、参加者数はコロナ禍以降では過去最高の148,000人以上となりました。2018〜2026年の出展社数と参加者数の推移は以下の通りです。 年度 出展社数 参加者数 CES 2018 3,900社以上 182,198人 *3 CES 2019 4,500社以上 175,212人 *4 CES 2020 約4,500社 171,268人 *5 CES 2021 約2,000社 約80,000人 CES 2022 2,300社以上 44,000人以上 CES 2023 3,200社以上 117,841人 *6 CES 2024 4,300社以上 138,789人 *7 CES 2025 4,500社以上 142,465人 *8 CES 2026 4,100社以上 148,000人以上 CESの参加者数は各年のATTENDANCE AUDIT SUMMARYに1桁単位で精密な数字が公表されています(コロナ禍の影響で完全オンライン化された2021年とハイブリッドで開催された2022年は公式発表なし)。これは参加バッジの発行数をもとにした、重複なしの参加者数です。CES 2026の正確な参加者数は後日公開される見込みです。 CES 参加バッジの価格 CESへの参加には参加登録が必要です。メディアやインフルエンサーではない一般参加者は有償で参加バッジを得られます。 参加バッジは紛失時に350 USDの手数料がかかるので要注意 CES 2026の参加登録は昨年同様9月11日に開始され、通常のチケットであるExhibits Plus Passの価格は同12月1日までの早期登録が149 USD、以降は350 USDでした。私は昨年に続き過去回の参加者特典により無料でした。 参加バッジは空港で受け取るとスムーズ 過去に一度だけCES公式のセッションを聴講するためのカンファレンスプログラミングパスを追加しましたが、以下の観点から今年も追加せずにブースのみを巡りました。 カンファレンスに参加するとブースを巡るための時間を削減せざるを得ない 多くのセッション動画は後日アーカイブ動画として無償で公開される セッション以外の動画も含まれていますが、実際に CES 2026関連の公式アーカイブ動画は500本以上が公開 されています。 会場の概要 Image source: https://www.ces.tech/explore-ces/maps-and-locations/ CESの展示会場は昨年同様、 LVCC Campus ・ Venetian Campus ・ C Space Campus の3つで構成されています。 LVCC Campus 大きく様変わりしたLVCC Centralの内部 LVCC CampusはLVCCことLas Vegas Convention and World Trade CenterのWest Hall・North Hall・Central Hall・South Hallを中心に構成されていて、CESのメイン会場とも言えます。特にCentral Hallは大規模工事を経て外観・内観が大きく変わりました。CES 2025以前の参加者はその変化に驚いたはずです。 GoogleはCVSやSphereでGeminiやAndroid XRをアピールしていた 今年は、CESの初回となる1967年から58年間連続で出展してきたSonyをはじめとして、Google、Samsungなどの大手企業がLVCCでの出展を取り止めています。もっともSonyはSony Honda Mobilityとしてブースを構え、販売を控えるAFEELAを大々的にアピールしていました。参加バッジのストラップもSonyではなくSony Honda Mobilityでした。また、Googleは会場外でGeminiやAndroid XRをアピールし、SamsungはWynnに独自のブースを構えていました。 昨年に続きWestgateは会場として利用されず、 CES Foundry や後述するNVIDIA Live、NVIDIA Showcaseの会場として、2023年12月にオープンしたラスベガスで最高層の Fontainebleau Las Vegas が追加されました。 Venetian Campus Venetian Expoの2Fから1Fを俯瞰する Venetian ExpoはCESの1か月前に開催されるAWS re:Inventでもお馴染みのVenetianを会場として、スタートアップ企業の集まるEureka Parkが設けられています。 Eureka Parkで勢いを増しているKorean Tech Image source: https://exhibitors.ces.tech/8_0/floorplan/?st=country&hallID=M&sv=South%20Korea このEureka Parkには、スタートアップ企業による個別のブースとは別に、特定の国が1カテゴリーとして集まっているという特徴があります。これまではフランス発のLa French Techが非常に目立っていましたが、近年は韓国発のKorean Techが勢いを増しています。 JETROが支援するJapan Pavilionとクリエイティヴ・ヴィジョンが推進するJAPAN TECH 日本発としてはJETROが支援する「 Japan Pavilion 」から31社、クリエイティヴ・ヴィジョンが推進する「 JAPAN TECH 」から37社がEureka Parkにブースを構えていました。 その他の会場 C Space Campusは主にマーケター向けの会場で、CESの出展社や参加者がビジネスミーティングするためのスペースが提供されています。 その他、前述の通りSamsungやMetaはCESのプログラムとは別に、Wynnに独自の会場を設けていました。ホテルの一室を利用してプライベートミーティングの場を設ける企業もよく見聞きします。 一般の来場者向けの会期は4日間ありますが、1人ですべての会場・すべてのブースを巡るのは非現実的です。私は今回、到着日をDay 0として、Day 3まで滞在し、以下の行程で会場を巡りました。 Day 0:NVIDIA Live Day 1:LVCC Campus Day 2:Venetian Campus Day 3:周りきれなかったLVCC CampusとVenetian Campus 会期中はとにかく歩き回りました。歩きやすい履き慣れた靴の準備が欠かせません。今年はOnのスニーカーを履いて臨みました。事前に訪れるブースを決めてルートを最適化して効率的に巡っているとはいえ、4日間の平均歩数は昨年同様に毎日20,000歩を超えていました。 会場間の移動 会場間の移動には時間がかかります。例年同様、会場間の移動には徒歩とVegas Loop、そしてライドシェアを利用しました。徒歩とライドシェアについての特筆すべきアップデートはありません。LVCCとVenetianの移動に使えるラスベガスモノレールや2階建てバスのDEUCE(デュース)、通常のタクシーも選択肢として挙げられますが、私はこれらを利用しませんでした。 日中は非常に混雑して会場間の移動には使えませんでしたが、Amazonの子会社であるZooxが2025年9月からラスベガス市内で提供している自動運転車による特定スポット間の移動も利用できました。 Vegas Loop LVCC Central Underground Station 開通以降、毎回お世話になっている Vegas Loop ですが、今年も何度か利用しました。Vegas Loopはイーロン・マスク氏率いるThe Boring Companyが運営するLVCCの会場間を繋ぐ地下トンネルです。徒歩ではそれなりに時間のかかるルートを、Teslaで効率よく結ぶ交通システムです。 Vegas Loop Map Station Map(2026年1月20日現在) Image source: https://www.lvcva.com/vegas-loop/ CES参加者向けの路線は、LVCCのWest Hall・Central Hall・South Hall間を結ぶ無償ラインと、LVCCからホテルを結ぶ有償ラインがあります。この有償ラインは、昨年のCES 2025時点では RESORTS WORLD線 しかありませんでしたが、CES 2025閉幕直後の2025年1月18日に WESTGATE RESORT線 が開通、そして2025年4月8日に Encore線 が開通しました。 運良く乗車できたVegas Loopを走るCybertruck 今回はLVCCからWynnへの移動時に一度だけ有償のEncore線に乗車しました。Vegas Loopを走っている車両はTesla Model S、3、X、そしてYの4種類ですが、時々Cybertruckが走っている姿を見かけていました。この機会に運良く乗車できれば、と考えていたところ、見事このEncore線でCybertruckに偶然にも乗車できました。 Vegas Loopのチケット購入ページ Image source: https://lvloop.com/tickets このVegas LoopはLVCC周辺だけではなく、将来的にはラスベガスの大通りを行き交うルートが計画されています。CES 2026の期間中も10:00から21:00までの時間限定ではあるものの、Resorts World Stationからハリー・リード国際空港へ向かう空港線が運行していました。今回は都合が合わず乗車できませんでしたが、片道12 USDとライドシェアよりも安価なので、次回ラスベガス入りする際には試してみたいところです。 Zoox ZooxのカウンターがあるResorts Worldのエントランス前乗り場 It’s not a car. を標榜する Zoox はラスベガスに登場した新しい「ロボタクシー」です。2020年にAmazonが買収し、現在は独立子会社として運営されていて、システムのバックエンドには AWS(EC2, S3, EKS, CloudWatch)が使われています 。 2026年1月現在は、誰でも無料乗車できる形でラスベガスを、ウェイトリストに登録する形でサンフランシスコをテスト走行しています。 AWS re:Invent期間中も多くの方が乗車レポートを投稿していましたが、CES 2026でも多くの方が乗車していました。今後はSXSWの開催地であるオースティン、そしてマイアミへの展開が 計画されています 。 ラスベガスのZoox走行エリア(2026年1月20日現在) Image source: Zoox Android App CES参加者の目線では、Vegas Loopが主にLVCCの会場間を繋いでいる一方で、Zooxはラスベガス市街のホテルやショッピングモールなどの施設を繋いでいます。ホテルからAREA15やTopgolf間の移動にも使えるので、市内観光の一環としても便利かもしれません。 Resorts WorldのZooxカウンター 私はVegas Loop StationもあるResorts WorldからZooxに乗りました。 Zooxは2025年5月にResorts Worldの公式ロボタクシーパートナーになっていて 、エントランスには有人のZooxカウンターが設けられています(記念撮影してもらえます)。 アプリで無人車両のドアを開ける直前の様子 Zooxの車両は完全に無人です。車両の予約はもちろん、ドアの開閉もすべてZooxアプリ経由で行います。このZooxアプリのインストールにはUSのアプリストアアカウントが必要です。JPリージョンのアプリストアアカウントではインストールできないので、Zooxに乗りたい場合は事前にアカウントの作成が必要です(もっとも開発者に属する多くの方は検証用途などで持っているとは思いますが)。 目的地に到着後、誰も乗せずに走り去っていくZoox 運転席もない自動運転車への乗車は初めての体験でしたが、特段の違和感を覚えることもなく、目的地までスムーズに到着しました。時期的に予約が集中していたため、私の利用時は30分以上待機しました。そのため日中の移動手段としては使いづらい場面もありますが、今後、車両台数や運用が拡充されれば、利便性はさらに向上すると考えられます。前述の通りMobilityはCES 2026のトップトレンドの1つです。特にラスベガスで開催されるテックイベントであるGoogle Cloud Next、re:Invent、そしてCESの参加者はぜひ乗車してみてください。 注目のトピック:XR Tech CESのカテゴリーのひとつに「 XR & Spatial Computing 」が設けられています。これは私が強い関心をもって継続的に追っている技術領域です。このパートでは、それらをXR Techとしてまとめてお伝えします。 XR関連企業の出展動向 CES 2026全体を通して「 XR & Spatial Computing 」カテゴリーの出展社数は 265 でした。CES 2025では「 AR/VR/XR 」カテゴリーの出展社数は 355 だったので、 昨年対比で90ブース減った ことになります。 LVCC Central Hallの会場地図 Image source: https://exhibitors.ces.tech/8_0/floorplan/?st=category&hallID=B 昨年同様、CES 2026では、LVCC Central Hallに「 GAMING | XR 」として分類された一画が設けられていました。 LVCC Central Hall内「GAMING | XR」エリアの会場地図 Image source: https://exhibitors.ces.tech/8_0/floorplan/?st=category&hallID=B このLVCC Centralの「GAMING | XR」エリアにブースを構える「 XR & Spatial Computing 」カテゴリーの出展社数は 32 でした。このエリアは例年通り、Insta360、XREAL、Pimax Technologyなどの中国企業が目立っていました。 また、CES 2024まではXR関連エリアの設けられていたスタートアップ企業が集まるVenetian CampusのEureka Parkですが、今年もそういったエリアは見当たりませんでした。 CESでは毎年、デザインやエンジニアリングにおいて優れた製品を表彰する CES Innovation Awards を実施しています。XR関連の製品は XR & Spatial Computing として分類され、計 12 製品が受賞しました。いわゆるHMD型・眼鏡型のXRデバイスは次の 5 製品です。 CES Innovation Awardsの会場とQualcommのブースに展示されていたGalaxy XR Galaxy XR Vuzix Ultralite Pro Enterprise Platform World's Lightest Full-Feature Colorful AR+AI Glasses World's Lightest Stylish AR Glasses XREAL Project Aura 奇しくも Android XR のHMD型デバイスである「 Galaxy XR 」と眼鏡型デバイスである「 XREAL Project Aura 」の両方が受賞していました。しかし、いずれもデモとして試せる状態になっている動態展示はありませんでした。 出展ブースのXRデバイスから見るトレンド CES 2026で体験したXRデバイス(HMD型・眼鏡型)の一部 例年通り、CES 2026でもたくさんのXRデバイスを試してきました。この写真はHMD型・眼鏡型のXRデバイスの一部です。 体感として CES 2024 では、まだHMD型のバリエーションを感じられましたが、 CES 2025 では、HMD型デバイスを抑えて一気に眼鏡型デバイスが増えました。そして今年のCES 2026ではさらにその傾向が強まり、とにかくたくさんの、それこそ食傷気味になるくらい多くの眼鏡型デバイスを見かけました。 この背景として以下の要因が考えられます。 世はまさに“大AI時代”であること 眼鏡型デバイスの先駆者である「XREAL」の成功 さらに後発にあたるMeta発の眼鏡型デバイス「Ray-Ban Meta」の大成功 中国企業の大攻勢 私自身は展示されていた眼鏡型デバイスのすべてを数えたわけではありませんが、 英香港紙SCMPのレポート によると、眼鏡系デバイスは約60社あり大半を中国ブランドが占めていたそうです。また、 中国メディア我爱音频のレポート 曰くAI機能を持つ主要な眼鏡型デバイスは36種あり、 中国メディア智东西のレポート 曰く27社の中国企業が「AIスマートグラス、VR、AR」製品を展示していたそうです。 中国は、国家戦略として6,000元(2026年1月20日現在で約13.6万円)以内のスマートグラスを対象に価格の15%、500元(同約1.1万円)を上限に 買い替え補助金の支給を公布 し、2026年1月1日から施行したと報じられています。今後、デバイスの「生産」に加えて一般消費者による「購入」もいっそう加速することが予測されます。 中国系の眼鏡型デバイスを一手に引き受けるMeta-Bounds 中国系の眼鏡型デバイスを一手に引き受けるMeta-Bounds EssilorLuxottica がRay-BanやOakleyをはじめとする 非常に多くのアイウェアブランド の眼鏡型デバイスを引き受ける一方で、「oppo」「ZTE」「ThinkAR」「Lenovo」をはじめとする中国系デバイスの多くを Meta-Bounds(莫界科技) が引き受けています。この Meta-Boundsのブランド名がMojie であり、CES Innovation Awardsで2製品がノミネートされています。 見かける機会の減ったHMD型デバイス 眼鏡型デバイスが増え続ける一方で、HMD型の新作はあまり見かけませんでした。既存デバイスを生かした展示という観点でも、前述の通りGalaxy XRの動態展示はなく、Apple Vision Proも“野良展示”を1件見かけただけでした。また、CES 2025で強く打ち出していたSonyのXYN Headsetは協業しているSiemensのブースで見かけた程度でした。もちろん見逃しているブースもあるかもしれませんが、全体的にHMD型デバイスの存在感は薄れている印象を受けました。 HMD型デバイスを打ち出し続けているShiftallとPimaxのブース しかし、昨年から継続してLVCC Centralで出展しているShiftallの「 MeganeX 8K Mark II 」や、例年通り存在感を放っていたPimaxなど、HMD型デバイスを全く見なくなったわけではありません。また、後述しますが、今年はPlay For Dream社から“伏兵”も発表されています。 日本発企業が示したXRデバイス向け要素技術 眼鏡型デバイスではEssilorLuxotticaとMeta-Boundsの二強とも言えますが、複数の日本発企業からXR関連デバイスに生かされる新しい要素技術が発表されています。 www.youtube.com 例えばガラスで有名なAGCは、高屈折率ガラス基板など、次世代デバイスに必要な要素を兼ね備えた複数の要素技術を発表しました。今後、これらの技術はXRグラスメーカーに技術供与されそうです。 www.youtube.com また、TDKはHapticセンサーやMeta-Optic Mirrorによる網膜投影技術を搭載したスマートグラスのソリューション「 TDK AIsight 」を発表しました。他にも、アルプスアルパインは触覚・嗅覚・聴覚を刺激する新しい五感刺激を発表するなど、技術立国として日本も存在感を見せていました。 XR Techのトレンドに引き続き、個人的に興味を惹かれたブースをいくつか紹介します。 Play For Dream Play For DreamのMR R&Dプロトタイプ Image source: Play For Dream Unveils MR Prototype at CES 2026 Apple Vision Proライクな製品で話題になったPlay For Dreamは、“事前発表なし”でMRのR&Dプロトタイプを発表しました。 G-X100-M1のリファレンスデザイン Image source: 万有引力(宁波)科技有限公司 これは2025年12月に公開された GravityXR(万有引力) の G-X100 を搭載した G-X100-M1のリファレンスデザイン を採用したものと思われるデバイスです。現時点ではまだプロトタイプなのでこのままリリースされるとは限りませんが、リファレンスデザイン通りなら重量は93gで遅延はわずか9msという非常に軽量かつ低遅延なデバイスです。 Cellid 過去最大級のブースを出展していたCellid ARグラス用ディスプレイを開発するCellidは、次世代ARグラス「 HJ1 AI Smart Glasses 」を展示していました。このデバイスは、Foxconn × Cellidによる取り組みで、Cellidはレンズ部のウェイブガイドを担当しました。そしてJorjin Technologies(Foxconn)はグラス本体を、GIS(Foxconn)は精密光学と表示関連を担当したそうです。 グラスを各パーツに分解した状態の展示 Cellidの特長のひとつである最大2000nitsの高輝度表示は美しく、約46gの軽量サイズでありながらEye Trackingモジュールを内蔵するなど、非常に期待できる眼鏡型デバイスです。 CellidはこれまでLVCC CentralではなくNorthに小規模なブースを構えていましたが、今年は同じNorthではあるものの壁際に過去最大級のブースを構えていました。グラスの表示部に関わる要素技術において突出している日本発企業であり、ここ数年の変化を見てきているだけに、個人的にも嬉しい気持ちになりました。 Even Realities Even Realities Even G1が日本で局所的な盛り上がりを見せた後、2025年11月に後継機となる Even G2 を発売した Even Realities は満を持してCentral Hallに大きく洗練されたブースを構えていました。 The Even Realities booth at #CES2026 in a flash. Where to find us: Booth #16833, Central Hall. January 6–9. pic.twitter.com/Q70D1fRTvq — Even Realities (@EvenRealities) 2026年1月8日 Even RealitiesはXREALに代表されるような拡張ディスプレイの類いではなく、必要な情報を重畳することを目的としたスマートグラスです。カメラがついていないのでプライバシーの問題が起きず、何よりこのジャンルでは数少ない国内利用に必要な「技適」取得済みで、安心して使える点も魅力です。 当時のOculus Riftがそうであったように、XR関連デバイスにおいて日本は重要な市場のひとつと言えます。 Mogura VR Newsのインタビュー記事 によれば、実際にEven G2は日本の店舗がもっとも売れているそうです。今後の展開が非常に気になる、そしていわゆるキャズムを越える可能性のあるデバイスのひとつだと考えています。 Meta Metaはプライベートブースの他に2か所のMeta Labを推していた MetaはWynn内に商談を目的としたプライベートブースを構えていました。私はこのプライベートには行きませんでしたが、Metaの旗艦店であるMeta Labに行きました。 Wynn Plaza Shopsに店舗を構えるMetaの旗艦店Meta Lab Las Vegas Metaは2025年10月に常設の Meta Lab Las Vegas を Wynn Plaza Shopsにオープンしました 。Meta LabではMeta AIグラスのRay-Ban MetaやMeta Ray-Ban Displayを購入できます。いずれも日本国内ではまだ購入できない眼鏡型デバイスです。 Meta Labで調達したMeta Ray-Ban Display(技適特例申請済み) Meta Lab Las Vegasの存在は事前に把握していたので、あらかじめ予約した上で現地に赴き、その場で Meta Ray-Ban Display を購入しました。このMeta Ray-Ban Displayは特に人気のデバイスで、生産数が限られているため予約なしでは購入できません。 CES初日、この Meta Ray-Ban Displayに大きなアップデートが入り 、テレプロンプターやEMGリストバンドによる手書き入力など複数の機能が追加されました。 EMGリストバンドで自動車のナビを操作する Image source: Garmin Newsroom LVCC West HallのGarminブースでは、 EMGリストバンドを自動車のナビ操作などに活用する実践的なPoC が紹介されていました。 Wynn内にオープンしていた特設のMeta Lab また、 Encore Esplanade のAwakening theater付近にもうひとつのMeta Labを構えていました。こちらの店舗は予約の仕組みがなく、並んだ順の入店となっていました。 注目のトピック:Fashion TechとBeauty Tech CES 2025に続き、CES 2026でも「 Fashion Tech 」と「 Beauty Tech 」のカテゴリーが設けられていました。また、同様にCES Innovation Awardsのカテゴリーとしても設けられていました。 CES 2026で「 Fashion Tech 」カテゴリーとして登録されているブース数は 111 、「 Beauty Tech 」カテゴリーとして登録されているブース数は 121 でした。 CES Innovation Awardsで 2026 Honoree in Fashion Tech を受賞したのは次の7製品でした。 fēnix® 8 Pro - MicroLED Galaxy Watch8 Littlebird Safety Tracker ModeX Bomber Jacket Snapdragon W5+ Gen 2/W5 Gen 2 Wearable Platforms VRING:ON, From Sketch to Factory, in One Flow WE-STIM™ CalfSleeve: Advanced muscle anti-aging device 同様に2026 Honoree in Beauty Techを受賞したのは次の10製品でした。うち4製品は L'Oréal Groupe で、業界随一の強さを感じました。 BALANCE AI Rejuvenation Shower System Hyper Rejuvenating Eye Patch Light Straight MASTER AI Multi-Therapy Pod maXpace Renergie Nano-Resurfacer | 400 Booster SCAR: AI-Powered Precision Scar Treatment and Coverage System Device SkinBoosters Jet Skinsight Water Saver Dose 個人的に興味を惹かれたブースをいくつか紹介します。 Mode Wearablesの「ModeX Bomber Jacket」 Mode Wearablesの「ModeX Bomber Jacket」 「 ModeX Bomber Jacket 」は、2026 Honoree in Fashion Techを受賞したモジュラー式スマートウェアです。スマートウェアと言えば、例えば2019年にLevi'sが発売した「 Levi’s Trucker Jacket with Jacquard by Google 」が思い浮かぶかもしれません。これはスマートフォンの拡張デバイスに近い立ち位置でしたが、ModeX Bomber Jacketは現代の利用シーンに即した機能を備えています。 *9 Wearable charging, energy harvesting, and power transfer solutions. Programmable illumination for signaling and fashion applications. Haptic feedback systems for improved situational awareness. Spatial computing products leveraging on-device AI. 眼鏡型デバイスが一般社会に普及した先には、特に消費電力の課題が考えられるので、こういったスマートウェアが必要になる未来が想像できます。 デジタルカラーチェンジネイルの「iPolish」 デジタルカラーチェンジネイルの「iPolish」 Eureka Parkにブースを構えていたフロリダ発の「 iPolish 」は、ネイルチップを含む専用のデバイスとアプリを使い、わずか5秒で400色以上のカラーを切り替えられる デジタルネイル です。百聞は一見に如かず、まずはX公式アカウントの動画を見てください。 When it's hard to choose, choose iPolish! Pre-orders available now at https://t.co/DogxQcrORr . #iPolish #ces2026 pic.twitter.com/hAWLOQcaaP — iPolish® (@iPolish_inc) 2026年1月11日 これは映画「トータル・リコール」を想起させる体験で、印象に残りました(聞いたことがない方は「トータル・リコール ネイル」で検索してみてください)。原理としてはネイルチップに電気泳動フィルム(electrophoretic film)が貼り付けられています。いわゆるE Inkと似たような仕組みなので、色の変わり方に見覚えがある方もいるのではないでしょうか。 デジタルデバイスという制約があるため、例えばネイルチップのサイズを個々人に最適化するのは難しいといった課題があると思われますが、かなり興味を惹かれたデバイスでした。 公式サイト では2026年6月発送予定で95 USDのプレオーダーで受け付けていますが、残念ながら発送先に日本は含まれていませんでした。 カスタム3Dプリントシューズの「Fitasy」 カスタム3Dプリントシューズの「Fitasy」 Image source: Fitasy Unveils Breakthrough Technology Making Custom-Fit 3D Printed Shoes More Accessible and Affordable, Supporting Better Foot Health 「 Fitasy 」は3Dプリンターで制作するシューズです。3Dプリンター×シューズの組み合わせは、例えば「 STARAY 」をはじめとして既に製品化されているものも多く、新規性は感じられない方が多いかもしれません。このFitasyはスマートフォンアプリでスキャンした足底面と側面にAIを組み合わせて正確な3Dモデルを作成し、そのデータに基づき、個々人に最適化したシューズを印刷するソリューションです。スマートフォンアプリで計測するという観点は「 ZOZOMAT 」に通じるものがあり、非常に興味を唆られました。 ラスベガスのメディア「FOX5 Vegas」にFitasyのCEOのインタビュー動画が公開されています。スキャンの様子も少し映っているので、興味のある方はぜひ見てください。2026年春に発送予定で210 USDのプレオーダーを受付中です。 www.fox5vegas.com ReFaの「ReFa AI color recipe PRO」 CES初出展となったReFaのMTG社 ここ数年、美容系の国内企業がCESに初出展を果たしていますが、今年は美容機器のReFaを展開するMTG社がCESに初出展していました。ブースではよく見かけるReFaの製品群ではなく美容師向けのアプリ「 ReFa AI color recipe PRO 」を展示していました。 美容師向けの「ReFa AI color recipe PRO」 このアプリは「 ハイトーンカラーの技術を、AIテクノロジーで再現するプロフェッショナル向け次世代ヘアカラーAIソリューション 」 *10 とのことで、ブリーチやカラーを日常的に行う派手髪クラスタとしても、特に関心を惹かれる製品でした。 花王の「my Symmetry & THE CORE」 花王のコンディショニング・ライフケアブランド「花王ライフケア研究所」によるTHE CORE 花王が2025年10月に立ち上げたコンディショニング・ライフケアブランド「花王ライフケア研究所」はJAPAN TECHブースのひとつとしてブースを出展していました。このブースでは、“8歩あるくだけで身体のゆがみを測定できる”アプリケーションの「 my Symmetry 」と、測定結果に応じて着用時のゆがみを補正するためのインソール「 THE CORE 」を展示していました。 カテゴリーとしてはHealth Techの方が相応しいかもしれませんが、身体の歪みを補正するソリューションはBeauty Techにも通じるものがあると考えています。ちなみに私の総合スコアは32点でした。しばらくこのアプリを利用してみます。 Perfectの「YouCam AI API」 Perfectの「YouCam AI API」 ZOZOは2022年4月から「 ZOZOCOSME 」でARメイクを提供しています。このARメイクにパーフェクト社が提供するソリューションを利用しています。そのパーフェクト社はCES 2026では 次世代「AIビューティーエージェント」を展示 していました。また、「 The New Retail Experience in the AI-Powered Store 」と題したセッションに登壇していました。 注目のトピック:その他 一部、XR Tech・Fashion Tech・Beauty Techを含むものの、その他の気になった展示を紹介します。 NVIDIA NVIDIAショーケースの巨大オブジェ NVIDIAはCES本編とは別に、FontainebleauのBleauLive TheaterでCEOによる講演「 NVIDIA Live with CEO Jensen Huang 」があり、講演後からFontainebleau 4FのCobalt Foyerでショーケースとして関連する様々なものを展示していました。私はラスベガスに到着したDay 0に講演に現地参加し、展示も眺めてきました。 革ジャンが代名詞となっているNVIDIAのフアンCEOによる講演 非常に多くの人で賑わっていたショーケースの様子 講演とショーケースを通して感じたのはまさに王者の風格です。NVIDIAにとってもはやLVCCにブースを構える必要などないのかもしれません。NVIDIAに所以のあるブースにはNVIDIAのオブジェが置かれていたので、ブースは出さずとも、NVIDIAの存在をCES会場のいたるところで感じられました。 Samsung SamsungのAIギャラリーゾーン Samsungは1月4日のメディア公開後、1月7日まで Wynnの特別会場で「 The First Look 」と題したイベントを開催 しました。前述の通りSamsungもこれまではLVCC Centralにブースを構えていましたが、今年は独自のブースを展開していました。 CES 2026の会期は1月9日まででしたが、Samsungの展示は1月7日までだったため、会期終盤に見られなかったという声もありました。 ブース全体の様子を映した60秒動画がYouTubeに公開されている ので、興味のある方は覗いてみてください。 パーソナライズされた美容ケアを実現するAI Beauty Mirrorと新製品のSamsung Galaxy Z TriFold 「AIライフパートナー」をテーマにしていたこともあってか、展示されていたものは家電をはじめとした生活に紐づくものが多く感じられました。Beauty Techに分類される「 AI Beauty Mirror 」は、いわゆるミラー系デバイスですが、オンデバイスで動作するAIが搭載されていることや、偏光ミラーとハーフミラーの組み合わせによって鮮明な映像を実現していることが特長的でした。 *11 In the next area, visitors entered a space modeled after a powder room where a circular mirror revealed itself as the AI Beauty Mirror. Powered by on-device AI, the technology signalled Samsung’s technology expansion into the beauty space. The hybrid design combines a polarised mirror with a half mirror to improve reflectivity and transparency, delivering clearer, more precise visuals. このデバイスは韓国ビューティー大手であるAMOREPACIFIC社の技術を生かしたものです。その根底には2026 Honoree in Beauty Techを受賞した「 Skinsight 」の技術が取り込まれています。 一方で、そのテーマ性もあってか「CES Innovation Awards」で「2026 Honoree in XR & Spatial Computing」を受賞した「Galaxy XR」が、このSamsung特設会場のどこにも展示されていなかったことは、個人的には少し残念でした。 Razer AIコンパニオンのひとりに日本人のSAOさんを起用したProject AVA RazerはCES 2025で “AI esports coach”として発表していた「 Project AVA 」 を、CES 2026では “YOUR AI DESK COMPANION”としてその全貌を公開 しました。 プライベートエリアに展示されていたデスクサイズのProject Ava いわゆるポストGateboxとも言える“キャラクター召喚装置”は目新しいものではなく、2025年にKickstarterでクラウドファンディングを実施していた「 CODE27 」や、CES 2026にも出展していた「 Dipal 」や「 Lepro Ami 」などが挙げられます。そういった状況でこのAIが前提になった時代にRazerが本格的に参入してきたことは、潮目が変わりつつあると言えるかもしれません。 CES 2026の翌週、1月11日から13日にかけて開催された NRF 2026 で GoogleはAgentic Shopping時代に向けた共通規格となる「UCP」を発表 しました。もしかすると、こういったAIコンパニオンがその間に介在する未来も十二分に考えられるのではないでしょうか。 Robot, Robot and Robot... 数年前は「NorthはEVのCES」という雰囲気もありましたが、EV市場は頭打ちになりつつあると言われています。今年はそれらがロボットに置き換わっていました。トップトレンドにAIとRoboticsが含まれているだけあります。とにかく多種多様なロボットを見ました。ここで言うロボットは人型ロボットです。効率を最大化するとき、人型であることが最適解なのかは気になりますが、フィジカルAIの機運を感じられました。 AI とロボットの発展によって人が“洗濯物を畳む”という作業から解放される日は来るか? これはあまりに好きすぎてしばらく眺めていた Dyna Robotics 社によるシャツの折り畳みロボット🤖🧺 #CES2026 pic.twitter.com/O4UvKzF7A4 — HEAVEN ちゃんᯅ a.k.a. ikkou (@ikkou) 2026年1月9日 Roboticsは専門分野ではないため深掘りはしませんが、DYNAの洗濯物を畳むロボットはとても印象的でした。興味があれば動画を再生してみてください。 おわりに 帰国時にハリー・リード国際空港で体験したAmazon Oneによる手のひら決済 例によって今回のCES視察は開発部門の福利厚生である「 セミナー・カンファレンス参加支援制度 」を利用しての参加となります。 昨年同様、今回もアメリカン航空の直行便を選択したためフライトは割高になりましたが、乗り継ぎがない分、時間を有効に使えました( ハワイアン航空のStarlink機内Wi-Fi にも惹かれましたが!)。CES 2027の開催日程は、今年と同じ日程の1月6日から9日の4日間と発表されています。参加意向のある方は、できるだけ早くフライトとホテルを手配すると良いでしょう。 例年通りのことですが、フライトとホテル以外にも一定の金銭的コストが発生しています。CESに限らず、海外で開催されるカンファレンスにおいては、そのコストに対して得られる成果に対するコストの正当性を説明するのは難しいかもしれません。しかし、XR領域は「 百聞は一見ならぬ“一体験”にしかず 」です。CESに関するニュース記事はCESの会期中から多く目にしますし、ChatGPTのdeep research機能などを使えば、ある程度の情報は得られます。現地に足を運び、自らの目と手で体験し、一次情報を得ることが重要だと認識しています。Zooxの乗車体験や、帰国時に体験したAmazon Oneによる手のひら決済も、一次情報を得る取り組みの一環だと考えています。 モーニングミーティングで訪れたご当地ファミリーレストランのBlueberry Hill Restaurant そして、CESはビジネスショーという性質上、個別に会話するプライベートブースが用意されています。いくつか参加しましたが、こういったオンサイトならではの対面コミュニケーションも、インターネットメディアの記事等からは得られない大きなメリットだと考えています。わざわざ現地に赴いて参加するのであれば、あらかじめそういった場をセッティングしておくことを強くおすすめします。 最後までご覧いただきありがとうございました。来年もまた、CES 2027のレポートをお届けできるように努めてまいります。 ZOZOでは、各種エンジニアを採用中です。ご興味のある方は以下のリンクからご応募ください。 corp.zozo.com 以上、現地からのレポートでした! *1 : https://www.ces.tech/press-releases/innovators-show-up-ces-2026-opens-today *2 : https://www.ces.tech/press-releases/what-not-to-miss-at-ces-2026/ *3 : https://cdn.ces.tech/ces/media/pdfs/ces-2018-audit-summary.pdf *4 : https://cdn.ces.tech/ces/media/pdfs/ces-2019-audit-summary.pdf *5 : https://cdn.ces.tech/ces/media/pdfs/2020-ces-attendance-audit-summary.pdf *6 : https://cdn.ces.tech/ces/media/pdfs/attendee-audit-summary-2023.pdf *7 : https://www.ces.tech/media/5tmfzidk/attendeeauditsummary_2024.pdf *8 : https://www.ces.tech/media/53eghnx5/ces-2025-attendee-audit-summary.pdf *9 : https://www.modewearables.com/ *10 : https://www.mtg.gr.jp/news/detail/2026/01/article_2481.html *11 : https://news.samsung.com/uk/ces-2026-an-entertainment-companion-for-every-moment-seen-and-heard
アバター
.entry .entry-content .table-of-contents > li > ul, .table-of-contents li:nth-child(2) { display: none; } はじめに こんにちは、検索基盤部検索研究ブロックの小倉です。普段はZOZOTOWNの検索精度改善を担当しています。検索研究ブロックでは2020年から検索結果の「あなたにおすすめ順」(以降「おすすめ順」と呼びます)とその改善に取り組んできました。その過程で「これまで積み重ねてきた改善は、トータルでどの程度効果があったのか?」を確かめるために、ネガティブテスト(最新のロジックと古いロジックを比較するA/Bテスト)を導入・運用してきました。しかし実際に運用を重ねる中で、ユーザー体験の悪化やロジック改善の機会損失といった問題も見えてきました。本記事では、ZOZOTOWN検索おすすめ順におけるネガティブテストを「導入して効果を測った段階」から「運用を見直すに至った段階」まで、その経緯と学びをご紹介します。 目次 はじめに 目次 背景・課題 ZOZOTOWN検索おすすめ順の改善 「1年間の積み上げ改善」をどう測るかという課題 候補に挙がった評価方法とその検討 ネガティブテストの設計と実施 ネガティブテスト設計時に意識したポイント 実施したネガティブテスト 結果から得られたこと 運用を重ねて見えてきた課題 ユーザー体験やビジネスへのマイナス影響 古いロジックの再現にかかる工数 改善活動が止まることによる機会損失 早期切り戻しとなった場合の改善幅の計測精度の低さ ネガティブテストの再定義と今後の運用方針 毎年の定期イベントから外す判断 KPI妥当性検証のためのスポットテストとしての再定義 プロジェクト成果はA/Bテスト積み上げで見る方針へ まとめ 背景・課題 ZOZOTOWN検索おすすめ順の改善 ZOZOTOWNでは、ユーザーごとにパーソナライズされた「おすすめ順」を検索結果の並び順として提供しています。私たちは、2020年からこの「おすすめ順」の改善プロジェクトを継続的に進めてきました。当初はヒューリスティックな手法が使われていましたが、ランキング学習で構築した機械学習(Machine Learning: ML)モデルを導入し、導入後も反復的にロジックを改善してきました。これらの改善を実施する際は、基本的には常にオンラインA/Bテストを行い、その効果を測った上でリリースの可否を判断しています。 「1年間の積み上げ改善」をどう測るかという課題 短期間で行うA/Bテストでは、売上金額などの指標はKPI(Key Performance Indicator)としては感度が低く、有意な差を検出しづらいという問題があります。そこでおすすめ順改善では、コンバージョン率(Conversion Rate: CVR)やクリック率(Click Through Rate: CTR)などのより感度が高い指標をKPIとしています。売上金額はKGI(Key Goal Indicator)であり、KPIはKGIの代理指標である、という位置付けです。感度の高い指標をKPIとすることで、小さな改善でも積極的にリリースしやすくなります。 プロジェクトを進める上では、ユーザー体験を良くすることだけでなく、中長期的にはKGIの向上に寄与することも求められます。1回1回のA/Bテストで「前より良くなった」ことは確認できる一方で、「この1年間の改善の積み重ねは、KGIの向上に寄与しているのか?」を定量的に把握できないことが課題になってきました。 そこで私たちは、「これまで積み重ねてきた改善の効果を、より直接的に測る方法はないか?」という問いに対して、さまざまなアプローチを検討することになりました。 候補に挙がった評価方法とその検討 1年間の改善を測る方法としては、主に次のような案が検討されました。 1. 個々のA/Bテスト結果の積み上げ試算 1回1回のA/Bテストで得られたupliftを積み上げ、1年間のupliftを推定する方法 シンプルで説明しやすい一方で、以下の懸念点がある 改善施策同士の相互作用(重複効果や打ち消し合い)の扱いが不適切 統計的有意差が出ていない結果を積み上げてしまうのは不適切 2. 長期ホールドアウト 一部ユーザーに対して長期間古いロジックの結果を表示し、最新のロジックと比較するA/Bテスト 積み上げた効果を直接比較できたり長期的な影響を観察できたりする一方で、以下の問題点がある 一部ユーザーに与えるマイナス影響 古いロジックを維持し続ける運用保守コスト 対象ユーザー数が少ないことによる検出力の低さ 3. ネガティブテスト(vs. 古いロジック) 通常のA/Bテストと同じ期間で、最新のロジックと古いロジック(1年前のロジック)を比較するA/Bテスト 通常のA/Bテストと同じ枠組みで実施できるため、既存のシステム・運用フローを流用しやすく、検出力も同程度を期待できる 「古いロジックに戻すことで、ユーザーの体験を意図的に悪化させる」という明確なデメリットがある 1年間で有効な改善を積み重ねているほど、悪化も大きくなる 4. ネガティブテスト(vs. 簡単なロジック) ランダム順など、ごく単純なロジックとの比較をするA/Bテスト 比較対象をベースラインとしてupliftを計測できるが、3の手法よりもさらにユーザー体験が悪化する おすすめ順プロジェクトではまず2の長期ホールドアウトを採用していましたが、KPIの有意な差を検出できませんでした。その後実施した3のネガティブテストではKPIの有意な差を感度高く検出できたため、以降はネガティブテストを採用しました。 なお、これらの評価方法を検討する上で私たちは『 A/Bテスト実践ガイド 真のデータドリブンへ至る信用できる実験とは 』という書籍を参考にしました。2の手法「長期ホールドアウト」は第15章で触れられています。3と4の手法については、第23章で「リバース実験」として紹介されている手法をベースに考案したもので、社内用語で「ネガティブテスト」と呼んでいます。こちらは一般的な名称ではなく、別の意味を持つ既存の専門用語と被っているため誤解を招く恐れがありますが、本記事では引き続き「ネガティブテスト」と表記します。 また、検索おすすめ順の改善とネガティブテストの思想については、以下のTech Blog記事でも触れています。 techblog.zozo.com ネガティブテストの設計と実施 ネガティブテスト設計時に意識したポイント ネガティブテストは、意図的に最新のロジックから古いロジックに切り替えるため、treatment群のユーザーにとっては体験が悪化します。十分なデータが取れるように短すぎず、かつユーザー体験への悪影響を抑えられるように長すぎない、適切なタイミングで切り戻しを行いたいと考え、設計段階で特に次の3点を重視しました。 指標 「この指標が有意差ありで悪化したら即時切り戻す」というガードレール指標を明確化し、ネガティブな影響が広がる前にテストを止められるようにした ガードレール指標の設計は、ネガティブテスト導入時の議論を通じて整備され、通常のA/Bテストにおける判断基準にもつながっている テスト期間 基本的には通常のA/Bテストと同じ期間設定で実施 曜日の影響を考慮して1週間分のデータが欲しいものの、ユーザー影響の大きさを鑑みて最低限のテスト期間は2〜3日に設定した 切り戻し判断とエスカレーションフロー ガードレール指標が一定の閾値を割った場合に「誰が」切り戻しを判断するのかを合意してから実施 ネガティブテストはその性質上切り戻しになる可能性が十分にあるため、事前にプロダクトオーナーの了承を得た上で実施 実施したネガティブテスト ZOZOTOWN検索おすすめ順におけるネガティブテストは、過去3回実施しました。 第1回 「MLモデル vs ヒューリスティックモデル」という構図で、初めての本格的なネガティブテストを実施 事前に複数回のMTGを行い、ガードレール指標や切り戻し基準を整備した上でスタートした 第2回 前年の運用を踏襲しつつ、「最新のMLモデル vs 1年前のMLモデル」という形でネガティブテストを実施 モデル自体を公平に比較するため、「1年前のMLモデル」も「最新のMLモデル」と同じ期間のログデータから学習した 第3回 第2回と同じ形式でネガティブテストを実施 後述する懸念を受け、「ネガティブテスト自体をどう位置づけるか?」を再定義するための議論・検討を集中的に行った 結果から得られたこと 第1回ネガティブテストでは、ヒューリスティックモデルに対してMLモデルが優位であることを明確に確認できました。プロダクトオーナーには、次のようなメッセージを分かりやすく伝えることができました。 「この1年間で、検索おすすめ順は確かに良くなっている」 「MLベースのアプローチに継続的に投資する価値がある」 一方で、第2回と第3回ネガティブテストはMLモデル同士の比較となりましたが、それぞれ次のような結果となりました。 第2回では、最新のMLモデルが優位ではあるものの、切り戻すほどの大きな差が出ないまま規定のテスト期間が経過 第3回では、早い段階で大きな差をつけて最新のMLモデルが圧勝し、切り戻し これらの結果から、 個々のA/Bテストを重ねることでおすすめ順は着実に改善できている と言えます。 運用を重ねて見えてきた課題 ユーザー体験やビジネスへのマイナス影響 あらかじめ想定・許容していたことではありますが、ネガティブテストでは次のような影響があります。 あえて精度の低いモデルを出すことで、ユーザーが欲しい商品に辿り着きにくくなる その結果、短期的にKGIやKPIが低下する ユーザーに「ZOZOTOWN検索は使いにくい」という印象を持たれてしまうリスクがある 第2回ネガティブテストでは、「マイナス影響を覚悟してテストを実施したものの、最終的に十分な差が得られなかった」という状況も経験しました。このとき、「ユーザーに負担をかけてまで得られる知見として、割に合っていたのか?」という問いが、プロジェクト内で意識されるようになりました。 古いロジックの再現にかかる工数 ネガティブテストで古いロジックを再現するには、単に古いモデルファイルをデプロイするだけでは済みません。 過去1年の間に実施されたシステム変更(特徴量の追加・削除、前処理の仕様変更、インフラ構成の変更など)を洗い出す それらを元に戻すか、互換性のある形で再現する テスト終了後には再度最新の状態へ戻す これらには相応の工数がかかりますし、1年前当時の状態を完全には再現できない場合もあります。 加えて、MLモデル同士の比較に特有の問題もあります。学習データとなるログの取得期間を比較対象のモデル同士で揃える場合、そのログは「最新の精度が良いモデルによって並び替えられた検索結果」に依存しています。これにより古いモデルがログを通じて「賢く」なってしまい、モデルの性能差が現れにくくなります。 改善活動が止まることによる機会損失 ネガティブテストの期間中は、他の改善施策のリリースを止めざるを得ません。本来であれば実施できたかもしれない改善施策を、一時的に棚上げすることになります。また、ネガティブテスト自体の準備・分析にも工数がかかるため、他の改善施策が後回しになります。 この「改善活動が止まることによる機会損失」は、テストを重ねるほど無視できないコストとして認識されるようになりました。 早期切り戻しとなった場合の改善幅の計測精度の低さ ネガティブテストでは、ユーザーへのマイナス影響を抑えるため、途中で切り戻しになる可能性があります。この場合、「1年間の改善幅」をどこまで精度高く推定できるかには、次のような制約が生じます。 規定のテスト期間を完走した場合と比べてサンプルサイズが小さくなり、推定値の信頼区間が広がる 切り戻しのタイミングや理由によっては、特定の曜日・キャンペーン期間などに偏ったデータしか取得できない その結果、「着実に良くなっている」という傾向は確認できても、「年間で◯%改善した」と言い切れるほどの精度では評価しづらい このように、早期切り戻しは「ユーザー体験やビジネスへのマイナス影響」を抑える一方で、「1年間の改善幅の計測精度」が低くなるというトレードオフの関係があります。 ネガティブテストの再定義と今後の運用方針 毎年の定期イベントから外す判断 当初、ネガティブテストは「1年間の改善を測るための定期イベント」として位置づけていました。しかし、複数回の運用を経て、次のようなデメリットが当初の想定よりも大きいことが分かってきました。 ユーザー体験やビジネスへのマイナス影響 古いロジックの再現にかかる工数 改善活動停止による機会損失 切り戻しとなった時の改善幅の計測精度の低さ こうした懸念は、プロダクトオーナーからも明確に共有されるようになりました。「ネガティブテストは、毎年なんとなくやるものではない」「目的に対して割に合わないタイミング・やり方もある」という認識のもと、 定期的に実施する運用からは外す という判断に至りました。 KPI妥当性検証のためのスポットテストとしての再定義 ただし、ネガティブテストを完全にやめてしまうわけではなく、その役割と使いどころを絞るという方針に切り替えました。具体的には、ネガティブテストを KGIの代理指標としての、KPIの妥当性を検証するためのスポットテスト として位置づけ直しています。例えば、A/BテストにおけるKPIの定義を変更した後、「そのKPIに沿って改善を積み重ねることで本当にKGIは向上するのか?」を確かめる場合などに実施します。 これにより、ネガティブテストの実施回数を絞りつつ、実施する際には「なぜ今やるのか」「どんな問いに答えるのか」を明確にする、という運用にしました。 プロジェクト成果はA/Bテスト積み上げで見る方針へ これまでの経緯から、現在のおすすめ順プロジェクトでは成果を以下の方針で測ることとしています。 有意に改善したA/Bテスト結果の積み上げ 統計的有意差ありで改善したA/Bテストの件数やupliftを一覧化し、「この1年間で、どのような施策でどれだけ改善したか」を整理 開発チーム内の目標として、「有意差ありの改善をN件出す」といったA/Bテスト起点の指標を置く 前述の懸念に対する対処 施策同士の相互作用は正確には分からないものと割り切る 有意差なしでもシステム面でのメリットが大きい場合はリリースするが、その結果は改善の積み上げには含めない ネガティブテスト自体は依然有効な選択肢です。ただしそれは「とりあえず毎年やるもの」ではなく、KPIを再設計したい時や既存の評価軸に大きな不安がある時など、限られた局面で慎重に実施するべきテストです。これが、数年にわたってネガティブテストを導入・運用してきた結果として得られた結論です。 まとめ 本記事ではZOZOTOWN検索おすすめ順改善におけるネガティブテストの運用をご紹介しました。ネガティブテストの導入によって複数回のA/Bテストによる改善の積み上げがトータルでどの程度の効果をもたらしているかを測ることができました。また、ネガティブテストの運用を見直したことでユーザー体験の悪化を最小限に抑え、開発リソースを前向きな改善施策へ回せるようになりました。本記事が、ネガティブテストの導入を検討している方や、既にネガティブテストを実施していて課題を感じている方の参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、グローバルシステム部フロントエンドブロックの平田です。 私が所属するチームでは ZOZOMETRY というtoBサービスを開発しています。スマートフォンを用いて身体計測し、計測結果を3Dモデルやデータとして可視化します。計測結果はWeb上で管理できます。 このサービスのフロントエンドではReact(Next.js)を採用しています。さらにそれらの知見を深めるために、NYで開催されたJSNation、React Summit US 2025に参加してきました。 この記事では現地参加ならではの経験や、参加したセッションへの考察などを紹介していきます! はじめに JSNationとReact Summitとは? Day 1 - JSNation Day 2 - React Summit After Party 気になったセッションについて The AI-Native Software Engineer AIネイティブ・ソフトウェアエンジニアへのシフト 「70%のAI」と「30%の人間」の境界線 生産性のパラドックスと検証の重要性 「Vibe Coding」とエンジニアリングの規律 まとめ Full-Stack AI Orchestration: Modular Agents, Observability, and the Edge モジュール型エージェントとLangGraph 信頼性を担保する「観測可能性(Observability)」 Temporalによる耐久性のあるワークフロー 実用的なAI技術スタックの全体像 まとめ React Strict Dom: Cross-Platform React Based on the Web 「Web標準」をクロスプラットフォームの基盤に 厳格なサブセットによる「予測可能性」の向上 低コストな移行とメンテナンス性 まとめ Goodbye, useState useState の積み重ねが招く「Rube Goldbergマシン」 「どこに保存するか」よりも「どうモデリングするか」 React 19とサーバーコンポーネントによる簡素化 まとめ Workshop The React Developer's Guide to AI Engineering LLMを「関数」として扱う設計思考 ストリーミングUIとRAGの実装 まとめ Debugging with Sentry AI using Seer, MCP, and Agent Monitoring 自社開発AI「Seer」によるエラー解析の自動化 MCPによるエコシステムの連携 まとめ イベント全体を通じて感じたこと 最後に JSNationとReact Summitとは? JSNation と React Summit は、JavaScriptおよびReactに特化した国際的なカンファレンスで、 GitNation が主催しています。現地参加とオンライン参加のハイブリッド形式で開催され、それぞれJavaScript、React.js関連の様々なセッションが行われます。また、ネットワーキングやオンラインワークショップ、アフターパーティーなど、多彩なプログラムが提供されています。最新の技術の動向を学び、世界中の開発者と交流する絶好の機会です。 様々な国や地域で開催されていますが、今回は2日間に渡ってアメリカで開催されたJSNation、React Summitに参加してきました。ZOZOからは昨年 アメリカ・ニューヨークで開催された同イベント にもエンジニアが参加しており、今回も引き続き参加できることになりました。 日付 時間帯(EST) イベント 場所 2025/11/17 9:00 - 17:00 JSNation Liberty Science Center 2025/11/18 9:00 - 18:00 React Summit Liberty Science Center 2025/11/18 19:00 - 22:00 After Party Zeppelin Hall Beer Garden それでは早速参加したそれぞれのイベントについて詳しく紹介していきたいと思います。 Day 1 - JSNation 今年も会場は昨年と同様のLiberty Science Centerです。 会場に向かう途中で見ることができるマンハッタンの光景 今年は昨年の同時期よりとても寒かったです チェックイン後にもらえる参加者バッジ(デザインが昨年と変わっていました) オープニング前の交流スペースの様子 今年の朝食はシンプルなベーグルでした オープニングの様子(写真提供:GitNation) 最初のセッションの様子(写真提供:GitNation) 質問ルーム(毎セッション後にスピーカーに質問できます)(写真提供:GitNation) お昼ご飯 ランチタイムはフィンテック系企業でフルスタックエンジニアをしている人々と、デザイン系SaaS企業でバックエンドエンジニアをしている人と一緒に過ごしました。我々グローバルシステム部が扱っているプロダクトはやはり世界的にもユニークなので、興味を持ってもらえることが多くて嬉しかったです。午前中のセッションの感想や旅行の話などをしたらあっという間に午後のセッションの時間になったので移動しました。 ちなみにデザイン系SaaS企業の人は、昨年会社が企業ブースを出したので、無料で参加できたそうです。 またセッション以外には、複数のスピーカー達が意見を交わすディスカッションコーナーが設けられており、リスナーとしても参加可能でした。 セッションよりも近い距離で見ることができ、質問も可能なためセッションにはない面白さがありました。 ディスカッションの様子 おやつタイム(写真提供:GitNation) 1日目のエンディング(写真提供:GitNation) Day 2 - React Summit ノベルティのマグカップ React Summitのオープニング(写真提供:GitNation) 企業ブース(写真提供:GitNation) 企業ブースもたくさんあり、幾つか回りました。こちらはおなじみのFigmaです。 プロジェクトによってはフロントチームがデザインをすることがあり、その際にFigmaのMCPサーバを使ってUIの実装をすることがあります。仕上がりにバラつきがあるので安定してUIを作れないかどうか聞いてみましたがそれはまだまだ難しいとのことでした。これからに期待ですね。 2日目のランチ この日のランチタイムは、前日に出会ったフィンテック企業の人々と、新たに出会ったノルウェーのコンサルティング企業のエンジニアと当日のスピーカーのNicolasさんと一緒にお昼を食べました。 セッションの感想やどんな仕事をしているか基本的な会話をした後、移住の話題で盛り上がりました。ノルウェーのエンジニアがスイスに移住検討中であること、Nicolasさんがイギリスから元々アメリカに移住したことなどを話しました。地理的にも、言語的にも色々と移動しやすいことはとても良いですね。 CodeRabbitのブース前にあった可愛いキャロットケーキ CodeRabbitのブース(写真提供:GitNation) こちらはPRをAIがレビューするツールのCodeRabbitのブースです。AIの導入によりPRの作成が高速化した一方で、チーム内レビューが追いついていないという問題は、どこのチームでも発生していると思います。このツールはコードレビューを自動化するサービスで、PR作成からマージまで大幅に高速化できるので、実際のプロダクトでも試してみたいと思いました。 イベントエンディングの様子(写真提供:GitNation) 気づけばあっという間に2日目も終了しました。この2日間はエネルギッシュな雰囲気に触れ、非常に刺激的で充実した時間を過ごすことができました。 After Party React Summitの後には、After Partyの場が用意されています。今年はZeppelin Hall Beer Gardenでの開催でした。せっかくの機会なのでぜひ参加したかったのですが、今回宿泊したホテルが会場からかなり遠かったため、安全面を考慮して泣く泣く参加を見送りました。代わりに、宿泊先が割と近かった一部の参加者と夕食を食べることになりました。 プチアフターパーティーの様子 気になったセッションについて それでは、特に印象に残ったセッションを4つご紹介します。これらはGitNationのウェブサイトでも視聴可能ですので、ぜひチェックしてみてください。 gitnation.com The AI-Native Software Engineer gitnation.com まず1点目は、Google ChromeのエンジニアリングリーダーであるAddy Osmaniさんによるセッションです。AI時代のエンジニアはどうあるべきかという問いに対し、 「The Human 30%(人間の30%)」 というキーワードを軸に、AIとの共存戦略を説いた非常に刺激的な内容でした。 プレゼンテーションの様子(写真提供:GitNation) 以下、気になったポイントをまとめます。 AIネイティブ・ソフトウェアエンジニアへのシフト AIは単なる補助ツールではなく、開発プロセスの初期段階から組み込まれる「AIネイティブ」なワークフローへと進化しているという点が非常に印象的でした。エンジニアの役割は、自ら一行ずつコードを書く「実装者」から、複数のAIエージェントを指揮して目的の成果物を得る「オーケストレーター」へと変化しています。これからは「どうコードを書くか」ではなく「どう正しいコードを構築させるか」という、より高次な視点が求められると感じました。 「70%のAI」と「30%の人間」の境界線 AIは定型的なコーディングやプロトタイプ作成など、タスクの約70%を驚異的なスピードでこなせます。しかし、残りの30%には人間の介入が不可欠です。特に、複雑なアーキテクチャ設計、エッジケースの考慮、セキュリティリスクの判断といった「高コンテキスト」な領域は、依然として人間にしかできない重要な付加価値です。AIが得意な「低コンテキスト」な作業を委ねることで、人間はより本質的な問題解決に集中できるという考え方に強く共感しました。 生産性のパラドックスと検証の重要性 AIによってコードの生成速度は上がりましたが、一方でプルリクエスト(PR)のサイズが肥大化し、レビューに要する時間が大幅に増加しているという「生産性のパラドックス」についても触れられていました。AIの提案は一見正しく見えても、微妙な誤りを含むことがあります。そのため、 「信頼せよ、されど検証せよ(Trust, but verify)」 という姿勢が、AI時代の品質担保において最も重要なスキルになると感じました。 「Vibe Coding」とエンジニアリングの規律 スピード重視の「Vibe Coding(ノリで書くコーディング)」と、厳格なプロセスを重視する「AI支援エンジニアリング」を、状況に応じて使い分けるスキルの重要性が語られていました。新規機能のプロトタイピングには前者を、ミッションクリティカルな本番環境の構築には後者をといった、AIとの距離感をコントロールする能力がこれからのエンジニアの武器になると確信しました。 まとめ このセッションを通じて、AIに代替されることを恐れるのではなく、AIを「能力の増幅器」としてどう使いこなすかというポジティブなマインドセットの重要性を再認識しました。 特に「人間の30%」である、複雑な文脈の理解や最終的な品質責任を担う部分は、エンジニアとしての専門性がより光る領域です。AIを単なる効率化ツールとしてだけでなく、自身の学習を加速させるパートナーとして活用し、日々の開発業務の質を一段階引き上げていきたいです。 Full-Stack AI Orchestration: Modular Agents, Observability, and the Edge gitnation.com 2点目は、NetflixのシニアエンジニアであるJamal Sinclair O'Garroさんによるセッションです。 AIを単なる「便利な機能」としてではなく、エンタープライズレベルの本番環境で耐えうる「堅牢なシステム」として構築するための、フルスタックなオーケストレーション戦略についてです。 プレゼンテーションの様子(写真提供:GitNation) 以下、気になったポイントをまとめます。 モジュール型エージェントとLangGraph Netflixのような大規模システムを支える視点から、AIエージェントを巨大な1つの塊にするのではなく、特定の責務に特化した「モジュール」として分割する設計が推奨されていました。セッションでは LangGraph を用いてエージェントをグラフ構造として定義します。それらを組み合わせることで、複雑なタスクを処理できる「モジュラーモノリス」なAIシステムを構築する手法が紹介されました。 信頼性を担保する「観測可能性(Observability)」 AIシステムのブラックボックス化を防ぐため、 LangSmith などのツールを活用してトレース(追跡)を行う重要性が強調されました。エージェントが「なぜその回答に至ったのか」の思考プロセスや、APIコールの履歴、トークンコストを可視化することで、エンジニアがデバッグ可能な状態を維持できます。これがプロダクション導入への鍵であると再認識しました。 Temporalによる耐久性のあるワークフロー AIの挙動は予測不可能で、外部APIの失敗もつきものです。そこで、ワークフローエンジンである Temporal を組み合わせるアプローチが紹介されました。これにより、処理が途中で失敗してもリトライや状態の復元が可能になり(Durable Execution)、システム全体の耐障害性を劇的に向上させることができます。 実用的なAI技術スタックの全体像 セッションでは、プロンプト管理から評価(Evaluation)、デプロイまで、モダンなフルスタックAI開発に必要なツール群が体系的に紹介されていました。 「ただAPIを叩くだけ」のフェーズから、エンジニアが主導して「信頼性の高いAIシステムを設計・運用する」フェーズへと移行していることが明確に示されていました。フロントエンドだけでなく、バックエンドやインフラ領域に興味があるエンジニアにとっても非常に興味深い内容でした。 まとめ 「AIを魔法として扱うのではなく、システム設計の一部として捉える」というJamalさんの言葉が非常に印象的でした。LangGraphでロジックを組み、Temporalで耐久性を高め、LangSmithで監視する。この「モダン・エンタープライズ・スタック」は、信頼性が求められるプロダクトにAIを組み込む上で、非常に参考になるアーキテクチャでした。 React Strict Dom: Cross-Platform React Based on the Web gitnation.com 3点目は、MetaのNicolas Gallagherさんによるセッションです。WebとネイティブReact Nativeの間にある「スタイリングとAPIの乖離」を解消する、 react-strict-dom についての解説でした。 プレゼンテーションの様子 以下、気になったポイントをまとめます。 「Web標準」をクロスプラットフォームの基盤に これまでのクロスプラットフォーム開発では、Webとネイティブで別々のコンポーネントやスタイル記述が必要でした。react-strict-domは、 div や span といった「Web標準のHTMLタグ」と「CSS」のサブセットを共通のインタフェースとして使用します。Webエンジニアが慣れ親しんだ知識をそのままネイティブ開発に転用できるだけでなく、プラットフォーム間のフラグメンテーション(断片化)を根本から解決しようとするアプローチです。Metaの強力な意志を感じました。 厳格なサブセットによる「予測可能性」の向上 「Strict(厳格)」という名の通り、あえて使用できるCSSプロパティを限定することで、どのプラットフォームでも一貫した挙動を保証しています。Meta内部での採用実績も紹介されており、開発効率の向上だけでなく、成果物の品質も担保されている点は非常に説得力がありました。特に「AIによるコード生成」との相性も良く、構造化された厳格なAPIがAIの生成精度を高めるという視点は、これからの開発において大きなメリットになると感じました。 低コストな移行とメンテナンス性 既存のReact NativeコンポーネントをWebへ、あるいはその逆へと移行する際のコストを大幅に下げることができます。特定のプラットフォームに依存した「方言」を排除し、標準的な記述に寄せることで、長期的なメンテナンス性が向上します。マルチプラットフォーム展開を前提とした大規模プロジェクトにおいて、この共通基盤は非常に強力な武器になると確信しました。 まとめ このセッションは、Reactエコシステムが再び「Web標準」に回帰しつつ、ネイティブの力を最大限に引き出そうとしている流れを象徴していました。 「Web向け」「アプリ向け」とエンジニアを分けるのではなく、共通の「React Strict DOM」という言語を通じて、より効率的にプロダクト価値を届ける未来がすぐそこまで来ていると感じます。我々のチームでも、今後のマルチデバイス展開を見据えて注視していきたい技術です。 Goodbye, useState gitnation.com 最後は、XStateの作者としても知られるDavid Khourshidさんによる、状態管理のパラダイムシフトを提案するセッションです。「複雑なアプリをuseStateだけで管理し続けるのはもう限界ではないか?」という、全Reactエンジニアが一度は直面する課題への処方箋でした。 プレゼンテーションの様子(写真提供:GitNation) 以下、気になったポイントをまとめます。 useState の積み重ねが招く「Rube Goldbergマシン」 簡単な状態管理には useState は最適です。しかし、アプリが成長するにつれて useState が10個、20個と並び、それらが相互に影響し合う「Rube Goldbergマシン(無駄に複雑な仕掛け)」のようなコードになってしまいます。この指摘には、深く頷かされるものがありました。特に「Boolean explosion(真偽値の爆発)」によって、あり得ない状態(例えば、読み込み中かつエラー中など)が理論上発生できてしまう設計の危うさを再確認しました。 「どこに保存するか」よりも「どうモデリングするか」 Davidさんは、状態をメモリやURLのどこに置くかという「ストレージ」の議論の前に、まず「アプリケーションのロジック(状態遷移)」を正しくモデリングすべきだと説いています。 単に値をセットするのではなく、「イベント(何が起きたか)」に基づいて「次の状態(どう変わるか)」を決定する宣言的な設計手法は、コードの予測可能性を劇的に高めます。この「イベント駆動」の考え方は、フロントエンドだけでなくシステム設計全般に役立つ視点だと感じました。 React 19とサーバーコンポーネントによる簡素化 React 19での新しいフック( useActionState など)や、Server Components、URLパラメータの活用により、手動で useState を管理するシーンはさらに減っていきます。そのような展望が示されました。 「状態を同期するために useEffect を頑張って書く」のではなく、プラットフォームやフレームワークが提供するプリミティブを使いこなします。ロジックをシンプルに保つ手法が今後のモダンな開発のスタンダードになると感じました。 まとめ セッションのタイトルこそ刺激的ですが、その本質は「Reactの基本機能に頼りすぎるあまり、設計を疎かにしていないか?」という問いかけでした。 AIがコードを生成してくれる時代だからこそ、人間は「あり得ない状態を排除する堅牢なモデル」を設計する能力を磨くことが必要だと感じました。 我々も、目の前の機能実装だけでなく、数年後もメンテナンス可能な「シンプルで美しい状態管理」を追求していきたいと強く思いました。 Workshop JSNation、React Summitでは参加者は現地セッションの他に、オンラインでワークショップを受講できます。その中で私は下記の2つを受講したので紹介します。 The React Developer's Guide to AI Engineering gitnation.com このワークショップでは、Reactエンジニアが「AI機能をただ呼び出すだけ」の段階を超えて、いかにAIをアプリケーションのコアロジックに組み込み、高度なユーザー体験を構築するかを実践的に学びました。 オンラインワークショップのスクリーンショット LLMを「関数」として扱う設計思考 AIを単なるチャットUIとして捉えるのではなく、構造化されたデータ(JSON)を返す「信頼できる関数」として定義する手法が中心でした。Zodなどのスキーマバリデーションを組み合わせ、LLMの出力を型安全に扱います。フロントエンドのUIコンポーネントとAIをシームレスに結合させる設計が非常に参考になりました。 ストリーミングUIとRAGの実装 LLMの長い生成時間をユーザーに感じさせないための「ストリーミングレスポンス」の実装を体験しました。また、独自のデータソースをAIに参照させるRAG(Retrieval-Augmented Generation)の基本構成もハンズオン形式で学びました。Vercel AI SDKなどを用いた実装は、すぐにプロダクトに応用できる内容でした。 まとめ AIエンジニアリングの本質は「プロンプトの調整」だけではなく、AIを既存のソフトウェアアーキテクチャの中にいかに「疎結合」に配置し、観測可能性を確保するかにあると強く感じました。Reactが得意とする宣言的なUIとAIを掛け合わせることで、Webアプリの可能性がさらに広がることを実感できたワークショップでした。 Debugging with Sentry AI using Seer, MCP, and Agent Monitoring gitnation.com このワークショップでは、エラー監視ツールの定番であるSentryが、AIやMCPを活用してデバッグ作業を自動化・効率化する方法を深掘りしました。 オンラインワークショップのスクリーンショット 自社開発AI「Seer」によるエラー解析の自動化 Sentryが開発した独自のAIサービス「Seer」の仕組みを学びました。Seerは、発生したエラーを過去の類似問題やスタックトレース、ソースコードの文脈と照らし合わせ、単なるエラー通知を超えた「根本原因の推測」と「修正案の提示」を自動で行います。人間がログを読み解く時間を大幅に短縮できる可能性を強く感じました。 MCPによるエコシステムの連携 最近注目されているMCPについても触れられていました。MCPを介してSentryのデータとAIエージェントを接続することで、エージェントが自律的にエラー情報を取得し、デバッグの調査を代行するデモが行われました。開発者がダッシュボードを確認しに行くのではなく、AIが自ら問題を調査して報告してくれる未来が現実味を帯びていました。 まとめ AIによるデバッグ支援は「便利」な段階から「不可欠」なインフラへと進化していると実感しました。特に大規模なアプリケーション(ZOZOMETRYなど)では、膨大なエラーログから真に重要な問題を特定するのは困難です。こうしたAIツールを適切に監視フローへ組み込むことで、サービス品質の向上と開発者のメンタル負荷軽減を両立できると確信しました。 上記2つは個人で受講したのですが、この他のワークショップをチームメンバーにシェアして取り組んでみようと計画中です。 イベント全体を通じて感じたこと 実は昨年も同イベントに参加したのですが、1年で大きく変わったように感じました。まず、今回のセッションの内容は予想通りAI関係のものが大幅に増えていました。さらに、内容もシニア向けにフォーカスしたものが増えた印象でした。 セッションの中でも「AIの導入により54%のリーダーがジュニアレベルの採用を減らすことを検討している」という発表がありました。実際、去年は求職中の卒業生やジュニアレベルのエンジニアなど様々な人がいました。一方、今回会話した人々は全員経験のあるエンジニアでした(たまたまなのかもしれませんが)。AIの台頭により我々エンジニアを取り巻く環境は大きく変わりつつあることを実感しました。 昨年は「世界のエンジニアと交流でき、技術知識をアップデートできた! モチベーションが上がった! 楽しい!」という感想が主でした。今回はそれだけでなく、エンジニアとしてこれからの社会を生きていく上で気持ちが引き締まり、去年とはまた違った方向性でモチベーションが上がりました。ワクワクするような新しい技術がこれからもたくさん出てくるでしょう。その中でも楽しみながらアップデートして行きたいと改めて感じたカンファレンスでした。 「AIはあくまで『掛け算』だ。重要なのは、そこに何を掛けるかだ」— Addy Osmani(写真提供:GitNation) 最後に 最後に、ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
.entry .entry-content .table-of-contents > li > ul, .table-of-contents li:nth-child(2) { display: none; } はじめに こんにちは。SRE部会員ID基盤SREブロックの田中です。 ZOZOではマイクロサービスの増加に伴い、昨今高度化するサイバー攻撃に対応しつつ、各システム間のセキュリティとその統一性を維持するための仕組みが求められていました。なかでも、認可基盤の統一は長年にわたる重要な課題のひとつでした。 そこで今回、Istioを活用することで認可機能をアプリケーションから切り離し、ZOZOTOWN共通の認可基盤を実装する方針を採用することにしました。 本記事では、IstioのAuthorizationPolicy機能を利用した認可制御の実装方法について紹介します。 目次 はじめに 目次 Istioとは 課題 Istioを選定した理由 AuthorizationPolicyによる認可制御 AuthorizationPolicyの基本構造 metadata selector(適用対象) action(動作) rules(認可ルール) ヘッダーを用いた認可の動作検証 正しくヘッダーを付与した場合 ヘッダーを付与せずにリクエストした場合 dry-runをfalseにした場合 導入時の注意点 istio-proxyの負荷上昇 段階的なロールアウト dry-runモードの活用 導入により得られた効果 まとめ Istioとは Istio は、分散システムやマイクロサービス間の通信を一元管理するために設計されたオープンソースのサービスメッシュです。各サービスにサイドカーとしてEnvoy Proxyを配置し、サービス間のトラフィックを透過的に制御します。 Istioを利用することで、以下のような機能を実現できます。 トラフィック管理(ルーティング、ロードバランシング) 可観測性(メトリクス、トレーシング、ロギング) セキュリティ(認証・認可、mTLS) 課題 ZOZOTOWN基盤ではマイクロサービス化の進行により、各アプリケーションが独自に認可処理を実装していましたが、徐々に以下のような課題が顕在化していきました。 課題 影響 アプリケーションごとにポリシーや実装方式が異なる 統一性が欠如し、セキュリティホールが生まれやすい。レビュー負荷の上昇 セキュリティ要件の変更時にすべてのアプリケーションへ個別対応が必要 改修コストとリリース速度への影響 これらの課題を解決するため、アプリケーションごとに認可機能を実装する方針から、Istioサービスメッシュによる統一的な制御を実現する方針に転換することを決定しました。 Istioを選定した理由 Istioを選定した主な理由は以下の通りです。 アプリケーションコードの変更が不要 : Envoy Proxyによる透過的な通信制御により、既存アプリケーションへの影響を最小化 言語・フレームワーク非依存 : Go、Java等の技術スタックに関係なく統一したポリシーを適用可能 宣言的な設定管理 : Kubernetesネイティブなリソースとして管理でき、GitOpsとの親和性が高い 豊富な認証・認可機能 : JWTの検証、mTLS、きめ細かなアクセス制御を標準機能として提供 AuthorizationPolicyによる認可制御 Istioの認可ポリシーは AuthorizationPolicy というリソースによって定義します。定義された認可ポリシーは各Envoy Proxyの認可エンジンによって評価され、リクエストに対してALLOW・DENYのどちらかを決定します。 AuthorizationPolicyはクラスタ全体や特定のNamespace・Podに対して設定が可能です。認可ポリシーはリクエストの属性(例:ヘッダー、JWTクレーム、メソッド、パス、宛先サービスなど)を条件として指定でき、ALLOW・DENYを決めるルールを柔軟に定義できます。 AuthorizationPolicyの基本構造 AuthorizationPolicyの基本構造をサンプルコードをもとに確認します。以下は検証用のnginx Podを対象とした認可制御を適用するAuthorizationPolicyの例です。 apiVersion : security.istio.io/v1 kind : AuthorizationPolicy metadata : name : nginx-test-authz namespace : authz-policy-test annotations : istio.io/dry-run : "true" spec : selector : matchLabels : app : nginx-test action : ALLOW rules : - to : - operation : methods : - "GET" paths : - "/*" when : - key : request.headers[x-test-token] values : - "allow-me" AuthorizationPolicyは大きく分けてmetadataと3つのセクション(selector、action、rules)で構成されています。 metadata 作成するAuthorizationPolicyの名前、作成するNamespace、dry-runモードのオン・オフを設定します。 dry-runモードを有効にした場合、認可ポリシーによる評価は行われますが、実際のアクションは実行されません。 selector(適用対象) AuthorizationPolicyは3つのスコープで適用できます。 (1) Workload単位(最も一般的) spec : selector : matchLabels : app : myapp (2) Namespace全体 metadata : namespace : default spec : {} (3) メッシュ全体(istio-system) metadata : namespace : istio-system spec : {} action(動作) アクション 説明 ALLOW 条件に一致した通信を許可 DENY 条件に一致した通信を拒否 AUDIT 条件に一致した通信を許可しつつ、ログに記録(検証用) AUDIT actionとdry-runモードは役割が似ており混同しやすいですが、利用目的が異なります。 前者はリクエストが認可エンジンにより評価されたかどうかの証跡・可視化を、後者はどのActionの対象として評価されたかの確認を目的としています。 どちらも実際の通信への影響はありません。 rules(認可ルール) rulesは from 、 to 、 when の3つの要素で構成されます。 from(リクエスト元の制御) 今回のサンプルコードにはありませんが、例えばService Accountを用いてリクエスト元を制御できます。 rules : - from : - source : principals : [ "cluster.local/ns/default/sa/sample-app" ] to(リクエスト先の制御) - to : - operation : methods : - "GET" paths : - "/*" 主な条件として以下が指定できます。 条件 説明 methods HTTPメソッド(GET、POSTなど) paths リクエストパス ports ポート番号 hosts ホスト名 when(追加条件) when : - key : request.headers[x-test-token] values : - "allow-me" 使用できるキーの例は以下の通りです。 キー 説明 request.headers[...] リクエストヘッダーの値 request.auth.claims[...] JWTのクレーム値 source.ip リクエスト元IPアドレス その他の制御条件については 公式ドキュメント を参照ください。 ヘッダーを用いた認可の動作検証 サンプルコードを用いて実際にAuthorizationPolicyを作成し、ログを確認しながら認可の様子を確認します。 前提として、nginx Podがすでに作成されており、サービスメッシュを通して通信ができる状態とします。 今回はヘッダーを用いた認可を検証します。dry-runモードを有効化することでリクエストに影響を与えず認可結果を確認できます。 先ほどのサンプルコードをapplyした後、以下のリクエストを実行します。 正しくヘッダーを付与した場合 $ curl -I -H " x-test-token: allow-me " http://nginx-test.authz-policy-test.svc.cluster. local HTTP/ 1 . 1 200 OK server: istio-envoy ... Istioのログを確認すると、リクエストが許可(shadow allowed)されていることがわかります。 2025-12-31T03:37:41.966028Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:166 shadow allowed, matched policy ns[authz-policy-test]-policy[nginx-test-authz]-rule[0] thread=35 ヘッダーを付与せずにリクエストした場合 $ curl -I http://nginx-test.authz-policy-test.svc.cluster. local HTTP/ 1 . 1 200 OK server: istio-envoy ... dry-runモードのためリクエストは成功していますが、Istioのログを確認すると、リクエストが拒否されるものと評価されていることがわかります(shadow denied)。 2025-12-31T03:38:01.890287Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:173 shadow denied, matched policy none thread=34 dry-runをfalseにした場合 次にdry-runをfalseに変更して、実際の挙動を確認します。 正しくヘッダーを付与した場合 $ curl -I -H " x-test-token: allow-me " http://nginx-test.authz-policy-test.svc.cluster. local HTTP/ 1 . 1 200 OK server: istio-envoy ... ヘッダーを付与せずにリクエストした場合 $ curl -I http://nginx-test.authz-policy-test.svc.cluster. local HTTP/ 1 . 1 403 Forbidden ... 事前にdry-runモードで確認したように、想定通りリクエストが拒否されました。 このように、AuthorizationPolicyを用いることでIstioを介したリクエストの認可制御を行うことができます。 導入時の注意点 ここで、私たちがAuthorizationPolicyを本番環境に導入する際、注意した点を紹介します。 istio-proxyの負荷上昇 認可処理を追加することでistio-proxyの負荷が上昇します。負荷試験等を実施し、上昇値が許容範囲かどうかあらかじめ確認しておくことを推奨します。 AuthorizationPolicyの認可方法によって上昇率が異なりますが、CPU使用量が1.2〜1.5倍まで上昇したケースも見られました。 段階的なロールアウト 対象ワークロード向けの全リクエストに対して最初から認可処理を実行するのは、本来許可すべきリクエストまでブロックされてしまうリスクがあります。 ZOZOでは Flagger を用いた手動カナリアリリースを実施しました。手動カナリアリリースの詳細については以下の過去ブログから参照できます。 techblog.zozo.com 上記に加え、AuthorizationPolicyのselectorによる認可対象コントロールを用いた下記の手順でリリースを実施しました。 カナリアPodに専用ラベルを付与し、selectorに設定することで独立した認可対象とする 段階的にカナリアPodへ向けるリクエストのパーセンテージを増加する 特定の段階までリクエストパーセンテージを増加させ意図しないブロックなどが行われていないことを確認する selectorをプライマリーPodに付与されているラベルへ変更し認可対象を切り替える 段階的に認可制御を適用することで、リスクを最小限に抑えたAuthorizationPolicyの本番導入が可能です。 dry-runモードの活用 本番導入前にdry-runモードを活用することで、実際のリクエストに影響を与えずに認可ポリシーの動作を確認できます。 Istioのログを分析し、想定通りの認可判定が行われているかを事前に確認することを推奨します。 導入により得られた効果 Istio AuthorizationPolicyを導入することにより、以下の効果が得られました。 効果 詳細 認可ロジックの一元化 アプリケーションコードから認可処理を分離し、統一的な管理が可能に セキュリティ要件変更時の対応工数を削減 AuthorizationPolicyの変更のみで全サービスに適用可能 開発チームの負担軽減 各アプリケーションで認可機能を実装・保守する必要がなくなった キャッチアップ工数の削減 認可処理が統一されたことにより、キャッチアップが必要なリソースが単一化された まとめ 本記事では、IstioのAuthorizationPolicyを活用したマイクロサービスの認可基盤の構築について紹介しました。 AuthorizationPolicyを導入することで、アプリケーションコードを変更せずに統一的なセキュリティポリシーを適用でき、認可処理の一元化を実現できました。また、宣言的な設定管理により、セキュリティ要件の変更にも柔軟に対応できるようになりました。 マイクロサービス環境での認可基盤の統一を検討している方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
.table-of-contents > li > ul > li > ul, .table-of-contents > li:first-child { display: none; } こんにちは、ZOZOの市橋です。2025年6月にリリースされたマッチングアプリである ZOZOマッチ のバックエンド開発を担当しています。本記事では、ZOZOマッチのリアルタイムメッセージング機能を実現するために、AWS AppSyncとGraphQL Subscriptionを活用したアーキテクチャと実装について紹介します。 なお、本記事ではバックエンドのアーキテクチャにフォーカスして解説しますが、ネイティブアプリ側の実装については別記事「 ZOZOマッチアプリのメッセージ機能を支えるFlutter × GraphQLの実装 」で紹介しています。こちらもご確認いただくことでより理解を深められます。 目次 目次 ZOZOマッチシステム全体の構成 AWS AppSyncとは GraphQLとは AppSyncの基本概念 GraphQLスキーマ Resolver なぜZOZOマッチでGraphQLを採用したのか マッチングアプリの要件 WebSocket通信の仕組みと利点 ZOZOマッチにおけるGraphQL Subscriptionの活用 利用シーン1: メッセージ送信と受信通知 概要 処理の流れ 実装内容 VTL Resolverの実装 利用シーン2: 未読件数のリアルタイム同期 概要 処理の流れ 実装内容 利用シーン3: バックエンドからのリアルタイム通知 概要 処理の流れ 実装内容 利用シーン4: チャンネル・メッセージ一覧取得 概要 処理の流れ 実装内容 運用上の課題 学習コスト モニタリングの難しさ ローカル開発環境の制約 まとめ さいごに ZOZOマッチシステム全体の構成 本記事で紹介するAWS AppSyncのZOZOマッチシステム内での位置付けを整理するため、まず全体のシステム構成をご紹介します。 ZOZOマッチのバックエンドシステムはAWS上に構築されています。アプリケーション実行基盤にはECS(Fargate)、開発言語はJava、フレームワークにはSpring Bootを採用しています。 ZOZOマッチのAPIアクセスはCloudFrontを経由して振り分けられます。CloudFrontでは、パスパターンによって振り分け先を制御しており、GraphQL関連のリクエストのみAppSyncにルーティングされます。 REST API : CloudFront → API Gateway → ECS (Java/Spring Boot) GraphQL API : CloudFront → AppSync → データソース(バックエンドAPI) AWS AppSyncとは AWS AppSyncは、AWSが提供するフルマネージドGraphQLサービスです。AWS AppSyncを理解するために、まずGraphQLの基本的な知識について説明します。 GraphQLとは GraphQLは、Facebook(現Meta)が開発したAPI用のクエリ言語およびランタイムです。REST APIの課題を解決するために設計されました。GraphQLには主に3つの操作タイプがあります。 操作 役割 HTTPメソッド相当 Query データ取得 GET Mutation データ作成・更新・削除 POST/PUT/DELETE Subscription リアルタイム通知 WebSocket Queryは必要なフィールドのみを指定して取得できるため、オーバーフェッチングやアンダーフェッチングを防止できます。SubscriptionはWebSocket接続を確立し、サーバー側のイベント発生時にクライアントへ即座にデータを配信します。 AppSyncの基本概念 AWS AppSyncを理解する上で重要な2つの概念、GraphQLスキーマとResolverについて説明します。 GraphQLスキーマ GraphQLスキーマは、APIの仕様を定義する設計図です。型定義と操作定義で構成されます。 GraphQLでは、 ID 、 String 、 Int 、 Boolean などの基本的なスカラー型に加えて、独自のカスタム型を定義できます。型名の後ろに感嘆符( ! )を付けることで必須項目を表現します。感嘆符がない場合は null を許容します。配列型は角括弧 [] で表現します。感嘆符の位置によって以下のように意味が変わります。 [Post] - 配列・要素とともにnull許容 [Post!] - 配列はnull許容、要素はnull不可 [Post]! - 配列はnull不可(空配列は可能)、要素はnull許容 [Post!]! - 配列・要素とともにnull不可 以下は型定義の例です。 type Post { id : ID ! title : String ! content : String ! status : PostStatus ! author : User ! } type User { id : ID ! name : String ! } enum PostStatus { DRAFT PUBLISHED ARCHIVED } 次に、操作定義(Query、Mutation、Subscription)の例を示します。GraphQLでは、 type Query 、 type Mutation 、 type Subscription という特別な型で、クライアントが実行できる操作を定義します。各操作には引数と戻り値の型を指定できます。引数に ! を付けると必須、付けないと任意になります。 type Query { getPost ( id : ID ! ): Post listPosts ( limit : Int ): [ Post ] } type Mutation { createPost ( title : String ! , content : String ! ): Post updatePost ( id : ID ! , title : String , content : String ): Post } type Subscription { onPostCreated : Post @ aws_subscribe ( mutations : [ "createPost" ] ) } Resolver Resolverは、GraphQL操作とデータソースを接続する橋渡しの役割を果たします。AppSyncでは、以下をデータソースとして接続できます。 *1 HTTP API DynamoDB Lambda RDS OpenSearch Amazon EventBridge ZOZOマッチでは、これらの選択肢の中からHTTP APIをデータソースとして採用しています。詳しい理由については後述の利用シーンで説明します。 Resolverでは、VTL(Velocity Template Language)またはJavaScriptを使用してGraphQLリクエストとデータソース間でデータを変換します。本記事ではVTLを使用した実装を紹介します。この変換は、Request Mapping TemplateとResponse Mapping Templateの2つで構成されます。 Request Mapping : GraphQL引数をデータソース固有のリクエスト形式に変換する。パラメータの追加や変換、認証ヘッダーの付与、リクエストメソッドやパスの指定などを行う Response Mapping : データソースのレスポンスをGraphQLスキーマに適合する形式へ変換する。データ構造の整形、エラーハンドリング、不要なフィールドのフィルタリングなどを行う Request Mapping Templateの例は以下の通りです。VTLの条件分岐やユーティリティ関数を使用してデータを変換します。 ## リクエストボディの構築 #set($body = {}) $util.qr($body.put("title", $ctx.args.title)) $util.qr($body.put("content", $ctx.args.content)) ## オプショナルなパラメータの処理 #if($ctx.args.tags) $util.qr($body.put("tags", $ctx.args.tags)) #end ## 認証情報の追加 #set($userId = $ctx.identity.sub) $util.qr($body.put("author_id", $userId)) { "version": "2018-05-29", "method": "POST", "resourcePath": "/api/posts", "params": { "headers": { "Content-Type": "application/json", "Authorization": "$ctx.request.headers.Authorization" }, "body": $util.toJson($body) } } Response Mapping Templateの例は以下の通りです。 #if($ctx.result.statusCode == 200) $util.toJson($ctx.result.body) #else #set($response = $util.parseJson($ctx.result.body)) #if($response.type) #set($errorType = $response.type) #else #set($errorType = "none") #end $util.error("Error response received. statusCode: $ctx.result.statusCode, type: $errorType, message: $response.message") #end なぜZOZOマッチでGraphQLを採用したのか GraphQLとAppSyncの基本概念を整理したところで、本章ではZOZOマッチがなぜAppSyncを採用したのか、その技術選定の背景と理由を説明します。 マッチングアプリとしてのZOZOマッチの要件を整理し、別のアプローチと比較しながら、GraphQL Subscriptionがどのように課題を解決するかを解説します。 マッチングアプリの要件 ZOZOマッチでは、チャットメッセージの送受信や未読件数の同期において、相手からの反応を即座に伝えるリアルタイム性が求められます。また、同時接続ユーザー数の増加に対応できるスケーラビリティも重要な要件です。 別のアプローチとして、クライアントが定期的にサーバーへHTTPリクエストを送信するポーリング方式が考えられますが、この方式には以下のような課題があります。 ポーリング間隔分の遅延が発生する 即時性とコストのトレードオフ(ポーリング間隔を短縮すれば遅延は改善するもののコストが増加し、延長すればコストは削減できるものの遅延が悪化する)が発生する 更新がないケースでも常時ポーリングが発生するため、サーバーリソースの無駄が多い アクティブユーザー数に比例して負荷が増加するため、スケーラビリティに懸念がある これらの課題に対し、WebSocket接続を確立してサーバー側のイベント発生時にクライアントへ即座にデータを配信するGraphQL Subscriptionを採用しました。 WebSocket通信の仕組みと利点 WebSocketは、クライアントとサーバー間で双方向の持続的な接続を確立し、リアルタイムにデータをやり取りするプロトコルです。WebSocketの利点としては以下が挙げられます。 双方向通信 : サーバー・クライアント双方からデータを送信でき、クライアントからのリクエストを待たずにサーバーからプッシュできる 低レイテンシ : 接続確立後はハンドシェイク不要のため、イベント発生から配信までの遅延が最小限 低オーバーヘッド : 接続確立後はHTTPヘッダの送信が不要のため、データ転送量を削減できる スケーラビリティ : 1つの接続で複数のメッセージをやり取りできるため、接続数を抑制できる WebSocketによるリアルタイム通信は以下のような流れで行われます。 まず、クライアントがサーバーに対してWebSocketハンドシェイク(HTTP Upgradeリクエスト)を送信し、WebSocket接続を確立します。この接続は一度確立されると、明示的に切断されるまで持続的に維持されます。その後、サーバー側で何らかのイベントが発生すると、サーバーは接続済みのクライアントに対して即座にデータを送信できます。クライアントはリアルタイムにデータを受信し、画面を更新します。この仕組みにより、クライアントからのリクエストを待つことなく、サーバー側でイベントが発生すると即座にデータを配信できます。 ZOZOマッチにおけるGraphQL Subscriptionの活用 ここまで、GraphQLの基本概念、AppSyncのスキーマとResolver、そしてWebSocketによるリアルタイム通信の仕組みについて説明してきました。 この章では、これらの技術要素をどのように活用しているのか、ZOZOマッチの具体的な実装例を交えて紹介します。 なお、ZOZOマッチではデータソースとしてHTTP APIを利用しており、ビジネスロジックやバリデーション等を一元管理するバックエンドAPIを設けています。弊チームではDDDを採用しており、ドメイン層にビジネスロジックを集約することで、AppSyncのResolverは薄いインタフェース層として保っています。これにより、バックエンドAPIとして独立したテストが可能になり、既存の運用ノウハウも活用できます。 利用シーン1: メッセージ送信と受信通知 概要 ZOZOマッチでは、マッチングした相手とチャットでメッセージのやり取りができます。このチャット機能において、送信したメッセージを相手の画面にリアルタイムで表示するために利用します。 処理の流れ メッセージ送受信の処理は以下の3つのステップで構成されます。 まず、チャット画面を開いた時にクライアントがそのチャンネルの channelId を指定して、AppSyncに対して onMessageModified Subscriptionを開始します。これにより、WebSocket接続が確立され、そのチャンネルのメッセージ更新のみをリアルタイムで受信できる状態になります。 次に、ユーザーがメッセージを送信すると、クライアントから createMessage Mutationが実行されます。AppSyncはHTTP Resolverを通じてECS上のREST APIを呼び出し、APIがデータベースにメッセージを保存してチャンネル情報を更新します。処理が完了すると、AppSyncはMutationの結果をクライアントに返却します。 最後に、AppSyncは createMessage Mutationの実行を検知します。そして、そのチャンネルの onMessageModified Subscriptionを購読しているすべてのクライアント(送信者と受信者の両方)に対して、自動的にメッセージ更新を配信します。これにより、受信者の画面にリアルタイムでメッセージが表示されます。 実装内容 それでは、GraphQLスキーマとVTL Resolverの実装を見ていきます。特に、Subscriptionのトリガー設定と、GraphQLからREST APIへの変換ロジックに注目してください。 GraphQLスキーマ: type Mutation { # メッセージ送信 createMessage ( channelId : String ! , kind : String ! , body : String ! ): MessageModifiedResponse @ aws_cognito_user_pools } type Subscription { # メッセージ変更をリアルタイム受信 onMessageModified ( channelId : String ! ): MessageModifiedResponse @ aws_subscribe ( mutations : [ "createMessage" ] ) @ aws_cognito_user_pools } type MessageModifiedResponse { channelId : String ! action : String ! message : Message ! } type Message { channelId : String ! sentAt : String ! senderUserId : String ! data : String ! } onMessageModified の @aws_subscribe(mutations: ["createMessage"]) ディレクティブでは、 createMessage Mutationが実行されるとトリガーされます。これにより、 channelId で onMessageModified Subscriptionを購読しているクライアントへデータが配信されます。つまり、あるチャンネルでメッセージが送信されると、そのチャンネルを購読しているクライアントへ即座に通知が届きます。 また、このスキーマでは、 @aws_cognito_user_pools ディレクティブによって、Cognito User Poolsで認証されたモバイルアプリのみがこの操作をできるように制限しています。 VTL Resolverの実装 ZOZOマッチでは、VTLを使用してGraphQL MutationをBackend REST APIへのHTTPリクエストに変換しています。なお、バックエンドAPIのエンドポイントURLは、AppSyncのHTTP Data Sourceに事前設定されており、VTLでは resourcePath のみを指定します。 Request Mapping Template: { "version": "2018-05-29", "method": "POST", "resourcePath": "/messages", "params": { "headers": { "Content-Type": "application/json", "Authorization": "$ctx.request.headers.Authorization" }, "body": { "channel_id": "$ctx.args.channelId", "message_body": "$ctx.args.body", "sender_user_id": "$ctx.identity.sub" } } } Response Mapping Template: #if($ctx.result.statusCode == 200) { "channelId": "$ctx.result.body.channel_id", "action": "$ctx.result.body.action", "message": { "channelId": "$ctx.result.body.message.channel_id", "sentAt": "$ctx.result.body.message.sent_at", "senderUserId": "$ctx.result.body.message.sender_user_id", "data": "$ctx.result.body.message.data" } } #else #set($response = $util.parseJson($ctx.result.body)) #if($response.type) #set($errorType = $response.type) #else #set($errorType = "none") #end $util.error("Error response received. statusCode: $ctx.result.statusCode, type: $errorType, message: $response.message") #end 利用シーン2: 未読件数のリアルタイム同期 概要 続いてはチャンネルの未読件数をリアルタイムで同期する仕組みです。メッセージを受信した際に未読バッジが即座に増え、メッセージを読んだ際に未読バッジが即座に消えます。 処理の流れ ここでは、メッセージを既読にして未読バッジが消えるケースを例に説明します。なお、メッセージ受信時に未読件数が増える場合も、同じ onChannelModified Subscriptionを通じて通知されます。 未読件数の同期処理は以下の3つのステップで構成されます。 まず、ユーザーのアプリがAppSyncに対して onChannelModified Subscriptionを開始します。これにより、チャンネル情報の更新をリアルタイムで受信できる状態になります。 次に、ユーザーがメッセージを既読にすると、アプリからAppSyncに対して clearUnreadCount Mutationを実行します。AppSyncはHTTP Resolverを通じてバックエンドのREST APIを呼び出し、データベースの未読件数を更新します。 最後に、AppSyncは clearUnreadCount Mutationの実行を検知します。その後、 onChannelModified Subscriptionを購読しているクライアント(この場合はユーザー)に対して、自動的にチャンネル更新情報を配信します。ユーザーのアプリは受信したチャンネル更新情報をもとに、画面上の未読バッジを更新します。 実装内容 これを実現する実装は以下の通りです。 GraphQLスキーマ: type Mutation { # 未読件数をクリア clearUnreadCount ( partnerUserId : String ! ): Channel @ aws_cognito_user_pools } type Subscription { # チャンネル更新を受信 onChannelModified ( userId : String ! ): Channel @ aws_subscribe ( mutations : [ "clearUnreadCount" ] ) @ aws_cognito_user_pools } type Channel { userId : String ! partnerUserId : String ! unreadCount : Int ! messageContents : MessageContents ! } type MessageContents { sentAt : String ! data : String ! } @aws_cognito_user_pools により、Cognitoで認証されたモバイルアプリのみが clearUnreadCount の実行と onChannelModified を購読できます。 onChannelModified の @aws_subscribe(mutations: ["clearUnreadCount"]) ディレクティブにより、 clearUnreadCount Mutationが実行されます。その後、該当する userId で onChannelModified Subscriptionを購読しているクライアントへチャンネル更新情報が配信されます。 VTL Resolverの実装: VTLを使用して clearUnreadCount MutationをバックエンドREST APIへのHTTPリクエストに変換します。 Request Mapping Template: #set($userId = $ctx.identity.sub) #set($partnerUserId = $util.urlEncode($ctx.args.partnerUserId.trim())) { "version": "2018-05-29", "method": "POST", "resourcePath": "/channels/unread-count/reset", "params": { "headers": { "Content-Type": "application/json", "Authorization": "$ctx.request.headers.Authorization" }, "body": { "user_id": "$userId", "partner_user_id": "$partnerUserId" } } } Response Mapping Template: #if($ctx.result.statusCode == 200) $util.toJson($ctx.result.body) #else #set($response = $util.parseJson($ctx.result.body)) #if($response.type) #set($errorType = $response.type) #else #set($errorType = "none") #end $util.error("Error response received. statusCode: $ctx.result.statusCode, type: $errorType, message: $response.message") #end 利用シーン3: バックエンドからのリアルタイム通知 概要 ZOZOマッチでは、ユーザーが送信するメッセージの一部は審査を経て相手に表示されます。審査を要する場合、ユーザーがチャットメッセージを送信すると、審査通過後にバックエンドサービス(SQS Consumer)がメッセージとチャンネルデータを非同期で更新します。この場合、AppSyncのMutationを経由しないため、Subscriptionはトリガーされません。この問題を解決するため、データ更新後に notifyChannelUpdate Mutation(IAM認証)を別途呼び出すことで、Subscriptionだけを明示的にトリガーしています。 以下の構成図は、この一連の処理に関わるAWSサービスとデータフローを示しています。 処理の流れ チャットメッセージ審査通過時のチャンネル更新処理は以下の4つのステップで構成されます。 まず、SQS Consumerがチャットメッセージ審査通過イベントを取得します。 次に、バックエンドサービス(ECS/Fargate)がメッセージとチャンネルデータをDynamoDBに書き込みます。 その後、バックエンドサービスがAppSyncに対して notifyChannelUpdate Mutation(IAM認証)を実行します。この時点でデータベースの更新は完了しており、Subscriptionのトリガーのみを目的としています。 最後に、AppSyncは notifyChannelUpdate の実行を検知し、 onChannelModified を購読しているクライアントに対して、チャンネル更新情報を配信します。 実装内容 実装はこちらです。 GraphQLスキーマ: type Mutation { # バックエンド専用: Subscription配信トリガー notifyChannelUpdate ( userId : String ! , partnerUserId : String ! ): Channel @ aws_iam # アプリ用: 未読件数をクリア clearUnreadCount ( partnerUserId : String ! ): Channel @ aws_cognito_user_pools } type Subscription { # チャンネル更新を受信 onChannelModified ( userId : String ! ): Channel @ aws_subscribe ( mutations : [ "clearUnreadCount" , "notifyChannelUpdate" ] ) @ aws_cognito_user_pools } type Channel { userId : String ! partnerUserId : String ! unreadCount : Int ! messageContents : MessageContents ! } type MessageContents { sentAt : String ! data : String ! } notifyChannelUpdate Mutationには @aws_iam ディレクティブが付与されており、IAM認証によるアクセスのみを許可しています。これにより、バックエンドサービス(ECS/Fargate)からのみ実行可能となり、モバイルアプリから直接呼び出すことはできません。 Channel 型は利用シーン2と同じ構造で、バックエンドから配信されるチャンネル更新情報の形式を定義しています。 このように、AppSyncのMutationを経由しない処理でも、データ更新完了後に notifyChannelUpdate を呼び出すことで、リアルタイム通知を実現しています。 利用シーン4: チャンネル・メッセージ一覧取得 概要 チャット画面を開いた際に、過去のメッセージ履歴を取得します。この機能では、サーバーからのリアルタイムなプッシュ配信が不要なため、SubscriptionではなくGraphQL Queryを使用します。 GraphQL Queryを使用することで、クライアントが必要なフィールドのみを指定して取得できます。これにより、REST APIで発生しがちなオーバーフェッチング(不要なデータまで取得してしまう)やアンダーフェッチング(必要なデータが不足して追加リクエストが必要になる)を防止できます。また、AppSyncがバックエンドのREST APIをラップすることで、BFF(Backend For Frontend)のような役割を果たし、クライアントにとって最適なデータ形式を提供できます。 処理の流れ メッセージ一覧取得の処理はシンプルな1ステップで構成されます。 クライアントがチャット画面を開くと、AppSyncに対して listMessages Queryを実行します。AppSyncはHTTP Resolverを通じてECS上のREST APIを呼び出し、APIがデータベースから指定されたチャンネルのメッセージ一覧を取得します。取得したメッセージはAppSyncを経由してクライアントに返却され、画面に表示されます。 実装内容 それでは実装を見ていきます。 GraphQLスキーマ: type Query { # メッセージ一覧取得 listMessages ( channelId : String ! , limit : Int ): [ Message ] @ aws_cognito_user_pools } type Message { sentAt : String ! senderUserId : String ! data : String ! } このスキーマでは、 @aws_cognito_user_pools ディレクティブによって、Cognitoで認証されたモバイルアプリのみに listMessages Query実行を制限しています。 VTL Resolverの実装: AppSyncはHTTP Resolverを使用してバックエンドAPIを呼び出し、メッセージ一覧を取得します。 Request Mapping Template: { "version": "2018-05-29", "method": "GET", "resourcePath": "/channels/$ctx.args.channelId/messages", "params": { "headers": { "Content-Type": "application/json", "Authorization": "$ctx.request.headers.Authorization" }, "query": { #if($ctx.args.limit) "limit": "$ctx.args.limit" #end } } } Response Mapping Template: #if($ctx.result.statusCode == 200) #set($messages = []) #foreach($item in $ctx.result.body.items) #set($message = { "sentAt": $item.sent_at, "senderUserId": $item.sender_user_id, "data": $item.data }) $util.qr($messages.add($message)) #end $util.toJson($messages) #else #set($response = $util.parseJson($ctx.result.body)) #if($response.type) #set($errorType = $response.type) #else #set($errorType = "none") #end $util.error("Error response received. statusCode: $ctx.result.statusCode, type: $errorType, message: $response.message") #end 運用上の課題 ここまで、ZOZOマッチにおけるGraphQL Subscriptionの具体的な実装例を紹介してきました。ここからは実際に運用する中で直面した課題と、それらに対する対応について紹介します。 学習コスト GraphQLは、REST APIとは異なるパラダイムであり、新たに概念を学ぶ必要があります。GraphQLのスキーマ設計やResolver実装、Subscriptionの仕組みなど、学ぶべき概念が多く、初期の学習コストが高いという課題があります。特にResolverの実装でVTL(Velocity Template Language)を利用する場合は、独自のテンプレート言語であるため、独特の記法に対して一定の慣れが必要です。 現状では、GraphQL周りの知見が一部のメンバーに属人化している状況です。そのため、今後は有識者が主導して勉強会を開催し、チームメンバー全体で仕組みを把握することを検討しています。 モニタリングの難しさ GraphQLでは、すべての操作が単一のエンドポイントに集約されるため、REST APIで培ったモニタリングの知見を直接的には活かせません。REST APIでは、エンドポイントごとにエラー率やレイテンシをモニタリングすることで影響範囲を迅速に特定できます。一方、GraphQLではエンドポイントが1つしかないため、デフォルト設定ではどのGraphQL操作で問題が起きているのかを特定することが困難です。 この課題に対しては、AppSyncのEnhanced Metricsを有効化することで対応しました。具体的には、以下の3つのレベルでメトリクスを取得しています。 Operation Level Metrics : Query/Mutation/Subscription単位でレイテンシやエラー率をモニタリング Resolver Level Metrics : 各Resolver単位でパフォーマンスを追跡 Data Source Level Metrics : バックエンドAPI呼び出しの状況をモニタリング これらのメトリクスはCloudWatch Metric Stream経由でDatadogに送信され、Operation単位でのパフォーマンス分析が可能になっています。これにより、問題が発生した際に、どのGraphQL操作のどのResolverで問題が起きているのかを特定しやすくなります。 ローカル開発環境の制約 AppSyncはAWSのマネージドサービスであるため、ローカル開発環境の整備に工夫が必要です。LocalStackのUltimateプランなど、AppSyncをローカルで再現できるツールも存在しますが、コストや運用面での制約があるため、ZOZOマッチでは現状これらのツールを採用していません。 そのため、GraphQLスキーマやVTL Resolverの動作を確認するには、実際にAWS環境にデプロイしてリクエストを送信する必要があります。これにより、ちょっとした修正でもAWS環境での確認が必要になるため、手間が発生します。また、開発者ごとに独立したAWS環境を用意することが現状はできていないため、共有の環境を使用せざるを得ず、他の作業と競合する場面もあります。 この課題に対しては根本的な解決策は見つかっていません。軽減策として、可能な限りバックエンドAPIのロジックを充実させ、AppSyncのResolverはシンプルに保つことで、AWS環境への依存を最小限にしています。また、GraphQLスキーマの変更は慎重に行い、事前にチーム内でレビューを実施することで、デプロイ後の手戻りを減らすよう努めています。ただ、最終的にはAWS環境での検証が不可欠であるため、現在も開発サイクルの長さは課題として残っています。 まとめ 本記事では、ZOZOマッチのリアルタイムメッセージング機能を実現するために採用したAWS AppSyncとGraphQL Subscriptionのアーキテクチャと実装について紹介しました。 実際の利用シーンを通じて、Mutation、Subscription、Queryの使い分けや、VTL Resolverを用いたGraphQLとREST APIの変換方法を解説しました。これらの実装により、ZOZOマッチは低レイテンシかつスケーラブルなリアルタイムメッセージング基盤を実現しています。 本記事が、同様のシステム開発を検討している方の参考になれば幸いです。 さいごに ZOZOでは、ZOZOTOWN一本足打法からの脱却を狙い、新規事業にも果敢に取り組んでいます。このような挑戦を一緒に楽しめる仲間を募集しています。ご興味のある方は、採用ページをご覧ください。 hrmos.co *1 : https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/data-source-components.html
アバター
ZOZO開発組織の2025年12月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2025年12月は、前月のMonthly Tech Reportを含む計22本の記事を公開しました。特に次の2記事はとても多くの方に読まれています。ぜひご一読ください。 techblog.zozo.com techblog.zozo.com 協賛 2025年12月は「 AI Engineering Summit Tokyo 2025 」に協賛しました。 technote.zozo.com 登壇 ZOZO.swift #1 12月8日に開催された「 ZOZO.swift #1 」に、ZOZOTOWN開発2部のらぷ( @laprasdrum )、つっきー( @tsuzuki817 )、WEARフロントエンド部のセータ、FAANS部のましょーの4名が登壇しました。 zozotech-inc.connpass.com speakerdeck.com speakerdeck.com speakerdeck.com speakerdeck.com AI Engineering Summit Tokyo 2025 12月16日に開催された「 AI Engineering Summit Tokyo 2025 」に、ブランドソリューション開発本部 本部長の増田がセッションで、CTOの瀬尾( @sonots )がパネルディスカッションで登壇しました。 ai-engineering-summit-tokyo.findy-tools.io speakerdeck.com ai-engineering-summit-tokyo.findy-tools.io Jagu'e'r データ利活用分科会 Looker User Meetup Part 5 12月17日に開催された「 Jagu'e'r データ利活用分科会 Looker User Meetup Part 5 」に、データシステム部の栁澤( @i_125 )が登壇しました。 speakerdeck.com 掲載 エンジニアtype クリスマス&お年玉プレゼントキャンペーンとして、2025年11〜12月に取り組んだ『HHKB×ZOZOTOWN限定アパレル』が商品のひとつとして紹介されました。 ※本記事の公開時点でキャンペーンは終了しています。 type.jp その他 ファッション通販白書 ZOZOTOWNのサービスを開始した2004年から2025年まで、過去20年間にわたって蓄積された9億点以上の購買データをもとに、「 ファッション通販白書 by ZOZOTOWN 」を発表しました。 以上、2025年12月のZOZOの活動報告でした! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、ブランドソリューション開発本部プロジェクト推進部PMOブロックの三谷です。普段はPMOとして、 ファッションコーディネートアプリ WEAR の開発組織が企画を実現する上で発生する様々な課題の解決サポートを行っています。 WEARは2014年のローンチ以来アップデートを繰り返し、様々な機能をリリースしてきました。その中で、1つ1つの案件が大きくなってしまうことがしばしばあり、リリースまでのリードタイムや価値検証のサイクルが長くなりすぎてしまうことがありました。この課題を解決すべく、2024年5月より私たちPMOブロックは開発責任者 @tsuwatch のもと、スクラム開発の導入を推進しました。本記事では、スクラム導入におけるPMOの取り組みをご紹介します! なお、今回ご紹介する内容はWEARのアプリ開発に限った話であり、Webサイトの開発は対象外としています。 目次 はじめに 目次 背景 スクラム開発の導入 WEARの内情に合わせたスクラム体制の構築 チームとロール スクラムマスター 横断的に3チームを俯瞰するスクラムマスター チーム専属でスクラムを進行するスクラムマスター 開発者 スプリントの動き リファインメント 開発〜リリース ツールと運用方法 ストーリーポイント計算の自動化 リードタイム計測の自動化 実運用で発生した課題とその解決策 PBIの方針がチーム内で決まらずスピード感が損なわれる QAのリソース不足によるリリース先送り スクラムを導入した効果 リリーススピードが向上 チームの自己組織化 今後より良い開発チームを目指してPDCAを行なっていくために 質と量を意識した開発 まとめ 背景 これまでWEARでは、ビジネス的な観点やUXの観点などから様々な要件が盛り込まれて工数が膨れ上がり、最終的な成果物によって得られる効果に見合わないほど工数が大きくなってしまうことが多々ありました。そこで、2024年5月のフルリニューアルが完了したことを機に、その後の機能開発は最低限の要件に絞って早くリリースし、ユーザーの反応を見て次の手を打つスクラム開発を導入しました。また、これまでの開発では企画〜リリースまでの各プロセスのリードタイムやリソースの効率などの各種メトリクスが可視化されておらず、チームの成長や積み上げ、生産性が向上しているかがわからない状態でした。スクラムの導入と共に、これらのメトリクスを可視化する仕組みも取り入れ、チームが振り返りながらより早くユーザーに価値を届けていける体制を構築しました。 スクラム開発の導入 WEARの内情に合わせたスクラム体制の構築 WEARの開発には、「PdM」「エンジニア」「デザイナー」「QA」など様々なロールのメンバーが関わっています。 その中でも特にWEARひいてはZOZO社(以下、ZOZO)の開発において特徴的と言えるロールが、「デザイナー」です。ZOZOではZOZOTOWN、WEAR、FAANS、ZOZOマッチなど様々なサービスを展開していますが、それらのユーザー体験はすべてデザイナーによってデザインされています。また、各サービスは独立してデザインされるのではなく、デザイナー組織のディレクター(以下、便宜上「デザインディレクター」と呼びます)が各サービスを統括して全体の世界観を保っています。そのため、WEARのデザインにおいてもデザインディレクターの承認を得る必要があり、そのこともスクラム体制に盛り込む必要がありました。 また、もう1つの特徴として「QA」がエンジニアから独立している点があります。エンジニアだけでテストを行う会社もあると思いますが、ZOZOではQA組織がエンジニアと独立して存在しており、開発が完了した成果物はQAによって検査することで品質を担保しています。この点もスクラム体制に盛り込みました。 このようなWEARの事情を考慮して構築したスクラム体制を以降で紹介していきます。 チームとロール WEARではスクラム導入以前から、個別のKPIを立てた3つの開発チームが存在していました。スクラム導入ではこのチームをそのまま引き継ぎ、3チームそれぞれ個別のバックログを持った1つのスクラムチームとなるようにしました。1つのチームのメンバー構成は、主に以下の通りです。 ロール 人数 主な役割 PdM 1名 スクラムではプロダクトオーナーを担います デザイナー 1〜2名 デザインの作成を担います エンジニア BEエンジニア 2〜3名 BEの開発を担います iOSエンジニア 2〜3名 iOSの開発を担います Androidエンジニア 2〜3名 Androidの開発を担います QA 1名 品質管理を担います アナリスト 1名 ユーザーの行動を分析し、チームの意思決定のサポートをします また、開発責任者(1名)とPMOが3チームを横断的に支援する構成にしています。上記の各ロールをスクラムの3つの役割である「開発者」「プロダクトオーナー」「スクラムマスター」に割り振ってスクラムを行っています。以降では、その中でもWEAR特有の構成である「スクラムマスター」と「開発者」について具体的に説明します。 スクラムマスター 一般的なスクラムと比べると特殊かもしれませんが、以下のようにスクラムマスターの役割を2ロールで分担して担っています。 横断的に3チームを俯瞰するスクラムマスター スクラム導入期には、3チームでの一斉導入にあたって本記事で紹介している体制の検討や基盤となる仕組みやルールの整備、メンバーへの教育など、様々な導入支援を行う必要がありました。この役割は、私の所属するPMOブロックが担いました。私を含めブロック内でスクラム開発に詳しい者がいなかったため、Scrum Allianceの「認定スクラムマスター(CSM)」を取得して学習したり、社外のスクラムコーチに相談したりして進めました。様々な観点を検討した上で、開発に関わるメンバー全員へスクラム導入のメリットが伝わるよう説明をしました。 スクラム導入後は、実運用で発生した課題に対する解決案の検討やそれに伴う仕組みの管理、スクラムチームがより良い開発をしていくための提案などを行っています。スクラムチームが直面する課題には、チーム外のステークホルダーが関わっていたり、複雑なため解決策を時間をかけて整理する必要があるなど、チームが単独で解決するにはコストのかかるものがあります。そういった課題に対して、実際に手や頭を動かして解決に導く役割を担っています。後述する、 実運用で発生した課題とその解決策 や 今後より良い開発チームを目指してPDCAを行なっていくために も、私たちPMOブロックが担った課題解決の一例ですので、ぜひご覧ください! チーム専属でスクラムを進行するスクラムマスター 今回ゼロからスクラムを始めるにあたり各チームには進行役のスクラムマスターが必要でしたが、PMOはスクラム以外の課題も担当しており、さらに人数も不足していたため、アサインできませんでした。そのため、各チームにはスクラム導入以前から開発進行を管理する「チームリーダー」がエンジニアから1名設定されていましたが、彼らにスクラムマスターの役割もお願いしました。スクラムイベントの進行やチーム改善などを行ってもらっています。 また、PMOとチームリーダーは毎週定例会を実施しています。そこでは、各チームの課題相談やナレッジの共有、必要に応じて全体の共通ルールの検討なども行い、改善活動を行っています。 開発者 エンジニア、デザイナー、QAが担います。WEARではデザイン、開発、QAがそれぞれ専門のロールに分かれているため、スクラムチームではそれぞれのロールが専任で担当します。ただし、役割やタスクを綺麗に線引きするのではなく、互いに補完し合ってチームとして必要なことを全員で実施していくようにしています。 エンジニアについて補足すると、WEARはiOS、Androidで同様のアプリを提供しているため、機能開発を行う際は基本的に両OSで同じ機能を同時に開発します。そのため、1つのチームに両OSのエンジニアがそれぞれアサインされています。 また、WEARの特徴的なロールとして先述したデザイナーとQAもスクラムチームに含めて開発しています。 スプリントの動き 各チームが2週間のスプリントで開発し、スプリントプランニング、デイリースクラム、スプリントレビュー、レトロスペクティブ、リファインメントを実施しています。その他、一般的なスクラムと大きく差がない部分は割愛し、以降ではWEAR特有の仕組みをメインで紹介します。 リファインメント 毎週定期で開催(例:1時間 × 2回)し、次のスプリント以降で開発着手したいプロダクトバックログアイテム(以下、PBI)を着手できる状態(Ready)にしています。 デザインの作成もReadyの条件に含めていて、リファインメントで完了させています。先述したように、デザインの完了にはデザインディレクター承認が必要ですが、開発着手までにとる構成にしています。 また、リファインメントでは、バグとなりうる仕様をあらかじめ排除する等の目的で、QA観点での仕様検討も行っています。 開発〜リリース リリースタイミングは定期で定めていて、スプリント終了後の翌営業日としています。開発が完了したPBIに対して順次、デザインレビュー・QAを行い、スプリント内でデザインレビュー・QAまで完了したものを次のリリースタイミングでリリースしています。以下の図は、いくつかのPBIの進行例を示しています。その中でも【A】と【D】のように、1週目で開発を完了させて2週目でデザインレビュー・QAを完了させることで、1スプリントでリリースできることを目指してPBIのボリュームを調整するようにしています。 ツールと運用方法 WEARでは、スクラムの進行にJiraを使用しています。Jiraにはバックログやスプリントの管理など、オンラインでスクラムを行う上で必要な機能が一通り備わっています。ここでは一般的なJiraの機能の紹介は割愛しますが、WEAR特有の使い方をしている部分を紹介します。 ストーリーポイント計算の自動化 PBIで必要な作業を各ロールがJiraの作業項目を作成して管理していますが、各作業のストーリーポイントを同名のフィールドにつけることで、スプリントプランニングやベロシティ計測に使用しています。WEARでは、1つの作業項目(親)をさらに細かい粒度のサブタスク(子)に分割して作業進捗を管理しており、サブタスク一つひとつにもストーリーポイントをつけています。 作業項目の粒度の例:〇〇画面のUI改修 サブタスク粒度の例:ボタンのレイアウト追加、〇〇処理にログを追加 Jiraの仕様では、ベロシティに計上されるストーリーポイントは作業項目のもののみで、サブタスクのストーリーポイントはベロシティに計上されません。そのため、親の作業項目にサブタスクのストーリーポイントの合計値を計算してつけなければなりませんが、デフォルトでは、サブタスクを追加やストーリーポイントを修正する度に手動で計算する必要があります。WEARでは、Jiraの「自動化」機能を用いて、サブタスクのストーリーポイントが変更されたのをトリガーに自動で親の作業項目とストーリーポイントを同期するシステムを組み込んでいます。 リードタイム計測の自動化 WEARでは、スクラムにおける各工程のリードタイムを計測して振り返りを行っています。作業項目やサブタスクの「実施開始日」「実施終了日」というフィールドに、作業した実際の日付を入力することで計測していますが、通常は手動で入力する必要があります。こちらもJiraの「自動化」機能を用いて、作業項目やサブタスクのステータスが変更されたのをトリガーにして自動で実施開始日・実施終了日を入力するシステムを組み込んでいます。 実運用で発生した課題とその解決策 PBIの方針がチーム内で決まらずスピード感が損なわれる PBIの方針は、前述の WEARの内情に合わせたスクラム体制の構築 で説明したように、スクラム外のデザインディレクターの承認を得る必要があります。そのため、チームのリファインメント内で決定ができず別枠でMTGを設けてデザインディレクターを含めて検討することになりましたが、なかなか予定が合わず、決定までに時間がかかってしまうことがありました。解決策として、デザイナーがデザインディレクターとの定期MTGにて相談した上で次回のリファインメントに持ってきてもらうようにしました。また、PBIの方針案を固めてからディレクターに相談するのではなく、検討の初期段階から相談するようにして後から方針が再検討になることを防ぎました。これによって、リファインメントのスピードが向上しました。 QAのリソース不足によるリリース先送り QAのタイミングについて スプリントの動き - 開発〜リリース で紹介しましたが、もともとはPBIごとではなくそのスプリントで開発完了したものをスプリントの後半1週間でまとめてQAをする運用でした。そのため、開発完了するPBIが多いスプリントではQAリソースが足りず、開発完了していてもリリースは次のスプリントに先送りというもったいない事態になることがありました。解決策として、QAでのバグ起票の多さがQAコストにつながっていたので、レトロスペクティブにバグを振り返る時間を作り、ユニットテストレベルのバグは開発段階で潰すことを目指しました。また、QA開始のタイミングをQA担当者に調整してもらい、PBIの開発完了タイミングでPBIごとに並行してQA開始できるようにしてもらいました。イメージは スプリントの動き - 開発〜リリース の図を参照ください。結果として、QAのリソース不足は解消され、リリースが先送りになることがなくなりました。 スクラムを導入した効果 リリーススピードが向上 スクラム導入以前はリリーススピードを計測していなかったため正確な評価ではありませんが、以前の体制で実施した一定期間の案件実施数と比較すると、案件実施数が増えました!また、PdMからも明らかにリリース件数が増やせたとコメントをいただきました! チームの自己組織化 定期的なスクラムイベントの実施により、チーム内のコミュニケーションが円滑化し関係値も構築されました!また、PdMやアナリストからユーザーの行動やチームKPIの進捗が共有されることで、目標に対するチーム全体の意識が高まり、チームで振り返り→その後のアクションを立てられるようになりました! 今後より良い開発チームを目指してPDCAを行なっていくために 質と量を意識した開発 前述の通り、リリーススピードが向上し、従来よりもたくさんのアウトプットをユーザーへ届けられるようになりました。質も当然意識しつつ、ユーザーの反応を見ながら開発を進めていましたが、まずはアウトプットの量を増やすことを目標としていました。その点は一定達成できたため、次のステップとしては、質と量の両方を意識して効率よく大きなアウトカムを生み出すことを目指しています。具体的な取り組みとしては、PBIの期待度をスコア化して期待度と工数のバランスを見てチームの動きを改善していく仕組みを検証しています。今後、良い仕組みが確立できたら別の記事にて紹介します! まとめ 本記事ではWEARのスクラム導入について、私たちPMOブロックが行ってきた支援について紹介しました。一筋縄ではいかない課題がたくさんありましたが、一つひとつ検討を繰り返して今のスクラム体制を築くことができました。開発チームに同じような課題を持たれている方がいれば、ぜひ参考にしてみてください! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.entry .entry-content .table-of-contents > li > ul { display: none; } 1. はじめに 検索基盤部 検索基盤ブロックの佐藤( @satto_sann )、岡田( @ryokada33 )、SRE部 検索基盤SREブロックの富田( @kei_gnu622 )です。 ZOZOTOWNアプリ用に検索機能を提供していたオンプレサーバー上のレガシーなAPIを、約1年かけてクラウド環境へ全面リプレイスしました。 このリプレイスプロジェクトでは スパイラル開発の導入 や 開発初期からの環境整備 といった工夫を積み重ねることで、当初のスケジュールどおり移行を完了しています。 また、副次的な効果として、 検索速度が約2倍に向上 しユーザー体験の改善にもつながりました。 本記事では、 大規模リプレイスを円滑に進めるために実践したプロジェクト運営の取り組み を、以下の流れで紹介します。リプレイスをはじめとした、ソフトウェアやマイクロサービス開発プロジェクトの参考になれば幸いです。 【2章】リプレイスプロジェクトの概要 今回のプロジェクトが直面した技術的・運用的な難しさと、限られたリソースの中でどのような制約があったかを説明します。 【3章】なぜスパイラル開発なのか 過去のWeb版リプレイスで直面した課題を振り返り、その学びから今回スパイラル開発を選択した理由と背景を解説します。 【4章】スパイラル開発を実践するための4つの工夫 2週間サイクルでの開発、mainブランチへの積極的なマージ、機能フラグの活用、開発初期からの環境整備という4つの実践ポイントを紹介します。 【5章】スパイラル開発を支えたテストプロセス 開発サイクルに組み込んだテスト戦略と、品質を維持しながらスピードを保つために設計したテストプロセスの全体像を説明します。 【6章】デプロイ基盤とリリース戦略 頻繁なリリースを支えた自動デプロイの仕組みと、本番環境への影響を最小限に抑えたリリース戦略を紹介します。 目次 1. はじめに 目次 2. リプレイスプロジェクトの概要 2.1. プロジェクトの難しさ 2.2. 制約と方針 3. なぜスパイラル開発なのか 3.1. 過去のリプレイスで直面した課題と学び 3.2. スパイラル開発の選択 4. 大規模リプレイスをスパイラル開発で回すポイント 4.1. 2週間ごとに「動くもの」をつくる 4.2. mainブランチへの積極的なマージ 4.3. 機能フラグを使って安全に本番リリース 4.4. 外部サービス連携部分のモック化 5. スパイラル開発を支えた「テストプロセス」 5.1. テストを開発のサイクルに組み込む 5.2. スパイラル開発のために設計したテストプロセス全体像 5.3. テストプロセスのポイント 5.3.1. E2E動作確認:実装直後から「プロダクトとしての挙動」を確かめる 5.3.2 レスポンス比較リグレッションテスト:守るべき仕様の破壊を防ぐ“自動チェック” 5.3.3. レビュー会:ユーザー目線での齟齬をスプリント単位で解消する 5.4. 成果と学び 6. デプロイ基盤とリリース戦略 6.1. 安全かつ効率的なデプロイの仕組み 6.2. 非機能面の品質確保 6.2.1. 負荷試験・障害試験を実施 6.2.2. SRE部のテックリードによるリリース前レビュー(プロダクションレディチェック) 6.2.3. リリース前に開発者とSREで監視指標の認識合わせ 6.3. リプレイスAPIへの安全な切り替え 6.3.1. 段階的なトラフィック切り替え 6.3.2. 即座に切り戻せる安心感 7. おわりに ちなみに 紹介 2. リプレイスプロジェクトの概要 本題へ入る前に、今回のリプレイスプロジェクトの概要を紹介します。ZOZOTOWNではWeb・アプリともに継続的にリプレイスを進めています。 techblog.zozo.com 今回のプロジェクトでは、ZOZOTOWNアプリに検索機能を提供するVBScriptで書かれたレガシーなオンプレAPI(以下、レガシーAPI)をリプレイスしました。リプレイス先は、クラウド環境で稼働するSpring Boot(Java)で構築されたAPI(以下、リプレイスAPI)です。 2.1. プロジェクトの難しさ このプロジェクトの難しさを少しお伝えすると、以下3つの技術的・運用的な難しさがありました。 課題1:膨大な検索条件の組み合わせ 検索に関わるAPIパラメータは70項目以上にのぼります。これらは自由に組み合わせられるため、考慮すべきパターンは「組み合わせ爆発」と言えるほど膨大です。あらゆる条件の組み合わせに対して正しく結果を返せることが求められました。 課題2:高トラフィック環境での安定稼働 検索はZOZOTOWNのほとんどのユーザーが日常的に利用する主要な機能であり、常に高トラフィックに晒されています。高負荷な環境下であっても安定した動作やサービスを止めることなくリプレイスすることが必須条件でした。 課題3:レガシーコードの移行 十数年にわたり機能追加を重ねてきた数万行のVBScriptコードを、Spring Boot(Java)へ移行するという大規模な技術的チャレンジがありました。レガシー資産を正確に読み解きつつ、新しいアーキテクチャへ落とし込む作業は想像以上に複雑で、多方面での工夫と知見が求められました。 2.2. 制約と方針 ソフトウェア開発プロジェクトでよく知られる「 アジャイルの鉄の三角形 」では、以下3要素がトレードオフの関係にあるとされています。 スコープ(範囲) スケジュール(期間) リソース(品質・コスト) 今回のリプレイスプロジェクトでは、この三角形の各要素について以下のような制約がありました。 スケジュール ZOZOTOWNの事業計画や、オンプレサーバーの縮退スケジュールを含めたリプレイス全体のロードマップとの兼ね合いから、期限を大幅に延長することは困難な状況でした。 スコープ “リプレイス”という特性上、すでに稼働しているZOZOTOWNアプリの「検索機能」を過不足なく実装する必要がありました。また、検索の機能全体を見渡すと、ネイティブアプリ、BFF(Backend For Frontend)、検索エンジンなど、複数のレイヤーが関わっています。しかし、リプレイス期間の肥大化を抑えるため、今回のスコープはBFFレイヤーのAPIリプレイスのみに絞り込みました。具体的には、ネイティブアプリ側のコードは一切変更せず、APIのインタフェース(リクエスト/レスポンス形式)も原則として維持する方針としました。 リソース 5〜7名体制と限られた開発リソースの中での開発となりました。 このように、スケジュールとスコープが固定された状態で、限られたリソースを最大限活用しながら品質を確保することが求められました。 3. なぜスパイラル開発なのか 過去のリプレイスプロジェクトでは、属人化や結合の遅れによって手戻りが多発し、大規模リプレイスの難しさを痛感しました。 今回も扱う範囲は広く複雑で、初期に全体計画を固めることが難しい一方、スクラムのような価値検証型の進め方とも相性が良くないと考えていました。そこで、実装する機能単位を区切って品質を積み上げられるスパイラル開発が最も適していると判断しました。本章では、その判断に至った背景と学びを簡潔に整理します。 3.1. 過去のリプレイスで直面した課題と学び 私たちは過去に、Web版ZOZOTOWNの検索結果ページの大規模なリプレイスを実施しました。(Web版のZOZOTOWNとアプリ版のZOZOTOWNの検索機能を担うロジックは別のマイクロサービスで動いていました)。 当時のリプレイスでは、度重なる手戻りが発生し、全体として大きく工期が延びてしまいました。最終的には無事リリースに至りましたが、その過程で「大規模リプレイスの難しさ」を強く実感する結果となりました。 課題1: 実装する機能単位での分担による属人化と認識齟齬 過去のリプレイスでは、機能単位(スコープ)ごとに設計からリリースまで全期間で担当者を割り当てる形で進めていました。この方式により、各担当者が自分の範囲に強い責任感を持って開発を進められた一方で、機能ごとの仕様理解、実装方針や設計思想が分断され、チーム全体での整合性を保つことが難しくなりました。 その結果、部分的には完成していても全体として連携できない状態が生まれ、後任者や他メンバーへの知識共有が難しくなるなど、属人化が進行しました。また、仕様の整合性確認やレビューにも余計な時間がかかる要因となりました。 課題2: フロントエンドとの結合の遅れによる課題 過去のリプレイスでは、チームが大きかったこともありバックエンド側の実装がある程度進んでから、結合テストを行う方針でした。フロントエンドやクライアントアプリとの結合は後回しになっていました。しかし、API設計段階でのレビューやモック連携は行っていたものの、終盤の結合タイミングで、想定外の仕様差分や不具合が一気に顕在化しました。 実際に動作する環境でのユーザーストーリーベースのテストをおこなった際に、バックエンドとクライアントの間で整合性を欠き、検索結果の挙動や表示内容に不具合が多く見られました。 E2E(エンドツーエンド)の動作を通して初めて明らかになる不具合が多く、設計段階では検知できなかった差分対応が後半に集中しました。その結果、仕様調整や修正対応がテストフェーズの負担を増大させる要因となり、リリース準備全体に遅延を引き起こしました。 学び これらの経験を通して、チームは次の3点を学びました。 早い段階での結合と検証が重要であること 実際の動作環境で早期に結合テストを行い、ユーザーストーリー単位で整合性を確認することが品質向上につながると学びました。 プロダクトとしての品質検証は初期の段階からプロセス全体に組み込むべきこと テストを後工程ではなく、開発の反復サイクル内に取り込むことで安定した品質を保てると考えました。 担当機能を固定しすぎないこと 長期間同じ機能の開発を担当し続けると属人化や認識齟齬が生じやすくなるため、メンバー間で担当する機能を固定せず、知識を共有しながら進めることが重要だと感じました。 3.2. スパイラル開発の選択 前回のリプレイスでは、広い範囲を一度に開発・結合しようとしたことで、認識齟齬や手戻りが増え、チーム全体の負荷が高まってしまいました。 今回のリプレイスは「新しい検索体験をつくる」プロジェクトではなく、既存検索の同等挙動を新システム上で再現するという性質を持つプロジェクトです。要件自体は明確でしたが、ZOZOTOWN20年の歴史を支えてきた大規模で複雑なコード群を扱う必要があり、全体を見通して計画を立てにくい構造的な複雑さが存在していました。 ※このような状況は、クネビンフレームワークにおける「Complex(複雑)な領域」に近い状態といえます。 .w70p .figure-image img { width:70%; } Tom@thomasbcox.com , CC BY-SA 4.0 , via Wikimedia Commons そのため、ウォーターフォール型のように初期段階で全体を計画しきる進め方では対応が難しく、一方で、スクラムのように仮説検証を繰り返す新規事業型のアプローチとも性質が異なっていました。そこで私たちは、 スパイラル開発 を採用することにしました。 スパイラル開発は、チームで集中して実装する機能を期間で明確に区切り、その期間でリリース品質まで集中して仕上げるサイクルを繰り返す開発手法です。 スクラムが「価値検証(アウトカム)を中心に、仮説を試しながら仕様を磨く」手法であるのに対し、スパイラル開発は「あるべき挙動が明確な機能を、確実に積み上げていく」ことを目的としたアプローチです。 今回のリプレイスプロジェクトでは、スクラムが得意とする価値検証型のサイクルを必須とせず、仕様の再現性と高品質なアウトプットを積み重ねるスパイラル開発のほうが適していると判断しました。 下表に、各手法と本プロジェクトとの相性をまとめます。 手法 特徴 今回のプロジェクトとの相性 ウォーターフォール 設計からリリースまでを一方向に進める。計画が明確な反面、変更や手戻りに弱い。 ✗ 既存仕様の再現範囲が広く複雑。終盤の結合で手戻りが発生しやすい。 スクラム アウトカムを中心据えて、機能単位の短期開発と価値検証を繰り返す △ 要求が明確で仮説検証を必要としないため、スプリント単位での価値検証が適さない。 スパイラル開発 設計・実装・テストを反復し、段階的にシステムを完成させる ◯ 既存検索の同等挙動を機能単位で再現し、品質を積み上げる進め方として適している。 社内の別の大規模案件でスパイラル開発を採用していたことも、選択を後押ししました。 最終的には予定していたすべての仕様をリプレイスできました。ただし、この仕組みを導入した当初は別の意図もありました。スケジュールの制約で対応の難しい仕様が生じた場合に、安全に後ろ倒しやドロップができる状態を作ることです。 4. 大規模リプレイスをスパイラル開発で回すポイント 本章では、スパイラル開発を円滑に進めるために私たちが実践した以下4つのポイントについて紹介します。 2週間ごとに「動くもの」を作る mainブランチへの積極的なマージ 機能フラグを使って安全に本番リリース 開発初期からアプリ上で動かせる環境整備 4.1. 2週間ごとに「動くもの」をつくる 前述の通り、スパイラル開発の核心は短いサイクルで「 実際に動く成果物 」を完成させ、チーム全体で動作確認を繰り返すことです。 実際に動く成果物=検索機能 として、私たちはプロジェクト全体で実装しなければならない機能を複数のスプリントに分割し、各スプリントで以下のように検索機能を順番に実装していきました。 スプリント1: ショップ検索 スプリント2: カテゴリ検索 スプリント3: 価格検索 ... 各スプリントでは、対象機能の設計から実装、テストまでを2週間で完了させることを目標としました。ここで重要なのは、「コードを書き終わる」ことではなく、「アプリ上で実際に動作する状態にする」ことをゴールに設定した点です。 この進め方によって、以下のメリットを得られました。 全レイヤーに関わることで属人化を防止 ひとつの機能を完成させるには、APIのプレゼンテーション層やビジネス層、データアクセス層など全レイヤーに手を入れる必要があります。また、スプリントごとに担当機能を入れ替えながら共同開発を進めることで、特定のメンバーだけが詳しい領域を作らず、チーム全体で知識を共有できました。 成果物が明確になり、進捗が見える化される 各スプリントの終わりには「使える検索機能がひとつ増える」という目に見える成果が生まれます。これにより、スケジュールに対して順調なのか遅れているのかを早い段階で把握でき、必要に応じて計画を調整することも可能になりました。 4.2. mainブランチへの積極的なマージ スパイラル開発を円滑に進めるうえで、ブランチ戦略も重要なポイントでした。私たちは、開発中の機能であっても積極的に 本番環境で動作しているコード=mainブランチ へマージする方針を採用しました。 なぜ開発用ブランチを長期間維持しないのか ZOZOTOWNでは、今回のリプレイス以外にも日々様々な開発が並行して進められています。このような状況で、リプレイス用の統合ブランチを長期間維持し続けると、mainブランチとの差分が大きくなることは明確でした。実際、過去のWeb版検索リプレイスでも、終盤の統合作業で予想外の時間を取られた経験がありました。 スプリントごとにmainへマージする運用 今回のリプレイスでは、各スプリントで実装した機能が一定の品質に達したら、速やかにmainブランチへマージするルールを設けました。これにより、並行して進む他の開発との差分を最小限に抑え、常に最新のコードベースで作業できる状態を維持しました。なお、「一定の品質に達した」ことをどのように担保したかについては、次章のテストプロセスで詳しく紹介します。 振り返りで見えた成果 プロジェクトの振り返りでも、以下のようなポジティブな声が上がっており、開発効率に大きく貢献していることが分かりました。 「ベースブランチへのマージを定期的におこなったので、同期負荷がWeb版リプレイスに比べて格段に下がった」 「他チームとのコードに関する調整がほとんどいらなかった」 4.3. 機能フラグを使って安全に本番リリース 開発途中の機能をmainにマージすることには「本番環境に影響を与えないか」という懸念が伴います。mainブランチへの積極的なマージを可能にしたのが、機能フラグ(Feature Flag)の活用です。 機能フラグの基本的な考え方 機能フラグとは、コード上に実装された新機能の有効・無効を、設定値によって切り替えられる仕組みです。機能フラグがOFFの状態では、新しいコードが本番環境に存在していても、実際のユーザーリクエストには影響を与えません。 機能フラグOFFで本番リリース 私たちは、開発中の機能をすべて「フラグOFF」の状態でmainにマージし、本番環境へのデプロイまでしていました。これにより、「コードは本番に乗っているが、機能としてはまだ有効化されていない」という状態を実現しました。 なお、フラグOFFの状態で、ユーザーのリクエストによってリプレイスコードにアクセスされた場合は、あえて明示的にエラーを返す仕組みにしました。エラーが発生した場合はアラートが発報される監視体制は整備済みのため、開発者が気づかないうちに未完成なコードが利用されてしまう事態を防止しました。 4.4. 外部サービス連携部分のモック化 機能フラグにより安全にコードを本番環境へ配置できるようになりましたが、開発段階で「実際に動くもの」を作るためには、もう1つ重要な課題がありました。それが、外部サービスとの連携です。 リプレイス後の検索機能は、複数のマイクロサービスと、それらが提供する50以上のエンドポイントに依存しています。商品情報、ショップ情報、お気に入り情報など、検索結果の表示に欠かせないデータは、すべてこれらのサービスから取得しています。 しかし、開発初期からすべての外部サービスに接続して開発を進めるのは、コストが高く現実的ではありません。 そこで、実装がまだ完了していない部分の連携にはモックを用意し、まずはAPI全体として「ひと通り動く」状態を早期に作りました。 この仕組みにより、スプリントごとに新しく実装した機能を、実際の端末からすばやく動作確認できるようになりました。これらを活用したテストプロセスについては、次章で詳しく説明します。 5. スパイラル開発を支えた「テストプロセス」 本章では、検索リプレイスを成功へ導いた要因の1つである「テストプロセス」について解説します。スパイラル開発において、複雑な検索機能を段階的に実装しながらも品質を維持できた要因、開発リズムにテストを組み込むための具体的な仕組み、さらにそれによって得られた成果と学びを、実践的な視点で紹介します。 5.1. テストを開発のサイクルに組み込む スパイラル開発では、短いサイクルの中でリリースを繰り返しながら品質を高めていくことが求められます。各スプリントで異なる関心領域を扱うなかでも、既に実装済みの部分も含めた品質を維持し続ける必要があります。 3章で紹介した過去のリプレイスプロジェクトのような「開発の後工程としてテストを行う」進め方では、十分な品質とスピードを両立することは困難でした。そこで私たちは、プロジェクト初期からテストを開発プロセスの中心に位置づけ、開発・検証・改善を同じリズムで進める体制を整えました。 5.2. スパイラル開発のために設計したテストプロセス全体像 今回のリプレイスは複雑な検索ロジックを扱う一方で、スプリントごとに実装する機能を区切りながら実装を進めます。そのため、部分ごとの開発を進めつつも、全体の品質を破綻させない状態をどのように作るかが重要なポイントとなりました。 これら背景を踏まえ、私たちは"いつ・何を・どの粒度で検証するか"を明確にした段階的テストプロセスを設計し、開発の初期段階から導入しました。 以下の表は、実際に運用していたテストプロセスをフェーズ別にまとめたものです。 テストグループ 実施タイミング テスト内容 目的 開発中テスト 開発中に随時・マージ前CI ユニットテスト コードレベルの正しさを即時に確認。境界値・異常系までカバーし、仕様の確認 開発中に随時 アプリE2E動作確認 実機/エミュレータを使い「プロダクトとして提供する形」で動作確認し、UI/UXレベルの齟齬を確認。 スプリントごとのテスト 2週間に1回 アプリE2E結合テスト スプリントで実装した範囲の結合テストを実施。 2週間に1回 新旧APIレスポンス比較リグレッションテスト 上記結合テストのケースについて、自家製自動APIテストツールを用いてリプレイス前後で比較し、守るべき仕様が維持されているか確認 2週間に1回 レビュー会(モンキーテスト) 成果物をチーム全員で俯瞰し、ユーザー視点で動作の違和感や齟齬を調整する リリース前テスト 全機能の開発完了後 受入結合テスト 開発チームによる、最終コードでの実機テスト。「実装したことがすべてできているか」を確認 全機能の開発完了後 QAリグレッションテスト 第三者QAチームによる、ユーザーストーリーに基づいた網羅的な品質確認 全機能の開発完了後 負荷試験 高負荷を想定した本番耐性の確認 5.3. テストプロセスのポイント プロセスの中でのポイントは、以下の3つのプロセスです。 開発中のE2E動作確認 新旧APIレスポンス比較リグレッションテスト レビュー会(モンキーテスト) 以下では、それぞれの役割と効果を説明します。 5.3.1. E2E動作確認:実装直後から「プロダクトとしての挙動」を確かめる 開発フェーズでは、開発者が実装したコードをエミュレータまたは実機から直接操作し、ZOZOTOWNアプリ上での実際のユーザー体験としてどう動作するかを随時確認できるような環境を整備しました。 検証端末の全メンバーへの配布 Android端末とiPhoneを開発メンバー全員に配布し、誰でもすぐに実機で動作確認できる体制を整えました。 ローカル実行のAPIと実機アプリの接続環境の整備 開発者がローカルで起動したAPIに対して、ZOZOTOWNアプリ(インハウス版)から直接リクエストを送れるようにしました。これにより、開発しながらアプリ上で挙動を気軽に確認できるようになりました。 このプロジェクトの実装レイヤーはバックエンドの開発ですが、単なる「APIが正しく返るか」を見るテストにとどめません。画面遷移・検索条件の適用・表示ロジックなど、“実際のユーザーが触れる状態”の挙動を初期段階から検証することが目的でした。 5.3.2 レスポンス比較リグレッションテスト:守るべき仕様の破壊を防ぐ“自動チェック” 今回のリプレイスでは、「旧サービスと同じ結果が返ること」が絶対条件でした。そのため、旧APIと新APIのレスポンス差分を自動比較できる自家製ツールを作成しました。スプリントごとに蓄積してきたすべてのテストケースを用いてリグレッションテストを実施しました。 特に効果が大きかったのは、 「スプリントごとに異なる機能を開発しても、全体として同じ振る舞いであることを機械的に保証できた」 ことです。 このプロセスを継続的に運用した結果、最終的にはテストケースが約1,000件まで蓄積され、それらを高頻度で実行できる状態を構築しました。その過程では、開発中に70件以上のリグレッションを検出しており、この自動チェックの有効性を十分に示す結果となりました。 5.3.3. レビュー会:ユーザー目線での齟齬をスプリント単位で解消する 2週間に1度のレビュー会では、開発チームに加え、チーム外の関係者もアプリでの挙動を実際に確認し、 成果物をユーザーの視点で体験する ことを重視しました。 レビュー会の目的は、単なる「動作チェック」ではなく、認知のズレをなくすことにありました。 デモを通じてチームの認識をそろえる 「実装者の意図」と「ユーザーが体験する挙動」が一致しているか 想定していない分岐・遷移がないか また、副次的な効果として「徐々に完成していく様子を共有できるためモチベーションが維持できる」「Tipsが共有されチームとしての開発効率が良い」効果もありました。振り返ってみると、この点こそがプロジェクト全体に大きな効果をもたらしていたと感じます。 5.4. 成果と学び 最終QA段階で不具合ゼロ 検索リプレイスでは、リリース前の最終QAで一件の不具合も発生せず、品質面で非常に安定した状態を保てました。 スケジュール遅延ゼロ リリース開始までの工程はすべて計画通りに進行し、想定していた作業を予定どおり完了できました。 リリース後のリグレッションもゼロ リリース後に細かなバグが見つかりましたが、その修正においてもリグレッションは一件もなく、整備したテストプロセスの再現性と堅牢性が確認できました。 この成果を通じて、私たちはテストを後追いで行うのではなく、開発リズムに組み込むことが品質とスピードの両立に直結することを実感しました。 テストは一部の担当者だけが実施する仕組みではなく、チーム全員が関わるプロセスとして設計することで、実際のユーザーへの影響を意識した実装や、認識齟齬・不具合を早い段階で修正できる文化が根づきました。 6. デプロイ基盤とリリース戦略 この章では、スパイラル開発を支えたインフラ構築と安全なリリースの仕組みについて紹介します。 ZOZOTOWNの検索機能は、1日あたり数百万回も利用される重要なサービスです。そのため、少しのダウンタイムも許されず、デプロイの安全性と効率性の両立が不可欠でした。SREは、それらの要件に基づいたデプロイ基盤を構築し、スパイラル開発における頻繁なリリースサイクルを支えました。 6.1. 安全かつ効率的なデプロイの仕組み ZOZOTOWNのプラットフォーム環境はEKSで稼働しており、FluxやFlaggerといった安全かつ効率的なデプロイを実現するための仕組みが導入されています。以下は弊社が KubeCon + CloudNativeCon Japan 2025 にスポンサーブースを出展した際に展示したアーキテクチャ図です。 引用:KubeCon + CloudNativeCon Japan 2025参加&協賛レポート 今回のリプレイスAPIのデプロイ基盤は、このプラットフォーム上にあるFluxとGitHub Actionsを組み合わせて 開発者がPRのマージをするだけ で完結するように設計されています。 詳細を以下に記載します。 ECRへのイメージプッシュで自動的にPR作成 開発者がリプレイスAPIリポジトリでDockerイメージをECRへプッシュすると、FluxのImageUpdateAutomationが新しいイメージを検知します。検知後、Kubernetesマニフェストを管理するインフラリポジトリへイメージタグを更新するcommitを自動的にpushします。その後、リリースブランチへ自動でPRを作成し、Slackで開発者へ通知します。 安全性を担保する自動承認 作成されたPRは、内容に応じて自動承認されます。自動化によるコミットのみの場合は、イメージ更新と判断しGitHub Actionsで自動承認されます。自動化によるcommit以外を含む場合はイメージ更新以外の変更を含むため、SREがPRをレビューします。 PRマージと自動デプロイの実行 2で自動的に承認されたPRは、開発者によってPRをマージします。マージ後はEKSクラスタ上のFluxが自動的に変更を検知し、変更内容をリプレイスAPIのDeploymentに反映します。反映後はFlaggerによるプログレッシブデリバリーが開始され、エラーが発生した場合はロールバックする仕組みもあるので、安全にリリースすることが出来ます。 本番環境は別途SREによる承認 開発環境(dev)やステージング環境(stg)は、マージと同時に自動デプロイされます。一方、本番環境(prd)にはRelease Gateという仕組みを導入しています。 techblog.zozo.com これはイメージ変更PRをマージ後に、SREによる承認を得なければprdへデプロイできない仕組みです。この承認プロセスにより、prd環境への影響はSREが確認しつつも、dev/stg環境では開発者自身で迅速にデプロイができるようになっています。 6.2. 非機能面の品質確保 今回の検索リプレイスはスパイラル開発による段階的な開発・リリースを進めてきましたが、最終的にはユーザーのトラフィックをリプレイスAPIにルーティングする必要があります。 特に、ZOZOTOWNの検索機能は日常的に高トラフィックを処理しておりビジネスの面でも重要な機能であるため、ユーザー影響を抑えるために慎重な対応が求められました。 そのため、本番リリース前にSREが中心になって以下のような非機能面の品質検証および運用体制の強化を実施しました。 負荷試験・障害試験を実施 SRE部のテックリードによるリリース前レビュー(プロダクションレディチェック) 開発者とSREとのリリース前の監視指標の認識合わせ 6.2.1. 負荷試験・障害試験を実施 ここではリプレイス前のセール時の最大リクエスト数の負荷を掛け、レイテンシや各種メトリクス指標で大きな性能悪化が出ないかを確認します。また事前に本番同等の負荷を掛けることで、本番環境へ出す前にリプレイスAPIのインフラ増強をしておくべきかを確認できます。 障害試験では、各マイクロサービスの呼び出し時に500系エラーやレスポンス遅延を意図的に発生させ、想定通りの挙動となるかを確認します。500や遅延はIstioのFault Injectionを使って実現しています。 これにより事前に呼び出す際のマイクロサービスで障害が発生しても意図通りな挙動であるかを確認します。 6.2.2. SRE部のテックリードによるリリース前レビュー(プロダクションレディチェック) 本番リリース前には、SRE部が独自に整備している「プロダクションレディマイクロサービスチェックリスト」を用いて、SRE部のテックリードによるレビューを実施しています。このチェックリストは、セキュリティやCI/CD、障害・負荷試験、監視、ドキュメント整備など、インフラ運用に必要な観点を網羅したものです。 チェックリストは形式的なものでなく、実際の運用・障害対応まで見据えた具体的な項目が並びます。「graceful shutdownの実装」「カナリアリリースの仕組み」「DatadogやSentryによる監視・アラート設計」「オンコール体制の整備」などです。 最終的に、SRE部のテックリードがこれらの項目をレビューし、「本番リリースOK」と判断できた段階でリリースを進めます。このプロセスを通じて、リリース後のトラブルや運用負荷を大幅に低減できるだけでなく、開発チームとの連携強化やナレッジの共有にもつながっています。 6.2.3. リリース前に開発者とSREで監視指標の認識合わせ これまでバグを出さないためのテストを行ってきましたが、検索リプレイスのような大規模な変更では、予期しない問題が多様な形で現れる可能性があります。 そのため開発チームとSREチームで、事前にリリース時の監視体制と切り戻し方針について議論し合意しました。また、数値基準を厳密に決めすぎると、「この数値は基準を超えているが、本当に切り戻すべきか?」という議論がリリース当日に発生し、判断が遅れる可能性もあります。 そこで私たちが合意したのは、具体的な数値ではなく、「判断の枠組み」でした。 【合意した切り戻しの方針】 断続的・継続的なエラー発生・単発のエラーではなく継続する場合 リプレイス前後で比較して明らかに体感できるレベルのレイテンシ悪化 ビジネス影響の兆候 検索実行数の不自然な減少 カスタマーサポートへの問い合わせ増加 売上影響の可能性 社内で策定したSLOに違反 重要なのは、「何かあったらひとまずレガシーAPIにリクエストを戻す」というシンプルで明快な方針を共有したことです。後述の6.3で説明するFlaggerのManual Gatingの利点は「いつでも安全に戻せる」ことです。この利点を活かし、迷ったら切り戻して安全確保する判断を優先できるようにしました。 【監視体制と判断プロセス】 合意した切り戻し方針を元に、N%リリース期間中は開発チームとSREチームが「同じ監視指標の画面を見て、同じ認識で判断できる状態」も整えました。 例えば、「断続的・継続的なエラー発生やレイテンシ悪化の確認」はDatadog、「リプレイス前後の比較」はレガシーAPIからログを送信しているSplunkの画面を見るといった認識を揃えました。 これらを常時モニタリングし、異常の兆候が見られた段階で開発チームとSREチームが即座に協議できる体制を整えました。 6.3. リプレイスAPIへの安全な切り替え 6.3.1. 段階的なトラフィック切り替え 本番リリースではユーザー影響を最小限に抑え、かつ長い期間をかけて段階的にリプレイスAPIへ切り替えることが求められました。ZOZOTOWNではFlaggerによる自動カナリアリリースを採用していますが、自動でリクエスト比率が上がってしまうため長い期間をかけたリリースには向いていませんでした。 そこで今回は、FlaggerのManual Gating機能を活用した段階的にリプレイスAPIへ切り替えました。 まず、機能フラグをONにしてリプレイスAPIを本番環境にデプロイした後、リクエストの99%をレガシーAPI、1%をリプレイスAPIにルーティングする設定で運用を開始します。この間、開発チームとSREチームが事前に合意した監視指標(レイテンシ、エラー率、ビジネス影響など)をもとに、リアルタイムで状況を確認しました。数日間問題がなければ、段階的にリプレイスAPIの比率を引き上げ、最終的に100%切り替えを完了します。この比率は以下の基準で設定しました。 このルーティング比重操作の詳細な技術は、以下のテックブログもご参照ください。 techblog.zozo.com 6.3.2. 即座に切り戻せる安心感 この仕組みの最大のメリットは、問題発生時にすぐ切り戻せることです。 もしバグやパフォーマンス劣化などの異常が検知された場合は、スクリプトを実行してトラフィックをすぐさまレガシーAPIへ戻せるため、ユーザー影響を最小限に抑えられます。その後、開発者が修正をデプロイし、再度Manual Gatingで段階的リリースを再開する、というサイクルを安全に回すことができました。 この「即座に切り戻せる安心感」があったからこそ、私たちは本番環境で大胆にリリースを進められました。実際のリリースでも、細かな挙動の違いを検知した際はすぐ切り戻し、修正後に再度段階的にリリースすることで、安全でスピーディな運用を実現できました。 7. おわりに 本記事では、リプレイスの過程でスパイラル開発をどのように取り入れ、実際のプロジェクト運営の中で活かしたのかを紹介しました。 小さく動かしながら品質を積み上げる仕組みづくり、早い段階で実環境に近い検証をする文化、そして安全にリリースできる基盤が、今回のリプレイス成功を支えた大きな要因でした。本記事が、同じようにレガシーシステムのリプレイスや段階的移行に取り組む方の参考になれば幸いです。 ちなみに 今回のリプレイスでは副次的な効果として検索速度が約2倍に向上しました。 ぜひZOZOTOWNアプリで検索してみてください!以前よりサクッと動く感覚を、体感していただけると思います。 紹介 このプロジェクトの完了により、Web・アプリ双方の検索基盤リプレイスがひと区切りとなり、検索UI/UXの改善をよりスピード感をもって進められる環境が整いました。今後は、この新しい基盤を活かして検索体験そのものをさらに良くしていくフェーズに入ります。 ZOZOでは、一緒にサービスをつくり上げてくれるエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251222/20251222150607.jpg"] { height: 600px; width: auto; } img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110649.png"], img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110658.png"], img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110708.png"], img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110716.png"] { border: 0.5px solid; } はじめに こんにちは、SRE部 検索基盤SREブロックの 花房 です。2025年12月8日〜10日の3日間、東京の虎ノ門ヒルズで開催された「 Open Source Summit Japan 2025 」に参加しました。 オープンソースの技術とコミュニティ をテーマとする本カンファレンスでは、AI・Cloud Native・Observability・Automotive Linuxといったトレンド技術や、コミュニティ運営に関連するリーダーシップ・組織論など、多岐にわたる領域の発表がありました。また、カンファレンスの中ではLinuxとGitの生みの親であるLinus Torvalds氏のインタビュー形式の基調講演があり、それを間近で聴けたことはエンジニアとして忘れられない貴重な体験になりました。 本記事では、カンファレンスの参加レポートを会場の様子や印象に残ったセッションを交えてお届けします。 目次 はじめに 目次 Open Source Summit Japanとは 特徴 会場の様子 セッションレポート Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure Scaling GitHub Actions To the CNCF You Never Know When You Need a Fork: Lessons Learned Building Valkey Panel: OSPOjp: Ways To Level up OSPO Stages With Knowledge and Strategies From Local Meetup Japan おわりに Open Source Summit Japanとは Open Source Summit Japan は、オープンソースをテーマとする国際カンファレンスです。オープンソースに関わるメンテナ・コントリビュータ・開発者・運用担当者が参加し、最新の技術動向やベストプラクティス、実践的なソリューションをお互いに共有します。また、技術面だけでなく、オープンソース技術を社会やビジネスにどのように役立てていくかも議論されます。そのため、コミュニティリーダーやビジネスリーダーにより、オープンソース活用における成功事例や推進方法も共有されます。 Open Source Summit Japan 2025のキービジュアル 本カンファレンスはLinux Foundationが主催しており、日本だけでなく海外でも開催されています。スポンサーにはオープンソースを活用する企業が名を連ねており、今年は以下の企業がスポンサーとして参加していました。 スポンサー 以上のように、役割を超えて多くの参加者・スポンサーが集まり、オープンソースに関する知見共有やネットワーキングが行われる場となっています。 特徴 本カンファレンスの特徴は セッショントラックの多さ です。 オープンソースに関連する分野や技術領域は多岐にわたるため、今回は以下の8トラックに細分化してセッションが開催されました。 Automotive Linux Summit Cloud & Containers Linux Open Source Leadership Operations Management OSPOCon Safety-Critical Software Zephyr さらに、本カンファレンスに留まらず、それぞれの技術領域に特化した以下の関連イベントも同時開催されました。 Cloud Native Community Japan OpenSearchCon Japan 2025 Rust Global: Tokyo Zephyr Project Meetup OpenSearchCon Japan 2025 の参加レポートは以下で公開しています。 techblog.zozo.com 来年も東京での開催が予定 されていますが、上記のように扱われるトピックの範囲が非常に広いため、参加する場合は事前に興味のあるトピックやセッションを絞り込んでおくことをおすすめします。当日、どのセッションに参加するかを決めるのは大変だと感じました。 会場の様子 本カンファレンスは、2025年12月8日〜10日の3日間、東京の虎ノ門ヒルズフォーラムで開催されました。 基調講演を含む各セッションは、5階のメインホール、5階の2部屋、4階の4部屋の計7部屋で行われました。各部屋ではリアルタイム翻訳を利用でき、英語が苦手な方でも参加しやすい環境が整っていたと思います。 メインホール 受付とSolutions Showcaseエリアは5階にありました。Solutions Showcaseエリアでは休憩時やランチタイムに食事が配布され、時間帯によっては非常に混雑するため注意が必要です。 受付 Solutions Showcaseエリア セッションレポート 特に印象に残ったセッションを紹介します。 Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure 本セッションでは、人間の介入なしでAIエージェントにインフラを管理させる場合のセキュリティ確保の方法について、具体的なアーキテクチャとオープンソースツールの紹介を交えて解説されました。 AIエージェントは、プロビジョニングからスケーリングに至るまでインフラの管理も担えるようになってきています。しかし、現状の課題として人間による変更のレビューが必須になってしまい、AIエージェントに全てを任せることはできません。AIは想定外の挙動をしたり、人間と同様に設定ミスを作り出したりしてしまうことがあるためです。また、攻撃者によりAIエージェントが操作され、脆弱性を埋め込むなど悪意のある行動を取らされる可能性もあります。 AIエージェントは有用ですが人間が常に動作をチェックする必要があるため、そこがボトルネックになってしまいます。また、人間の介入がないと不正な設定や悪意のある行動の影響が瞬時に拡散し、抑えられなくなってしまいます。さらに、AIの変更スピードにリアルタイムで対応するようなモニターやポリシーがないために、異変に気付くことが遅れる、もしくは気付けないという問題も発生します。 上記の課題に対する解決策は以下の2つです。 セキュリティポリシーの宣言的定義と適用 リアルタイム監視とワークロードの隔離 1つ目の解決策では、ポリシーをコードとして管理し、専用ツールによって常にインフラで有効な状態にしておきます。ツールは、Kubernetesリソースに対してはKyvernoやOPA、クラウドリソースに対してはCrossplaneが存在します。以下の図のように役割が分かれます。 公開資料 「Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure」 P.5より引用 Kyvernoでは、新規リソースの内容がコード管理されたポリシーに従っているかを判定し、不正なリソースが作成されることを未然に防ぎます。Crossplaneでは作成後のリソースのポリシー違反を検知した場合、ポリシーに従うように自動修正する機能が存在します。これらの機能により、AIにより生成された不正な設定がそのまま適用されることを自動的に防ぎます。 2つ目の解決策では、ワークロードの不審なアクションをリアルタイムで検知し、即座にネットワーク的に隔離することで被害の拡散を防ぎます。例えば、Falcoがワークロードの不審なアクションを検知した際、IstioやNetwork Policiesを利用してワークロード自体や侵害されたサービスを即座に隔離します。これにより、悪意のある行動やAIにより生成された脆弱性の拡散を自動的かつ迅速に防ぎます。 本セッションで紹介されたアーキテクチャとツールは、AIエージェントが自律的かつ安全にインフラを管理できる環境を実現していました。以下が全体像を図にしたものです。 公開資料 「Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure」 P.20より引用 Crossplane, Kyverno, Falco, Istioといったツールはオープンソースという点ですぐに導入できます。そのため、AIネイティブで安全なインフラを実現するためのハードルは高くないと感じました。また、本セッションの内容は、AIエージェントだけでなく人間によるインフラ変更にも有効だと考えます。AIほどのスピードは出せませんが、不正な設定や悪意のある行動は人間にも起こり得るためです。 本セッションで紹介されたアーキテクチャは、AI時代のインフラ管理の最先端であり、今後のスタンダードになっていくであろうと感じました。 Scaling GitHub Actions To the CNCF 本セッションでは、CNCF (Cloud Native Computing Foundation)におけるGitHub Actions Self-hosted Runnerの導入事例について紹介されました。 CNCFに所属するオープンソースプロジェクトは230を超えており、そのほとんどがソースコード管理とCIのプラットフォームとしてGitHubを利用しています。CIワークロードに関しては、CNCFのGitHub Enterpriseアカウントに集約しており、共通のインフラを利用しているような状態でした。そのような中で、GitHubから無料提供されていた「Large Runner」が2025年6月から有償化されるというアナウンスが、1年前である2024年6月にありました。有償化されるとCIの費用の全てがCNCFのアカウントにかかります。そこで、CI用のインフラをCNCFが所有しているオンプレ環境であるEquinix Metalに移行する話もあったようですが、Equinix Metalはサービス終了予定になったため行き場がなくなってしまいました。そのため、CNCFではCIを支えるインフラの新規構築が急務となりました。 CNCFは検討の結果、 Kubernetes上でGitHub Actions Self-hosted Runnerを運用する方法を採用 しました。ツールとしては、 Action Runner Controller (ARC) を利用しています。ARCはオープンソースのKubernetes Operatorで、その名の通りGitHub ActionsのSelf-hosted Runnerを運用管理してくれます。さらに、 Argo CDを用いたGitOps によりARCやKubernetesクラスタを管理しています。これにより、オープンソースプロジェクトのメンテナー自身が各々のSelf-hosted Runner環境をアップデートできる仕組みが整備されました。 ARCの導入後、新たな問題が発生しました。ARCはコンテナ環境でCIワークロードを実行しますが、オープンソースプロジェクトによってはコンテナでは動作しない、マシンレベルの仮想化や低レイヤーのネットワーク設定が必要なワークロードも存在しました。既存のARCはコンテナ環境を前提としていたため、CNCFでは VM環境でCIワークロードを実行できるような仕組み を追加しました。さらに、 CIワークロードで利用するための独自のVMイメージを構築するツール も開発しています。 最終的に以下の図のような構成になりました。 公開資料 「Building CNCF-Grade GitHub Action Runners」 P.21より引用 CNCFでは、ARCや独自のCIワークロード用VMを活用してGitHub Actions Self-hosted Runnerを運用しています。ARCとArgo CDを組み合わせることでスケール性とメンテナンス性を両立し、各プロジェクトのメンテナーも使いやすいプラットフォームを構築していることが参考にすべき姿だと感じました。また、独自のVMイメージを構築するツールはオープンソースで公開されているため、ぜひ試してみたいと思いました。このようにオープンソースで公開しているとユーザに気軽に試してもらえるため、自分で作成したツールも、たとえニッチなものであってもオープンソースとして公開していきたいと思いました。 You Never Know When You Need a Fork: Lessons Learned Building Valkey 本セッションでは、Redisをフォークして「Valkey」を立ち上げた際に実施した内容について紹介されました。 Redisのライセンス変更に伴いValkeyが誕生した当時、Redisの運営体制は不透明でクローズドでした。デプロイは独自の環境かつRedis Inc.だけが実施可能であり、ベンチマークテストも同様のクローズドな独自システムで行われていました。会議や意思決定もクローズドな場で行われ、コミュニティの活動は活発になりにくい状況でした。 そこで、Valkeyはフォーク前までのコミュニティの形は残し、全ての開発プロセスをオープンにするという運営体制を築きました。さらに、3つの工夫によりオープンであることの維持と継続的なリリースを実現しました。 ブランチとタグの保護ルールの有効化 : リリースの際には必ずPRをオープンして変更を誰かにチェックされるという文化を作り出す ドキュメントのバージョン管理と更新の自動化 : リリースやコントリビューションの方法、セキュリティに関する情報が常に最新の状態で公開される Valkeyのパッケージ提供先とビルドスクリプトのメンテナンス : Valkeyが各パッケージとして問題なく再配布されるようにすることで、Valkeyに触れる人が増え、コミュニティの発展に繋がる Valkeyのメンテナがコミュニティを非常に大事にしていることから、大規模なオープンソースはコミュニティなしで成り立たないことを改めて実感しました。また、コミュニティに還元するにはリリースを継続的に続ける必要があり、そのためにはコミュニティをオープンにして誰もがプロジェクトに参加しやすく、活発にし続けることが重要だと感じました。 Panel: OSPOjp: Ways To Level up OSPO Stages With Knowledge and Strategies From Local Meetup Japan 本セッションは、日本におけるOSPO (Open Source Program Office) 活動のナレッジと戦略を共有することを目的として開催されました。 企業でOSS貢献に取り組む際、推奨されているため貢献を始めるものの、他の取り組みよりも優先度を下げてしまい続かないことは多々あると思います。OSS活動の課題として、組織のOSS戦略が欠如していると、メンバーが貢献の価値を証明できずOSS活動を縮小してしまう「OSSの落とし穴ループ」に陥ることがあります。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.5より引用 解決策の1つは、組織内にOSPOを設置し、成熟度モデルに基づいて組織の意識を「利用のリスク管理」から「貢献の価値創出」へとシフトさせることです。OSPOとは、組織のオープンソース運用の中核を担う専門部署です。具体的な活動としては、オープンソース活用の戦略やポリシーを作成し適用すること、オープンソースに関するトレーニング、外部のオープンソースコミュニティとの関係構築などが挙げられます。 以下の図では OSSおよびOSSコミュニティ活動のメリット を成熟度ごとに示しています。成熟度が上がると、そのメリットが 組織内 に留まらず エコシステム へとより範囲が広くなります。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.15より引用 以下の図では OSSに対するマインドセット を成熟度ごとに示しています。成熟度が上がると、組織のマインドセットは OSSのリスク から 価値創出 へと変化します。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.16より引用 以下の図では 組織内でのOSPOの役割 を成熟度ごとに示しています。成熟度が上がると、 ルール整備や教育 から 組織内外の関係構築や戦略的な意思決定のサポート へと変化します。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.17より引用 OSPOの在り方は組織によって様々で正解はありませんが、以上が既存のナレッジから構築された成熟度モデルでした。「OSSの落とし穴ループ」を脱するために参考となるモデルです。 本セッションを通して、OSPOとは何か、その役割と価値について知ることができました。パネルディスカッション形式で、日本でOSPOを実践している方々の話を伺うこともでき、非常に貴重で学びになる時間でした。今後、自身のオープンソース活動を展開していく上で本セッションの内容を活かしていきたいと思います。 おわりに 本記事では、「Open Source Summit Japan 2025」の参加レポートをお届けしました。 3日間、オープンソースの世界に没頭し、刺激を受け続けた貴重な機会になりました。技術の最前線に触れられるだけでなく、コミュニティ運営やビジネスでの活用方法など多角的な知見を得ることができ、非常に有意義だったと感じています。また、長期的に「どのようにオープンソースと関わっていくか」を考えるきっかけにもなりました。今後は、オープンソースの戦略的な活用に加え、コミュニティへの還元・貢献の頻度を増やしていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、ビジネス・アナリティクス部マーケティング・サイエンスブロックの 茅原 です。普段はマーケティング施策の効果検証を担当しています。マーケティング・サイエンスブロックではAI協働型分析フロー構築の取り組みをしています。本記事では本取り組みの詳細や、この中で得られた知見をご紹介します。 目次 はじめに 目次 背景・課題 取り組みの紹介 分析環境の標準化 AI活用フロー検証のためのGitHubリポジトリ構築 分析設計書の活用 分析作業フローの定型化 再利用性を高める工夫 文化の醸成 まとめ 背景・課題 近年、生成AIの発達が目覚ましく、開発をはじめとするエンジニアリング組織ではその活用事例が多く見られるようになってきました。一方でデータ分析組織においては、エンジニアリング組織ほど生成AIとの協業事例が見られないという所感があります。これは分析組織にとって、 生成AIの持つ不確実性の高さが致命的である ため、と考えています。例えば、生成AIは「ただ数値を転記する」といった簡単なタスクにおいてもハルシネーションを起こし、誤ったアウトプットを生成してしまうことがあります。データ分析においては、アウトプットの正確性が非常に重要であるため、こうした不確実性の高さは大きな障壁となっています。 本記事ではその不確実性を低減したAI協働型分析フロー構築の取り組みについてご紹介します。またこうした取り組みは、 生成AIに限定せず、人間も含めた組織全体のアウトプット品質向上にも寄与する ものと考えています。以下は現在検証中の内容ですが、同様の課題を抱える方の参考になれば幸いです。 取り組みの紹介 分析環境の標準化 まず前準備として、分析環境の標準化を行いました。前提として、弊チームでは環境としてはローカル/クラウド、IDEとしてはVS Code/JupyterLabなど、メンバーによって作業環境が統一されていない状態でした。こういった多様な環境によって各環境に合わせた設定が必要となり、AI活用ナレッジの転用が難しくなってしまう、という状況となっていました。そのため、Google CloudのCompute Engine (GCE)インスタンスへVS CodeからリモートSSH接続する形式に統一しました。これによりAI活用ナレッジの横展開が可能になりました。 AI活用フロー検証のためのGitHubリポジトリ構築 こちらも前準備として分析環境の標準化に加え、AI活用フロー検証のためのGitHubリポジトリ構築も行いました。具体的には、20人規模のチームで使用していたGitHubリポジトリを、4〜5人規模のチーム単位に分割しました。 旧来のリポジトリでは、リポジトリ全体へ影響を及ぼす設定変更に他チームとの合意が必要であり、検証/改善をスムーズに進められない状況になってしまっていました。チーム単位で分割することで、各チームは独自に設定を変更できるようになりAI活用ナレッジの試行錯誤がしやすくなりました。AI協働型分析フローを検証する段階においては、GitHubリポジトリの共有範囲は「ルールやフローを共有する範囲」で区切ることが検証スピードの観点から望ましいと考えています。 以下は実際のリポジトリのディレクトリ構成例です。 aia-ms/ ├── projects/ # 依頼部門ごとに案件を管理するディレクトリ │ ├── marketing_div/ # マーケティング部依頼案件ディレクトリ │ │ └── project_001/ # 案件ディレクトリ │ │ ├── overview.md # 分析設計書ファイル │ │ ├── data/ # CSV等のデータファイルディレクトリ │ │ ├── sql/ # SQLファイルディレクトリ │ │ ├── scripts/ # Python, Rの分析コードファイルディレクトリ │ │ ├── notebooks/ # Jupyter Notebookのノートブックファイルディレクトリ │ │ ├── reports/ # Markdown等のレポート関連ファイルディレクトリ │ │ └── etc │ └── etc ├── labs/ # メンバーごとに自由に使用するディレクトリ │ ├── kayahara/ │ └── etc ├── scripts/ # 再利用可能なスクリプトを管理するディレクトリ │ ├── bq_utils/ # BigQueryを操作するスクリプトをまとめたディレクトリ │ └── etc └── .github/ # GitHub関連設定ディレクトリ │ ├── workflows/ # GitHub Actionsワークフローを管理するディレクトリ │ ├── prompts/ # GitHub Copilot Chat用プロンプトファイルを管理するディレクトリ │ ├── agents/ # GitHub Copilot Custom Agents設定ファイルを管理するディレクトリ │ └── copilot-instructions.md # GitHub Copilot Chat設定ファイル 分析設計書の活用 以前から部内で使用していた分析設計書について、AI協働のためさらに活用するようになりました。前提として、弊チームでは「良い分析のためには、良い分析設計書が不可欠である」と考え、AI協働以前の2020年ごろから分析設計書を重んじるフローを構築していました。これはデータ分析が「1つの問題に対してアプローチが無数にある」タスクであり、「何を解くか」「なぜ解くか」「どう解くか」を関係者全員で合意形成しておくことが重要、という思想からです。AIとの協働においては、疑似的に「関係者」の一員としてAIが加わるため、AIに対しても「なぜこの分析をするのか」「何をもって成功とするのか」を明確に伝える必要があります。 分析設計書を活用することで、AIに対してもこれらの情報を正確に伝えることができ、アウトプットの不確実性を低減できました。具体的には、設計書の観点の見直し・充実化を行い、全案件で設計書を作成・レビューするような運用ルールを設定しました。 設計書には、主に以下の項目を記載しています。 依頼の概要 分析対象に関する基礎情報や依頼内容 依頼部門の理想/課題 依頼の背景として依頼部門が抱える理想や課題 分析対象の理想/課題 依頼部門が抱える理想や課題を踏まえた、分析対象に関する理想や課題 本案件で取り組む課題とゴール 分析対象の理想や課題に対して、本案件で取り組む具体的な課題と分析におけるゴール アウトプット予定項目 ゴールを達成するために必要なアウトプット項目 また、以前はGoogle Sheetsで管理していた分析設計書を、GitHubでMarkdown形式として管理する方式に変更しました。これにより、作業時にはAIへ設計書の内容を容易に参照させることができる上、設計書のレビュー時にもプルリクエスト上でGitHub Copilot Reviewを活用できるようになりました。結果として、設計書の品質向上にも寄与しました。 分析作業フローの定型化 AIのアウトプットの不確実性を下げるために、分析作業フローの定型化も行いました。AI協働型分析の良くない典型例として、「課題感を共有してAIに自由に分析させた結果、意図しないフローで進められ、期待したアウトプットが得られない」というものがあります。 具体的には、「Pythonファイルの中に長大なSQLを書いてしまう」「本来Pythonですべきような複雑な集計処理をSQLで行ってしまう」といったケースです。これを防ぐために、AI協働型分析での分析作業フローを下記のように定型化し、フローと各ステップでのアンチパターンをプロンプト内で明示的に伝えるようにしました。 社内Wikiから資料をMarkdown形式でダウンロードする 資料を基にBigQueryからSQLでデータを抽出する 抽出したデータをPythonで加工・モデル適用し、分析結果を得る 分析結果をMarkdown形式のレポートにまとめる 各ステップで再利用可能なPythonスクリプトを開発し、安定して各ステップの作業ができるようにしました。具体例としては、ステップ4では「CSVを自動でMarkdown形式の表に変換する」スクリプトを作成しました。非常にシンプルなタスクであり、AIにも可能そうに見えますが、実際にこのようなタスクを任せた際にハルシネーションが発生し「元のCSVと全く違った数値がレポートに転記される」といった事象が発生しました。そのため、このようにフローを定型化し、各ステップで再利用可能な仕組みを構築することで、AIのアウトプットの不確実性を低減しました。 再利用性を高める工夫 AI協働型分析の大きな強みの1つは、 「再利用性の高さ」 です。例えば、同じような分析タスクが発生した場合、過去の分析コードやプロンプトを再利用し、効率的に分析を進めることができます。これを最大限活用するため、LLMツールの機能を積極的に活用する方針を採りました。 具体的には、GitHub Copilotの以下の機能を活用しています。これらの設定ファイルはいずれもMarkdown形式で保存されています。リポジトリ内で共有可能なため、チーム内での横展開が容易です。 Prompt Files : あらかじめ定義したプロンプトをファイルとして保存し、再利用可能にする機能。これにより、分析フローの各ステップで使用するプロンプトを標準化し、チーム内で共有できるようになった。 Custom Agents : 複数のプロンプトやツールを組み合わせて、特定のタスクに特化したエージェントを作成できる機能。これにより、フローにおける各ステップを自動化するエージェントを構築し、効率的に分析を進められるようになった。 以下は分析設計書をレビューするプロンプトファイルの例です。以下のようなファイルを作成し所定のフォルダに保存することで、GitHub Copilot Chatからスラッシュコマンド形式ですぐ呼び出すことが可能となります。 --- agent: 'edit' description: 'Review the analysis design document for clarity, consistency, and completeness, then propose specific revisions and improvements to its structure, logic, and wording.' --- - 回答は **必ず** 日本語で記載してください - 添付された文章を以下の観点でレビューし、修正を提案してください # 全体 - 論理の流れがマクロからミクロへ分断なく接続できているか - 解釈が揺れる表現、不明確な表現を使用していないか ## 1. 施策/依頼の概要 - 分析対象について、十分な情報があるか - 例: 施策意図、対象者、訴求方法、施策内容、検証設計(RCT有無) ## 2. 事業部の理想/課題 - 事業部の長期的な理想/課題が理解できる記述か / 短期的であったりタスクベースであったりしないか ## 3. 施策の理想/課題 - 本施策が何を目指しているか、KPIベースで記載できているか ## 4. 本案件で取り組む課題とゴール ### 課題 - タスクリスト等ではなく課題の形式(~できていない 等)で記載できているか ### ゴール - タスクリスト等ではなくゴールの形式(~によって○○を××する 等)で記載できているか - 課題に紐づいたゴールとなっているか - どのようなビジネスアクション or 貢献に繋げたいか記載できているか ### 問 - タスクリスト等ではなく問の形式(~か?)で記載できているか - ゴールに紐づいた問となっているか ### 判断指標/基準 - 判断指標とその定量基準を記載できているか(暫定でも可) - 判断指標/基準がない場合、代替となるもの(目標ライン等)を記載できているか ## 5. アウトプット予定項目 - アウトプット項目形式として、名詞の箇条書きにできているか - 具体的に算出する指標を記載できているか (主要なもののみでOK) これらの作業単位で組み上げたプロンプト&エージェントと、各案件の分析設計書を組み合わせることで、効率的かつ安定的にAI協働型分析を進められる仕組みの構築を試みています。 文化の醸成 上記のような仕組みの整備に加え、文化の醸成にも取り組みました。日夜進歩するAIへの理解度やLLMツールの活用状況は、どうしても各メンバーの興味や意欲に依存してしまう部分があり、差が付きやすいという課題があるかと思います。この差を放置すると、AIに明るくないメンバーにとっては「気が付いたら複雑なフローを組まれていて着いていけない」といった状況になり、悪循環に陥りかねません。そこで、下記の2施策を実施しました。 1点目は事例共有会の開催です。実際の案件でのAI活用事例を共有し、「どんな良さがあったか」「うまくいかないときにどうすればいいか」といったポイントをセットで共有しました。これにより、各メンバーにまず「やってみたい」と思ってもらう、使ってみて躓いたときに「もう一度試してみよう」と思ってもらうことをターゲットとしました。 2点目は初心者向けレクチャの開催です。「そもそもどんなツールがあるのか」「各ツールでどんな操作をすれば何ができるのか」という基礎的な内容を、弊チームの業務フローに合わせた形でレクチャしました。これにより、各メンバーが「やってみたい」と思ったときにすぐに試せるような土台を作りました。 上記の主要施策の他にも、Slackのtimesチャンネルやチームの朝会での情報共有など、継続的に文化醸成を図る取り組みを行っています。 まとめ 本記事ではAI協働型分析フロー構築の取り組みを紹介しました。AI協働型分析フローの導入を検討している方がいれば、ぜひ参考にしてみてください。上記はまだ検証中のステータスであり、今後も改善を続けていく予定です。新たな知見が得られ次第、改めて共有いたします。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.table-of-contents > li > ul > li > ul { display: none; } .w70p .figure-image img { width:70%; } 12月1日〜12月5日にラスベガスで AWS re:Invent 2025 が開催され、ZOZOのエンジニアが5名参加しました。この記事の前半では熱気に溢れる会場の様子を、後半では面白かったセッションについてご紹介します! AWS re:inventとは 現地の様子 メイン会場 朝食や昼食 Sports Forum 無料のStarbucks Coffee AWS主催のパーティ セッションレポート Build observable AI agents with Strands, AgentCore, and Datadog (AIM233) AIエージェント運用の課題 Strands AgentsとAgentCore デモ:AWS Newsletter Agent AWS Well-Architected Framework - Generative AI Lens DatadogによるLLM Observability まとめ SEC320-R: The AWS Security Incident Response Challenge: Defend the Cake! ストーリー設定:Unicorn Cake Companyを守れ! インシデント対応のアプローチ(NIST SP 800-61 Rev.3) 環境のアーキテクチャ CISOからの指示 取り組んだセキュリティ課題 1. アクセス制御の強化 2. クラウンジュエル(秘密のレシピ)の保護 3. 検出と警告の設定 4. ウェブサイトの保護 攻撃者の手口:知っておくべき統計 IAM設定の問題点 Shift Left:開発段階からのセキュリティ AWS CIRT リソース Workshopの様子 まとめ Accelerate platform engineering on Amazon EKS (CNS301-R1) Workshopの概要 プラットフォーム基盤構築 生成AIを活用したプラットフォームエンジニアリング Progressive Deliveryの実践 感想 Using Amazon Q to Cost Optimize Your Containerized Workloads (CMP348) Workshopの概要 Amazon Qによる移行分析 負荷試験でGravitonへの移行の効果測定 段階的な移行アーキテクチャ Phase 1: 移行用のGraviton NodePoolを用意し、pilot環境での移行検証 Phase 2: capacity-spreadの導入 Phase 3: トラフィック分散 感想 Infrastructure Innovations (KEY004) 講演のはじめに 主なアップデート #1 Graviton5 主なアップデート #2 Lambda Managed Instances 主なアップデート #3 Vectors ゲストセッション 講演を通じて感じたこと おまけ Advanced AWS Network Security : Building Scalable Production Defenses [REPEAT] (SEC303-R1) 主な内容 AWS Network Firewallとは Route 53 Resolver DNS Firewallとは Labのアーキテクチャ図 AWS Network Firewallでの通信制御 DNS Firewallでの通信制御 Workshopまとめ The Kiro coding challenge (DVT317) Workshopの概要 実践 まとめ Ditch your old SRE playbook: AI SRE for root cause in minutes (sponsored by Resolve AI) (AIM260-S) 概要 内容 ソフトウェアエンジニアリングの進化 AI for prod Resolve AIの3つの重要な特徴 AIエージェント構築のアプローチ比較 感想 [NEW LAUNCH] Resolve and prevent future operational issues with AWS DevOps Agent [REPEAT] (DVT337-R1) AWS DevOps Agentができること - AWS上のリソースの可視化 AWS DevOps Agentができること - 障害調査 実際の使い所 おわりに AWS re:inventとは re:Invent はAmazon Web Services(AWS)が主催するAWS最大のカンファレンスです。このイベントでは、AWSが提供する様々なクラウドサービスに関する新サービスやアップデートが発表されます。 2025年は世界中から約60,000人以上が、日本から昨年と同程度がラスベガスに集まりました。今年のre:Inventでは、「 AIアシスタントからAIエージェントへ 」という転換点を象徴するイベントでした。 現地の様子 メイン会場 The Venetian Resort Las Vegasがメインの会場となっており、KeynoteやEXPO、Swag(ノベルティ)など様々なコンテンツがここに集合しています。そんな会場で出会った様々なコンテンツをご紹介いたします! メイン会場では、AWS re:Invent会場への入口案内があり、この時点ですでに私たちはテンションがとても上がっていました。周りの参加者からも笑い声や笑顔で写真を撮る人も多くとてもよい空間となっていました。 エントランスの様子 メイン会場に入ると、大きな黒板がありチョークで好きなように描けました。たくさんの参加者がここで社名を書き記念撮影を行なっていました。イベントが行われる5日間このブースもとても盛り上がっていました。 メイン会場にある大きな黒板 ZOZOTOWNの名を記しました! メイン会場で特に盛り上がっていたブースはKiroのお化け屋敷でした。筆者もお化け屋敷に入りました! コーディングによるバグやエラーなどがホラー演出として現れ、それをKiroが救ってくれるといった内容だったと思います。このお化け屋敷を抜けた後に、記念写真をとってもらい、さらにKiroのSwagもいただきました! Kiroのお化け屋敷 メイン会場にはAWSの公式グッズが売っているショップもありました! シャツやパーカー、リュック、タンブラーなど様々な商品が売っておりどれも魅力的でした! 来年参加される方はぜひ立ち寄ってみてください! AWS公式ショップ「AWS Merch Store」 メイン会場には2日目から世界的な企業がブースを構えているEXPOエリアがオープンします! EXPOエリアの様子 各企業のブースに行って、議論を交わしたりサービスの説明を受けたりしました。 EXPOエリアにある企業ブースの様子 アンケートに答えたりゲームをクリアしたりするとSwagがもらえます。現地参加したメンバーもSwag集めを楽しんでいました! 特に Resolve.ai さんのブースでは、オリジナルのパーカーに目の前で自分の名前を印字してくださる企画が開催されており、長蛇の列ができていました! 長蛇の列ができていたResolve.aiさんのブースの様子 どの企業もブースやSwagに力を入れており、持って帰るのがかなり大変でしたが、様々な企業の話も聞けて楽しかったです! 現地でいただいた数々のSwagたち! 朝食や昼食 re:Inventの会期中は、朝食や昼食が各会場で提供されていて、参加者なら無料で食べられます! ビュッフェ形式なので好きなものを食べられました! 現地でいただいたビュッフェの様子 さらに、セッション会場の通路で定期的に軽食も提供されます。Workshopなど長めのセッションに参加する時は、軽食やコーヒーを持ち込んで栄養補給しながら臨みました! セッション会場の通路で定期的に提供される軽食 Sports Forum AWSがスポーツ領域でどのようにクラウドやAIを活用しているか、展示・セッション・体験ブースを通じて体感できるSports ForumというイベントゾーンがCaesars Forum会場で開催されていました! Caesars Forum 会場で開催されていたSports Forumというイベントゾーン F1からNBAまで様々なスポーツでの技術活用例が展示されており、セッションの合間のリフレッシュにもなり、とても楽しい空間でした。eSportsのRiot Games社も展示しており、ゲームを体験できるスペースも用意されていました。 eSportsのRiot Games社の展示 ゲームを体験できるスペース 無料のStarbucks Coffee re:Invent参加者には、会場であるVenetian内のスターバックスでドリンクとフード、合わせて4点までが無料で提供されます! 朝8:30くらいに行った際は30分ほど並んだので、時間に余裕のある方はぜひお立ち寄りください! re:Invent参加者には、Venetian内のStarbucks Coffeeでドリンクとフードが無料で提供される AWS主催のパーティ APJ Kick Off Partyとre:Play会場内の様子 初日(12/1)は、「 APJ Kick Off Party 」に行ってきました。本イベントはアジア太平洋地域と日本からre:Inventに参加する方向けのAWS公式のネットワーキングイベントです。ラスベガスのナイトクラブで行われます。今年の出演はマーク・ロンソンさんでした。ブルーノ・マーズさんとの「Uptown Funk」など、大ヒット曲を生み出しています。 また、最終日前日、12/4には「 re:Play 」というパーティーが行われました。これはre:Inventのフィナーレのようなもので、大きな広場を使い行われました。日付が変わるまでパーティーでした。最後を締め括ったのはDJのKASKADEさんで、会場は大盛り上がりでした! セッションレポート ここからはre:Inventの参加メンバーが気になったセッションを紹介します。 Build observable AI agents with Strands, AgentCore, and Datadog (AIM233) SRE部 カート決済SREブロックの小松です。ZOZOTOWNのカート決済機能のリプレイス・運用・保守に携わっています。また、全社のAWS管理者としての役割も担っています。 今回は興味を持っていたサービスの1つであるStrands AgentsとAmazon Bedrock AgentCoreについて、AIエージェントの本番運用に向けたObservability(可観測性)の観点から紹介されたセッションに参加しました。 DatadogのSr. Technical AdvocateであるKunal Batra氏と、AWSのSr. Developer AdvocateであるDu'An Lightfoot氏による共同セッションで、その内容について簡単ではありますがご紹介します。 AIM233 セッションの様子 AIエージェント運用の課題 セッション冒頭では、AIエージェントをスケールで運用する際の課題が紹介されました。 AIエージェント運用の課題 Hallucinations & Quality : LLMが不正確な情報を生成する可能性があり、継続的な評価と修正が必要 Complexity : エージェントは複合システムであり、内部の動作が見えないとトラブルシューティングが困難 LLM Costs : 本番ワークロードではコストが容易に膨らむ Security & Safety : プロンプトインジェクションやデータ漏洩のリスク 特に印象的だったのは、エージェントが「非決定的(non-deterministic)」であるという点です。同じ入力でも異なる出力を返す可能性があり、従来のソフトウェアとは異なるアプローチでの監視が必要になります。 印象的だったエージェントが「非決定的(non-deterministic)」であるという点 Strands AgentsとAgentCore 続いて、AWSが提供する、エージェント構築・運用ツールが紹介されました。 Strands Agents は、わずか数行のコードでエージェントを構築できるオープンソースのPython SDKです。モデルに依存せず、MCPやA2Aプロトコルもサポートしています。 Strands Agents Amazon Bedrock AgentCore は、エージェントを本番環境にデプロイするための包括的なプラットフォームです。 Amazon Bedrock AgentCore AgentCoreは以下のコンポーネントを提供します。 Runtime : エージェントのホスティング環境。DockerfileまたはCode-zipでデプロイ可能 Memory : 短期記憶(チャット履歴、セッション状態)と長期記憶(セマンティック、ユーザー設定、エピソディック) Observability : OpenTelemetry(OTEL)ベースのテレメトリ収集 Identity/Policy/Gateway : セキュリティとアクセス制御 デモ:AWS Newsletter Agent セッションではAWS Newsletter Agentを使ったデモが行われました。このエージェントはAWSの最新ニュースを取得し、AIに関連するアップデートをフィルタリングしてニュースレターとして配信します。 AWS Newsletter Agentのデモ デモでは、以下のような流れが紹介されました。 ユーザーが「What's new in AWS today?」と質問 エージェントが current_time と fetch_aws_news ツールを実行 AI/MLに関連するニュースを抽出してサマリーを生成 ニュースレターをメールで送信 デモの流れ さらに、エージェントが自然言語の指示だけでEventBridgeのスケジュールを作成し、毎日自動でニュースレターを配信する設定まで行っていました。 AWS Well-Architected Framework - Generative AI Lens セッションでは、AWS Well-Architected Frameworkに Generative AI Lens が2025年に追加されたことも紹介されました。 AWS Well-Architected FrameworkにGenerative AI Lens 6つの柱それぞれにAIエージェント向けのベストプラクティスが定義されています。 Operational Excellence : 包括的なObservabilityの実装 Security : 安全で責任あるアウトプットのためのGuardrails Cost Optimization : 長時間実行ワークフローにおける停止条件の設定 DatadogによるLLM Observability セッション後半では、Datadogを使ったAIエージェントの監視デモが行われました。 Datadogを使ったAIエージェントの監視デモ Datadogの LLM Observability では、以下のような情報を一元的に確認できます。 Summary : エラーレート、LLM呼び出し回数、トークン使用量の概要。 Evaluations : エージェントの応答品質を評価。「Failure to Answer」の割合や、カスタム評価(Financial Advisory Compliance Checkなど)の結果を確認できる。 Cost : トークン使用量とコストの推移。最もコストがかかっているモデル呼び出しを特定できる。 Traces : エージェントの処理フローを詳細に追跡。特に印象的だったのは、マルチエージェント構成(Budget Guru → Triage Agent → Prompt Injection Agent → PII Detection Agent)の各ステップが可視化されている点。 Security & Safety : プロンプトインジェクション、出力の有害性(Toxicity)、PII(個人情報)の検出状況を監視できる。 Experiments : 異なるモデルやプロンプトを比較して、品質・コスト・レイテンシのトレードオフを分析できる。Claude Haiku 4.5など複数モデルが比較されていた。 まとめ AIエージェントを本番運用する上で、従来のアプリケーション監視とは異なる観点が必要だと実感しました。特に以下の点が印象に残っています。 非決定性への対応 : 同じ入力でも出力が変わるため、品質評価の自動化が重要 コスト可視化 : トークン単位でのコスト追跡により、予期せぬコスト増加を早期に検知 セキュリティ監視 : プロンプトインジェクションやPII漏洩のリアルタイム検出 マルチエージェントのトレース : 複数エージェントが連携する場合、処理フローの可視化が不可欠 弊社でもAIエージェントの活用を進めていますが、本番運用に向けてはObservabilityの整備が重要な課題になりそうです。StrandsとAgentCore、そしてDatadogの組み合わせは、その解決策の1つとして検討していきたいと思います。 SEC320-R: The AWS Security Incident Response Challenge: Defend the Cake! このWorkshopは、架空の企業「Unicorn Cake Company」のセキュリティチームとして、攻撃者(Grumpy Cats)から秘密のレシピを守るという、ゲーム形式で楽しみながらセキュリティインシデント対応を学べる内容でした。 SEC320-R Workshopの様子 このWorkshopでは、Blue Teamとして、AWS Lambdaで実装されたRed Teamの攻撃を防御するシナリオをシミュレートします。30分の準備期間の後、攻撃が開始され、リアルタイムで対応していく形式です。 Workshopの概要 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット ストーリー設定:Unicorn Cake Companyを守れ! 参加者は「Unicorn Cake Company」という架空のケーキ製造会社のクラウドセキュリティチームとして参加します。この会社はユニコーン向けの魔法のケーキを製造しており、その秘密のレシピ「UnicornCakesSecretRecipe.txt」が攻撃者に狙われています。 攻撃者は「Grumpy Cats(気難しい猫たち)」と呼ばれる集団で、レシピを盗むか、盗めなければ破壊しようとしています。 インシデント対応のアプローチ(NIST SP 800-61 Rev.3) Workshopでは、 NIST SP 800-61 Rev.3 に基づいたインシデント対応のフレームワークが紹介されました。 インシデント対応のフレームワーク Preparation(準備) : インシデント対応能力の確立、ツールの準備、スタッフの訓練 Detection & Analysis(検出と分析) : 脅威の検出と影響範囲の分析 Containment, Eradication & Recovery(封じ込め、根絶、復旧) : 被害の拡大防止と復旧 Post-Incident Activity(事後活動) : 振り返りと改善 Incident Response 特に印象的だったのは「 Incident Response is a TEAM SPORT! 」というメッセージです。セキュリティインシデント対応は一人で行うものではなく、チーム全体で取り組むべきものだという点が強調されていました。 特に印象的だった「Incident Response is a TEAM SPORT!」というメッセージ 環境のアーキテクチャ Workshopの環境は単一リージョン(us-east-1)で構成されており、以下のコンポーネントで構成されています。 環境のアーキテクチャ図 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット 主要コンポーネント CloudFront Distribution → Lambda (Competition Upload) → S3 (Competition Uploads) VPC内 Unicorn Cake Company Website → S3 (Unicorn Production Bucket) WorkshopHints (本番環境外、ヒント用) Unicorn Logging (ログ保存用) 組織構造 25人のユーザーが5つのチームに分かれている 営業、HR、開発、食品工学、レストラン経営者 全員がアクセスキーと管理者権限を持っている (これがセキュリティ的にヤバい設定) 全員がアクセスキーと管理者権限を持っている設定! 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット CISOからの指示 Workshopでは、CISO(Rainbow Sparkles氏)からの緊急ブリーフィングという形で、取り組むべき課題が提示されます。 CISOからの指示 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット 最重要ルール:「生産を停止してはいけません」 ユニコーンたちのために魔法のケーキを作り続けなければならないという制約の中で、セキュリティを強化する必要があります。これは実際の本番環境でも同様で、セキュリティ対策のために事業を止めることはできないという現実的な制約を体験できます。 取り組んだセキュリティ課題 CISOから提示された課題は大きく4つのカテゴリに分かれていました。 1. アクセス制御の強化 MFAの有効化 : 全ユーザーにMFAを設定(まずは「Unicorn_Cakes_Sales_1」で実演) IAM認証情報レポートのダウンロード : 現状の把握 2. クラウンジュエル(秘密のレシピ)の保護 削除・ダウンロードの追跡 : 誰がレシピにアクセスしたかを特定できるようにする 改ざん検知 : レシピが変更されていないことを確認する方法 不変ストレージ : S3 Object Lockを使用して、管理者でも削除できないようにする 多層防御 : S3バケットポリシーでAWSアカウント内からのみアクセス可能にする 3. 検出と警告の設定 Amazon GuardDutyの有効化 : 24時間365日の脅威検知 即時アラートの設定 : EventBridgeを使用してレシピへのアクセスをリアルタイムで通知 CloudTrailの設定確認 : ログが正しく収集されているかの確認 4. ウェブサイトの保護 可用性の維持 : ウェブサイトが継続して動作することを確保 復旧手順の確立 : 万が一破損した場合の復旧方法 攻撃者の手口:知っておくべき統計 Workshopでは、実際の攻撃者がどのような方法で初期アクセスを獲得するかという統計も紹介されました。 実際の攻撃者がどのような方法で初期アクセスを獲得するかという統計 66% : 有効なIAM認証情報を使用(うち約1/3がroot認証情報) 13% : 公開されたEC2インスタンス経由 この統計からも、IAM認証情報の管理がいかに重要かがわかります。 IAM設定の問題点 Workshopの環境には、意図的にセキュリティ上の問題が含まれていました。 IAM設定の問題点 Long-term Security Credentials : 長期間有効なアクセスキーの使用 MFA未設定 : MFAトークンが設定されていない(×マーク) 過剰な権限 : 全ユーザーがInline Policyで管理者権限を持っている また、「Overprivileged website」として、EC2インスタンスがIMDSv1を使用して過剰な権限を持つIAMロールがアタッチされているという、典型的な攻撃対象となる構成も紹介されました。 典型的な攻撃対象となる構成 Shift Left:開発段階からのセキュリティ Workshopの最後には、「Shift security left in your development flow」というメッセージとともに、 Kiro が紹介されました。 Shift security left in your development flow 開発段階からApplication Security Testingを導入することで、本番環境に脆弱性を持ち込まないようにするアプローチです。 AWS CIRT リソース セッションの最後には、AWS CIRT(Customer Incident Response Team)が提供する、有用なリンク集がQRコードで共有されました。インシデント対応についてさらに学びたい方は、こちらのリソースも参考になります。 builder.aws.com Workshopの様子 会場では多くの参加者がチームごとに分かれて、リアルタイムで攻撃に対応していました。スコアボードには各チームのポイントが表示され、競争形式で楽しみながら学べる環境でした。 まとめ このWorkshopを通じて、以下の点が特に印象に残りました。 セキュリティと事業継続のバランス :「生産を停止してはいけない」という制約の中でセキュリティを強化する経験は、実際の本番環境でも直面する課題そのものでした。 多層防御の重要性 : IAMポリシー、S3バケットポリシー、MFA、GuardDuty、CloudTrailなど、複数のレイヤーで防御することの重要性を実感しました。 検知と対応の速度 : 攻撃はいつ来るかわかりません。GuardDutyやEventBridgeを使ったリアルタイム検知の仕組みが、早期対応には不可欠。 チームワーク : 「Incident Response is a TEAM SPORT!」というメッセージ通り、一人ではなくチームで対応することの重要性を学びました。 弊社でもAWS環境のセキュリティ強化を進めていますが、このWorkshopで学んだ実践的な知識を活かして、より堅牢なセキュリティ体制を構築していきたいと思います。 Accelerate platform engineering on Amazon EKS (CNS301-R1) SRE部 プラットフォームSREブロックのさかべっちです! 普段はZOZOTOWNのマイクロサービス基盤をEKSで構築・運用しています。 最先端のプラットフォームエンジニアリングを学ぶため参加してきました! 多くの人が帰国する最終日に開催されたセッションにもかかわらず、予約していない人はほとんど入れていなかったとても人気のセッションでした。 Workshopの概要 BackstageやArgo CDなどの最先端ツールやKiroを駆使して、ハンズオン形式で開発プラットフォームを構築するものでした。Workshopで使用したプラットフォームは、EKS Auto Mode上に構築されており、非常に豪華な構成でした。 ハンズオン形式で開発プラットフォームを構築するWorkshop 主なコンポーネントは以下の通りです。 Backstage:開発者ポータル Argo CD:GitOpsエンジン Argo Workflows:CI/CDワークフロー Argo Rollouts:Progressive Delivery KubeVela:OAM(Open Application Model)ベースのアプリケーション管理 kro:Kubernetes Resource Orchestrator Kargo:環境間のプロモーション管理 Amazon Q:生成AIアシスタント 今年発表されたEKS Capabilitiesについてもここで紹介されました。 発表されたばかりのEKS Capabilitiesについても紹介 Argo CD、ACK(AWS Controllers for Kubernetes)、kroがマネージドで提供されるようになり、運用負荷が大幅に下がりそうです。VPC CNIやFluent BitなどのEKS addonとは別物で、詳しくは下記のbreakout sessionで紹介されているのでご覧ください。 www.youtube.com プラットフォーム基盤構築 Workshopの前半は、Backstageを使ってプラットフォーム基盤を構築していく内容でした。Backstageを使うと、サービスカタログ、API一覧、ドキュメント、CI/CDパイプラインの状態などを一元管理できます。特に感動したのは、テンプレートからCI/CDパイプラインを数クリックで構築できる点です。Application Name、AWS Region、EKS Cluster Nameなどを入力して「CREATE」を押すだけで、GitLabリポジトリの作成からArgo CDの設定まで自動で行ってくれます。 たった数分でGitOpsワークフローの構築が完了する たった数分でGitOpsワークフローの構築が完了しました。GitOpsとは、インフラやアプリケーションの状態をGitで宣言的に管理する手法です。Gitにマニフェストをpushすると、Argo CDが差分を検知して自動でEKSクラスターに反映してくれます。 生成AIを活用したプラットフォームエンジニアリング Kiroをプラットフォームエンジニアリングに活用する方法も紹介されました。Kiroを活用して先ほどのプラットフォーム基盤にアプリケーションをデプロイします。 KiroにRustアプリケーションのDynamoDBテーブルとデプロイマニフェストを作成してもらいました。 Please use the Kubevela OAM component definition ...(中略) as a template and create a new Kubevela OAM component definition with the name of ddb-table using this dynamodb table CRD yaml Create an OAM manifest ...(中略) using Kubevela templates Include in strict order of dependency using dependsOn between components: - DynamoDB table with component-policy trait for permissions - Service account - Rust app (deploy last) - Path based ingress with /rust-app routing KubeVelaを用いたアプリケーションマニフェストが生成されます。KubeVelaを使うと、OAM(Open Application Model)に基づいてマイクロサービスを1つのファイルで定義できます。通常であればDeployment、Service、Ingressなどを個別に書く必要がありますが、KubeVelaのOAMを使えばこれだけでOKです。 apiVersion: core.oam.dev/v1beta1 kind: Application metadata: name: rust-application spec: components: - name: rust-webservice type: appmod-service properties: image: <image> image_name: java-app port: 8080 targetPort: 8080 replicas: 2 appPath: "/app" env: - name: APP_ENV value: "DEV" readinessProbe: httpGet: path: /app/ port: 8080 initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 5 resources: requests: cpu: "500m" memory: "256Mi" limits: memory: "512Mi" functionalGate: pause: "20s" image: "httpd:alpine" extraArgs: "red" performanceGate: pause: "10s" image: "httpd:alpine" extraArgs: "160" traits: - type: path-based-ingress properties: domain: "*.elb.us-west-2.amazonaws.com" rewritePath: true http: /app: 8080 また、kro(Kubernetes Resource Orchestrator)を使うと、cicdpipelinesやeksclusterといった新しいカスタムAPIを作成でき、KubernetesとAWSリソースのデプロイをさらにシンプルにできます。上記のコードをGitにpushすると自動でビルド・テスト・デプロイが実行されます。 git pushすると自動でビルド・テスト・デプロイが実行される Progressive Deliveryの実践 Workshopの後半では、Argo Rolloutsを使ったProgressive Deliveryを体験しました。Argo Rolloutsの詳細な設定はプラットフォームエンジニアが担当し、開発者はKubeVelaのコンポーネントを使ってgit pushするだけで段階的デプロイを実現できるようにします。 CI/CDパイプラインと組み合わせると、Progressive Deliveryは以下のようなフローになります。 開発者がGitリポジトリにコードをプッシュ Argo Workflowsがコードをコンパイル、イメージをビルド、ECRにプッシュし、マニフェストのイメージタグを更新 Argo CDがマニフェストの変更を検知し、DEV環境へのデプロイをトリガー Rolloutが新バージョンを20%に展開し、機能テストを実行 40%、60%、80%と段階的に展開し、パフォーマンステストを実行 テスト失敗時は自動で最後の安定バージョンにロールバック 実際にわざとエラーになる変更を入れてテストしてみました。 わざとエラーになる変更を入れてテストする revision:4のAnalysisRunが「Failed」となり、自動でロールバックされています。修正を加えて再度git pushすると、無事Successfulになりました。 修正を入れて再度git pushすると、無事Successfulになる DEV環境へのデプロイが成功した後はPROD環境へのデプロイです。DEVからPRODへの昇格管理にはKargoを使いました。Kargo UIで「Promote」をクリックすると、先ほどDEVでデプロイが成功したイメージを使用して、自動でK8sマニフェストを更新してくれます。 DEVでデプロイが成功したイメージを使用して、自動でK8sマニフェストを更新してくれる DEVで成功したイメージをそのままPRODに適用できるので、環境間の差異による問題を防げます。 さらに、DevOps Research and Assessment (DORA) メトリクスも自動で計測・可視化されます。これにより、チームや組織のソフトウェアデリバリのスピードと品質の改善点を特定できます。 チームや組織のソフトウェアデリバリのスピードと品質の改善点を特定できる 最終的に構築したDevOpsアーキテクチャの全体像は下記の通りです。 最終的に構築したDevOpsアーキテクチャの全体像 感想 Backstageの導入を検討していたのですが、実際に触ってみてCI/CDパイプラインをテンプレート化して開発者に提供できる点が便利であることを実感しました。また、EKS Capabilitiesで今後Argo CDなどがマネージドで提供されるようになると、プラットフォームの構築・運用コストが大幅に下がりそうです。 ただ、弊チームでは現在FluxCDを使ってGitOpsを運用していますが、今回のre:Inventの発表を見る限りArgo CDがかなり推されている印象だったので、中長期的にはArgo CD移行も視野に入れて検討していく必要がありそうです。今後様々なEKSエコシステムのEKS Capabilitiesも使えるようになると嬉しいです! WorkshopのソースコードはGitHubで公開されているので、興味のある方はぜひ試してみてください! github.com Using Amazon Q to Cost Optimize Your Containerized Workloads (CMP348) 引き続きさかべっちが参加したWorkshopをご紹介します! こちらはAmazon Qを活用してEKSワークロードをGravitonに移行し、コスト最適化を行うWorkshopです! 私が入社前のアルバイトをしていた頃に、チーム内でGravitonの移行について話題が上がっていたので、今後移行の予定ができたときのためにキャッチアップしておこうと考えて参加しました! AWS GravitonはAWSが独自開発したARMベースのプロセッサです。x86インスタンスと比較して最大40%優れたコストパフォーマンスを実現し、さらに最大60%少ないエネルギー消費という特徴があります。 Graviton移行の流れは以下の5ステップです。 Graviton移行の流れ Workshopの概要 サンプルのチャットボットアプリケーション(Java、Python、Go、.NET Coreから選択)をx86からGravitonへ移行する流れを体験しました(自分はGoを選択して進めました)。Amazon QにGraviton移行の専用agentがすでに組み込まれており、Amazon Qに質問しながら移行の分析や実装を進めていくスタイルでした。 Amazon Qによる移行分析 まず、Amazon Qに現状のEKSインフラがGraviton移行可能かを分析してもらいました。 Q. Analyze the EKS infrastructure for Graviton readiness すると、Karpenter NodePoolの設定やアプリケーションマニフェストの問題点、コンテナビルドの準備状況などを詳細に分析してくれます。さらに言語ごとの移行複雑度をスコアリングしてくれるので、どのアプリから移行すべきかの判断材料になりました。 コスト分析もAmazon Qにやってもらいます。 Q. Perform EKS Graviton migration cost analysis for my go app 現在のx86コストとGraviton移行後の予測コストを比較したレポートを作成してくれました。月額約14%の削減、年間では約$600の削減が見込まれるという結果でした。 負荷試験でGravitonへの移行の効果測定 Workshopでは事前に用意されていた負荷試験ツールを実行して、Graviton移行前後のパフォーマンスを比較しました。 Graviton移行前(x86) #[Mean = 2192.524, StdDeviation = 47.983] Requests/sec: 0.45 Amazon Qから言われた通りにGraviton移行で必要な下記の変更を加えてから負荷試験を実施します。 nodepool: kubernetes.io/arch に arm64 を追加 instance-familyを c7g に変更 Graviton移行後 #[Mean = 1593.935, StdDeviation = 57.964] Requests/sec: 0.63 レイテンシが約27%改善、スループットは約40%向上という結果になりました! Gravitonはコスト最適化のイメージでしたが、パフォーマンスもかなり向上していることを知りました。 段階的な移行アーキテクチャ 本番環境での移行時は、いきなり全てをGravitonに切り替えるのではなく、段階的に進めることが推奨されていました。これにより、問題が発生した場合でも影響範囲を限定できます。 Amazon Qに段階的な移行方法を教えてもらいます。 Q. Can you help me create a migration runbook based on the progressive Graviton migration? 3フェーズ(Dev → Staging → Production)の段階的移行計画、バリデーション基準、ロールバック手順まで含んだ手順を生成してくれます。 また、マイクロサービスごとの移行は下記の手順で実行します。 Phase 1: 移行用のGraviton NodePoolを用意し、pilot環境での移行検証 Phase 1: 移行用のGraviton NodePoolを用意し、pilot環境での移行検証 Phase 2: capacity-spreadの導入 Phase 2: capacity-spreadの導入 Phase 3: トラフィック分散 Phase 3: トラフィック分散 具体的には topologySpreadConstraints を使って、x86とGravitonの両方にPodを分散させながら徐々に移行していく方法です。 topologySpreadConstraints: - labelSelector: matchLabels: app.kubernetes.io/name: goapp maxSkew: 1 topologyKey: capacity-spread whenUnsatisfiable: DoNotSchedule 感想 Graviton移行用のagentを使うと、インフラの分析からコスト試算、実装の提案、段階的な移行まで一貫してサポートしてくれます。Graviton移行の取り掛かりとしてはハードルがかなり下がった印象です。 特に印象的だったのは、負荷テストでパフォーマンスが大幅に向上した点です。Gravitonは「コスト削減」のイメージが強かったのですが、実際にはレイテンシ27%改善・スループット40%向上と、パフォーマンス面でも大きなメリットがあることを体感できました。 弊チームではZOZOTOWNのマイクロサービス基盤をEKSで運用していますが、マイクロサービスごとに使っている言語が異なるため、今回学んだ段階的な移行アプローチは非常に参考になりました。 具体的には、環境ごと(Dev → Stg → Prd)、NodePoolごと(x86とGravitonを併用)に段階リリースしていく方法は、弊チームでも必須になると思います。移行の予定が決まった際はAmazon Qに現状のインフラを分析してもらい、移行の優先度を決めるところから始めてみたいと思います。 Infrastructure Innovations (KEY004) SRE部 基幹プラットフォームSREブロックの若原です。普段はZOZOの倉庫システムやブランド様向けの管理ページなどのサービスのオンプレミスとクラウドの構築・運用に携わっています。 現地時間12/4の朝に行われた、AWS SVPのPeter DeSantis氏とVice President of Compute and Machine Learning ServicesであるDave Brown氏によるKeynoteを聴講してきましたので、この基調講演の概要と印象に残ったポイントをお伝えします。 www.youtube.com 講演のはじめに 講演では、AIがクラウドと開発者コミュニティに何をもたらすのかという問いから始まりました。Peter氏はAIトランスフォーメーションがもたらす未来を語る前に、まず変わらない大切な価値に目を向けるべきだと強調しました。 どれだけ世の中が変化しようとも、AWSが創業以来一貫して大切にしてきたSecurity(セキュリティ)、Availability(可用性)、Elasticity(弾力性)、Cost(コスト)、Agility(俊敏性)の5つのAttributes(特性)は、クラウド基盤の核心であり、AI時代においてさらに重要になると述べていたのが印象的でした。 主なアップデート #1 Graviton5 AWSが自社開発する次世代CPU「Graviton5」が発表されました。高いコア数やキャッシュ性能を備えながら省電力・高性能を両立している点が特徴で、複雑なデータ処理やAIワークロードにも適した設計になっています。 主なアップデート #1 Graviton5 主なアップデート #2 Lambda Managed Instances Lambda関数をEC2インスタンス上で実行可能にする新機能として紹介され、サーバーレスの簡易さを保ちながらEC2の柔軟なインスタンスタイプ選択と割引価格モデルを活用し、柔軟なトラフィックワークロードのコスト最適化と性能制御を可能にすると説明されました。 主なアップデート #2 Lambda Managed Instances 主なアップデート #3 Vectors Amazon S3 Vectorsがネイティブなベクトルデータの保存・検索をサポートするクラウドストレージとして紹介され、生成AIや意味検索(RAG)など大規模AIワークロードのコストを最大90%削減しつつ効率的に処理できる基盤として発表されました。 主なアップデート #3 S3 Vectors ゲストセッション 新機能のアップデート以外にも、AppleのPayam Mirrashidi氏が登壇し、AWS Gravitonを活用してSwiftベースのバックエンドでパフォーマンスとコスト効率を大幅に向上させた事例を紹介しました。また、TwelveLabs社のJae Lee CEOはS3 Vectorsを使って大規模な動画データを効率的にインデックス化・検索するAIシステムの事例を共有し、さらにDecart社のDean Leitersdorf CEOはTrainium3を用いたライブ・ビジュアル・インテリジェンスの実演を披露するなど、大変興味深い内容となりました。 ゲストセッションの様子 講演を通じて感じたこと AWSはAI時代の最前線で先進的なサービス・機能を展開しつつも、クラウドの原点となる価値を創業以来常に大切にしているというメッセージが強く伝わってきました。 今回発表されたGraviton5、Lambda Managed Instances、S3 Vectorsなどは、単に追加された新機能ではなく、基本となる要素であるAgilityやCostといった基本価値をさらに強化するものに感じられました。今後のAWSとクラウドの進化がますます楽しみです。 セッションの様子 おまけ 講演の冒頭で、Peter氏は学部時代に愛読していた「CSの教科書」の第7版が最近刊行され、その中にNitroシステムとGravitonプロセッサに関する新しい章が追加されたことを紹介していました。 これを記念して、会場に投影されたQRコードを読み取った先着1000名に、その最新版の教科書を講演後にExpoホールでプレゼントするというサプライズ企画があり、私も運よく受け取れました。ひとつ想定外だったのは、喜んで受け取ったのは良いものの、この本がほぼ2kgある大著で思いのほか重く、帰りの預け荷物の重量制限が頭をよぎり少しハラハラしてしまいました。無事に持ち帰れたので、これから大切に読ませていただこうと思います。 サプライズ企画で手に入った「CSの教科書」第7版 Advanced AWS Network Security : Building Scalable Production Defenses [REPEAT] (SEC303-R1) AWS環境におけるネットワークセキュリティ強化方法を実践的に学ぶ2時間のWorkshopに参加しました。 最初に講師の方から約30分ほどの座学があり、AWS Network FirewallやDNS Firewallの基礎、今回のLabのアーキテクチャについて解説がありました。その後は用意された環境を使い、参加者が自分の手で設定を進めながら学ぶハンズオン形式で進んでいきます。 SEC303-R1セッションの様子 #1 主な内容 冒頭の座学では、オンプレミス環境のように単一のファイアウォールで全通信を集約するモデルではなく、AWSではユースケースごとに適切な境界を組み合わせて防御する、という設計思想が強調されていました。 本WorkshopではEgress(外向き通信)とEast-West(VPC間通信)に焦点が当てられ、それぞれの通信に対して多層的に検査・制御するセキュリティを構築するという内容でした。 Ingress(内向き通信)ではWAFによるアプリケーションレイヤー保護が主役となる一方、本Workshopで取り扱ったEgressやVPC 間通信ではAWS Network FirewallとRoute 53 Resolver DNS Firewallの2つのサービスが中心的な役割を果たし、それぞれどのように組み合わせて環境を保護すべきかについて学びました。 SEC303-R1セッションの様子 #2 AWS Network Firewallとは AWS上のネットワークトラフィックを保護するためのマネージド型ファイアウォールサービスで、AWS環境のネットワークを総合的に守るためのセキュリティ基盤として利用できるサービスです。侵入防止(IPS)、ステートフル/ステートレスルール、ドメインフィルタリングといった機能を備えており、既存のセキュリティ運用に合わせた詳細なトラフィックの検査が可能です。Suricataというルール形式に対応している点も特徴で、既存のセキュリティ運用で利用している独自ルールやコミュニティルールをそのまま適用できます。 Route 53 Resolver DNS Firewallとは AWS環境内のDNSクエリを保護するためのマネージド型ファイアウォールサービスで、悪性ドメインへのアクセスやDNSを悪用した攻撃をDNSレイヤーで防止するための基盤として利用できます。AWSが提供する脅威ドメインリストや独自のカスタムリストを使ってドメイン単位で通信を制御でき、DNSトンネリングや不正なクエリを早期に検知・遮断できます。VPCのRoute 53 Resolverと統合されており、アプリケーションを変更することなくDNSトラフィックを一元的に監査・制御できるのが特徴です。 Workshop参加者には複数のVPCがTransit Gatewayで接続された環境を渡され、その上でネットワークセキュリティ構築を段階的に体験できる内容となっていました。 Labのアーキテクチャ図 Labのアーキテクチャ図 VPCの内部から外向きの通信状態を確認するテストスクリプトが用意されており、Labの開始時点では、画像のようにあらゆる通信が通ってしまう状態になっています。 Labの開始時点では、あらゆる通信が通ってしまう状態になっている ここからAWS Network FirewallとDNS Firewallを使って、攻撃に悪用されるポイントを1つずつ対策しながらセキュリティを強化していきます。 AWS Network Firewallでの通信制御 ここでは例としてAWS Network FirewallのGeoIP機能を使って、特定の国や米国・カナダ以外への通信をブロックするルールを追加し、地域ベースでの通信制御を実践します。 コンソール上のVPCサービスから、Lab用に用意されているFirewallを選択します。Firewall policy settings→StatefulRuleGroupへ移動し、RulesセクションからEditします。 Edit rulesからルールを追加する USとカナダ以外への通信をブロックするSuricataルール表記を追記してSaveします。 # Block Traffic To/From Any Country Besides the US or Canada drop ip any any -> any any (geoip:any,!US,!CA; msg:"Drop traffic to countries other than US and Canada"; sid:10000009;) 1分ほど待つとRuleが適用され、先ほどまで通っていたcurlコマンドが無事Blockされるようになりました。 curlコマンドがBlockされるようになった DNS Firewallでの通信制御 次に、DNS Firewallを用いて、独自のドメインブロックリストを作成し、攻撃者に悪用されやすいTLDをまとめてブロックする設定例を紹介します。 VPCの画面からDNS Firewallを選択、Rule groupsへ移動し、新規でRule groupを作成します。作成したRule groupに対してAdd ruleでブロックしたいドメインのリストを記載します。 Rule groupを作成する 設定例は以下の通りです。 Rule name: <Rule名は自由に設定> Domain list: Custom domain list and Create new domain list Domain list name: Domains: 以下を記載 *.ru *.cn *.xyz *.cyou *.pw *.ws *.gq *.surf *.cf *.ml Domain redirection setting: Select Inspect all (Default) Action: Block Block response: OVERRIDE Record type: CNAME Record value: dns-firewall-block TTL (seconds): Enter 0 適用後、該当ドメインへのリクエストがBlockされることを確認できました。 該当ドメインへのリクエストがBlockされることを確認できた Workshopまとめ 今回紹介した内容以外にも、プロトコルとポートのミスマッチを利用した攻撃を検出するPort/Protocol Enforcementや、AWS Managed Domain Listsを利用したDNS Firewallの設定などもありました。 また、上記で設定した検知のログをCloudWatch Logsで確認したり、VPC間通信のトラフィック制御を試したりするなど、他にもいくつかのシナリオを想定したLabもありました。内容が盛りだくさんで時間内に到底収まらないほどのボリュームでした。 普段意識しづらい外向き通信のネットワークセキュリティについて、実際に手を動かして学ぶ、とても良い機会となりました。 The Kiro coding challenge (DVT317) ZOZOMO部 SREブロックの中村です。ZOZOMOなどのマイクロサービスのSREを担当しています。ZOZOMO部ではAI Agentを活用した業務の効率化や本番運用にAIへの積極的活用に力を入れており、re:Inventでは多数のAgentサービスの新発表や既存サービスのアップデートが行われ、それに関するセッションも多く行われました。 Kiroを用いた仕様書駆動開発のWorkshopに参加したのでそのセッション内容をご紹介します。また、「Ditch your old SRE playbook: AI SRE for root cause in minutes」のセッションも合わせて紹介します。 Workshopの概要 このWorkshopは、KiroというエージェントネイティブIDEを使った実践的なコーディングチャレンジです。難易度が徐々に上がる10個のコーディングチャレンジをKiroの支援を受けながら課題を解決していきます。参加者は約70〜80人ほどで、他のWorkshopと比較してもとても人気のセッションとなっていました。 The Kiro coding challengeの会場 Workshopの冒頭ではKiroや仕様書駆動開発についての簡単な説明が行われました。仕様書駆動開発では、PLANNING DESIGN → IMPLEMENTATION → TESTING & QA まで一貫してカバーし、仕様を先に定義してから開発するので、手戻りが少なく高品質な製品を出荷できるような説明が図とともに行われました。 仕様書駆動開発に関する説明 実践 Kiroの説明と、Workshopの説明が終わった後、AWSが用意してくれたWorkshopの環境に接続し、今回のWorkshopの概要等を確認できます。必要に応じてセットアップなど行なっていきます。 今回のWorkshopでは1から10のタスクが用意されており、それらをKiroを使って解決していきます。 Workshopの様子 #1 Workshopの様子 #2 タスクを解決するプロンプトをKiroで叩き、解決していってもらいます。序盤はプロジェクトのセットアップをメインに行うタスクが多く存在しました。 タスク6では、ついに仕様書駆動開発を使用するタスクがでてきました。Kiroに対し、仕様を伝えそれを仕様書として作成するように依頼することで、Kiroの管理下に仕様書が作成されます。仕様書を作成したのち、Kiroは仕様書を元に実装を行なっていきます。 Workshopの様子 #3 Workshopの様子 #4 タスク8では、AgentHooksを利用しコードの変更をトリガーに、対応のドキュメントを併せて更新するような指示を追加できました。AgentHooksを利用するとドキュメント更新以外のアクションも実行できるので色々と夢が広がる機能ですね! Workshopの様子 #5 また、このWorkshop中は常にTOP10の順位が前のスクリーンに投影されていて、エンジニア全員で競い合い会場が熱気に包まれていました。 Workshop中は常にTOP 10までの順位がスクリーンに投影されていた まとめ 本セッションを通じて、仕様書駆動開発がどのように行われていくのか、またKiroを利用した仕様書駆動開発をどのように行なっていくのかを学べました! 実際にWorkshopで利用したリポジトリはこちらです。どのような仕様書が作られたか気になった際はぜひ確認してみてください! github.com Ditch your old SRE playbook: AI SRE for root cause in minutes (sponsored by Resolve AI) (AIM260-S) 概要 このセッションでは、 Resolve AI社 により、SRE領域におけるAIエージェントの活用についてBreakout session形式で実施されました。本番環境の運用やインシデントの根本的な原因の分析を数分で実行するなど、開発でなく運用面にフォーカスをおいていました。SREという文字が含まれるセッションは少なく、こちらもとても人気でした。 AIM260-S セッション会場の様子 内容 ソフトウェアエンジニアリングの進化 セッション冒頭では、数年ほど前からAIがソフトウェアエンジニアリングに与えている大きな影響、進化を3段階に分けて説明されました。 1段階目ではAIがコードを書くのでなく、人間が書いたものを自動補完するようなところから始まり当初は人間がオペレーターとなって作業を行なっていました。 2段階目ではAgentが登場した頃で、AIがオペレーターとなり作業し、それを人間が管理、監視するという、現段階で多くのエンジニアが行なっていることなのだと思います。今はこの段階の進化レベルにあると考えられています。 3段階目はすでに移行の過程である、またはこれからAIの進化が到達すると考えられており、AIが開発だけなく、今までエンジニアが扱ってきた様々な監視ツールや本番でのインフラ構成など運用に必要なあらゆるツールを人間同様に使いこなせるという話がされました。 セッションの様子 AI for prod Resolve AI社が提供するAI Agentを用いて実際にインシデント対応するデモが実施されました。このAgentは事前にアプリのコードはもちろん、インフラの構成等全てを知識として把握しています。GrafanaからSlackのアラートチャンネルにエラーが通知された時にそれをトリガーにAgentが起動しエラーを調査します。 Slackでは何が原因でどうすれば解決できるかを提案してくれます。しかしなぜその結論になったのかを知りたい場合は、Resolve AIのWeb UIを確認しAgentがどのような調査を行なったか詳細な情報を確認できます。 デモでの調査内容は次の通りです。 ログクエリの生成 履歴の確認 トレースの調査 インフラストラクチャイベントの確認 チャート・ダッシュボードの分析 コードのチェック Resolve AIの3つの重要な特徴 セッションではResolve AIの構築にあたり、3つの重要な特徴について説明されました。特にマルチエージェントを生かして本番環境での実用性を説明しているように感じられました。 理解(Understand) Knowledge graphを用いてシステム全体を理解する 優秀なプリンシパルエンジニアが持つような知識を再現 システムの歴史 障害パターン 過去のポストモーテム 学習(Learn) ツール使用履歴からの学習 ダッシュボードの使用頻度など、ツールの使われ方から学習 アラートの優先度ラベル(P1など)を評価基準として活用 インシデントチャンネルでの議論から原因究明・修復プロセスを学習 チーム・組織レベルでカスタムコンテキストを追加可能 推論(Reason) マルチエージェントアーキテクチャ プランナーが「何をすべきか」を判断 専門化されたサブエージェントがエビデンスを収集 レート制限やペイロードの処理を理解 人間のフィードバックループを組み込み、継続的に改善 AIエージェント構築のアプローチ比較 このセクションでは、インシデント対応を自動化するAIエージェントの構築には、大きく4つのアプローチがあると説明をされました。セッションを聞いた内容を簡単に要約してみました。 LLMs エラーをそのままLLMに投げる最もシンプルな方法。時には正解するが、複雑なシステムではコンテキスト不足で機能しない。 セッションの様子 LLMS + tools(MCP) MCPサーバー経由でObservabilityツールにアクセス。データは取れるが、状態管理がなく「1時間分見るべきか、1週間か」の判断ができない。 Single Agent ランブックやコンテキストを詰め込んだ単一エージェント。肥大化して遅くなり、未知のインシデントに対応できなくなる。 Multi-Agent Systems 専門化されたエージェント群の協調動作。最も柔軟だが、オーケストレーションの複雑さがマイクロサービス同様の課題になる。 セッションの様子 感想 今回のセッションでSREにフォーカスを当てたAIエージェントのサービスを初めて知りました。確かにインシデントの調査や一部対応は既存のAIエージェントでもできますが、既存の情報のみでしか判断等できずに自律的な成長などをエージェントにさせようとすると、とても難しいことだと改めて感じました。 セッションの内容はAWS公式のYouTubeチャンネルに公開されているので、さらに細かい内容を知りたい方はぜひそちらの動画をご覧ください! www.youtube.com [NEW LAUNCH] Resolve and prevent future operational issues with AWS DevOps Agent [REPEAT] (DVT337-R1) 計測システム部 バックエンドブロックの髙橋です。普段はZOZOMETRY、ZOZOGLASS、ZOZOMATなどの計測プロダクトのバックエンドの開発・運用を担当しています。 今回、Keynoteで新たに発表された「 AWS DevOps Agent 」について、Keynoteをリアルで聞きながら興味を持ちました。発表後にハンズオンを受けてきましたので、ハンズオンの内容とAWS DevOps Agentの使いどころについてご紹介します。 まず、AWS DevOps Agent(以下、DevOps Agent)とは、今回のre:Inventで発表された Frontier Agents の一角を成すAIエージェントです。主に運用上発生する障害などのインシデントに対し迅速に原因を調査し、それに対する解決策を提案してくれます(2025年12月の記事公開時ではPublic Preview)。その上で、今回のハンズオンでは用意されたシナリオを通じ、DevOps Agentの実際の使い所について学んできました。 AWS DevOps Agentができること - AWS上のリソースの可視化 現在ログイン中のアカウントのAWSリソースのトポロジが表示される DevOps Agentでは、最初のステップとしてAgent Spaceというスペースを作成します。作成し、用意されたWebポータルにアクセスすると、現在ログイン中のアカウントのAWSリソースのトポロジが表示されます。 AWS DevOps Agentができること - 障害調査 今回のWorkshopでは、DynamoDBの書き込みキャパシティを意図的に大幅に絞り、システムに障害を発生させました。すると、予めセットされていたCloudWatch Alarmが発報します。 予めセットされていたCloudWatch Alarmが発報する 発報された後、DevOps Agentの「Start an investigation」ページで、「Investigate the DynamoDBWriteThrottleAlarm(CloudWatchで発報されたアラーム名)alarm」と入力し、調査してもらいます。 Start an investigationで調査してもらう 調査が進むと、「Root cause」という部分でDynamoDBの書き込みキャパシティの小ささが根本原因として書かれていることがわかります。 DynamoDBの書き込みキャパシティが小さい旨が根本原因とわかる さらに、「Mitigation Plan」で実際の復旧に必要な作業の計画を立ててくれます。 実際の復旧に必要な作業の計画を立ててくれる このように、今までは人間がやっていた障害内容の調査→原因究明→復旧のための計画までをDevOps Agentにアウトソースできます。 実際の使い所 DevOps Agentでは人間が先ほどの画像のように指示して調査させるだけではなく、Datadogなどのツールから異常検知されたことをトリガーとして自動的に調査を開始できます。 docs.aws.amazon.com 実際の運用現場では、アラートが鳴り、人間が気付いてからの初動対応は数秒レベルの即時とはいかない場合が多いですし、その時のメンバーによってシステムに対する習熟度や調査時に見る観点も違うでしょう。そのような場合にDevOps Agentが先回りして即時に自動で調査を開始し、人間に対して原因と復旧計画を提供することによってより高品質かつ、迅速な障害対応を行えると考えています。 現在DevOps Agentは現在Public Preview期間であるため、いきなり本番環境に組み込まずともチームのカオスエンジニアリング・障害訓練等からまず使ってみることもおすすめです。チームでのカオスエンジニアリングの記事は以下をご覧ください。 techblog.zozo.com おわりに セッションや展示ブースで多くのことを学べるのはもちろん、AWSのエキスパートや他社のエンジニアの方々と交流し、多くの刺激を受けられるのが現地参加の醍醐味です。今回得た知見を社内外に共有しながら、これからもAWSを活用してプロダクトとビジネスの成長に貢献していきます。 ZOZOではAWSが大好きなエンジニアを募集しています。奮ってご応募ください! corp.zozo.com
アバター
Developer Engagementブロックの @ikkou です。2025年もいよいよ終わりに近づいてきました。この季節の風物詩、「アドベントカレンダー」には皆さんも参加されましたか? ZOZOは例年アドベントカレンダーに参加し、2020年以降、記事数を100本→125本→175本→225本→275本と増加、そして今年は過去最高の計300本の記事を公開しました! 本記事ではその概要をお伝えします。 ZOZO Advent Calendar 2025 今年は合計11個のカレンダーを完走し、12月1日から25日の間に300本の記事を公開しました! 🎅 𝑯𝒂𝒑𝒑𝒚 𝑯𝒐𝒍𝒊𝒅𝒂𝒚𝒔 🎄 ZOZOの愉快なエンジニアたちによるアドベントカレンダー『ZOZO Advent Calendar 2025』の全300記事を完走しました🎉 今年も多種多様な記事が公開されました! 時勢を反映してか 80/300 前後はAI関連の記事でした! https://t.co/E9h4a6hxG5 pic.twitter.com/IcMICM0kJ3 — ZOZO Developers (@zozotech) 2025年12月25日 qiita.com 実施概要 ZOZO Advent Calendar 2025は以下の形式で運用しました。 形式 : 任意参加 運用方法 : Slackチャンネルで実施と参加を呼びかけ、各自が空いている日に登録 公開先 : ZOZO TECH BLOG、Qiita、Zenn、note、個人ブログなど 参加人数 : 130名 公開記事数 : 合計300本 もっとも多くの記事を書いたのは今年もshiozakiさんで、今年は計30本の記事を公開して3年連続で最多記録を更新しています。特に「シリーズ 5」は「C言語以外の◯言語でHelloWorld!を書いてみる」シリーズとして、ひとりで25記事を完走しています。 また、シリーズ 11では YasuhiroKimesawa さんが、スクラム関連に特化してひとりで25記事を完走しています。 そして、昨年に続き今年もチーム単位でひとつのカレンダーを担当する動きも見られました。 シリーズ 2: データ基盤ブロック シリーズ 4: カート決済SREブロック シリーズ 7: 計測システム部 シリーズ 8: 推薦系(推薦研究、MA推薦、推薦基盤) シリーズ 10: 25新卒 特定の技術領域に興味がある方は、ぜひ各シリーズの記事をチェックしてみてください。チームの雰囲気や勢いが伝わってくると思います。 アドベントカレンダーは、アウトプットの練習や執筆スキルを高める絶好の機会です。ZOZOではテックブログをアウトプットの主軸に置いていますが、「まだテックブログを書く自信が無い」「テックブログに書くにはネタが小粒」のような場合に、アドベントカレンダーは良い機会です。 運営目線では今年の見どころをDay 1の記事として公開しています。 zenn.dev 2025年の振り返り ZOZOのアドベントカレンダーでは例年その年を振り返る記事を公開しています。 開発組織の振り返り ZOZOの開発組織については、執行役員 兼 CTOの @sonots さんが例年恒例となっている「振り返りと現状」を記事にまとめています。 qiita.com 推薦基盤チームの取り組み 推薦基盤チームに特化した「振り返りと現状」も記事にまとまっています。 qiita.com 2025年の注目記事 ZOZO Advent Calendarは「バズる」ことを目的としていませんが、それでも多くの方に読まれた記事があります。特に注目された記事を2つ紹介します。 1つ目はZennで公開されたrem( @remsleep_zzz )さんによる、Reactの状態管理の歴史を振り返る記事です。技術的な深掘りと歴史的な背景の両方がバランスよく盛り込まれていて、多くの方に読まれました。 zenn.dev 2つ目はQiitaで公開されたRu Ito( rabbit_x86 )さんによる、VS Code拡張機能19選の記事です。開発効率化に直結する内容で、目に留まりやすかったことが要因と考えられます。 qiita.com 2025年の記事傾向 全300記事のタイトルをChatGPTさんに分析してもらったところ、以下のような傾向が見られました。 まず、全300記事のうち、AI関連の記事は約80本と全体の27%を占めていて、時流を色濃く反映したものになっていました。ZOZOでは7月に開発AIエージェントを全エンジニアに導入していて、その影響も大きいと考えられます。Claude Code関連の記事だけでも28本ありました。 corp.zozo.com 次点では、クラウド・SRE・運用系が20%、BigQueryを含むデータ基盤系が15%と続いています。 AI関連に関しては、進化の速度があまりにも早く、来年どのような状況になっているか想像がつきません。しかし、今年も既にそうであるように、AIを活用した開発効率化やプロダクト価値向上の取り組みが中心になると考えています。 過去のアドベントカレンダー ZOZOでは2018年から毎年アドベントカレンダーに参加しています。過去の取り組みは以下をご覧ください。 年 カレンダー 2024年 ZOZO Advent Calendar 2024 2023年 ZOZO Advent Calendar 2023 2022年 ZOZO Advent Calendar 2022 2021年 ZOZO Advent Calendar 2021 2020年 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 4 2019年 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 4 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 5 2018年 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 3 最後に ZOZOでは、プロダクト開発以外にも、アドベントカレンダーのような外部への発信も積極的に取り組んでいます。 一緒にサービスを作り上げる仲間をはじめ、エンジニアとしての技術力向上や外部発信に意欲的な方を積極的に募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
はじめに こんにちは、AI・アナリティクス本部データサイエンスブロックの大戸徳仁です。普段は、サービスや機能の現状把握・要因分析、施策の効果検証、需要予測モデルの開発・運用などを担当しています。私が所属するチームでは、「データに基づいた意思決定を支援すること」をミッションに、社内の各部門に対してデータ分析サービスを提供しています。 その取り組みの一環として、ZOZOの物流拠点「ZOZOBASE」のデータ活用に取り組んでいます。中でも、出荷計画や人員配置の判断材料となる「注文数の予測」については、予測精度が安定しない、予測工数がかかるといった課題があります。本記事では、これらの課題に対してどのようにアプローチしたのか、そしてプロジェクトを通じて得られた気づきについて紹介します。 目次 はじめに 目次 背景 課題 課題解決アプローチ 1.評価指標の設計 2.EDA(探索的データ分析) 3.予測モデルの構築 4.予測モデルの評価 リリース・仮運用 課題解決への第一歩 仮運用を通じて見えた新たな課題 次の改善フェーズに向けて まとめ 参考文献 背景 ZOZOの物流拠点であるZOZOBASE(以下、BASE)では、毎日膨大な量の注文が処理されています。その中で、日々の注文数をどれだけ正確に予測できるかは、BASEの業務を安定的に運営するうえで非常に重要です。注文数の予測は、次のような多くの意思決定の基盤となります。 日々のピッキング・出荷量の計画 出荷量に応じた人員配置や作業負荷の調整 運営体制の設計 これらの意思決定を適切に行うためにも、日々の注文数をブレなく予測できる状態が望まれます。 注文数予測は、物流現場の運営を支えるだけでなく、お客様へ商品を遅延なく届けるための体制づくりにも直結 します。 こうした背景から、過去データをもとに注文数を予測するモデルを構築し、現場の業務フローに組み込むプロジェクトを開始しました。 なお、 今回のプロジェクトは、毎日決まった時間に、翌日から7日後までの日次の注文数を予測する ことが要件です。翌日単体の予測だけでなく複数日先を同時に予測するため、トレンド・周期性・イベント影響など複数要因を適切に扱う必要があります。 課題 注文数予測が現場の重要な意思決定の基盤である一方、従来の手動方法にはいくつかの課題がありました。ここでは、モデル構築へ着手する前に明らかとなった課題を整理します。 課題1:予測に工数がかかる 従来の予測は現場担当者が手動で行っており、日々一定の工数が発生していました。予測作業そのものが現場担当者の負担となり、本来の出荷業務へ時間を割きづらい状態となります。 課題2:予測精度が安定しない 注文数は、次のような多くの要因によって日々変動します。 曜日ごとの周期性 セールやキャンペーン、クーポン施策の有無 天候や季節変動 これらが複合的に作用するため、担当者が手動で補正し続けるには限界があります。誤差が大きくなるだけでなく、そのばらつきも大きくなり、予測が安定しない状況となっていました。予測値が実績値を上回る(予測>実績)場合は、人員余りによるコスト増につながります。一方で、予測値が実績値を下回る(予測<実績)場合は、人員不足を招き、発送遅延につながる重大なリスクとなります。 課題3:属人的で再現が難しい 手動での予測では、担当者ごとに「こういう日は増えやすい」といった、経験値に基づく判断が行われており、予測プロセスが属人的となっていました。そのため、同じ条件でも担当者が変わると予測値が変わったり、予測を外した場合でもどの判断が影響したのかを振り返りづらくなったりと、改善につなげることが難しくなります。 これらの課題を解決するために、 日々の注文数を自動で予測でき、かつ精度や傾向を継続的に評価・改善できる仕組みを構築 する必要性が明確になりました。 課題解決アプローチ 本章では、前章で整理した課題を踏まえ、どのような考え方で評価指標を設計し、データを分析したうえで予測モデルを構築・評価したのかを紹介します。 評価指標の設計 EDA(探索的データ分析) 予測モデルの構築 予測モデルの評価 1.評価指標の設計 まず、モデル構築に先立ち、 どのような予測であれば「良い」と言えるのか を明確にしました。予測精度の良し悪しは評価指標の設計によって大きく左右されるため、ここを最初に定義することが重要です。 今回は、次の理由からMAE(日別の絶対誤差の平均値)を評価指標として採用しました。 誤差を「件数ベース」で把握でき、現場担当者にとって直感的 外れ値の影響を受けにくく、平常日の誤差が素直に評価できる BASEの業務では「何件ズレるか」がそのまま人員計画や作業負荷に影響する MAPEやRMSEといった他の指標もありますが、「日々の作業量を安定的にコントロールする」という目的に対しては、MAEが最も適していると判断しました。 また、BASEの業務では、日別の誤差は小さくても、 予測値が実績値を下回る状態(予測<実績)が連続すると発送遅延につながる重大なリスク となります。そのため、単なる誤差の大小だけでなく、 どの方向に外れたか も含めて評価する方針としました。 2.EDA(探索的データ分析) 評価指標を定義したうえで、次に取り組んだのがEDA(探索的データ分析)です。ここでは、目指す精度水準を達成するうえで、 注文数データの特徴や構造を把握 することを目的としています。EDAの結果、注文数の変動には「曜日による周期性」「セールやキャンペーンなどのイベント影響」「直近の注文動向」といった要因が絡み合っていることが分かりました。 ※本記事のグラフは、構造を説明するための仮想データを用いており、実際の注文データではありません。 時系列データの変動成分 時系列データは、3つの変動成分で構成されています。 トレンド成分 周期成分 残差(ノイズ)成分 変動成分を分解する方法はいくつかあり、代表的なものとしては次のようなものがあります。 移動平均法を利用した分解(加法モデルを仮定) 移動平均法を利用した分解(乗法モデルを仮定) STL分解(Seasonal Decomposition Of Time Series By Loess) 今回は、日次データを1週間周期(period=7)でSTL分解し、各成分を確認しました。STL分解は外れ値に強く、周期性の強さが時期によって変化するようなデータにも柔軟に対応できるためです。 ■トレンド 年間を通じた大きな波が見られ、中長期的な需要変動を確認できます。 ■周期性 曜日による周期性が強く、1週間のリズムが明確に現れています。また、夏から冬に向けて周期成分の振れ幅が徐々に大きくなっている構造も読み取れます。 ■残差(ノイズ) ランダムな揺らぎに加えて、イベント時には大きな変動が生じており、トレンドや周期性だけでは説明できない要素も存在します。 自己相関(ACF)と偏自己相関(PACF) 自己相関(ACF):現在の値と過去の値との相関 偏自己相関(PACF):間接的な影響を除いたうえでの、直接的な相関 例:今日の値が一昨日の値と相関しているように見えても、実際には「一昨日 → 昨日 → 今日」という形で、昨日を介した間接的な影響による相関の場合がある。偏自己相関では、こうした間接的な影響を取り除いた相関を確認できる。 ■自己相関(ACF) 注文数は「同曜日(ラグ7の倍数)」で強い相関が見られます。 ■偏自己相関(PACF) 間接的な影響を除いた場合、「同曜日(ラグ7,14,21,28)」の影響が顕著なため、週次の周期性を明示的に扱えるモデル構造が、予測精度の安定化に重要だと考えられます。 3.予測モデルの構築 予測モデルの構築では、前節までで整理した評価方針とEDAの結果を踏まえ、「どのモデルを採用するか」「どのように学習・検証するか」という2つの観点を中心に検討を進めました。ここでは、実際に行ったモデル選定と学習・検証のアプローチを紹介します。 モデル選定 今回はProphetを採用しました。主な理由は以下のとおりです。 週次や年次の周期性を標準機能として扱える トレンドの変化点を検出できる 休日やイベント情報を組み込みやすい モデル構造がシンプルで、予測根拠を現場担当者へ説明しやすい 1週間先までの複数日予測を、特別な工夫を加えずに自然に扱える これらの特性は、ZOZOの注文数データの構造と業務要件の双方と相性が良いと判断しました。 モデル構築 時系列予測では、「過去データで学習し、未来データで評価する」ことが重要です。ランダム分割すると未来情報が混入し、実運用に近い精度を正しく評価できなくなるため、今回は、学習期間を伸ばしながら評価するエクスパンディング型のクロスバリデーション法を採用しました。具体的には、次の流れでデータを扱っています。 データを時系列順に分割し、前半を学習+検証データ、後半を最終評価用データとして使用 前半データに対して、学習期間を伸ばしながら、「学習 → 直後1週間の予測 → MAE算出」を1年分(52週分)繰り返す 52週分の評価結果を平均し、年間を通した頑健性を評価 この枠組みのもとで、Prophetの性能を最大限引き出すため、Optunaを用いて次のハイパーパラメータを最適化します。 changepoint_prior_scale :トレンド成分の柔軟性 seasonality_prior_scale :季節成分の柔軟性 seasonality_mode :季節成分の種類(加法/乗法) changepoint_range :トレンド変化点候補のレンジ n_changepoints :トレンドの変化点の数 Optunaでは、学習期間を伸ばしながら1年分(52週分)の評価で得られた平均MAEを最適化指標とし、値が最も小さくなるパラメータセットを採用しました。 4.予測モデルの評価 事前に定義した評価方針をもとに、構築したモデルが業務で利用可能かどうかを、テストデータを用いて確認します。BASEの業務では、日別の誤差は小さくても、 予測値が実績値を下回る状態(予測<実績)が連続すると発送遅延につながる重大なリスク となります。そのため、日別の誤差に加えて、次の観点でもモデルを評価しました。 週単位での誤差Σ(日別の誤差)を確認 予測値が実績値を下回る(予測<実績)ケースが少ないモデルを評価 特定曜日・特定イベント期間に偏りや弱点がないか これらの観点をもとに、BASEの担当者と誤差の傾向を確認しながら、「特徴量の調整→再学習→評価」の改善サイクルを繰り返し、日々の運用で活用できる水準へとモデルをブラッシュアップしました。 ここから、次章で紹介する仮運用フェーズへ進みます。 リリース・仮運用 予測モデルが業務で利用できる水準まで仕上がったため、評価時に分割していたテスト期間も含めた全データでモデルを再学習し、日次で予測値を更新できる環境を整えました。こうして、モデルが現場で実際に活用できるかを確かめる仮運用フェーズへ移行しました。本章では、仮運用を通じて得られた気づきと、次の改善に向けた取り組み内容について紹介します。 課題解決への第一歩 仮運用を進める中で、手動予測と比較して日々の誤差のばらつきは小さくなり、予測の安定性が向上していることが確認できました。注文数を「自動で」かつ「一定の精度で」予測できるようになったことで、現場担当者は本来の出荷業務へ集中しやすい環境に近づいています。一方で、仮運用を通じて、予測対象の捉え方まで含めて精度を高めていく必要があることも見えてきました。 仮運用を通じて見えた新たな課題 BASEの業務では、注文が受注順に発送されるとは限りません。日付指定・取り寄せ・予約注文など、お客様からの注文日とBASEからの発送タイミングがずれるケースは多くあります。その結果、次のような日が発生します。 お客様からの注文数は少ないのに、発送すべき注文が多い お客様からの注文数は多いのに、発送すべき注文が少ない こうした特性を踏まえ、BASEでは「お客様から入る注文数」をそのまま扱うのではなく、「お客様へ発送可能な状態の注文数」へ変換したうえで出荷計画や人員配置をしています。仮運用を通じて、「お客様へ発送可能な状態の注文数」へ変換するロジックが従来のままでは、改善効果を十分に引き出せないことが分かってきました。 現場で最終的に必要としている数字は、「何件注文が入るか」ではなく「何件発送できるか」 という視点です。そのため、変換ロジックを含めた一連のプロセスとしての見直しが必要となります。 次の改善フェーズに向けて 前節で見えた新たな課題を踏まえ、現在は次のような取り組みを進めています。 日付指定・取り寄せ・予約注文などの比率分析 注文が「発送可能」になるまでのリードタイム分析 お客様からの注文数予測と発送可能な注文数の統合 お客様から入る注文数の予測モデルの高度化にとどまらず、現場の業務判断にそのまま活用できる形へつなげる仕組みとして、改善を継続しています。 まとめ 本記事では、BASEにおける注文数予測の課題に対して、 「業務で使える予測とは何か」 という観点から、モデル構築プロセスと仮運用を通じて得られた気づきを紹介しました。まだ改善半ばではありますが、従来は手動で行われていた注文数予測をモデル化することで、予測精度のばらつきや属人性といった課題を解消し、日々の注文数を安定して予測できる環境へと近づいています。 今回の取り組みを通じて改めて感じたのは、 データ分析や予測モデルは「高い精度を出すこと」自体が目的ではなく、データに基づいた意思決定を支援して初めて価値を発揮する という点です。予測精度が高くても、業務特性上のリスクが高まってしまったり、そもそも現場が必要としている指標と一致していなければ、実務での効果は限定的になります。事前に課題やビジネス要件を整理し、実務に即した形で改善を重ねていくことが、結果としてお客様価値やビジネスインパクトにつながると考えています。今後もBASEと連携しながら、 現場の意思決定に使える予測 を目指して、継続的に改善を進めていきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 参考文献 Pythonによる時系列分析
アバター
.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、EC基盤開発本部SRE部の金田、 花房 、松石です。普段はSREとしてZOZOTOWNのインフラ運用や開発を担当しています。 ZOZOでは gatling-operator をOSSの負荷試験ツールとして公開・運用してきました。しかし、Gatling本体の破壊的変更やメンテナー不足といった課題に直面し、新たな負荷試験ツールとして k6 の導入を進めています。 本記事では、gatling-operatorが抱えていた課題と、k6への移行に至った経緯、そしてClaude Codeを活用した既存シナリオの移行方法についてご紹介します。 目次 はじめに 目次 背景・課題 gatling-operatorとは Gatlingの破壊的変更 Gatling 3.11での変更 Gatling 3.12での変更 メンテナー不足 検討した対応策 なぜk6を採用したのか 要件 k6を採用した理由 導入にあたっての課題と工夫 共通ライブラリの実装 Gatlingのfeeder機能とは k6標準機能の課題 feederライブラリの実装 シナリオ参照方法の検討 採用したアプローチ:SharedVolume & LocalFile Git連携によるシナリオ管理 ディレクトリ構成 initContainerによるシナリオ取得 GitHub認証 Datadogによる試験結果の可視化 k6標準のレポート機能の課題 Datadogダッシュボードの採用 Datadogへのメトリクス送信 OpenTelemetry方式での送信 メトリクスへのタグ付け ダッシュボードの構成と拡張性 Claudeサブエージェントを使ったシナリオ移行 Claudeサブエージェントとは シナリオ移行での活用方法 どのように実現しているか 移行結果と効果 サブエージェントを使う上でのポイント おわりに 背景・課題 gatling-operatorとは gatling-operator は、ZOZOがOSSとして公開している負荷試験ツールです。 Gatling をKubernetes環境で実行するためのKubernetes Operatorとして実装されており、分散負荷試験の実行やレポートの自動生成などの機能を提供しています。 Gatlingの破壊的変更 gatling-operatorの運用を続ける中で、Gatling本体のバージョンアップに伴う破壊的変更が大きな課題となりました。 Gatling 3.11での変更 Gatling 3.11では、これまで提供されていた gatling.sh というGatlingを実行するためのシェルスクリプトが削除されました。代わりに、MavenやGradleといったJavaのビルドツールを使用することが必須となっています。 gatling-operatorは gatling.sh を実行することを前提に実装されていたため、この変更に対応するには大幅な修正が必要となりました。また、シナリオやリソースファイルを配置するディレクトリ構成も、ビルドツールを使った実行では基本的に固定されるようになり、後方互換の問題も生じます。 Gatling 3.12での変更 Gatling 3.12では、複数のログファイルをまとめてレポートを作成する機能が削除されました。gatling-operatorは分散実行した各Podのログファイルを集約してレポートを生成する仕組みを採用していたため、この変更は致命的な影響を与えます。 なお、この機能はEnterprise版では引き続き利用可能とのことです。しかし、gatling-operatorはOSS版のGatlingを前提に設計されており、Enterprise版を利用する設計への変更は困難です。 メンテナー不足 gatling-operatorのメンテナーは現状2名体制で運用しています。Gatlingの破壊的変更に追随するための改修工数を考えると、この体制では十分な対応が困難な状況でした。 検討した対応策 これらの課題に対して、以下の3つの対応策を検討しました。 対応策 メリット デメリット gatling-operatorの改修 既存シナリオ資産がそのまま使える 改修規模が大きく、将来性に懸念 Gatling Enterprise版への移行 メンテナンスコスト削減 金銭的な追加コストが発生 他負荷試験ツールへの移行 メンテナンスコスト削減、OSSなら追加コストなし 既存シナリオ資産の移行が必要 検討の結果、OSSとして広く利用されており、Kubernetes環境での実行もサポートされている k6 への移行を選択しました。既存シナリオの移行については、生成AIを活用することで工数を削減できると判断しました。 なぜk6を採用したのか 要件 gatling-operatorのメンテナーが不在なことや育成コストの高さから、新たな負荷試験ツールを模索しました。OSSとしてのコミュニティの大きさやKubernetesで運用する既存基盤に導入できる形が条件です。Gatlingから他の負荷試験ツールに移行するにあたって、以下の3つの要件がありました。 メンテナビリティの高さ : なるべくメンテナンスコストを抑えるために、自社で独自のツールを作成しなくても良いOSSの採用を検討。 負荷試験結果をレポートとして残せるか : Gatlingを用いた負荷試験では、Gatlingレポートが出力されるため、ZOZOではエビデンスとしてそのレポートを使用していた。そのため、移行先のツールでも結果を見られるというのは重要。 シナリオ作成の容易さ : ZOZOでは、要件によるがバックエンドとSREがシナリオを作成する。そのため、シナリオ作成の際に学習コストやレビューコストがなるべく低い言語を採用する必要がある。 k6を採用した理由 移行先を検討する際、以下のツールが候補に上がりましたが、後述する理由からk6への導入が決まりました。 Locust k6 ZOZOでは、事業部単位でSREが存在しており、負荷試験基盤を持っています。その1つとしてWEARチームの負荷試験基盤でk6の導入実績があったため、社内ナレッジを活用できることは大きなメリットでした。 techblog.zozo.com また、k6はGrafana製のOSSであり、Kubernetes上でk6を動作させるための k6-operator がすでにあります。さらに xk6 を使用することで、カスタムビルドし簡単に拡張機能などを導入できます。k6-operator、xk6などもGrafana社が作成している公式のOSSであり、メンテナンスを自社で担わなくて良いのは大きなメリットでした。k6-operatorはCRDを導入しており、マニフェストファイルを適用するだけで負荷試験が簡単に実行でき、既存のgatling-operatorと変わらずに運用できる点も魅力です。 さらに、シナリオ作成をする際にJavaScriptで記述できるのもメリットでした。負荷試験シナリオでは、複雑な処理は実装しないため、レビューや学習のコストを抑えられる点は魅力的でした。 以上の理由から、最終的にはk6をPoC的に導入することが決定しました。また実際にk6を検証する中で、足りない機能などは別途、検証・実装・議論をしつつ導入していきました。 導入にあたっての課題と工夫 共通ライブラリの実装 k6への移行を進める中で、Gatlingには存在するものの、k6には標準で用意されていない機能があることに気づきました。特に問題となったのが feeder 機能です。 Gatlingのfeeder機能とは Gatlingのfeederは、CSVファイルなどの外部データソースからテストデータを読み込み、各仮想ユーザーに順次または循環的にデータを供給する仕組みです。例えば、ログインシナリオで複数のユーザー認証情報を使い回す場合などに重宝します。 // Gatlingでのfeeder使用例 val feeder = csv( "users.csv" ).circular val scn = scenario( "Login" ) .feed(feeder) .exec(http( "login" ).post( "/auth" ).body(StringBody( """{" email ":" ${email} "," password ":" ${password} "}""" ))) ZOZOの負荷試験シナリオでは、このfeeder機能が多くのシナリオで使用されていました。ユーザー認証情報や商品ID、セッション情報など、様々なテストデータをfeederで管理していたため、k6への移行にあたって同等の機能が必須でした。 k6標準機能の課題 k6にはCSV読み込み機能はあるものの、Gatlingのfeederのような順次供給・循環利用の機能は標準で提供されていません。 // k6標準のデータ読み込み(feederのような機能はない) import { SharedArray } from 'k6/data' ; const data = new SharedArray ( 'users' , function () { return JSON . parse ( open ( './users.json' )) ; }) ; この方法では、どのVUがどのデータを使用するかの制御や、データの循環供給を自前で実装する必要がありました。 feederライブラリの実装 そこで、Gatlingのfeeder機能を再現する共通ライブラリを実装しました。このライブラリは k6_scenarios/common_lib/feeder/ ディレクトリに配置され、以下の機能を提供します。 // feederライブラリの使用例 import { csv } from '../common_lib/feeder/feeder.js' ; // CSVデータを循環的に供給するfeederを作成 const userFeeder = csv ( open ( './users.csv' )) . circular () ; export default function () { // 各VUの実行ごとに次のデータを取得 const userData = userFeeder . next () ; http . post ( ` ${ ENDPOINT } /auth/login` , JSON . stringify ({ email : userData . email , password : userData . password })) ; } 実装したfeederライブラリは、以下の供給戦略をサポートしています。 供給戦略 説明 Gatling相当 circular() データを循環的に供給(末尾に達したら先頭に戻る) csv().circular random() ランダムにデータを選択 csv().random queue() 順次供給(データが尽きたらエラー) csv().queue また、配列データや重み付きデータにも対応しています。 import { csv , array , weighted } from '../common_lib/feeder/feeder.js' ; // 配列からfeederを作成 const itemFeeder = array ([ 'item1' , 'item2' , 'item3' ]) . circular () ; // 重み付きでデータを選択 const scenarioFeeder = weighted ([ { weight : 70 , data : { type : 'browse' } } , { weight : 20 , data : { type : 'search' } } , { weight : 10 , data : { type : 'purchase' } } ]) ; このライブラリを実装したことで、既存のGatlingシナリオで使用していたfeeder機能をk6でも同様に利用できるようになりました。シナリオ移行の際も、feederの使用パターンを変換ルールとして定義することで、スムーズな移行が可能になっています。 シナリオ参照方法の検討 k6-operatorでは、シナリオファイルをRunner Podにマウントする方法として以下の3つが提供されています。 方法 概要 メリット デメリット ConfigMap シナリオをConfigMapとして登録 シンプルで手軽 容量制限あり(約1MB)、複数ファイル不向き VolumeClaim PVCにシナリオを配置 ファイルサイズ制限なし、複数ファイル対応 構成が複雑 LocalFile カスタムイメージにシナリオを内包 再現性が高い、外部依存なし 軽微な修正でも再ビルドが必要 gatling-operatorでは、シナリオを含んだコンテナイメージをビルドして実行する方式を採用していました。これはLocalFileに相当する方法です。しかし、シナリオを修正するたびにコンテナイメージのビルドとプッシュが必要となり、開発サイクルが遅くなりがちという課題がありました。 k6への移行にあたり、この開発サイクルを改善したいという狙いもあり、シナリオ参照方法を見直すことにしました。 採用したアプローチ:SharedVolume & LocalFile 検討の結果、公式ドキュメントで紹介されている3つの方法ではなく、VolumeClaimとLocalFileのハイブリッドなアプローチを採用しました。 このアプローチでは、PVC(PersistentVolumeClaim)は使用せず、Podの共有ボリューム(emptyDir)とinitContainerを組み合わせます。initContainerでGitリポジトリからシナリオファイルをクローンし、Runner Containerが共有ボリューム経由でそのファイルを参照する仕組みです。 このアプローチには以下のメリットがあります。 PVC不要でシンプル : VolumeClaimのような永続ボリュームの管理が不要で、マニフェスト管理やシステム構成がシンプルになる 自動クリーンアップ : 共有ボリュームはPod削除時にデータも削除されるため、後片付けが不要 開発サイクルの改善 : シナリオの修正はコミット&プッシュするだけで反映されるため、コンテナイメージのビルドが不要になる ブランチ指定による柔軟性 : 開発中のシナリオも開発ブランチを指定することで、すぐに試験可能 一方で、parallelismを設定してRunner Podが複数になる場合は、Pod分だけGit cloneが実行される点には注意が必要です。ただし、sparse-checkoutを使用して必要なディレクトリのみをクローンすることで、この影響を最小限に抑えています。 Git連携によるシナリオ管理 k6のシナリオファイルは、既存のgatling用シナリオと同様にGitHubリポジトリで管理しています。TestRunリソースのinitContainerで git-sync を使用し、テスト実行時にリポジトリからシナリオファイルをクローンする仕組みを実装しました。 ディレクトリ構成 zozo-benchmark/ └── k6_scenarios/ ├── common_lib/ # 共通ライブラリ │ ├── feeder/ # テストデータ読み込み用 │ └── metrics/ # メトリクス処理用 ├── sample/ # シナリオディレクトリ │ ├── testrun.yaml # TestRunマニフェスト │ └── test.js # シナリオファイル └── resources/ # テストデータ └── sample/ └── data.csv initContainerによるシナリオ取得 TestRunリソースでは、テスト実行前にinitContainerが2段階の処理を行います。 prepare-sparse-checkout : sparse-checkoutのパターンファイルを作成し、必要なディレクトリのみをクローン対象として指定 clone-data : git-syncを使用してGitHubリポジトリから指定ブランチのシナリオファイルをクローン apiVersion : k6.io/v1alpha1 kind : TestRun metadata : name : k6-sample spec : parallelism : 2 arguments : --out experimental-opentelemetry runner : volumes : - name : git-sync-config emptyDir : {} - name : scenario-data emptyDir : {} volumeMounts : - name : scenario-data mountPath : /test initContainers : - name : prepare-sparse-checkout image : busybox:1.36 command : [ "/bin/sh" , "-c" ] env : - name : TARGET_SCENARIO value : scenario_samples/simple args : - | cat <<EOF > /git-sync-config/sparse-checkout-patterns k6_scenarios/common_lib/ k6_scenarios/resources/ k6_scenarios/${TARGET_SCENARIO}/ EOF volumeMounts : - name : git-sync-config mountPath : /git-sync-config - name : clone-data image : registry.k8s.io/git-sync/git-sync:v4.4.2 args : - --repo=https://<シナリオ格納リポジトリ>.git - --link=zozo-benchmark - --depth=1 - --root=/test - --one-time - --sparse-checkout-file=/git-sync-config/sparse-checkout-patterns envFrom : - secretRef : name : k6-github-app-secrets-xxxx env : - name : GITSYNC_REF value : main # クローンするブランチを指定 volumeMounts : - name : git-sync-config mountPath : /git-sync-config readOnly : true - name : scenario-data mountPath : /test script : localFile : /test/zozo-benchmark/k6_scenarios/scenario_samples/simple/test.js この仕組みにより、シナリオファイルの修正をコミット・プッシュするだけで、次回の試験実行時に最新のシナリオが反映されます。 GITSYNC_REF でブランチを指定できるため、開発中のシナリオも開発ブランチを指定することですぐに試験できます。 GitHub認証 プライベートリポジトリからのクローンには、GitHub Appsによる認証を使用しています。 認証情報の管理には、AWS Secrets ManagerとExternal Secretsを組み合わせています。GitHub Appsの認証情報をSecrets Managerに保存し、External Secrets経由でKubernetes Secretとして読み込みます。 apiVersion : external-secrets.io/v1beta1 kind : ExternalSecret metadata : name : k6-github-app-secrets-xxxx spec : refreshInterval : "1h" secretStoreRef : name : cluster-secretstore kind : ClusterSecretStore data : - secretKey : GITSYNC_GIT_CREDENTIAL remoteRef : key : k6/github-app - secretKey : GITSYNC_GIT_CREDENTIAL_PRIVATEKEY remoteRef : key : k6/github-app-privatekey clone-dataコンテナでは、このSecretを envFrom で参照しています。 - name : clone-data image : registry.k8s.io/git-sync/git-sync:v4.4.2 envFrom : - secretRef : name : k6-github-app-secrets-xxxx git-syncはこれらの環境変数を自動的に読み取り、GitHub Appsで認証してプライベートリポジトリへのアクセスを実現しています。 Datadogによる試験結果の可視化 Gatlingからk6への移行にあたり、試験結果を可視化・保存する方法についても検討しました。負荷試験基盤を利用するエンジニアの移行ハードルを上げないよう、また、利用体験を損なわずに試験結果をこれまで通りエビデンスとして活用できるよう、以下の要件を満たす必要がありました。 Gatlingレポートと同等のメトリクスや統計情報を可視化できること 試験結果を一定の期間保存し、後から確認・比較できること 検討の結果、k6のメトリクスをDatadogに送信し、独自のダッシュボードで試験結果を可視化する方式を採用しました。以降では、Datadogダッシュボードを採用した理由と構築方法について説明します。 k6標準のレポート機能の課題 gatling-operatorでは、Gatlingにより出力されたHTMLレポートをAWS S3に保存する機能が存在し、利用者はS3上のファイルにアクセスすることで試験結果を確認できました。k6でも同様の方式で試験結果を保存・確認できないか検討しました。 k6にはHTMLレポートを出力する標準機能である handleSummary 関数や、よりリッチなHTMLレポートを出力できる benc-uk/k6-reporter が存在します。しかし、クラウドストレージにレポートを保存する仕組みは存在せず、コストをかけて作り込む必要があったため、この方式は採用しませんでした。 JSON/CSV出力機能を利用する案も検討しましたが、可視化の作り込みにコストがかかり、また上記と同様にレポートを保存する仕組みを構築する必要があったため、こちらも採用しませんでした。 Datadogダッシュボードの採用 他の案も比較検討しましたが、普段から監視ツールとしてDatadogを使用していることから、k6のメトリクスを利用した独自のDatadogダッシュボードを作成する方式を最終的に採用しました。 Datadogなら以下のようにそれぞれの要件を満たせました。 ダッシュボード機能により試験結果を可視化できること Datadogのメトリクスは長期間保存され 1 、タグ付けにより試験ごとの絞り込みと比較が可能なこと 既存のDatadog環境を活用できるため、以下のメリットがありました。 低い導入コスト :負荷試験基盤にはdatadog-agentを導入済みのためk6のメトリクス収集が容易 統合的な分析 :Datadog上で試験結果と試験中のトレースやカスタムメトリクスを紐付けて確認できる 利用者の認知負荷や学習コストの軽減 :使い慣れたダッシュボード機能で柔軟に試験結果を可視化できる Datadogへのメトリクス送信 k6の試験結果をDatadogダッシュボードで可視化するには、k6のメトリクスをDatadogに送信する必要があります。Datadogへのメトリクス送信には、負荷試験基盤に導入済みのdatadog-agentを活用しました。また、試験結果を区別できるようにメトリクスにタグを付与する工夫も行いました。 OpenTelemetry方式での送信 k6からDatadogにメトリクスを送信する方法として、 StatsD方式 と OpenTelemetry方式 が存在します。StatsD方式は拡張機能を含んだコンテナイメージをビルドする必要があったため、Kubernetesマニフェストへの設定追加のみで済むOpenTelemetry方式を採用しました。 以下の手順でk6からDatadogへのメトリクス送信を実現しました。 公式ドキュメント に従いdatadog-agentのOTLPメトリクス受信を有効化 k6のメトリクス送信設定をConfigMapとして作成 TestRunにConfigMap参照と --out experimental-opentelemetry 引数を追加 ConfigMapとTestRun設定例はそれぞれ以下の通りです。 apiVersion : v1 kind : ConfigMap metadata : name : k6-system-config data : K6_OTEL_EXPORTER_TYPE : "grpc" K6_OTEL_GRPC_EXPORTER_ENDPOINT : "datadog-agent.kube-support.svc.cluster.local:4317" K6_OTEL_SERVICE_NAME : "k6" K6_OTEL_METRIC_PREFIX : "k6_" K6_OTEL_GRPC_EXPORTER_INSECURE : "true" apiVersion : k6.io/v1alpha1 kind : TestRun metadata : name : k6-metrics-sample spec : arguments : --out experimental-opentelemetry runner : envFrom : - configMapRef : name : k6-system-config メトリクスへのタグ付け Datadogダッシュボードで試験結果を区別するにはメトリクスにタグを付与しておく必要があります。負荷試験基盤で統一的なタグを付与できるように、共通ライブラリとして performRequest というHTTPリクエストのラッパー関数を実装しました。 以下が実装した performRequest です。 commonTags はダッシュボードでの試験結果の絞り込みに利用する共通タグです。また、詳細分析で利用できるリクエスト固有のタグも付与しています。 import { check } from 'k6' ; import { commonTags } from './tags.js' ; export function performRequest ( requestName , requestFunc , expectedStatus = 200 ) { try { const estimatedPath = `/ ${ requestName } ` ; const preRequestTags = { ... commonTags , request_name : requestName , path : estimatedPath , endpoint : requestName } ; const response = requestFunc ( preRequestTags ) ; const actualPath = getPathFromUrl ( response . url ) || estimatedPath ; const statusCategory = getStatusCategory ( response . status ) ; const checkResult = check ( response , { [ ` ${ requestName } : status is ${ expectedStatus } ` ] : ( r ) => r . status === expectedStatus , [ ` ${ requestName } : response time < 5000ms` ] : ( r ) => r . timings . duration < 5000 , } , { ... commonTags , request_name : requestName , path : actualPath , status_code : response . status . toString () , status_category : statusCategory , endpoint : requestName }) ; const isSuccess = response . status === expectedStatus ; if ( ! isSuccess ) { console . error ( `✗ ${ requestName } : ${ response . status } - ${ actualPath } ` ) ; } return response ; } catch ( error ) { console . error ( ` ${ requestName } error: ${ error } ` ) ; throw error ; } } commonTags の内容は以下の通りです。 export const commonTags = { test_run_id : __ENV . K6_TEST_RUN_ID || console . warn ( 'K6_TEST_RUN_ID not set. Use environment variable for consistent ID across pods.' ) || `test-run- ${ new Date () . toISOString () . slice ( 0 , 16 ) . replace (/ [ T: ] / g , '-' )} ` , env : __ENV . K6_ENV || 'stg' , scenario : __ENV . K6_SCENARIO || 'default' , team : __ENV . K6_TEAM || 'default' , } ; performRequest を利用するには以下の2つのステップが必要です。 TestRunでの環境変数の設定 シナリオファイル内のリクエスト処理を performRequest を使用するように書き変える TestRunの環境変数とシナリオファイルの記述例はそれぞれ以下の通りです。 env : - name : K6_TEST_RUN_ID value : "member-api-stress-test-20251209" - name : K6_ENV value : "stg" - name : K6_SCENARIO value : "member-api-create-and-update-user" - name : K6_TEAM value : "member-api-team" import { performRequest } from '../common_lib/metrics/metrics.js' ; import http from 'k6/http' ; export default function () { performRequest ( 'create_user' , ( tags ) => { return http . post ( 'https://api.example.com/users' , JSON . stringify ({ name : 'Test User' , email : 'test@example.com' }) , { headers : { 'Content-Type' : 'application/json' } , tags }) ; } , 200 ) ; performRequest ( 'update_user' , ( tags ) => { return http . put ( 'https://api.example.com/users/123' , JSON . stringify ({ name : 'Updated User' }) , { headers : { 'Content-Type' : 'application/json' } , tags }) ; } , 200 ) } ; ダッシュボードの構成と拡張性 以下の画像は今回作成したDatadogダッシュボードのメイン部分です。 4つのタグにより試験結果を絞り込むことができます。 タグ名 説明 test_run_id テスト実行の識別子 scenario テストシナリオ名 team 実行チーム名 env テスト対象環境 各ウィジェットはGatlingレポートの項目を参考にしつつ、k6固有のメトリクスも確認できる構成にしました。Gatlingレポートと同等以上の試験結果の可視化を実現できたと思います。 さらに、従来のGatlingレポートが固定形式だったのに対し、Datadogダッシュボードでは以下のような柔軟な拡張が可能です。 カスタムメトリクスを組み合わせた分析 試験内容や目的に応じたダッシュボードのカスタマイズ チームごとの独自ダッシュボード作成と共有 Claudeサブエージェントを使ったシナリオ移行 k6を導入した後の課題として、Scalaで記述された既存シナリオをどのように移行していくかがありました。Gatlingシナリオが約130個もあるため、全てを手動でk6用に移行するのは困難でした。 さらに、シナリオ移行を実現するにあたって、共通のコンテキストを用いて移行することは必須でした。具体的には以下のようなルールに準拠する必要がありました。 feeder機能やDatadogにタグ付きメトリクスを送信するための共通ライブラリを使用すること シナリオをGit連携で使えるようにする一方、秘匿情報(ログイン・パスワードなど)が含まれたCSVファイルはAWS Secrets Managerから取得すること これらの要件から、Claudeサブエージェントを用いたシナリオ移行を検証しました。 Claudeサブエージェントとは Claude Codeの機能で、より専門的なタスクに用いることができ、コンテキスト管理を容易にすることが可能となります。より詳細な内容は、公式ドキュメントをご覧ください。 code.claude.com シナリオ移行での活用方法 シナリオ移行にあたって、以下のような使用方法を検証しました。まず claude コマンドでClaude Codeを起動します。 $ claude Claude Codeの起動後、サブエージェントを使用してGatlingシナリオをk6に移行したい旨の指示を送信します。その後、サブエージェントからの質問に対して回答します。以下の具体例では、 5. Type something. に対象のシナリオパス( simulations/ZozoSessionApiBasicLoadTest.scala )を入力しています。 > sub-agentを使用してGatling(Scala)シナリオをk6に移行したい ⏺ 了解しました。Gatling(Scala)シナリオをk6に移行するためのサブエージェントを起動します。 まず、移行対象のGatlingシナリオを特定するため、現在のリポジトリ構造を確認させてください。 ⏺ 多数のGatlingシナリオがあります。どのシナリオを移行したいか確認させてください。 ☐ 移行対象 どのGatlingシナリオをk6に移行しますか?特定のシナリオを指定するか、移行対象のカテゴリを教えてください。 ❯ 1. member-id-pf-sre関連 zozo-id, zozo-session-api, zozo-member-api等のシナリオ 2. 特定のファイルを指定 ファイル名またはパスで個別に指定 3. すべてのシナリオ simulations/配下の全シナリオを順次移行 4. その他 カスタムで指定 5. Type something. Enter to select · Tab/Arrow keys to navigate · Esc to cancel サブエージェントが起動し、移行処理が実行されます。 ⏺ simulations/ZozoSessionApiBasicLoadTest.scalaをk6に移行します。k6-scenario-migratorエージェントを起動します。 ⏺ k6-scenario-migrator(Migrate ZozoSessionApi to k6) ⎿ +13 more tool uses (ctrl+o to expand) · Sketching… (esc to interrupt · 2m 38s · ↓ 440 tokens) 以下が移行結果です。 ⏺ 移行完了 🎉 simulations/ZozoSessionApiBasicLoadTest.scala を k6 に正常に移行しました。 作成されたファイル | ファイル | パス | |------------|-----------------------------------------------------------------------------------| | k6シナリオ | k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/test.js | | TestRun CR | k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/testrun.yaml | シナリオ概要 Webシナリオ (ZOZO_SESSION_PLATFORM=web): 1. Init Session × 3回 2. Save Session × 3回 3. Get Session × 57回 Appシナリオ (ZOZO_SESSION_PLATFORM=app): 1. Init Session × 1回 2. Get Session × 100回 Gatling → k6 変換ポイント | Gatling | k6 | |-----------------------|--------------------------------| | constantUsersPerSec | constant-arrival-rate executor | | check(status.is(200)) | performRequest() + thresholds | | jsonPath().saveAs() | JSON.parse() + 変数保存 | 実行方法 kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/testrun.yaml どのように実現しているか 以下のようなディレクトリ構造で、サブエージェントを作成しています。 .claude ├── agents │ └── k6-scenario-migrator.md # サブエージェントに対する指示やチェックリストを記載 ├── docs │ └── scala-to-k6-migration-guide.md # 変換ガイドライン(具体的なライブラリの使い方や変換ルールを記載) └── settings.local.json 前提として、それぞれのプロンプトはコンフルエンス上で整備した社内向けのk6ユーザーガイドラインをもとにしています。このユーザーガイドラインをコンテキストとして、サブエージェント用のプロンプトを作成しました。サブエージェント(.claude/agents/k6-scenario-migrator.md)には「何をすべきか」を指示しています。変換ガイドライン(.claude/docs/scala-to-k6-migration-guide.md)には「どう変換すべきか」の詳細ルールを記載しています。 シナリオ移行の品質を担保するため、サブエージェントでは以下の4つの観点でプロンプトを設計しました。 まず、事前質問による情報収集です。エージェント起動時にチーム名・マイクロサービス名・対象ファイルを確認させることで、後続の処理に必要な情報を漏れなく取得します。 #### 質問1: チーム名の確認 このシナリオを管轄するチーム名を教えてください。 例: member-id-pf-sre, cart-sre, search-sre #### 質問2: マイクロサービス名の確認 対象となるマイクロサービス名を教えてください。 例: zozo-id, zozo-session-api, zozo-member-api 注意: 「-api」サフィックスは除去し、「-」は「_」に変換します。 #### 質問3: Scalaシナリオファイルパスの確認 移行元のScalaシナリオファイルのパスを教えてください。 例: simulations/ZozoIdLogin.scala 次に、命名規則の明確化です。ディレクトリ名やシナリオ名の移行ルールを明示することで、一貫性のあるディレクトリ、ファイルの作成を実現しています。 #### マイクロサービス名からディレクトリ名を生成 1. 「-api」サフィックスを除去 - zozo-session-api → zozo-session 2. ハイフン「-」をアンダースコア「_」に変換 - zozo-session → zozo_session #### Scalaファイル名からシナリオ名を生成 1. キャメルケースをスネークケースに変換(ディレクトリ名用) - ZozoIdLogin → zozo_id_login 2. 環境変数: K6_SCENARIOはハイフン繋ぎに統一 - zozo_id_login → zozo-id-login - 理由: Datadogメトリクスで一貫性を保つため #### 最終的なディレクトリパス k6_scenarios/scenario_<チーム名 > /<マイクロサービス名 > / < scenario _name> / 続いて、チェックリストによる品質担保です。移行完了後に確認すべき項目をリスト化し、共通ライブラリの使用やDatadogタグの設定漏れを防いでいます。 - [ ] ` performRequest ` 関数を使用している - [ ] ` tags ` パラメータをすべてのHTTPリクエストに渡している - [ ] feederライブラリ( ` csv ` , ` array ` など)を適切に使用している - [ ] 環境変数にデフォルト値を設定している - [ ] K6 _ TEST _ RUN _ ID, K6 _ ENV, K6 _ SCENARIO, K6 _ TEAMを設定している - [ ] testrun.yamlの ` K6_SCENARIO ` とtest.jsの ` options.scenarios ` のキー名が同じ(ハイフン繋ぎ)になっている - [ ] testrun.yamlに ` cleanup: post ` を設定している(テスト終了後にPodを自動削除するため) - [ ] CSVデータに秘匿情報が含まれるかどうかを判断している - [ ] 秘匿情報を含むCSVの場合、External Secretsで管理している - [ ] 秘匿情報を含むCSVの場合、ExternalSecretマニフェスト(secrets.yaml)を作成している - [ ] 秘匿情報を含むCSVの場合、AWS Secrets ManagerにCSV形式のプレーンテキストデータを登録する必要があることをユーザーに伝えている - [ ] 秘匿情報を含まないCSVの場合、 ` k6_scenarios/resources/scenario_<チーム名>/ ` 配下に配置している - [ ] CSVデータをExternal Secretsから読み込む場合、適切にマウント・読み込み処理を実装している - [ ] CSVデータをローカルファイルから読み込む場合、正しい相対パスを使用している - [ ] CSVデータ読み込み時のエラーハンドリングが実装されている - [ ] ディレクトリ構成が ` scenario_<チーム名>/<マイクロサービス名>/<scenario_name> ` に準拠している - [ ] 相対パスでのインポートが正しい( ` ../../../common_lib/... ` ) - [ ] testrun.yamlが作成されている(**必須**) - [ ] testrun.yamlのlocalFileパスが正しい - [ ] testrun.yamlのTARGET _ SCENARIOパスが正しい - [ ] testrun.yamlのmetadata.nameが適切に設定されている - [ ] 存在しない・不要なimportは行わない - [ ] ex ` import { open } from 'k6'; ` 次に、セキュリティを考慮した分岐です。CSVデータの性質(秘匿情報の有無)に応じて処理を分岐させ、機密情報をリポジトリへ含めないようにしています。 #### パターン1: 秘匿情報を含むCSV(External Secrets使用) 対象: ログイン情報、APIトークンなどの秘匿情報 → AWS Secrets Managerに登録し、External Secretsでマウント #### パターン2: 秘匿情報を含まないCSV(ローカルファイル) 対象: 商品一覧、カテゴリ情報などの公開データ → k6 _ scenarios/resources/scenario_<チーム名 > / に配置 最後に、入力例と出力例の記載です。SREに加え開発者がこのシナリオ移行を実施する可能性があるため、移行後の動作方法などを具体的に明記し、すぐに動作確認を実施できる出力を目指しました。 ## 例 ### 入力例 ``` チーム名: member-id-pf-sre マイクロサービス名: zozo-session-api Scalaファイル: simulations/ZozoSessionProxyTest.scala ``` ### 出力例 ``` ✓ k6シナリオの生成が完了しました 生成されたファイル: - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/test.js - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/testrun.yaml - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/README.md ディレクトリ構造: k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/ ├── test.js ├── testrun.yaml └── README.md 実行方法(Kubernetes): # TestRunの適用 kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/testrun.yaml # ログ確認 kubectl logs -l k6_cr=zozo-session-proxy-test --tail=100 -f # TestRunの削除 kubectl delete testrun zozo-session-proxy-test 注意: ローカル実行(k6コマンド)は使用しません。必ずKubernetes上で実行してください。 ``` 以上が、サブエージェントに記載している主なプロンプトの内容です。 サブエージェントで使用する変換ガイドラインは、社内用のk6使用方法ドキュメントを落とし込む形で作成しています。内容としては、具体的なライブラリの使用例やシナリオを移行する際の具体的なBefore/Afterのソースコードを記載しています。 下記に、ガイドラインとなるプロンプトの全文を記載します。 プロンプトの全文 # Gatling (Scala) から k6 (JavaScript) へのシナリオ移行プロンプト ## 概要 このドキュメントは、既存のGatling(Scala)で記述された負荷試験シナリオを、k6(JavaScript)に移行するための生成AIプロンプトです。 zozo-benchmarkリポジトリの共通ライブラリを活用し、効率的かつ標準化されたk6シナリオを生成します。 ## 前提条件 - zozo-benchmarkリポジトリの既存実装を参照すること - k6-operatorのTestRun CRDを使用したKubernetes環境での実行を想定 - 共通ライブラリ(feeder, metrics)を活用すること - External Secretsでシークレット管理を行うこと --- ## 移行プロンプト 以下のプロンプトを生成AIに入力してください。Scalaシナリオを添付することで、k6シナリオへ変換されます。 ``` # タスク 以下のGatling(Scala)シナリオをk6(JavaScript)シナリオに変換してください。 zozo-benchmarkリポジトリの標準に従い、共通ライブラリとベストプラクティスを適用してください。 --- # 変換ルール ## 1. 基本構造 ### 環境変数の取得 **Scala (Before)** ```scala val endpoint = sys.env.getOrElse("ENDPOINT", "xxxx") val clientToken = sys.env.getOrElse("CLIENT_TOKEN", "dummy123456789") val usersPerSec = sys.env.getOrElse("CONCURRENCY", "1").toInt val durationSec = sys.env.getOrElse("DURATION", "10").toInt ``` **JavaScript (After)** ```javascript // オプションで使用する環境変数には必ずデフォルト値を設定 const ENDPOINT = __ENV.ENDPOINT || 'xxxx'; const CLIENT_TOKEN = __ENV.CLIENT_TOKEN || 'dummy123456789'; const VUS = __ENV.VUS ? parseInt(__ENV.VUS) : 1; const RATE = __ENV.RATE ? parseInt(__ENV.RATE) : 1; const DURATION = __ENV.DURATION || '10s'; ``` ## 2. HTTPプロトコル設定とヘッダー ### HTTPプロトコルとベースURL **Scala (Before)** ```scala val httpProtocol = http .baseUrl(endpoint) .userAgentHeader("Mozilla/5.0...") .shareConnections val requestHeaders = Map( "Content-Type" -> "application/json;charset=UTF-8", "Accept-Encoding" -> "gzip, deflate", "accept" -> "application/json", "zozo-api-client-token" -> clientToken ) ``` **JavaScript (After)** ```javascript // k6ではベースURLをオプションとして設定せず、リクエストごとにフルURLを指定 // 共通ヘッダーは変数として定義し、リクエスト時に使用 const commonHeaders = { 'Content-Type': 'application/json;charset=UTF-8', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'zozo-api-client-token': CLIENT_TOKEN, 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0' }; ``` ## 3. フィーダー(テストデータ) ### CSVデータの読み込みパターン CSVデータの読み込み方法は、**データの性質によって2つのパターン**に分かれます: #### パターン1: 秘匿情報を含むCSV(External Secrets使用) **対象データ:** - ログイン情報(ユーザー名、パスワード) - APIトークン、アクセスキー - その他の認証情報 **重要:** 秘匿情報を含むCSVファイルは、**リポジトリに含めず**、External Secretsを使用してAWS Secrets Managerから読み込みます。 **手順:** 1. **AWS Secrets ManagerにCSVデータを登録** ``` # AWS Secrets ManagerにプレーンテキストとしてCSV内容を保存 # キー: zozo-id/login_info # 値: CSV形式のプレーンテキスト user_id,password test_user_1,password123 test_user_2,password456 ``` 2. **ExternalSecretマニフェストを作成** ( ` secrets.yaml ` ) ```yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: zozo-id-login-info-secrets-xxxx namespace: default spec: refreshInterval: "0" secretStoreRef: name: cluster-secretstore kind: ClusterSecretStore data: - secretKey: login_info_zozo_id remoteRef: key: zozo-id/login_info ``` 3. **testrun.yamlでSecretをマウント** ```yaml spec: runner: volumes: - name: login-secrets secret: secretName: zozo-id-login-info-secrets-xxxx volumeMounts: - name: login-secrets mountPath: /secrets readOnly: true ``` 4. **test.jsでSecretから読み込み** ```javascript import { csv } from '../../../common_lib/feeder/feeder.js'; import { open } from 'k6'; // Load login credentials from Secret (mounted at /secrets/login_info_zozo_id) let loginFeeder; try { const secretData = open('/secrets/login_info_zozo_id'); if (!secretData || secretData.trim() === '') { throw new Error('Secret file is empty'); } loginFeeder = csv(secretData, {}, 'login-info'); console.log('[INFO] Loaded login info from Secret: /secrets/login_info_zozo_id'); } catch (error) { console.error('[ERROR] Failed to load login info from Secret:', error.message); console.error('[DEBUG] Check: kubectl get secret <secret-name> -o yaml'); throw new Error('Login info data source not available. Secret is required.'); } // default関数内で使用 export default function () { const userData = loginFeeder.circular(); // userData.user_id, userData.password などが利用可能 } ``` **参照実装:** - ExternalSecretマニフェスト: ` k6_scenarios/scenario_member_id_sre/zozo_id/secrets.yaml ` - test.js実装: ` k6_scenarios/scenario_member_id_sre/zozo_id/login_scenario/test.js ` (44-55行目) --- #### パターン2: 秘匿情報を含まないCSV(ローカルファイル) **対象データ:** - 商品一覧 - カテゴリ情報 - テスト用の公開データ **配置場所:** ``` k6_scenarios/resources/scenario_<team_name>/ └── *.csv # 例: products.csv, categories.csv など # チーム別の例 k6_scenarios/resources/scenario_member_id_sre/ ├── products.csv └── categories.csv k6_scenarios/resources/scenario_cart_sre/ ├── cart_items.csv └── user_sessions.csv ``` **注意:** - 基本は ` scenario_<team_name>/ ` 直下に配置 - 整理のため、さらにサブディレクトリを作ることも可能(例: ` scenario_samples/feeder/ ` ) - サブディレクトリを作る場合は、importパスを適切に調整してください **Scala (Before)** ```scala val feeder = csv("products.csv") // シナリオ内で使用 .feed(feeder.circular) ``` **JavaScript (After)** ```javascript import { csv } from '../../../common_lib/feeder/feeder.js'; import { open } from 'k6'; // CSVファイルを読み込み(resourcesディレクトリから相対パスで参照) // パスの例: scenario_member_id_sre のチームの場合 const csvData = open('../../../resources/scenario_member_id_sre/products.csv'); const productsFeeder = csv(csvData, {}, 'products'); // default関数内で使用 export default function () { // circular(): 順番に1つずつ選択(ラウンドロビン) const product = productsFeeder.circular(); // random(): ランダムに選択 // const product = productsFeeder.random(); // perVu(): VUごとに固定データを割り当て // const product = productsFeeder.perVu(); // productには CSV の各カラムがプロパティとして含まれる // 例: product.product_id, product.name, product.price など } ``` ### Feederのメソッド - ` circular() ` : ラウンドロビンで順番に選択(Gatlingのcircularと同じ) - ` random() ` : ランダムに選択(Gatlingのrandomと同じ) - ` perVu() ` : VUごとに固定データを割り当て(Gatlingのqueueと同じ) ### AtomicIntegerの変換(ユニークID生成) **重要**: k6にはGatlingの ` AtomicInteger ` のようなスレッドセーフなカウンターがありません。 複数のVUが同時に実行される環境でユニークなIDを生成するには、VU IDとイテレーション番号を組み合わせる必要があります。 **Scala (Before)** ```scala // 初期値は環境変数のCURRENT_MAX_MEMBER_ID val memberIdCounter = new AtomicInteger(currentMaxMemberId) // カスタムfeederを作成し、member_idをインクリメントしていく val memberIdFeeder = Iterator.continually(Map("member_id" -> memberIdCounter.incrementAndGet())) var single_request = scenario("test scenario") .feed(memberIdFeeder) .exec(...) ``` **JavaScript (After)** ```javascript import exec from 'k6/execution'; const CURRENT_MAX_MEMBER_ID = __ENV.CURRENT_MAX_MEMBER_ID ? parseInt(__ENV.CURRENT_MAX_MEMBER_ID) : 0; export default function () { // 各VUに独立したID空間を割り当ててユニークなIDを生成 // VU 1: 1000001, 1000002, 1000003, ... // VU 2: 2000001, 2000002, 2000003, ... // これにより、複数のVUが同時に実行しても重複が発生しない const memberId = CURRENT_MAX_MEMBER_ID + (exec.vu.idInTest * 1000000) + exec.scenario.iterationInInstance + 1; // memberIdを使用したリクエスト performRequest('example', (tags) => { return http.post(`${ENDPOINT}/api/endpoint`, JSON.stringify({ member_id: memberId }), { headers: { ...commonHeaders }, tags } ); }, 200); } ``` **説明**: - ` exec.vu.idInTest ` : VUごとに一意のID(1, 2, 3, ...) - ` exec.scenario.iterationInInstance ` : そのVU内でのイテレーション番号(0, 1, 2, ...) - ` 1000000 ` : 各VUに100万のIDスペースを確保(VUごとに100万回以上イテレーションする場合は調整が必要) - この方法により、409 ConflictなどのID重複エラーを防止できます **生成例**: - ` CURRENT_MAX_MEMBER_ID=0 ` の場合: - VU 1, iteration 0: 1000001 - VU 1, iteration 1: 1000002 - VU 2, iteration 0: 2000001 - VU 2, iteration 1: 2000002 ## 4. HTTPリクエスト ### リクエストの変換(performRequest関数を使用) **重要**: 必ず ` performRequest ` 関数を使用してください。これにより: - Datadogへのメトリクス送信 - カスタムタグの自動付与 - 標準化されたエラーハンドリング が実現されます。 **Scala (Before)** ```scala val login = exec( http("Login") .post("/auth/v1/login") .headers(requestHeaders) .body(StringBody("""{"id": "${email}","password": "${password}"}""")) .check(status.is(200)) .check(jsonPath("$.id_token").saveAs("id_token")) .check(jsonPath("$.refresh_token").saveAs("refresh_token")) ) ``` **JavaScript (After)** ```javascript import { performRequest } from '../common_lib/metrics/metrics.js'; import http from 'k6/http'; export default function () { const userData = userFeeder.circular(); // performRequest(リクエスト名, リクエスト関数, 期待するステータスコード) const loginResponse = performRequest('login', (tags) => { const payload = JSON.stringify({ id: userData.email, password: userData.password }); return http.post(`${ENDPOINT}/auth/v1/login`, payload, { headers: { ...commonHeaders, }, tags // 必ずtagsを渡す(Datadogのカスタムタグ付与のため) }); }, 200); // レスポンスからデータを取り出す const loginData = JSON.parse(loginResponse.body); const idToken = loginData.id_token; const refreshToken = loginData.refresh_token; } ``` ### 各種HTTPメソッドの例 **GET リクエスト** ```javascript performRequest('healthcheck', (tags) => { return http.get(`${ENDPOINT}/auth/v1/healthcheck/db`, { headers: commonHeaders, tags }); }, 200); ``` **POST リクエスト(JSONボディ)** ```javascript performRequest('create_user', (tags) => { const payload = JSON.stringify({ email: userData.email, zozo_id: userData.zozo_id, password: userData.password_hash }); return http.post(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, payload, { headers: commonHeaders, tags }); }, 200); ``` **PUT リクエスト** ```javascript performRequest('update_user', (tags) => { const payload = JSON.stringify({ email: userData.email, zozo_id: userData.zozo_id, password: userData.password_hash }); return http.put(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, payload, { headers: commonHeaders, tags }); }, 200); ``` **DELETE リクエスト** ```javascript performRequest('delete_user', (tags) => { return http.del(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, null, { headers: commonHeaders, tags }); }, 200); ``` **認証ヘッダー付きリクエスト** ```javascript performRequest('logout', (tags) => { return http.post(`${ENDPOINT}/auth/v1/logout`, null, { headers: { ...commonHeaders, 'Authorization': `Bearer ${idToken}` }, tags }); }, 200); ``` ## 5. シナリオとオプション設定 ### シナリオ設定の変換 **重要**: testrun.yamlの ` K6_SCENARIO ` 環境変数とtest.jsの ` options.scenarios ` のキー名は**必ず同じ名前(ハイフン繋ぎ)**にしてください。 理由: - ` http_req ` メトリクス: performRequest関数で ` K6_SCENARIO ` の値がタグとして付与される - ` iterations ` メトリクス: k6のエグゼキュータが ` options.scenarios ` のキー名をタグとして自動付与する - 名前が異なると、Datadogダッシュボードで2つの異なるscenario名が表示され、メトリクスが分断される **Scala (Before)** ```scala val single_request = scenario("zozo-id login") .feed(feeder.circular) .exec(captcha) .exec(login) .exec(refresh_token) .exec(validate_token) .exec(logout) setUp( single_request.inject( constantUsersPerSec(usersPerSec) during(durationSec seconds) ).protocols(httpProtocol) ) ``` **JavaScript (After)** ```javascript import { sleep } from 'k6'; // オプション設定 // 注意: scenariosのキー名は testrun.yamlのK6_SCENARIOと同じハイフン繋ぎにする export const options = { scenarios: { 'zozo-id-login': { // testrun.yamlのK6_SCENARIO="zozo-id-login"と一致させる // 一定のレートでリクエストを送信 executor: 'constant-arrival-rate', // 1秒あたりのイテレーション数 rate: RATE, // レートの適用期間 timeUnit: '1s', // 事前に確保するVU数 preAllocatedVUs: VUS, // テスト期間 duration: DURATION, // 最大VU数(必ずpreAllocatedVUs以上) maxVUs: 100, }, }, }; // セットアップ処理(テスト開始時に1回だけ実行) export function setup() { console.log('Execution parameters:', JSON.stringify({ ENDPOINT, VUS, RATE, DURATION, }, null, 2)); } // メインのテスト処理 export default function () { const userData = userFeeder.circular(); // リクエストを順番に実行 performRequest('captcha', (tags) => { return http.get(`${ENDPOINT}/auth/v1/irregular-login/detect`, { headers: commonHeaders, tags }); }, 200); const loginResponse = performRequest('login', (tags) => { const payload = JSON.stringify({ id: userData.email, password: userData.password }); return http.post(`${ENDPOINT}/auth/v1/login`, payload, { headers: commonHeaders, tags }); }, 200); const loginData = JSON.parse(loginResponse.body); const idToken = loginData.id_token; const refreshToken = loginData.refresh_token; // 以降のリクエストを続ける... // 必要に応じてsleepを追加(Think Time) // sleep(1); } // クリーンアップ処理(テスト終了時に1回だけ実行) export function teardown(data) { console.log('Test completed'); } ``` ### Executorの選択 Gatlingの ` constantUsersPerSec ` は、k6の ` constant-arrival-rate ` に対応します。 **その他のExecutorオプション** 1. **constant-vus**: 固定数のVUを一定期間実行 ```javascript executor: 'constant-vus', vus: 10, duration: '30s', ``` 2. **ramping-vus**: VU数を段階的に増減 ```javascript executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '30s', target: 10 }, { duration: '1m', target: 10 }, { duration: '30s', target: 0 }, ], ``` 3. **ramping-arrival-rate**: 到着レートを段階的に増減 ```javascript executor: 'ramping-arrival-rate', startRate: 5, timeUnit: '1s', preAllocatedVUs: 10, maxVUs: 100, stages: [ { duration: '30s', target: 50 }, { duration: '1m', target: 50 }, { duration: '30s', target: 0 }, ], ``` ## 6. Datadogメトリクスとカスタムタグ ### 環境変数でカスタムタグを設定 TestRun YAMLファイル( ` testrun.yaml ` )で以下の環境変数を設定してください: ```yaml spec: runner: env: # テスト実行ID(必須:Datadogダッシュボードでのフィルタリングに使用) - name: K6_TEST_RUN_ID value: "zozo-id-login-stg-2025-10-22" # 環境名(必須) - name: K6_ENV value: "stg" # シナリオ名(必須:test.jsのoptions.scenariosのキー名と一致させる) - name: K6_SCENARIO value: "zozo-id-login" # チーム名(必須) - name: K6_TEAM value: "member-id-pf-sre" # その他の環境変数 - name: ENDPOINT value: "xxxx" - name: VUS value: "10" - name: RATE value: "5" - name: DURATION value: "60s" ``` ### カスタムメトリクスの生成(オプション) 基本的なメトリクスは自動的にDatadogに送信されますが、カスタムメトリクスを追加する場合は以下のようにします: **注意**: カスタムメトリクスを生成する場合コストが発生するため、@platform-sreチームに相談してください。 ```javascript import { Counter, Trend, Rate, Gauge } from 'k6/metrics'; // カスタムメトリクスの定義 const errorCounter = new Counter('custom_errors'); const loginDuration = new Trend('custom_login_duration'); const successRate = new Rate('custom_success_rate'); export default function () { const startTime = Date.now(); const loginResponse = performRequest('login', (tags) => { // ... リクエスト処理 }, 200); if (loginResponse.status === 200) { successRate.add(1); loginDuration.add(Date.now() - startTime); } else { successRate.add(0); errorCounter.add(1); } } ``` ## 7. External Secretsの使用 ### シークレットの参照 TestRun YAMLファイルでExternal Secretsを参照します: ```yaml apiVersion: k6.io/v1alpha1 kind: TestRun metadata: name: zozo-id-login-test spec: parallelism: 4 script: localFile: /test/zozo-benchmark/k6_scenarios/zozo-id-login/test.js runner: image: grafana/k6:0.54.0 env: # External Secretsからシークレットを取得 - name: CLIENT_TOKEN valueFrom: secretKeyRef: name: zozo-id-client-token-secret key: token # ConfigMapから設定を取得 - name: K6_TEST_RUN_ID value: "zozo-id-login-stg-2025-10-22" envFrom: # GitHub認証用シークレット(必須) - secretRef: name: k6-github-app-secrets-xxxx # Datadog設定用ConfigMap(必須) - configMapRef: name: k6-system-config ``` ### Secretsの管理場所 シークレット情報は以下で管理されます: - **k6-github-app-secrets**: GitHub認証用(シナリオクローン用) - ` k8s/main/k6-secrets/secrets.yaml ` - **gatling-secrets**: 既存のGatlingシークレット(API tokenなど) - ` k8s/main/gatling-secrets/secrets.yaml ` ## 8. ディレクトリ構成 ### シナリオファイルの配置 ``` zozo-benchmark/ └── k6_scenarios/ ├── common_lib/ # 共通ライブラリ │ ├── feeder/ │ │ └── feeder.js │ └── metrics/ │ ├── metrics.js │ └── tags.js ├── resources/ # テストデータ(チーム別に管理) │ └── scenario_<チーム名>/ # チーム別リソースディレクトリ │ └── *.csv # 秘匿情報を含まないCSVファイル └── scenario_<チーム名>/ # チーム別シナリオディレクトリ └── <マイクロサービス名>/ # マイクロサービス別ディレクトリ └── <scenario_name>/ # シナリオ名ディレクトリ ├── testrun.yaml # TestRun マニフェスト ├── test.js # シナリオファイル └── README.md # ドキュメント(オプション) ``` **例**: member-id-pf-sreチームのzozo-oidc-provider-apiのAuthorizationCodeFlowテストの場合 ``` zozo-benchmark/ └── k6_scenarios/ ├── resources/ │ └── scenario_member_id_sre/ │ └── test_data.csv └── scenario_member_id_sre/ └── zozo_oidc_provider/ └── zozo_oidc_provider_api_authorization_code_flow_test/ ├── testrun.yaml ├── test.js └── README.md ``` **命名規則**: - **ディレクトリ名**: Scalaファイル名をスネークケース(アンダーバー繋ぎ)に変換 - 例: ` ZozoOidcProviderApiAuthorizationCodeFlowTest.scala ` → ` zozo_oidc_provider_api_authorization_code_flow_test/ ` - **K6_SCENARIOとoptions.scenariosのキー名**: ハイフン繋ぎに統一 - 例: ` zozo-oidc-provider-api-authorization-code-flow-test ` - 理由: Datadogメトリクスで一貫性を保つため ### 相対パスでのインポート ```javascript // feederライブラリのインポート import { csv, array } from '../../../common_lib/feeder/feeder.js'; // metricsライブラリのインポート import { performRequest } from '../../../common_lib/metrics/metrics.js'; // CSVファイルの読み込み(resourcesディレクトリから) // パスの例: scenario_member_id_sre のチームの場合 const csvData = open('../../../resources/scenario_member_id_sre/data.csv', 'r'); ``` **例**: member-id-pf-sreチームのzozo-oidc-providerのAuthorizationCodeFlowテストの場合 ```javascript import { csv, array } from '../../../common_lib/feeder/feeder.js'; import { performRequest } from '../../../common_lib/metrics/metrics.js'; const csvData = open('../../../resources/scenario_member_id_sre/data.csv', 'r'); ``` --- # 変換対象のScalaシナリオ 以下のScalaシナリオをk6に変換してください: [ここにScalaシナリオのコードを貼り付け] --- # 期待する出力 以下の2つのファイルを生成してください: 1. **test.js**: k6シナリオファイル - 上記のルールに従って変換 - すべてのインポートを含める - コメントで各セクションを説明 2. **testrun.yaml**: TestRun マニフェストファイル - 適切なリソース設定 - 環境変数とシークレットの設定 - initContainersの設定(シナリオクローン用) 3. **README.md** (オプション): シナリオの説明 - テスト目的 - 実行方法 - 必要な環境変数 ``` --- ## 使用例 ### 1. Scalaシナリオを準備 ```scala // simulations/ZozoIdLogin.scala class ZozoIdLogin extends Simulation { val endpoint = sys.env.getOrElse("ENDPOINT", "xxxx") // ... (既存のScalaコード) } ``` ### 2. 生成AIに入力 上記の「移行プロンプト」全体をコピーし、最後の「変換対象のScalaシナリオ」セクションにScalaコードを貼り付けて、生成AIに入力します。 ### 3. 生成されたk6シナリオを確認 生成AIが以下を出力します: - ` test.js ` : k6シナリオファイル - ` testrun.yaml ` : TestRun マニフェスト - ` README.md ` : 説明ドキュメント(オプション) ### 4. リポジトリに配置 ```bash # シナリオディレクトリを作成 # 例: member-id-pf-sreチームのzozo-idのLoginテストの場合 mkdir -p k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login # ファイルを配置 mv test.js k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ mv testrun.yaml k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ mv README.md k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ # CSVファイルがある場合(秘匿情報を含まないもののみ) mv *.csv k6_scenarios/resources/ ``` ### 5. テスト実行 ```bash # TestRunを適用 # 例: member-id-pf-sreチームのzozo-idのLoginテストの場合 kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/testrun.yaml # ログ確認 kubectl get testruns kubectl logs <pod-name> # Datadogダッシュボードで結果確認 [Datadogダッシュボードのリンク] ``` --- ## 参考資料 - [ k6 公式ドキュメント ]( https://grafana.com/docs/k6/latest/ ) - [ k6-operator GitHub ]( https://github.com/grafana/k6-operator ) --- ## トラブルシューティング ### タグが付与されない場合 **問題**: Datadogダッシュボードでカスタムタグが表示されない **解決策**: 1. ` performRequest ` 関数を使用しているか確認 2. ` tags ` パラメータをHTTPリクエストに渡しているか確認 3. TestRun YAMLで環境変数(K6 _ TEST _ RUN _ ID, K6 _ ENV, K6 _ SCENARIO, K6 _ TEAM)を設定しているか確認 4. ConfigMap(k6-system-config)を参照しているか確認 ### シナリオがクローンされない場合 **問題**: TestRun実行時にシナリオファイルが見つからない **解決策**: 1. initContainersが正しく設定されているか確認 2. GitHub認証用Secret(k6-github-app-secrets)が存在するか確認 3. sparse-checkoutパターンが正しいか確認 4. ブランチ名(GITSYNC _ REF)が正しいか確認 ### メトリクスがDatadogに送信されない場合 **問題**: Datadogにメトリクスが表示されない **解決策**: 1. ConfigMap(k6-system-config)を参照しているか確認 2. OTEL関連の環境変数が設定されているか確認 3. ネットワーク接続を確認 移行結果と効果 実際にGatlingシナリオをk6に移行した結果を記載します。サブエージェントを用いて移行したシナリオを実行したところ、問題なくマイクロサービスに想定リクエストが送信されていることを確認できました。Gatlingで実現していた以下の機能がk6でも同様に動作しています。 環境変数による設定切り替え(エンドポイント、VU数、実行時間など) CSVからのテストデータ読み込み Datadogへのメトリクス送信とカスタムタグ付与 具体的な実装例を記載しただけでなく、チェックリストを使用したことでシナリオ移行の精度が上昇したと考えられます。 一方で、課題も発生しました。移行後のシナリオにて、不要なモジュールimport文を生成し、実行エラーになってしまうことがありました。 // 生成された不要なimport import { open } from 'k6' ; 移行ガイドラインに「使用禁止のimportパターン」を明示的に追記することで解決しました。 - [ ] 不要な・存在しないimportは行わない - [ ] ex ` import { open } from 'k6'; ` サブエージェントを使う上でのポイント 今回の取り組みを通じて、シナリオ移行の精度が高いサブエージェントを実装できました。ポイントは以下のとおりです。 まず、ガイドラインの整備が最重要です。サブエージェントに渡すコンテキストの質が出力品質を直接左右します。具体例を多く含めることで変換精度が向上しました。そのため、ガイドラインの完成度をどの程度高くできるかがポイントとなります。 次に、チェックリストで品質を担保することです。LLMの出力にはばらつきがあるため、チェックリストで出力の精度を担保できたと考えています。特に「やってはいけないこと」を明示すると効果的に制限できました。 最後に、完全自動化ではなく半自動化として捉えることです。単純なシナリオであれば、サブエージェントの出力をほぼそのまま使用可能なことがわかりました。一方で、最終的なレビューや実際にシナリオを実行してみるなどの動作確認はまだまだ必要に思いました。 サブエージェントによるシナリオ移行は、手動での書き換えと比較して大幅な工数削減が期待できます。同様の移行を検討している方は、まず単純なシナリオから試し、ガイドラインを徐々に改善していくアプローチがおすすめです。 おわりに 本記事では、gatling-operatorのメンテナンス課題とk6導入時の工夫、シナリオ移行における生成AIの活用事例について紹介しました。 今回の負荷試験ツール移行では以下を実現しました。 負荷試験ツールのメンテナンス負荷軽減 コンテナイメージビルドからの脱却によるシナリオ修正サイクルの改善 Datadogダッシュボードによる試験結果の可視化と分析強化 生成AIを活用したシナリオ移行の効率化 今後は、残りのGatlingシナリオのk6への移行を進めつつ、Claudeサブエージェントへのプロンプトをさらに改善し、移行精度の向上に取り組んでいきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co 弊社のメトリクス保存期間は1年間です。永続的に残したい場合はダッシュボードのスクリーンショットで対応します。 ↩
アバター
.table-of-contents > li > ul > li > ul { display: none; } はじめに こんにちは、SRE部 検索基盤SREブロックの 花房 です。2025年12月11日に東京の虎ノ門で開催された「 OpenSearchCon Japan 2025 」にZOZOのエンジニア5人が参加しました。本記事では、会場の様子と印象に残ったセッションについて紹介します。 はじめに OpenSearchCon Japanとは 会場の様子 セッションレポート Maximize Resource Efficiency with Separated Index and Search Workloads 課題 解決策: ワークロードの物理的分離(Reader-Writer Separation) Remote Store(S3など)でデータの共有を実現 感想 Intelligent Japanese Search With OpenSearch 感想 Lessons From Migrating To OpenSearch: Shard Design, Log Ingestion, and UI Decisions 感想 OpenSearch Agentic Memory: Intelligent Context 感想 Mastering Search Performance: Advanced Query Insights for Production OpenSearch 感想 From Chaos To Control: GitOps Disaster Recovery With the OpenSearch Operator 感想 おわりに OpenSearchCon Japanとは オープンソースの分散型検索・分析エンジンであるOpenSearchに特化した国際カンファレンスです。OpenSearchの開発者やユーザが、主なユースケースである検索・オブザーバビリティ・セキュリティアプリケーションに関する技術的な最新情報や知見を共有します。 主催はLinux Foundation・OpenSearch Software Foundation・AWSです。毎年、世界各地で開催されており、今年は日本以外にヨーロッパ・インド・北アメリカ・韓国で開催されました。日本での開催は今年が初めてでした。 OpenSearchCon Japanのキービジュアル 過去のセッションの録画や資料は以下のリンク先から確認できます。本記事の執筆時点ではまだ公開されていませんが、OpenSearchCon Japanの録画も公開されると思います。 events.linuxfoundation.org 会場の様子 今回の会場はビジョンセンター東京虎ノ門でした。5階の3部屋の内、1部屋を受付・ロビーとして、2部屋を発表会場として利用していました。 ロビーでは飲み物や軽食が提供されており、配置されていた丸いテーブルは他の参加者と話しやすい環境を作り出していたと感じました。 受付・ロビーの様子 発表会場の様子 セッションレポート 特に印象に残ったセッションを紹介します。 Maximize Resource Efficiency with Separated Index and Search Workloads SRE部 検索基盤SREブロックの 富田 です。普段はZOZOTOWNの検索基盤の運用・改善を担当しています。私はIndexingとSearchのワークロード分離に関するセッションについて紹介します。 課題 OpenSearchでは、検索(search)とインデキシング(indexing)が同じDataノードで動くのが基本でした。しかし、この仕組みは以下のような課題がありました。 searchとindexingが同じリソース(CPU、メモリ、I/O)を奪い合うため、片方の負荷がもう片方に悪影響を与える 大量のindex更新が走ると、検索レイテンシが顕著に悪化する リソースを追加したくても「検索だけ増やしたい」「書き込みだけ減らしたい」といったワークロード別のスケールが出来ない 公開資料 「Maximize Resource Efficiency with Separated Index and Search Workloads」 P.14より引用 解決策: ワークロードの物理的分離(Reader-Writer Separation) OpenSearch v3.0で導入されたReader-writer Separationは、search用ノードとindexing用ノードを物理的に分離します。 検索処理:Searchノード(Reader) インデックス作成:Dataノード(Writer) これによりSearchノードは検索だけに集中し、indexing負荷の影響を一切受けなくなるというメリットがあります。 公開資料 「Maximize Resource Efficiency with Separated Index and Search Workloads」 P.18より引用 Remote Store(S3など)でデータの共有を実現 Searchノードがインデックスを持たなくても検索できる仕組みとして、Remote Store(リモートストレージ)が前提になります。 Primary shardがセグメントをアップロード Search ReplicaがRemote StoreからPullする この仕組みによってsearchとindexingの完全分離が実現されています。 感想 ZOZOTOWNの検索基盤でも、商品インデックスにindexingによる更新が走ると検索レイテンシに悪影響を与えるという課題に何度も直面していました。 今回のセッションで紹介されたReader-Writer Separationは、まさにこの問題を根本から解決できる仕組みなので非常に期待が大きい機能だと感じました。ただしこの機能はOSS OpenSearchのみ利用可能で、AWSのAmazon OpenSearch Serviceではまだサポートされていません。 非常に良い機能だと思いますし、AWSのサービス側でもサポートされる予定と聞いたので期待しています。 (2026/01/07修正)現時点ではAWSサービスでのサポート予定はまだありません。 Intelligent Japanese Search With OpenSearch 検索基盤部 検索技術ブロックの今井です。普段は、検索システムの開発や検索精度の改善に取り組んでいます。 私からは、Amazon Web Service Japanの後藤氏によるOpenSearchを活用したインテリジェントな日本語検索のセッションを紹介します。 検索システムは主にドキュメントを格納する indexing とクエリから結果を取得する retrieval の2つで構成されているのですが、セッションでは検索精度の観点で主に retrieval を取り上げています。日本語検索の精度を改善したい方、困っている方は内容を把握しておくと良いと思います。 発表の様子 OpenSearchで利用可能な4つのretrieval手法について紹介がありました。ここでは各手法の特徴やメリット・デメリットの話がありました。 語彙検索(Lexical Search) BM25アルゴリズムによるキーワードマッチで高速かつ透明性は高いが、同義語に対応できず文脈も理解できない 密ベクトル検索(Dense Vector Search) BERTなどで意味をベクトル化し、「犬」と「ペット」のような意味的に近い言葉もマッチできるが、計算コストが高くなぜマッチしたのかの説明が困難 疎ベクトル検索(Sparse Vector Search) SPLADEなどでキーワードに重みを付け、メモリ効率が良いが日本語対応モデルが少ない 初回はラフに検索して2回目に最終結果を出すために検索するTwo-Phase Searchや効率的にスパース検索するNeural Sparse ANN Search, SEISMICの新機能も簡単に紹介もあった ハイブリッド検索 上記手法を組み合わせ、スコアベースまたはランクベースでスコアを統合 ハイブリッド検索のベクトル用インデックスとして、ドキュメント全体のベクトルを持つのか、Nested k-NN機能で1ドキュメントにネストで複数ベクトルを持つのかなどベクトル検索のためのインデックス設計の重要性についても解説がありました。 発表の様子 検索精度の向上には以下の要素も重要です。 リランキングによる結果の再評価 クエリ拡張によるシノニム展開や意図理解 LLMを活用したAgentic Search リランキングでは、OpenSearchの新機能として ベクトル検索のMMRリランキング が導入されたようです。これは関連性と多様性の両方を考慮したリランキングをMMRというランキングのバランスを取るアルゴリズムで実現する機能です。 これらの技術を組み合わせることで、ユーザーの意図を深く理解し、高精度な日本語検索を実現できると紹介されました。 感想 日本語検索に関する現在の取り組みが広く分かるセッションでした。ベクトル検索のインデックス設計の話で、Nested k-NN機能で1ドキュメントにネストで複数ベクトルを持つ手法は、ドキュメントに重要要素が複数あるユースケースではこの手法を使うと効果的だと感じました。 また、Neural Search Pipelineについての解説の中では、クエリをSearch Pipelineの中でベクトル化する機能が備わっている話があり、モデルを配置しセットアップすることで容易に利用可能になる点で印象に残りました。 公式ドキュメントの「 Semantic search using a model in Amazon SageMaker 」には、SageMakerを利用したベクトル検索の導入方法が記載されており使い方のイメージができました。 Lessons From Migrating To OpenSearch: Shard Design, Log Ingestion, and UI Decisions 検索基盤部 検索基盤ブロックの岡田です。普段はZOZOTOWNの検索機能に関する機能追加や保守運用を行っています。私からは、Sansan株式会社 藤原氏のログ基盤をセルフホストのElasticsearchからAmazon OpenSearch Serviceへ移行するプロジェクトに関するセッションを紹介します。 1日約150GBという大規模なログ流入がある環境で、移行時に実際に直面したトラブルについて具体的な原因と対応を知ることができた大変興味深いセッションでした。以下の3つの具体的な課題とその際の対応が紹介されました。 可用性の確保(Yellowステータス対策) コスト重視のシングルノード構成ではレプリカを配置できず、ステータスがYellowになる課題がありました。これに対し、インスタンスサイズを下げて台数を増やす(複数台構成・マルチAZ)ことで、コストを維持したまま、許容リスクとのバランスを取った現実的な可用性を確保していました。 高負荷への対応(CPU高騰・バッファ溢れ) 構成上の問題でシャード数が3,000を超え、特定ノードでのインデックス作成負荷によりCPU使用率が高騰してしまいました。対策として、データ量に基づき「5日でローテーション」するISM Policyを適用してシャード数を約300まで削減しました。そうして更新間隔(refresh_interval)を120秒へ緩和することで解決を図りました。 運用リスクの顕在化(Redステータスとログ欠損) さらなる負荷軽減のためにレプリカを「0」にした結果、ノード障害時にシャードが消失し、クラスターがRedステータス(機能不全)に陥りました。これにより自動復旧せず手動対応が必要となり、ログ欠損も発生してしまったとのことです。最終的には、インデックス負荷を根本から低減するために、マッピングの見直しやRemote Backed Storageの検討といったアプローチが紹介されました。 感想 本セッションを通じて痛感したのは、安定運用は継続的なチューニングによって初めて実現するという点です。ベストプラクティスは万能ではなく、実際のワークロードを観察しながら構成を磨き続ける姿勢こそが成功の鍵であることを強く認識しました。 特に、OpenSearchをアプリケーションの検索エンジンとして利用する場合、この姿勢は一層重要になります。アプリケーションのトラフィック特性やデータ構成の仕様要件に踏み込んだ丁寧な設計と、それを支える継続的なモニタリングが不可欠であることを改めて学ぶことができました。 OpenSearch Agentic Memory: Intelligent Context 検索基盤部 検索基盤ブロックの 佐藤 です。AIエージェントに「長期記憶」を持たせるための基盤として提案された OpenSearch Agentic Memory に関するセッションについて紹介します。本セッションでは、従来のLLMが抱える「金魚の記憶問題」を解決し、ユーザーとの対話を継続的に深めていくための実装アーキテクチャやデモが数多く取り上げられていました。 発表内容については以下のブログで取り上げられています。詳しくはこちらをご参照ください。 opensearch.org OpenSearchを「記憶装置」として活用し、短期記憶と長期記憶を分離する設計は非常に興味深く、AIエージェントの実用性を大きく引き上げる手法だと感じました。また、REST APIベースのフレームワーク非依存な構成により、LangChainをはじめとする既存エコシステムと柔軟に統合できる点も魅力的でした。 本セッションが焦点を当てていたのは、AIエージェントが抱える本質的な課題──「コンテキストを保持できない」という問題でした。コンテキストウィンドウの制約により長い対話履歴を保持できなかったり、セッションを跨ぐと過去情報を完全に忘れてしまったりする限界が、これまでのユーザー体験を大きく阻害していました。 これに対し、発表者らが提示した解決策は OpenSearchを用いた永続的なエージェント用メモリ「Agentic Memory」 です。 アーキテクチャは大きく以下の4つのコンポーネントで構成されています。 公開資料 「OpenSearch Agentic Memory」 P.6より引用 Conversation Memory(短期記憶) :現在のセッションに必要な会話履歴を保持 Memory Builder(情報抽出) :LLMを用いて「記憶として残すべき情報」を抽出・構造化 Long term Memory(長期記憶) :永続的な知識としてOpenSearchに保存 Activity Log(監査ログ) :記憶の読み書きを追跡し、透明性を担保 この二層構造により、AIエージェントは必要なときにだけ長期記憶からベクトル検索で関連情報を取り出し、過去のやりとりを踏まえた文脈的に豊かな応答が可能になります。 具体的な実装ノウハウとしては、以下の3つの記憶戦略が紹介されました。 Semantic Strategy :会話の中から重要な事実や知識を抽出 User Preference Strategy :ユーザーの嗜好(好み、履歴、選択基準)を蓄積 Summary Strategy :セッション全体を要約して長期的な理解を保存 また、ユーザー別・エージェント別に独立した記憶領域である「 Namespaces(名前空間) 」を作れるため、マルチテナント環境への展開にもそのまま対応できる設計となっています。 デモでは、LangChainを用いたショッピングエージェントや旅行計画エージェントが紹介され、過去の嗜好や計画を思い出しながらユーザーに提案する様子が説明されました。特定のフレームワークに依存しないREST API構成のため、既存のエージェント基盤との統合性が高い点も強調されていました。 感想 AIエージェントの賢さはモデルの性能だけでなく、「何をどのように覚え続けるか」で決まる、というメッセージが非常に腹落ちしました。短期記憶と長期記憶をOpenSearch上で分離し、LLMはあくまで「記憶を整形する役」として使う設計は、運用とスケーラビリティの観点でも現実的だと感じました。 また、既存のLLMは膨大なコンテキストを一度に入力すると精度が低下する傾向があります。しかし、この二層構造により必要な情報だけを効率よく取り出せる点も魅力的でした。特に、ユーザー嗜好や行動履歴を扱える仕組みは検索のパーソナライズ機能との相性が良く、プロダクト体験の向上にも大きく寄与すると感じました。 Mastering Search Performance: Advanced Query Insights for Production OpenSearch SRE部 検索基盤SREブロックの 花房 です。このセッションでは、「Query Insights」というクエリパフォーマンス調査用プラグインについて紹介されました。 検索クエリのパフォーマンス調査や最適化が必要になる場面は多々あります。標準機能によりスローログは出力可能ですが、出力されたログをUI上で確認したり、詳細に分析したりするのは難しいという課題があります。 Query Insightsはクエリパフォーマンス分析のための機能と専用ダッシュボードを提供してくれるプラグインです。プラグインをインストールすると、OpenSearch Dashboardsに「Query Insights Dashboards」というメニューが追加され、GUIベースで詳細な分析が可能になります。プラグインに関連するリポジトリを以下に記載しておきます。 プラグイン: https://github.com/opensearch-project/query-insights ダッシュボード: https://github.com/opensearch-project/query-insights-dashboards Query Insightsでは現在、以下の3つの機能が利用可能です。 Top N queries :レイテンシ・CPU・メモリに基づいて、リソースを大量に消費しているクエリを特定できる。さらに、Pattern Grouping機能により類似したクエリはグループ化して表示も可能。 Live queries :実行中のクエリに関する各種メトリクスをリアルタイムで確認できる。 Configuration :Top N queriesの抽出閾値や集計間隔、Query Insights自体のデータの出力先を設定できる。 感想 クエリパフォーマンスの分析データを取得・保存する場合、やはりクラスタ全体への負荷影響が気になります。しかし、このプラグインでは優先度キューや非同期処理などを活用したアーキテクチャにより影響を最小限に抑えていました。ユーザのことを考慮し、導入しやすい工夫がされていると感じました。 Query Insights DashboardsのUIは使いやすい雰囲気を感じました。今後はProfile API Visualizerへの対応も進められるようなので、さらなる利便性の向上に期待を寄せたいと思います。 From Chaos To Control: GitOps Disaster Recovery With the OpenSearch Operator 花房 です。このセッションは、OpenSearch OperatorとGitOpsを活用した障害復旧の自動化手法についての発表でした。 OpenSearch Operatorは、OpenSearchの運用管理を自動化するKubernetes Operatorです。クラスタやダッシュボードをはじめとする基本的なリソースに加え、インデックスやスナップショットなどのライフサイクル設定もKubernetesのCustom Resource (CR) として宣言的に定義できます。発表の中では、Node pools, Roles, Plugins, Security, Dashboardsの5つが設定項目の例として挙げられていました。 github.com GitOpsは、Gitリポジトリを唯一信頼できるソースとして管理し、専用ツールによってリポジトリと実環境を同期させることでデプロイや運用を自動化する手法です。発表の中では、ツールとしてArgo CDを利用していました。 github.com OpenSearch OperatorとArgo CDを組み合わせることで、インフラ構築から詳細設定までを一貫してコードで管理し、障害発生時には自動復旧する高度な仕組みを実現していました。仮にクラスタが完全に消失しても、以下のプロセスを経て自動的に再構築・復旧が完了します。 Argo CDがGitリポジトリに基づいてKubernetesにクラスタをデプロイ OpenSearch Operatorがクラスタをプロビジョニング 感想 セルフホストでの運用や障害対応はどうしても負荷が高くなってしまうものですが、この方法ならマネージド並とはいかないまでも、運用負荷をかなり抑えつつシステムの安定性も保つことができると感じました。 注意点として、スナップショットの復元におけるデータの一部損失の可能性が挙げられていました。そのようなパターンも想定し、クラスタが再構築された後はインデキシングのバッチをフックし、インデックスを作り直すところまで完全に自動化できると理想的だと考えました。 おわりに 本記事では、OpenSearchCon Japan 2025の参加レポートをお届けしました。イベントへの参加を通して得られた主な収穫・知見は以下の通りです。 ZOZOTOWNの商品検索にとって有用と考えられる新機能について、日本の開発コミュニティの方から直接説明を聞き、理解を深められた 検索・オブザーバビリティ・セキュリティアプリケーションの各領域における日本国内でのユースケースやプラクティスを知れた Query InsightsやMCPサーバなど、ユーザの利用体験を向上する拡張機能について、デモを通じて効果と可能性を実感できた 1日だけの開催でしたが、その期間の短さを感じないほどOpenSearchに関する多くの学びを得られた充実したカンファレンスでした。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、「ZOZOMO」の ブランド実店舗の在庫確認・在庫取り置き 機能の開発を担当しているZOZOMO部OMOブロックの木目沢です。先日、プロダクト開発メンバーとビジネスメンバー合同で 2日間の「ユーザーストーリー」ワークショップ を開催しました。 1日目は「キャンプ」を題材にユーザーストーリーの型と会話を体験し、2日目は実際のZOZOMO店舗在庫取り置きでユーザーストーリーを書き、 優先順位を決めて実際に開発する ところまで行いました。 この記事では、その2日間をまとめて振り返ります。 目次 はじめに 目次 開発チームの課題感と、ワークショップのねらい なぜ「ユーザーストーリー」なのか? 1日目:キャンプでユーザーストーリーの型を体験する メインワーク1:キャンプで“WHAT”を考える メインワーク2:WHYを考える──価値の核心を探る メインワーク3:WHAT/WHYを“実現したと言える”受け入れ基準=AC(Acceptance Criteria)をつくる メインワーク4:環境が変わればACも変わる──秋キャンプに変更 1日目の振り返り:ユーザーストーリーは“会話”のための道具 2日目:実プロダクトで優先順位を決め、その場で開発まで 実プロダクトでユーザーストーリーを出し切る プロダクトオーナー×開発チームで優先順位付け ワークショップ全体を通しての学び おわりに 開発チームの課題感と、ワークショップのねらい まず前提として、私たち OMOブロック は「開発専門の部署」です。これまでは、ビジネス部門などから依頼(要望や企画)を受けて、それを実現するための開発をする、という流れが中心でした。 もちろん、そのやり方でもプロジェクトは作れます。ただ、続ける中でいくつものモヤモヤが見えてきました。 開発メンバーのアイデアが入りづらい ──開発メンバー一人ひとりは「開発者」という属性だけでなく、それぞれ異なるバックグラウンドや視点を持っている。そうしたメンバーのアイデアが活かされにくい状況はもったいない ビジネスと開発のやり取りが「依頼」と「回答」になりがちで、 多くのミーティングが組まれたり、Slackの会話が延々と続いたりする 結果として、 いわゆる社内の「請負開発」のようになっている ゴールや背景、ユーザーにとっての価値がよく見えないまま開発してしまうことが多かった 「これって、誰のどんな課題を解決したくてやっているのか?」 「この機能を作った先に、どんなアウトカムがあるのか?」 そういった肝心な部分が曖昧なまま、「仕様どおりに作ること」に引っ張られてしまうのは開発側にとっても力を発揮しにくい状況です。ビジネス側にとっても、本当に欲しかったものとずれる原因になります。 OMOブロックが持っている技術的な知見とビジネス側のアイデアを掛け合わせ、さらに各メンバーそれぞれのバックグラウンドや視点を活かし、 一緒にインクリメントを積み上げていきたい と考えています。 「開発」「ビジネス」という役割をはっきりと分けて仕事をするのではなく、役割の垣根を超えて、全員が“プロダクトが目指す世界・ユーザー価値・アウトカム”に焦点を当てている状態 を目指したいと考えています。 最終的にはこのようなことを目指したいと考えています。 スクラムガイド2020では、プロダクトについて以下のように定義されています。 A product is a vehicle to deliver value. It has a clear boundary, known stakeholders, well-defined users or customers. A product could be a service, a physical product, or something more abstract. The Scrum Guide 2020 より ここでは、プロダクトは「手段」であり、「価値を提供すること」が目的であると示されています。 開発チームが作るだけという状況はプロダクトではなく、プロジェクトです。プロダクトを作っていくという状況をつくるべく、ZOZOMO店舗在庫取り置きではスクラムを採用しました。 そして、今回開発とビジネス間における会話のための道具として、 ユーザーストーリー を活用することにしました。 ユーザーストーリーを共通言語にして、ビジネス・開発それぞれ同じ土俵で会話できるようにする第一歩として、このワークショップを企画しました。 なぜ「ユーザーストーリー」なのか? ユーザーストーリーを一言でいうと、 「価値のアイデアを、会話しやすい形に翻訳したもの」 です。 ビジネスと開発の間には、どうしても“言語のギャップ”があります。 ビジネス側は「売上」「会員数」「体験価値」などの言葉で語る 開発側は「API」「スキーマ」「パフォーマンス」「テストケース」などの言葉で語る どちらも間違っていないのですが、別々の言語のままでは、会話のハードルがどうしても高くなります。 そこで登場するのが ユーザーストーリー です。アイデア(価値の定義)は、次のような形で表現できます。 【WHO】◯◯において。 【WHAT】XXXしたい。 なぜなら【WHY】〜のため。 【AC(Acceptance Criteria)】それは何ができれば実現したと言えるか。 このフォーマットによって、以下のような効果が期待できます。 WHAT(なにがしたいか)/WHY(背景・価値)を、ビジネスと開発とで 同じ言葉で共有できる AC(Acceptance Criteria:受け入れ基準)は技術の用語を使わず、 「なにができればWHAT/WHYが実現できているか。検証可能なものを書く」 ことで、 ビジネス側も理解しやすくなる 結果として、ユーザーストーリーを中心に 自然な会話が生まれる ユーザーストーリーはそのまま プロダクトバックログアイテム となり、実装からスプリントレビューまで一貫して利用できる つまりユーザーストーリーは、 ビジネスと開発の垣根を取り払うためのツールになりうる のです。 私たちは、この「共通言語」をきっかけに、開発だけでなくビジネスも含めたチーム全員が、 ユーザー価値とアウトカムにフォーカスする状態 を作っていきたいと考えています。 1日目:キャンプでユーザーストーリーの型を体験する ここからは、2日間の流れを順番に振り返ります。まずは、ユーザーストーリーの基本を“遊びの題材”で体験した1日目からです。 メインワーク1:キャンプで“WHAT”を考える 今回のワークのお題は、 “夏の北海道にキャンプへ行く” という設定です。3チームに分かれてこの設定をもとにユーザーストーリーを作成していきます。 このシナリオに基づいて、「キャンプで何をしたいか?」= WHAT についてブレインストーミングを実施しました。 WHOは各チームのメンバー、つまり「私たち」です。 Miroの付箋には、各チームからさまざまなWHATが出ました。 森林浴したい 渓流釣りしたい 芝生を歩きたい 焚き火したい キャンプファイヤーしながら合唱したい 水辺を散策したい ここでは“良し悪し”をつけるのではなく、まずは出し切ることが目的です。「キャンプ」という1つの体験にも、こんなに多様な望みがあるのかと、チーム内でも驚きがありました。 メインワーク2:WHYを考える──価値の核心を探る 次のステップは WHY(なぜそれをしたいのか?) を書き添えることです。 例えば、 “森林浴したい” に対しては、こんなWHYが出てきました。 デジタルデトックスのため 何もしていないことをしてみたいから リラックスしたいから WHYを書いていくほど、単なる行動(WHAT)が、 「その人にとっての価値」 に変わっていくのがわかります。 あるチームでは、「WHYが深まるほど価値が整理されていく感じがする」という感想も出ていました。 メインワーク3:WHAT/WHYを“実現したと言える”受け入れ基準=AC(Acceptance Criteria)をつくる 続いて、ユーザーストーリーの肝とも言える AC(Acceptance Criteria:受け入れ基準) です。 ACとは、ざっくり言うと WHAT/WHY を実現した、終わった、と言える客観的な基準 のことです。 ACは技術の言葉を使わず、 「なにができればWHAT/WHYが実現できているか。検証可能なものを書く」 ことがポイントです。 WHAT:森林浴したい WHY:リラックスしたいから たとえば、このWHAT/WHYに対して、あるチームが出したAC案は次の通りでした。 まずは森林と呼べる場所へ行くこと デジタルデバイスの電源を8時間以上OFFにすること リラックスしたと感じたこと(アンケートなりで確認) ACで大事な点の1つは、 ACを「技術仕様のリスト」にしないこと です。 「APIレスポンスが〜」「DBが〜」といった実装寄りの言葉ではなく、あくまで 利用者視点で『できている状態』を書きます 。そうすることで、ビジネス部門のメンバーもストーリーの完了イメージを掴みやすくなります。 この「ビジネスでも理解できるAC」を軸に、実装方法は開発側で柔軟に検討することに、 ビジネスと開発の役割分担と協調のポイント があります。 また、ユーザーストーリーワークショップの題材では、技術的な実装は関係ないため技術的な言葉を意識することなく、検証可能なものという制約に集中できるのが狙いでもありました。 客観的な形で必ず検証可能なものであることもACでは重要です。 最終的にできたユースケースはこちらです。 メインワーク4:環境が変わればACも変わる──秋キャンプに変更 ワークショップの後半では、 「急に秋の北海道キャンプに変更!」 というイベントが発生します。 WHAT/WHYは変えないまま、季節変更に伴って ACを見直し てもらいました。 夏と秋では、以下のように前提条件が大きく変わります。 気温 日照時間 できないアクティビティも出てくる その変化を踏まえ、例えば「森林浴したい」のACは、以下のように現実的な条件が追加されました。 寒さ対策で1時間ウォーキングする ここからの学びはとてもシンプルで重要です。 ACは固定ではなく、状況に応じて調整・交渉してよい ということです。 だからこそ、ビジネス側も「これなら現場目線でOK」「ここは譲れない」といったコメントをしやすくなります。 リファインメントやレビューの場で、一緒にACを調整していくイメージです。 追加されたACを含む、最終的なユースケースはこちらです。 1日目の振り返り:ユーザーストーリーは“会話”のための道具 ワークを通して印象的だったのは、 付箋そのものより、付箋をきっかけにした会話の方が重要だった ということです。 WHAT/WHY/ACを作っていく過程で、以下のようなやり取りが自然と生まれ、議論の質が上がっていきました。 「それって本当にWHYに沿ってる?」 「そのACだと何が嬉しいんだっけ?」 「それならもっと良い表現があるかも」 ユーザーストーリーは、単なるアウトプットではなく、 価値を共通理解するための“会話” のために必要な道具なのだと体感できた1日目でした。 2日目:実プロダクトで優先順位を決め、その場で開発まで 2日目は、1日目で身につけた型と感覚を、 実際のZOZOMOプロダクト開発に適用する日 です。 実プロダクトでユーザーストーリーを出し切る まずは対象となるプロダクトを1つ選び、そこに対して「こうなったらもっと良くなるはず」というアイデアを、チームメンバー全員で付箋に書き出していきました。 ここでもフォーマットは同じです。 【WHO】◯◯において。 【WHAT】XXXしたい。 なぜなら【WHY】〜のため。 【AC(Acceptance Criteria)】それは何ができれば実現したと言えるか。 ビジネス側からは「この指標をもっと改善したい」「この業務オペレーションを減らしたい」といった声が上がりました。開発側からは「このフローは技術的に改善余地がある」「この組み合わせならすぐ試せそう」という意見も出ました。 役割を超えてどんどんストーリーが出てくるのが印象的でした。 プロダクトオーナー×開発チームで優先順位付け 次に行ったのが、出てきたユーザーストーリーの 優先順位付け です。 評価の軸はシンプルに2つだけに絞りました。 価値の高さ 開発のしやすさ(実現コストの低さ) Miro上に2軸のマトリックスを用意し、プロダクトオーナーと開発メンバーが会話しながら各ストーリーの位置を決めていきます。 「このストーリーについて、価値は高いけど既存システムへの影響が大きいのでコスト高め」 「これは小さいけど、いますぐ改善するとユーザー体験の印象がかなり変わりそう」 「この2つは似ているので、まとめて1つのストーリーにしませんか?」 といった議論を通じて、 価値とコストをセットで考える感覚 を共有できたのがよかったポイントです。 このプロセスを経てマトリックスの右上(=価値が高くて、かつ開発しやすい)に位置するストーリーの中から、 「取り組むべき1つ」を決定 をしました。 このストーリーは実際にプロダクトバックログに積まれ、リファインメントを経てスプリントで実装されました。 ワークショップ全体を通しての学び 2日間を通じて学んできたユーザーストーリーですが、最後に書籍の引用からユーザーストーリーの定義を振り返ります。 『「ユーザーストーリ」はXPで提唱されたプラクティスで、 ユーザーの言葉で書かれた機能の説明である。 そのものではないが、従来の「要求仕様書」を置き換えるものだ。 このユーザーストーリはアナログを重視し、 紙のカードに書いて会話によって伝えることが推奨されている。』 (引用)アジャイル開発とスクラム~顧客・技術・経営をつなぐ協調的ソフトウェア開発マネジメント 平鍋 健児 (著), 野中 郁次郎 (著) No 584 ※ 「ユーザーストーリ」と記述されていますが、原典を尊重し、そのまま引用しております。 開発・ビジネスの役割を超え、全員がユーザー価値・アウトカムにフォーカスする 第一歩として、このワークショップは良いきっかけになりました。 おわりに ユーザーストーリーワークショップ実施から3か月が経過しました。ワークショップの効果は非常に大きく、以下のような変化が起きています。 ビジネスチームのメンバーがリファインメントへ参加するようになり、ユーザーストーリーをベースとして会話するようになった スプリントレビューにも積極的に参加してもらうことになり、多くのフィードバックを得ながらプロダクトを進められるようになった プロダクトオーナーにビジネスチームのメンバーをアサインすることになり、ビジネスと開発の距離がさらに近くなった ビジネスチームのメンバーも開発チームのメンバーもアイデアを出しやすくなった 今後もユーザーストーリーを起点に、ビジネスと開発が一緒になってプロダクトを育てていく取り組みを続けていきたいと思います。 以上、ZOZOMOユーザーストーリーワークショップ 全2日のレポート でした。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。最後までご覧いただきありがとうございました! corp.zozo.com
アバター