TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

937

はじめに こんにちは、ZOZOTOWN開発本部フロントエンドエンジニアの齋藤( @Jin_pro_01 )です。 2025年11月30日に開催された「フロントエンドカンファレンス関西2025」に参加しました。本記事では、会場の様子やトークについてご紹介します。私自身LT枠での登壇もしたので、その内容についても記載しています。 フロントエンドカンファレンス関西とは フロントエンドカンファレンス関西 は、Webフロントエンド領域に関心のあるエンジニアやデザイナーを対象とした技術イベントです。カンファレンスのコンセプトは「IGNITE KANSAI(出会いが共鳴し、次の誰かを動かす)」。運営スタッフ・登壇者・参加者などすべての熱が混ざり合い、相互に影響しあうカンファレンスを目指すというテーマで開催されました。 会場の様子 今回の会場は「マイドームおおさか」の8Fでした。ワンフロアを貸し切り、2つのルームに分かれてトークが行われました。 マイドームおおさか前の看板 フロントエンドカンファレンス関西2025の看板 フロントエンドカンファレンス関西2025のバナー トークの内容 どれも非常に興味深い内容でしたが、特に印象に残ったトーク、LTをご紹介します。最後に私が今回登壇させていただいた内容についても記載します。 なぜフロントエンド技術を追うのか?なぜカンファレンスに参加するのか? speakerdeck.com sakitoさんによる基調講演では、タイトルにある2つの問いに対する答えが語られました。カンファレンス参加者が共通で考えるべき問いを、sakitoさんなりに言語化して紹介するものでした。 まず「なぜフロントエンド技術を追うのか」という問いに対して、そもそもの技術の追い方としてビルドツールの変遷を例に挙げ、"点ではなく線で追う"というアプローチが紹介されました。ツールが多く流行り廃りも激しい領域であるフロントエンド技術の歴史的背景や経緯を追いながらキャッチアップしていく方法です。その中でそのツールはどういった思想のもとなぜ生み出されたのか、どんな時に真価を発揮するのかについて理解し、サービスの持つ課題に対して最も価値を発揮する技術を選定していく必要があります。そのために、フロントエンドの技術を追っていく必要があるという話がされていました。 次に「なぜカンファレンスに参加するのか」という問いです。カンファレンスはドキュメントには載っていない、実際に技術を使ってみた現場の生の声を聞ける場所で、さらに異なる現場での課題感やその解決策について参加者同士で議論できます。技術を追うだけでは得られない誰かの生きた知見を自身の知見にするために、あるいは自社に還元するためにカンファレンスに参加するという1つの答えを提示されていました。そしてフロントエンドカンファレンスにはフロントエンド技術に興味を持ち、課題を抱えた人たちが同じ目的で集まっています。そのような議論をするのにこれ以上適切な場所はないという話があり、私自身深く納得し、何度も頷きながら聞いていました。 このカンファレンスの最初に、「なぜカンファレンスに参加するのか」という問いが言語化され共有されたことで、参加者にひとつ火が灯されたように感じました。とても印象に残る基調講演だったので、早速その日の懇親会でもこの基調講演について直接お話を伺いました。 俺流レスポンシブコーディング 2025 speakerdeck.com レスポンシブなWebサイトを作るためにどのようにコーディングするべきか、実際のコード例を交えたテクニックや考え方についての発表でした。 特に印象的だったのは、ピクセルパーフェクトではなく「デザインの意図パーフェクト」を目指すという考え方です。無限の変数の上に成り立つWebサイトでは、完全なピクセルパーフェクトは存在しません。また、CSSはスタイルの「命令書」ではなく「提案書」であるという発想も新鮮でした。定義したルールの中でブラウザに「よしなに」レスポンシブデザインを作り上げてもらう。どれも今後の開発業務に活かせる内容ばかりで、非常に勉強になりました。 このプロパティいつ使うん? 〜MDNのCSSリファレンス、全部読んでみた〜 www.arfes.jp 532個にも及ぶCSSプロパティをすべて読み、その中でも特に注目すべきCSSプロパティについて紹介する発表でした。 いつもの実装をちょっと楽にするプロパティ、悪用しても面白いプロパティ、これからの実装を大きく変え得るプロパティについてそれぞれ紹介があり、どれも興味深いものでした。Limited Availabilityのものもありましたが、実用例と合わせてまとめられており弊社のプロダクトでも活かせそうなものばかりでした。ちなみにすべてのCSSプロパティを読んだ記録はNotionにまとめられており、自分も挑戦してみたくなりました。 ソースマップはどのように元コードへの参照を保持するか www.docswell.com TypeScriptがJavaScriptにトランスパイルされた後、元のTypeScriptのどこがエラーの原因かを伝えるのがソースマップです。その内部実装が一体どのようになっているかについての発表でした。 変換されたソースマッピングの仕組みについて、5分のLTで47枚ものスライドを用いてわかりやすく詳細に解説されていました。普段あまり意識することのない領域だったため、非常に興味深かったです。 TypeScriptがブラウザで実行されるまでの流れを5分で伝えたい speakerdeck.com 私も今回LT枠で登壇させていただきました。我々の書いたTypeScriptがどのようなプロセスを経てユーザーのブラウザ上で動くのかについて発表しました。 内容としてはTypeScriptが実際にブラウザで実行されるまでの流れをビルドプロセスとし、大きくトランスパイル、バンドル、ミニファイという工程の3つに分けて説明しました。それぞれどのようなことをするのか、どのようなツールを使うのか、実際にそれらのツールを使うとどのような入力からどのような出力がされるのかを資料にまとめています。 5分という限られた時間の発表での時間配分など、技術カンファレンスでの登壇は初めてで大変緊張し思うようにいかない部分もありましたが、学びの多い貴重な機会となりました。採択いただいたことに改めて感謝申し上げます。 おわりに 今回のイベントで同じ悩みや関心を持つ人たちと直接言葉を交わし、他社の現場の生の声を聞くことで多くの知見を得られました。そして、1つの目標としていたカンファレンス登壇を達成できた嬉しい日でもありました。各発表から得た学びに加えて懇親会での交流など、非常に楽しく、学びにもなる刺激的な一日でした。 最後に、素晴らしいイベントを企画・運営してくださった運営スタッフの皆さまに心より感謝申し上げます。来年の開催も楽しみにしています! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
アバター
こんにちは、プロダクト戦略部の土屋です。普段はFAANS(アパレル店舗で働くショップスタッフ向けの業務支援ツール)のプロダクトマネジメントを担当しています。 「新しいことをやりたいけれど、既存タスクで手一杯」プロダクト開発の現場では、こうした状況は珍しくありません。特に、特定の領域や役割にタスクが偏りやすい構造的な課題を抱えた組織では、新しいチャレンジが後回しになってしまうケースも多いのではないでしょうか。 この記事では、チーム内のリソースの偏りという組織的な課題に対して、私たちが取り組んだ「PoC専用開発レーン」の設計と運用についてご紹介します。設計思想から開発フローまで具体的な内容をお伝えできればと思います。 FAANSの開発チームについて FAANSの開発チームは、バックエンド、フロントエンド(Web/iOS/Android)、デザイン、プロダクトマネジメントの各専門領域で構成されています。PdMが要件定義と優先順位の調整を図り、デザイナーがUI/UXを設計、エンジニアが実装を担当するという、比較的オーソドックスな体制です。 具体的な開発フローとしては、まずPdMが事業側からの要望やユーザー課題を整理し、デザイナーと連携しながらUI/UXを固めていきます。デザインデータが完成次第、バックエンド・フロントエンドの各エンジニアと連携しながら仕様の細部を固めつつ実装に進みます。開発が完了したらQAによるテストを経てリリースするという流れで日々の開発が行われています。 もちろん、各エンジニアチームには通常の機能開発以外にも、技術負債の解消やライブラリのアップデート、パフォーマンス改善といった保守運用系のタスクが常にあります。また、デザインチームについては、FAANSだけでなく複数のプロダクトやプロジェクトを掛け持ちしているので、タイミングによってはリソースの確保に事前調整が必要になります。 そんな事情もあり、普段の開発では「バックエンド・フロントエンド・デザインのタイミングが揃わないとなかなか動き出せない」というのが暗黙の前提になっていました。 直面していた課題 直面していた課題を振り返ると、バックエンドの領域にタスクが継続的に集中している状況がありました。バックエンドはソフトウェア開発の屋台骨ともいえる存在であり、優先度の高い機能改修や緊急性の高い対応が日常的に発生していました。そうした背景もあり、バックエンドチームは常に目の前のタスクに追われ、新しい取り組みに十分な工数を割く余裕がなかなか生まれない状態になっていました。デザインチームについても、複数案件の並行対応や人数的な制約があり、スピード感をもって新しい挑戦に取り組むことが難しい場面も少なくありません。 一方で、フロントエンドチームには時期によって余力のあるメンバーが出てくることもあり、FAANS開発チーム全体としてはリソースに偏りが生じていました。 その結果、新しい取り組みをしたくても「バックエンドが動けない」「デザインが動けない」といった理由から後回しになるケースが常態化しつつありました。競争環境が変化し続ける中で、検証やチャレンジのタイミングが遅れることは大きなリスクにもつながります。 こうした背景があり「フロントエンド側の余力を戦略的に活用し、バックエンドやデザインに依存しない形で何か開発が進められないか」という問いが生まれてきました。この課題意識を起点に、PoC専用の開発レーンを設ける構想へとつながっていきました。 チーム 現状の課題と状況 バックエンド 優先度の高いタスクが多く、新規開発のリソース確保が難しい デザイン 複数案件の並行対応や人数的な制約がありリソース確保に調整が必要 フロントエンド タイミングによっては一部メンバーに余裕があり、比較的自由に動きやすい PoC専用開発レーンの設計 これまでの課題を踏まえると、バックエンドやデザインのリソースが空くのを待つ前提では、新しい取り組みの検証スピードを上げられません。アイデアを素早く試し、学習サイクルを回すためには、既存の開発フローとは独立した仕組みが必要でした。 そこで通常の開発ラインとは切り離し、PoCに特化した専用レーンを新たに設けることにしました。既存フローの中にPoCを組み込むと、本番開発との優先順位の競合が避けられず、緊急度の高いタスクが入るたびにPoCは先送りされてしまいます。専用のレーンを用意することで、本番開発への影響を抑えつつ、検証を並行して進められる体制を整えました。 このレーンでは、スコープを「フロントエンドのみで完結するプロトタイプ」に限定しています。モックやスタブを活用し、バックエンドが未整備でも動作する仕組みを用意することで、依存の大きい領域を待たずに検証を進められるようにしました。本来バックエンドが担う処理も、「フロントエンド側でどこまで代替できるか」という観点で柔軟に検証しています。 また、デザインの領域においても、UIの品質や体験の一貫性を損なわないよう、既存のデザインルールをベースにする最低限の設計指針を設けています。加えて、作業の効率化と初期検討の加速を目的に、AIツールの活用も推奨しています。こうした工夫により、限られた体制でも一定の品質を担保しながら、スピーディに検証を進められるように設計しています。 PoCで想定しているケースとしては、UIレベルでの新機能の仮説検証、フェイクデータを用いたUXインタラクションの確認、AI関連機能のプロトタイピングなどです。体制はPMとフロントエンドエンジニアのみというシンプルな構成にし、チーム内から「試したい機能」や「気になっている仮説」を広く募集する運用としています。 PoCの意義を定義する PoCを導入するにあたり、まず「一般的なPoCの理解」と「チームとして実施するPoC」を明確に区別するところから始めました。名前は同じでも、前提としている目的やゴールが微妙に異なり、その認識がずれると期待値の齟齬が生まれます。この差分をチームで揃え、エンジニアリングの境界をクリアにするところから着手しました。 1. 一般的なPoCの理解 一般的には、PoCは次のように捉えられがちです。 本番実装に向けた“前段階” → 小さく作って、最終的にそのままプロダクションに向かう前提で考えられることが多い。 ある程度完成度の高い検証物を作るもの → 動くものを作り、そこでユーザーの反応や技術的な実現性を検証するイメージ。 本番実装に“進むため”の判断材料 → 基本的には「GO」することを前提としたマイルストーン扱いになりやすい。 こうした一般像だと、「どの程度作る必要があるのか」「どのレベルの完成度が必要か」が曖昧なまま進み、工程が重くなるケースが多いことが課題です。 2. チームで定義したPoC これに対して、私たちはPoCの役割をより軽量で判断特化に寄せて定義し直しました。 本番実装を“やるかどうか”を判断する活動 → PoCは「やる/やらない/いつやるか」を決めるための材料集め。 “作らないことで得られる利益”も検証対象 →「これはやらないほうがいい」と低コスト・短時間で判断できることもPoCの成果。 検証物は捨てるつもりで最小限必要なものだけを作る → 動くものは必要最小でOK。目的は決定の前倒し。 正解より“判断の質”を重視 → 結果「今回は採用しない」に至った場合も“成功”と捉える。 1. 一般的なPoCの理解 2. チームで定義したPoC 目的・位置づけ 本番実装に向けた「前段階」 実装要否の「判断材料」 前提スタンス 本番化が前提 やらないのも重要な成果 アウトプット 完成度の高い動くモノ 判断ができる最小限の動くもの 重視すること 技術検証、ユーザー反応 早期の意思決定・判断の質 重要なのは、“作ること”そのものではなく、“判断に必要な材料を短期間で揃えること”です。この考え方をエンジニアと共有し、PoCでは「検証物は捨てるつもりで、判断に必要な最小限だけを作る」という原則をチーム内で統一しました。 「最小限って、どこまで?」という議論も当然ありましたが、最終的には“振る舞いだけ掴めれば十分。作りすぎはむしろ困る”という、普段の開発ではまず出てこないPoCらしい落とし所に落ち着きました。PoCで求められる完成度は、体験の“手触り”を得られるかどうかであり、本番品質を追い始めると、スピードの面でも検証の本筋から外れてしまうことがあります。 仮に検証の結果として「今回は採用しない」という判断に至ったとしても、それは失敗ではなく、「低コストで正しい選択ができた」という意味で成功と捉えています。事実、本格的な実装へ踏み込む前に見極めができれば、時間・工数ともに大きく節約できるため「やらない」という判断は大きな価値のひとつです。 また、この価値観をチームで共有しておくことで、PoCが“前向きに失敗できる場”として機能し、アイデアを出す側も過度に結果を恐れずより多くの選択肢を素早く試せるようになります。 こうした方針により、PoCは「やる・やらない・いつやるか」といった意思決定を前倒しし、本番開発の優先順位づけにも役立つ仕組みとして設計しました。 具体的な開発フロー PoC開発は大きく4つのフェーズで進める設計にしています。 最初のフェーズは「企画・要件定義」です。PdMがPoCの目的、検証したい仮説、検証観点、完了基準を定義します。ここで大事なのは、「何を検証するのか」をあらかじめ明確にしておくことです。PoCにおいてはある程度の曖昧さを許容して動き出すことがポイントではありますが、最低限何が知りたいのかはクリアにしておく必要があります。 なお、PoCのアイデア自体はエンジニアと一緒に検討しており、「こんな機能があったらいいな」「これ試してみたい」といった声を広く募集しています。現場で感じる課題やユーザーの声に近いところからアイデアが生まれることもあれば、技術ドリブンで試してみたいという動機から始まることもあります。どの入口からの提案であっても歓迎できるよう、気軽にアイデアを出せる雰囲気を大切にしています。 次のフェーズは「事前準備・スコープ設計」です。本当に検討する価値があるかを精査した上で、各プラットフォーム(Web/iOS/Android)の担当を割り当てます。技術的に検証の必要な項目があれば明文化し、大まかなスケジュールを引いておきます。 3つ目のフェーズが「開発・仮説検証」です。ここでUI実装、モック接続、動作確認を進めていきます。限られた工数の中で最大限の学びを得ることが目的なので、完璧を目指すよりも「触って確認できる状態」を優先します。できあがったUIの共有方法は各プラットフォームの担当におまかせして、柔軟かつライトに進められるよう設計しています。 最後のフェーズは「レビュー・評価」です。関係者を集めて実際に操作しながらレビューし、この機能をどうしていくか最終的に判断します。同時に、今回の検証で得られたナレッジや課題、気づきも洗い出して記録に残していきます。 得られた成果 PoC開発を進める中で、これまで手をつけづらかったUX改善にも取り組めました。検証だけで終わらず、実際にリリースされている機能もあり、この取り組みで生まれたアイデアがプロダクト品質の向上に寄与しています。 通常の開発では、KPIに直結する機能や顧客要望への対応が優先されやすく、ユーザー体験を整えるための改善はどうしても後回しになりがちです。PoCでは、優先度は高くないものの価値のあるテーマにも一定の時間とリソースを充てられており、細かな体験改善や将来のプロダクトにつながるアイデアの検討が進んでいます。こうした取り組みを通じて、中長期的な価値に寄与する領域にも少しずつ着手できている実感があります。 また、PoCによってプロトタイプという形で実際に手を動かす中で、チーム全体があらためてUXの重要性や改善の余地を実感できたことも、今後の開発方針に影響を与える大きな収穫でした。PoCは、単なる機能検証の場ではなく、自由度の高さを活かしてプロダクトの“未来の選択肢”を広げるための戦略的な取り組みとして、今後も活用していきたいと考えています。 実施して出てきた課題 PoCを始めてみると、うれしい誤算として多くのアイデアが寄せられました。ただ、数が増えるほど「どれから検証すべきか」という優先順位づけが難しくなる課題も見えてきました。さらに、寄せられる案の中にはPoCというより通常の機能改修に近いものも混ざっていて、本来の “意思決定のための検証” という意図からズレる場面も出てきました。 そもそもPoCは、開発フローの設計も含め、やりながら整えていく側面が強く、扱うテーマも抽象度が高い取り組みです。そのため、進行中に生まれる小さな曖昧さが積み重なり、PoCとして扱う範囲の線引きが見えにくくなりやすい性質があります。こうした境界の揺らぎは、優先順位の判断をより難しくする要因にもなっていました。 この課題については、いまもベストプラクティスを探っている段階です。どこまで曖昧さを残し、どこから整理していくべきかを、進めやすさや開発スピードとのバランスをとりながら検討しています。 一例として、検証テーマの粒度や、PoCで扱う論点の範囲を最初に軽く決めておくだけでも、後の判断や優先順位づけが格段にしやすくなります。ただ、初期段階で基準を固めすぎてしまうと、PoCの強みである柔軟な探索の余地をかえって狭めてしまう恐れもあります。こうしたトレードオフを踏まえ、実務で運用しやすい落とし所を模索している状況です。 PoC開発をより扱いやすく、価値につながりやすい形へ育てていくためにも、試行錯誤を続けながら、少しずつより良い仕組みに寄せていきたいと考えています。 まとめ この記事ではFAANS開発チームで取り組むPoC開発についてご紹介しました。 この取り組みはまだ発展途上ですが、チームとして小さな改善を積み重ねる中で、着実に機能し始めています。重要なのは、一度仕組みを作って終わりにするのではなく、実際の検証プロセスから得られた気づきを元に柔軟にアップデートし続けることです。早く試し、早く学び、的確に「やる/やらない」を判断できる体制は、長期的な競争力にも直結します。 今後もチーム全体で学びを共有しながら、PoCがより価値を生む仕組みになるよう改善を続けていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
アバター
.table-of-contents > li > ul { display: none; } はじめに こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田です。 普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当しています。本記事では夜間・休日にインスタンス数を0にスケールされた開発環境を、Slackから起動できるツール「sc8leon(スケールオン)」について紹介します。開発環境のゼロスケール運用で同様の課題を抱えている方の参考になれば幸いです。 目次 はじめに 目次 背景・課題 背景 課題 sc8leonの概要 機能一覧 アーキテクチャ 実装 使用技術 設定ファイル 設定ファイルの構造 依存関係の管理 マイクロサービスの起動/停止 Lambda環境でのGit操作 マニフェストの編集 GitHub Apps認証 GitHub Appsによる自動マージ AuroraClusterの起動/停止 スケジュール機能 Schedule Start(起動時間の前倒し) Schedule Stop(停止時間の延期) 自動リセット 使い方 操作手順 導入効果 まとめ 背景・課題 背景 ZOZOTOWNの開発環境は、コスト削減を目的として夜間・休日にKEDAを利用したゼロスケールを導入しています。EKS上で稼働するPodのレプリカ数を0にするとともに、関連するAWSリソース(主にAuroraCluster)も停止することでインフラコストを削減しています。 ZOZOTOWNの開発環境におけるコスト最適化については、以下の資料でも詳しく紹介しています。併せてご覧ください。 speakerdeck.com この夜間・休日の停止を検討した際、開発環境を主に使用する開発部署から以下のような懸念が挙がりました。 日跨ぎに関連する処理のリリース前に開発環境での動作確認ができない 夜間・休日に本番障害が発生した際、緊急リリースに向けた開発環境での検証ができない 早朝から業務を開始する際や休日出勤時に開発環境が使えない これらの懸念を解消することが夜間・休日停止の導入条件となったため、必要なときに誰でも環境を起動できるツールの開発が必要になりました。 課題 ZOZOTOWNのバックエンドは多数のマイクロサービスが協調して動作しています。各マイクロサービスを稼働させているEKS環境は「 CIOpsからGitOpsへ。Flux2でマイクロサービスのデプロイを爆速にした話 」で紹介しているようにFluxによるGitOpsを導入しており、Gitリポジトリの状態がクラスタに同期されます。そのため、kubectlコマンドによるオブジェクトの直接変更ができず、ゼロスケールの解除には以下の作業が必要でした。 各マイクロサービスのScaledObjectに設定されているMinReplicaを変更するマニフェスト修正 Pull Requestの作成とレビュー依頼 マージ後、Fluxによるクラスタへの同期を待機 AuroraClusterはAWSコンソールから手動で起動 この手順には以下の課題がありました。 課題 詳細 運用負荷 深夜・休日にマニフェスト修正とPR作成を行う必要があり、PRのレビュー・承認も含めて最低2名の対応が必要になる 対象の多さ 多数のマイクロサービスを起動する必要があり、手動での対応は現実的ではなく、対応漏れのリスクもある 属人化 マニフェストの修正方法やAuroraClusterの起動手順を知っている人しか対応できない 戻し忘れのリスク 起動後にMinReplicaを元に戻すPRを忘れると、翌日以降もゼロスケールが解除されたままになる これらの課題を解決するために、誰でもSlackから簡単に環境を起動でき、自動でリセットされるツール「sc8leon」を開発しました。 sc8leonの概要 sc8leonは、ゼロスケールされた開発環境をSlackから起動できるツールです。Slack Workflowから実行でき、環境の起動からリセットまでを自動化しています。 機能一覧 sc8leonは以下の5つの機能を提供しています。 機能名 説明 ユースケース Start ゼロスケールされている環境の即時起動 深夜に障害が発生し、開発環境で修正確認を行いたい Stop 起動中の環境を停止 動作確認が完了したので環境を停止する Schedule Start 起動時間のリスケジュール 翌朝6時から業務を開始するため、5:30に起動時間を設定する Schedule Stop 停止時間のリスケジュール 日跨ぎの動作確認を行うため、深夜2時まで停止時間を延長する Status 起動状況、起動/停止スケジュールの可視化 現在の環境の状態やスケジュールを確認する アーキテクチャ sc8leonのアーキテクチャ sc8leonは以下のコンポーネントで構成されています。 コンポーネント 役割 Slack Workflow ユーザーからの操作を受け付け、Lambdaを呼び出す AWS Lambda sc8leonのメイン処理を実行 Amazon EventBridge Scheduler 起動/停止/リセットのスケジュール実行 AWS Secrets Manager GitHub Appsの認証情報を保存 GitHub Apps マニフェスト変更のPR作成・マージを自動化 Flux Gitリポジトリの変更をEKSクラスタに同期 実装 使用技術 sc8leonは以下の技術スタックで実装しています。 カテゴリ 技術 言語 Go CLIフレームワーク urfave/cli 実行環境 AWS Lambda スケジューラ Amazon EventBridge Scheduler AWS SDK aws-sdk-go-v2 Git操作 go-git GitHub API go-github YAML操作 sigs.k8s.io/yaml 設定ファイル 背景で述べた「対象の多さ」という課題に対して、sc8leonでは操作対象となるマイクロサービスとAuroraClusterを設定ファイルで事前に定義しています。これにより、ユーザーは環境を指定するだけで、関連するすべてのリソースを一括で起動・停止できます。 設定ファイルは環境ごとに用意しており、 go:embed を使用してバイナリに埋め込んでいます。そのため、対象の追加・変更時は再ビルドが必要です。 //go:embed config/*.yaml var configFiles embed.FS func LoadConfig(target string ) (*TargetSetting, error ) { data, err := configFiles.ReadFile(fmt.Sprintf( "config/%s" , target)) if err != nil { return nil , err } var setting TargetSetting yaml.Unmarshal(data, &setting) return &setting, nil } 設定ファイルの構造 設定ファイルには service (マイクロサービス)と rdscluster (AuroraCluster)の2種類のリソースを定義できます。 マイクロサービスの設定例 services : - type : service namespace : zozotown-api serviceName : zozotown-api manifest : scaledObject : path : path/to/scaledobject.yaml compactSeqIndent : true deployment : path : path/to/deployment.yaml timestampKeyName : last-canary-retry-timestamp compactSeqIndent : false dependencies : - type : rdscluster name : zozotown-rds-cluster フィールド 説明 namespace Kubernetesのnamespace serviceName サービス名 manifest.scaledObject.path ScaledObjectマニフェストのファイルパス manifest.scaledObject.compactSeqIndent YAML出力時のリストインデント設定 manifest.deployment.path Deploymentマニフェストのファイルパス manifest.deployment.timestampKeyName Flaggerの更新をトリガーするためのアノテーションキー名 dependencies 依存するリソース(起動順序の制御に使用) ZOZOTOWNのEKS環境にはFlaggerも導入されています。 timestampKeyName は、Deploymentのアノテーションにタイムスタンプを付与してPodの再作成を促すために使用しています。マニフェストの変更だけではFlaggerによるカナリアデプロイがトリガーされない場合に利用します。 ZOZOTOWNでのFlagger導入については、以下の記事も参照してください。 techblog.zozo.com AuroraClusterの設定例 services : # 同一AWSアカウント内のAuroraCluster - type : rdscluster clusterID : zozotown-rds-cluster startScheduleName : zozotown-aurora-start originStartTime : cron(30 6 ? * MON-FRI *) stopScheduleName : zozotown-aurora-stop originStopTime : cron(0 0 ? * TUE-SAT *) # 別AWSアカウントのAuroraCluster - type : rdscluster awsAccountID : "123456789012" role : sc8leon-access-role clusterID : other-account-cluster startScheduleName : other-account-cluster-aurora-start originStartTime : cron(0 7 ? * MON-FRI *) stopScheduleName : other-account-cluster-aurora-stop originStopTime : cron(0 0 ? * TUE-SAT *) フィールド 説明 awsAccountID 別AWSアカウントの場合のアカウントID role 別AWSアカウントの場合のAssumeRole用ロール名 clusterID AuroraClusterのクラスタID startScheduleName 起動用EventBridge Schedulerのスケジュール名 originStartTime 元の起動時間(リセット時に復元する値) stopScheduleName 停止用EventBridge Schedulerのスケジュール名 originStopTime 元の停止時間(リセット時に復元する値) originStartTime と originStopTime は、sc8leonのリセット処理でEventBridge Schedulerのスケジュールを復元する際に使用します。 依存関係の管理 マイクロサービスには dependencies を設定でき、起動時は依存先を先に起動し、停止時は依存元を先に停止します。これにより、DBに依存するマイクロサービスが起動した際にDBがまだ起動していないといった問題を防いでいます。 マイクロサービスの起動/停止 KEDAを使用したゼロスケールでは、ScaledObjectリソースの minReplicaCount を0に設定します。cron triggerで勤務時間中のみ desiredReplicas を1以上に指定し、勤務時間外はcron triggerが無効になります。これにより、 minReplicaCount の設定に従ってPodのレプリカ数が0になります。 sc8leonでは、この minReplicaCount の値を変更することでマイクロサービスの起動/停止を制御しています。 起動 : minReplicaCount を1以上に変更(実際の実装ではKEDAのcron triggerに設定された desiredReplicas と同じ値に変更) 停止 : minReplicaCount を0に変更 FluxによるGitOpsを導入しているため、以下の流れでクラスタに反映されます。 go-gitでリポジトリをクローンし、ブランチを作成 マニフェストファイルを編集 コミット・プッシュ GitHub Apps経由でPull Requestを作成 CIの完了を待機後、Pull Requestをマージ Fluxがマージを検知し、クラスタに同期 Lambda環境でのGit操作 sc8leonでは、go-gitとgo-billyを使用してGitのストレージとファイルシステムの両方をインメモリで扱っています。 func NewGitClient(repositoryPath, repositoryName string , awscli *AWSClient) (*GitClient, error ) { tokenSrc, _ := FetchToken(awscli) token, _ := tokenSrc.Token() // ファイルシステムをインメモリで作成 fs := memfs.New() // Gitのストレージもインメモリで作成し、shallow cloneを実行 r, err := git.Clone(memory.NewStorage(), fs, &git.CloneOptions{ URL: fmt.Sprintf( "https://x-access-token:%s@github.com/st-tech/%s.git" , token.AccessToken, repositoryName), ReferenceName: plumbing.ReferenceName( "refs/heads/master" ), SingleBranch: true , Depth: 1 , }) return &GitClient{repository: r, FS: fs}, nil } ポイントは以下の3点です。 設定 説明 memory.NewStorage() Gitオブジェクト(コミット、ツリー等)をインメモリで管理 memfs.New() ワーキングツリー(ファイル)をインメモリで管理 SingleBranch: true, Depth: 1 shallow cloneにより、必要最小限のデータのみ取得 マニフェストの編集 マニフェストの編集では、YAMLをAST(抽象構文木)としてパースし、 minReplicaCount の値を直接変更しています。 func EditMinReplicaCount(node *yaml.Node, replicaCount string ) { updateMappingNode(node, [] string { "spec" }, "minReplicaCount" , replicaCount) } GitHub Apps認証 sc8leonではGitHub Appsを使用してGitHub APIを操作しています。GitHub Appsの認証情報(App ID、Installation ID、秘密鍵)はAWS Secrets Managerに保存し、Lambda関数から取得しています。 Lambda環境では、 AWS Parameters and Secrets Lambda Extension を使用してSecrets Managerから認証情報を取得しています。このExtensionはローカルHTTPエンドポイント( localhost:2773 )を提供し、Secrets Managerへのアクセスをキャッシュします。これにより、APIコール数の削減とレイテンシの改善を実現しています。 func GetSecretValueWithLambdaExtension(secretName string ) ( string , error ) { secretExtensionEP := fmt.Sprintf( "http://localhost:2773/secretsmanager/get?secretId=%s" , secretName) headers := map [ string ] string { "X-Aws-Parameters-Secrets-Token" : os.Getenv( "AWS_SESSION_TOKEN" ), } client := &http.Client{} req, _ := http.NewRequest( "GET" , secretExtensionEP, nil ) for key, value := range headers { req.Header.Set(key, value) } resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var secretValue GetSecretValueOutput json.Unmarshal(body, &secretValue) return secretValue.SecretString, nil } 取得した認証情報を使用して、GitHub AppsのInstallation Tokenを生成します。このトークンはGit操作(clone、push)とGitHub API操作(PR作成、マージ)の両方で使用します。 func FetchToken(awscli *AWSClient) (oauth2.TokenSource, error ) { appValues, _ := getGitHubAppSecret(awscli) appid, _ := strconv.ParseInt(appValues.AppID, 10 , 64 ) appTokenSource, _ := githubauth.NewApplicationTokenSource(appid, [] byte (appValues.PrivateKey)) instid, _ := strconv.ParseInt(appValues.InstallationID, 10 , 64 ) return githubauth.NewInstallationTokenSource(instid, appTokenSource), nil } Installation Tokenは有効期限が1時間であるため、処理完了後は RevokeToken でトークンを無効化し、セキュリティリスクを軽減しています。 GitHub Appsによる自動マージ 課題で述べた「運用負荷」と「属人化」を解決するために、GitHub Appsを使用してPRの作成からマージまでを自動化しています。 通常、マニフェストを管理しているリポジトリのmasterブランチにはブランチ保護ルールが設定されており、PRのマージには最低1名のApproveが必要です。しかし、GitHub Appsが生成したトークンでは、同じGitHub AppsによるApproveができないという制約があります。 この制約を回避するために、ブランチ保護ルールの「Allow specified actors to bypass required pull requests」設定を有効にしています。この設定にsc8leon用のGitHub Appsを追加することで、sc8leonが作成したPRはApproveなしでマージが可能になります。 func (c *GitHubAPIClient) CreatePullRequest(owner, repo, base, head, title, body string ) (*github.PullRequest, error ) { pr, resp, err := c.client.PullRequests.Create(c.ctx, owner, repo, &github.NewPullRequest{ Title: github.String(title), Body: github.String(body), Base: github.String(base), Head: github.String(head), }) return pr, nil } func (c *GitHubAPIClient) MergePullRequest(owner, repo string , number int ) (*github.PullRequestMergeResult, error ) { result, resp, err := c.client.PullRequests.Merge(c.ctx, owner, repo, number, "" , &github.PullRequestOptions{}) return result, nil } また、PRのマージ前にCIが完了するのを待機しています。これにより、CIでエラーが検出された場合はマージされません。 func (c *GitHubAPIClient) WaitMergeUntilWIPComplete(owner, repo, branchName string , timeout int ) error { for { result, _, _ := c.client.Checks.ListCheckRunsForRef(c.ctx, owner, repo, fmt.Sprintf( "heads/%s" , branchName), &github.ListCheckRunsOptions{CheckName: github.String( "WIP" )}) checkrun := result.CheckRuns[ 0 ] switch checkrun.GetStatus() { case "queued" , "in_progress" : time.Sleep( 5 * time.Second) default : return nil } } } AuroraClusterの起動/停止 AuroraClusterの起動/停止は、AWS SDK for Go v2を使用してRDS APIを直接呼び出しています。 func (c *AWSClient) StartRDSCluster(setting *RDSClusterSetting) ( string , error ) { rdscli, ok := c.rds[setting.AWSAccountID] if !ok { rdscli = c.rds[ "default" ] } out, err := rdscli.StartDBCluster(c.ctx, &rds.StartDBClusterInput{ DBClusterIdentifier: aws.String(setting.ClusterID), }) if err != nil { return "" , err } return *out.DBCluster.Status, nil } 複数のAWSアカウントにまたがるAuroraClusterを操作する必要があるため、設定ファイルにAWSアカウントIDとAssumeRole用のRole ARNを記載しています。これらの情報を使用して、アカウントごとにRDSクライアントを生成しています。 スケジュール機能 起動時間や停止時間のリスケジュールには、 Amazon EventBridge Schedulerのone-time schedules を使用しています。one-time schedulesは指定した時間に一度だけ実行され、 ActionAfterCompletion: Delete の設定により実行後に自動で削除されます。 func (c *AWSClient) CreateScheduleForLambda(scheduleName, scheduleExpression, lambdaFunction, payload string ) error { _, err := c.scheduler[ "default" ].CreateSchedule(c.ctx, &scheduler.CreateScheduleInput{ FlexibleTimeWindow: &schedulertypes.FlexibleTimeWindow{ Mode: schedulertypes.FlexibleTimeWindowModeOff, }, Name: aws.String(scheduleName), ScheduleExpression: aws.String(scheduleExpression), ScheduleExpressionTimezone: aws.String( "Asia/Tokyo" ), Target: &schedulertypes.Target{ Arn: aws.String(fmt.Sprintf( "arn:aws:lambda:%s:%s:function:%s" , ...)), RoleArn: aws.String(fmt.Sprintf( "arn:aws:iam::%s:role/..." , ...)), Input: aws.String(payload), }, ActionAfterCompletion: schedulertypes.ActionAfterCompletionDelete, }) return err } Schedule Start(起動時間の前倒し) 早朝から環境を使いたい場合に、起動時間を前倒しで予約する機能です。指定された時間にStart処理を実行するone-time scheduleを登録します。 func scheduleStart(env Env, startTime *time.Time) (ScheduleStartResult, error ) { // ...省略... scheduleName := "sc8leon-start" scheduleExpression := fmt.Sprintf( "at(%s)" , startTime.Format( "2006-01-02T15:04:05" )) payload := fmt.Sprintf( `{"env": "%s", "command": "start"}` , env) if err = awscli.CreateScheduleForLambda(scheduleName, scheduleExpression, "sc8leon" , payload); err != nil { return ScheduleStartResult{}, errors.Join(err, errors.New( "failed to create eventbridge schedule" )) } // リセット処理のスケジュールも登録 registerResult, err := RegisterResetFunc(env, awscli, *startTime) // ...省略... } Schedule Stop(停止時間の延期) 日跨ぎの動作確認など、通常の停止時間を超えて環境を使いたい場合に、停止時間を延期する機能です。Schedule Stopでは以下の3つのone-time scheduleを登録します。 スケジュール 実行タイミング 処理内容 停止延期処理 本来の停止時間(0:00)の30分前 マイクロサービスの minReplicaCount を維持、AuroraClusterの停止スケジュールを変更 停止処理 指定した停止時間 マイクロサービスとAuroraClusterを停止 リセット処理 翌営業日のAM10:00 設定を元の状態に戻す 停止延期処理を本来の停止時間の30分前に実行するのは、KEDAのcron triggerが0:00に minReplicaCount を0にしてPodを削除するためです。その前に minReplicaCount を1以上に変更し、Podの削除を防ぎます。また、30分という時間は、PR作成・マージ・Fluxによるクラスタへの同期が完了するのに十分な余裕を持たせています。 func scheduleStop(env Env, stopTime *time.Time) (ScheduleStopResult, error ) { // ...省略... // 停止延期処理: 本来の停止時間の30分前に実行 expandScheduleName := "sc8leon-expand-stop-time" expandScheduleExpression := fmt.Sprintf( "at(%s)" , time.Date(stopTime.Year(), stopTime.Month(), stopTime.Day(), 0 , 0 , 0 , 0 , stopTime.Location()). Add(- 30 *time.Minute).Format( "2006-01-02T15:04:05" )) expandSchedulePayload := fmt.Sprintf( `{"env": "%s", "command": "expand-stop-time", "schedule_time": "%s"}` , env, stopTime.Format( "2006/01/02 15:04:05" )) if err = awscli.CreateScheduleForLambda(expandScheduleName, expandScheduleExpression, "sc8leon" , expandSchedulePayload); err != nil { return ScheduleStopResult{}, errors.Join(err, errors.New( "failed to create eventbridge schedule" )) } // 停止処理: 指定された時間に実行 stopScheduleName := "sc8leon-stop" stopScheduleExpression := fmt.Sprintf( "at(%s)" , stopTime.Format( "2006-01-02T15:04:05" )) stopSchedulePayload := fmt.Sprintf( `{"env": "%s", "command": "stop"}` , env) if err = awscli.CreateScheduleForLambda(stopScheduleName, stopScheduleExpression, "sc8leon" , stopSchedulePayload); err != nil { return ScheduleStopResult{}, errors.Join(err, errors.New( "failed to create eventbridge schedule" )) } // リセット処理のスケジュールも登録 registerResult, err := RegisterResetFunc(env, awscli, *stopTime) // ...省略... } 停止延期処理では、マイクロサービスの minReplicaCount を維持するだけでなく、AuroraClusterのEventBridge Schedulerの停止時間も変更します。 func changeScheduleRDSCluster(clusterSetting *RDSClusterSetting, awscli *AWSClient, stopTime time.Time) (ChangeScheduleRDSClusterResult, error ) { scheduleExpression := fmt.Sprintf( "cron(%d %d ? * MON-FRI *)" , stopTime.Minute(), stopTime.Hour()) if err := awscli.UpdateEventBridgeSchedule(clusterSetting.AWSAccountID, clusterSetting.StopScheduleName, scheduleExpression); err != nil { return ChangeScheduleRDSClusterResult{}, errors.Join(err, errors.New( "failed to update eventbridge schedule" )) } return ChangeScheduleRDSClusterResult{ ScheduleName: clusterSetting.StopScheduleName, ScheduleExpression: scheduleExpression, }, nil } 自動リセット sc8leonで変更した設定は、翌営業日に自動でリセットされます。これにより、戻し忘れによるゼロスケール解除状態の継続を防いでいます。 func RegisterResetFunc(env Env, awscli *AWSClient, resetTime time.Time) (RegisterResetFuncResult, error ) { scheduleName := fmt.Sprintf( "sc8leon-reset-%s" , resetTime.Format( "20060102" )) // 既に同日のリセットスケジュールが登録されている場合はスキップ sc, err := awscli.GetSchedule( "default" , scheduleName) if err == nil && sc != nil { return RegisterResetFuncResult{AlreadyRegisterd: true , ...}, nil } // 平日はAM10:00、土日はPM10:00にリセット resetHour := 10 if resetTime.Weekday() == time.Saturday || resetTime.Weekday() == time.Sunday { resetHour = 22 } scheduleExpression := fmt.Sprintf( "at(%s)" , time.Date(..., resetHour, 0 , 0 , 0 , ...).Format( "2006-01-02T15:04:05" )) payload := fmt.Sprintf( `{"env": "%s", "command": "reset"}` , env) awscli.CreateScheduleForLambda(scheduleName, scheduleExpression, "sc8leon" , payload) return RegisterResetFuncResult{...}, nil } リセット処理では以下を実行します。 マイクロサービス : minReplicaCount を0に戻すPRを作成・マージ AuroraCluster : EventBridge Schedulerの起動/停止時間を設定ファイルに記載された元の値に戻す 使い方 sc8leonはSlack Workflowから実行できます。環境ごとに専用のSlackチャンネルが用意されており、ワークフローを選択して実行するだけで環境の起動・停止が可能です。 Slack WorkflowとLambdaを組み合わせたChatOpsの仕組みについては、以下の記事も参照してください。 techblog.zozo.com 操作手順 該当環境のSlackチャンネルでワークフローを選択(例: sc8leon-start-dev ) ポップアップで対象環境を選択し、送信ボタンを押下 ワークフローが投稿したメッセージのスレッドで「Run command」を選択 実行完了後、Slackに結果が通知される 各機能(Start / Stop / Schedule Start / Schedule Stop / Status)で同様の手順で操作できます。Schedule Start / Schedule Stopでは、起動・停止したい日時も選択します。 導入効果 sc8leonの導入により、以下の効果が得られました。 課題 導入前 導入後 運用負荷 マニフェスト修正とPR作成・レビューで最低2名の対応が必要 Slackから1人で即時実行可能 対象の多さ 多数のマイクロサービスを手動で起動、対応漏れのリスク 設定ファイルで一括管理、自動で全サービスを起動 属人化 マニフェスト修正方法を知っている人しか対応できない 誰でもSlackから操作可能 戻し忘れ MinReplicaを元に戻すPRを忘れるリスク 翌営業日に自動リセット 特に、深夜・休日の緊急対応時において開発者が自身で環境を起動できるようになったことで、SREへの依頼待ち時間がなくなり、迅速な対応が可能になりました。 まとめ 本記事では、ゼロスケールされた開発環境をSlackから起動できるツール「sc8leon」を紹介しました。 GitOpsを導入した環境では、kubectlによる直接変更ができないため、マニフェスト修正→PR作成→マージという手順が必要でした。sc8leonはこの手順を自動化し、GitHub Appsによる認証とLambda上でのインメモリGit操作を組み合わせることで、Slackからの簡単な操作で環境を起動できるようにしました。 また、EventBridge Schedulerのone-time schedulesを活用し、起動時間の前倒しや停止時間の延期といったスケジュール機能を実現しています。さらに、翌営業日に自動でリセットされるため、戻し忘れを防止できます。 GitOps環境でマニフェスト変更を自動化する際のポイントは、GitHub Appsによる認証とブランチ保護ルールのバイパス設定、そしてLambda環境でのGit操作です。これらを組み合わせることで、人手を介さずにPRの作成からマージまでを完結させることができます。 sc8leonの導入により、深夜・休日でも開発者自身が環境を起動できるようになり、SREへの依頼待ち時間がなくなりました。開発環境のゼロスケール運用において、運用負荷と利便性のバランスを取る1つの解決策として参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
.table-of-contents > li > ul { display: none; } はじめに こんにちは、ZOZOMO部SREブロックの中村です。普段は ZOZOMO のSREを担当しています。 本記事では、ECS on FargateにPipeCDを導入してGitOpsベースのデプロイ基盤を構築した取り組みをご紹介します。デプロイ経路の複数存在による管理の煩雑さと、段階的デプロイができない課題をPipeCDでどのように解決したかを解説します。 本記事がECS on Fargateを運用していてGitOps化に興味がある方や、PipeCDの導入を検討している方の参考になれば幸いです。 目次 はじめに 目次 導入前の課題 デプロイ経路の複数存在による管理の煩雑さ 段階的デプロイができない 解決アプローチ PipeCDとは 導入構成 実装 Control Planeの構築 Control Planeの初期設定 Pipedの構築 マニフェストファイルの構成 アプリケーションの登録 デプロイの実行 Webhook連携による自動テスト実行 導入効果 デプロイ経路の一本化 段階的リリースによるリスク低減 まとめ 導入前の課題 PipeCD導入前、私たちのチームでは以下の課題を抱えていました。 デプロイ経路の複数存在による管理の煩雑さ 段階的デプロイができない デプロイ経路の複数存在による管理の煩雑さ アプリケーションのタスク定義をデプロイする経路が複数存在していました。アプリ、インフラそれぞれのデプロイの仕方によってデプロイ方法が異なり、どの経路でデプロイされたかの追跡が困難な状況でした。これにより以下の問題が発生していました。 デプロイ履歴の一元管理ができない 障害発生時の原因特定に時間がかかる 予期しないロールバックを引き起こす可能性 段階的デプロイができない 従来の環境では、新しいバージョンのアプリケーションを一度に全てのタスクにデプロイしていました。問題のあるリリースでは全ユーザーに影響するリスクがありました。 カナリアリリースの仕組みがない 問題発生時の影響範囲が大きい ロールバックに時間がかかる 解決アプローチ これらの課題を解決するために、PipeCDの導入を決定しました。 CDツールの選定にあたり、Argo CD、CodePipeline(CodeBuild + CodeDeploy)、PipeCDを比較検討しました。 観点 Argo CD CodePipeline PipeCD Kubernetes対応 サポート 非対応 サポート ECS 対応 非対応 サポート サポート GitOps 対応 非対応 対応 カナリアリリース 対応 対応 対応 Argo CDはEKSを含むKubernetes環境では広く採用されていますが、ECSへのネイティブ対応がありません。CodePipelineはECS on Fargateに対応していますが、GitOpsのようにGitの情報を正とは必ずしも言えないと思います。一方、PipeCDはECSを含むマルチプラットフォームに対応しており、単一のツールでGitOpsとカナリアリリースを実現できる点が決め手となりました。 PipeCDとは PipeCD は、マルチクラウド・マルチプラットフォームに対応した継続的デリバリー(CD)ツールです。GitOpsの原則に基づき、Gitリポジトリをデプロイにおいて信頼できる唯一の情報源(Single Source of Truth)として扱います。 主な特徴としては以下の点が挙げられます。 GitOpsベース : プルリクエストによるデプロイ操作で、変更履歴が明確に管理される マルチプラットフォーム対応 : Kubernetes、ECS、Lambda、Cloud Run、Terraformなど幅広く対応 段階的デプロイ : カナリアリリース、ブルーグリーンデプロイメントを標準サポート セキュリティ : デプロイ用の認証情報がクラスター外に出ない設計 可視化 : デプロイ状況をWeb UIでリアルタイムに確認可能 またPipeCDは、大きく分けてControl PlaneとPipedの2つのコンポーネントで構成されます。 コンポーネント 役割 Control Plane Web UI、APIサーバー、デプロイ状況の管理 Piped 実際のデプロイを実行するエージェント。GitリポジトリとECSを監視し、差分を検知してデプロイを実行 Pipedはデプロイ対象の環境(今回はECS)と同じネットワーク内に配置します。 詳細は以下の PipeCD公式ドキュメント をご参照ください。 Control Plane Piped Concepts 導入構成 今回構築した環境の全体像は以下のとおりです。 ECS on FargateへのPipeCD導入アーキテクチャ Control Plane : 専用のECSで稼働 Piped : デプロイ対象と同じVPC内のECSクラスターで稼働 マニフェスト管理 : 専用のGitHubリポジトリで管理 PipeCDへのログインには、Entra IDを用いたSAML認証を採用しました。弊社ではSSO基盤としてEntra IDを広く利用しており、一元管理されたアクセス管理を実現しています。 実装 Control Planeの構築 Control Planeは以下のコンポーネントで構成され、今回はこのような構成で作成しました。 コンポーネント 役割 今回の構成 Server Web UI、APIサーバー ECS on Fargate Ops 管理用サーバー ECS on Fargate Gateway gRPC/HTTPルーティング Envoy(サイドカー) Cache セッション・ログのキャッシュ Redis(サイドカー) Datastore デプロイ情報の永続化 Aurora MySQL Filestore ログ等のファイル保存 S3 PipeCDの利用を検証した際にそこまで膨大なセッションやログのキャッシュを持たないことから、ElastiCacheを利用するのではなく、サイドカーにRedisを立ててコスト面を抑える構成にしました。ECS上でのControl Plane構築例は PipeCD公式のデモリポジトリ を参考に実装しました。 事前準備 Control Planeを構築する前に、以下のリソースを作成しました。 Aurora MySQL : Datastore用のデータベース S3バケット : Filestore用のバケット ALB : gRPC/HTTP通信用のロードバランサー(Target GroupはgRPC用とHTTP用の2つ) Secrets Manager : 設定ファイルや暗号化キーの保存 ECSタスク構成 Control PlaneのECSタスクを、以下のコンテナで構成しました。 ┌─────────────────────────────────────────────────────┐ │ ECS Task (Control Plane) │ │ ┌─────────────┐ ┌─────────────┐ ┌───────────────┐ │ │ │ Redis │ │ Envoy │ │ PipeCD Server │ │ │ │ (cache) │ │ (gateway) │ │ │ │ │ │ port:6379 │ │ port:9090 │ │ port:9080-9083│ │ │ └─────────────┘ └─────────────┘ └───────────────┘ │ └─────────────────────────────────────────────────────┘ Redis : ログやセッション情報のキャッシュ Envoy : gRPC/HTTPリクエストのルーティング(ALBからのトラフィックを受信) PipeCD Server : Web UIとAPIを提供 設定ファイル Control Planeの設定は control-plane-config.yaml で定義します。 apiVersion : "pipecd.dev/v1beta1" kind : ControlPlane spec : datastore : type : MYSQL config : url : ${DB_USER}:${DB_PASS}@tcp(pipecd-mysql:3306) database : controlplane filestore : type : S3 config : bucket : ${ENV}-${RESOURCE_PREFIX}-pipecd-filestore region : ap-northeast-1 stateKey : ${statekey} sharedSSOConfigs : - name : cognito-azure provider : OIDC oidc : clientId : ${clientId} clientSecret : ${clientSecret} issuer : https://cognito-idp.ap-northeast-1.amazonaws.com/${userPoolId} redirectUri : https://pipecd.${DOMAIN}/auth/callback scopes : - openid - profile 設定ファイルはBase64エンコードしてSecrets Managerに登録し、ECSタスク定義から参照します。詳細は 公式ドキュメント(Configuration reference) をご参照ください。 また、Envoy(Gateway)の設定は envoy-config.yaml で定義します。EnvoyはgRPC/HTTPリクエストをPipeCD Serverの各サービスにルーティングする役割を担います。 admin : address : socket_address : address : 0.0.0.0 port_value : 9095 static_resources : listeners : - name : ingress address : socket_address : address : 0.0.0.0 port_value : 9090 filter_chains : - filters : - name : envoy.filters.network.http_connection_manager typed_config : "@type" : type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type : AUTO stat_prefix : ingress_http http_filters : - name : envoy.filters.http.grpc_web - name : envoy.filters.http.router typed_config : "@type" : type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config : name : local_route virtual_hosts : - name : envoy domains : - '*' routes : # Piped Service(Pipedとの通信) - match : prefix : /grpc.service.pipedservice.PipedService/ grpc : {} route : cluster : grpc-piped-service # Web Service(Web UIからの通信) - match : prefix : /grpc.service.webservice.WebService/ grpc : {} route : cluster : grpc-web-service # API Service(外部APIからの通信) - match : prefix : /grpc.service.apiservice.APIService/ grpc : {} route : cluster : grpc-api-service # その他のHTTPリクエスト(静的ファイル等) - match : prefix : / route : cluster : server-http clusters : - name : grpc-piped-service http2_protocol_options : {} connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : grpc-piped-service endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9080 - name : grpc-web-service http2_protocol_options : {} connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : grpc-web-service endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9081 - name : grpc-api-service http2_protocol_options : {} connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : grpc-api-service endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9083 - name : server-http connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : server-http endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9082 Envoyの設定では、リクエストのパスに応じてPipeCD Serverの各サービスにルーティングしています。 ECSタスク定義(CloudFormation) Control PlaneのECSタスク定義は以下のように構成しました。Redis、Envoy(Gateway)、PipeCD Serverの3コンテナをサイドカー構成で動作させています。 Secretsで宣言しているCONTROL_PLANE_CONFIGとENVOY_CONFIGは、それぞれの設定ファイルをBase64エンコードした値です。これらはControl PlaneとEnvoyの起動時に利用されます。必要なリソースは別途宣言する必要があります。 ECSTaskDefinition : Type : 'AWS::ECS::TaskDefinition' Properties : Family : !Sub '${Env}-${ResourcePrefix}-pipecd-control-plane' NetworkMode : 'awsvpc' RequiresCompatibilities : - 'FARGATE' Cpu : !Ref ECSTaskCPU Memory : !Ref ECSTaskMemory ExecutionRoleArn : !GetAtt ECSTaskExecutionRole.Arn TaskRoleArn : !GetAtt ECSTaskRole.Arn ContainerDefinitions : # Redisコンテナ(キャッシュ用) - Name : 'redis' Image : 'redis:7.4-alpine' Essential : false PortMappings : - ContainerPort : 6379 Protocol : tcp Command : - 'redis-server' - '--appendonly' - 'yes' - '--maxmemory' - '128mb' - '--maxmemory-policy' - 'allkeys-lru' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'redis' # Envoyコンテナ(Gateway) - Name : 'pipecd-gateway' Image : !Ref PipeCDGatewayImageURL # envoyproxy/envoy Essential : false PortMappings : - HostPort : 9090 ContainerPort : 9090 Protocol : tcp Command : - '/bin/sh -c '' echo $ENVOY_CONFIG | base64 -d >> envoy-config.yaml; envoy -c envoy-config.yaml; '' ' EntryPoint : - sh - '-c' Secrets : - Name : 'ENVOY_CONFIG' ValueFrom : !Sub '${SecretsManagerEnvoyConfigArn}:config::' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'pipecd-gateway' DependsOn : - ContainerName : 'redis' Condition : 'START' # PipeCD Serverコンテナ - Name : 'pipecd-server' Image : !Ref PipeCDServerImageURL # ghcr.io/pipe-cd/pipecd Essential : true Command : - !Sub '/bin/sh -c '' echo $CONTROL_PLANE_CONFIG | base64 -d >> control-plane-config.yaml; echo $ENCRYPTION_KEY >> encryption-key; pipecd server --insecure-cookie=true --cache-address=localhost:6379 --config-file=control-plane-config.yaml --encryption-key-file=encryption-key; '' ' EntryPoint : - sh - '-c' Secrets : - Name : 'CONTROL_PLANE_CONFIG' ValueFrom : !Sub '${SecretsManagerControlPlaneConfigArn}:config::' - Name : 'ENCRYPTION_KEY' ValueFrom : !Sub '${SecretsManagerEncryptionKeyArn}:key::' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'pipecd-server' DependsOn : - ContainerName : 'redis' Condition : 'START' Control Planeの初期設定 Control PlaneのECSタスクがデプロイされた後、初期設定を実施します。この手順でPipedの認証情報を取得するため、Piped構築の前に実施します。 OPSサーバーへの接続 PipeCD OPSサーバーに接続し、プロジェクトの作成やユーザー管理を実施します。OPSサーバーは外部公開していないため、SSM Session Managerを使用してポートフォワードで接続します。 ポートフォワードが確立されたら、ブラウザでlocalhostを叩き、OPSサーバーにアクセスして管理画面を開きます。 プロジェクトの作成 管理画面から「Add Project」でプロジェクトを作成します。 項目 設定値 ID プロジェクト識別子(例:your-project-dev) Description 任意の説明 Shared SSO cognito-azure(control-plane-config.yamlで定義したSSO設定名) プロジェクト作成後に表示される Static Admin Username と Static Admin Password は、初回ログインに必要なため必ずメモしておきます。 ユーザーグループの作成 Control PlaneのURLにアクセス(例: https://pipecd.your-domain.com ) 作成したプロジェクト名を入力 Static Adminの認証情報でログイン 「Settings」→「User Groups」からユーザーグループを作成 グループ名 ロール 権限 Admin Admin プロジェクト全体の管理権限 Editor Editor デプロイ・設定変更(ユーザー管理除く) Viewer Viewer 読み取り専用 権限の詳細は 公式ドキュメント(Authentication and authorization) をご参照ください。 SSOログインの確認 ログアウトし、「Login with OIDC」をクリック Entra IDの認証画面でログイン Pipedの登録 Control PlaneにPipedを登録し、Piped構築に必要な認証情報を取得します。 「Settings」→「Piped」→「Add Piped」をクリック Piped情報を入力(Name、Description) 「Save」をクリック 表示される以下の情報をメモ Piped Id : Piped設定ファイルの pipedID に使用 Base64 Encoded Piped Key : Piped設定ファイルの pipedKeyData に使用 これらの値は次の「Pipedの構築」で使用します。 Pipedの構築 PipedはControl Planeと通信し、実際のデプロイを実行するエージェントです。デプロイ対象のECSクラスターと同じVPC内にECS on Fargateで構築しました。 Pipedの設定は piped-config.yaml で定義します。 apiVersion : pipecd.dev/v1beta1 kind : Piped spec : projectID : ${PROJECT_ID} pipedID : ${PIPED_ID} pipedKeyData : ${PIPED_KEY_DATA} apiAddress : pipecd.${DOMAIN}:443 git : sshKeyData : ${SSH_KEY_DATA} repositories : - repoId : ${REPO} remote : git@github.com:hoge/${REPO}.git branch : main platformProviders : - name : ECS type : ECS config : region : ap-northeast-1 notifications : routes : - name : xxxxx_ci_notice events : - DEPLOYMENT_TRIGGERED - DEPLOYMENT_SUCCEEDED - DEPLOYMENT_FAILED - DEPLOYMENT_ROLLING_BACK receiver : xxxxx_ci_notice receivers : - name : xxxxx_ci_notice slack : hookURL : ${SLACK_WEBHOOK_URL} pipedID と pipedKeyData は、Control PlaneのWeb UIからPipedを登録した際に発行されます。詳細は 公式ドキュメント(Configuration reference) をご参照ください。 ECSタスク定義(CloudFormation抜粋) PipedのECSタスク定義は以下のように構成しました。Secretsで宣言しているCONFIG_DATAは、piped-config.yamlをBase64エンコードした状態で取得し、Pipedの起動時に利用されます。 ECSTaskDefinitionPiped : Type : 'AWS::ECS::TaskDefinition' Properties : Family : !Sub '${Env}-${ResourcePrefix}-pipecd-piped' NetworkMode : 'awsvpc' RequiresCompatibilities : - 'FARGATE' Cpu : !Ref PipeCDPipedTaskCpuType Memory : !Ref PipeCDPipedTaskMemoryType ExecutionRoleArn : !GetAtt ECSTaskExecutionRole.Arn TaskRoleArn : !GetAtt ECSTaskRole.Arn ContainerDefinitions : - Name : 'piped' Image : !Sub '${ECRRepositoryUri}:${PipeCDPipedImageTag}' Essential : true EntryPoint : - sh - -c Command : - /bin/sh -c "piped piped --config-data=$(echo $CONFIG_DATA)" Secrets : - Name : 'CONFIG_DATA' ValueFrom : !Sub '${SecretsManagerPipedConfigArn}:config::' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'piped' IAMロール PipedがECSサービスをデプロイするために、タスクロールに以下の権限を付与しています。 ECSTaskRole : Type : 'AWS::IAM::Role' Properties : RoleName : !Sub '${Env}-${ResourcePrefix}-pipecd-piped-ecs-task-role' AssumeRolePolicyDocument : Version : '2012-10-17' Statement : - Effect : 'Allow' Principal : Service : 'ecs-tasks.amazonaws.com' Action : 'sts:AssumeRole' Policies : - PolicyName : 'PipedECSServiceAccess' PolicyDocument : Version : '2012-10-17' Statement : - Effect : 'Allow' Action : - ecs:CreateService - ecs:CreateTaskSet - ecs:DeleteTaskSet - ecs:DeregisterTaskDefinition - ecs:DescribeServices - ecs:DescribeTaskDefinition - ecs:DescribeTaskSets - ecs:DescribeTasks - ecs:ListClusters - ecs:ListServices - ecs:ListTaskDefinitions - ecs:ListTasks - ecs:RegisterTaskDefinition - ecs:RunTask - ecs:TagResource - ecs:UntagResource - ecs:ListTagsForResource - ecs:UpdateService - ecs:UpdateServicePrimaryTaskSet - elasticloadbalancing:DescribeListeners - elasticloadbalancing:DescribeRules - elasticloadbalancing:DescribeTargetGroups - elasticloadbalancing:ModifyListener - elasticloadbalancing:ModifyRule Resource : "*" - Effect : 'Allow' Action : - iam:PassRole Resource : - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${Env}-${ResourcePrefix}-ecs-task-execution-role' - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${Env}-${ResourcePrefix}-task-role' PipedステータスのONLINE確認 PipedのECSタスクが起動したら、Control PlaneでPipedのステータスを確認します。 Control PlaneのWeb UIにログイン 「Settings」→「Piped」へ移動 登録したPipedのステータスを確認 ステータスが ONLINE (緑色のインジケーター)になっていれば、Control Planeとの接続が正常に確立されています。 ステータスがOFFLINEの場合は、以下を確認してください。 Piped設定ファイルの pipedID と pipedKeyData が正しいか apiAddress がControl PlaneのURLと一致しているか ECSタスクのログでエラーが出ていないか マニフェストファイルの構成 PipeCDでECSアプリケーションを管理するために、以下のファイルを用意しました。 ファイル 役割 app.pipecd.yaml PipeCDアプリケーション定義(デプロイ戦略、参照ファイルの指定等) servicedef.yaml ECSサービス定義 taskdef.yaml ECSタスク定義 app.pipecd.yaml PipeCDのアプリケーション設定ファイルです。デプロイ対象のサービス定義・タスク定義ファイルやターゲットグループを指定します。 apiVersion : pipecd.dev/v1beta1 kind : ECSApp spec : name : hogehoge labels : env : dev team : zozo-xxxx app : hogehoge notification : mentions : - event : DEPLOYMENT_TRIGGERED - event : DEPLOYMENT_SUCCEEDED - event : DEPLOYMENT_FAILED - event : DEPLOYMENT_ROLLING_BACK input : serviceDefinitionFile : servicedef.yaml taskDefinitionFile : taskdef.yaml targetGroups : primary : targetGroupArn : arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/primary/xxxxxxxxxxxxx containerName : hogehoge containerPort : 8080 canary : targetGroupArn : arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/canary/xxxxxxxxxxxxx containerName : hogehoge containerPort : 8080 pipeline : stages : - name : ECS_CANARY_ROLLOUT with : scale : 20 - name : ECS_TRAFFIC_ROUTING with : canary : 10 - name : WAIT_APPROVAL - name : ECS_PRIMARY_ROLLOUT - name : ECS_TRAFFIC_ROUTING with : primary : 100 - name : ECS_CANARY_CLEAN 設定項目の詳細は 公式ドキュメント(Configuring ECS application) をご参照ください。 servicedef.yaml ECSサービスの定義ファイルです。 version : v1 spec : cluster : arn:aws:ecs:ap-northeast-1:xxxx:cluster/sample-cluster serviceName : hogehoge desiredCount : 6 launchType : FARGATE schedulingStrategy : REPLICA networkConfiguration : awsvpcConfiguration : subnets : - subnet-xxxx - subnet-yyyy - subnet-zzzz securityGroups : - sg-xxxx assignPublicIp : DISABLED propagateTags : SERVICE deploymentController : type : EXTERNAL taskdef.yaml ECSタスクの定義ファイルです。コンテナイメージやリソース設定を記述します。 family : hogehoge networkMode : awsvpc requiresCompatibilities : - FARGATE cpu : "256" memory : "512" executionRoleArn : arn:aws:iam::xxxx:role/ecsTaskExecutionRole taskRoleArn : arn:aws:iam::xxxx:role/hogehoge-task-role containerDefinitions : - name : app image : xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hogehoge:latest essential : true portMappings : - containerPort : 8080 protocol : tcp logConfiguration : logDriver : awslogs options : awslogs-group : /ecs/hogehoge awslogs-region : ap-northeast-1 awslogs-stream-prefix : ecs アプリケーションの登録 マニフェストファイルをGitリポジトリにプッシュしたら、Control PlaneでアプリケーションをPipedに紐づけます。 Control PlaneのWeb UIにログイン 「Applications」→「Add」をクリック アプリケーション情報を入力 項目 設定値 Name アプリケーション名(例:hogehoge) Kind ECS Piped 登録済みのPipedを選択 Repository piped-config.yamlで定義したリポジトリを選択 Path マニフェストファイルが配置されているディレクトリパス Config Filename app.pipecd.yaml 「Save」をクリック 登録が完了すると、PipedがGitリポジトリを監視し始めます。 デプロイの実行 アプリケーションの登録後、以下の流れでデプロイが実行されます。 初回デプロイ(Quick Sync) アプリケーション登録直後は、現在のマニフェストの状態でデプロイが実行されます。「Applications」画面でデプロイの進行状況を確認できます。 通常のデプロイフロー マニフェストファイル(taskdef.yaml等)を変更 GitリポジトリにPush Pipedが変更を検知し、自動でデプロイを開始 app.pipecd.yamlで定義したパイプラインに従って段階的にデプロイ 補足: カナリアリリースの流れ app.pipecd.yamlでカナリアリリースのパイプラインを定義していた場合、以下の流れでカナリアリリースが実行されます。 ECS_CANARY_ROLLOUT: Canary用のTaskSetを作成 ECS_TRAFFIC_ROUTING: Canaryにトラフィックを流す WAIT_APPROVAL: 承認待ち(Web UIで「Approve」をクリック) ECS_PRIMARY_ROLLOUT: Primary用のTaskSetを新バージョンに更新 ECS_TRAFFIC_ROUTING: Primaryに100%のトラフィックを戻す ECS_CANARY_CLEAN: Canary用のTaskSetを削除 承認待ちの間にCanary環境で問題が見つかった場合は、Web UIから「Cancel」でロールバックできます。 Webhook連携による自動テスト実行 PipeCDの通知機能を活用して、デプロイ成功後に自動でE2Eテストを実行する仕組みを構築しました。 Pipedの設定(piped-config.yaml)で、特定のアプリケーション・環境のデプロイ成功時にWebhookを送信できます。 # piped-config.yaml notifications : routes : - name : acceptance_test_webhook events : - DEPLOYMENT_SUCCEEDED labels : app : test-app env : dev receiver : webhook_receiver receivers : - name : webhook_receiver webhook : url : ${WEBHOOK_URL} 私たちの環境では、WebhookをZapierに送信し、ZapierからGitHub Actionsのワークフローをトリガーしています。GitHub Actionsのワークフローでは、DevのAWS環境で稼働するtest-appへE2Eテストを実行しています。これにより、dev環境のtest-appデプロイ成功後にE2Eテストが自動実行される仕組みを実現しました。詳細は 公式ドキュメント(Configuring Notifications) をご参照ください。 導入効果 PipeCDの導入により、以下の効果が得られました。 デプロイ経路の一本化 段階的リリースによるリスク低減 デプロイ経路の一本化 GitOpsの原則に基づき、全てのデプロイがGitリポジトリを経由するようになりました。 デプロイ履歴がGitのコミット履歴として残る 誰が・いつ・何をデプロイしたかが明確になった PipeCDのWeb UIでデプロイ状況をリアルタイムに確認可能 段階的リリースによるリスク低減 カナリアリリースの導入により、リリースに伴うリスクを大幅に低減できました。 新バージョンの問題を早期に検知可能 問題発生時の影響範囲を限定できる 自動ロールバックにより迅速な復旧が可能 まとめ 本記事では、ECS on FargateにPipeCDを導入してGitOpsベースの段階的リリースを実現した取り組みを紹介しました。ECS on FargateでGitOps化を検討している方や、カナリアリリースを導入したい方は、ぜひPipeCDを検討してみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
こんにちは、技術戦略部のwirohaです。2025年11月20日、21日に「 アーキテクチャConference 2025 」が開催されました。ZOZOはGoldスポンサーとして協賛し、スポンサーブースを出展しました。 アーキテクチャConference 2025 入口の暖簾 technote.zozo.com 本記事ではZOZOから登壇したセッションの紹介と、協賛各社やZOZOの協賛ブースの様子をまとめてお伝えします! セッションレポート 巨大モノリスのリプレイス──機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略 speakerdeck.com 矢部による発表 前半、物流開発部 矢部による発表(撮影:Findyさま) アーキテクチャConference2025ではZOZOで取り組んでいるVBScriptで書かれた20年物の基幹システムのリプレイスについて、杉山との共同発表という形で発表しました。インフラ構成やアーキテクチャ実装の詳細は杉山パートに集約し、私はマネージャー視点での組織マネジメントや機能整理の進め方をメインにお話ししました。 今回の発表では、成功事例だけでなく、かなり赤裸々な話をさせていただきました。「設計文化がなかった」「等価リプレイスを選んだのに、結局『手順の書き換え』になってしまった」「売上貢献度の不明な機能が大量にあった」など、正直あまり表に出したくないような失敗談も包み隠さず話しました。これは、リプレイスプロジェクトの理想と現実のギャップ、そして組織への理解醸成がいかに重要かという点を伝えたかったからです。 発表後のAsk The Speakerには、予想以上に多くの方が来てくださいました。特に印象的だったのは、「うちも同じような状況です」「まさに今、その罠にはまっています」という声が多かったことです。レガシーシステムのリプレイスに悩んでいる方、マイクロサービス化を進めたいけれど現実的な壁にぶつかっている方など、同じ課題を抱えたエンジニアの方々と直接お話しできました。 技術的な相談だけでなく、「組織をどう説得したか」「機能整理をどう進めたか」といった、泥臭い部分への質問も多くいただきました。同じ悩みを持つ方々と直接繋がれたことが、今回の登壇で得た一番の収穫でした。 杉山による発表 後半、SRE部 杉山による発表(撮影:Findyさま) 今回のカンファレンスでは、ZOZOの基幹システムリプレイスにおける背景や現状の課題、その解決に向けたアーキテクチャ選定の考え方、そして実際のアーキテクチャ事例について紹介しました。段階的移行を可能にするストラングラーパターンや発送領域でのイベント駆動設計、アーキテクチャ選定の意図などについて詳しく説明しました。 聴講者はジュニアからシニアまで幅広い層で、アーキテクチャ図を用いた説明の際には大きくうなずきながら聞いている方も多く見られました。チームメンバーからも「近くの参加者が強く共感していた」とのフィードバックがあり、内容に対する理解や納得感を得られたと感じています。セッション後のAsk The Speakerでは、アーキテクチャの詳細を確認しに来られる方や、自社システムと比較しながら質問される方も多く、技術選定や移行戦略への関心の高さが伺えました。質問は特にストラングラーパターンやイベント駆動の設計に関するものが多く、技術的にも興味を持っていただけた手応えがありました。 今回の登壇を通じて、ZOZOが基幹システムリプレイスにおいて、イベント駆動やドメイン分割といったモダンなアーキテクチャ選定や段階的移行戦略を実践している点をポジティブに発信できたと感じています。参加者からの質問や反応からも、当社の取り組みが他社にとっても関心の高いテーマであることが確認でき、有意義なセッションとなりました。 また、社外発信としての効果に加え、説明構成や伝え方の工夫など、今後の登壇機会に向けた学びも得られました。技術的・コミュニケーション的な観点の双方で、非常に充実した機会となりました。 各社ブースレポート 基幹システム本部リプレイス推進部の岡本です。アーキテクチャカンファレンスに1日目のみ参加しました。セッションを拝聴する機会はありませんでしたが、他社ブースを回って色々な話を聞くことができました。私からは、個人的に印象に残ったブースをいくつか紹介いたします。アーキテクチャ以外の内容も多くなりますが、ご容赦ください。 株式会社ジェイテックジャパン様 以前から個人的に関心を寄せていたイベントソーシングのフレームワーク「 Sekiban 」の開発者である高丘様に、直接お話を伺うことができました。 イベントソーシングを用いたサービスを継続運用する際のリアルな難しさや、その課題解決にも繋がるアプローチの1つである 動的一貫性境界(Dynamic Consistency Boundary) について詳しく教えていただき、大変有意義な時間となりました。 株式会社ジェイテックジャパン様のブースでノベルティとしていただいたタンブラー ノベルティとしていただいたタンブラーも素敵で、早速愛用しています。 株式会社アンチパターン様 SaaSの開発・運用・販売を支援する「 SaaSus Platform 」について興味深くお話を伺いました。 特に「導入企業には競争優位性を生み出すコア機能の開発に集中してほしい」というプロダクトの想いに強く共感しました。これは私が日々取り組んでいるシステムリプレイスにおいても常に意識しているポイントです。 また、AWSアカウント活用戦略など技術的な学びも多く、今後マルチテナント設計が求められる場面でも活かしていきたいと考えています。 株式会社ログラス様 普段からログラス様の社員の方々によるドメイン駆動設計や関数型プログラミングに関する発信を拝見していたため、直接それをお伝えできたのは貴重な機会でした。 また、 新卒1期生採用の開始についての発信 も印象に残っており、その背景にある想いを伺うことができたのも良かった点です。 株式会社ログラス様のブースでノベルティとしていただいた技術同人誌 ノベルティとしていただいた技術同人誌もクオリティが高く、ぜひ弊社でも参考にしたい取り組みだと感じました。内容も個人的に興味のあるテーマが多く、楽しく読み進めています。 ZOZO展示レポート wirohaです。今回はブース展示に加えて、アーキテクチャConference連動企画で投稿したポスターを展示していただきました。 ZOZOブースの様子 ブースでは、セッションで発表した基幹システムのほか、カート決済基盤、ショップ直送基盤、検索基盤のアーキテクチャをご紹介しました。じっくりと読み込む方や質問してくださる方が多く、深い交流ができました。CTOの瀬尾もブースに立っており、長期的な方向性や意思決定の経緯についてもお話しできました。 「巨大モノリスのリプレイス 機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略」のパネル 「Javaを使用しているZOZOTOWNのアーキテクチャ」のパネル ノベルティとして、スニーカー等のソールの汚れをこすって落とせる消しゴムや、遊び心あるギミック付きのボールペンなどを配布しました。どちらも大変好評で、ZOZOを知っていただく良い機会になりました。 シューズ用クリーナー消しゴムのノベルティ(撮影:Findyさま) ポスターの方は、Fulfillment by ZOZOと実店舗在庫確認・取り置きサービスのアーキテクチャを展示しました。 ポスター展示 AWSを中心としたサーバーレスアーキテクチャ DynamoDBによるOutboxパターンとCDCを用いたCQRSアーキテクチャ 各企業のアーキテクチャが並び、サービスの特性による差異を考えながら見るのが楽しいスペースになっていました。こちらも多くの方が行き交い、読んでくださっていました。 おわりに ZOZOから参加したメンバーの一部で記念撮影! 今回はじめて協賛して参加者のみなさまとお話しし、アーキテクチャへの関心の高さや熱量を感じました。普段はなかなか見られない各社のアーキテクチャを見てディスカッションできるのは、大変貴重な機会でした。みなさま、ありがとうございました! ZOZOでは、セッションでお話しした基幹業務のエンジニアや、その他の分野でもエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは。ブランドソリューション開発本部WEARバックエンド部SREブロックの和田です。 普段はWEARのSREチーム(以下、WEARSRE)に所属し、開発や運用に加えて、ここ3年ほどはマネージャーとしてチーム運営にも携わっています。 WEARSREは発足以来、クラウドを活用した課題解決に取り組んできました。その一方で、10年以上稼働し続けているオンプレミス環境の運用については、長らく他部署に任せきりの状態が続いていました。 プロダクト専任のSREチームでありながら、システム全体にアプローチできていない。この矛盾を解消すべく、私たちはオンプレミス環境への関与を少しずつ拡大し、最終的に自チームでの運用を実現しました。 本記事では、これら一連の取り組みを振り返りつつ、チームの成長と現場で得られた気づきをご紹介します。 目次 はじめに 目次 これまでの取り組み 取り残されたオンプレミス環境 解決策 1. オンプレミス環境の現状把握 2. WEAR基幹DB更改への挑戦 1. オンプレミス環境を含めた検証環境の整備 2. 外部連携APIの無停止による更改 3. フォロー体制下での運用・保守の引き継ぎ 今後の展望 終わりに これまでの取り組み WEARはサービス開始から10年以上が経過したプロダクトであり、元々はオンプレミス環境のみで構築されていました。IISを中心とした多数のWindows Serverで構成され、サービス成長と共に基盤の仮想化や増設を繰り返しながら拡大を続けてきました。 こうした状況下、システムの効率化を目指したパブリッククラウドの部分的な導入が始まり、大規模な案件をトリガーにクラウド化が加速していきます。これに合わせて発足したのがWEARSREであり、当初はクラウド環境の課題解決を主軸に活動していました。 主要な取り組みとして、膨大なトラフィックを支える画像配信システムの刷新や、APIを含む様々なワークロードの基盤共通化を実施しました。その結果、クラウド環境の大部分は再構築されました。2023年以降はクラウド環境における大規模刷新に一区切りをつけ、整備した基盤を日々改善するフェーズに入っています。 取り残されたオンプレミス環境 クラウド環境の整備が進む一方で、WEARの大部分を占めるオンプレミス環境にはアプローチができていませんでした。ここには、ユーザーデータを保持する基幹DBや主要API、100以上のバッチといったプロダクトの心臓部が稼働しています。クラウド環境に対してWEARSREが取り組む一方、これらの重要な領域は、「クラウド化が完了するまでの間、運用は従来の担当部署が継続する」という役割分担になっていました。 WEARのシステム概要図 WEARSREがこの領域に関与できていない状況は、「組織的な負荷」と「ハイブリッド環境特有の技術的課題」という2つの構造的な問題を生んでいました。 「組織的な負荷」としては当該部署への業務負荷が常態化していました。これは、あくまで移行完了までの「暫定措置」であったはずの他部署による運用・保守が長期化したことによるものです。また、部署を跨ぐがゆえの詳細なヒアリングや日程調整といったコミュニケーションコストもかかっていました。 一方で「ハイブリッド環境特有の技術的課題」も無視できないほど肥大化していました。これは、WEARのシステム基盤がクラウド・オンプレミス環境間で密接に依存していることに起因しています。オンプレミス側で問題が発生しても、体制上の切り分けによりWEARSRE単独では深部に踏み込めず、解決が難しい場面もありました。 これらの問題に拍車をかけたのは「いずれリプレイスされる」という曖昧な見込みです。いずれはなくなるものという観点からオンプレミス環境の直接的な改善が見送られがちとなり、更に事態を悪化させていました。また、当初は活発だったクラウド化に向けたリプレイスの動きもトーンダウンしており歯止めがかからない状況でした。 今後のサービス成長を鑑みると、前述した課題がボトルネックになるであろうことは明白でした。課題を解決するため、従来の体制の見直しも含めWEARSREがきちんとオンプレミス環境と向き合うことが必須であると考えました。 解決策 オンプレミス環境と向き合うにあたって、解決すべき課題はたくさんありました。段階的に課題解決するため、大きく分けて「オンプレミス環境の現状把握」「WEAR基幹DBの更改」「フォロー体制下での運用・保守の引き継ぎ」の3フェーズで進行しました。 1. オンプレミス環境の現状把握 「オンプレミス環境への関与を深める」と意気込んだものの、当時はその全容が全く見えていない状態でした。開発頻度が低いために有識者は不在で、中には名前すら聞いたことのないシステムも存在します。頼みの綱であるドキュメントも長らく更新されておらず、実態と合致しているか確証が持てない状態でした。 そこで、まずはオンプレミス環境の全体像を正確に把握すべく、以下の3点を中心に調査を開始しました。 OSのライフサイクルやハードウェア保守期限の洗い出し 各システムの仕様、システム間の依存関係の洗い出し 各システムの移行計画や開発状況の確認 続いて、これらの情報をもとに、各システムを3つに分類しました。 計画的に廃止可能なシステム: 開発チームによる移行計画が進んでおり、将来的に運用が不要となるシステム。短期的にはOS更新や保守延長が必要だが、長期的にはシステム運用そのものの廃止を見込んでいる。 廃止方針だが計画未定のシステム: 廃止を検討しているものの、複雑な外部依存により具体的な計画が立てられないシステム。改修頻度が低く利用も縮小傾向にあるため、サーバー台数の最適化やクラウドリフトによる延命を検討する。 オンプレミス継続を選択したシステム: コスト対効果等の観点から、意図的にオンプレミス維持を判断したシステム。過去にクラウド化を検討した経緯はあるが、移行コスト等を総合的に判断し、現状環境での継続運用を決めている。 分類を進めていくと、従来の廃止ロードマップだけではカバーしきれない複雑な依存関係が見えてきました。そのため、対象システムを持つ各開発チームと綿密な協議を重ねました。その過程で、廃止計画の練り直しや、凍結されていた廃止対応の再始動が進み、具体的なアクションへとつながりました。こうした現場の協力もあり、オンプレミス環境の解像度は向上し、注力すべきポイントが明確に見えてきました。 2. WEAR基幹DB更改への挑戦 「オンプレミス環境の現状把握」の中で浮上した最大の課題は、WEAR基幹DBにおけるSQL ServerのEOLとハードウェア保守期限が同時に迫っていたことです。これにより、ハードウェア刷新を含む大規模なDB更改への対応が急務となりました。 WEAR基幹DBはユーザーデータを保持し、ほぼ全システムが依存する最大規模のミドルウェアであり、オンプレミス保守の中でも最も困難な対応が予想されます。加えて、5年前に実施した前回更改時とは異なり、現在はクラウドとオンプレミスが複雑に依存し合っています。 このようなハイブリッド環境下で、従来の環境ごとの業務分担を維持することには無理がありました。そこで、WEARSREが旗振り役となり、他部署と連携しながら計画から実行までを進める方針をとりました。この決断ができた背景には、オーナーシップを持って推進する我々に対し、専門的な知見が必要な場面で都度フォローしてくれる他部署の心強いバックアップがありました。 ここではDB更改の詳細については割愛しますが、この体制ならではの取り組みを2点紹介します。 1. オンプレミス環境を含めた検証環境の整備 今回のDB更改は、新サーバーへのデータ移行(ダンプ・リストア)を伴うため、当日はWEARをメンテナンスモードにし、切り替え作業と入念な動作検証を経て再開する計画でした。 当初は準備として、DB参照先の切り替え手順確認や、発行クエリの互換性調査を中心に進めていました。クエリの互換性調査ではDMA(Data Migration Assistant)を使用し、一定期間内に発行されたクエリの中から、互換性を満たしていないものを洗い出しました。 DMAを用いたクエリ互換性の検証については以下の記事をご参照ください。 techblog.zozo.com DMAにより修正すべきクエリは特定できましたが、こうした静的な調査だけでは、切り替え後の挙動を完全に保証しきれません。切り戻しが困難な移行であるため、実環境での動作確認が不可欠です。そのため、オンプレミスとクラウドを網羅した検証環境の整備に踏み切りました。 検証環境の構築には、双方のシステムを再現し新DBへ接続する必要があります。クラウド側は比較的スムーズに進みましたが、技術的なハードルが高かったのはオンプレミス環境の再現です。他部署のサポートを仰ぎつつ、ブラックボックス化していた仕様や細かいパラメータ設定を一つひとつ洗い出す地道な作業が続きました。 結果として、サービス全域のリグレッション試験を実施できる環境が完成し、この過程でWEARSRE内にアプリケーションセットアップを自走できるナレッジも蓄積されました。完成した検証環境は、当日の予行演習や構成変更後の負荷試験など、多岐にわたり活用されました。 2. 外部連携APIの無停止による更改 5年前の更改時との大きな違いとして、外部事業者に提供する「WEAR Connect API」の存在がありました。これは、コーディネート画像を中心としたWEARのデータを外部事業者のECサイト等で活用していただくためのAPIです。 通常、DB更改時のダンプ・リストア中は全システムを停止しますが、当該APIの停止は利用企業のECサイト運営に直接影響します。先方の事業影響を最小限に抑えるため、今回は「APIを停止させない」方法を模索しました。 着目したのは、「レプリケーション停止された状態でしばらく稼働し続けるRODB群」です。WEARの基幹DBはSQL Serverのパブリッシャの役割を担うMAINDBと、サブスクライバの役割を担う複数台のRODBで構成されています。更改手順の中には、MAINDBをダンプする前にRODBに向けたレプリケーションを停止する手順が存在します。その後、RODBに変更予定はなく、そのまま廃止される予定でした。これらを有効活用することで無停止を実現できるのではないかと考えました。 結果的には、以下の条件が揃っていたため、RODBを活用した無停止運用が可能だと判断しました。 API仕様: 外部事業者のECサイト運営で必須なのは「参照系API」のみ。更新系APIは一時停止可能。 データの一貫性: MAINDBのレプリケーションを停止しても、停止直前までのデータがRODBに残っていればサービス提供に支障はない。 非機能要件: MAINDBに比べRODBのスペックは劣るが、API側のキャッシュ機構で負荷を吸収できる見込みがある。 WEARにおけるnginx TCP Load Balancer 実装としては、クラウド上にnginx TCP Load Balancerを構築し、参照系の通信をRODBへオフロードさせる構成に変更しました。当日は各作業フェーズに応じて、適宜DBの参照先を変更することで、外部事業者に影響を与えることなく更改を終えることができました。 なお、nginx TCP Load Balancerを活用した手法はZOZOTOWNでも採用されています。詳細は以下の記事をご参照ください。 techblog.zozo.com 3. フォロー体制下での運用・保守の引き継ぎ WEAR基幹DB更改後も、約1年間は他部署によるフォロー体制を維持し、実務を通じたナレッジを獲得していきました。現在では以下のような業務をWEARSREだけで完結できるようになっています。 インフラ管理: ハードウェアの予算策定、見積もり、OS/MWのバージョン管理 ネットワーク: 開発案件に伴う物理・論理構成の設計 障害対応: オンコール対応、復旧指揮、メンテナンス対応 こうした実績の積み重ねにより、フォローアップのための定例会はその役目を終えました。現在は、WEARのオンプレミス環境に関するあらゆる問い合わせや、依頼をWEARSREが一手に引き受けています。 WEARのシステム概要図(引き継ぎ後) 今後の展望 まずは、引き続きWEARSREが主体となりオンプレミスの運用・保守を安定して実施していきます。ここには、アプリケーションやミドルウェアの安定稼働の実現だけでなく、サーバー機器の選定・発注・老朽化対応といったオンプレミス特有の物理的なインフラ管理業務も含まれます。本記事で取り上げた基幹DB更改だけでなく、今後も様々な保守案件を控えています。これらを引き継ぎ後も品質を落とすことなく確実に推進し、プロダクトの安定稼働に貢献します。 次に、新たに迎え入れたオンプレミス環境の解像度を高め、より効果的かつ現実的な解決策を提案できるチームを目指します。当初は、「全てのシステムを新しい言語で書き換え、すべての基盤をクラウド化する」という方針を掲げていました。しかし、その前提を見直すべき時期に来ていると考えています。単一的なクラウド移行に固執するのではなく、コスト、パフォーマンス、移行リスクなどを総合的に鑑み、「プロダクトにとって何が真に最適か」を追求することが重要です。それぞれの環境の強みを最大限に活かしたハイブリッドなアプローチで、WEARのさらなる成長を技術面から牽引していきます。 終わりに 当初はシステムの実態が掴めず、現状把握に多くの時間を要しました。しかし、保守案件を積み重ねるにつれて、他部署からの問い合わせや機能開発で発生するインフラの構成変更を多角的に提案できるようになりました。これはチームとして大きな成長だと実感しています。 また、私個人の学びとしては、システムの刷新を急ぐあまり、オンプレミス環境の現状把握と課題解決が後手に回ってしまった点は反省すべき点です。新旧の環境を切り離して捉えてしまっていましたが、移行期間中もサービスは等しく稼働し続けます。たとえ過渡期の環境であっても、現在進行形でサービスを支えているのはそのシステムです。本件を通じて改めてそのことを実感しました。 本取り組みは、WEARSREだけの力で成し遂げられたものではありません。長年にわたり運用を支え、移行期間中も親身になってフォローしてくださった他部署の皆様に、深く感謝いたします。また、これまではクラウド技術を主軸としてきた私たちにとって、オンプレミス環境への本格的な介入は大きな挑戦でした。それでも、この変化を前向きに受け入れ、未知の領域にもかかわらず積極的に課題に取り組んでくれたWEARSREのメンバーを誇りに思います。 これからもチーム一丸となって、WEARの信頼性向上に努めてまいります。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。ZOZOTOWN開発本部プロダクト戦略部Tech-PMの高橋です。普段はZOZOTOWNの新機能開発、改善に関わりながらPMとしてプロダクトの開発進行を担当しています。 今回は、2023年11月にリリースした「アイテムレビュー機能」にまつわる、約2年間のプロジェクトの裏側とリリース後に直面した課題、そしてどのように改善を進めていったかをまとめました。 待望のアイテムレビュー機能、ついにリリース ZOZOTOWNでは以前から、お客様・ショップ様双方からレビュー機能の追加を求める声が多く寄せられていました。しかし、監視体制、ブランド様の意向、開発規模など、越えるべきハードルが多く、長年リリースできずにいた背景があります。 そんな中、2023年11月29日、ついに念願のレビュー機能を公開しました。リリース後Xの反応でも「やっと付いた!」「助かる!」という声が多く、ポジティブな反応は81.5%を占めていました。 リリース後に売上指標が低下 好評を得た一方で、リリース後商品詳細ページ経由の売上指標(カート投入率 / CVR / 受注金額)などが低下していることがわかりました。 ユーザーの反応は良く、ZOZOTOWNとしても待望の機能です。そのため、レビュー機能自体を即座に削除する判断ではなく、 「アイテムレビューあり」で売上数値を改善させる事 を探る改善プロジェクトがすぐに立ち上がりました。 先に結論:今のUIとリリース直後UIの比較 一見すると、大きな差異はありません。違いは商品名の下に★があるかないかだけに行き着きました。この違いに到達するまでの改善プロセスを紹介します。 改善スピード最優先の体制構築 改善方針が決まるまでのスピードが求められる状況だったため、次のように体制を再構築しました。 最小限メンバーで改善内容の検討 PM / 企画責任者 / 開発責任者 / デザイン / 分析のみに絞る 意思決定者をZOZOTOWN CPOの1名に統一 開発優先度をトップクラスに設定 改善内容を決めてから開発を開始 ZOZOTOWNは開発に関わる方がとても多く、さまざまな関係者や確認フローが存在します。すべての関係者に確認を取っているとひとつひとつの進行に時間がかかりすぎる事となります。この体制により、コミュニケーションコストが大幅に削減され、改善サイクルを高速に回すことができました。 3回に渡るABテストの実施 商品名下の★削除に行き着くまで、3回に渡るABテストを実施し、改善するパターンを見出しました。 1回目ABテスト:アイテムレビューUI「ありvsなし」 2回目ABテスト:レビュー件数が0件の時の見せ方改善 3回目ABテスト:アイテムレビューUI全体の最適化 1回目:アイテムレビューUI あり VS なし アイテムレビュー機能リリース後初めてのABテストです。レビュー機能の有無が、同一商品の売上数値にどれだけ改善寄与しているかを計るために実施しました。 Aパターン:アイテムレビュー機能あり Bパターン:アイテムレビュー機能なし 結果「なし」の勝利 最初の検証の結果、レビューUIを表示しないパターンのほうが売上指標は良いと分かりました。しかしこの段階では、機能そのものを消す判断はせず、「レビューの“見せ方”に問題がある」という可能性を考え次の検証へ進みました。 2回目:レビュー件数が0件時の見せ方改善 1回目ABテストの結果から、 レビュー件数が0件の場合、売上数値が顕著に低下することが判明 しました。 仮説 レビュー件数が0の場合でも★の表示があることで、レビューがついていない商品のレビュー点数が、「0点」として認識されてしまっているのではないか。 ABテストパターン Aパターン:アイテムレビュー機能あり Bパターン:アイテムレビュー機能なし Cパターン:レビュー0件の場合に、商品名下の★と、リストUI上の★を削除する Dパターン:レビュー0件の場合に、レビューUIごと削除する 結果 「B:レビューUIなし」が一番結果◯ CパターンvsDパターンを比較した場合、「C:0件★削除」が結果◯ 結論 レビュー件数が0件だった場合に限り、「C:0件★削除」パターンを採用 Bパターンが一番いいのは変わらないため引き続き改善検討 3回目:アイテムレビューUI全体の最適化 1回目、2回目ABテスト結果から、以下の事が分かりました。 ★レビュー点数が 4.5 以上の場合、レビューを表示したほうが売上指標に良い影響を与える レビュー件数よりも、 点数の方が重要 である レビュー件数が5件以下の商品PVが、商品全体PVの約6割 仮説 レビュー点数が低い商品の場合、★の表示は購買意欲に悪影響を及ぼしているのではないか ★の表示は、レビュー全体の平均値として出しているが、本来高いレビュー点数になりうる商品でもレビュー件数が少ない(1件〜3件など)商品で、レビュー点数が低い場合、悪影響を及ぼしているのではないか ABテストパターン Aパターン:アイテムレビュー機能あり Bパターン:アイテムレビュー機能なし Cパターン:商品名下の★をレビュー件数に関わらず削除する Dパターン:レビュー件数が3件以下の場合、商品名下の★と、リストUI上の★を削除する 結果 「Cパターン:商品名下の★をレビュー件数に関わらず削除する」 の勝利 この結果から、レビュー機能を表示したほうが、表示しない場合と比べて売上指標の向上につながることが分かりました。 最終的にたどり着いたUI 最終的には、次の仕様で改善をリリースしました。 レビュー件数が1件以上の場合 商品名下の★を削除 レビュー件数が0件の場合 商品名下の★に加えて、リスト上の★も削除 売上指標へのインパクト 2024年度にZOZOTOWNで実施したプロダクト改善によるGMV純増のうち、57.2% がレビュー機能改善による効果でした。 リリース直後の売上低下を巻き返し、改善によって大きく貢献できた取り組みとなりました。 今回の改善から得た学び 待望の機能でも、UX次第で期待とは異なる効果が起きうる ユーザーがどう「解釈」するかに細心の注意が必要。 レビュー件数より、レビュー点数の方が購買に大きく影響 件数が少ない場合、平均点が安定しないケースも。 スピードが求められる改善では、意思決定者を絞ることが重要 プロジェクトの推進力が段違いになる。 諦めず、データに基づいた検証を重ね続けること 今回の最適解は、3回のABテストの積み上げによって辿り着いたもの。 まとめ 今回の一連の改善プロセスは「ユーザーにとって本当に価値あるUIとは何か」「プロダクトの成長に必要な意思決定とは何か」を改めて深く考える機会になりました。今後もレビュー機能の価値向上と、より良い購買体験の実現に向けて改善を続けていきます。 ZOZOでは、一緒にサービスを作り上げてくれるPM(プロジェクトマネージャー)を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co
アバター
はじめに こんにちは、WEARフロントエンド部iOSブロックの西山です。普段はWEAR iOSチームのマネジメント兼アプリの開発を担当しています。今年のWWDC25で、新しいソフトウェアデザインのLiquid Glassが発表されました。透明感のあるUIと流動的なアニメーションが特徴的なこの新デザインは、WEARアプリに大きな影響を与えました。鋭意進行中の取り組みとして、本記事では、Liquid Glass対応を計画的に進めるための取り組みを紹介します。 目次 はじめに 目次 Liquid Glassとは 課題 UIが大きく変わることでデザイン崩れが発生 タブバー タブバーの裏側にコンテンツが透過しない タブバーとコンポーネントが被る ナビゲーションバー ボタンに枠がつく ナビゲーションバーの下に配置してあるコンポーネントとの一体感を失う その他のコンポーネント Switchが大きくなったことによるレイアウト崩れ アラート(アクションシート)の変化 デザイナー、エンジニア、QAのコスト増大が予想される Liquid Glassの学習コスト 移行戦略 開発プロセスの整備 ブランチ戦略 Xcode 26対応 Xcode 16を使用している場合の例 Liquid Glassを無効にする Liquid Glass効果を動的に切り替えられるようにする Liquid Glass対応で分岐できるようにする 対応内容を一部紹介 contentInsetAdjustmentBehavior = .never の対応 contentInsetAdjustmentBehavior = .never の問題点 extendedLayoutIncludesOpaqueBars = true の対応 まとめ Liquid Glassとは iOS 26から導入される新しいデザインで、次の特徴があります。 ガラスのような質感と透明感 光の反射/屈折のようなリアルな動き 流動性のあるアニメーション https://www.apple.com/jp/newsroom/2025/06/apple-introduces-a-delightful-and-elegant-new-software-design/ より引用 ナビゲーションバーやタブバーなどの標準コンポーネントにデザインが適用されるので既存アプリの確認が必要になります。 課題 WEARアプリの確認と現在の状況を踏まえ、次のような課題がありました。 UIが大きく変わることでデザイン崩れが発生 デザイナー、エンジニア、QAのコスト増大が予想される Liquid Glassの学習コスト UIが大きく変わることでデザイン崩れが発生 タブバーとナビゲーションバーを透過させて確認したところ複数の問題が見つかりました。 タブバー タブバーの裏側にコンテンツが透過しない Before After タブバーとコンポーネントが被る Before After ナビゲーションバー ボタンに枠がつく Before After ナビゲーションバーの下に配置してあるコンポーネントとの一体感を失う Before After その他のコンポーネント Switchが大きくなったことによるレイアウト崩れ Before After アラート(アクションシート)の変化 Before After こちらは一部で、この他にも細かいところの問題は存在しています。 デザイナー、エンジニア、QAのコスト増大が予想される WEARは、基本的に3つのOSをサポートしています。今は過渡期のためiOS 16もサポート中ですが、近々iOS 17, 18, 26のサポートになります。サポートOSは、メジャーバージョンが登場してから数ヶ月後の更新となるため、1年に1回更新されます。そのため、現時点から約2年間は、iOS 26未満をサポートする必要があり、Liquid Glassに対応すると新旧それぞれのデザインを並行管理していく必要が出てきます。それぞれのデザイン調整、開発、テストを行うことで、担当する各チームのコストが増大すると予想されます。 Liquid Glassの学習コスト 新しい概念のため、まずはLiquid Glassを理解する必要があります。既存のコンポーネントのどこをどのような形にすることでLiquid Glassとしてベストなのか見極める必要もあります。まだ登場して間もないため、ノウハウも少なく手探り状態になることは否めません。さらにデザイナーのリソースもこちらに多くを割ける状態ではありませんでした。 移行戦略 リスクと対応コストを考慮し、次の方針で進めます。 Liquid Glassを無効にするオプションをフルで利用する レイアウト修正を最優先で行う レイアウト修正を行いながら原因を把握し、デザインや実装方法を見直す レイアウト修正完了後、Liquid Glassのベストプラクティスを探る Liquid Glassの為に構造を大きく変える変更は可能であればiOS 18以前にも適用させる 1に関してですが、Appleは、Liquid Glassを無効にするオプションを用意しており、現時点では 次のメジャーバージョンリリースまで(約1年)有効 とされています。 開発プロセスの整備 修正後は新旧デザインの確認が必要になるため、効率よく進めるための状態を作る必要があります。 ブランチ戦略 普段WEAR iOSでは、developブランチを除外したGit-flowで開発しています。今回の対応では、Liquid Glass用の開発ブランチを作成することも考えられます。しかし、生存期間は長くなりマージコストの増大が懸念されるため既存と同じように進めます。そのため、旧デザインに影響を与えないように対応していく必要があります。 Xcode 26対応 当たり前ですが、Xcode 26対応は必須です。Xcode 26対応の前に先行して進めることもできますがとても非効率になります。 Xcode 16を使用している場合の例 Xcode 26でビルドが通るブランチを用意 Xcode 26で不具合の修正 Xcode 16用のブランチに切り替え Xcode 16用のブランチに修正を取り込む Xcode 16でビルドし確認 問題があれば2から繰り返し Liquid Glassを無効にする Liquid Glassを無効にする設定です。 Info.plist に UIDesignRequiresCompatibility を追加することで無効にできます。 Liquid Glass効果を動的に切り替えられるようにする こちらを参考に swizzling を使ってRelease版には影響がないよう、デバッグメニューから動的に変えられるようにしています。 zenn.dev デバッグメニューの設定 Liquid Glass対応で分岐できるようにする 同一ブランチでの開発になるため、タブバーの透過等で分岐が必要になります。不具合の修正では極力Liquid Glass用の分岐は入れたくないですが、どうしても必要になるケースも考えられます。また、Liquid Glass無効化オプションの廃止後も、iOS 18をサポートする期間があることを考慮します。 class LiquidGlass : NSObject { @objc static var isEnabled : Bool { UserDefaults.standard.isLiquidGlassEnabled } } WEARには一部Objective-Cが残っているため、Objective-Cからも参照できるようにしています。今はデバッグメニューから切り替えられる値を参照してますが、オプションが無効になってからは次のように変える予定です。 class LiquidGlass : NSObject { @objc static var isEnabled : Bool { if #available(iOS 26.0 , * ) { true } else { false } } } 対応内容を一部紹介 レイアウト修正の中で、最初に取り掛かったのはタブバー周りの対応です。タブバーの背景を透過させるだけでもLiquid Glassらしさが出ます(個人の感想です) Before After WEARのタブバーは背景色が指定されており、タブバーの裏に画面が回り込むような実装になっていない画面が多数ありました。 そういった画面の大多数はスクロールできる画面なので、 ScrollView 系を修正します。 ※WEARでは、SwiftUIの導入も徐々に進めてはいますが、まだまだUIKitがメインなのでUIKitの対応になります。 主に次の2つを利用しているところの修正が必要でした。 contentInsetAdjustmentBehavior = .never extendedLayoutIncludesOpaqueBars = true contentInsetAdjustmentBehavior = .never の対応 iOS 11以前は scrollView.contentInset を実装側で調整する必要がありましたが、iOS 11からは自動で調整してくれるようになりました。当時の選択肢としては、 never or automatic の2択が取れましたが、 WEARでは never を採用していました。その背景もあり scrollView.contentInset.top は contentInsetAdjustmentBehavior = .never を指定して調整していました。 contentInsetAdjustmentBehavior = .never の問題点 contentInset.top だけの設定であればまだ良かったのですが、今回の対応で contentInset.bottom も調整する必要があります。 never のままだと、タブバーの高さをセットする必要があり煩雑になるので、次の方法に変更しました。 contentInsetAdjustmentBehavior = automatic にする(デフォルト値なので指定なし) オートレイアウトの bottom は superview に貼る 注意点は、今まで scrollView.contentInset を参照している場所は scrollView.adjustedContentInset を参照するように変える必要があるところです。 adjustedContentInset は自動調整された後の値になっているので automatic にした際は、こちらの参照が適切になる箇所が出てきます。 extendedLayoutIncludesOpaqueBars = true の対応 こちらは、ナビゲーションバーの裏側に画面を通すために使用しており、 edgesForExtendedLayout = .top とセットで使用されています。タブバーを透過させるため bottom も追加します。 extendedLayoutIncludesOpaqueBars = true edgesForExtendedLayout = [.top, .bottom] ただこれだと旧デザインで、スクロールした最下部のアイテムがタブバーの裏に隠れてしまいます。 最下部のアイテムがタブバーの裏に隠れている そのため、次のようにします。 extendedLayoutIncludesOpaqueBars = true edgesForExtendedLayout = isLiquidGlassEnabled ? [.top, .bottom] : .top 極力分岐はしたくないと上述してましたが、早速使ってしまっています。ただこちらは、Liquid Glassのリリースをする前にタブバーを半透明にする対応をリリースし、事前に分岐を無くしておきたいと考えています。 まとめ 本記事ではWEARアプリにおけるLiquid Glass対応を進めるための取り組みを紹介しました。これからLiquid Glass対応に取り組もうとしている方の参考に少しでもなれば幸いです。まずは不具合を修正し、その後、最適なLiquid Glassの対応を模索していければと思います。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
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
アバター