TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

949

ZOZO開発組織の2025年11月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2025年11月は、前月のMonthly Tech Reportを含む計6本の記事を公開しました。今夏に実施した合同勉強会で発表した内容を基にした記事は、はてなブログランキングのおすすめエントリーとして紹介されました。 🏅今週のはてなブログランキング〔2025年11月第2週〕より、おすすめエントリーを紹介します🏅 ZOZOTOWN フロントエンドにおけるディレクトリの分割戦略 - ZOZO TECH BLOG https://t.co/stuhjLPhsR hiro @hiro0218 さん — はてなブログ|思いは言葉に。 (@hatenablog) 2025年11月12日 協賛 2025年11月は「 FlutterKaigi 2025 」「 JJUG CCC 2025 Fall 」そして「 アーキテクチャConference 2025 」の3つのカンファレンスに協賛しました。 technote.zozo.com techblog.zozo.com technote.zozo.com techblog.zozo.com technote.zozo.com 登壇 Kotlin Fest 2025 11月1日に開催された「 Kotlin Fest 2025 」に、ZOZOTOWN開発2部のにしみー( @nishimy432 )が登壇しました。 techblog.zozo.com 📢 ZOZOエンジニア登壇情報 #KotlinFest 2025にZOZOTOWN開発2部のにしみー @nishimy432 が『AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド』というテーマで登壇します! 🕕時刻 18:20 - 18:40 📍場所 var(ホールB) 🔗 https://t.co/Yq8yOH9PBL ぜひお越しください! — ZOZO Developers (@zozotech) 2025年10月31日 speakerdeck.com JJUG CCC 2025 Fall 11月15日に開催された「 JJUG CCC 2025 Fall 」に、ZOZOのJavaチームより井草、半澤、宮澤の3名がセッションに登壇しました。また、スポンサーLTに平林が登壇しました。 📢 JJUG CCC 2025 Fall ZOZOエンジニア登壇情報 #1 ZOZOのJavaチームより商品基盤部の井草が『膨大なデータをどうさばく?Java × MQで作るPub/Subアーキテクチャ』というテーマで登壇します! 🕚️11:00 - 11:20 📍 Room J ぜひお越しください! https://t.co/n2PIrm0tU1 #jjug_ccc — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com 📢 JJUG CCC 2025 Fall ZOZOエンジニア登壇情報 #2 ZOZOのJavaチームより、ECプラットフォーム部でブロック長・テックリードを務める半澤が『ZOZOTOWNカート決済リプレイス ── モジュラモノリスという過渡期戦略』というテーマで登壇します! 🕐️13:15 - 13:35 📍 Room J #jjug_ccc — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com 📢 JJUG CCC 2025 Fall ZOZOエンジニア登壇情報 #3 ZOZOのJavaチームより、ZOZOMO部の宮澤が『Axon Frameworkのイベントストアを独自拡張した話』というテーマで登壇します! 🕐️13:40 - 14:00 📍 Room J ぜひお越しください! #jjug_ccc — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com speakerdeck.com JSConf.jp 2025 Pre Event 11月15日に開催された「 JSConf.jp 2025 Pre Event 」に、計測システム部の林が「 Redux → Recoil → Zustand → useSyncExternalStore: 状態管理の10年とReact本来の姿 」というタイトルで登壇しました。 📢 ZOZOエンジニア登壇情報 本日 11/15 開催の https://t.co/iHfqcKDcec 2025 Pre Event に計測システム部の林が『Redux → Recoil → Zustand → useSyncExternalStore:状態管理の10年とReact本来の姿』というテーマで登壇します! https://t.co/lmBQEUTSvI #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com JSConf JP 2025 11月16日に開催された「 JSConf JP 2025 」に、計測システム部の林と、WEARフロントエンド部 テックリードの冨川( @ssssotaro )が登壇しました。 本日 11/16 に開催される『JSConf JP 2025』にて、計測システム部の林 @www_REM_zzz が『JavaScript・TypeScript安全開発カルタ』というタイトルで登壇します! 🕙 10:40-10:50 📌 Track B 🔗 https://t.co/TXH8nlUmVM #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月16日 本日 11/16 に開催される『JSConf JP 2025』にて、WEARフロントエンド部 テックリードの冨川 @ssssotaro が『Atomics APIを知る』というタイトルで登壇します! 🕙 10:20-10:30️ 📌 Track C 🔗 https://t.co/b16g68OSpv #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月16日 speakerdeck.com Flutter ZY 11月19日に開催された「 Flutter ZY 」に、ZOZOマッチの開発を担う新規事業部の池田と大野の2名が登壇しました。 speakerdeck.com speakerdeck.com 開発組織でアクセシビリティを進める技術 by SmartHR 11月19日に開催された「 開発組織でアクセシビリティを進める技術 by SmartHR 」に、ZOZOTOWN開発2部の菊地( @hiro0218 )が「 セマンティックHTMLによる アクセシビリティ品質向上の基礎 」というタイトルで登壇しました。 speakerdeck.com アーキテクチャConference 2025 11月20日に開催された「 アーキテクチャConference 2025 」1日目に、物流開発部の矢部とSRE部の杉山が「 巨大モノリスのリプレイス──機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略 」というタイトルで登壇しました。 「アーキテクチャConference 2025」にて、SRE部の杉山と物流開発部の矢部が『巨大モノリスのリプレイス機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略』というタイトルで登壇します! 🕑️14:05-14:45 📌 E会場 🔗 https://t.co/FlMbbz1Cxc #zozo_engineer #アーキテクチャcon_findy pic.twitter.com/4dm5rkjtzL — ZOZO Developers (@zozotech) 2025年11月20日 speakerdeck.com After JJUG CCC 2025 Fall 11月25日に開催された「 After JJUG CCC 2025 Fall 」に、ZOZOMO部の木目沢( @pilgrim_reds )が「 意外と難しいドメイン駆動設計の話 」というタイトルで登壇しました。 speakerdeck.com Mobile App with AI Meetup Tokyo 11月28日に開催された「 Mobile App with AI Meetup Tokyo 」に、技術戦略部の堀江( @Horie1024 )が「 ZOZOにおけるAI活用の現在 ~モバイルアプリ開発でのAI活用状況と事例~ 」というタイトルで登壇しました。 『Mobile App with AI Meetup Tokyo』で技術戦略部の堀江が発表した資料を公開しています!ぜひご覧ください! 『ZOZOにおけるAI活用の現在 ~モバイルアプリ開発でのAI活用状況と事例~』 https://t.co/GiwdnPlqtL #zozo_engineer #mobilewithai — ZOZO Developers (@zozotech) 2025年12月1日 speakerdeck.com フロントエンドカンファレンス関西2025 11月30日に開催された「 フロントエンドカンファレンス関西2025 」に、ZOZOTOWN開発2部の齋藤( @Jin_pro_01 )が『 TypeScriptがブラウザで実行されるまでの流れを5分で伝えたい 』というタイトルで登壇しました。 11/30に開催される『フロントエンドカンファレンス関西2025』にて、ZOZOTOWN開発2部の齋藤 @Jin_pro_01 が『TypeScriptがブラウザで実行されるまでの流れを5分で伝えたい』というタイトルで登壇します! 🕙 16:40~ 📌 ナレッジワークルーム 🔗 https://t.co/hTygsaWYSM #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月28日 speakerdeck.com 掲載 HHKB × ZOZOTOWNコラボレーション 11月18日の14:00から12月5日の11:59にかけて、HHKBことHappy Hacking KeyboardとZOZOTOWNのコラボレーションアイテムの受注販売を実施しました。これは昨年実施したGitHubとZOZOTOWNのコラボレーションに続くITコラボ第2弾として企画されたもので、多くの注目を集めました。 corp.zozo.com www.pfu.ricoh.com pc.watch.impress.co.jp www.itmedia.co.jp www.4gamer.net realsound.jp www.gizmodo.jp また、受注販売期間中に実施された HHKBユーザーミートアップ Vol.9 にて、HHKB × ZOZOTOWNのコラボアイテムを展示するブースを設けたほか、ZOZOコラボ記念トークセッション「 HHKB×FASHION TECHでひらく、創造性の扉 」にて、技術戦略部の諸星( @ikkou )とIT支援部の溝渕( @assu_ming )が登壇しました。 green-keys.info 以上、2025年11月のZOZOの活動報告でした! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。Developer Engagementブロックの @wiroha です。2025年11月13日に「 FlutterKaigi 2025 」が開催されました。ZOZOはSilverスポンサーとして協賛し、スポンサーブースを出展しました。ZOZOは、2025年6月30日にFlutter製アプリ「 ZOZOマッチ 」をリリースしており、今回初めてFlutterKaigiに協賛しました! technote.zozo.com 本記事では、エンジニアが気になったセッション、協賛ブースの様子、スカラーシップスポンサーとして参加した学生支援交流イベントについてお伝えします! エンジニアが気になったセッションの紹介 Dart and Flutter MCP serverで実現するAI駆動E2Eテスト整備と自動操作 新規事業部の池田です。酒井さんによる「 Dart and Flutter MCP serverで実現するAI駆動E2Eテスト整備と自動操作 」のセッションが勉強になりました。E2Eテストは便利な反面、メンテナンスや実行コストが高くて「なかなか運用が大変…」という印象をずっと持っていたので、AIを使って効率化できるという話にはかなり惹かれました。特にClaude CodeやMCPを使って、UIの操作をAIが理解して自動で実行してくれる仕組みはすごく興味深かったです。実際のデモを見るとまだ時間がかかったり、動作が少し不安定な部分もあったりするようですが、今後モデルが進化していけば現実的に使えるレベルになりそうだなと感じました。 全体を通して、AIを使ってE2Eテストをより柔軟で効率的にしていく流れが見えてきて、今後のモデル改善やツールの進化にすごく期待したいと思いました。 アプリバイナリに対する不正対策とセキュリティ向上 新規事業部のじゅんじゅんです。FujiKinagaさんによる「 アプリバイナリに対する不正対策とセキュリティ向上 」のセッションがとても勉強になりました。 普段はアプリ開発でバイナリレベルまでセキュリティを意識する機会があまりないため、興味を持ってこのセッションを見に行きました。Firebaseではクラッシュ分析などを行っていましたが、「Firebase App Check」というツールは初めて知り、とても便利そうだと感じました。近年、サイバー攻撃を受けた企業のニュースを目にすることも増えており、このようなツールを活用して監視体制を整えることの重要性を改めて実感しました。 また、「 root_jailbreak_sniffer 」という脱獄検知パッケージも初めて知りました。モバイルアプリにおけるセキュリティ対策の幅広さを理解し、ひとつ詳しくなれた気がします。 通信の暗号化、コードの難読化、秘匿情報の管理、OSやSDKを最新の状態に保つなど、基本的な対策を徹底することの大切さも学びました。 ブースレポート 協賛ブースではZOZOマッチのサービスを知るきっかけになればと、恋みくじ企画を実施しました。技術に絡めたおみくじを楽しんでいただきました。また、ご縁がありますようにという願いをこめたノベルティ「色縁ぴつ(色鉛筆)」の配布も行いました。 実際の開発で使っているWidgetbookの展示も行いました。「実際のプロダクトで使われているものを見られる機会は珍しい」という声を多くいただき、導入のきっかけや運用方法、どんなメリットがあるのかといった質問もたくさん寄せられました。開発現場でのリアルな事例をお伝えできたのは、とても良い機会になったと思います。 また、ZOZOマッチアプリを実際の端末で体験してもらい、アニメーションやUIの細かな部分まで見ていただけました。Flutterならではの動きやデザインの表現力を実際に感じてもらえたのが嬉しかったです。 さらに、ZOZOとして初めてFlutterを採用したプロジェクトということもあり、「なぜFlutterを選んだのか?」「導入の際にどんな課題があったのか?」といった質問も多くいただきました。技術選定の背景やチームのチャレンジに興味を持ってもらえたのはとても印象的でした。 学生支援交流イベント 新規事業部のじゅんじゅんです。今回のFlutterKaigiでは、初の試みとして学生交流イベントを実施しました。イベント期間中は専用ブースを出展し、特定の時間に学生グループが順番に訪れる形式で交流しました。ブースではまず会社を紹介し、その後はカジュアルに学生の皆さんと話す流れになっていました。 ブースに訪れた多くの学生と直接話すことができ、エンジニア歴や得意な言語、開発経験などを伺うことができました。Flutterを触り始めたばかりの方から、数年使い込んでいる方までレベルも幅広く、非常に刺激的な時間でした。また、印象的だったのは関東以外の学生が大半を占めていたことです。特に関西からの参加者が多く、Flutterコミュニティの広がりを肌で感じました。 今回が初めての学生支援・交流イベントということで、どのような雰囲気になるのか不安もありましたが、結果として多くの学生と実際に言葉を交わす貴重な機会となり、想像以上に充実した時間を過ごせました。来年は、今回の経験を活かしてより多くの学生が参加できるよう、規模を拡大して開催してほしいと思います。 おわりに FlutterKaigi 2025では、最新のFlutter技術に触れられる貴重な機会となり、ZOZOとしてもFlutterコミュニティへの貢献と交流を深めることができました。今後もFlutterを活用した開発を進めていく中で、今回の学びや出会いを活かしていきたいと思います。実行委員会の皆さま、参加者の皆さま、ありがとうございました! FlutterKaigiをきっかけに、ZOZOマッチのFlutterアプリ開発にご興味のある方は、ぜひ以下のリンクから関連記事をご覧ください。 ZOZOマッチアプリのアーキテクチャと技術構成 FigmaからFlutterへ ── デザイントークン自動変換とUIカタログで実装を加速 デバッグメニューでFlutterのアプリ開発をスムーズに! ZOZOマッチアプリのメッセージ機能を支えるFlutter × GraphQLの実装 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
アバター
目次 目次 はじめに この記事で伝えたいこと GitHub Universe 2025現地レポート GitHub Universe 2025とは 開催地(サンフランシスコ)の様子 会場(フォートメイソンセンター)の様子 現地でのエピソード GitHub Universe 2025発表内容の所感 GitHubはAIコーディングの前途を照らす 新機能の発表からスタンスが伺える 例: Code Quality GitHubはこれからも"developer pain"を解消し続ける おわりに はじめに ブランドソリューション開発本部FAANS部バックエンドブロックの 輿水 です。 現地時間10月28日、29日の2日間、サンフランシスコで開催されたGitHub Universe 2025に参加してきました。本記事では、現地の様子をお伝えしつつ、発表内容の所感を綴ります。 この記事で伝えたいこと 新機能の発表から、"developer pain"を解消し続けるというGitHubの揺るぎないスタンスが伺えた AIコーディングへの楽観論が見直されつつある中、GitHubがその前途を照らしてくれるという期待が持てた GitHub Universe 2025現地レポート GitHub Universe 2025とは GitHubが毎年開催する開発者向けカンファレンスです。キーノートでの新機能発表を皮切りに、数多くのセッションやワークショップが行われます。昨年に引き続き、今年もサンフランシスコのフォートメイソンセンターが会場となりました。 当社でもGitHubやGitHub Copilotを日常的に活用していることから、今回、参加する運びとなりました。 開催地(サンフランシスコ)の様子 アメリカ西海岸、シリコンバレーのお膝元で、エンジニアなら一度は訪れてみたい憧れの地です。ゴールデンゲートブリッジ、アルカトラズ島、セールスフォース・タワーなど、数多くのランドマークがあることでもよく知られています。 当日は天候に恵まれました。緯度は日本の東北地方とほぼ同じですが、地中海性の気候のおかげで日中は羽織るものが要らないほどの陽気でした。気温・湿度ともに快適そのものでした。 そして、さすがはサンフランシスコ、自動運転タクシーのWaymoが当たり前のように街中を走っています。むしろ、タクシーやライドシェアよりWaymoの方が多いのではないか、と感じるほどでした。 会場(フォートメイソンセンター)の様子 会場のフォートメイソンセンターはサンフランシスコの海沿いに位置する元軍事施設です。現在はリノベーションされ、芸術と文化の発信地として活用されています。 会場入り口です。写真は早朝でまだ陽が昇りきっていないため少し暗いですが、実際は朝から参加者の熱気で溢れていました。 会場内のパノラマ。それぞれの建物が異なるテーマのパビリオンになっています。 パビリオンの中はこのような雰囲気です。 奥へ進むと、壁沿いにスポンサーブースがずらりと並んでいました。 ここがキーノートの会場。数々の新機能がここで発表されました。 ちなみに、朝食・昼食だけでなく、おやつのドーナッツまで無料で振る舞われていました。円安の昨今、サンフランシスコでの食事代は馬鹿になりません。これは本当にありがたかったです。写真は朝食のフルーツカップです。種類が豊富で個数制限もありません。写真は実際に食べたもののごく一部である、ということだけ付け加えておきます。 現地でのエピソード 今回、日本からの参加者向けに、GitHubの方々が懇親会や本社見学会を企画してくださいました。単身での出張だったため、これは本当に心強かったです。何より、いつか訪れたいと夢見ていたGitHub本社に足を踏み入れることができ、感無量でした。 素晴らしい機会をご用意いただいたGitHubの皆様、ありがとうございました。 GitHub Universe 2025発表内容の所感 GitHubはAIコーディングの前途を照らす ここからは、発表内容についての所感を綴ります。 GitHubは"developer pain"の解消で一貫している。だから、AIコーディングの未来はきっと明るい。 これが、今回私が最も強く感じたことです。 AIコーディングはソフトウェア開発を激変させましたが、同時に新たな課題も生んでいます。GitHubはその負の側面から目を逸らさず、新機能を通じて解消しようとしています。Agent HQやCode Qualityといった発表は、まさにその姿勢の表れだと感じました。 詳しく見ていきましょう。 新機能の発表からスタンスが伺える AIコーディングが開発を加速させるという期待は、ある程度現実のものとなりました。しかし一方で、逆効果や負の面も浮き彫りになってきています。 ある研究 ではAIコーディングによって逆に生産性の低下が示唆されており、技術的負債やスケーラビリティの低下、システムの不安定化を懸念する 識者 もいます。 もちろんAIコーディングは発展途上であり、これが本質的な課題なのか、過渡期ゆえの現象なのかは慎重な見極めが必要です。とはいえ、現場の感覚として、AIコーディングにある種の「つらみ」を感じる瞬間があるのは事実ではないでしょうか。 GitHub Universe 2025での発表は、この課題を直視したものでした。 キーノート でGitHubのKyle Daigle氏はこう述べました。 GitHubは"developer pain"を解消し、混沌を鎮めるために存在する(GitHub exists to solve developer pain and to tame the chaos.) AIコーディングは本来、私たちがより多くのことを成し遂げるためのものでした。しかし皮肉なことに、IDE、ターミナル、Webのチャット、モバイルアプリ…と、ツールを行き来する手間は増え、新たな"developer pain"が生まれています。その解決策として発表されたのが、 Agent HQ でした。その他にもCode Qualityなどの新機能が、同様にAIコーディングの"developer pain"を解消するものとして発表されました。 いつの時代も、"developer pain"を解消することを基本姿勢としてきたGitHubです。AI全盛の時代になっても、そのスタンスは変わらないことが伺えました。 次のセクションでは、Code Qualityを例に、GitHubがどう課題に向き合おうとしているのか掘り下げてみます。 例: Code Quality 本カンファレンスで、 GitHub Code Quality のパブリックプレビューが発表されました。 関連セッション の冒頭、GitHubのMarcelo Oliveira氏が用いたレーシングカーのアナロジーが非常に印象的でした。 彼はF1の伝説的ドライバー、Alain Prost氏の言葉を引用しました。 速く見えるときは滑らかではなく実際には遅い。遅く見えるときこそ滑らかで本当に速い(When I look fast, I'm not smooth and I am going slowly. And when I look slow, I am smooth and I'm going fast.) 真に速いドライバーは滑らかでコントロールが効いているのです。 Oliveira氏はこれをソフトウェア開発に置き換え、コード生成はストレート、コードレビューはコーナーであると説きました。ストレートでいくら加速しても、コーナーを曲がりきれなければ意味がありません。スピードとコントロールはトレードオフではなく、両立させるべきものなのです。 この話は、現在の"developer pain"の本質を突いています。AIはコーディングを劇的に加速させました。しかし、そのスピードにコントロールが追いついているのか、私たちは知る術を持っていませんでした。いわば、スピードメーターやテレメトリーがない状態でアクセルを踏み続けているようなものです。自分がどれほどの速度で走っているのか分からないままコーナーに突入し、曲がりきれずにクラッシュして、そこで初めて速すぎたと気づく。つまり、本番障害や技術的負債の蓄積という事故が起きてから、ようやく問題に気づくのです。これが今の私たちの"developer pain"です。 GitHub Code Qualityは、このテレメトリーの役割を果たします。AIと静的解析を組み合わせ、スピードが出すぎていないか、コントロールが失われていないかをモニタリングし、問題があればドライバー(開発者)に警告してくれるのです。 具体的には、プルリクエストやリポジトリのスキャンでの品質の問題検出、GitHub Copilotによる自動修正、ルールセットによる品質基準の強制などが可能です。コード品質は保守性(Maintainability)と信頼性(Reliability)の2カテゴリで評価され、ダッシュボードでスコアを追跡できます。検出にはCodeQLによる静的分析とAI分析が併用され、C#、Go、Java、JavaScript、Python、Ruby、TypeScriptなど主要言語に対応しています。 AIコーディングで加速したはいいが、品質が見えなくて不安。そんな現場の"developer pain"に寄り添い、必要な計器を与えてくれる、非常に頼もしい機能だと感じました。 GitHubはこれからも"developer pain"を解消し続ける Oliveira氏は、スピードと品質がトレードオフになるようなことはもう二度とないようにしていこう、というメッセージでセッションを締めくくりました。この言葉に、これからも"developer pain"を解消し続けるという、GitHubの姿勢が凝縮されていると思います。 AIコーディングへの楽観論が見直されつつある昨今ですが、GitHubがその前途を照らしてくれるなら、私たちは安心してアクセルを踏み続けられます。そう期待させてくれるカンファレンスでした。 おわりに 今回は私にとって初めての海外カンファレンス参加でした。単身での渡航、時差、治安、正直なところ不安もありました。しかし、やはり行って良かったです。得られたものは想像以上に大きかったです。このような機会を与えてくれた会社に感謝しています。 ぜひまた、この場所に戻って来たいと思います。最後までお読みいただき、ありがとうございました。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、EC基盤開発本部SRE部の杉山です。普段はSRE部のテックリードを務めています。 本記事では、2024年10月〜2025年4月に実施した、オブザーバビリティ製品移行の実現可能性を検証するPoC(Proof of Concept)について、その検証観点や分かったことを紹介します。 目次 はじめに 目次 前提 背景 目的 PoCチーム フェーズ/スケジュール Ph.1 機能検証について 検証対象の機能一覧 課題と対応 対応策 機能検証まとめ Ph.2 運用検証について 運用フローの変更 Traceの見え方 IaC化についての移行コスト 運用検証まとめ まとめ 前提 ※本記事の内容は、2024年10月〜2025年4月に実施したPoCの検証結果に基づいています。記載している機能や挙動は当時のバージョンでの確認内容であり、今後のアップデートにより変更される可能性があります。 背景 当社では、監視・ログ収集・トレースなどのオブザーバビリティを実現するために、特定のオブザーバビリティ製品を利用しています。しかし、技術の進化や要件の変化に伴い、他製品への移行を検討する際に「現実的に移行は可能なのか?」という問いに対して、確信をもって答えづらい場面がありました。そこで、実際に移行が可能かどうかを確認するPoCを実施し、その知見を本記事にまとめました。 目的 PoCの目的は以下の通りです。 移行に伴う技術的課題の洗い出し 移行ができない機能の代替案の技術検討と仮実装 移行後の運用影響の調査 PoCチーム PM/テックリード:杉山(筆者) メンバー:各マイクロサービス担当SREより合計10名 フェーズ/スケジュール Ph.1:機能移行の検証(2024/10〜2025/01) Ph.2:運用影響の検証(2025/02〜2025/04) Ph.1 機能検証について 現状利用している全機能を対象に、移行可否を検証しました。以下に検証対象の機能一覧を示します。対象は多岐にわたり主要な機能はすべて網羅しています。 検証対象の機能一覧 Integrations AWS Integrations CloudWatch Metrics (Standard) CloudWatch Custom Metrics CloudWatch Metric Streams PrivateLink経由での接続 Slack Integration Webhook Integration PagerDuty Integration Elasticsearch Integration Kubernetes Integrations Sentry Integration GCP Integrations Infrastructure Monitoring Hosts Containers Kubernetes Processes Serverless Network APM Service Catalog Traces Profiles DBM(Database Monitoring) DSM(Data Streams Monitoring) APM Instrumentation Auto instrumentation Java Go Istio Manual instrumentation Java Go Istio Custom spans Custom metrics Istio Ingress Gatewayのトレース取得 サービス間通信のトレース取得 Progressive Delivery対応 レガシーシステム (VBScript) VBScript環境のTrace Monitor Threshold Alert 発報条件 複合モニター 条件の種類 Anomaly Detection(異常検知) Downtime設定(任意時間のアラートミュート) ワンショットダウンタイム スケジュールダウンタイム Traceメトリクスの利用可否 Metrics System Metrics CPU メモリ ディスク ネットワーク Runtime Metrics GC Heap Non-heap Thread Custom Metrics CloudWatch LogsのMetricsFilterで生成したカスタムメトリクス送信 CronJobなどから送信しているカスタムメトリクス Argo Workflowのメトリクス送信(Custom Metricsと同様に取得可能か) Dashboard Service Dashboard SLO Dashboard Logs Filter機能 AWS S3 Archive機能 任意の場所やSaaSへのフォワード レガシー環境の独自ライブラリからのログ送信 Synthetics API Test Browser Test Private Locations IaC対応 Terraform対応 SSO対応 Azure AD連携 ORG分割(プロダクト別) 課題と対応 検証対象の機能を網羅的に確認し、各機能の移行可否を評価しました。多くは移行可能でしたが、一部は移行先で同等機能が存在しない、あるいはOSS仕様上の制約があるなどの理由で、難易度が高いものもありました。 対応策 OSSはコントリビュートによる機能追加の可否まで踏み込みました。また、レガシー環境向けの独自ライブラリについては、筆者が作者であったこともあり、移行先製品が対応するOpenTelemetryを利用したライブラリを新規作成しました。以下は一部の対応例です。 Java Auto Instrumentation 自動計装では、一部のSpan情報やResource属性が不足しており、十分なトレースデータを取得できないケースがある。 必要な情報を補完するため、該当箇所ではマニュアル計装を追加して対応。 一部のJVMメトリクスは自動計装のみでは取得できない。 javaagentを起動オプションで手動起動することで、取得範囲が拡張されることを確認。 Flagger Istio上でProgressive Delivery(Canaryリリースなど)を制御するために利用しているFlaggerについて、移行先候補のProviderがなく利用できない。 OSSにコントリビュートして機能追加を行い、マージ後に利用可能であることを確認。 Istio Auto Instrumentation Istioでは、Tracerを有効化する際に、複数の監視バックエンドへネイティブ形式のトレースデータ(テレメトリ)を同時に送信できない。 OpenTelemetry Collectorの設定をカスタマイズし、複数の宛先にOTLP形式でトレースを送信する構成で対応。 既存のネイティブ形式は利用できないため、トレース情報の差分を確認。 多少の違いは、Collectorの設定ファイルでカスタムProcessorを定義して対応。 この構成により、複数のバックエンドに同一トレースデータを分岐して送信できるようにした。 Log Forwarding オブザーバビリティ製品と、Amazon S3の両方にログを送信したい。 複数の宛先へのログ送信は、OpenTelemetry Collectorのカスタムビルド(Linux/Windows)で対応。 必要な設定とExporterを追加し、AWS S3など複数の出力先へ送信できるようにした。 Legacy Tracer Library リプレイス未完のレガシー環境はTraceに非対応。 移行先に対応したOtelTracerCOM Library(Windows向け)を作成。 手動計装でDLLを適宜利用し、複数の監視バックエンドへトレースデータを送信できるようにした。 「無いものは作る!」という方針のもと、チームで積極的に課題解決に取り組みました。 その結果、製品の仕様によらない課題については概ね対応の見通しを立てることができました。 機能検証まとめ 検証対象の多くは移行可能であることを確認。 一部はOSSへのコントリビュートや独自ライブラリで補完可能。 検証の結果、現時点の要件では一部機能が満たせないため、継続利用を含めた複数の選択肢を検討することにした。 なお、製品側の進化により状況が変わる可能性もあるため、継続的な情報収集が重要。 例:DBMはPoC時点では移行不可能だったが、2025年12月時点では利用可能になっていることを確認(機能の詳細は未確認)。 Ph.2 運用検証について 運用検証は下記観点で評価しました。 運用フローの変更とその影響 Traceの見え方(トラブルシュートの容易さ) IaC化(Infrastructure as Code)に関する移行コスト ダッシュボード構成などの運用要素 移行作業全体のコストとドキュメント整備 以下に、特に注目すべき検証内容を記載します。ダッシュボード構成および移行コストに関する内容は、後述の「運用検証まとめ」で整理しています。 運用フローの変更 移行先製品の推奨運用フローと現行フローを比較したところ、オブザーバビリティの思想やトラブルシュート手法に大きなギャップがありました。現場では、開発環境で実際に変更後のフローを試してもらい、フィードバックを収集しました。 その結果、運用ドキュメントの大幅な改定が必要であることに加え、ドキュメント整備や教育にかかるコストも想定以上に大きいことが分かりました。特にトラブルシュートに関しては、Traceを起点とする現行の調査方法と大きく異なるため、現場の混乱が見られました。現場からは「運用方法をどう適応させるか」といった懸念もあり、チームで新しい運用スタイルに合わせる必要性が明確になりました。 このように、机上では問題なさそうに見える変更でも、運用検証を通じて現場の意見を集めることの重要性を再認識しました。 Traceの見え方 移行先製品のTrace画面(UI、情報量、表示形式)は、現在利用している製品とは設計思想が異なり、構成や操作感にも違いがありました。その結果、トラブルシュートの手順や情報の見せ方にも変化が生じています。 検証時に挙がった主な意見は以下の通りです。 画面遷移の構成が異なり、エラーの発生箇所を特定するまでの流れが変わる。 サービス間の呼び出し関係やトレースの関連付けの表現が異なり、把握までに慣れが必要。 Span情報の扱いや付加できるメタデータの形式が異なるため、一部で追加計装が必要。 Trace、メトリクス、ログの紐づけ方が製品によって異なり、調査の進め方も変わる。 このような違いは、単なるUIの差ではなく、オブザーバビリティ製品がもつデータモデルや設計思想の違いによるものと考えられます。どちらが優れているという話ではなく、各製品が重視する観測軸や分析のアプローチが異なるため、運用チームとしてはそれに合わせたワークフローを再設計する必要があると感じました。 検証を通じて、トラブルシュートのUXはツールの機能だけでなく、チームの習熟度や運用文化にも大きく影響されることを改めて実感しました。 IaC化についての移行コスト 現行はTerraformによるIaC化が進んでおり、コード管理はMUST事項です。 移行先でTerraform化を進める場合は、手順が複雑になることが分かりました。 概算では、IaC化におおよそ半年超の工数が見込まれ、大きな負荷になると判断しました。 運用検証まとめ ドキュメント上の機能比較だけでは見えない、ツールの設計やチームの慣れなど複数の要素が、障害対応の速度や品質に影響することが分かりました。ダッシュボード構成の表現も変わります。慣れの問題もありますが、これらの学習コストも見逃せません。現場運用者の声は重要な判断材料です。IaC管理においては、既存コードの変換は難しく、新たにコード化する必要があります。TerraformのImport/Exportなど、コード化を支援する機能が備わっている点も、製品選定において非常に重要なポイントだといえます。 まとめ 本記事では「オブザーバビリティ製品移行の実現可能性を検証したPoCの事例」を紹介しました。PoCを通じて、機能や性能の違いだけでなく、観測データの扱い方や運用文化の違いも、移行における重要な検討要素であることが明確になりました。 今回のPoCで得られた主な知見は以下の通りです。 機能面では、多くの要件を移行先でも再現可能であり、実装による対応範囲の広さを確認できた。 一部の機能は、OSSへのコントリビュートや独自ライブラリの追加によって補完可能であることが分かった。 SaaSやOSSなど、提供方式やデータモデルの違いによって実現方法が変わるため、機能単位ではなく構成単位で検討する必要がある。 運用面では、チームの慣れやトラブルシュートの流れ、可視化の思想が変わることで、ドキュメント整備や教育コストが発生する。 これらの変化は単なるツールの使い勝手の違いではなく、組織の運用プロセスや役割分担にも影響するため、早期に検証環境を用意し、現場の意見を反映しながら調整を進めることが重要。 IaC化の検証では、コード管理の方式やツール連携の違いが運用負荷に直結することが分かり、Terraformの取り込み方は各社の製品方針に左右されることを確認した。 これらの結果から、オブザーバビリティ製品の移行は技術的には十分に実現可能であると判断しました。 一方で、「どのように運用に組み込むか」「どのようにチームに浸透させるか」という非機能面での設計と準備が重要であることが分かりました。 移行を進める際は、プロダクトやインフラ単位での影響評価に加え、SREや開発チームの運用習熟度を考慮した段階的な移行計画が求められます。 また、PoCを通じて改めて感じたのは、オブザーバビリティは単にデータを「見る」ための仕組みではなく、障害検知・改善・意思決定の精度を高める「文化や習慣」として根付かせることが大切だという点です。ツールの差はそれを支える手段の1つにすぎず、自社の運用スタイルや目指す可観測性のレベルに合った形を選ぶことが最も重要だと感じました。 最終的に、今回のPoCからは次のような結論を得ました。 移行は技術的に可能。ただし、運用・教育・IaC化などの非機能領域にかかるコストは小さくない。 検証を通じて得られた知見は、今後の製品評価や設計方針の議論にも活用できる。 移行はゴールではなく、組織としてオブザーバビリティをどう進化させていくかを考える起点。 本PoCが、同様の課題を検討している方々にとって、計画立案や検証設計の参考になれば幸いです。 ZOZOでは、システムの信頼性や可観測性を高めながら、より良い開発体験を追求できるエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co
アバター
はじめに こんにちは。ZOZOTOWN開発本部の齋藤( @Jin_Pro_01 )です。 2025年8月26日にちょっと株式会社さんと共同で「ちょっと株式会社×株式会社ZOZO フロントエンド合同勉強会」をクローズドイベントとしてオンラインで開催しました。両社におけるフロントエンドを用いたプロダクト開発における具体的な事例を共有し、課題解決の糸口を見つけるきっかけや両社のエンジニアの知見を深めることを目的として開催しました。当日は両社合わせて50名以上の方に参加いただき、大変貴重な機会となりました。 この記事ではまず当日の勉強会の内容について紹介し、その後に運営する中での工夫や得た学びについて共有します。 登壇内容まとめ 本勉強会のファシリテーションはちょっと株式会社さんの西村さんにご担当いただき、両社から次の4名が登壇しました。 発表タイトル 登壇者 最近のNext.jsのことはなにもわからないのでClaude Codeに作らせてみた ちょっと株式会社 こばしゅん v0 を活用したプロトタイピングづくり ちょっと株式会社 後藤 滉希 WEAR Webリプレイスの進め方 株式会社ZOZO 岩崎 拳志 ZOZOTOWNフロントエンドにおけるディレクトリの分割戦略 株式会社ZOZO 菊地 宏之 最近のNext.jsのことはなにもわからないのでClaude Codeに作らせてみた 最新のNext.jsについてClaude Codeを活用してキャッチアップする試みについてお話しいただきました。AIを開発にどう活かすか、その具体的なアプローチが共有されました。 v0 を活用したプロトタイピングづくり v0を活用した効率的なプロトタイピング手法について発表いただきました。開発の初期段階でいかにスピーディーにUIを構築するか、その実践的なテクニックが紹介されました。 WEAR Webリプレイスの進め方 speakerdeck.com ファッションコーディネートアプリ「WEAR by ZOZO」のWeb版リプレイスプロジェクトについて、そのアーキテクチャや技術選定、プロジェクトの進め方など、大規模リプレイスの裏側をお話ししました。 ZOZOTOWNフロントエンドにおけるディレクトリの分割戦略 speakerdeck.com 大規模サービスであるZOZOTOWNにおいて、コードの保守性や開発効率をいかに高めるかという観点から、フロントエンド開発のディレクトリ分割戦略について共有しました。 こちらの内容については以下でテックブログとしても公開しておりますのでぜひご覧ください。 techblog.zozo.com 各社の発表後、弊社の技術顧問である古川陽介さんよりそれぞれの発表に対してご講評をいただきました。技術顧問という立場ならではのフィードバックや議論を深める質問をいただき、大変有意義な時間となりました。 クロストーク 勉強会の後半では登壇者と参加者を交えたクロストークとして感想共有の時間を設けました。 「両社のAI導入の進み具合」「ZOZOTOWNで未だ動き続けるVBScript」「プロトタイプを作るAIをどのように業務で活用していくか」など発表内容に関する質疑応答や両社が抱える共通の課題についての意見交換などが行われました。 勉強会運営の工夫と学び 今回このような勉強会を実施し、ZOZOという組織の中で社内外に向けてアウトプットする機会を創出できたことは私自身にとって大きな成果でした。 ここからは、この勉強会を開催する中で得た気付きや工夫について紹介します。 開催形式を工夫することで、開催および参加ハードルを下げる 今回の勉強会は、「両社のみのクローズドな勉強会」「オンライン開催」「ランチタイムの13:00〜15:00」「業務都合に合わせた任意参加」という形を取りました。企画当初は「オフラインで開催するなら会場の選定」「開催時期」「外部からの参加者の有無」など様々なアイデアが浮かんでいました。しかし、それらは当然準備や調整に事実上多くの労力と時間を要します。まずは運営側・参加側の双方に負担の少ない形で開催したことが成功の大きな要因だったと感じています。 それに合わせ、両社からより多くのエンジニアに参加してもらいたいと考え参加ハードルの低い雰囲気を目指しました。参加を強制しない分、人が集まりづらいことや主体的に参加してもらうことが難しくなるデメリットはありましたが、コストをかけず勉強会を開催できるというところにそれ以上のメリットを感じました。 スムーズな運営を可能にした連携体制の重要さ 開催形式を工夫したといえ、合同勉強会は両者間の調整など準備が多いことに変わりはありません。しかし、ちょっと株式会社さん側のコーディネーターである茨木さんの存在が非常に大きく、コミュニケーションからタスク管理まで円滑に準備を進められました。この場を借りて改めて感謝申し上げます。 ZOZO側では私が窓口および、登壇者への声掛けや社内でのアナウンスをまとめて実施しました。その中ではZOZOのDeveloper Engagementブロックや、全社横断のフロントエンドワーキンググループである「FEST」にもご協力いただきました。FESTは社内のフロントエンドエンジニアの連携強化、技術力向上といった目的とした組織で、今回は社内への告知や参加者の募集および当日の運営サポートなどで力を貸していただきました。 このような勉強会の運営を円滑に進められる体制が整っていたことも、今回の勉強会成功における大きな要因となったと感じています。 勉強会のコンテンツを形づくる登壇者、ゲストへの声かけ より参加したいと思える勉強会にするため登壇者を選定する上で意識したのは、プロダクトやエンジニア歴などが異なるバックグラウンドを持つメンバーへ登壇を依頼することでした。 WEARの開発に携わるジュニア枠として岩崎、ZOZOTOWNの開発に携わるシニア枠として菊地に登壇を依頼しました。さらに弊社の技術顧問である古川陽介さんにも当日の講評やクロストークへの参加を快諾いただき、会全体の質の担保と多角的な議論が期待できる強力な布陣を整えられました。 おわりに 勉強会の開催後にアンケートを実施し、参加者から勉強会全体と各登壇内容についての感想を集め、登壇者へフィードバックとしてお届けしました。「他社の事例が聞けて刺激になった」「明日から使える知見があった」などのコメントが寄せられました。 このように合同勉強会という形で社外の目線を意識したことが資料の質を高め、その結果として社外への素晴らしいアウトプットの機会、さらには登壇者へのフィードバックを届ける機会に繋がったと感じています。 ちょっと株式会社さんとZOZOから登壇いただいた4名、およびご参加いただいた皆様のおかげで自社内だけでは得られない情報や知見を共有できる貴重な機会となりました。誠にありがとうございました。今後もZOZOを盛り上げるべく、こういったイベントを企画・開催していきたいと思います! なお、今回の勉強会はちょっと株式会社さんで企画している「フロントエンド技術に関する合同社内勉強会」の一環として、ちょっと株式会社CEOの小島さんにお声がけいただき実現しています。詳細については以下をご参照ください。 note.com ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
アバター
.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、情報セキュリティ部SOCブロックの大山です。普段はSOC業務、いわゆる「守る側」の業務を担当しています。攻撃者視点の理解をより深めることで、検知の質や対応手順の説得力を高めることを目的に、OffSec社が提供する「OSCP+」および「OSCP」を受講し、それぞれ資格試験に合格しました。 本記事では、同じブロックの 兵藤 と共同で受講から合格までの流れや学び、実務への還元ポイントをまとめて紹介します。 目次 はじめに 目次 背景・課題 OSCPについて 試験形式(OSCP+) OSCPとOSCP+の関係 学習リソース 試験までの準備 学習リソースと時間配分 試験 1回目(不合格) 2回目 結果発表 学習・演習・実技試験から得た知見のSOCへの還元 各メンバーのトリアージ精度の向上(SIEM/EDR観点) 検知ルールの拡張・更新 ハードニング(設定強化)の取り組み まとめ 背景・課題 SOCの業務では、日々の監視・対応・改善を着実に回すことが求められます。一方で、運用の最適化だけでは「攻撃者が次に何を狙うか」を先回りして捉えるのが難しい場面もあります。防御観点だけで事象を見ると、プロダクトやログの仕様理解に寄ってしまい、 攻撃の目的や一連の手口を物語として結び直す力(攻撃者目線) の面で弱くなりがちなことが課題として存在しました。このギャップを埋めるために、攻撃側の基本動作を自分の手で実践し、検知・対応・ハードニングへと還元することを目的に、SOCブロック内の数名がOSCPおよびOSCP+を受講することになりました。なお、今回の受講費用および受験費用は会社(ZOZO)が負担してくれました。この場で感謝申し上げたいと思います、ありがとうございます! OSCPについて OSCP(OffSec Certified Professional) は、OffSecが提供する実技重視のペネトレーションテスト認定です。列挙・初期侵入・権限昇格・横展開・証跡化を“手順と再現性”まで含めて証明することを求められます。この一連の基礎スキルを体系的に学ぶための学習リソースとして、準備コースであるPEN-200があります。 試験形式(OSCP+) 受験環境: 受験者専用VPN内の小規模ネットワーク。 制限時間: 実技試験が23時間45分。終了後、24時間以内にレポート提出。 構成と得点: スタンドアローン3台(各20点=初期侵入10/昇格10)Active Directory(AD)セット40点。 合格点は70点: 部分点や複数の合格シナリオが定義されている。 レポート要件: 再現性を重視し、攻撃手順・コマンド・証跡を明確に記述する必要がある。 ツールの使用は制限: 大量自動スキャンやAIチャットボットの利用は禁止。Metasploit/Meterpreterは1台のみで使用可能といった詳細ルールが設けられている。 OSCPとOSCP+の関係 受験に合格するとOSCPとOSCP+の2つが付与される。 OSCPは失効しない生涯認定、OSCP+は発行から3年で更新が必要という違いがある。 OSCP+の維持は、 3年ごとの更新(再認定試験/他のOffSec上位資格/CPEの取得など) で行う。 学習リソース コースの契約期間中は以下の学習リソースへアクセスできます。 PEN-200(Penetration Testing with Kali Linux): 列挙・エクスプロイト・証跡収集・ADやクラウド(AWS含む)の基礎までを網羅し、OSCP+受験準備に最適化されたテキスト及び動画コンテンツ。 Challenge Labs: 実際の試験を模した複数のVMからなる小規模ネットワーク環境。 試験までの準備 「攻撃プロセスを自分の手で回し、再現可能なメモ(チートシート)を残す」 ことを主眼に学習を進めました。業務との両立を前提に、平日は短時間でも手を動かし、週末にまとまった演習とノート整備をしました。 学習リソースと時間配分 PEN-200(OSCP+の公式教材):約1.5か月: 教材の完走を最優先に、テキストのインプットと章末演習を実施。 Challenge Labs(チャレンジラボ):約3か月: いくつかあるラボ環境を順に攻略していく反復演習で列挙→初期侵入→権限昇格→横展開の“型”を定着させました。 PEN-200以外の学習リソース:約3か月: 最初の試験は不合格となったため、2回目の試験までにPEN-200を補う内容を書籍や他のオンライン学習リソースで学習。 SOCブロック内での勉強会:月1回~: 期間中の学習の大半は業務時間外で実施、SOCブロック内で月1回、業務時間内の勉強会を開催し進捗共有・学びの社内還元。 試験 1回目(不合格) 朝9時に試験をスタートし、ADセットに取り組みました。開始から1時間ほどでADセットの1台目に侵入しましたが、その後の権限昇格の糸口が全くつかめず、その後9時間ほど飛ばしてしまいました。気分転換を兼ね、スタンドアローンAに手を出したところ2時間ほどで権限昇格まで完了しました。 スタンドアローンAを攻略した勢いのままスタンドアローンBに取り組みました。こちらは少し難しいくらいの難易度で、3時間ほどで攻略が完了しました。スタンドアローンCは侵入までは容易でしたが、権限昇格の糸口がなかなか見つからず、スタンドアローンで合格点を狙いだしていたためかなり絶望しました。この段階で夜中の3時頃でした。 諦めムードでADセットに再度挑みましたが、迷走に迷走を重ね試験時間が終了しました。 結構、落ち込みました。 2回目 1回目の試験から3か月ほど空き、再試験に挑みました。朝9時に試験をスタートし、ADセットに取り組みました。列挙の段階で前回とは違う問題が出されていることに気づきました。最初に提供された認証情報でADセットの1台目にアクセスし、30分程度で権限昇格に成功しました。その後ADセット全体に対する列挙をすると、勝ち筋が見えたので意気揚々と進めていましたがある初歩的なミスにより、3時間ほど無駄にしました。ミスに気づいたあとはスムーズにADセット2台目の侵入まで完了しました。 ADセット2台目の権限昇格では、いつも使っている手段が通じなかったことにより5時間ほど迷走しました。気分転換でスタンドアローンに取り組んだところ、スタンドアローンA, Bと連続してスムーズに攻略できました。スタンドアローンCは列挙段階で時間がかかると踏み、ADセットへ戻ることにしました。 5時間迷走した内容は戻ってきてすぐに解決し、その後1時間ほどでAD全体の侵害が完了しました。この段階で夜の0時だったのでスクリーンショットの確認やレポートに記載する素材の抜け漏れがないかを確認し、朝の6時頃に就寝しました。 13時頃に起床し、12時間ほどレポートを執筆して無事に提出できました。 結果発表 レポート提出から10日後、登録したメールアドレス宛に合格通知メールがとどきました。以降、Offsecのポータルから認定証書等がダウンロードできるようになっていました。 学習・演習・実技試験から得た知見のSOCへの還元 情報セキュリティ部SOCブロックの兵藤です。ここからは自分も記載していこうと思います。自分がOSCPを受けた時は今から約1年前でまだOSCP+になる前の試験でした。 取得後、OSCP学習で培った“攻撃者目線の思考プロセス”をSOC業務へどう還元したか、大山さんと共に具体的な内容を避けつつ(会社のセキュリティに関わる内容のため)解説していきます。 各メンバーのトリアージ精度の向上(SIEM/EDR観点) “攻撃ストーリー”で見る癖が定着し、普段のアラートを単発ではなく、攻撃ライフサイクル上の「列挙相当か/初期侵入の試行か/権限操作の兆しなのか/etc.」をまず判定する共通フレームができました。ZOZOの環境では様々と便利なツールを導入していますが、最終的には人の目で監視・分析するため、各メンバーの共通認識と言うのは非常に重要であると感じます。 また、脆弱性情報のトリアージも同様でした。CVSSなどの数値的な評価だけでなく、社内環境を踏まえた「これってウチで刺すとどこまで侵害されそうか」を感覚的にイメージしやすくなりました。怪しいものを自分たちで試すことがある程度できるようになったことも大きいですね。 検知ルールの拡張・更新 主に、いわゆる振る舞い検知に関する詳細なルールを追加しました。また、検証によりSIEMにデフォルトで存在するルールの動作・精度(よくあるものはちゃんと検知する)も確認できました。 ハードニング(設定強化)の取り組み 実際に悪用できるかの観点があわさったことにより、より説得力のある強化提案が可能になりました。このことを踏まえ、以下の方針にまつわる設定強化が行われました。 最小権限と役割分離 横展開耐性の強化 まとめ 本記事ではOSCP+に挑戦し、最終的に合格に至るまでの道順と、その学びをSOC業務へどう還元したかを解説しました。セキュリティについて学んでみたいが何をしていいかわからない方やこれからOSCPを受験してみたい方、SOCって何しているんだろうと疑問に思っている方等、ぜひ参考にしてみてください。今後はさらなる上位資格であるOSEPやOSED等、日々の業務を拡張できるよう、知識を蓄えていければなと考えています。 ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
.entry .entry-content ul > li > ul { display: none; } .entry-content td { text-align: left; } はじめに こんにちは、ECプラットフォーム部マイクロサービス戦略ブロックの半澤です。2025年11月15日(土)に「 JJUG CCC 2025 Fall 」が開催されました。ZOZOはブーススポンサーとして協賛し、ブースを出展しました。 JJUGは、日本におけるJava技術の向上・発展と一層の普及・活性化を目指して設立された、ボランティアメンバーで運営される日本のJavaユーザーコミュニティです。CCCは、JJUGが主催する「Javaに閉じず」「技術に閉じず」オープンソースを中心とする様々なコミュニティから参加者を募って、コミュニティを横断した「クロスコミュニティなカンファレンス」です。 ZOZOTOWNでは、かつてVBScriptによって構築されていたシステムから、段階的にJavaへの移行を進めています。現在は、カート決済基盤、ショップ直送基盤、検索基盤、基幹システムなど、多くの領域でJavaを使用しています。私たちが日々使っている技術を支えてくれているコミュニティへ貢献したいという思いから、今回はじめて協賛することになりました! 本記事では「Javaエンジニアの視点」でZOZOから登壇したセッションと気になったセッションの紹介、協賛ブースの様子や後日開催したアフターイベントについて、まとめてお伝えします。 JJUG CCC 2025 Fallのパネル はじめに 登壇内容の紹介 ZOZOTOWNカート決済リプレイス ── モジュラモノリスという過渡期戦略 半澤のコメント Axon Frameworkのイベントストアを独自拡張した話 宮澤のコメント Javaエンジニアが気になったセッションの紹介 Javaコミュニティへの参加から貢献まで、すべて教えます TestcontainersのReusable Containersを使った、リポジトリテストの高速化 AI駆動開発の最前線:GitHub Copilot Agent ModeとCoding Agentで実現する高速マイクロサービス開発 ZOZOブースの紹介 技術パネル JJUG CCCの楽しい思い出を残そう! 企画 「Javaの好きなところ」集計結果 1位:型・コンパイルなどの言語仕様系(16件) この場を見ていた商品基盤部の井草からのコメント 2位:エコシステム・ツール・ライブラリ(11件) 3位:歴史・スタンダード性(8件) 4位:使っていて楽しい・愛着・個人的なつながり(7件) 5位:JVM・プラットフォーム特性(6件) 同率6位:複数のカテゴリが4件ずつ コミュニティ・人・文化 後方互換性 進化 9位:マスコット愛(2件) アフターイベント おわりに 登壇内容の紹介 今年のJJUG CCC 2025 Fallではセッションに3名が採択され、スポンサー特典LTに1名が登壇しました。採択されたセッションのうち、ZOZOでの事例を元にした2つのセッションを紹介します。 ZOZOTOWNカート決済リプレイス ── モジュラモノリスという過渡期戦略 ECプラットフォーム部マイクロサービス戦略ブロックの半澤は『ZOZOTOWNカート決済リプレイス ── モジュラモノリスという過渡期戦略』というタイトルで登壇しました。 半澤のコメント ZOZOTOWNのカート決済システムは「カートに入れる」から「決済や注文作成」「注文履歴」まで、ECの心臓部とも言える重要な機能を担っています。絶対に止められないシステムでありながら、リプレイスの必要性は高まる一方。しかも与えられた期間は3年間という制約付きです。 20年以上かけて蓄積された11万行のコード、複雑に絡み合ったビジネスロジック、分散トランザクションで一貫性を保っているデータ。これを全社方針であるマイクロサービスに移行するには、どうすればいいのか? 悩みに悩んだ末、私たちが選んだのは「モジュラモノリス」という過渡期戦略でした。 このセッションでは、この決断に至るまでの意思決定プロセスや、モジュラモノリスの実装概要、そして実際に運用して分かったメリット・デメリットについてお話ししました。 当日はなんと満員御礼! 想像以上に大勢の方にお集まりいただき、驚きと同時に大変嬉しく思っております。協賛ブースや懇親会で質問や感想を伝えに来てくださった方もいて、モジュラモノリスへの関心の高さを改めて実感しました。少しでも皆様の参考になっていれば幸いです! speakerdeck.com ボランティアカメラマン成瀬さんに撮っていただいた半澤の写真 Axon Frameworkのイベントストアを独自拡張した話 ZOZOMO部OMOブロックの宮澤は、『Axon Frameworkのイベントストアを独自拡張した話』というタイトルで登壇しました。 宮澤のコメント Axon FrameworkはJavaにおけるCQRS+ESの主要フレームワークの1つです。本セッションでは、CQRS+ESの基本知識に加え、フレームワークのイベントストアを独自拡張し、Amazon DynamoDBをイベントストアとして用いる方法を解説しました。 フレームワークを利用するメリットは認知負荷を抑え、コード品質を保つことができる点にあります。一方で、フレームワークが標準提供するサービス選定では、運用体制やシステム要件に適合しない場合があり、制約が生じることもあります。 本セッションでは、独自拡張によってこうした制約を解消し、フレームワークの恩恵を活かしながら、実際のシステム要件や運用体制に沿ったサービス選定を実現した事例をご紹介しました。 専門的でやや尖った内容ではありましたが、多くの方にご参加いただき大変嬉しく思います。少しでも皆様の参考になれば幸いです! speakerdeck.com ボランティアカメラマン成瀬さんに撮っていただいた宮澤の写真 Javaエンジニアが気になったセッションの紹介 ZOZOのJavaエンジニアが気になったセッションをいくつか紹介します。 Javaコミュニティへの参加から貢献まで、すべて教えます 商品基盤部の商品基盤1ブロックの井草です。杉山貴章( @zinbe )さんと田端大志( @tbtdis )さんの『Javaコミュニティへの参加から貢献まで、すべて教えます』を紹介します。 JJUG幹事の杉山さんによるセッションは、ユーモアあふれる“杉山節”全開で、会場が笑いと温かさに包まれました。 冒頭では元Sun Microsystemsのジョン・ゲージ氏の言葉「Don't be shy(恥ずかしがらずに)」を引用。杉山さんが何度か発言されており、Javaコミュニティ活動の根底にある精神を感じさせます。このセッションは杉山さんの魔法がかかっていて、セッション終了後は、自分はJJUGのメンバーであり、同じJJUGメンバーの顔見知りの知り合いが一人増えている状態になります。 続いて、JavaコミュニティとJJUG(Japan Java User Group)の概要紹介へ。 2025年春のJJUG CCCには約700名が来場し、日本最大規模のJavaイベントとしての存在感を見せました。 さらに今年、新たに「CCC協会」という非営利法人を設立。これにより、運営体制が強化され、コミュニティ活動の継続的な支援が可能になったとのことです。JJUGが次のフェーズへ進んでいる印象を受けました。 speakerdeck.com 続いて登壇した富士通の田端さんは、自身のキャリアとコミュニティ活動の関わりについて熱意をもって語りました。 印象的だったのは、「海外カンファレンスへの参加で人生が変わった」という話です。初めてJEPに参加し、サンフランシスコでカニを食べたその日から日本のJava界隈の有名人と顔見知りになったというエピソードも印象的でした。「自ら飛び込めば、その先には思いがけない出会いやチャンスがある」とのメッセージが心に響きました。 今回の登壇も、ベルギーの「Devoxx」で杉山さんと行動を共にした際に、その場で登壇を勧められたことがきっかけだったそうです。まさに「飛び込んだ先で次の扉が開く」ことを体現したエピソードでした。 両登壇者に共通していたのは、「行動すること」「つながること」の重要性です。Javaという技術を軸に、人とのつながりを通じて成長とキャリアの広がりを得ることの大切さを感じました。 speakerdeck.com TestcontainersのReusable Containersを使った、リポジトリテストの高速化 ZOZOMO部OMOブロックの木目沢です。Rentaro Moriさんの『TestcontainersのReusable Containersを使った、リポジトリテストの高速化』について紹介します。 UnitTestでリポジトリのテスト、特にRDBを扱うような副作用のあるテストをどこまで行うべきかは、どのチームも迷うところではないでしょうか? テストする場合にネックとなるのは、テストデータの問題とかかる時間の問題だと思います。セッションのテーマでもあったTestcontainersのReusable Containersを利用することはその解決策の1つとして検討してもよいのかなと感じました。 この機能はいわゆるテスト実行時にDBコンテナを立ち上げたままにしておいて、コンテナの起動や停止の処理時間を削減するというものです。確かに立ち上げたままにして、DBのDDLとデータの調整だけできれば実際のDBを用いたテストは可能だと感じました。 AI駆動開発の最前線:GitHub Copilot Agent ModeとCoding Agentで実現する高速マイクロサービス開発 ZOZOTOWN開発本部 バックエンドリプレイスブロックの平林です。てらだよしお( @yoshioterada )さんの『AI駆動開発の最前線:GitHub Copilot Agent ModeとCoding Agentで実現する高速マイクロサービス開発』について紹介します。 AI Agentを使った開発の限界まで挑戦した話と、その際に気づいた点などをお話しいただきました。まさにAI駆動開発の最前線という感じで、生成AIをつかった開発速度の上限を目指した話が印象的でした。 5段階に分けてSpecを定義する、毎回Agent側にセルフレビューを強制するなど、実際に高速で開発を回したことによる、生きた知見を共有いただき大変参考になりました。 ZOZOブースの紹介 協賛ブースでは、Javaエンジニアを中心として多数のZOZOスタッフが入れ替わりながらブースに立っていました。 ZOZOの協賛ブース紹介 技術パネル JJUG CCC用に「Javaを使用しているZOZOTOWNのアーキテクチャ」パネルを作成し、展示しました。このパネルをきっかけにブースの前で足を止めてくださる方も多く、アーキテクチャの設計思想や登壇内容についてのご質問、意見交換などさせていただき、貴重な交流の機会となりました。 Javaを使用しているZOZOTOWNのアーキテクチャ アーキテクチャ図を見て話している姿 JJUG CCCの楽しい思い出を残そう! 企画 今回、ZOZOブースにて「JJUG CCCの楽しい思い出を残そう!」をテーマに、参加者のみなさんがJavaへの思いを形にできる企画を実施しました! 参加者の皆さんに「Javaの好きなところ」というお題で、ホワイトボードに自由にJavaへの想いを書いていただき、チェキ写真をその場でプレゼント。ZOZOが大切にしている価値観の1つ「愛」をテーマに、Javaへの愛を通じてエンジニア同士が共感できる場を作りたいという想いから企画しました。 チェキを撮影している姿 企画に参加してくださった方には、今回のために作成した特別な「ギターマンステッカー」もプレゼントしました。このステッカーはZOZOTOWNで表示されるローディングアニメーションをもとにデザインしており、角度を変えるとキャラクターが動いて見えるレンチキュラーステッカーになっています。 ギターマンレンチキュラーステッカー 「Javaの好きなところ」集計結果 集まった回答をカテゴリごとに分けて分析したところ、Javaコミュニティの愛が詰まった素敵な結果となりました。Javaコミュニティを牽引される方々からの回答も合わせて、多かった順にご紹介します! 1位:型・コンパイルなどの言語仕様系(16件) 圧倒的1位は、やはりJavaの言語仕様に関する回答でした! 「型安全♡」「静的型付け」といったシンプルな回答から 「コンパイル時に沢山怒ってくれる♡」という愛情あふれる表現まで 「メソッドチェーンで書けるところ。ストリーム処理♡」 新機能への期待も:「switch式」「レコード型」 そして伝統的な「public static void main()」への愛も! まーや( @maaya8585 )さん ズバリJavaらしさを端的に表している「型」というJJUG幹事のまーやさんからの回答。Javaの強い型システムは、コンパイル時に間違いを早期に検出してくれる安心感があり、特に大規模なチーム開発で威力を発揮します! 増田 亨( @masuda220 )さん 増田さんのホワイトボードアップ 増田さんからは、深いプログラミングモデルの話が聞けました。 まず「振るまいを持つenum」ですが、Javaのenumは単なる定数の集まりではなく、振る舞い(メソッド)を持てる点が特徴的です。状態遷移のロジックやビジネスルールをenumに閉じ込めることで、if文やswitch文が散らばらず、ドメイン駆動設計とも相性の良い設計が可能になります。 さらに「class only baseのプログラミングモデル」と「mutable huge objects → immutable small objects」は、Project Valhallaで目指されている方向性、特にValue Class(現在Early Access段階の機能)への言及です。 従来のJavaでは全てのクラスがidentityを持っていましたが、Value Classはidentityを持たないイミュータブルなクラスです。identityがないため、==演算子は参照の同一性ではなくフィールドの値の等価性を比較するようになります。 また、JVMはヒープ平坦化などの最適化によって小さな不変オブジェクトをより効率的に扱え、キャッシュミス低減などメモリ効率の向上が期待されています。 こうした最先端の話を楽しそうにスラスラと話す増田さんを見て、改めて尊敬の念を抱きました! この場を見ていた商品基盤部の井草からのコメント イベントの面白さは、想定していなかった対話が生まれるところにあります。JJUG CCC 2025 FallのZOZOブースでは、そんな“化学反応”が起きました。 増田亨さんがJavaをテーマに語る姿を、ZOZOのエンジニアたちが真剣に見つめていました。そこでは、ただ知識が伝わるのではなく、“思い”が共有されていました。 伝える人には、伝えたい熱がある。聞く人には、知りたいという好奇心がある。その交差点に立てるのが、イベントブースの醍醐味だと思います。 ZOZOブースで、日本のドメイン駆動設計の伝道師から、Javaへの「愛」を伝え聞いている写真 2位:エコシステム・ツール・ライブラリ(11件) 充実したエコシステムもJavaの大きな魅力のようです! 「Spring Bootのエコシステム」 「gradle」などのツール 「標準ライブラリが豊富」「資産がたくさんある」 「充実したリファレンス」 長年の蓄積による豊富なライブラリやツールが、開発を強力にサポートしてくれることへの感謝の声が多数寄せられました。 谷本 心( @cero_t )さん 今回はありませんでしたが、Javaチャンピオンの谷本さんがこれまでのJJUG CCCで行っていたアンカンファレンスは、いつも楽しみなコーナーの1つでした。 新人教育、資格の必要性、OR Mapper、AI活用など、Javaを取り巻く様々なテーマについて参加者と語り合う場で、エコシステム全体を見渡している谷本さんならではの企画だと感じていました。 エコシステムの重要性は、VBScriptからJavaへ移行を進めているZOZOにとっても、改めて実感している部分です。Javaを中心に多くの人が繋がって助け合うこの仕組みを、今後は支える側として、OSS貢献やコミュニティ活動を通じて盛り上げていきたいと考えています! 3位:歴史・スタンダード性(8件) Javaのスタンダード性も高く評価されています! 「Theスタンダード」 「みんなが使ってる!」「みんな使ったことある!」 「一番使っている、使われている」 「歴史があるところ」「歴史」 多くの人が使っているからこその安心感、そして情報の見つけやすさが魅力ですね。 4位:使っていて楽しい・愛着・個人的なつながり(7件) 技術的な側面だけでなく、感情的なつながりも大切にされています! 「仕事でも遊びでも使っている所」 「書いてて楽しい ビルドの実感 フィードバック」 そして何より心に響いたのが、Javaコミュニティを長年牽引されている方々からいただいたこちらの回答です。 てらだよしお( @yoshioterada )さん さくらば( @skrb )さん 杉山貴章( @zinbe )さん 運よく、ブースでJavaチャンピオンのお二人が回答を書かれるシーンを目撃しました。お二人とも「Javaの好きなところ? うーん…」と熟考されていたのが印象的でした。すぐに答えが出てくるものと思っていた分、その様子に少し驚かされました。 そして書かれたのが、「Java = 人生」という内容でした。 JJUG幹事の杉山さん含め御三方が長年にわたってJavaコミュニティを支え続けてこられたのは、Javaそのものへの深い愛があったからこそ。 「私の人生そのもの!!」「No Java, No Life」「Javaと共に生きてきたので」という言葉に、その想いが凝縮されていると感じました! 5位:JVM・プラットフォーム特性(6件) JVMの力も見逃せません! 「30億のデバイスで走るところ」 「JVMがあれば動く!」 「JVM 愛♡」 「Write Once, Run Anywhere」 同率6位:複数のカテゴリが4件ずつ コミュニティ・人・文化 「コミュニティがあたたかい♡」 「師匠が熱く語ってくれること!」 山本ユースケ( @yusuke )さん Javaデベロッパがいつでも集える場所として、Javaチャンピオンのユースケさんが2021年に開設されたDiscordサーバ「#Javaディスコ」には個人的に大変お世話になっています。毎晩入り浸り、技術書を読んだり、Javaの質問をさせてもらったりと、本当にありがたい場所です。 実際にコミュニティの場を作り、日々大切に育てているユースケさんならではの回答だと感じます! 後方互換性 「後方互換がちゃんとしてる所!!」 進化 「常に進化している」 「古いけど新しい♡」 「10年先にも必ずある」 「歴史があるのにどんどん進化していくところ」 個人的には、「10年先にも必ずある」という回答が特に刺さりました! 長年使われてきたVBScriptからJavaへリプレイスを進めているZOZOとしては、技術選定における長期的な安定性という観点で、非常に重要な点だと感じています。 9位:マスコット愛(2件) 「Dukeがかわいい!」 「デュークがかわいい」 マスコットキャラクターDukeも人気です! 今回の協賛のために、ZOZOもオリジナルステッカーを作成してブースで配布しました。 Duke&マックスくんステッカー その他にも「べつに嫌いじゃないぞ! null!!」など個性的な回答が寄せられました!こうした愛情たっぷりのコメントも、Javaコミュニティの温かさを物語っていますね。 今回のアンケートを通じて、型安全やエコシステムといった技術的な強みはもちろん、コミュニティの温かさや長年培われてきた信頼や「楽しい」「かわいい」といった感情的な繋がりまで、多角的にJavaが愛されていることが分かりました。 著名な方々も一般参加者も、分け隔てなく交流し、知識や想いを共有し合える。そんなフラットで温かいコミュニティの雰囲気こそが、JJUG CCCというイベントの素晴らしさだと感じました! ご回答いただいた皆さん、ありがとうございました! アフターイベント 今年はLINEヤフー、出前館、ZOZOの3社で After JJUG CCC 2025 Fall を開催しました! JJUG CCCの懇親会の延長のような和やかな雰囲気の中、各社から1名ずつShort Talkを行い、その後3名によるパネルディスカッションを実施しました。 ZOZOからのShort Talkは、ZOZOMO部OMOブロックの木目沢が『意外と難しいドメイン駆動設計の話』というタイトルで発表しました。 speakerdeck.com パネルディスカッションには、LINEヤフーのきしだなおきさん、出前館の神保宏和さん、ZOZOの宮澤碧の3名が参加し、「JJUG CCCの振り返り」や「JavaとAI」をテーマに各社での取り組みから個人的な取り組みまで語っていただきました。 少人数ならではの距離感で、参加者同士がじっくり交流できる楽しい時間となりました。 おわりに 今回のJJUG CCC 2025 Fallを通じて、開催前からアフターイベントまで、とても充実した時間を過ごすことができました。 ブースやセッション、アフターイベントで交流してくださった皆さん、本当にありがとうございました。これからも一緒に楽しみながらコミュニティを盛り上げていけたら嬉しいです。次のJJUG CCCでもぜひお会いしましょう! ZOZOメンバー集合写真 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.entry .entry-content ul > li > ul { display: none; } .entry-content td { text-align: left; } はじめに こんにちは、データ・AIシステム本部データシステム部推薦基盤ブロックの 関口 柊人 です。普段はZOZOTOWNのレコメンドシステムの開発やHOME面の改善などに取り組んでいます。 ECサービスにおいて「何を成功指標とするか」は、サービスの方向性を決める重要な要素です。ZOZOTOWNのHOME面も例外ではなく、従来は売上やCTRといった短期的な指標を中心に改善してきました。しかし複数チームでの運用が拡大し、レコメンドシステムの高度化が進む中で、推薦基盤チームとして「短期指標だけではユーザー体験を十分に捉えられていないのではないか」という課題感が徐々に強まりました。 短期的な売上最適化だけでは見落としてしまう価値があるのではないでしょうか。ユーザーの長期的な満足度やサービスへの愛着といった要素をどう測り、育てていくべきでしょうか。 本記事では、こうした課題意識から始まったZOZOTOWN HOME面のKPI再設計プロジェクトについて紹介します。その背景、取り組み、そして得られた成果をお伝えします。 ※本記事で紹介するKPI再設計は、ZOZOTOWNのHOME面に限定した新しい取り組みです。今後、運用状況やサービスの変化により内容が変更される場合もありますので、ご了承ください。 目次 はじめに 目次 背景・課題 HOME面の運用状況 従来のKPI設計の課題 運用上の課題 レコメンドにおける課題 HOME面で目指す姿 HOME面の役割の再定義 運用チームごとの目的の把握 HOME面の理想の状態 HOME面のミッション 新指標設計と運用戦略 設計方針 新しいKPI体系の設計 売上軸:短期的な成果を測定 認知・流入軸:中期的な関係構築を測定 エンゲージメント軸:長期的な関係性を測定 運用プロセスへの組み込み 従来の運用課題に対するアプローチ 定常モニタリング ABテスト評価指標への追加 新指標によるレコメンド施策の効果検証 商品パーソナライズ施策での効果検証を実施 A/Bテスト結果 結果の詳細分析と行動変容の考察 多様性の向上(商品多様性 6.96% UP) 再訪問間隔の短縮(6.13% UP) 再訪問率・アクティブユーザー率の変化から見える行動変容のステージ エンゲージメント深化の段階的プロセス仮説 今後の展望:HOME面運用とレコメンド施策の進化 HOME面運用の高度化 レコメンド施策の戦略的発展 まとめ 背景・課題 HOME面の運用状況 ZOZOTOWN HOME面は、ユーザーがアプリを起動した際、最初に表示されるページです。モジュールと呼ばれる単位で商品をグルーピングし、横スクロールで関連商品を閲覧できるカルーセル型UIを採用しています。 本記事で「HOME面」と表現している箇所は、主にこのモジュールでの商品表示や運用に関する内容を指しています。 現在、HOME面のモジュールは複数のチームで運用・管理されており、以下のようなチームが関わっています。 分類 チーム名 開発 推薦基盤 ビジネス 販促(アパレル・シューズ) ビジネス コスメ ビジネス ZOZOUSED ビジネス ZOZOVILLA(ラグジュアリー) ビジネス 広告 従来のKPI設計の課題 HOME面運用メンバーは、これまで 売上とCTR を中心にHOME面を評価し、月次定例で実績を共有してきました。しかし運用を続ける中で、推薦基盤チームとしては以下のような課題を感じるようになりました。 運用上の課題 短期的売上に直結しないがサービス貢献する要素の評価ができていない 特集記事やコーデ提案など、購買目的がない時でもユーザーが楽しめるコンテンツの価値を測れない そのため、そのような目的での新機能リリースは難しくなっている 月次定例が実績の共有に留まっている 中長期的に取り組むべき課題やアクションが検討されていない チームを横断した課題感やアクションの共有がない 運用チーム全体で「ZOZOTOWN HOME面をどのようにしていきたいか」の共通認識が持てていない レコメンドにおける課題 レコメンドシステムに「 フィルターバブル 」や「多様性の喪失」といった歪みが生じる レコメンドシステムの最適化が進むほど、表示される商品が特定嗜好に偏る傾向が生じ、「未知の商品との出会い」や「サービスへの愛着」といった長期的価値を創出しにくくなっていた エンゲージメント指標の欠如 推薦基盤チームの目標は「長期的なユーザーエンゲージメントの向上」だったが、売上やCTRといった短期指標ではユーザー体験の変化を評価できない状態だった HOME面で目指す姿 HOME面の役割の再定義 KPI再設計の第一歩は、「そもそもHOME面とは何を提供すべき場所なのか」という根本的な問いから始まりました。日々の運用に追われる中で、HOME面の本質的な役割や提供価値が曖昧になりつつあるという課題感があったからです。 そこで、推薦基盤チームが中心となり、サイト全体における「各ページの役割」や「導線・要素の意味」を再定義しました。運用に関わる全チームと議論を重ね、全員が納得する形で策定を進めました。 整理の結果、HOME面は以下の多面的な役割を担っています。 ZOZOTOWNを案内するページ とりあえず戻ってくるページ 商品の提案・情報提供のページ ざっくり欲しいものが決まっているユーザーへの訴求 ZOZOらしさの提供 これらを踏まえ、推薦基盤チームと運用チームで議論を重ねた末に、「モジュールを通じて、トレンドや季節感のあるアイテムやZOZOTOWNの推しが伝わる拠点」とHOME面を再定義しました。その結果、HOME面は単なる情報一覧ではなく、「発見」と「回遊」を生み出す場として明確に位置付けられました。 運用チームごとの目的の把握 次に、運用チームが増えている状況を踏まえ、各チームがHOME面をどのような目的で運用しているかヒアリングしました。 担当チーム HOME面での目的 販促(アパレル・シューズ) 売上の最大化 コスメ ZOZOCOSMEファンの増加・認知拡大 ZOZOUSED USED商品詳細面・一覧面への流入・認知拡大 ZOZOVILLA(ラグジュアリー) ZOZOVILLAと親和性の高いユーザーへ露出し、認知獲得。エンゲージの蓄積とのちの購入を目指す 広告 露出・流入向上、エンゲージメントの獲得 各チームのKPIや着目点は異なるものの 共通して「HOME面を通じた認知拡大や利用頻度の向上が、後の販促効果を高め、最終的な売上に寄与する」 という考えが根底にあることが分かりました。 HOME面の理想の状態 こうした現状認識を踏まえ、HOME面が本来目指すべき「理想の状態」を3つの価値軸で整理しました。現状では、HOME面の役割に対する認識と、評価指標との間に乖離が生じていました。その乖離を是正し、HOME面を「短期的な売上創出の場」から「ユーザー体験を起点に、長期的な成長と収益を生み出す場」へ再定義することを目指しました。 項目 目的 具体的な状態 売上向上 GMV最大化 ユーザーがスムーズに購入へと進む動線が整備され、納得感のある購買体験を実現 認知拡大 多様な商品との出会いを創出 リターゲティングに依存せず、ユーザーが新しいブランドやカテゴリと「偶然の出会い」を体験 エンゲージメント蓄積 愛されるZOZOTOWNの実現 ユーザーが「また見に来たい」と感じる継続的な関係性を構築 これまでの運用は売上指標を中心に進んできましたが、なぜ売上が生まれたのか、持続可能なのかを理解しない限り、長期的な成長は望めない可能性があります。そのため、「購入前の体験」や「認知・回遊を生み出す仕組み」こそ、今後のHOME面設計において重視すべき要素だと位置づけました。 HOME面のミッション HOME面を複数チームの共通資産として設計するには各チームの成功条件をそろえる必要があります。これまでに整理したHOME面の役割、各チームへのヒアリング結果、そして理想の状態を踏まえ、HOME面のミッションを以下のように設定しました。 新たな出会いを通じた「認知と流入」の起点 多様なブランド・ショップとユーザーの「偶然の出会い」を創出 過去の購買履歴に囚われない商品提案で、潜在的な興味・関心を掘り起こし ZOZOTOWNと顧客の関係性を深める「育成の場」 単発の購買ではなく、ユーザーのライフスタイルと共に成長する関係性を構築 「ZOZOTOWNがある生活」に価値を感じてもらえる体験を提供 この2つのミッションを果たすことで、 短期的な売上最大化から長期的な関係性構築を通じた持続的な成長 への戦略転換を目指しています。 新指標設計と運用戦略 設計方針 HOME面の理想の状態とミッションを実現するため、 「売上・認知・エンゲージメント」の3軸 によるKPI体系を設計しました。ここで定義した「認知」と「エンゲージメント」は、前段のミッションで掲げた「認知と流入」「育成の場」の考え方に対応しています。従来の売上・CTR中心の指標では捉えきれなかった価値を測定するため、各軸で複数の指標を組み合わせた包括的な評価体系を構築しています。 新しいKPI体系の設計 売上軸:短期的な成果を測定 以前から重視していた売上関連の指標を、より多角的に評価できるよう拡張しました。 指標選択の背景 :従来の「売上金額」のみでは、ユーザー行動の多様性を捉えきれませんでした。例えば、高単価商品1点の購入と低単価商品10点の購入は同じ売上でも、ユーザーの関与度や商品への接触範囲が大きく異なります。そこで金額・点数・単価の3つに分解することで、「どのような売上なのか」を理解できるようにしました。 KPI 設計意図 モジュール経由の受注金額 モジュールの全体的な売上貢献度を評価。以前からの基本指標として継続 モジュール経由の受注点数 売上金額だけでは見えない「購買機会の創出」を評価。低単価でも多くのユーザーに価値を提供できているかを測定 モジュール経由の受注商品の単価 売上の「質」を評価。高単価商品の販売促進の効果や、ユーザーの購買意欲の高さを測定 認知・流入軸:中期的な関係構築を測定 ユーザーの興味拡大や新しい発見を促進できているかを測定する指標群です。 指標選択の背景 :認知の成果を測るには「関心の入り口(CTR)」から「実際の行動(流入)」まで段階的に捉える必要がありました。また、ZOZOTOWNの強みである「多様なブランド・カテゴリとの出会い」を活かすため、ブランド軸とカテゴリ軸での流入を別々に測定しました。 KPI 設計意図 モジュールCTR 認知拡大の「前段階」を評価。ユーザーの関心を引くコンテンツを提供できているかの基礎指標 モジュール経由ブランド流入数 ブランド認知拡大の効果を直接測定。ZOZOTOWNの強みである「多様なブランドとの出会い」を定量化 モジュール経由カテゴリ流入数 カテゴリ横断の興味拡大を測定。ユーザーの購買領域の拡張効果を評価 エンゲージメント軸:長期的な関係性を測定 ユーザーとサービスの関係性の深化を測定する、今回新たに導入した指標群です。 指標選択の背景 :レコメンドシステムの研究で、多様性や再訪パターンが長期的なエンゲージメントを予測するサロゲート指標として有効であることが示されています 1 。この知見を参考に、「商品多様性」「再訪問率・再訪問間隔」「アクティブユーザー率」を選定しました。 KPI 設計意図 商品多様性 フィルターバブル回避効果を定量化。レコメンドシステムが特定の商品に偏らず、幅広い商品を提案できているかを評価 アクティブユーザー率 サービス定着度を測定。単なる訪問ではなく、実際の購買行動を伴う「質の高いエンゲージメント」を評価 HOME面の再訪問率 リピート利用の促進効果を測定 HOME面の再訪問間隔 ユーザーの「また見たい」という動機の強さを直接測定。短い間隔ほど高いエンゲージメントを示す 運用プロセスへの組み込み 新指標を効果的に活用するため、以下の運用プロセスに組み込みました。 従来の運用課題に対するアプローチ 新しいKPI体系により、従来の運用上の課題を以下のように解決しました。 短期的売上に直結しないサービス貢献要素の評価 : エンゲージメント軸の指標により、コーデ提案などのコンテンツが長期的な関係性構築に与える影響を定量化 結果として、売上には即座に結びつかないが将来的な価値を生み出すコンテンツの価値を可視化し、新機能開発の意思決定を支援 月次定例の質的向上 : 3軸のKPI体系により、各チームの異なる目的(売上拡大、認知向上、エンゲージメント獲得)を統一的に評価 単なる実績共有から、「HOME面の理想の状態に向けた具体的なアクション」を議論する場へと転換 チーム横断での課題共有と改善アクションの立案が活発化 定常モニタリング モニタリングダッシュボードに指標を追加 運用チームとの月次定例において: HOME面の理想の状態に向けて運用チーム全体でモニタリング 各チームにおける最新状況を把握 モジュールの実績を確認し、改善案を提案するとともに、具体的な改善アクションを実施 長期的に活用できる有益な知見を取りまとめ ABテスト評価指標への追加 評価指標にエンゲージメント指標を追加 ABテスト終了後に施策の深掘り分析を実施 分析結果を元に改善施策を検討・実施 新指標によるレコメンド施策の効果検証 商品パーソナライズ施策での効果検証を実施 新しい指標体系の有効性を検証するため、 ZOZOTOWN HOME面の商品パーソナライズ施策 で効果検証しました。この施策では、Two-TowerモデルとVertex AI Vector Searchを組み合わせ、新規性・多様性を意識したパーソナライズを実現しています。 施策内容 : ユーザー情報(年齢、性別、閲覧・購入履歴等)を元にモジュールのコンテンツ並び順をパーソナライズ化 対象 : HOME面に訪れたZOZOTOWN会員(非会員は対象外) 期間 : 約1か月 詳細な技術内容については、以前の記事「 ZOZOTOWNホーム画面におけるモジュールコンテンツのパーソナライズ施策 」をご覧ください。 A/Bテスト結果 売上・認知拡大KPIの結果 施策により 「商品クリック58%増・経由受注26%増」 という大幅な改善を達成し、GMVの大幅なupliftを実現しました。 エンゲージメントKPIの結果 新たに設計したエンゲージメント指標を確認したところ、以下の結果を得られました。 指標 改善率(T/C uplift値) 商品多様性 6.96% アクティブユーザー率 0.22% 再訪問率 0.01% 再訪問間隔 6.13% 結果の詳細分析と行動変容の考察 従来の売上・CTR中心の指標では見えなかった ユーザーの行動変容 を、新しいエンゲージメント指標によって詳細に分析しました。 多様性の向上(商品多様性 6.96% UP) 項目 内容 定義 商品多様性(coverage: 全商品のうち、どれだけの割合の商品がユーザーに表示されたか) 数値的な成果 パーソナライズが一部の人気商品に頼るのではなく、これまで埋もれがちだったニッチな商品や新しいブランド(ロングテール商品)まで、幅広くユーザーに届けられている 行動変容の考察 ユーザーあたりクリックした商品ユニーク数も6.30% UP 。単にシステムが多様な商品を提示しただけでなく、 ユーザーの探索行動そのものが活発化 している 従来の指標では「クリック数が増えた」という結果しか見えませんでした。しかし新指標によって、ユーザーがより幅広い商品に関心を示すようになった 行動パターンの変化 を捉えられました。これは 「システムが多様な商品を提案し、ユーザーもその多様な提案を歓迎して積極的に探索している」 状態です。このような状態が顧客の「飽き」を防ぎ、長期的なエンゲージメント(LTV)に寄与していると考えられます。 再訪問間隔の短縮(6.13% UP) 項目 内容 定義 テスト期間中のHOME面の再訪問時間 行動変容の考察 ユーザーの 利用習慣の変化 を直接的に示している。自分に最適化されたコンテンツが提示されることで、「またすぐに見たい」という 内発的な動機の変化 が生まれている 従来の売上指標では「購入に至ったかどうか」しか見えませんでしたが、この指標により 購入に至らない訪問でも価値のある体験を提供できているか を測れるようになりました。 再訪問率・アクティブユーザー率の変化から見える行動変容のステージ 再訪問率(0.01% UP) と アクティブユーザー率(0.22% UP) については、大きな変化は見られませんでした。しかし、この結果も重要な示唆を与えてくれます。 従来であれば「効果が限定的」という結論で終わってしまいがちです。しかし新しい指標体系により、 ユーザーの行動変容には段階がある ことを確認できました。訪問頻度の低いライトユーザーを新たに惹きつけてリピーターへと転換させるには時間がかかります。短期的な購入者数の押し上げには至らないものの、 行動変容の兆候は確実に現れている と解釈できます。 エンゲージメント深化の段階的プロセス仮説 今回の結果から、 エンゲージメントの深化は段階的なプロセスを辿る という新たな仮説を立てました。 このモデルに基づけば、今回の「再訪問間隔の短縮」という結果は、行動変容プロセスの第一段階におけるポジティブな兆候を捉えたものであり、 長期的な成功に向けた先行指標である可能性 が考えられます。 重要なのは 、従来の指標では見逃していた「ユーザーの内発的な動機の変化」や「探索行動の活発化」といった 行動変容を定量的に捉えられるようになった ことです。これにより、施策の効果をより深く理解し、長期的な改善戦略を立てることが可能になりました。 今後の展望:HOME面運用とレコメンド施策の進化 新しいKPI体系の導入により、HOME面の改善に向けた新たな可能性が見えてきました。今後は以下の2つの軸で取り組みを進めていく予定です。 HOME面運用の高度化 運用チーム間の連携強化 新指標を活用した月次定例の質的向上 チーム横断での改善アクションの立案・実施 データドリブンな意思決定の推進 エンゲージメント指標を含む包括的なダッシュボードの活用 短期・長期指標のバランスを考慮した施策評価 ユーザー行動変容の観点からの効果測定 レコメンド施策の戦略的発展 長期効果の検証 エンゲージメント指標を活用したHoldout Testの実施 長期的なLTV向上に与えるエンゲージメント指標の調査 新たな価値創造 「未知の商品との出会い」を促進する仕組みの強化 ユーザーの成長段階に応じたパーソナライズ 文脈や気分を捉えた高精度なレコメンドの実現 この取り組みを通じて、ZOZOTOWNのHOME面をより一層、 ユーザーと長期的な関係性を育む場 として進化させていくことを目指しています。 まとめ 本記事では、ZOZOTOWN HOME面におけるKPIの再設計について紹介しました。従来の短期的な売上指標が中心の評価から、「売上・認知・エンゲージメント」の3軸による包括的な指標体系への転換により、より持続的で戦略的なサービス改善を実現できました。 特に重要な成果として、以下の4点が挙げられます。 戦略基盤の構築 : HOME面の「理想の状態」と「ミッション」を定義し、改善活動における戦略的な方向性を明確化 評価フレームワークの策定 :「売上・認知・エンゲージメント」の3軸によるKPI設計 運用プロセスの具体化 : KPIを活用した定常モニタリングとABテスト評価によるPDCAサイクルの確立 データに基づくインサイトの獲得 : 商品パーソナライズ施策の効果検証と新指標による示唆の獲得 ECサービスにおける長期的な顧客価値の向上や、複数チームが関わるプロダクトのKPI設計を検討している方がいれば、ぜひ参考にしてみてください。 私たちは今回のKPI再設計を通じて、 数値の向上だけでなく、ユーザーの行動変容そのものを理解し、育てていく ことの重要性を実感しました。HOME面運用の高度化とレコメンド施策の戦略的発展により、ユーザーとブランドの長期的な関係性を育む場としてのZOZOTOWNを目指していきます。 本記事の内容について、より詳細な情報や図表については以下の登壇資料もご参照ください。 speakerdeck.com ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com Yuyan Wang et al. " Surrogate for Long-Term User Experience in Recommender Systems ." KDD '22. ↩
アバター
はじめに こんにちは、YSHP部SREブロックの濵砂です。普段は主にシステムリプレイスを担当しています。YSHP部では2025年から、ZOZOTOWN Yahoo!店に関わるシステムを段階的にGoで刷新しています。 2025年9月27日、28日に Go Conference 2025 が開催されました。本記事では、会場の様子や印象に残ったトークについてご紹介します。 Go Conference 2025 とは Go Conferenceは、Goエンジニアはもちろん、Goに興味を持つすべての人に学びと交流の場を提供する国内最大のGoのカンファレンスです。去年から久しぶりにオフラインイベントとして行われ、今年は定員450人のチケットが1週間も経たないうちに完売するほどの人気ぶりでした。 会場の様子 会場は東京都渋谷区にあるAbema Towersでした。 会場のAbema Towers Abema Towersの2フロア(10階と11階)が会場として開かれており、10階がセッション会場で、11階がスポンサーブースとなっていました。基本的には1、2セッションが同時に行われ、1日目のワークショップが開催されている時間帯には最大4部屋で同時にセッションやワークショップが行われていました。 たくさんのGopherくんが賑やかにお出迎え 11階のスポンサーブースの入り口横で、大きなメインボードいっぱいのGopherくんがお迎えしてくれました。 スポンサーブースの様子 スポンサーブースでは、参加者向けのアンケートや展示がたくさん行われていました。 株式会社Datachainさんのブース 株式会社Datachainさんのブースでは「あなたが次に欲しい新機能は?」をテーマに意見が募られていました。参加者からは「スタックトレースが欲しい」「Option型が欲しい」といった実践的な声が寄せられていました。一方で「シンプルなままでいてほしい」というGoらしい意見も多く、コミュニティの多様な価値観が垣間見えました。 株式会社ナレッジワークさんのブース 株式会社ナレッジワークさんのブースでは、掲示されたGoのコードを対象に参加者が自由にコードレビューをする企画が実施されていました。多くのGopherが足を止めてレビューに参加していました。再訪した際には、ボードが付箋で埋めつくされていました。模範レビューをまとめた冊子も配布されており、持ち帰ってからも学びを深められるブースでした。 さまざまな"界隈"から参加者が来ていました 10階のセッション会場前には「どこ界隈から来ましたか?」というテーマのホワイトボードが置かれており、Gopher同士の交流のきっかけが生まれる場所になっていました。 セッション どのトークも非常に興味深い内容でしたが、その中でも特に印象に残ったセッションを紹介します。 Generics speakerdeck.com このセッションの目的は、Goの型とinterfaceとGenericsの内部構造を理解して正しく使い分けることです。特に、多くのGoエンジニアが陥りがちな「Typed Nil」のような問題を避け、コンパイル時に型安全性を担保できるGenericsを適切に活用できるようになることを目指した内容となっています。 speakerdeck.com もう1つのセッションでは、Goにジェネリクスが導入されるまでの背景が紹介されていました。あわせて、設計の核心となった理論モデルであるFG(Featherweight Go)とそれにジェネリクスを追加したモデルのFGG(Featherweight Generic Go)についても説明がありました。具体的な内容は次のとおりです。 ジェネリクス設計の変遷 :初期の「新しい概念のcontractsを型制約に使う」案から、現在の「既存の概念のinterfaceを型制約に使う」設計に至るまでの経緯。 型安全性の確保 :ジェネリクス導入以前のGoの最小モデル(FG)では実行時パニックのリスクがありました。FGGではコンパイル時の型制約で、そのリスクを排除できる点。 コンパイル戦略の理解 :Goが採用したモノモーフィゼーションによって、ジェネリックなコードがどのように具体化され、どのような特性を持つのかという点。 ジェネリクスが導入されるに至る過程で、Goのシンプルさを損なわずに進化するための理論的裏付けが存在することを実感しました。内容は難解で、資料を何度も読み返しながら理解を深める必要がありましたが、言語設計の思想とそれを支える理論の繋がりに触れられる貴重なセッションでした。 synctest speakerdeck.com このセッションの主な目的は、Goの 非同期処理 を含むコードをテストする際に生じるテストの不安定さや実行時間の長期化を解消することです。 その解決策として、Go 1.25で正式リリースされた新しい標準ライブラリパッケージである testing/synctest を深掘りして紹介しています。具体的には、 synctest が提供する以下の主要な機能と概念を理解し、いかにして安定した高速な並行処理テストを実現するかを学ぶことを目指しています。 隔離性(Bubble) :テストを隔離された「Bubble」内で実行する仕組み。Bubbleとは testing/synctest パッケージ固有の概念で、テストコードを実行するための隔離された環境のこと。内部では仮想的な時間が進む。 仮想時間 :バブル内のゴルーチンがブロックされた時に時間が進む仕組み(これにより、長い time.Sleep を含むテストも瞬時に完了する)。 Durably Blocked :テストの決定性を保証するために、ゴルーチンが「Durably Blocked」と見なされる条件。ゴルーチンが待機状態にある際、そのブロックがバブルの外部イベントによっては解除されないことが保証された状態を指す。 API の使用法 : synctest.Test 関数と synctest.Wait 関数を適切に使用する方法。 非同期処理を対象にしたテストにおける課題や難所が豊富に示されており、共感や新たな気付きを得ながら聴講しました。私たちの部署でも、これからシステムリプレイスプロジェクトに伴って非同期処理のテストを書く機会があります。高い信頼性と実行速度を両立させたテストを実現するため、実際の利用例に基づきながらsynctestの動作原理を深く理解する上で、こちらのセッションはとても参考になりました。 ワークショップ Go Conference 2025の新しい取り組みの1つとして、今回はワークショップが実施されました。3つの時間帯それぞれで3種類のワークショップが実施されたため、合計9種のワークショップが開かれました。今回はその中から1つピックアップしてご紹介させていただきます。 今日から始めるpprof speakerdeck.com Goに標準で同梱されているプロファイリングツールpprofを初めて使う人や、これから使い始めたい人向けにワークショップが開催されました。pprofを実際に使ってパフォーマンス改善のサイクルを回せるようになることを目的とした内容でした。具体的には以下のようなことを学べました。 pprof の基本を理解し、プログラムに組み込む :プロファイルとは何かを理解し、CLIツールやウェブサーバーにpprofの機能(計装)を導入する方法を習得する。 プロファイルを取得・可視化する :実行中のプログラムからCPU使用量などのプロファイルデータを取得し、 go tool pprof を使ってそのデータをウェブUIでグラフとして表示し、確認できるようにする。 プロファイルの結果に基づいて改善する :可視化された結果(特にFlame GraphやTopのFlat/Cumの値)を正しく読み解き、ボトルネックとなっている箇所を特定する。そのうえで、具体的な改善案を導き出せるようになる。 改善効果を検証し、実務へ応用する :改善後のプロファイルを取得して元のバージョンと比較し、改善がどれほど効果的であったかを客観的に評価するスキルを身につける。また、テストでのプロファイル取得や本番環境での継続的プロファイルの概念まで理解を広げる。 まとめると、「pprofを使ってGoプログラムのパフォーマンスに関する課題を 計測 、 特定 、 改善 、 検証 する」という一連のスキル習得を目指すことがこのワークショップの目的です。 新人SREである私にとって、CNCFのオブザーバビリティ白書に継続的プロファイリングの重要性が言及されているという点も合わせて新たな知見でした。ログやメトリクスだけでは把握しきれないコードレベルのふるまいを可視化することが、今後のSREにとって重要であると認識できました。 まとめ 参加者、関係者の合計人数が500人を超えたそう 2025年のメインテーマは「sync.go」 Go Conference 2025のテーマは「sync.go」でした。去年のテーマ「一期一会」に続き、「繋がり」を重視したテーマが続いています。これは、直接顔を合わせて交流できるオフラインイベントならではの価値を改めて伝える、コミュニティイベントならではの素晴らしいテーマだと感じました。 新しいコミュニティの発足や、多くの学生らしき方々の参加も見受けられ、今後ますますのGoコミュニティの盛り上がりを予感しました。来年のGo Conference 2026でより多くのGopherと交流できることを今からとても楽しみにしています。 2026年も開催されるそうです! 最後に、数ヶ月にわたり企画段階から当日運営に至るまで、貴重な時間と情熱を注いでくださったスタッフと登壇者の皆様に、心より感謝を申し上げます。本当にありがとうございました! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 2025年11月1日(土)に東京コンファレンスセンター品川で開催された「 Kotlin Fest 2025 」に、Androidアプリ開発を担当しているメンバーが参加・登壇しました。本記事では、イベントの概要や得られた知見についてご紹介します。 Kotlin Fest とは Kotlin Festは、「Kotlinを愛でる」をテーマに掲げ、Kotlinに関する知見共有とKotlin開発者の交流を目的とした技術カンファレンスです。 Kotlin Fest 2025は2025年11月1日(土)に開催され、Kotlin言語やサーバーサイド、Android、AI等の多種多様なカテゴリについて、20以上のセッションが提供されていました。 登壇内容の紹介 AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド ZOZOTOWNのAndroidアプリ開発に携わっているにしみー( @nishimy432 )は『 AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド 』というタイトルで登壇しました。 本セッションでは、AIを活用してレガシーコードを「Kotlinらしい」モダンなコードに刷新するための実践的なアプローチと、そのための効果的なプロンプトについて説明しました。 speakerdeck.com にしみーのコメント 実際の開発の中でAIを活用してレガシーコードをKotlinらしく置き換えていくために意識したことや工夫したことについてお話ししました。KotlinFestへの登壇は初めての挑戦で緊張しましたが、発表の準備を進める中でKotlinの魅力を改めて認識でき、私自身のKotlinへの愛もより深まる貴重な機会となりました。 セッション紹介 ここからは現地参加したZOZOのAndroidアプリエンジニアが気になったセッションを紹介します。 Rewind & Replay: Kotlin 2.2 が変えるCoroutine デバッグ最前線 ZOZOTOWN開発1部Android1ブロックの愛川です。Masayuki Sudaさんによる『 Rewind & Replay: Kotlin 2.2 が変えるCoroutine デバッグ最前線 』を紹介します。このセッションは、Kotlin 2.2とIntelliJ IDEA 2025の連携により実現されたコルーチンデバッグの進化に焦点を当てたものでした。前半はローカル変数の消失やステップ実行の不安定さなど、開発者が抱えていた問題点の紹介やそれらの技術的背景について紹介されていました。後半の5大改善のパートでは、デバッグの安定性向上により、特定のコルーチン内でステップ実行を継続できるようになった点などの改善点が紹介されており、今後の開発体験を大きく変えるものだと感じました。 私自身、suspend関数の詳細な仕組みを理解していなかったのですが、各種の問題点と技術的背景から丁寧に説明していただいたことで納得感を持って理解できました。 speakerdeck.com 【招待セッション】Kotlinを支える技術:言語設計と縁の下の力持ち ZOZOTOWN開発2部Androidブロックのにしみーです。JetBrains所属のジュラノフ ヤンさんによる招待セッション『 Kotlinを支える技術:言語設計と縁の下の力持ち 』を紹介します。セッション前半では、Kotlinに新機能を追加する際の開発プロセスの裏側が紹介されました。後半では、ビルドツールをはじめとする周辺ツールに追加予定の新機能や今後のKotlinの方向性についてのお話があり、Kotlinの進化と将来性が感じられる内容でした。 特にKotlinの新機能に関するパートでは、言語仕様を変更することの重大さを感じられました。また、1つの機能が導入されるまでに社内で活発に検討されている過程を知れたのは、日々Kotlinを利用する開発者として非常に興味深く感じました。 Kotlin言語仕様書への招待 〜コードの「なぜ」を読み解く〜 ZOZOTOWN開発2部Androidブロックのにしみーです。Honda Yusukeさんによるセッション『 Kotlin言語仕様書への招待 〜コードの「なぜ」を読み解く〜 』を紹介します。このセッションでは、Kotlinの言語仕様書の紹介から仕様書の読み解き方の解説、さらに仕様書ベースでのいくつかのKotlin仕様の紹介まで、幅広く解説していただきました。 正直なところ、私にとって言語仕様書は全くの未知の世界で、EBNFベースの表記法もこのセッションで初めて知りました。最初は難しい記法だと感じましたが、とても丁寧に解説してくださったおかげで、読み解き方を理解できました。 特に、普段当たり前に使っている「オーバーロード解決」についてその内部仕様を言語仕様書のレベルで知れ、Kotlinへの理解がさらに深まったと感じています。 speakerdeck.com ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界 ZOZOTOWN開発2部 Androidブロックの山田です。shiita0903さんによる『 ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界 』を紹介します。 Android開発におけるUiStateの肥大化を懸念し、実践的な解決法を知りたくて聴講しました。 セッションではまず、関数型のLens(getter/setter のペア)という基本的な考え方を示し、getter/setterを自作してLensの仕組みを深く理解する流れで進みました。Lensを用いることで、ネストしたdata classに対するcopy()の連鎖を抽象化し、部分更新を簡潔に記述できることが示されました。一方で、Lens単体ではコレクションやsealedクラスの扱いに難があることも指摘され、それをArrow-ktで解決する事例や、コード生成やcopy { … }といった読みやすい記法まで、実践的に丁寧に紹介されていました。 Arrow-ktによって多くの課題は技術的に解決できますが、効果的に使いこなすにはある程度の習熟が必要だと感じました。とはいえ、関数オブジェクトとしてのgetter/setterのようなシンプルな要素からここまで実現できるLensの設計思想と、それを短く読みやすく表現できるKotlinの柔軟性には改めて感動しました。 speakerdeck.com さいごに 今回のKotlin Fest 2025への参加を通じて、Kotlinコミュニティの活発さと、技術の進化を感じられました。また、セッションだけでなく、ブースでの交流を通して様々なエンジニアの方々と意見交換をしたり、技術的なアドバイスをもらったりと非常に有意義な時間を過ごせました。このような貴重な場を作ってくださったスタッフおよび登壇者の皆様に、心より感謝申し上げます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.entry .entry-content ul > li > ul { display: none; } .entry-content td { text-align: left; } はじめに こんにちは、データ・AIシステム本部データシステム部データ基盤ブロックの栁澤( @i_125 )です。私はデータ基盤の安定化・効率化を目指しつつ、Analytics Engineerとしてデータ利活用領域にも踏み込み、データマート整備やLooker周辺の整備・サポートに取り組んでいます。本記事では、Lookerダッシュボードに生成AIによる要約機能を組み込んだ「Gemini in Looker」の活用事例をご紹介します。 目次 はじめに 目次 背景 Gemini in Lookerとは 概要 標準機能とLooker Extension アーキテクチャ全体像とデータフロー システム構成 セキュリティ上の重要な設計判断 実際のデータフロー(安全な設計) もしCloud Run側から呼び出していたら(危険な設計との比較) User AttributeとAccess Filterによる権限制御 機能面で工夫した3つのポイント 1. プロンプトの一部を外部ファイル化 2. 複数クエリをまとめて要約 3. 事前プロンプトの固定化 本番適用時の技術的課題と解決策 フィルタ状態の同期 課題:初期状態に依存した要約の不整合 解決策: 動的なフィルタ状態の取得 Looker ExtensionでのSecret Key利用 PDFダウンロード対応 実際の動作例 実装のポイント セキュリティ設計 プロンプト設計 要約処理の工夫 まとめ 背景 弊社ではZOZOTOWNに出店いただいているテナント企業様向けに、Lookerを用いた売上分析ダッシュボードを提供しています。社内ではLookerのUI上で直接ダッシュボードを閲覧する一方、テナント企業様には Looker Embed (Lookerのダッシュボードを外部アプリケーションに埋め込む機能)を通じて提供しています。このように、同じLookerインスタンスを社内外で共有しています。別のテナント企業様の売上情報が閲覧できてしまうと重大な問題となるため、厳格なアクセス制御が必須です。 このようなセキュアな環境でダッシュボードを運用し、データ利活用が進むにつれてダッシュボードの情報量も増えていきました。そんな中、ビジネス側から「ダッシュボード全体を要約して理解しやすくできないか」という提案がありました。これを受けて、生成AIを活用したダッシュボード要約機能を検討することになりました。 Gemini in Lookerとは 概要 ご存じの方も多いと思いますが、改めてGemini in Lookerについて説明します。Gemini in Lookerは、Looker上で利用可能な「生成AIによる支援機能群」の総称です。複数の機能が存在し、それぞれ異なる目的で利用できます。 機能名 提供形態 概要 Conversational Analytics 標準機能 チャットUIで自然言語の質問に応答し、グラフやテキストで分析結果を返す機能 LookML Assistance 標準機能 自然言語で LookML のコードやパラメータを生成・補助する機能 Visualization Assistant 標準機能 自然言語から可視化の JSON 設定を生成する機能 Dashboard Summarization Looker Extension ダッシュボードの各タイル(グラフ・表)を要約し、次のアクションを提案する機能 Explore Assistant Looker Extension 自然言語での会話形式でデータ探索やグラフ生成を支援する機能 Query Insights Looker Extension Explore 結果を要約する機能 ※2025年11月時点では、標準機能はプレビュー版として提供されています。最新のステータスは公式ドキュメントをご確認ください。 この辺りは日々目まぐるしく新たな機能追加や名称の変更があり得るため、最新情報は 公式ドキュメント を参照ください。今回はDashboard Summarization機能をZOZOTOWNテナントユーザー向けダッシュボードに埋め込む形で実装しました。 標準機能とLooker Extension 上記の機能は、提供形態によって大きく2つに分類されます。 標準機能 Lookerの管理画面から設定を有効化するだけで利用可能 開発作業は不要で、すぐに利用を開始できる カスタマイズの自由度は限定的 Looker Extension Looker Extension Frameworkを使って開発するカスタムアプリケーション OSSとしてGitHubで公開されており、自社の要件に合わせてカスタマイズ可能 導入には開発とデプロイの作業が必要 プロンプトの調整やUI変更など柔軟な対応が可能 今回はLooker ExtensionとしてOSSで公開されている dashboard-summarization をカスタマイズして実装しました。マルチテナント環境でのセキュリティ要件やビジネス要件に合わせて独自に拡張しています。 アーキテクチャ全体像とデータフロー システム構成 公式で提供されているアーキテクチャ図をベースに、ZOZOの環境に合わせた構成を実装しました。前提として、弊社ではLooker(core)を利用しています。 赤枠部分がデフォルトのOSS実装で提供されている部分から追加・変更した部分です。Cloud Load BalancingとCloud Armorを追加しました。Cloud RunへのアクセスはCloud Load Balancingを経由し、Cloud ArmorによってLookerインスタンスからのIPアドレスのみを許可しています。APIトークンによる認証で一定の安全性は確保されていますが、念のためIPアドレス制限も追加することで二重の防御を実現しています。 LookerインスタンスのIPアドレスについては 公式ドキュメント を参照してください。 セキュリティ上の重要な設計判断 公式で提供されているアーキテクチャ図 を見ると、Cloud RunがLooker API経由でBigQueryやData Sourcesへアクセスするように見えます。しかし実装を確認したところ、実際には異なる設計でした。フロントエンド(Looker Extension)がユーザー権限でLooker Query APIを呼び出し、取得したデータをCloud Run側へ送信する設計になっています。 マルチテナント環境では、この設計が重要なポイントです。今回参照されるダッシュボードは全テナント共通です。もしCloud Run側からLooker Query APIを呼び出す設計にすると、エンドユーザーから提供されたパラメータをそのまま利用する可能性があります。これによりパラメーター改ざん攻撃のリスクが生じ、他テナントのデータを閲覧できてしまう恐れがありました。 実際の実装では、権限制御およびクエリ実行はLooker内で完結しています。 実際のデータフロー(安全な設計) 上図は実際に採用した安全な設計のデータフローです。Looker Extensionがユーザー権限でLooker Query APIを呼び出すことで、アクセス制御が確実に適用されます。 もしCloud Run側から呼び出していたら(危険な設計との比較) 対照的に、もしCloud Run側からLooker Query APIを呼び出す設計にしていた場合、ユーザーパラメータがそのまま利用されます。これによりパラメーター改ざん攻撃リスクや他ショップデータ漏洩の危険性がありました。 User AttributeとAccess Filterによる権限制御 今回のダッシュボードで扱うデータは、テナント企業様が運営する ショップ (販売元)単位で管理されています。1つのテナント企業様が複数のショップを運営しているケースもあるため、アクセス制御もショップ単位で実装する必要があります。 この制御を実現するために、LookerのUser AttributeとAccess Filterを組み合わせています。各ユーザーのUser Attributeには閲覧可能なショップIDのリストが設定されており、このリストに基づいてアクセス可能なデータが自動的に制限されます。 具体的には以下の仕組みでセキュリティを担保しています。 上図のように、以下の3つのステップでアクセス制御が実現されています。 Looker Embedでダッシュボードを表示しているアプリケーション側で一意のユーザーIDをユーザー属性(User Attribute)として保持した形で、Lookerユーザーを発行 Lookerモデル上でaccess_filterを設定することで、ユーザーのUser Attributeに紐づくショップIDの条件をクエリ発行時に自動付与。例: WHERE shop_id IN (123, 456, 789) この条件はフロントエンドから悪意のあるリクエストを送っても変更できない 図中の黄色でハイライトされている「クエリ生成」と「SQL実行」の部分で、User Attributeに基づくアクセス制御が自動的に適用されます。このフローにより、クエリ実行はLooker内で完結しており、パラメーター改ざん攻撃等のリスクを回避できています。 機能面で工夫した3つのポイント 1. プロンプトの一部を外部ファイル化 デフォルトのプロンプトでは汎用的な要約しか生成されません。そこでZOZOTOWNのビジネスコンテキストを含むガイドラインファイル( guideline.txt )を作成しました。サーバー起動時に読み込んでプロンプトに含めることで、サイトの特徴を踏まえた現実的なNext Actionを提示できるようになりました。たとえばガイドラインファイルには、以下のような情報を含めています。 サイトの特性や傾向に関するデータ(ユーザー属性、購買傾向、サイト構成など) ビジネス上の制約や推奨される運用方法 状況に応じた具体的な施策の選択肢 また、外部ファイル化することで、コードを変更せずにビジネス情報を更新できるメリットも得られました。 2. 複数クエリをまとめて要約 デフォルトの実装では、個別クエリ要約エンドポイント( /generateQuerySummary )を使ってタイルごとに要約を生成し、それを箇条書きで並べるスタイルでした。しかし、ダッシュボード全体の情報を統合した上での要約が欲しいという要望がありました。 OSSにはダッシュボード統合要約エンドポイント( /generateSummary )も用意されていますが、デフォルトでは使用されていません。そこでフロントエンド側で両方のエンドポイントを組み合わせるよう実装を変更しました。まず collateSummaries 関数で各クエリの個別要約を生成します(内部で /generateQuerySummary を使用)。その結果を generateFinalSummary 関数で /generateSummary エンドポイントに渡します。これによりダッシュボード全体を俯瞰した統合要約を生成できるようになりました。 // 各クエリの個別要約を生成 const querySummaries = await collateSummaries( queryResults, nextStepsInstructions, restfulService, extensionSDK, dashboardMetadata, setQuerySummaries, setLoadingStates ); // 個別要約を統合してダッシュボード全体の要約を生成 await generateFinalSummary( querySummaries, restfulService, extensionSDK, setFormattedData, nextStepsInstructions ); 3. 事前プロンプトの固定化 デフォルト実装ではユーザーが自由に質問を入力できるUIになっていますが、社外テナントユーザーが利用するケースではセキュリティリスクが懸念されました。そこで、ユーザー入力欄を廃止し、フロントエンド・バックエンド両方で固定プロンプトのみを使用するように変更しました。今回は特殊ケースなのでこのような実装に変更しましたが、こういった柔軟な変更ができるところもOSSの利点と言えます。 本番適用時の技術的課題と解決策 次に、本番適用時に直面した技術的課題とその解決策について説明します。 フィルタ状態の同期 本番適用時、ダッシュボードのフィルタ機能と組み合わせた際に技術的な課題が発覚しました。 課題:初期状態に依存した要約の不整合 デフォルトの実装では、ダッシュボード要約は初期ロード時のフィルタ状態を基に生成されていました。そのため、以下のような問題が発生しました。 ユーザーがフィルタを変更 画面上のグラフ・表は新しいフィルタで更新される しかし要約生成時は初期フィルタ状態のデータを参照 結果として、画面表示と要約内容が乖離 この問題は、Looker Extension SDKにおけるダッシュボードメタデータの取得タイミングに起因していました。 解決策: 動的なフィルタ状態の取得 要約生成時に tileHostData.dashboardFilters から現在のフィルタ状態を取得し、それを fetchDashboardDetails に渡すように変更しました。これにより常にユーザーが見ている状態と一致した要約が生成されます。フィルタ変更後も画面表示と要約内容の整合性が保たれるようになりました。 // 最新のフィルタ状態でダッシュボードメタデータを再取得 const latestMetadata = await fetchDashboardDetails( dashboardId, core40SDK, extensionSDK, tileHostData.dashboardFilters || {} ); // メタデータを更新して一貫性を保つ setDashboardMetadata(latestMetadata); // 最新のクエリ結果を取得 const latestResults = await fetchQueryData(latestMetadata.queries, core40SDK); Looker ExtensionでのSecret Key利用 実装時に個人的に混乱したポイントとして、Looker Extension FrameworkではUser Attributeに名前空間が自動的に付与される仕組みがあります。 公式ドキュメント によると、拡張機能ID my-extension::my-extension でUser Attribute my_value を使用する場合の例を示します。Looker側で実際に設定する属性名は my_extension_my_extension_my_value になります。 extensionSDK.createSecretKeyTag() でSecret Keyを参照する際は、フロントエンド側では my_value のように短い名前を指定するだけです。Frameworkが自動的に名前空間を付与してくれます。以下は実際の使用例です。 // バックエンドAPIへのリクエスト時にシークレットキーを使用 const response = await extensionSDK.serverProxy( ` ${ restfulService } /generateSummary` , { method : 'POST' , headers : { "Content-Type" : "application/json" } , body : JSON . stringify ( { querySummaries , nextStepsInstructions , // フロントエンド側では短い名前を指定 client_secret : extensionSDK.createSecretKeyTag( "my_value" ) // Looker側では my_extension_my_extension_my_value として設定 } ) } ); PDFダウンロード対応 Extension Tileを含むダッシュボードをPDFダウンロードする場合、 extensionSDK.rendered() を呼び出してレンダリング完了を通知する必要があります。この実装がないと、Lookerのレンダラが応答を停止してしまいます。 詳細は 公式ドキュメント を参照してください。 なお、サマリ内容自体はPDFに含まれません。サマリは非同期で生成されるため、標準のダウンロード機能では取得できない仕様となっています。 実際の動作例 以下は、BigQueryの公開データセット( bigquery-public-data.thelook_ecommerce )を使用したデモダッシュボードでの動作例です。ダッシュボードには複数のタイル(グラフや表)が表示されており、右サイドに「サマリを生成」ボタンがあります。 なお、このダッシュボードの数字はZOZOTOWNの実際の売上データとは一切関係ありません。 ボタンをクリックすると、各タイルの内容を分析し、ダッシュボード全体を俯瞰した要約とNext Actionが生成されます。なお、スクリーンショット内のNext Action部分はビジネス情報を含むためマスキングしています。 実装のポイント 本記事で紹介した実装における重要なポイントを以下にまとめます。 セキュリティ設計 User AttributeとAccess Filterによるマルチテナントデータアクセス制御 パラメーター改ざん攻撃等のリスクを回避 事前プロンプトの固定化によるセキュリティ担保 ユーザー入力の制限によるセキュリティ強化 Cloud Load BalancingとCloud Armorによる二重防御 APIトークン認証に加えてIPアドレス制限を実施 プロンプト設計 ZOZOTOWNのビジネスコンテキストを外部ファイル化(guideline.txt) 現実的なNext Actionの提示 要約処理の工夫 複数クエリの統合要約の実装 ダッシュボード全体を俯瞰した要約生成 フィルタ状態の動的取得による整合性の確保 ユーザー操作に応じた要約生成 まとめ 本記事では、Gemini in LookerのDashboard Summarization機能をマルチテナント環境で安全に実装する方法を紹介しました。 現在はお試しリリースの段階ですが、マルチテナント環境での生成AI活用機能を、セキュリティを担保しながら安全に実装できました。今後はユーザーフィードバックを元にした継続的な改善を検討していきます。 Gemini in Lookerの導入を検討している方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co
アバター
はじめに こんにちは。データシステム部・推薦基盤ブロックの上國料( @Kamiko20174481 )です。私たちのチームは、ZOZOTOWNの推薦システムを開発・運用し、ユーザー一人ひとりに最適な購買体験を届けることを目指しています。 これまでは施策ごとに推薦システムをゼロベースで構築していたため、施策実施までのリードタイムが長く、推薦システムの運用負荷も高まりやすいという課題がありました。この課題を解決するために、 ユーザーや商品をEmbedding(埋め込みベクトル)として表現し、それらを一元的に管理して複数の推薦施策で再利用できる仕組み の構築に取り組みました。以降では、この仕組みをEmbedding基盤と呼称します。 本記事では、この取り組みの背景となった課題とEmbedding基盤の設計方針・アーキテクチャ、導入後の知見を紹介します。同様の課題に取り組む方々の参考になれば幸いです。 目次 はじめに 目次 推薦チームが抱える機械学習システムの課題 施策のリードタイムが長い 再利用性の不足・重複発生 Embedding基盤のアプローチ 設計方針 Embedding基盤のアーキテクチャ Embeddingの保存先 Embeddingの空間整合性の担保 Embedding基盤の活用パターン バッチ推薦での活用 リアルタイム推薦での活用 Embedding基盤の運用から得た学び 施策展開のスピード向上 共通の埋め込み空間は便利だが、万能ではない まとめと今後の展望 最後に 推薦チームが抱える機械学習システムの課題 Embedding基盤の説明を始める前に、当時チームが抱えていた推薦システム開発の課題について整理します。 施策のリードタイムが長い MLベースの推薦システムを開発する際は、モデルの設計や実装に加え、特徴量設計や学習データの整備、モデルの保存・配信、評価指標の設計など、多くの工程が必要になります。我々のチームでは、これらの工程を施策ごとに個別に実装していたため、 1つの施策をリリースするまでに数カ月単位の時間がかかることも珍しくありませんでした 。 特に課題だったのは、新しい機能に推薦を導入したい場合でも、 既存の仕組みをすぐに転用できず準備に多くの時間がかかっていたことです 。結果として「仮説を立ててから実際にユーザー反応を確認するまで」に大きなタイムラグが生じていました。 再利用性の不足・重複発生 MLベースの推薦システムは、一度リリースして終わりではなく、 継続的な保守・改善 が求められます。しかし、同様の目的であっても施策ごとに独立したモデルを構築していたため、以下のような非効率が生じていました。 案件ごとに似たような特徴量生成クエリやコードが実装され、車輪の再発明が発生 複数のシステムが存在することによるメンテナンス負荷の増大 特に特徴量まわりでは、ほぼすべての案件でBigQueryのデータマートやリアルタイムデータ基盤からユーザー情報・商品情報を取得して生成するという共通パターンが存在します。しかし、各案件でそれぞれ独自にクエリを作成しているため、似たようなクエリが複数の場所に散在するケースが多く見られます。その結果、 メンテナンスの行き届かないコードが増えていく構造的な問題 が生じていました。 モデルについても同様で、似たようなアルゴリズムや目的を持つモデルのコードが複数存在し、再利用性の低さやモデル管理の煩雑さを招いていました。 今後も推薦システムの数が増加していくことを踏まえると、 現状のままでは運用コストが増大していく懸念 がありました。 Embedding基盤のアプローチ 以上の課題を踏まえ、Embedding基盤の設計・構築を進めました。 設計方針 Embedding基盤の目的は、 学習済みのEmbeddingを共通資産として再利用できるようにすること です。 推薦チームでは、過去に実施したホーム画面のパーソナライズ施策で Two-Towerモデル を導入し、ユーザーと商品のEmbeddingを生成し、推薦リストの作成に活用しています。 一方で、これらのEmbeddingは特定の施策内でのみ利用されており、他施策から容易に再利用できる仕組みは存在していませんでした。 このため、学習済みのEmbeddingを他施策にも活用できるようにすることで、 施策立ち上げの効率化と一定のパーソナライズ効果の維持 を実現することを目指しました。 過去のTwo-Towerモデルを活用した施策に関する記事は以下をご参照ください。 techblog.zozo.com techblog.zozo.com techblog.zozo.com Embedding基盤のアーキテクチャ Embedding基盤では、 Two-Towerモデル訓練パイプライン と Embedding生成パイプライン の2種類のパイプラインで構成されています。以下に全体のアーキテクチャを示します。 まず、右側の Two-Towerモデル訓練パイプライン では、ユーザー行動データや商品メタデータを入力として、ユーザーと商品の埋め込み空間を学習します。 Two-Towerモデルは、ユーザー側の特徴を入力とする User Tower と、商品側の特徴を入力とする Item Tower から構成され、それぞれの出力が同一の埋め込み空間上で近いほど「ユーザーがその商品に関心を持つ可能性が高い」と判断されます。 学習済みのUser TowerとItem Towerは、Vertex AI Model Registryに登録され、後続のEmbedding生成パイプラインで参照されます。 続いて、左側の Embedding生成パイプライン では、Model RegistryからUser TowerとItem Towerを取得し、各ユーザー・商品のEmbeddingを生成・保存します。このパイプラインはさらに2つのパイプラインに分かれており、 全件更新パイプライン と 差分更新パイプライン で構成されています。 パイプライン名 役割 Embedding全件更新パイプライン 新しいモデルの学習完了時に、すべてのユーザー・商品のEmbeddingを再生成し、BigQueryのモデルバージョン単位のパーティションに書き換える。 Embedding差分更新パイプライン ユーザー行動の変化を反映するため、直近でアクティブだったユーザーのみを対象にEmbeddingを再生成し、既存パーティションに差分マージする。 今回、商品Embeddingは全件更新のみとし、ユーザーEmbeddingのみ差分更新する設計としました。理由は、商品カタログの変化頻度がユーザー行動に比べて低く、コスト対効果の観点で差分更新のメリットが小さいためです。一方でユーザー行動は時間経過で変化しやすいため、差分更新で最新の嗜好を推薦結果に反映しやすくしています。これらのパイプラインはCloud Schedulerで定期実行され、日次で全件更新、時間毎で差分更新する運用としています。 Embeddingの保存先 以上の処理によって生成されたEmbeddingは、用途に応じて複数のストアに保存されます。用途は大きく分けて、 オフラインサービング と オンラインサービング の2種類です。 まず、オフラインサービング用途では BigQuery を採用しました。その理由は以下の通りです。 スケーラビリティ : ZOZOTOWNのユーザー数・商品数は非常に多いため、大規模データを効率的に保存・クエリできるBigQueryは適している。 柔軟なクエリ : BigQuery Vector Searchを活用することで、Embeddingに基づく類似度検索やスコア計算をSQLベースで簡単に実装できる。 既存システムとの親和性 : 既にZOZOTOWNのデータ分析基盤としてBigQueryを活用しており、既存のデータソースと容易に連携できる。 一方で、BigQueryにはリアルタイム性や低レイテンシの観点で制約があるため、オンラインサービング用途では Vertex AI Vector Search を採用しました。 商品Embeddingをもとにインデックスを構築・管理することで、リアルタイムな類似商品検索やパーソナライズ検索が可能です。 Embeddingの空間整合性の担保 ここまで、Embeddingを生成・保存する仕組みを説明しました。次に、生成されたEmbeddingの空間整合性をどのように担保しているかについて説明します。 Embeddingの空間整合性とは、Embedding同士が同じ埋め込み空間上で比較可能な状態にあることを指します。今回使用しているTwo-Towerモデルを含め、一般的なEmbeddingモデルでは学習のたびにパラメータが変化するため、再学習すると埋め込み空間の基準軸が変わります。その結果、 異なる学習回で生成されたEmbeddingを混在させると、距離計算の基準がずれて正しい類似度を得られなくなる という課題があります。 異なる学習回で生成されたEmbeddingの空間整合性を保つ方法として、前バージョンのEmbeddingを基準に線形変換などを行うアライメント処理も検討可能です。しかしこの手法は、過去との対応関係を継続的に維持する必要があり、ユーザー行動や商品カタログが日々変化するZOZOTOWNのような環境では、 安定して整合性を保つのが難しくなる懸念 があります。また計算自体は大きな負荷ではありませんが、アンカーサンプル(基準座標)の選定やアライメント後に空間が正しく対応づけられているかを確認する検証作業が必要になり、運用コストが増す可能性もあります。 今回のEmbedding基盤では、こうした懸念よりも「最新モデルのEmbeddingを素早く反映し、施策を高速に回すこと」を優先しました。そのため、アライメント処理で空間を固定するのではなく、 モデルのバージョン管理で整合性を担保する設計 を採用しています。 これを実現するために、 Embedding基盤ではモデルの状態とバージョンを一元的に管理するためのテーブルをBigQuery上に配置 しています。 これをManifestテーブルと呼び、モデルの状態遷移とバージョンなどのメタデータを管理しています。 このテーブルではTwo-TowerモデルのUser TowerとItem Towerのモデルバージョンをペアとして扱い、Embedding生成時と取得時にペアを参照することで整合性を保つ仕組みになっています。 各モデルの状態は次の4種類で、Embeddingの生成フローに応じて切り替えています。 状態 役割 STAGING 新しいモデルの学習が完了し、これから全件更新にてEmbedding生成に使用される。 ACTIVE 全件更新にてEmbedding生成が完了し、差分更新にてEmbeddingが更新され続けている状態。常にこの状態は1つだけ存在する。 ARCHIVE 過去にACTIVEだったモデル。分析や検証用に保持。 UNUSED 学習済みだがEmbedding生成に使われなかったモデル。 新しいモデルが学習を完了すると、まずSTAGING状態として登録されます。その後、STAGINGのペアを参照して全件更新パイプラインでEmbeddingを生成し、完了時にモデルの状態がACTIVEに更新されます。モデルがACTIVEに切り替わる際、それまでACTIVEだったモデルは自動的にARCHIVEに移行します。一方で、Embedding差分更新パイプラインは常に最新のACTIVEモデルペアを参照してEmbeddingの更新を継続します。 この仕組みにより、 Embedding生成と利用のタイミングで常に同じモデルペアを参照することが保証される ため、異なる学習回で生成されたEmbeddingが混在することによる不整合を防止できます。また、 利用側は常にACTIVEモデルを参照しておけば、モデルのバージョン変化を意識することなく安定した埋め込み空間を利用できる という利点もあります。 Embedding基盤の活用パターン 前章で紹介したように、Embeddingはオフライン用途では BigQuery 、オンライン用途では Vertex AI Vector Search に保存しています。ここでは、それらの環境を活用し、実際の施策でどのようにEmbeddingを利用しているかを紹介します。 どちらの環境でも、ユーザーや商品のEmbeddingを用いた 類似商品検索 や パーソナライズ検索 が可能です。ただし、施策ごとに求められる 処理タイミングやレイテンシ要件 が異なるため、オフライン処理とオンライン処理で環境を使い分けています。 バッチ推薦での活用 日次や時間単位でバッチ推薦する際は、 BigQuery に保存されたEmbeddingを参照し、類似度計算やランキングを行います。大量の候補商品を扱う場合は、 BigQuery Vector Search を利用することで、SQLベースで効率的に類似度検索し、推薦リストを生成できます。 一方、対象商品が限定されているケースでは、Embeddingを取得して単純な類似度計算だけでも十分対応できます。 リアルタイム推薦での活用 リアルタイム応答が求められる推薦施策では、 Vertex AI Vector Search を利用します。BigQueryと同様にEmbeddingを用いた類似度検索が可能で、 ミリ秒単位の低レイテンシ処理 を実現できる点が強みです。Embeddingをクエリとして扱うことで、ユーザーや商品の特徴に応じて即時に推薦商品を取得できます。 Embedding基盤の運用から得た学び Embedding基盤の導入・運用を通じて得られた知見を紹介します。 施策展開のスピード向上 特に顕著な効果は、モデル開発からA/Bテストに至るまでのリードタイム短縮です。 以前は、特徴量設計・学習データ整備・モデル配信・評価設計など多くの工程を経る必要があり、 1施策あたり数ヶ月の開発期間 を要することもありました。Embedding基盤の導入により、これらの工程の多くを共通化・自動化できるようになり、 数週間単位で施策を試せる ようになりました。現在では、要件次第で 2週間ほどでモデリングが完了するケース も出てきています。 また、Embeddingや推薦モデルを共通管理することで、施策ごとに独立したモデルを構築する必要がなくなり、 車輪の再発明を防止 できています。 共通の埋め込み空間は便利だが、万能ではない Embeddingを共通化したことで施策開発のスピードは大きく向上しましたが、一方で、どんな施策にも万能というわけではありません。 空間を共通にしている分、施策毎の出力結果が似通いやすく、特定の目的や文脈に合わせた 個性づけが必要になる場面 が多くなる懸念があります。 例えば、ある施策では新規ユーザーに対して多様な商品を推薦したい一方で、別の施策では既存ユーザーに対して嗜好に特化した商品を推薦したいとします。共通の埋め込み空間を用いる場合、 同じ距離尺度で類似度を計算するため、両者の推薦結果が似通ってしまい、施策ごとの目的に最適化しづらくなる可能性 があります。 現在は、共通のEmbeddingをベースにしながら、施策ごとにリランキングや後処理を追加し、最終的な推薦結果を調整するアプローチを採用しています。この方法により、共通Embeddingの利点を活かしつつ、施策ごとの個別最適化も実現しています。 まとめと今後の展望 本記事では、ZOZOTOWNの推薦システムにおけるEmbedding基盤の設計と運用について紹介しました。Embedding基盤の導入により、次のような成果が得られています。 施策展開のリードタイム短縮 :数ヶ月かかっていた施策開発が、数週間、場合によっては2週間程度で完了するようになった 開発コストの削減 :Embeddingや推薦モデルを共通管理することで、車輪の再発明を防止し、メンテナンス負荷を軽減 柔軟な推薦施策への対応 :バッチ処理からリアルタイム検索まで、幅広いユースケースでの活用を実現 一方で、共通の埋め込み空間には限界もあり、施策ごとに目的や文脈が異なる中で最適化が難しいケースも見られました。 現在は、 過去の記事 でも紹介した カート追加最適化モデルをベースとしたEmbedding を活用していますが、今後は異なる目的軸(たとえば閲覧最適化モデルや画像ベースのモデルなど)でのEmbedding生成も視野に入れ、 多面的な表現空間の構築 を検討していきたいと考えています。 最後に Embedding基盤は、 推薦システム開発のスピードと品質を両立する共通インフラ として位置づけています。今後も運用の知見を反映しながら、柔軟で拡張性の高い形へとアップデートを続けていきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。ZOZOTOWN開発本部フロントエンドの菊地( @hiro0218 )です。 2021年、ZOZOTOWNはフロントエンドリプレイスを開始しました。現在、ホームページや商品一覧ページなど主要なページのNext.js化が完了し、運用フェーズに入っています。詳細は以下の記事を参照してください。 techblog.zozo.com 開始当初、他社事例を参考にしながら、よくある課題を未然に防ぐディレクトリ構成を設計しました。本記事では、約4年にわたる運用で改善を重ねてきたディレクトリの分割戦略について紹介します。 ※本記事は2025年8月にちょっと株式会社との合同勉強会で発表した内容を基にしています。 speakerdeck.com 背景 現在、私が携わっている領域におけるフロントエンド開発は4チーム、合計30名強で運用しています。この規模で効率的に開発を進めるため、ディレクトリ構成は重要な要素となります。 避けたい課題 過去の経験や他社事例から、以下のような課題を未然に防ぐ必要がありました。 配置場所が曖昧:「なんとなくここに置いた」コンポーネントが増え、後から見つけにくい 再利用性が低い:ページ固有の処理を含むコンポーネントは、他のページで使いにくい 影響範囲が不明確:コンポーネントを変更する際、どこで使われているか把握しづらい チーム開発が非効率:メンバーごとに配置ルールの解釈が異なり、コードレビューで迷う 新規開発にあたり、これらを考慮した設計戦略を採用しました。 解決アプローチ:責務分離パターン 役割を明確にした配置(責務分離)を行いました。この設計を選んだ理由は「迷わない分類」です。新しいコンポーネントを作成する際、配置場所で迷う時間を最小化し、メンバー間で判断が分かれないようにすることを重視しました。 この設計では、以下の2点を重視しています。 運用中の移動最小化 : 一度配置したコンポーネントは、基本的に移動が不要 予測可能な構造 : ディレクトリ階層が深くならず、探索しやすい この設計を実現するため、以下の2層構造を採用しました。 コンポーネント層 ( src/components/ ) 役割別に5つのディレクトリに分類 UI インフラ層 ( src/ui/ ) コンポーネントが利用する共通基盤 コンポーネント層の実装を進める中で、UIインフラ層の必要性が見えてきました。 コンポーネント層と UI インフラ層の関係 コンポーネント層の設計 コンポーネントを5つの役割に分類し、それぞれの責務と依存関係を定義しています。 ディレクトリ構成 他社事例を参考にして5つのディレクトリに分割した構成を採用しました。 src/components/ ├── UI/ ├── Models/ ├── Pages/ ├── Layouts/ └── Functional/ Next.jsのルーティング用ディレクトリ( src/pages )と区別するため、頭を大文字としています。 責務ごとの分類 各ディレクトリの役割は以下のように定義されています。 名前 役割 格納するコンポーネント例 UI 純粋なUI要素 Button, Collapse, Image... Models ドメインロジックがある ProductList, BrandList... Pages ページ専用 HomePage, SearchPage... Layouts アプリに関わるレイアウト Header, Footer... Functional UIを伴わないアプリケーション機能 Analytics, GlobalStore... この分類のポイントは「新しくコンポーネントを作るとき、どこに配置するか迷わない」ことです。曖昧な判断基準では、メンバーごとに解釈が分かれ、レビュー時の議論コストが増大します。 コンポーネント作成時の判断フロー コンポーネントを作成する際は、以下の順で判断します。 特定のページでのみ使用するか? YES → src/components/Pages/ に配置 判断基準:他のページでは再利用されない固有の実装 例:HomePage、SearchPage アプリ全体のレイアウト構造に関わるか? YES → src/components/Layouts/ に配置 判断基準:アプリ全体の構造・骨格を担当 例:Header、Footer 特定のドメインロジックを含み複数ページで使用するか? YES → src/components/Models/ に配置 判断基準:特定のドメインに関連する機能を持つコンポーネント 例:ProductList、BrandList UIのみの汎用的なコンポーネントか? YES → src/components/UI/ に配置 判断基準:ビジネスロジックを含まない純粋なUI要素 例:Button、Collapse、Image 重要:ドメイン固有の名前を持つコンポーネントも、データを表示するだけならUIに配置。Modelsとの違いは データ取得やビジネスルールを含むかどうか UIを伴わないアプリケーション機能か? YES → src/components/Functional/ に配置 判断基準:直接ユーザーに表示されないアプリケーション機能 例:Analytics、GlobalStore 実装例 UIは純粋な表示、Modelsはドメインロジックという責務の分離を、実際のコードで確認します。 UI コンポーネント propsで受け取ったデータを表示するだけのシンプルな実装です。 // UI/ProductCard - UI コンポーネント export const ProductCard = ( { name , price , image } ) => ( < Card > < Image src = { image } /> < Title > { name } </ Title > < Price > { price } </ Price > </ Card > ); Models コンポーネント データを取得し、UIコンポーネントを組み合わせて表示します。 // Models/ProductList - ドメインロジック + UI を利用 export const ProductList = ( props ) => { const { products } = useProductData(props); return ( < Grid > { products?. map (( product ) => ( < ProductCard key = { product. id } { ...product } /> )) } </ Grid > ); } ; ProductCard (UI)は純粋な表示、 ProductList (Models)はデータ取得とUIの組み合わせという責務の違いが分かります。 テストファイルの配置 コンポーネントの分類と同様に、テストファイルの配置もルールが必要です。テストやStorybookのファイルは、対象のコンポーネントファイルと同じディレクトリに配置することで、関連するファイルを一箇所にまとめて管理しています。 components/UI/Button/ ├── Button.tsx ├── Button.test.tsx ├── Button.stories.tsx └── Button.module.css この配置により、実装とテストの対応関係が分かりやすくなり、ファイル間の移動もスムーズになります。 依存関係のルール コンポーネント間の依存関係は「自分の横か下にある分類のコンポーネントのみ参照してよい」という原則に従います。 依存の基本原則:上位から下位への一方向のみ許可。 Pages ( src/components/Pages ): Models、UI、Functionalを参照可能 Models・Layouts : UIとFunctionalを参照可能 UI : Functionalのみ参照可能 Functional : 外部依存なし(最下位) 各ディレクトリは、同じディレクトリ内のコンポーネント同士も参照可能です(Pagesを除く)。 注記 : ここでの「Pages」は src/components/Pages (ページ専用コンポーネント)を指します。Next.jsのルーティング用ディレクトリである src/pages は、アプリケーションのエントリーポイントとして特別な役割を持つため、Layoutsを含むすべてのコンポーネントを参照可能です。 この依存関係により、循環参照を防ぎ、変更の影響範囲を予測しやすくなります。 依存関係のチェック 設計したディレクトリ構成のルールが守られるよう、以下のツールを導入しています。 ディレクトリ間の依存ルール eslint-plugin-strict-dependencies により、誤った依存関係を自動的に検出し、コードレビュー時の負担を軽減しています。 'strict-dependencies/strict-dependencies' : [ 'error' , [ // Pages コンポーネントの依存ルール { module : 'src/components/Pages' , allowReferenceFrom : [ 'src/pages' ] , // Next.jsのルーティング用ディレクトリからのみ参照可能 allowSameModule : false , } , // Models コンポーネントの依存ルール { module : 'src/components/Models' , allowReferenceFrom : [ 'src/components/Pages' ] , allowSameModule : true , } , // Layouts コンポーネントの依存ルール { module : 'src/components/Layouts' , allowReferenceFrom : [ 'src/pages' ] , // Next.jsのルーティング用ディレクトリからの参照を許可 allowSameModule : true , } , // UI コンポーネントの依存ルール { module : 'src/components/UI' , allowReferenceFrom : [ 'src/pages' , // Next.jsのルーティング用ディレクトリ 'src/components/Pages' , // ページ専用コンポーネント 'src/components/Layouts' , 'src/components/Models' , ] , allowSameModule : true , } , // (省略)他にも多数のルールを定義... ] ] Pages ディレクトリの特殊な設定 上記の設定において、Pagesディレクトリ( src/components/Pages )のみ allowSameModule: false を採用しています。これは、ページ間の独立性を保証するための設計です。 Pagesディレクトリは Cart/ 、 Home/ 、 Search/ のようにページごとにサブディレクトリが分かれており、各ページ専用のコンポーネントが配置されています。 allowSameModule: false により、あるページのコンポーネントが別のページのコンポーネントを参照することを禁止しています。 // NG: Cart ページが Home ページのコンポーネントを参照 import { HomeComponent } from "../Home/HomeComponent" ; もし複数のページで使いたいコンポーネントが出てきた場合、本来はModels、UI、Layoutsのいずれかに配置すべきコンポーネントである可能性が高いため、適切なディレクトリへの移動を検討します。 循環参照のチェック dependency-cruiser を導入し、PR上で循環参照を自動検出しています。これにより、複雑な依存関係による保守性の低下を未然に防いでいます。 現在の運用規模 この設計で約4年間運用した結果、執筆時点では以下のような規模でコンポーネントが配置されています。 ディレクトリ 割合 UI 約 50% Functional 約 20% Models 約 13% Pages 約 11% Layouts 約 6% UI インフラ層の設計 コンポーネント層で使用する共通基盤として、スタイル定義やテーマシステムを管理しています。 ディレクトリ構成 src/ui/ ├── themes/ # 色・mixin・関数・ブランドテーマ定義 │ ├── mixin/ # Mixin ヘルパー │ ├── function/ # 関数ヘルパー │ └── themeVariants/ # ブランド別テーマ ├── styled/ # Emotion のスタイル関数群 ├── libs/ # UI ユーティリティ ├── constants/ # UI 共通定数 └── stylelint-plugins/ # カスタム Stylelint ルール 統一されたスタイル定義 ZOZOTOWNではCSS in JS(Emotion)を採用しています。SassやPostCSSのようにmixinやfunctionを用意し、ホバーエフェクトやタイポグラフィ計算など、スタイリングの共通処理を提供しています。 const Button = styled.button ` ${ ( { theme } ) => theme.mixin.hoverOpacityEffect() } ; ` ; const Label = styled.span ` font-weight: ${ ( { theme } ) => theme.function.fontWeight( "bold" ) } ; ` ; 実装者はスタイルの詳細を意識せず、一貫性のあるUIを構築できます。 品質担保 Stylelintプラグインにより、 EmotionのSSR制約 ( :first-child 等)や line-height など、プロジェクト固有ルールを自動検証しています。 テーマシステムの活用 これらの基盤を活用した具体例として、ZOZOTOWNではThemeProviderを用いたテーマシステムを導入しています。 ブランドテーマ ZOZOTOWNには、特定のブランド向けにブランドカラーを反映しているページがあります。対象ブランド数は多くありませんが、拡張性を考慮してテーマシステムを利用しています。 const BRAND_COLOR = "#000" ; // ブランドカラー export const colors: ColorTheme = { button : { primary : { background : BRAND_COLOR } , } , text : { red : BRAND_COLOR, blue : BRAND_COLOR, } , } ; 同じ「カートに入れる」ボタンでも、ブランドページでは自動的にそのブランド固有のカラーが適用されます。 サイトジャック ブランド出店の施策で「サイトジャック」と称して、ZOZOTOWNのカラーを期間限定でブランドカラーに置き換えることがあります。 const JACK_COLOR = "#FF1493" ; // サイトジャックカラー export const siteJackTheme: ColorTheme = { header : { background : JACK_COLOR } , navigation : { border : JACK_COLOR } , button : { primary : { background : JACK_COLOR } } , // ... } ; このテーマを適用すると、ZOZOTOWNのヘッダーやナビゲーションなどの主要な要素が、期間限定でブランド固有のカラーへ一括変更できます。 まとめ 本記事では、ZOZOTOWNフロントエンドにおけるディレクトリの分割戦略について紹介しました。コンポーネントを5つの役割に分類し、判断フローと依存関係のルールを設けることで、配置場所で迷わない設計を実現しています。また、自動チェックの仕組みやUIインフラ層の整備により、一貫性のある開発環境を構築しています。 もちろん、ファイル数の増加により見直しを検討している箇所も出てきています。しかしながら、当初の設計から大きく変えずとも大規模開発に耐えうる基盤が維持できており、新規メンバーもスムーズに開発に参加できる状況です。責務分離を意識したディレクトリ設計は、長期的な開発において有効なアプローチであると実感しています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
ZOZO開発組織の2025年10月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2025年10月は、前月のMonthly Tech Reportを含む計14本の記事を公開しました。このタイミングで6月にリリースした「 ZOZOマッチ 」関連の記事を一斉に公開しています。ぜひご一読ください。 ZOZOマッチアプリのアーキテクチャと技術構成 FigmaからFlutterへ ── デザイントークン自動変換とUIカタログで実装を加速 デバッグメニューでFlutterのアプリ開発をスムーズに! ZOZOマッチアプリのメッセージ機能を支えるFlutter × GraphQLの実装 ZOZOマッチにおけるモデル開発の不確実性との向き合い方 登壇 extension DC 2025 Day3 @ LINEヤフー 10月3日に開催された「 extension DC 2025 Day3 @ LINEヤフー 」に、ZOZOTOWN開発2部の森口( @laprasdrum )が『 実装で解き明かす並行処理の歴史:Swift ConcurrencyからNSThreadまで遡ろう 』というタイトルで登壇しました。また、同ZOZOTOWN開発2部の10/3 ZOZOTOWN開発2部の濵田( @ios_hamada )がパネルトークに参加しました。 【ZOZOエンジニア登壇情報】 現在開催中の『extension DC 2025 Day3 @ LINEヤフー』にZOZOTOWN開発本部の森口 @laprasdrum と濵田 @ios_hamada の2名が登壇します🎙️ https://t.co/DzG9oYKefl #extension_dc — ZOZO Developers (@zozotech) 2025年10月3日 speakerdeck.com スクラム祭り2025 10月3日・4日に開催された「 スクラム祭り2025 」に、ZOZOMO部の木目沢( @pilgrim_reds )が『 社内請負スクラムから脱却する〜複雑性に適応するスクラムチームの作り方〜 』というタイトルで登壇しました。 confengine.com speakerdeck.com 【Omiai×出前館×ZOZO】1,000万ユーザー超サービス3社が語る"今だから話せる失敗" 10月10日に開催された「 【Omiai×出前館×ZOZO】1,000万ユーザー超サービス3社が語る"今だから話せる失敗" 」に、ZOZOTOWNプロダクト戦略部の高橋が『 アイテムレビュー機能導入からの学びと改善 』というタイトルで登壇しました。 ◤ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄   #今だから話せる失敗 ___________◢ 10月10日(金)に株式会社出前館 本社で開催される『1,000万ユーザー超サービス3社が語る"今だから話せる失敗"』にZOZOTOWNでTech-PMを務める高橋 智仁が登壇します🎙️ ぜひ会場にお越しください! https://t.co/hPexyB4IyO — ZOZO Developers (@zozotech) 2025年10月7日 speakerdeck.com GPU UNITE 2025 10月15日に開催された「 GPU UNITE 2025 」に、生産研究開発部の安東が『 ロバストかつ大規模なクロスシミュレーションのための衝突処理 』というタイトルで登壇しました。 Vue Fes Japan 2025 10月25日に開催された「 Vue Fes Japan 2025 」に、WEARフロントエンド部 テックリードの冨川( @ssssotaro )が『 なんでRustの環境構築してないのにRust製のツールが動くの? 』というタイトルで登壇しました。 10/25 (土) に開催される『Vue Fes Japan 2025』にて、WEARフロントエンド部 テックリードの冨川 @ssssotaro が16:25開始のLTに登壇します! 発表タイトル 『なんでRustの環境構築してないのにRust製のツールが動くの?』 セッション詳細 🔗 https://t.co/ArJKAfPW3A #vuefes #vuejs #zozo_engineer — ZOZO Developers (@zozotech) 2025年10月24日 speakerdeck.com Cybozu Days 2025〜ノーコードAIランド〜 10月27日・28日に開催された「 Cybozu Days 2025〜ノーコードAIランド〜 」に、コーポレートエンジニアリング部の新井が『 実践者が語る︕「⾃律」と「ガバナンス」を両⽴する市⺠開発の勘所 』というタイトルで登壇しました。 AI Agent Summit ’25 Fall 10月30日・31日に開催された「 AI Agent Summit ’25 Fall 」のDay 2 基調講演に、CTOの瀬尾( @sonots )が『 Gemini CLI の全社利用を支える技術 』というタイトルで登壇しました。 10/30~31 に開催される「AI エージェント」をメインテーマとしたイベント「AI Agent Summit ’25 Fall」のDay 2 基調講演にCTOの瀬尾 @sonots が登壇します! 「Gemini CLI の全社利用を支える技術」についてご興味をお持ちの方はぜひご参加ください! https://t.co/dhN7Pmgnrk #gcai_agent — ZOZO Developers (@zozotech) 2025年10月29日 Google Cloud AI Agent Summit ’25 Fall🍂 #gcai_agent 🟦Day2 基調講演:10/31(金)13:00 - 14:00 開発の常識を覆す AI エージェントの実装と活用 ✔︎ Agent Development Kit (ADK) ✔︎ Gemini CLI ✔︎ バイブ コーディング ライブ配信はこちら: https://t.co/BewsTgSsDZ pic.twitter.com/FaLbJDrM3T — Google Cloud Japan (@googlecloud_jp) 2025年10月31日 掲載 LINEヤフーストーリー LINEヤフーのコーポレートブログ「LINEヤフーストーリー」に、グループCEO連載の第3回として代表取締役社長兼CEO 澤田のインタビュー記事が掲載されました。 www.lycorp.co.jp Girls Meet STEM 先月に引き続き、「 Girls Meet STEM 」プログラムの一環として8月18日に実施した「 ZOZOTOWNとWEARを支える技術と働き方をのぞいてみよう! 」に関する記事がエンジニアtypeに掲載されました。 techblog.zozo.com type.jp WEAR「着回し提案」 ユーザーが特定のアイテムを選択すると、ファッションに特化したAIがユーザーの好みに合わせた着回しを提案、WEARの新機能「着回し提案」に関する記事が複数メディアに掲載されました。 www.watch.impress.co.jp eczine.jp corp.zozo.com XD(クロスディー) XD(クロスディー)に、ZOZOTOWNの歴史を紐解く記事が掲載されました。 exp-d.com 日経クロストレンド 日経クロストレンドの「日経MJ」インタビューに、代表取締役社長兼CEO 澤田のインタビュー記事が掲載されました。 xtrend.nikkei.com ECzine ECzineの「押さえておきたい!ECトレンド図鑑」に、ZOZOの20年間を振り返る記事が掲載されました。 eczine.jp その他 「ZOZOTENT」を竣工 本社を置く西千葉に新棟「ZOZOTENT(ゾゾテント)」を竣工し、10月15日から利用開始しています。 corp.zozo.com 2026年3月期 第2四半期 決算発表 10月31日に2026年3月期第2四半期決算を開示しました。詳細は以下のリンクにある開示資料をご確認ください。 corp.zozo.com 以上、2025年10月のZOZOの活動報告でした! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、SRE部プラットフォームSREブロックの さかべっち です。2025年度に新卒で入社しました。普段はZOZOTOWNにおけるプラットフォーム基盤の運用・改善を担当しています。 本記事では、Istio Operatorが非推奨となったことを受けて、サービス断を一切発生させることなくHelmへの移行を完遂させた取り組みをご紹介します。移行作業で直面した技術的な課題とその解決方法について、実際の経験をもとに解説します。 目次 はじめに 目次 背景 移行方針 移行戦略と2つの重要なポイント ポイント1: サービス断は絶対NG ポイント2: いかに工数を最小限に抑えるか 各移行先の比較検討 技術的な課題と解決方法 課題1: Istio OperatorとHelmのリソース競合 課題2: Helm Chartで提供されない項目の設定方法 課題3: Istio Operatorをいかに安全に削除するか 結果 まとめ 背景 Istio Operatorは、Istioのコンポーネントを一元管理するためのオペレーターとして、ZOZOTOWNのプラットフォーム基盤で重要な役割を果たしてきました。IstioOperatorというカスタムリソース定義(CRD)を通じて、Istiod、Gatewayなどの設定を宣言的に定義できます。これにより、複雑なIstioの構成管理をIaC(Infrastructure as Code)として効率的に運用できました。 しかし、 Istio公式から非推奨の発表 があり、version 1.23以降へのアップグレードができないという制約が明らかになりました。 移行前時点での状況 ZOZOTOWNのプラットフォーム基盤のIstioはversion 1.23で運用 version 1.23のEOL(End of Life)が迫っている 利用中のIstio Operatorではversion 1.23から先にUpgrade不可 このような背景から、後回しにしていた移行作業が急務となり、サービス稼働を継続しながら移行を実施する必要がありました。 移行方針 移行戦略と2つの重要なポイント ポイント1: サービス断は絶対NG ZOZOTOWNは24時間365日稼働しているECサイトであり、サービス断は避けなければいけません。 そのため、以下のような対策を徹底しました。 カナリアリリースを採用してサービス影響を最小化 開発環境、検証環境で十分に手順検証と影響確認を実施してから本番環境で移行を実施 問題が発生してもすぐに戻せるよう各ステップの変更に対して詳細な切り戻し手順を用意 ポイント2: いかに工数を最小限に抑えるか 移行手順の詳細検討の結果、弊ブロックで実施してきた従来のIstioアップグレード手順をベースにできることが判明しました。 この発見を活かし、v1.23.2のIstio Operatorからv1.25.3のHelmへの移行を実施しました。 このアプローチにより、EOL対応とIstio Operator脱却を同時に達成し、工数を大幅に削減できます。 ただし、サービス断なしの要件を満たすため、リスク管理には特に注意を払う必要がありました。特に、Upgradeに伴う変更内容も把握する必要があったので、Istioのリリースノートを確認したり、切り戻し手順の整備に充分な時間を確保しました。 各移行先の比較検討 以下の観点から各手法を詳細に検討しました。 項目 Istio Operator istioctl Helm リソース管理方式 カスタムリソースで一括管理 CLIツールで直接デプロイ Helmチャートでコンポーネントごとに管理 GitOps対応 ◎ FluxCDで管理可能 × CLI実行が必要 ◎ HelmReleaseで完全対応 設定の柔軟性 ◎ IstioOperator API ◎ IstioOperator API △ valuesで対応しきれるかは不明 公式サポート × 非推奨(v1.23まで) ◎ 継続サポート ◎ 継続サポート カナリアアップグレード ◎ 可能 ◎ 可能 ◎ 可能 非推奨の発表が出た段階で移行先の検討は過去に進められており、istioctlおよびHelmの2つが主な選択肢として検討されました。 当初、istioctl installは以下のメリットから有力候補でした。 IstioOperator APIをそのまま使用でき、移行が比較的スムーズ カナリアアップグレードが引き続き可能 Istio公式が推奨する方法で今後もサポートが継続される しかし、istioctl用のCI/CDが必要なこと、FluxCDとの統合が困難であること、GitOpsワークフローに組み込みにくいことが課題となりました。ZOZOTOWNでは「 CIOpsからGitOpsへ。Flux2でマイクロサービスのデプロイを爆速にした話 」で紹介しているように、FluxCDを使用したGitOps基盤を採用しています。この基盤との親和性が重要な要件でした。 一方、Istio Operatorの非推奨が発表された際、Helmはalpha版だったため、移行するリスクや難易度が高いと判断しました。そのため、すぐに対応が必要というほどの緊急度ではないとして、移行は後回しにされてきました。 しかし、version 1.23から先にUpgrade不可であることが判明し、Istio Operator脱却の温度感が高い状況になりました。そして、移行プロジェクトを開始する頃には、ZOZOTOWNのエコシステムでもHelmの利用が一般的になってきていたため、Helmを選択しました。 さらに、ZOZOTOWNで採用しているFluxCDのHelmReleaseを使用することで、既存のGitOpsへ統合できます。他のKubernetesアプリケーションと同じツールで宣言的に管理できることも大きな決め手となりました。 下図に示すように、Istio OperatorとHelmはどちらもIaCとして宣言的に管理できる点で本質的には共通しています。Helmは業界標準のパッケージマネージャーとして、エコシステムとの統合性に優れています。 Helmを選択したものの、Istio OperatorからHelmへの移行には課題が残っていました。 まず、 Istio公式のHelm upgrade手順 では、HelmベースのIstioインストール間でのアップグレード手順のみが記載されていました。Operator環境からの移行に必要な既存リソースの扱い方や完全削除の方法といった移行方法は言及されていませんでした。また、IstioOperatorとHelm valuesでは設定の階層構造とパラメータ名が大きく異なり、全ての設定を1対1でマッピングできるかが不明でした。特にZOZOTOWNのプラットフォーム基盤ではIstioのIngressGatewayを利用しており、かなり細かい設定が適用されていました。それらを網羅しないとサービス断につながるため、慎重な移行計画が必要でした。 これらの課題をどのように解決したかについては、「技術的な課題と解決方法」で詳しく説明します。 技術的な課題と解決方法 前述した移行プロジェクトで直面した技術的な課題とその解決方法を詳しく解説します。Istio OperatorからHelmへの移行は、単純な置き換えではなく、いくつかの固有の課題への対応が必要でした。 移行は大きく3つのフェーズ(Control Plane導入、Data Planeのカナリアアップグレード、Istio Operator削除)で実施しました。Data Planeのカナリアアップグレードは Istio公式のHelm upgrade手順 を参考にしました。しかし、Control Plane導入とIstio Operator削除のフェーズを中心にIstio Operator特有の課題がいくつか発生しました。 これから、主に3つの課題とその解決方法を解説していきます。 課題1: Istio OperatorとHelmのリソース競合 第1の課題は、IstioにおけるCRDの管理権限を、Istio OperatorからHelmに移転する方法でした。 IstioのCRD管理には istio-base というHelmチャートが用意されています。しかし、 istio-base をデプロイしようとすると以下のようなエラーが発生します。 エラー例: Warning InstallFailed: Helm install failed for release istio-system/istio-base with chart base@1.25.3: Unable to continue with update: CustomResourceDefinition "wasmplugins.extensions.istio.io" in namespace "" exists and cannot be imported into the current release: invalid ownership metadata 以下の図でこのエラーの状態を説明します。まず、Istio OperatorやHelmにおけるリソース管理は、各リソースに所有者情報がメタデータとして記録されます。 istio-base によってデプロイされるリソースは、すでにIstio Operatorのコントローラーが作成しています。その状態で istio-base をデプロイすると同じリソース名でcreateの処理が走ります。Kubernetesでは同じ名前でリソースをデプロイしようとすると、名前の競合によりエラーになってしまうので、Helmの管理下に置き換わっていない状態です。 解決方法: 既存のリソースにHelm管理用のラベル・アノテーションを付与するスクリプトを作成し実行しました。 #!/bin/bash kubectl label sa istio-reader-service-account -n istio-system app.kubernetes.io/managed-by = Helm --overwrite kubectl annotate sa istio-reader-service-account -n istio-system meta.helm.sh/release-name = istio-base --overwrite kubectl annotate sa istio-reader-service-account -n istio-system meta.helm.sh/release-namespace = istio-system --overwrite kubectl label customresourcedefinition wasmplugins.extensions.istio.io app.kubernetes.io/managed-by = Helm --overwrite kubectl annotate customresourcedefinition wasmplugins.extensions.istio.io meta.helm.sh/release-name = istio-base --overwrite kubectl annotate customresourcedefinition wasmplugins.extensions.istio.io meta.helm.sh/release-namespace = istio-system --overwrite ... このラベルとアノテーションにより、Helmが既存のリソースを認識し、管理下に置くことができました。これにより、既存のCRDを削除せずに、Helmの管理下にスムーズに移行できました。 課題2: Helm Chartで提供されない項目の設定方法 第2の課題は、Helm Chartのvaluesとして提供されないが、IstioOperatorで細かく設定される項目の設定方法でした。 特にZOZOTOWNで運用しているIngressGatewayの設定は数百行の設定が存在していました。これらの設定を全てHelmに移行する必要がありました。 Helmでは、以下のようにvaluesを通じて設定をカスタマイズできる仕組みが提供されています。valuesファイルに定義された設定が処理され、最終的なKubernetesマニフェストが生成・applyされます。以下はFluxCDのカスタムリソースであるHelmReleaseを用いた例です。 valuesでの設定例(HelmRelease) apiVersion : helm.toolkit.fluxcd.io/v2 kind : HelmRelease metadata : name : istio-ingressgateway namespace : default spec : interval : 5m chart : spec : chart : gateway version : 1.25.3 sourceRef : kind : HelmRepository name : istio namespace : istio-system dependsOn : - name : istiod namespace : istio-system values : resources : limits : cpu : "2" memory : "3Gi" requests : cpu : "2" memory : "3Gi" しかし、Helmチャートで公開されているvaluesスキーマ( gateway/values.yaml で確認可能)は、すべての設定項目をカバーしているわけではありません。version 1.25.3現在、ZOZOTOWNのIstio IngressGatewayで設定していた以下の項目が、Helmチャートでは対応できないことが判明しました。 コンテナのライフサイクル設定(最新versionはvaluesで設定可能) DNS設定のカスタマイズ 解決方法: この問題を解決するため、HelmReleaseのPost Renderers機能を活用しました。Post Renderersは、Helmがテンプレートからマニフェストを生成した後、それをKubernetes環境へ適用する前に、任意の変換処理を挿入できる機能です。FluxCDのHelmReleaseでは、Post Renderersを宣言的に定義できるため、GitOpsワークフローとシームレスに統合されます。 我々は、KustomizeをPost Renderersとして使用し、valuesでカバーできない設定を追加するアプローチを採用しました。 Post Renderers利用での設定例(HelmRelease) apiVersion : helm.toolkit.fluxcd.io/v2 kind : HelmRelease metadata : name : istio-ingressgateway namespace : default spec : interval : 5m chart : spec : chart : gateway version : 1.25.3 sourceRef : kind : HelmRepository name : istio namespace : istio-system dependsOn : - name : istiod namespace : istio-system values : resources : limits : cpu : "2" memory : "3Gi" requests : cpu : "2" memory : "3Gi" postRenderers : - kustomize : patches : - patch : | apiVersion : apps/v1 kind : Deployment metadata : name : istio-ingressgateway namespace : default spec : template : spec : dnsConfig : options : - name : "ndots" value : "1" このPost Renderersを使用したアプローチにより、IstioOperatorのすべての設定をHelmReleaseに移行できました。 課題3: Istio Operatorをいかに安全に削除するか 第3の課題は、Istio Operatorの安全な削除方法の確立でした。 IstioOperatorにはKubernetesのFinalizerが付与されています。Finalizerとは、対象リソースの依存関係を考慮した削除処理を行う仕組みです。例えば、リソースを削除するとFinalizerで定義された処理が走り、その処理が完了するまで対象リソースは削除されません。IstioOperatorの場合、自身が管理するリソースを削除してから自身を削除する動作となっています。この仕様により、不用意なIstioOperator削除は、稼働中のIstioコンポーネントの完全削除につながり、サービス断のリスクがありました。 検証でこの動作を確認したところ、CRDの所有権は既にHelmへ置き換わっているので、Istio OperatorのコントローラがCRDを削除しようとしても、Helmによって保護されます。そして、IstioOperatorはDeletionTimestampが設定された状態で永続に残り続けてしまう問題がありました。 解決方法: この問題を解決するため、以下の手順を確立しました。 IstioOperatorからFinalizerを削除 不要となった旧バージョンのIstiodやIngressGatewayを個別に削除 Finalizerがない状態でのIstioOperatorリソース削除 このアプローチにより、稼働中のサービスに影響を与えることなく、安全にIstioOperatorを削除できました。 # Finalizerを削除 kubectl patch istiooperator istio-control-plane-1-23-2 -n istio-system \ --type =' merge ' -p ' {"metadata":{"finalizers":[]}} ' # 関連リソースも合わせて削除 kubectl delete deployment istiod-1-23-2 -n istio-system kubectl delete deployment istio-ingressgateway-1-23-2 -n istio-system kubectl delete serviceaccount istio-operator -n istio-system kubectl delete clusterrole istio-operator kubectl delete clusterrolebinding istio-operator 各コンポーネントの削除後、カスタムリソースのIstioOperatorを削除し、最後にコントローラーとして役目を終えたistio-operator(deployment)を削除しました。 結果 上記の技術的課題を解決し、以下の成果を達成しました。 段階的な切り替えと詳細な切り戻し手順によりサービス断なしでの移行を実現 v1.23.2 → v1.25.3へのアップグレードを同時に実施し、工数を大幅削減 今後も継続してIstio Upgradeが可能 まとめ 本記事では、Istio OperatorからHelmへの移行における詳細な手順と技術的な課題の解決方法を紹介しました。 移行作業では「サービス断は絶対NG」という前提を守りつつ、Istioのバージョンアップと工数圧縮も同時に実現できました。 今後は、Helmベースでの運用を継続し、より安定的で保守しやすいIstioの運用体制を構築していきます。同様の移行を検討されている方の参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは、データシステム部MA推薦ブロックの佐藤( @rayuron )です。私たちは、主にZOZOTOWNのメール配信のパーソナライズなど、マーケティングオートメーションに関するレコメンドシステムを開発・運用しています。 早速ですが、先日ZOZOマッチというサービスをリリースしました。 corp.zozo.com 新規サービスのアルゴリズム開発では、既存サービスと異なり、ユーザー行動データが存在しない状態からスタートします。本記事では、ZOZOマッチのレコメンドアルゴリズム開発において、リリース前のモデル性能やシステム負荷に関する不確実性にどう向き合い、リリースまでたどり着いたかを紹介します。 はじめに 背景 課題 1. モデルの評価とリリース判断が困難 2. ユーザー数とシステム負荷の予測が困難 課題を解決したアプローチ 1. ドメインと技術の調査 ドメイン調査 技術調査 2. プロトタイプを使った評価 3. モックデータを使った負荷試験 得られた効果 1. 関係者間での合意形成 2. 安定したシステム稼働 今後の展望 レコメンドの精度向上 おわりに 背景 ZOZOマッチは、ファッションジャンル診断などの情報をもとに、ZOZO独自のAIが「好みの雰囲気」の相手を紹介するマッチングアプリです。登録時に年齢や性別などの基本情報に加え、複数のコーディネート画像の中から好みのファッションを選択することで、「好みの雰囲気」の相手を導き出します。さらに、プロフィールに登録された全身写真からユーザー自身の雰囲気も分析することで、一人ひとりにおすすめの相手を紹介します。 MA推薦ブロックでは、このサービスのコアとなるレコメンドアルゴリズムの開発を担当しました。しかし、新規サービスのため、ユーザーのプロフィール情報や行動履歴が一切存在しない状態からのスタートとなりました。 課題 新規サービスのアルゴリズム開発を進める上で、主に以下の2つの大きな課題に直面しました。 1. モデルの評価とリリース判断が困難 通常のレコメンドシステム開発では、過去のユーザー行動データを使ってモデルの精度をオフライン環境で評価します。しかし、新規サービスではそのデータが存在しないため、定量的な評価が困難でした。また「このアルゴリズムで十分な品質か」を客観的に示せず、関係者との合意形成も困難でした。 このような相互的な好みを考慮する仕組みは「相互レコメンドシステム(Reciprocal Recommendation System)」と呼ばれ、ユーザー間の双方向の関係性をモデリングします。そのため、これまで私たちが携わってきた、ユーザーに商品を推薦する片方向のレコメンドシステムとは異なります。実際、「どのくらいの精度があればユーザーが満足するのか」「片方向の精度と双方向の精度のバランスはどうあるべきか」といった根本的な問いに対する答えを、初期の調査段階で見つけられませんでした。 2. ユーザー数とシステム負荷の予測が困難 サービスリリース後にどれくらいのユーザーが登録し、どの程度の頻度で利用するかは未知です。ユーザー数に依存する処理時間や必要なリソースが分からないため、処理の長時間化やOOMなどにより、レコメンドのサービングが遅れ、サービス全体に影響を与えるリスクがありました。 これらの課題を解決しないまま開発を進めると、リリース後に大きな問題を引き起こすリスクがありました。 課題を解決したアプローチ 上記の課題に対して、以下3つのアプローチで解決を図りました。 1. ドメインと技術の調査 効果的な解決策を設計するため、マッチングアプリのドメインと技術を調査しました。上記の課題に対する直接的な解決策ではないものの、ドメインと技術の理解を深めることで、課題の本質を捉えられます。 ドメイン調査 まずは、一般的なマッチングアプリについて、さまざまな観点で調査しました。ターゲットとなるユーザー層や、使用目的による行動の違い、人気ユーザーへのいいね集中や新規ユーザーの不利といったバイアス、メッセージ疲れや友人発見のリスクなどを洗い出しました。 次に、ZOZOマッチ固有のサービスの特性やバイアスを分析しました。特に、初期リリース時には検索機能を持たずレコメンドのみでマッチングする設計のため、レコメンド精度が特に重要となります。また、性別、年齢、会いたいエリアだけではなく、ファッションでのマッチングがコンセプトであることやその他のビジネスロジックを整理しました。 ファッションでマッチングを行うために、基本的なプロフィール情報やファッション関連データに加え、ファッションジャンルの診断結果を活用します。将来的には、WEARの投稿・閲覧データやZOZOTOWNの購買データとの連携も視野に入れ、他のマッチングアプリにはないZOZOならではのデータ活用方針を定めました。 マッチングアプリのレコメンドにおけるKPIは「マッチ率」であるため、ドメイン調査の結果を踏まえ、レコメンドモデルでは次の2つの指標の向上を目指す必要があります。 レコメンドとして表示された相手にユーザーが「いいね」を送る回数 ユーザーが「いいね」を送った相手から、「いいね」を返してもらえる回数 しかし、リリース前の初期フェーズでは、いいねやマッチのデータが取得できないため、これらを直接の目的変数として最適化できません。そこで、マッチングアプリの先行研究やユーザー行動の知見を踏まえ、「ユーザーが登録した条件と相手に求める条件が互いに一致するユーザーをレコメンドすることで、マッチ数が増加する」という仮説を立てました。条件には、性別や年齢、会いたいエリアといった基本的なプロフィール情報に加え、ファッションジャンルの相性を使用します。 技術調査 ドメイン調査と並行して、相互レコメンドの研究動向を調査し、ZOZOマッチに適用可能な手法を検討しました。 ZOZOTOWNのようなECサイトでは、商品をユーザーにレコメンドする片方向のレコメンドシステムが一般的です。一方、マッチングアプリは、相互レコメンドと呼ばれる双方向のレコメンドが必要です。つまり、ユーザーAのユーザーBへの嗜好と、ユーザーBのユーザーAへの嗜好の両方を考慮します。相互レコメンドシステムでは、以下の3つのコンポーネントを持つアーキテクチャが主流です。 ユーザーA → ユーザーBへの嗜好スコア予測 ユーザーB → ユーザーAへの嗜好スコア予測 2つのスコアを集約する関数 最終的に、ZOZOマッチでもこの基本構成を採用しました。まず初回リリース時点ではユーザーの行動データがないため、ユーザーが事前に登録した情報を使い、コンテンツベースのレコメンドから始めます。その後、データが蓄積された段階で協調フィルタリングを組み合わせたモデルに移行する方針を決定しました。 2. プロトタイプを使った評価 モデルの評価とリリース判断が困難という課題に対しては、モデルの品質を確認するためにプロトタイプを作成し、関係者からフィードバックを得ることで、リリース判断の合意形成を図りました。 ZOZOが運営する別サービスであるWEARには、既にファッションに関する画像とそれに紐づくジャンルのデータが蓄積されています。このデータを活用して、プロトタイプを作成しました。ただし、WEARはファッションコーディネート投稿がメインであり、マッチングとは目的が異なります。このため、WEARでの傾向が必ずしもZOZOマッチに当てはまるとは限らないというバイアスを関係者間で共有しました。 プロトタイプには、性別や年齢、会いたいエリアといった基本的な情報を表示する機能を実装しました。さらに、サービスの核となる、自分が相手に求めるファッションジャンルと相手が自分に求めるファッションジャンルの両方を確認できるようにしました。 プロトタイプにはWEARデータだけでなく、社員のプロフィール画像や好みのコーディネート画像を任意で登録できる機能も実装しました。これにより、実際のZOZOマッチに近い形でレコメンド結果を評価できるようになり、よりリアルなフィードバックを得ることができました。また、自分自身を起点にしたレコメンドを確認できるため、「この人はタイプだけど、自分には似合わなさそう」といった双方向性を考慮した評価も可能になりました。 アプリのターゲットユーザーとなり得る社内メンバーにレコメンド結果を見てもらい、フィードバックを収集しました。プロトタイプを素早く作成し、ビジネスサイドや社内メンバーからフィードバックを得て改善するというサイクルを回すことで、リリース判断の合意形成を図りました。完全な双方向でのフィードバック検証は実現できなかったものの、多くの具体的な改善案を得ることができました。 モデル開発初期は定性評価を中心に行い、リリース直前には定量評価も実施しました。具体的には、社内ベータテストとして約30名のターゲット層に該当する社員が実際にアプリを使用し、複数のレコメンドモデルを比較評価しました。評価指標は「レコメンドされた相手に対するいいね率」とし、最も高いスコアを示したモデルを最終的に採用する判断に至りました。 具体的には以下のようなフィードバックを得られました。他にも多数の意見がありましたが、ここでは一部を紹介します。 相手に希望する年齢以外のユーザーが上位に出てくるので、表示の優先度付けを改善できないか ジャンルの中で一番強い要素がマッチしていると「お、タイプだな」と思うので、全体のジャンルの距離の中でも、ジャンルの中で一番強い要素の距離が近いともっと加点されてもいいのでは 好みでなくても写真のクオリティが高いといいねを押したくなったので、写真のクオリティ度を表示の優先度に加味できるといいねが飛び交い、マッチ数が増えそう タイプな人が1人いるだけでテンションが上がる 3. モックデータを使った負荷試験 ユーザー数とシステム負荷の予測が困難という課題に対しては、LLMを活用したモックデータの作成というアプローチを採用しました。 負荷試験を行うため、ドメインと技術の調査で言語化したドキュメントをもとに、モックデータを作成しました。具体的には、実在の統計データ(都道府県別の人口分布など)とZOZOマッチのサービス仕様をLLMに入力し、現実的なユーザー属性分布の設計を支援してもらいました。その後、設計した分布に基づいてユーザーデータを生成するSQLクエリを作成し、マッチングアプリの一般的なユーザー行動パターンを反映したモックデータを生成しました。アクティブユーザー数を可変として、10万人、20万人、30万人などといった規模でシミュレーションを実施しました。 今回、レコメンドを作成するためのバッチシステムとして、Google Cloudの機械学習ワークフロー管理サービスであるVertex AI Pipelinesを採用しました。負荷試験を行う場合であっても本番同等のパイプラインの最初にサンプルデータ作成コンポーネントを追加するだけで、負荷試験を実施できる構成を取っています。 設計したユーザー属性分布に基づいてSQLクエリでDDLを作成。この時ユーザー数は可変なパラメータとして指定 年齢、性別、会いたいエリアなどのユーザー属性といいねやマッチといった行動データをデータベースに挿入 本番相当のレコメンドパイプラインを実行し、実行時間やリソース使用量を計測 以下は、ユーザーの会いたいエリアを決められた割合に基づいてランダムに割り当てるSQLクエリの例です。 -- 会いたいエリア WITH user_rand AS ( SELECT user_id, RAND() AS r -- 再現性が不要の場合 FROM users ), user_meeting_areas AS ( SELECT user_id, CASE WHEN r < 0 . 1275 THEN ' TOKYO ' WHEN r < 0 . 2113 THEN ' KANAGAWA ' WHEN r < 0 . 2911 THEN ' OSAKA ' ... END AS area_name FROM user_rand ) ... 生成したデータが妥当かどうかはデータ分布を可視化し直感や統計と大きく乖離していないことを確認しました。また、生成したデータはあくまで負荷試験用であり、モデル学習には使用していません。実装済みのビジネスロジックを用いて、システムの処理時間やリソース使用量を計測することが目的です。 得られた効果 これらの取り組みにより、以下の成果が得られました。 1. 関係者間での合意形成 ユーザー行動データがないため、モデルの評価やリリース判断が困難でしたが、プロトタイプによる評価を経て、レコメンド結果に対して多くの具体的な改善案を得ることができました。それらの改善を繰り返し、最終的には関係者間でのリリース判断の合意形成を実現し、リリースに至りました。 2. 安定したシステム稼働 リリース前のシミュレーションを通じてユーザー数に対する負荷を予測できたので、必要十分なアーキテクチャ設計とリソースの適用が可能になりました。シミュレーションでは、想定ユーザー数に対してレコメンド生成バッチが十分な時間内で完了することを確認し、必要なコンピューティングリソースを見積もることができました。リリース後も、レコメンドパイプラインは想定通りの処理時間内で完了し、執筆時点でのシステム稼働率は100%を維持しており、安定したサービス提供ができています。 今後の展望 レコメンドの精度向上 ZOZOマッチ内のデータが蓄積され次第、以下のような改善を段階的に進めていきます。 ユーザーの行動ログに基づく協調フィルタリングの導入 ユーザーのプロフィール画像や自己紹介文といったマルチモーダルなデータの活用 ZOZOTOWNやWEARのデータを活用したクロスプラットフォームなレコメンドの実現 おわりに 本記事では、ユーザーのプロフィール情報や行動履歴がない新規サービスにおけるアルゴリズム開発の不確実性に対して、どのようにアプローチして課題を解決したかを紹介しました。 新規サービスの開発では、既存サービスとは異なる難しさがあります。しかし、ドメインと技術調査、プロトタイプによる検証、そしてモックデータを使った負荷試験など、工夫次第で不確実性を軽減できました。 ZOZOマッチはまだスタート地点に立ったばかりです。今後もユーザーに価値を提供できるよう、継続的な改善を続けていきます。 ZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味がある方は以下のリンクからぜひご応募ください! corp.zozo.com
アバター
.entry .entry-content ul > li > ul { display: none; } .entry-content td { text-align: left; } はじめに こんにちは、新規事業部フロントエンドブロックの 池田 です。普段はZOZOマッチのアプリ開発を担当しています。2025年6月にマッチングアプリ「 ZOZOマッチ 」をリリースしました。ZOZOマッチにはメッセージ機能があり、この機能を実現するためにGraphQLを用いています。本記事ではFlutterアプリでGraphQLを用いたリアルタイムメッセージ機能の開発の知見と工夫した点をご紹介します。 なお、ZOZOマッチアプリ全体のアーキテクチャや技術構成については、別記事「 ZOZOマッチアプリのアーキテクチャと技術構成 」で詳しく紹介しています。 目次 はじめに 目次 GraphQLとは GraphQLの主な特徴 1. 単一エンドポイント 2. 必要なデータのみ取得 3. 強力な型システム 4. リアルタイム通信のサポート GraphQLの3つの操作タイプ ZOZOマッチでのGraphQLの利用背景 Flutter×GraphQLの実装 Flutterへの導入 主要パッケージ GraphQL Clientの設定 型安全なコード生成 GraphQLでのデータ取得 GraphQLでの送信処理 Subscriptionでのリアルタイム反映 開発で得た知見と工夫点 Optimistic Update(楽観的更新)によるUX改善 実装の流れ アプリバックグラウンド時のSubscription管理 発生する問題 解決策 AWS AppSyncとの統合における課題と解決策 まとめ GraphQLとは GraphQLは、Meta社が公開したAPIのクエリ言語およびランタイムです。REST APIの課題を解決するために開発され、現在では多くの企業で採用されています。 GraphQLの主な特徴 1. 単一エンドポイント REST APIでは複数のエンドポイント( /users 、 /messages など)が存在しますが、GraphQLではすべてのリクエストが単一のエンドポイント(通常は /graphql )に送信されます。 2. 必要なデータのみ取得 クライアントが必要なフィールドを明示的に指定できるため、オーバーフェッチング(不要なデータの取得)やアンダーフェッチング(追加リクエストが必要)を防げます。 # 必要なフィールドだけを指定 query { user(id: "123") { name avatar # この2つのフィールドのみ取得 } } 3. 強力な型システム スキーマによって厳密な型定義が行われ、開発時の型安全性が向上します。また、 GraphQL Playground などのツールで自動的にドキュメントが生成されます。 4. リアルタイム通信のサポート Subscription機能によりWebSocket 1 経由でリアルタイムデータ配信が可能です。これがZOZOマッチのメッセージ機能で重要な役割を果たしています。 GraphQLの3つの操作タイプ GraphQLには主に3つの操作タイプがあります。 操作タイプ 用途 REST APIでの相当 Query データの取得 GET Mutation データの作成・更新・削除 POST/PUT/DELETE Subscription リアルタイムデータの購読 WebSocket/SSE ZOZOマッチでのGraphQLの利用背景 ZOZOマッチではユーザー同士がリアルタイムでメッセージをやり取りする機能があります。マッチングアプリにおいて、メッセージ機能は最も重要な機能の1つであり、以下の要件を満たす必要がありました。 メッセージの送受信がリアルタイムで反映される オフライン時のメッセージもオンライン復帰時に受信できる 既読の管理ができる 従来のREST APIでこれらを実現しようとすると、以下の課題がありました。 リアルタイム性の実装が複雑 : ポーリングでは遅延が発生し、WebSocketの実装は複雑 複数回のAPIコール : メッセージ一覧、ユーザー情報、既読状態などを別々に取得する必要がある オーバーフェッチング : 不要なデータも含めて取得してしまい、通信量が増加 型安全性の確保が困難 : APIレスポンスの型定義を手動で管理する必要がある これらの課題を解決するため、GraphQLを採用しました。GraphQLでは以下の機能により各課題に対応できます。 Subscription : WebSocketベースのリアルタイム通信を標準機能として提供し、リアルタイムでのメッセージの反映を実現 単一エンドポイント : 1回のリクエストで必要なデータ(メッセージ、ユーザー情報、既読状態など)をまとめて取得可能 柔軟なクエリ : クライアント側で必要なフィールドのみを指定して取得でき、通信量を最適化 強力な型システム : スキーマから型安全なコードを自動生成し、開発時の型チェックを実現 メッセージ一覧画面 メッセージ画面 Flutter×GraphQLの実装 Flutterへの導入 FlutterでGraphQLを利用するために、以下のパッケージを導入しました。 graphql_flutter はGraphQLクエリの実行とウィジェットの提供、 graphql_codegen はGraphQLスキーマから型安全なDartコードの自動生成を担当します。 主要パッケージ dependencies : graphql_flutter : ^5.2.1 dev_dependencies : graphql_codegen : ^2.0.0 GraphQL Clientの設定 まず、アプリ全体でGraphQL Clientを利用できるように設定します。WebSocketによるSubscriptionをサポートするため、HTTPとWebSocketの両方のリンクを設定しています。また、 AuthLink を使用してBearerトークンによる認証を管理し、すべてのGraphQLリクエストに認証情報を自動的に付与しています。WebSocketの実装に関しては後述で詳細に記載しています。 static Future<GraphQLClient> _initializeGraphQLClient(Ref ref) async { final dio = ref.watch(dioProvider); final buildConfig = ref.watch(buildConfigProvider); final authLink = AuthLink( getToken: () async { final token = await _getAccessToken(ref); return 'Bearer $token' ; }, ); final dioLink = Link.from([DioLink(buildConfig.graphQlEndpoint, client: dio)]); final webSocketLink = _initializeWebSocketLink(ref); final link = Link.split( (request) => request.isSubscription, // Subscriptionの場合はWebSocketLink、それ以外(Query/Mutation)はHTTP Linkを使用 await webSocketLink, authLink.concat(dioLink), ); final client = GraphQLClient( cache: GraphQLCache(store: InMemoryStore()), link: link, ); return client; } アプリのエントリーポイントでProviderとして設定します。 void main() { runApp( GraphQLProvider( client: _initializeGraphQLClient(), child: MyApp(), ), ); } 型安全なコード生成 graphql_codegenを用いて、GraphQLのスキーマから自動的にDart型を生成します。 GraphQLスキーマファイルの配置 lib/ ├── graphql/ │ ├── schema.graphql # サーバーのスキーマ │ └── queries/ │ └── messages.graphql # クエリ定義 クエリの定義 (messages.graphql) query ListMessages($channelId: String!, $limit: Int, $nextToken: String) { listMessages(channelId: $channelId, limit: $limit, nextToken: $nextToken) { items { ...MessageFields } nextToken } } mutation CreateMessage($channelId: String!, $kind: String!, $body: String!) { createMessage(channelId: $channelId, kind: $kind, body: $body) { channelId action message { ...MessageFields } } } subscription OnMessageModified($channelId: String!) { onMessageModified(channelId: $channelId) { action channelId message { ...MessageFields } } } コード生成の設定 (build.yaml) targets: $default: builders: graphql_codegen: options: schema: lib/graphql/schema.graphql queries_glob: lib/graphql/queries/**.graphql output_directory: lib/graphql/generated コード生成の実行 flutter pub run build_runner build --delete-conflicting-outputs これにより、型安全なクエリ実行用のクラスが自動生成されます。自動生成によってタイプミスのリスクを減らし、フォーマットを効かせることができます。また、Schemaが適切でなかった場合は自動生成の際にエラーが発生するため、問題を早期に検知できるメリットがあります。 GraphQLでのデータ取得 ZOZOマッチではマッチングしたお相手が表示されるメッセージ一覧画面とそこから遷移できるメッセージ画面でQueryを使ってデータを取得しています。 以下のコードは、メッセージ一覧を取得する際の実装例です。 Query$ListMessages$Widget は前述のコード生成により作成された型安全なウィジェットで、GraphQLのQueryを簡潔に実行できます。 Query$ListMessages$Widget( options: Options$Query$ListMessages( fetchPolicy: FetchPolicy.networkOnly, variables: Variables$Query$ListMessages( channelId: channelId, ), ), builder: (result, {fetchMore, refetch}) { if (result.data == null && result.isLoading) { return const CommonLoadingView(); } if (result.hasException) { return CommonErrorView( onRetry: () async { await refetch?.call(); }, ); } MessageListWidget(); } ); このコードでは、 Options$Query$ListMessages でクエリを設定し、 fetchPolicy に networkOnly を指定して常に最新のデータをサーバーから取得します。また、 Variables$Query$ListMessages で型安全にGraphQL変数( channelId )を渡しています。 builder 内では、クエリの実行状態に応じて3種類のUIを表示しています。 result.isLoading がtrueの場合はローディング画面を表示し、 result.hasException がtrueの場合はエラー画面を表示します。エラー画面では refetch を呼び出すことでリトライ機能も提供しています。データ取得が成功した場合は、メッセージリストウィジェットを表示します。 graphql_flutterパッケージが提供するWidgetベースのAPIを利用することで、GraphQLのクエリ実行とFlutterのUI更新が自然に統合されています。また、 fetchMore や refetch などの機能も標準で提供されるため、ページネーションやデータの再取得も簡単に実装できます。 GraphQLでの送信処理 次にMutationを使ったメッセージ送信処理を紹介します。GraphQLのMutationは、データの作成・更新・削除といった副作用を伴う操作に使用します。以下はメッセージ送信時の実装例です。 Mutation$CreateMessage$Widget( options: WidgetOptions$Mutation$CreateMessage( onCompleted: (_, _) async { // メッセージ送信完了時の処理 }, onError: (error) async { if (error == null ) { return ; } // エラー時の処理 logger.error( 'Error creating message: $error' ); }, ), builder: (runMutation, result) { MessageBarWidget( onSubmit: (text) async { runMutation( Variables$Mutation$CreateMessage( channelId: channelId, body: text, ), ); } ); } ); このコードでは、 Mutation$CreateMessage$Widget でメッセージ送信を実装しています。 onCompleted で送信成功時、 onError でエラー時の処理を定義します。 builder から提供される runMutation 関数を呼び出すことでMutationを実行します。 MessageBarWidget のテキスト送信時に、 Variables$Mutation$CreateMessage を使って型安全に必要なパラメータ( channelId 、 body )を渡しています。この実装により、ユーザーがメッセージを入力して送信ボタンを押すと、GraphQL Mutationが実行されサーバーにメッセージが送信されます。 Mutationの実行は非同期で行われ、送信中の状態は result オブジェクトから取得できます。これにより、送信中のローディング表示や、送信失敗時のリトライ機能なども簡単に実装できます。 Subscriptionでのリアルタイム反映 メッセージ一覧へのマッチングの反映やお相手からメッセージの受信をリアルタイムで反映する際にはSubscriptionを用います。GraphQL Subscriptionは、WebSocketを使用してサーバーからクライアントへリアルタイムでデータをプッシュする仕組みです。前述したようにWebSocketLinkの分岐の追加が必要となります。 ZOZOマッチでは、QueryとSubscriptionを組み合わせて使用しています。Queryで初期データを取得し、その後Subscriptionでリアルタイムの変更を受信するという役割分担です。この設計には以下の理由があります。 初期データの確実な取得 : Queryで画面表示時に必要な全データを一度に取得できる エラーハンドリングの明確化 : 初期データ取得とリアルタイム更新でエラー処理を分離できる ページネーション対応 : 過去のメッセージ取得などにはQueryが適している 以下はメッセージ一覧の変更(新規マッチングやメッセージ受信)を購読する実装例です。 useEffect(() { WidgetsBinding.instance.addPostFrameCallback((_) { // メッセージ一覧を取得した後に、Subscriptionを購読する subscription = graphQLClient .subscribe( Options$Subscription$OnChannelModified( variables: Variables$Subscription$OnChannelModified( userId: userId, ), ), ) .listen((event) { // 取得したデータをUIに反映させる }); }); return () async { await subscription?.cancel(); }; }, []); このコードでは、 useEffect フックを使用してウィジェットのライフサイクルに合わせたSubscriptionを管理しています。 graphQLClient.subscribe メソッドで Options$Subscription$OnChannelModified を購読し、ユーザーIDに関連するチャンネルの変更を監視します。 listen メソッドのコールバック内で、サーバーから送信されたイベントを受信し、UIに反映させます。これにより、新しいマッチングが成立したり、お相手からメッセージを受信したりした際に、リアルタイムでメッセージ一覧が更新されます。 重要な点として、 useEffect のクリーンアップ関数で subscription?.cancel() を呼び出すことで、ウィジェットが破棄される際に適切にSubscriptionを解除しています。これにより、メモリリークを防ぎ、不要なWebSocket接続を維持しないようにしています。 開発で得た知見と工夫点 Optimistic Update(楽観的更新)によるUX改善 メッセージ送信時のユーザー体験を向上させるため、Optimistic Update(楽観的更新)を実装しました。これは、サーバーからの応答を待たずユーザーの操作を即座にUIへ反映させる手法です。ユーザーがメッセージを送信した際にリクエストの結果を待ってからUIに反映するとUIに反映されるまでの時間が長くなってしまうため不安に感じてしまうことがあります。 実装の流れ 即座のUI更新 : ユーザーがメッセージ送信ボタンをタップすると、Riverpodで管理しているメッセージリストに一時的なメッセージオブジェクトをローカルで追加し、即座にUIへ表示 バックグラウンドでの送信 : 並行してGraphQL Mutationでサーバーへメッセージを送信 データの同期 : Subscription経由でサーバーから正式なメッセージデータを受信後、一時的なメッセージを置き換え この実装により、ネットワーク遅延と関係なく、ユーザーは自分の送信したメッセージが即座に表示されるため、操作感が向上します。送信失敗時は、エラー状態を表示し、失敗したメッセージ文をメッセージバーへ戻す動作にしています。 楽観的更新に対応済みのメッセージ送信 楽観的更新なしのメッセージ送信 アプリバックグラウンド時のSubscription管理 モバイルアプリ特有の課題として、アプリがバックグラウンドに移行した際のWebSocket接続の扱いがあります。iOSやAndroidは、バッテリー消費を抑えるため、バックグラウンドアプリのネットワーク接続を制限します。これにより、GraphQL Subscriptionで使用しているWebSocket接続が自動的に切断されてしまいます。 発生する問題 アプリをバックグラウンドにすると、WebSocket接続が切断される この間に送信されたメッセージは、Subscriptionでは受信できない ユーザーがプッシュ通知でメッセージを確認してアプリに戻っても、UIが更新されていない 解決策 この問題に対して、次の2つのアプローチで対応しました。 自動再接続の実装 : autoReconnect: true の設定により、アプリがフォアグラウンドへ復帰した際、WebSocket接続を自動的に再開し、Subscriptionの購読を復帰 データの再取得 : アプリのライフサイクルイベントを監視し、フォアグラウンド復帰時に最新のメッセージリストをQueryで再取得 class MessageListScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { useOnAppLifecycleStateChange((previous, current) { if (current == AppLifecycleState.resumed) { // フォアグラウンド復帰時に最新データを取得 refetch?.call(); } }); // 以下、通常のWidget実装 } } この実装により、ユーザーがアプリに戻った際には常に最新の状態が表示されるようになりました。 AWS AppSyncとの統合における課題と解決策 バックエンドで AWS AppSync を利用している場合、標準のWebSocketLinkではAWS AppSync独自の認証形式に対応できないという課題 2 があります。AWS AppSyncはWebSocket接続時に特別な形式の認証ヘッダーを要求するため、カスタムのWebSocketLink実装が必要になりました。 AWS AppSyncは認証情報をBase64エンコードしてURLパラメータとして渡し、リクエストボディも独自の形式で送信します。これに対応するため、CustomWebSocketLinkを実装しました。 /// [WebSocketLink]をベースにした、カスタムのWebSocketLink class CustomWebSocketLink extends Link { CustomWebSocketLink({ required this .getToken, required this .realTimeEndpoint, required this .host, this .subProtocol = GraphQLProtocol.graphqlWs, }); final String subProtocol; final Future< String ?> Function () getToken; final String realTimeEndpoint; final String host; Future< void > connectOrReconnect() async { final token = await getToken(); final authHeader = { 'Authorization' : token, 'host' : host}; final encodedHeader = base64.encode(utf8.encode(jsonEncode(authHeader))); final url = '$realTimeEndpoint:443/graphql/realtime?header=$encodedHeader&payload=e30=' ; await _socketClient?.dispose(); _socketClient = SocketClient( url, config: SocketClientConfig( serializer: _AppSyncRequest(authHeader: authHeader), inactivityTimeout: const Duration(minutes: 2), queryAndMutationTimeout: const Duration(milliseconds: 5000), ), onMessage: (message) { logger.info( 'GraphQL Subscription message: $message' ); }, ); } } /// AWS AppSync固有の認証形式に対応するためのリクエストシリアライザー /// 参考: https://github.com/zino-hofmann/graphql-flutter/issues/682#issuecomment-759078492 class _AppSyncRequest extends RequestSerializer { const _AppSyncRequest({required this .authHeader}); final Map< String , dynamic > authHeader; @override Map< String , dynamic > serializeRequest(Request request) => { 'data' : jsonEncode({ 'query' : printNode(request.operation.document), 'variables' : request.variables, }), 'extensions' : { 'authorization' : authHeader}, }; } SocketClient の設定では、 inactivityTimeout で非アクティブ時のタイムアウトを設定します。 onMessage コールバックでSubscriptionのメッセージ受信状況をログ確認でき、デバッグが容易になります。 まとめ 本記事ではFlutterアプリにおけるGraphQLの実装を紹介しました。GraphQLの導入によってリアルタイムのメッセージ機能の実現ができました。GraphQLの利用を検討している方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com WebSocketは、クライアントとサーバー間で双方向通信を可能にするプロトコルです。HTTPと異なり、一度接続を確立すると、サーバーからクライアントへ任意のタイミングでデータを送信できます。 ↩ graphql_flutterのGitHubにissueが上がっています。 https://github.com/zino-hofmann/graphql-flutter/issues/682 ↩
アバター
はじめに こんにちは、新規事業部フロントエンドブロックの 大野純平 です。2025年度に新卒入社し、現在のチームに配属されました。2025年6月に新規事業としてリリースされた、全身見える直感型マッチングアプリ「ZOZOマッチ」のアプリ開発を担当しています。「ZOZOマッチ」は、ZOZOとして初めてFlutterを採用したモバイルアプリです。 zozomatch.jp ZOZOマッチの開発では、様々な状態を再現する作業に多くの時間を費やしており、効率化が課題となっていました。 本記事では、この課題を解決するデバッグメニューの作り方と効果的な機能を紹介します。 目次 はじめに 目次 背景・課題 解決策:デバッグメニューの導入 デバッグメニューの呼び出し方法 実装した機能と具体的な活用場面 1. ログ表示機能 2. 画面遷移デバッグ 3. SharedPreferencesデバッグ 4. API通信モック機能の切り替え 5. 時刻デバッグ機能 まとめ 背景・課題 ZOZOマッチの開発業務の中で、コードレビューやバグ修正時に特定の状態を再現する作業が頻繁に発生しています。マッチングアプリという性質上、審査ステータス・写真の登録状態・時刻に依存するイベント・チュートリアルの表示状態など、非常に多くの状態が複雑に絡み合っています。これらの状態を手動で再現する作業は毎回10分以上かかり、エンジニアの集中力を削ぐだけでなく、状態の再現ミスによるバグの見逃しリスクも高まっていました。 アプリの画面数が100を超える規模となっており、特定の画面にたどり着くまでに複数の画面を経由します。さらに、期間限定キャンペーンや特定時刻の機能など、時間に依存したイベントのテストでは、その時刻になるまで待機するかシステム時刻を変更していました。 これらの非効率な作業により、本来の開発業務に集中できる時間が削られ、チーム全体の生産性低下を招いていました。 解決策:デバッグメニューの導入 これらの課題を解決するため、アプリ内にデバッグメニューを実装しました。 デバッグメニューは、開発・テスト時のみ使用できる特別な画面です。本番環境では動作しないため、セキュリティリスクを排除しています。この画面から、通常のユーザー操作では再現困難な様々な状態をUIから手軽に設定・再現できます。今回はそのデバッグメニューの中から効果的であった機能を紹介します。 デバッグメニューの呼び出し方法 デバッグメニューへのアクセス方法として、以下の2つの方法を実装しています。「端末を振る」については、 shake_gesture パッケージを使用し、実現しました。 // 1. 端末を振る(ShakeGesture) ShakeGesture.registerCallback( onShake: () { // 遷移する動作 }, ); // 2. 同じタブを複数回連続タップ(5回の例) if (tapCount.value == 5) { // 遷移する動作 } 実装した機能と具体的な活用場面 1. ログ表示機能 アプリ内のイベントやエラーを時系列で確認できる機能です。開発中の問題を素早く特定するために、 Talker を採用しました。 Talkerを選んだ理由は次のとおりです。 専用のログ画面があり、アプリ内で確認できる ログレベル(debug、info、warning、error)を色分け表示でき、視認性が高い 例外を自動で捕捉して記録できる Dio、Riverpod、BLoCなどの主要パッケージと統合できる フィルタリングで目的のログをすぐに絞り込める Talker専用画面については、パッケージのREADMEに掲載されている Web Demo をご覧ください。 デバッグメニューでは、次のログを表示します。 アプリケーション動作 各画面のbuild実行とレンダリング状況 画面遷移時のルート変更とスタックの変化 外部サービス連携 Google Analyticsのユーザー行動イベント(表示・いいね・画面表示) 送信の成功/失敗と送信内容の事前検証 FirebaseのInstallation ID設定・更新 各エンドポイントの接続状態と設定値 認証/トークン処理の進行状況 HTTP 通信 リクエスト(ボディ・ヘッダー・パラメータ) レスポンス(ボディ・ステータスコード・処理時間) 設定・環境情報 アプリ名、バージョン、ビルド番号、パッケージ情報 環境(開発/本番)の切り替え状況 デバッグリポジトリの使用状況 ログ画面はリアルタイムで更新され、新しいログが上部に追加されます。ログレベルやキーワードで絞り込みでき、必要な情報にすばやく到達できます。テキストとして共有する機能もあり、チーム内の情報共有が容易です。非エンジニアでも簡単にログを共有できる点は大きなメリットとなりました。 Talkerは特に他チームとの連携に役立ちました。具体的には、QAチームやデザイナーチームから不具合報告を受けた際、再現時のログをチケットに添付してもらう運用にしました。これにより、ログを確認するだけで「バックエンドのレスポンスが誤っているのか」「アプリ実装が間違っているのか」を即座に判別でき、調査の初動時間を大幅に短縮できました。こうした迅速な対応を実現できたのは、非エンジニアでも簡単にログを共有できる仕組みがTalkerに備わっていたからです。 アプリチーム単体でも多くの利点がありました。Crashlyticsとの連携設定が容易で、Google Analytics導入時にはイベント送信の正しさを即座に確認でき、デバッグメニューでは難しい検証を補完できました。さらに、常に画面のルーティング情報が出力されるため、階層の深い画面にいても自分の位置を即座に把握できる点も便利でした。 この機能により、デバッグ時間の短縮、予期しないエラーの早期発見、API通信の可視化、状態管理の理解促進、分析イベントの検証を実現し、開発効率が大きく向上しました。 2. 画面遷移デバッグ アプリ内の任意の画面へワンタップで直接遷移できる機能です。検索と履歴を備え、開発中の移動コストを下げます。ZOZOマッチの画面数は100を超えるため、通常操作では深い階層(例:オンボーディング途中の全身写真を登録する画面)への到達が手間でした。 この機能はZOZOマッチの遷移で採用している go_router の仕組みを応用することで実現しています。この記事では詳細を割愛しますが、RouteConfigurationから定義済みのパス情報を抽出する仕組みにすることで、新規画面の追加時も追加実装が不要な作りにしています。 オンボーディング途中の全身写真を登録する画面 画面遷移デバッグでは様々な機能が搭載されています。実際の画面をもとに、機能詳細を解説していきます。 画面遷移デバッグ画面 画面IDをテキスト入力して遷移したい画面を検索できる機能。ZOZOマッチには100種類以上の画面があるため、この検索機能で目的の画面へすぐアクセスが可能。 直近でデバッグメニューから遷移した画面の履歴を表示する機能。特定の画面へ繰り返し遷移する際に履歴から開けるので、毎回検索する手間を省ける。 全てのルートをリスト形式で表示する。この一覧をタップすると該当の画面へ1ステップで移動ができる。 このツールにより、深い階層の画面へ数秒で移動でき、開発時間を短縮できました。全画面を容易に確認できるため、リグレッションの早期発見にも有効です。 現在の課題は、ユーザー固有の画面(例:相手のプロフィール)へ遷移する際に、ユーザーIDなどのパラメータを指定できず、動的ルートに対応できていない点です。今後は、パラメータ入力に対応した遷移デバッグの実装を検討しています。 3. SharedPreferencesデバッグ SharedPreferencesに保存されているデータの確認・編集機能です。アプリ内の各種フラグや設定値を管理するSharedPreferencesの値を、開発中に簡単に確認・変更できるデバッグ画面として実装しています。 ZOZOマッチでは、チュートリアル表示フラグやIn-App Reviewのリクエスト制御など、通常は特定の条件でしか変更されない値が多数存在します。これらの値を手動で操作できることで、開発・テスト効率が向上しました。 例えば、チュートリアルの再確認では、一度表示した後は通常再表示されないためデザイン変更や修正後の確認が難しい状況でしたが、フラグを任意のタイミングでリセットすることにより再表示が可能になりました。また、In-App Reviewのテストでは、同一ユーザーには一定期間レビューを表示しないフラグを制御できるため、表示履歴を削除し、開発中に何度でも表示をテストできるようになりました。 この機能により、検証のリードタイムを短縮し、開発サイクルを高速化できます。 4. API通信モック機能の切り替え バックエンド開発と並行してフロントエンドを進める際、未実装APIや再現しづらいレスポンスが課題でした。本番用Repositoryとダミーデータを返すDebugRepositoryを切り替え、API通信をモック化できるようにしました。切り替えはデバッグメニューから行えます。 実装アプローチは以下のとおりです。 DebugRepositoryは本番Repositoryを継承し、必要なメソッドのみをオーバーライドしています。 // 本番用Repository class UserRepository { Future<User> getUserById( String id) async { final response = await apiClient.get( '/users/$id' ); return User.fromJson(response.data); } } // Debug用Repository class DebugUserRepository extends UserRepository { @override Future<User> getUserById( String id) async { // ダミーデータを返す return User( id: id, name: 'テストユーザー' , age: 25, ); } } Riverpodによる動的切り替えでは、デバッグフラグに応じてRepositoryを実行時に切り替えます。 // Providerでの使用例 @riverpod UserRepository userRepository(Ref ref) => selectRepositoryByBuildMode( // 本番用のRepository () => UserRepository( apiClient: ref.watch(apiClientProvider), ), // デバッグ用の設定 debugConfig: ( factory : () => DebugUserRepository(), enabled: ref.watch(isDebugModeProvider), ), ); 全てのRepositoryで統一的に切り替えられるよう、 selectRepositoryByBuildMode という共通ヘルパーを用意しています。 T selectRepositoryByBuildMode<T>( T Function () releaseOrProfile, { ({T Function () factory , bool enabled})? debugConfig, }) { // リリースビルドでは常に実APIを使用 if (!kDebugMode) { return releaseOrProfile(); } // デバッグビルドでフラグがONの場合はDebugRepositoryを使用 if (debugConfig != null && debugConfig.enabled) { final debugInstance = debugConfig. factory (); return debugInstance; } // それ以外は実APIを使用 return releaseOrProfile(); } この仕組みにより、次が可能になります。 ビルドモード連動 - kDebugMode でリリースビルドを判定し、本番では常に実APIを使用 動的切り替え - デバッグビルドでは画面上のトグルで即座に切り替え 可視化 - 現在使用中のRepositoryをコンソールで確認 また、デバッグメニューに統合したことで、画面上のトグルで手早くモック機能を切り替え、本番APIとモックデータを用途に応じて使い分けられます。 API通信モックを導入したことで、APIの実装完了を待たずにUIを実装・確認でき、バックエンドとフロントエンドの並行開発が可能になりました。また、空リスト・大量データ・特殊文字列などのデータパターンを簡単に再現でき、動作確認が容易になりました。 バグ修正時は、DebugRepositoryでレスポンスを任意に制御し、エラー状態を即時に再現・検証できます。 5. 時刻デバッグ機能 ZOZOマッチでは特定時刻の機能が多く実装されており、その動作確認が開発の大きな課題となっていました。チャンスタイム 1 やレコメンド更新 2 など、特定の時刻でのみ動作する機能をテストするために、従来は端末の時刻設定を変更する必要がありました。しかし、これによりバックエンドから取得できるデータとの整合性に問題が発生しました。 この問題を解決するため、サーバー時刻ヘッダー機能を導入しました。すべてのAPIレスポンスに含まれる date ヘッダーからサーバー時刻を取得し、端末時刻との差分(オフセット)を計算します。これにより、端末の時刻設定に依存しない正確な時刻を取得できるようになりました。 // 従来の実装(端末時刻に依存) final now = DateTime.now(); // 端末の時刻設定に影響される // 新しい実装(サーバー時刻を使用) final now = serverTimeClock.now(ref); // サーバー時刻を基準とした正確な時刻 デバッグビルドでは、特別なヘッダー x-znm-debug-timer に任意の時刻を指定できます。サーバー側はそれを受け取り、 date ヘッダーとは別で x-znm-debug-timer-response に特殊な時刻を設定してくれる仕組みになっています。この仕組みを利用し、アプリ内だけで有効な仮想時刻を使い、特定時刻の機能を検証します。任意の時刻はデバッグメニューから容易に切り替えでき、テスト効率が向上します。 例えば、「20:20」など特定の時刻にのみ動作する機能のテストに活用することにより、任意のタイミングで動作確認が可能になりました。これにより、端末時刻の変更に伴う副作用を排除し、特定時刻の機能を安全かつ効率的に検証できるようになりました。 まとめ 本記事では、Flutterのデバッグメニュー構築を紹介しました。デバッグメニューは、単なる効率化を超えて、チームの働き方と開発体験を変える基盤になり得ます。今後も実運用のフィードバックを取り込み、より多くの検証・開発フローで最適化を進めていきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com 20時20分になるとその日の新着ユーザーを無料で限定人数いいねできる機能のことです。 ↩ 特定時刻になると、ホームタブに表示されるユーザーが更新されます。 ↩
アバター
.entry .entry-content ul > li > ul { display: none; } .entry-content td { text-align: left; } はじめに こんにちは、計測プラットフォーム開発本部SREブロックの纐纈です。 2025年10月6日〜9日にダブリンで開催されたSREcon25に参加してきました。本記事では、現地の様子と気になったセッションについて報告いたします。 目次 はじめに 目次 SREconとは 現地の様子 セッション紹介 SRE for AI and AI for SRE インシデントの概要 学んだこと Why Risk Management Requires Taking Risks: A Practical Guide to Getting SRE Teams AI-Ready AI活用の段階的アプローチ 具体的な成果 弊チームへの適用 Performance Consistency in the Cloud パフォーマンス変動の主な原因 緩和策 学んだこと CPU Utilization: The Hidden Cost of Running Hot 高CPU使用率の隠れたコスト 最適化の戦略 MLOps 2025: A Journey into the Past and the Future MLOpsの進化:過去から現在へ 教訓 現在の課題 未来のトレンド 2025年のベストプラクティス 学んだこと 最後に SREconとは SREcon は、USENIXが主催するSite Reliability Engineering(SRE)に関する国際カンファレンスです。世界中からSREエンジニアやシステム運用に携わるエンジニアが集まり、最新のプラクティスや知見を共有します。 今回の特徴としては、AI/MLシステムの運用に関するセッションが増えており、SREの役割が従来のインフラ運用からAIシステムの品質保証やリスク管理にまで拡大していることがうかがえました。また、クラウドネイティブなアーキテクチャの普及に伴い、Kubernetesやマイクロサービスの運用に関する具体的な事例も多く紹介されました。 現地の様子 今回の会場はDublin Convention Centreで、3つのセッションルームと展示エリアがありました。 SREcon開催前の様子 比較的小さな規模のカンファレンスですが、その分、参加者同士の交流が活発な印象を受けました。また、ヨーロッパやアメリカからの参加者が多く、後から参加者リストを確認したところ、日本からの参加者は私1人だけのようでした。SREconは年に数回開催されており、次回は2026年3月にアメリカのシアトルで開催される予定です。 セッション紹介 ここからは、今回参加したセッションの中から特に印象に残ったものをいくつか紹介します。 SRE for AI and AI for SRE まず、AnthropicのNiall Murphy氏によるセッションでは、2024年8月に発生したClaude AIの品質劣化インシデントについて詳細な事後分析が共有されました。このインシデントは、モデル自体の問題ではなく、サービング基盤のシステムの問題がモデルの品質低下を引き起こしたという、非常に興味深い事例でした。 インシデントの概要 8月上旬から、XやRedditで「Claudeが以前より性能が落ちた」という報告が急増しました。しかし、調査を進めても以下のような状況でした。 モデルの変更は行っていない 標準的な品質評価(SWE-bench等)では問題なし モニタリングアラートは一切発報していない 再現が非常に困難(リクエストのパターンやトラフィック状況によって動的に変化) 最終的に、3つの独立したバグが複合的に影響していたことが判明しました。 コンテキストウィンドウルーティングエラー :100万トークン対応のリリース準備時に、短いコンテキストのリクエストを長いコンテキスト用インフラにルーティングした際、正しく動作しなかった。 出力破損バグ :トークン生成処理の最適化により、特定の状況下で確率分布が誤った値となった。極めて低い確率のトークンが選択される(中国語や英語のクエリに対してタイ語のトークンが返される等)。 Top-K XLAコンパイラバグ :混合精度の演算とコンパイラバグの組み合わせで、修正を試みた結果さらに悪化するという状況も発生。 学んだこと このセッションから得られた最も重要な点は、 品質がML/AIシステムにとって適切なSLOである ということです。モデルが正しく動作しなければ、可用性やレイテンシーは意味を持ちません。また、システムの問題が品質に影響を与えることを前提に、SREチームも品質のモニタリングが必要だと強く認識しました。 さらに、プライバシーポリシーとデバッグのトレードオフについても考えさせられました。Anthropicはユーザーのプロンプトと応答を保存しないポリシーを持っているため、「英語のクエリに対してタイ語のトークンが返される」といった明らかな異常を自動検出できませんでした。 弊チームでも、サイズ推奨システムをはじめとしたMLシステムを運用しています。パイプラインの変更がモデルの品質へ影響を与える可能性を常に意識し、品質メトリクスを重要なSLOとして扱うべきだと感じました。 Why Risk Management Requires Taking Risks: A Practical Guide to Getting SRE Teams AI-Ready NVIDIAのGeForce NOWプラットフォームのSREチームによる、AIを活用してインシデント対応時間を劇的に短縮した事例の紹介でした。グローバルで35拠点、25,000台以上の物理サーバー、100,000個以上のGPUを26人のチームで運用している中で、AIをどのように活用しているかが具体的に語られました。 AI活用の段階的アプローチ GeForce NOWチームは、AIの採用を4つのレベルで段階的に進めていました。 「 Why Risk Management Requires Taking Risks 」 p.5より引用 レベル1:要約(Summarization) プレイブックやドキュメントの要約 最も簡単に始められるが、ドキュメントの品質に大きく依存 80%正しい回答では信頼を失うため、実は最も難しい 例:プレイブックに「サービスAを再起動」と書かれているのに、「サービスBを再起動」と要約された場合、障害が悪化してしまう 一度でも誤った情報を提供すると、チーム全体がAIの回答を信頼しなくなり、AI導入の意味が薄くなる レベル2:情報の統合(Synthesis) リアルタイムでメトリクス、SLO、システム状態を集約 「ブルガリアで何が起きているか?」といった質問に対し、1分以内で全情報を提示 20分かかっていたコンテキスト収集が1分未満に短縮 レベル3:評価(Evaluation) システムの状態を評価し、問題の可能性がある箇所を提案 ベイジアンネットワーク を活用した根本原因の分析 コンポーネント間の依存関係を確率とともにマッピング 与えられた障害から最も可能性の高い根本原因を推定 観測可能性のギャップも可視化される レベル4:アクション実行(Action-Taking) AIが実際にシステムに対して修復アクションを実行 サービス再起動などの安全な操作から開始 人間の承認ステップを組み込んで安全性を担保 具体的な成果 週次サービスレビューの準備時間が2〜3時間から5分へ短縮されました。また、エグゼクティブからの問い合わせにSREチームを経由せずAIが回答できるようになりました。 弊チームへの適用 弊チームでは、そこまで多くのサーバーを運用していないため、GeForce NOWのような大規模なAI導入は現時点ではコストに見合わないと考えています。しかし、 レベル1の要約 や レベル2の情報統合 は、ドキュメントの整備と合わせてすぐにでも試してみたいと感じました。特にインシデント対応時の初動で、過去の類似インシデントやプレイブックを素早く参照できることは大きな価値があると思います。 Performance Consistency in the Cloud AWSによる、クラウド環境でのレイテンシー変動の原因と対策についてのセッションでした。ほとんどのアプリケーションではレイテンシーの変動は許容範囲ですが、高頻度取引やゲーム、ベンチマークなど、マイクロ秒単位の一貫性が重要なサービスにとっては致命的な問題となります。 「 Performance Consistency in the Cloud 」 p.5より引用 パフォーマンス変動の主な原因 1. マルチテナンシー(最大の要因) クラウドプロバイダーは公平性を保つため、リソースへ制限を実装しており、これが変動の最大の原因となっています。AWS Nitroは専用ハードウェアに制御をオフロードすることで、カスタマーのCPUサイクルを消費せずに管理しています。 2. 共有リソース 複数のテナントが同じCPUソケットを共有する場合、L3キャッシュやメモリバスが共有されます。CPUサーマルとパワー管理の制限も共有されるため、一方の負荷が高まると全体の周波数が低下し、他のテナントのパフォーマンスも影響を受けます。AWS Nitroハイパーバイザーは、この影響を1%未満のオーバーヘッドに抑えています。 3. ストレージ マネージドブロックストレージは、ローカルに見えても実際にはネットワーク経由でアクセスされます。パスの長さやネットワークの輻輳によって変動が発生します。AWS SRD(Scalable Reliable Datagram)では、複数のパスを使用することで輻輳を回避し、レイテンシーを改善しています。 4. ネットワーク データセンター内の配置によって異なるネットワークパスが選択されます。デフォルトの配置は暗黙的であるため、Placement Groupsを使用して同じネットワークスパインに配置することで改善できます。 緩和策 バースト可能なインスタンスを避ける より大きなインスタンスを選択してリミットを上げる 専用ホストを使うことでCPUソケット全体を占有 ARM64 CPUは固定周波数で予測可能性が高い ローカルNVMe SSDの使用:ただしエフェメラル(揮発性)の制約あり 学んだこと 現状弊チームでは、このようなレイテンシーの変動はあまり問題になっていません。しかし、将来的にリアルタイム性が重要なサービスを運用する場合、今回の知見を活かしたインスタンス選定やアーキテクチャ設計をしたいと感じました。 CPU Utilization: The Hidden Cost of Running Hot GitHubのスピーカーによる、高いCPU使用率で運用することによる隠れたコストについてのセッションでした。従来の「CPU使用率を80%以下に保つ」という経験則は必ずしも正しくないことが示されました。ワークロードによって最適な使用率は異なります。 なお、このセッションはこちらのブログ記事を元にされています。 github.blog 高CPU使用率の隠れたコスト 1. レイテンシーの劣化 キューイング理論により、使用率の上昇に伴いレイテンシーは非線形に増加します。CPU使用率90%以上では、わずかなトラフィック増加でも大きなレイテンシースパイクを引き起こします。特にP99/P999のテールレイテンシーが最初に影響を受けます。 2. 障害時のバッファの欠如 障害が発生したインスタンスからのトラフィックを吸収する余裕がなく、カスケード障害が発生しやすくなります。また、オートスケーリングの遅延が致命的になります。 3. デプロイメントリスク ローリングデプロイメントは一時的な容量損失を引き起こすため、高使用率での運用はデプロイメント起因の障害リスクを高めます。 4. 運用オーバーヘッド 常に容量の問題へ対処する必要があり、アラート疲れが発生します。また、パフォーマンス調査の余裕がなくなります。 最適化の戦略 サービスのレイテンシー/使用率カーブを理解する レイテンシーSLOに基づいて目標使用率を設定する リクエストシェディング/ロードシェディングを実装 平均だけでなくP99や分布を監視する 所感として、GitHubのような大規模サービスでは、コスト削減のために高いCPU使用率で運用することが多いと思いますが、その場合でもレイテンシーや障害リスクを十分に考慮する必要があると感じました。弊チームではまだそこまで高い使用率で運用していませんが、将来的にスケールする際には今回の知見を活かして適切なバランスを取っていきたいと思います。 MLOps 2025: A Journey into the Past and the Future 最後にZalandoのAlejandro Saucedo氏によるセッションでは、MLOpsの過去10年間の進化を振り返り、現在の課題と今後の展望について語られました。特に印象的だったのは、MLOpsの基本原則は変わらないものの、LLMやエッジMLといった新しい技術トレンドに対応するための進化が求められているという点でした。 「 SRE for ML; A Journey into the Past & the Future 」 p.8より引用 MLOpsの進化:過去から現在へ 初期のMLOps(2015-2020) Jupyter Notebookがそのまま本番環境で動いていた モデルのデプロイは手動で、再現性の確保が困難 モニタリングは限定的で、モデルの劣化を検知できない データのバージョン管理も不十分 現代のMLOps(2020-2025) 標準化されたパイプライン(MLflow、Kubeflowなど) モデルレジストリによるバージョン管理 自動再トレーニングの仕組み Feature Storeによる特徴量の一元管理 ML特化のモニタリング(データドリフト検知など) 教訓 1. ソフトウェアエンジニアリングの原則が適用できる モデルやデータにもバージョン管理が必要であり、MLパイプラインにもCI/CDが適用できます。また、モデルのコードにもコードレビューやテストが必要です。 2. モニタリングは極めて重要 データドリフト(入力データの分布変化)、モデルパフォーマンスの劣化、特徴量の分布シフト、予測品質の経時的変化など、ML特有の監視項目があります。 3. データの問題は永遠に続く データ品質の問題、ラベリングの課題、データのバージョニング、プライバシーとコンプライアンスなど、データに関する課題は尽きません。 4. モデルのデプロイ後も運用が続く 本番環境でのモニタリング、A/Bテストのフレームワーク、段階的なロールアウト、ロールバック機能など、デプロイ後の運用が重要です。 現在の課題 スケール 大規模データセットでのトレーニング:数TB〜数PB規模のデータを効率的に処理する必要がある 高スループット・低レイテンシーでのサービング:数千〜数万リクエスト/秒を数ミリ秒以内で処理 コスト管理:GPU利用料金の高騰により、学習コストとサービングコストの最適化が重要 リソースの最適化:限られたGPU/TPUリソースを複数チームで効率的に共有 複雑性 複数モデルを組み合わせたシステム:推奨システムでは複数のモデルを連携させる必要があり、依存関係の管理が複雑 モデルアンサンブル:精度向上のために複数モデルの予測を組み合わせるが、レイテンシーとのトレードオフ オンライン学習:リアルタイムでモデルを更新する仕組みでは、データの一貫性やモデルの安定性が課題 リアルタイム特徴量:ストリーミングデータから特徴量を計算し、推論へ即座に利用する必要がある ガバナンス モデルの説明可能性:金融や医療などの分野では、予測結果の根拠説明が法的要件となる場合がある バイアスの検出と緩和:トレーニングデータの偏りがモデルに反映され、不公平な予測を生む可能性 規制対応:GDPR、AI規制法など、地域によって異なる法規制への対応 監査証跡:誰が、いつ、どのデータで、どのモデルを学習・デプロイしたかの完全な記録 未来のトレンド 1. LLMOps LLMの台頭により、ファインチューニングのワークフロー、プロンプトエンジニアリングのパイプライン、RAGシステム、モデルの組み合わせなど、新しいMLOps領域が生まれています。 2. Edge ML デバイス上での推論、Federated Learning、モデルの圧縮、プライバシー保護MLなど、エッジでのML実行が重要になっています。 3. AutoMLの進化 Neural Architecture Search、自動特徴量エンジニアリング、メタ学習、Few-shot学習など、より高度な自動化が進んでいます。 4. MLOpsプラットフォーム エンドツーエンドのソリューション、クラウドに依存しないツール、オープンソースエコシステム、ベンダーの統合が進んでいます。 2025年のベストプラクティス インフラ モデルをファーストクラスの成果物として扱う:コードと同様に、モデルもバージョン管理され、CI/CDパイプラインでテストされるべき 自動化を徹底する:手動デプロイや手動モニタリングは人的ミスの原因となるため、可能な限り自動化 再現性を確保する設計:同じコード、同じデータ、同じ環境で学習すれば、常に同じモデルが生成されることを保証 最初からスケールを考慮した設計:小規模プロトタイプで成功しても、本番スケールで動かなければ意味がない プロセス クロスファンクショナルチーム(ML + SRE + Product):ML研究者だけでなく、SREとプロダクトマネージャーが協働することで、実用的なシステムを構築 実験トラッキングの規律:すべての実験(ハイパーパラメータ、データセット、結果)を記録し、後から再現・比較可能にする 明確なプロモーション基準:どの指標が改善されれば本番にデプロイするかを事前に定義し、恣意的な判断を避ける MLシステム向けのインシデント対応:従来のシステム障害とは異なり、モデルの品質劣化やデータドリフトに対応するランブックが必要 文化 モデル障害に対するブレームレスポストモーテム:モデルの品質劣化が発生した際、個人を責めるのではなく、システムの改善に焦点を当てる 継続的な学習マインドセット:MLの技術は急速に進化しているため、チーム全体で最新の知見をキャッチアップし続ける チーム間での知識共有:成功事例だけでなく、失敗事例も共有することで、組織全体の学習速度を上げる 不確実性を受け入れる:MLモデルが100%正確ではなく、常に誤差を含むことを前提とした設計とコミュニケーション 学んだこと このセッションを通して、MLOpsの基本原則は変わらないものの、新しい技術トレンドに対応するための進化が求められていることを再認識しました。特に、最初からスケールを考慮した設計については、弊チームでもプロトタイプから本番環境への移行で苦労も多いため、今後のプロジェクトで意識していきたいと思います。また、MLシステム開発に普段関わっていないSREやバックエンドチームでも、最近MLOpsの知識を求められる場面が増えてきているため、今回の内容は非常に参考になりました。 最後に 今回SREcon25に参加して、世界中のSREエンジニアが抱える課題や、それに対する様々なアプローチを知ることができました。 他のカンファレンスとの比較になりますが、SREconは技術的な深掘りだけでなく、文化的な側面にも焦点を当てている点が非常に印象的でした。また、AI/MLシステムの運用に関するセッションが増えており、SREの役割が従来のインフラ運用からAIシステムの品質保証やリスク管理にまで拡大していることを強く感じました。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、データサイエンス部商品データサイエンスブロックで内定者アルバイト中の しゅがー です。我々のチームでは、AIやデータサイエンスを活用したプロダクト開発のため、研究開発に取り組んでいます。本記事では、Geminiを用いて商品画像を分類する方法と背景がCTRへ与える影響を分析した結果をご紹介します。 目次 はじめに 目次 背景・課題 分析手法 データセット作成(Geminiによる背景ラベル付与) プロンプトの工夫と試行錯誤 分類精度の検証方法 バッチ処理を利用した推論速度の向上 背景以外の要素がCTRに差を生まないことを検証 背景がCTRに差が生むことを検証 まとめ 最後に 背景・課題 ECサイトにおける商品画像の背景はサイトごとや売り手によって異なります。一般的に電子機器などの機能性が重視される商品では商品のディテールが鮮明にわかる白背景が主流です。対して、ZOZOTOWNで扱うようなファッション・コスメ領域では白背景だけでなく、木や階段などのオブジェクトを背景に含むような画像も利用されます。そこで、私たちは「商品画像の背景が、ユーザの購買意欲に影響を与えているのではないか」という仮説を立てました。この仮説を検証する一環として、白背景の画像と背景を持つ画像(例:白以外の色が入った背景や木・人物といった要素を含むもの)でCTRの比較分析をしました。このような分析を積み重ねることで、どのような背景がCTRを高めるのか明らかにすることを目指しています。 次セクションから具体的に分析の手順を説明しますが、前提として今回の分析で扱う商品はZOZOCOSMEのフレグランスカテゴリに該当するものだけに限定しています。 分析手法 分析の流れとしては以下のステップを踏みました。 データセット作成(Geminiによる背景ラベル付与) 背景以外の要素がCTRに差を生まないことを検証 背景がCTRに差が生むことを検証 次に、それぞれのステップについて詳細を述べていきます。 データセット作成(Geminiによる背景ラベル付与) 分析のはじめに商品画像を白背景と背景ありのグループに分類し、データセットを作成する必要がありました。従来、このような画像分類タスクは、大量の教師データを用意して専用のモデルを学習させるか、地道な手作業に頼るのが一般的でした。しかし、本記事で紹介するアプローチの興味深い点は、マルチモーダルLLM(Gemini)を活用することで、この工程を劇的に効率化している点です。近年のマルチモーダルLLMは、画像内の背景を人間に近い精度で認識し、自然言語で与えられた柔軟な定義に基づいて高精度で分類する能力を持っています。 今回はGemini APIを用いてフレグランスカテゴリーの商品を以下のグループに分類しました。画像はイメージのため、実際に分析に利用したサンプルとは異なるものです。 プロンプトの工夫と試行錯誤 精度の高い分類を実現するため、プロンプトにはいくつかの工夫を加えています。 1. 「商品の箱」の扱いを明確にし、誤分類を防止 フレグランスカテゴリーの画像では、商品本体とその箱が一緒に写っていることがよくあります。当初の定義では、これを背景あり(ID:1)と誤って分類してしまうケースがありました。そこで、白背景(ID:0)の定義に「商品の箱以外に何もオブジェクトがないもの」という一文を追加しました。これにより、商品パッケージが写っていても、背景が白であれば正しく白背景として分類されるようになり、精度が向上しました。 2. 「その他」ラベルの利用を制限し、分類の一貫性を担保 今回のデータセットはフレグランス商品が中心のため、「その他(ID: 2)」に該当する画像はほとんどないと想定していました。しかし、モデルが判断に迷った場合、安易に「その他」を選んでしまう可能性を排除する必要がありました。これを防ぐため、プロンプトに「基本的に0, 1どちらかのラベルをつけてください。データはフレグランスに関するものがメインなのでそれ以外に該当するようなものを2にしてください」という指示を追加しました。この一文によって、モデルに可能な限り主力カテゴリ(0か1)での分類を促し、ラベルの一貫性を高めることができました。 3. 「その他」ラベルの利用を制限し、分類の一貫性を担保 Few-shotプロンプトを利用 モデルの推論時に分類例となるサンプルを渡すことで精度を向上させるFew-shotプロンプトという手法を用いました。白背景と背景ありの画像の代表的な画像を数枚、ラベルを付与してプロンプトとして渡すことで精度向上を狙いました。 具体的にプロンプトは以下を利用しました(Few-shotプロンプトは一部省略しています)。 あなたはECサイトの商品画像を分類するエキスパートです。 以下の分類定義に基づいて、与えられた複数の画像をそれぞれ最も適切なカテゴリIDに分類してください。 全ての画像に対する分類結果を、JSONの配列形式で一度に返してください。 各JSONオブジェクトには、画像の識別のために `filename` と `id` を含めてください。 例: [ {"filename": "image_001.jpg", "id": 0}, {"filename": "image_002.png", "id": 1} ] --- 分類定義 --- - ID: 0, ラベル名: 白背景 - 説明: 白背景に商品が写っているものと商品の箱以外に何もオブジェクトがないものを指す。 - ID: 1, ラベル名: 背景あり - 説明: 白以外の単色やグラデーションの背景のもの。木や葉のようなオブジェクトがあったり、体の一部が写っているなど何かしらのオブジェクトが背景に写っているものを指す。 - ID: 2, ラベル名: その他 - 説明: 0, 1に当てはまらないもの。商品が明確に写っていない、カテゴリ判断が不能なものを指す。 --- 注意点 --- 少しでも背景に色がついていたり、何かしらのオブジェクトがある場合は1を選択してください。 基本的に0, 1どちらかのラベルをつけてください。データはフレグランスに関するものがメインなのでそれ以外に該当するようなものを2にしてください。 --- 分類例 --- 入力画像 "image_001.jpg":(画像が埋め込まれる) ... 出力: [ {"filename": "image_001.jpg", "id": 0}, ... ] 分類精度の検証方法 マルチモーダルLLMによる自動分類は便利ですが、その精度を検証する工程は不可欠です。今回の分析では以下のような手順で定性的な評価をしました。 1. ランダムサンプリングによる目視確認 全データの中からランダムに抽出した100件のサンプルに対してGeminiでラベルを付与し、画像とラベルを目視で確認しました。 2. 誤分類傾向の分析とプロンプト修正 目視確認中に、上記で説明したような誤分類が発生した場合はプロンプト修正や注意点を追加しました。 3. 改善後の再評価 プロンプト修正後、再度ラベルを付与して1と同様のサンプリングと目視確認をして意図通り分類できていることを確認しました。 1〜3の検証サイクルを繰り返し、今回の分析目的(背景の有無によるCTRの比較)においては、十分精度が担保できていると判断してから全データへの適用を進めました。 バッチ処理を利用した推論速度の向上 Gemini APIのTipsとして以下のように一度のリクエストで複数の画像のラベルを推論することで、実行時間の短縮が可能です(以下、バッチ処理と呼び、後述するバッチAPIとは別物です)。 def classify_image_batch (model: GenerativeModel, image_paths: list [Path], prompt: list [Path]): """指定された画像のバッチを一度に分類します。""" # バッチ内の各画像をプロンプトに追加 for image_path in image_paths: with open (image_path, "rb" ) as f: image_part = Part.from_data(f.read(), mime_type= "image/jpeg" ) prompt.append(f " \n 入力画像 ({image_path.name}):" ) prompt.append(image_part) prompt.append( " \n 出力:" ) # モデルの設定 generation_config = GenerationConfig( temperature= 0.1 , response_mime_type= "application/json" , ) # API呼び出し response = model.generate_content( contents=prompt, generation_config=generation_config, ) # 結果をJSONとしてパース return json.loads(response.text) 今回は高々数千枚のデータに対しての推論であったため、上のような簡易的なバッチ処理を導入することにしました。バッチ処理によって、1時間程度かかった推論を数分程度まで短縮することが可能となりました。 また、数万、数十万規模のデータに対して推論する場合には バッチAPI を利用することをオススメします。 背景以外の要素がCTRに差を生まないことを検証 本分析で重要になるのが、 比較するグループ間に背景以外の差がないことを示す ことです。一般的に、CTRに影響のある要素として値段や表示位置などが挙げられます。表示位置の場合、サイト上で先に表示される商品ほどCTRが高くなると知られています。これらがグループ間で偏りがあると背景以外の要素がCTRへ影響を与えてしまうかもしれません。 この偏りがないことを確認するため、私たちは TOST(Two One-Sided Test) という統計手法を用いました。一般的な統計検定(t検定など)の2群比較では、2群間に差がないことを帰無仮説として設定し、帰無仮説を棄却して差があることを示します。もし帰無仮説が棄却できない場合は2群間に「差があるとは言えない」と表現できますが、これでは2群の分布が一致することは示せません。そこで同等性を示す検定法の1つとしてTOSTがあります。同等性マージンと呼ばれる許容される誤差を設定し、片側検定を2回行うことで分布の差がマージンの範囲内に収まることを証明する手法です。 TOSTの手順は以下の通りです。 同等とみなせる差の範囲(同等性マージン)を定義する。 2つの片側検定を設定する。1つは「グループ間の差が同等性マージンの下限以上である」という仮説、もう1つは「グループ間の差が同等性マージンの上限以下である」という仮説である。 両方の検定が統計的に有意である場合(つまり、両方の仮説が棄却された場合)、2つのグループは実質的に同等であると結論付ける。 そして、本分析ではCTRへの影響が大きいと予想される表示位置に対してTOSTで検定した結果、 データセットの2つのグループには背景以外に統計的差異がない と判断しました。 背景がCTRに差が生むことを検証 ここでは、背景の有無によって分けたグループ間のCTR分布を比較します。以下のグラフは商品ごとのCTR分布をグループ間で比較しています。このグラフから、背景ありグループの方がCTRの中央値が高く、全体的にCTRが高い傾向にあることが読み取れます。 以下の表は白背景グループを基準とした背景ありグループの各指標の差分になります。max/min/mean/median/std. CTRに関しては、商品ごとにCTRを算出して最大値/最小値/平均/中央値/標準偏差を計算しました。group CTRに関してはそれぞれのグループ全体でインプレッション数とクリック数を算出しCTRを計算しました。 商品ごとの平均値で比較すると0.2%ほどCTRが高くなり、group CTRでは0.6%近く差が生まれました。数値で見える差は小さいように感じますが、ZOZOTOWNのようなユーザ数の多いサービスにおいては大きな売り上げの違いにつながります。 指標 差分 (白背景を基準) max CTR -4.6% min CTR -0.06% mean CTR +0.23% median CTR +0.36% std. CTR -0.19% group CTR +0.56% まとめ 本記事ではGemini APIを用いて商品画像を分類する方法と背景画像の差異によるCTRへの影響分析の結果を紹介しました。 マルチモーダルLLMを活用することで、従来は手間のかかった商品画像の分類工程を効率化し、迅速な分析が可能 になりました。また、分析からフレグランスカテゴリーの商品は、白背景にするよりも何かしらの色やオブジェクトを配置した方がCTRは高くなる傾向にあることが分かりました。 商品画像の背景分析を検討している方がいれば、ぜひ参考にしてみてください。今後は、別カテゴリの商品に関する分析や相関だけでなく因果関係を考慮した分析に取り組んでいきたいと考えています。 最後に ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター