マーケティングオートメーション - TECH PLAY - TECH PLAY

TECH PLAY

マーケティングオートメーション

イベント

マガジン

技術ブログ

はじめに こんにちは、データシステム部MA推薦ブロックの佐藤( @rayuron )です。私たちは、主にZOZOTOWNのメール配信のパーソナライズなど、マーケティングオートメーションに関するレコメンドシステムを開発・運用しています。 以前、テックブログで工数やAI活用による工数削減を計測する仕組みを、タスク管理ツールの GitHub Projects で構築する方法をご紹介しました。 前回の記事 が計測の仕組みの作り方を扱ったのに対し、本記事では仕組みを1年間運用して得られた結果とその考察、そして今後の展望をご紹介します。 目次 はじめに 目次 背景と課題 計測の仕組みと運用 計測の仕組み 運用の整備 結果と考察 AIによる工数削減とAI活用率の推移 作業内容別の効率 定性的な変化 体感と実測のギャップ 今後の展望 Spec: 仕様の明文化 Eval: 成果物の品質の評価 AIが自律的に動く状態の構築 ソウゾウのナナメウエの創出 おわりに 背景と課題 昨今のAIの進化は目覚ましく、私たちの業務でもAIを活用する機会が増えています。AIを使うことで、業務の工数削減や品質向上など、多くのメリットを享受できます。特に工数削減については、「タスクを捌く時間がなんとなく短くなった」という体感がありました。一方で、実際どれだけ効果があったのかは把握できていませんでした。そこで、私たちはAI活用の効果のうち工数削減を計測することにしました。 AIによる工数削減を測るには、AIを使った時と使わなかった時の工数を比較する必要があります。しかし、現実にはある1つのタスクに対してAIを使う場合と使わない場合を同時に観測できません。AIの利用回数やPull Request(以下PR)のマージ数などの指標は取得できるものの、これらは活動量を示すものであり、工数削減に「どれだけ効果があったか」を直接測るものではありません。また、MLチームの業務にはモデル開発の実験・ドキュメント作成・プロジェクトマネジメント業務・登壇準備など、PRにならない業務が多く、PRベースの計測ツールでは網羅的に計測できませんでした。 AIの効果が測れないままでは、投資対効果を説明できませんし、どのパターンの業務がAIと相性が良いのかも感覚的にしか分からず、改善の方向性も定まりません。そこで私たちは、効果を測る仕組みを作り、得られたフィードバックをもとにAI活用の改善を進めることにしました。 計測の仕組みと運用 まず取り組みを見ていただく前に、私たちの通常業務を紹介します。後述する結果はチームのタスクの内容に大きく依存するため、先に知っていただくと結果を解釈しやすくなると思います。 私たちMA推薦ブロックは、エンジニア3人の小規模チームです。メール・LINE・Pushなどの配信をパーソナライズするレコメンドシステムを開発・運用しており、業務例は以下の通りです。 施策起案/要件定義:施策インパクトの事前分析・要件定義 システム/モデル設計:推薦モデル・システムの設計 システム/モデル実装:推薦モデル・システムの実装、テスト、デプロイ 効果検証:A/Bテストダッシュボード作成、効果検証レポーティング作成 運用保守:オンコール対応、パッケージ更新、問い合わせ対応 その他:プロジェクトマネジメント、チームの仕組み作り、目標設定、勉強会、登壇など 開発にはGitHubを使用しており、ドキュメント管理には Confluence を使っています。AIツールとしては、 Claude Code ・ Codex ・ Devin などを必要に応じて使い分けており、メインではClaude Codeを使っているメンバーが多いです。 計測の仕組み 背景と課題で述べたとおり、同一タスクにおいてAIを使った場合と使わなかった場合を同時に観測できません。そこで、タスクの担当者にAIを使わなかった場合の想定工数を見積もってもらい、実際にかかった工数との差を「AIによる削減工数」として記録することにしました。 具体的には以下の仕組みを作成し運用しています。 全タスクをGitHub Issueで管理しGitHub Projectsに紐付け、カスタムフィールド「AI削減工数」「AI活用の成功・失敗事例」に記録する GitHub ProjectsのデータをBigQueryへ毎日自動エクスポートし、ダッシュボード上で可視化する 全体像は以下の通りです。記録したデータは日次で BigQuery へエクスポートされ、BIツールの Data Studio(旧Looker Studio) のダッシュボードから確認できます。 この計測方法について、詳細は以下のテックブログで紹介しています。 techblog.zozo.com Issue作成とフィールド入力には、Claude Codeの Skill をそれぞれ用意して、運用負荷を下げています。例えば、Issueを標準テンプレートで作成する /create-issue や、AIとのセッションログを解析してAI活用フィールドを自動入力する /fill-ai-usage です。なお、入力内容そのものをAIに判断させるわけではありません。あくまで担当者の判断を補助するために使います。例えば、以下のようなタスクで工数削減があった場合、担当者は次のように記録します。 Issueタイトル:MLパイプラインの実装 AI削減工数:2時間 AI活用の成功・失敗事例:パイプラインのステータスをポーリングするスクリプトを書いて、エラー時にClaude Codeで自動修正させて再実行させるループを作った AI活用の成功・失敗事例の欄には、Issueのタスクそのものではなく、タスクを進める中でAIをどう活用したかを記録します。こうした記述が、工数削減にどうAIが効いたかを後から振り返るナレッジになります。 運用の整備 計測の仕組みを作っただけでは、チーム全員が使いこなせるようにならず、改善のサイクルも回りません。そこで、以下の運用を整備しました。 チーム目標への組み込み:「AIで社員を0.5人増やす」を半期のチーム目標に設定した。削減した時間を1か月あたりの営業日数×8時間と比較することで、チーム全体で「1か月あたり0.5人増えた」と言える状態を目指した 週次振り返り: 毎週ダッシュボードで削減実績を確認し、ナレッジを共有する。削減工数が最も多かったメンバーに社内のピアボーナスを送った AI活用会: AI活用において、活用方法を知っているかどうかの差が大きいため、全員がAIの機能や使い方を「知っている」レベルに揃えることを目的に、毎週1時間開催してきた。その後はチーム外のメンバーにも拡大した。現在は、社内のAI活用レベル指標 AZARS に基づき、既存業務のうちAI活用の効果が大きいものを特定し、改善を進めている デモ会: AI活用で削減できた時間を使い、施策提案のためのプロトタイプ作りと他チームへの提案をしている 以下は、週次振り返りで使っていたダッシュボードの一例です。 結果と考察 このような取り組みの結果、以下のような変化がありました。 AIによる工数削減とAI活用率の推移 FY2025H1とFY2025H2を比較すると、AIによる工数削減とAI活用率の計測結果は以下の通りです。 期間 AIによる工数削減 完了件数 平均AI活用率 FY2025H1(2025/04〜09) 212.6時間 269件 45.0% FY2025H2(2025/10〜2026/03) 439.9時間 260件 71.5% 合計 652.5時間 529件 -- ※ AI活用率は「AI削減工数が記録されたIssue件数 ÷ 完了したIssue全件数」で算出しています。 月別に見ると、削減工数は2025年10月の90.2時間と2026年3月の96.8時間がピークでした。1人月を各月の営業日数×8時間とすると、2025年10月は約0.51人月、2026年3月は約0.58人月に相当します。半期のチーム目標に掲げた月0.5人分は、この2つのピーク月で達成できました。Issue単位のAI活用率は2025年6月の28.0%から、2026年3月には88.6%まで上がりました。また、削減工数の伸びはFY2026に入っても続いています。2026年7月時点の集計では、2026年4〜6月の3か月だけで253.6時間を削減しており、6月単月の114.5時間は計測を開始してから最も大きい月間削減です。 H1からH2で削減工数は全体で見ると212.6時間から439.9時間へ、約2.1倍に伸びました。AI活用件数は121件から186件へ1.54倍、活用1件あたりの平均削減は1.76時間から2.37時間へ1.35倍です。つまり、AIを適用するタスクが増える「広がり」と、1件のタスクの中でAIに任せる工程が増える「深まり」の両方が寄与したと考えています。 ただし、この伸びは12か月の時系列を見た変化であり、活用率の上昇が削減を生んだという因果を示すものではありません。MA推薦ブロックは2025年2月に立ち上がったチームで、FY2025H1はチームの仕組み作りや環境の整備といった、AIの効きにくいタスクの比率が高い時期でした。さらに、この1年で利用しているAIの性能自体も大きく向上しています。チームの工夫と、タスクの変化、AIの性能向上が重なった結果として解釈していただきたいです。 作業内容別の効率 以下のグラフに示す作業内容別では、システム開発の削減工数が42.5時間から103.0時間へ伸びました。伸び率ではテスト・QAが4.2時間から47.0時間へ約11倍、テックブログ執筆・登壇が4.0時間から53.3時間へ約13倍となりました。 総量だけでなく効率も以下のグラフで確認します。1営業日あたりの削減工数は、AI削減工数の合計を実作業日数の合計で割った値です。作業内容ごとに見ると、上位はテックブログ執筆・登壇の0.53時間/営業日、分析・レポーティング作成の0.49、社内活動の0.42でした。下位はプロジェクトマネジメントと要件定義がともに0.14、設計が0.19でした。 ※ なお、この指標は小さいIssueほど高く出やすいですが、作業内容ごとのIssueの粒度に大きな差はないことを確認しています。 この傾向から、成果物が明確で作業に時間のかかるタスクほどAIの効果は出やすく、意思決定や対人調整を中心とするタスクほど効果は出にくいと考えています。 効果が出やすいタスクの中でも、コード生成やドラフト作成は特に速さを実感しやすい作業です。実際にAIによる開発量が増えていることは、外部ツールでも確認できています。開発生産性の計測サービスである Findy Team+ のデータを見ると、AI活用が広がったH2に、PR作成数はH1の約1.5倍、デプロイ頻度も1営業日あたり1.3件から2.1件へ増えました。 定性的な変化 数字には表れない、以下のような変化もありました。 チームのナレッジが蓄積された:AI活用の記録やデモ会での議論を通じて、タスクごとのAIの利用例を後から参照できるようになり、ナレッジの共有が進んだ メンバーのAIの活用レベルが上がった:AI活用の成功・失敗を経て、AIのより効率的な使い方を学び、実践できるようになった チーム横断の交流が生まれた:勉強会をチーム外へ広げた結果、他部署との交流が増え、AI活用のナレッジが社内に広がった プロトタイプ駆動の業務の進め方が生まれた:AIはアイデアをすぐ形にできるので、自分たちやステークホルダーからのフィードバックをすぐ得られる。この相性の良さを活かしたデモ会からは、デモを起点に案件化を判断する実例も生まれた また、失敗の記録からも学びがありました。1つ目は2.0時間、2つ目は1.0時間の工数増として記録されたものです。 Claude Codeに方針を委ねようとして、結局出力がよくわからなくなり最終的に自分が方針決めをした。 GitHub Projectsのフィールド埋めをコマンド経由で自動化しようとしたところ手動でやった方が良いことに気づいた。設計時間分のロスが発生した。 当たり前と言われれば当たり前ですが、これらは方針決めのような曖昧なタスクをAIに委ねると逆に時間を失うこと、自動化にも判断が必要なことを示しています。こうした失敗事例も、チーム内で共有することで、次のタスクで同じ失敗を繰り返さないようにしました。 体感と実測のギャップ メンバーからは、半期振り返りで以下のような声が上がりました。 AIで社員を0.5人増やすことが目標であったが、分析やコーディング業務では既に「俺が3人分になる…」ケースがある 一方、計測した工数削減を見ると、ピーク月でも96.8時間で約0.58人月の削減にとどまっています。エンジニア3人のチームにおける0.58人月の削減を、3人で3.58人分の業務をこなしたとみなすと、1人あたりは約1.2倍となり、体感の3人分とはギャップがあります。 このギャップについては、以下のように解釈しています。 実装をAIが一瞬で終わらせる体験は脳に強く残り、その印象に引っ張られ、業務全体が速くなったように感じる タスクが半日で終わってもその分2倍働くのは難しく、空いた時間は別のタスクに使われる 「理解してから実装する」が「実装されたものを理解する」に逆転し、実装時のフロー状態に入れない 並列でタスクを回せる分、コンテキストスイッチの回数と時間あたりのインプット量が増え、脳が疲れて作業の速度が落ちる 人間のレビューの待ち時間や対人コミュニケーションのコストが変わらない場合、AIで作業を速くこなせるほど人間の対応頻度が増えるので人間がボトルネックになる 今後の展望 FY2026H1の目標は、AIによる工数削減を月1人分に引き上げることです。これまでのように各自のタスクにAIを使って速く終わらせるだけでは、この目標に届かないというのがチームの共通認識です。特に、考察で見えたのは私たちのチームの伸びを止めているのがAIの性能というよりは、人間や運用体制だということでした。人間の集中力や、コンテキストスイッチへの耐性は簡単には変えられません。変えられるのは人間によるAIの効果的な使い方や運用体制だと考え、具体的には以下のように改善を進めています。 Spec: 仕様の明文化 1つ目は、実行前に仕様と完了条件を明文化して、AIの成果物への理解度を上げることです。結果と考察で触れた失敗事例が示すとおり、仕様と完了条件が固まっていないタスクをAIへ投げると、出力の良し悪しを判断できず、かえって時間がかかります。仕様の明文化に加えて、仕様の検討時に過去の類似タスクを参考として提案する仕組みも作り始めています。 Eval: 成果物の品質の評価 2つ目は、成果物の品質評価を自動化しやすい形に整備することです。品質のうち定量化できるものはテストとして実装し、自動的に評価します。一方、定量化が難しいものについては判断基準をガイドラインとして整備し、それに沿って評価できるようにします。これにより、成果物の生成だけでなく評価においても人間の介入を減らせると考えています。 このような動きをレビューの自動化にもつなげるつもりです。レビューの目的は、規範適合・検証の代行・意思決定と合意形成・知識の伝達などと整理できます。規範適合や検証の代行といったタスクはAIと自動的なテストに任せ、人間は意思決定と合意形成を中心に行うことでAIと人間の役割を分担します。 以下の図の左側が現在の状態です。デプロイの前に第三者によるレビューを必須としているため、AIで1人の実装が速くなっても、チーム全体のスピードは上がりにくい構造です。右側のように自動テストとAIレビューを挟むことで、この構造を変えていきます。 成果物の定量化と自動的なテストが進むほど、人間からAIへ委譲できるタスクは増えると考えています。そして、人間の確認を要するタスクが減れば、レビュー待ちは少なくなり、各自は自走して高速にタスクを進められると考えています。 AIが自律的に動く状態の構築 3つ目は、人が毎回指示しなくてもAIが自律的に動き、使うほど賢くなっていく状態を作ることです。SpecとEvalが整備されたあとは、人間のトリガーを待たずにAIが安全に動き、人間の判断やAIの成功・失敗のフィードバックを適用しながら自ら改善していく状態を目指しています。 私たちのチームでは、自律的なAIが活躍でき、効果の大きそうな以下の領域から着手し、次のような状態を作ろうとしています。 アラート対応:アラートを検知するとIssueを自動作成し、AIエージェントが原因調査から修正対応までを行い、アラートを解決する 定型的な改修:トリガーとなるイベントを受けて、仕様が明確な定型的な改修をAIが行い、リリース前の状態を作る データ分析:人間とAIが仮説を作り、データの前処理・分析・可視化までを行い、結果をレポートとしてまとめる モデル開発:人間とAIが実験計画を作り、分析→実装→実験を繰り返し、モデルの精度を改善して、結果をレポートとしてまとめる ソウゾウのナナメウエの創出 最後は、AIで生まれた余白の一部を、意図的に新しい価値づくりへ投資することです。考察で見たとおり、削減した時間をすべて次のタスクの前倒しに使うと脳が疲れるだけになりかねません。実際に、デモ会ではビジネスサイドへの提案を文書から動くデモへ変える取り組みを続けています。こういった活動を広げることで、ZOZOが企業理念でZOZOらしさとして掲げる ソウゾウのナナメウエ なアイデアの実現に挑戦し続けられるチームでありたいと考えています。 おわりに 本記事では、AIによる工数削減を計測して見えた結果と考察、今後の展望をご紹介しました。 現在ZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味がある方は以下のリンクからぜひご応募ください! corp.zozo.com
はじめに こんにちは。データシステム部・MA推薦ブロックの住安( @kosuke_sumiyasu )です。 私たちのチームは、ZOZOTOWNのメール・LINE・プッシュ通知といったマーケティングオートメーション(MA)の推薦システムを開発・運用しています。目指しているのは、ユーザーひとりひとりに最適な配信を届けることです。 ZOZOTOWNで本番運用されている推薦モデルは、価格・ブランド・カテゴリ・カラーといった テーブル特徴量 のみを学習に用いていました。そのため、商品画像が持つ視覚情報(シルエット・質感・カラー・柄)を活用できていませんでした。「オーバーサイズシルエット」や「光沢感」「チェック柄」といった、人が画像から読み取れる「見た目の好み」を推薦に反映できていなかったのです。 下図は、四角い縁のメガネを好むユーザーを例に、画像から「見た目」を捉えることで目指した推薦の姿を示したものです。従来のモデルではカテゴリは「メガネ」で合っていても、丸縁やサングラスといった「見た目」の異なる商品が混ざってしまいます。一方、画像から「見た目」を捉えられれば、ユーザーが好みそうな四角い縁のメガネを中心に推薦できます。 そこで私たちは、 商品画像から視覚的特徴を捉えた画像特徴量を生成する仕組み を構築し、既存の推薦モデルに特徴量として組み込むことで、「見た目の好み」を捉えるマルチモーダル推薦システムを実現しました。実際に、この推薦モデルをあるメール配信施策に適用しました。A/Bテストの結果、メール経由サイト流入率(CTR)・メール経由購入率(CVR)・経由売上(メール経由で発生した売上)のすべてで有意な改善が得られました。しかもこの画像特徴量は特定の施策にとどまらず、全社のどの推薦・検索モデルからでも利用できる共通の基盤として提供しています。 本記事では、この取り組みの背景にある課題、画像特徴量を生成・提供する仕組み、そして推薦モデルへの特徴量の組み込みで工夫した点を中心に紹介します。マルチモーダルな特徴量を推薦に活かしたい方の参考になれば幸いです。 目次 はじめに 目次 背景・課題 前提となる推薦システム 課題1: 推薦モデルが「見た目」を捉えられていない 課題2: 画像Embeddingを全社で利用できる基盤がない アプローチの全体像 画像Embeddingを安定供給する仕組みの構築 差分更新によるコスト削減 モデル・バージョンを管理し、VIEWで全社へ提供する 推薦モデルのマルチモーダル化によるパーソナライズ精度向上 モデルの選定 事前学習済みモデルを使用した理由 Item Towerへの組み込み Gated Multimodal Unit(GMU)で画像の寄与度を動的に制御する 特徴量単位の Dropout(Feature/Modality Dropout)で特定特徴量への依存を抑える 定量評価(オフライン) 効果 「見た目の好み」の反映による主要指標の改善 全社共通の画像Embedding基盤の整備 まとめ 今後の展望 最後に 背景・課題 前提となる推薦システム ZOZOTOWNのMAにおけるパーソナライズされたアイテム推薦の一部では、 Two-Towerモデル を使用しています。これは、ユーザーを表現するUser Towerと商品を表現するItem Towerの2つのニューラルネットワークからなります。学習済みの各Towerを使うことで、ユーザーと商品の特徴量をそれぞれEmbeddingに変換できます。このEmbeddingは、特徴を捉えた数値ベクトルで、意味の近いものほどベクトルも近くなる性質を持ちます。両Towerの出力を同じ潜在空間上にマッピングするように学習することで、ユーザーとアイテムの近さをコサイン類似度で測れるようになります。推薦時は、任意のユーザーのEmbeddingと各商品のEmbeddingの類似度を計算し、類似度が高い商品から順に推薦します。 ZOZOでは、このEmbeddingを Embedding基盤 として一元管理し、どの部署からでも利用できるようにしています。私たちの 汎用推薦システム も、この基盤を使用して配信する商品を選定しています。 課題1: 推薦モデルが「見た目」を捉えられていない このItem Towerの特徴量は、価格・ブランド・カテゴリ・カラーなどの テーブル特徴量 のみでした。そのため、 ユーザーの視覚的な嗜好を推薦に反映できない という課題が残っていました。同じカテゴリ・ブランドの商品でも、ユーザーが好むシルエットや柄、質感はさまざまです。しかし従来の推薦モデルは見た目の情報を持たないため、「興味のあるカテゴリやブランドは合っているけれど、見た目の趣味は違う」という結果になりがちでした。例えば筆者は、結婚式用に無地のパステルカラーのネクタイを探していたのですが、柄物ばかりが推薦されてしまい、改善の余地を感じていました。 課題2: 画像Embeddingを全社で利用できる基盤がない 商品画像が持つ視覚情報を推薦に活かすには、それを数値ベクトルに変換した 画像Embedding として扱うのが有効です。しかし当時は、商品画像すべてを画像Embedding化する仕組みも、それを全社で共有する基盤も存在していませんでした。そのため、各チームが検索や推薦で画像特徴量を使いたくても、それぞれが独自に実装する必要があり、開発工数の増加や品質のばらつきが生じます。そこで本プロジェクトでは、 画像Embeddingを常に使える状態で組織に提供し続ける基盤 を構築し、それを推薦モデルに組み込むことで「見た目の好み」を捉えられるようにすることを目指しました。 アプローチの全体像 課題を解決するために、大きく2つに取り組みました。 画像Embeddingを安定供給する仕組みの構築 :商品画像から視覚的特徴を表す画像Embeddingを日次バッチで生成し、BigQueryのVIEWで提供する。どの推薦・検索モデルからでも、常に最新の画像特徴量を利用できる 推薦モデルのマルチモーダル化によるパーソナライズ精度向上 :その画像Embeddingを推薦モデルのアイテム特徴量として組み込み、「見た目の好み」を捉えてパーソナライズ精度を高める マルチモーダル推薦は、次の3つのパイプラインで実現しています。 パイプライン 役割 generate-image-embedding 商品画像から画像Embeddingを生成し、BigQueryへ保存する train-product-recommendation 画像Embeddingを特徴量に加えてTwo-Towerモデルを学習する generate-product-embedding 学習済みモデルでユーザー・商品のEmbeddingを生成する このうち、train-product-recommendationとgenerate-product-embeddingは、もともと運用している既存のパイプラインです。今回はそこに、画像Embeddingを生成するgenerate-image-embeddingを新たに追加しました。あわせて、train-product-recommendationのモデルアーキテクチャと入力特徴量を変更しています。 これらのパイプラインで生成したユーザー・商品のEmbeddingを使って、施策ごとに配信商品を選定します。 以降では、本記事の中心である「画像Embeddingを安定供給する仕組みの構築」と「推薦モデルのマルチモーダル化によるパーソナライズ精度向上」を詳しく紹介します。 画像Embeddingを安定供給する仕組みの構築 画像Embeddingの生成パイプラインは、 Agent Platform Pipelines(旧Vertex AI Pipelines) 上に実装し、日次バッチで実行しています。全体像は次のとおりです。 処理は大きく4ステップで構成されます。 Embedding化の対象とするアイテム集合を取得する 商品画像を取得し、Cloud Storage(以下GCS)へ保存する 事前学習済みの画像モデルで画像Embeddingを生成する 生成したEmbeddingをBigQueryへ保存し、VIEWとして提供する 画像Embeddingの生成(ステップ3)には、 Hugging Face で公開されている事前学習済みモデル( SigLIP 2 )をGPU上で利用しています。画像の保存先にはGCS、Embeddingの保存先にはBigQueryを使っています。なお、SigLIP 2を採用した理由は、のちほど「モデルの選定」で説明します。 この中で工夫した「差分更新によるコスト削減」と「全社への提供」を順に紹介します。 差分更新によるコスト削減 ZOZOTOWNで扱う商品画像は、サイト上でアクティブな商品に限っても数千万枚の規模にのぼります。これらをすべてEmbedding化すると計算コストが大きいため、各商品(商品×カラー)につき代表の1枚に絞ってEmbedding化しています。それでも対象は数百万枚あり、さらに新着商品を考えると、毎日およそ数十万枚を新たにEmbedding化する必要があります。 これらを毎日すべて計算し直すと、GCSからマシンへ画像を転送するオペレーション料金や、推論時間の増加に伴うマシン料金がかさみ、個々は小さくても積み重なると無視できないコストになります。そこで、すべての画像を毎日計算し直す 全件更新 ではなく、未処理分のみを計算する 差分更新 を採用しています。具体的には、次の2つのステップで「まだ処理していないものだけ」を対象にします。 画像の保存(ステップ2) :すでにGCSへダウンロード済みの画像は除外し、未取得の商品画像のみを保存する Embedding生成(ステップ3) :すでに計算済みのEmbeddingは除外し、未計算の商品画像のみを対象とする これにより、新着商品だけを処理すればよくなり、ダウンロードコストと計算コストを抑えられます。また、GCSはAgent Platform Pipelinesの実行リージョンと同じRegionalバケットを使うことで、リージョン間レプリケーション費用やエグレス料金も抑えています。 モデル・バージョンを管理し、VIEWで全社へ提供する 画像Embeddingを全社の共通資産として提供するうえで重要になるのが、 モデルとバージョンの管理 です。精度改善のためにモデルを差し替えたり、複数のモデル・バージョンをA/Bテストで並行させたりすることがあります。そのたびに、利用者が「いまどのモデル名・バージョンが最新で有効か」を追いかけてクエリを書き換えるのは負担が大きく、更新への追従漏れも起こる可能性があります。そこで、利用者がそれらを意識しなくても、常に最新の有効なEmbeddingを取得できる仕組みを用意しました。 具体的には、次の3つのテーブル・VIEWでモデルとバージョンを管理しています。 テーブル / VIEW 種別 役割 product_image_embedding_raw テーブル 生成したEmbeddingを、商品ID・モデル名・モデルバージョン・生成日とあわせて追記する。過去分も残すため、複数のモデル・バージョンが共存する model_manifest テーブル 提供対象とするモデル・バージョンにアクティブフラグを立てる product_image_embedding VIEW model_manifestのアクティブなバージョンに絞り、商品ID × モデル名ごとに最新のEmbeddingを返す product_image_embedding_rawテーブルを直接参照する場合は、利用者がクエリのたびにモデル名やバージョンをWHERE句で指定する必要があります。これをVIEWにまとめることで、利用者はproduct_image_embeddingのVIEWを参照するだけで、常にアクティブなモデル・バージョンの最新Embeddingを取得できます。一方でproduct_image_embedding_rawテーブルにはバージョンごとの履歴が残ります。そのため、モデルのA/BテストではTreatment用のVIEWを用意することで、特定バージョンを指定した検証にも対応できます。 この仕組みによって、追跡性と再現性を確保しつつ、A/Bテストにも対応できます。当初の施策にとどまらず、検索や他の推薦面でも安心して利用できる全社共通の資産として提供できるようになりました。 推薦モデルのマルチモーダル化によるパーソナライズ精度向上 画像特徴量を活かしてパーソナライズ精度を高めるために工夫した点を紹介します。工夫したポイントは2つあります。1つ目が「画像Embedding生成モデルの選定」、2つ目が「生成した画像Embeddingを推薦モデルに組み込む方法」です。特に後者が重要で、画像特徴量は単純に足すだけでは効果が薄く、シンプルな2つの工夫を加えることでモデルの精度を大きく改善できました。 モデルの選定 画像Embeddingの生成には、事前学習済みの SigLIP 2 を採用しています。SigLIP 2は、 CLIP から派生したモデルです。CLIP系のモデルは、画像を扱うImage Encoderと、説明テキストを扱うText Encoderの2つから構成されます。学習時は、対応する画像と説明テキストのペアは近づけ、対応しないペアは遠ざけます。こうした対比的な学習をcontrastive学習と呼び、これにより画像と言語が同じ空間で結びつきます。なお、CLIPがsoftmaxベースの損失を用いるのに対し、採用したSigLIP系はこれをsigmoid損失に置き換えている点が特徴です。 画像が言語の意味と対応づけて学習されるため、得られる画像Embeddingは「柄」「シルエット」「質感」といった視覚的特徴を捉えやすいと考えられます。 CLIP系のモデルの中でSigLIP 2を選んだのは、論文記載のとおり、ゼロショットの分類・検索タスクのベンチマークで良い結果が示されているためです。 事前学習済みモデルを使用した理由 ZOZOの商品画像でファインチューニングする選択肢もありましたが、今回は事前学習済みモデルをそのまま使う方針としました。理由は次の3点です。 テキスト側の教師データがない :CLIP系の追加学習に必要な、画像とペアになる説明テキストを大規模に用意できていない まず有効性を検証したい :画像特徴量が推薦に効くかは未検証のため、まずは低コストに効果を確かめたい 基盤モデルの進化が速い :将来、高性能なモデルへ載せ替える余地を残したい Item Towerへの組み込み 画像Embeddingは、まずItem Towerの入力としてそのまま使えるように整えます。下図のように、画像EmbeddingをItem Towerの入力特徴量の1つ(image_embedding)として追加します。User Tower側は変更せず、Item Tower側にのみ画像特徴量を加えています。 使用した画像Embeddingは768次元です。これを価格やカラーといった他のテーブル特徴量とそのまま結合すると、画像だけで次元の大部分を占めてしまい、他の特徴量の影響が埋もれてしまいます。そこで、画像Embeddingを2層の多層パーセプトロン(768 → 256 → 128)で128次元に圧縮してから、他の特徴量と結合します。これにより、画像とテーブル特徴量の次元のバランスを取りつつ、画像から推薦に効く表現を学習できるようにしています。 ただし、この「圧縮してそのまま結合する」方法だけでは、期待したほどの精度改善が得られませんでした。そこで、さらなる精度改善に向けて次の2つの機構を導入しています。 Gated Multimodal Unit(GMU)で画像の寄与度を動的に制御する 次元を揃えて結合するだけでは、画像をどれだけ重視するかが全商品で一律になってしまいます。しかし本来、画像をどれだけ重視すべきかは商品によって異なります。例えば、Tシャツは柄が選択の決め手になるため画像を重視したい一方、靴下はカラーやブランドといったテーブル特徴量で十分なことが多いです。そこで、画像特徴量の寄与度だけをアイテムごとに動的に調整できるよう、 GMU を参考にしたゲート機構を導入しました。 論文の2モダリティ版GMUは2つのモダリティをゲート値で線形補間するため、片方を強調するともう片方が抑制されるトレードオフを持ちます。これに対して本実装は、 他の特徴量はそのままで、画像特徴量にのみsigmoidゲートを掛ける一方向型のゲート を採用しました。これは、2モダリティ版GMUからもう片方を抑制する項を取り除いた独自の変種で、アイテムごとに画像特徴量の重みづけだけを調整できます。これにより、カテゴリやブランドなどのアイテム情報から、その商品で画像特徴量をどれだけ重視するかを動的に決められます。 一方向型にした理由は、既存のテーブル特徴量(カテゴリ・価格など)は複数のA/Bテストで有効性が実証されており、その表現力をそのまま維持した状態で、画像特徴量を追加したかったためです。 特徴量単位の Dropout(Feature/Modality Dropout)で特定特徴量への依存を抑える もう1つの工夫が、学習のたび、入力の一部をランダムにマスクすることで、特定の特徴量への過度な依存を防ぐDropoutです。よく使われるDropoutは個々のニューロン単位でマスクしますが、今回は特徴量単位でマスクする Feature Dropout を行います。なかでも画像Embeddingは、モダリティ全体を1単位としてマスクし、これを特に Modality Dropout と呼びます。実際には、テーブル特徴量(価格・ブランド・カテゴリ・カラーなど)は各フィールドを、画像Embeddingはモダリティをまるごと1つの塊として、それぞれ独立かつランダムにマスクします。 なぜこれが効くのかを、カラーと画像Embeddingを例に説明します。カラーからもユーザーが好む大まかな色味は学習できますが、画像Embeddingを使えば、より詳細なカラーやシルエット、柄まで捉えられる可能性があります。しかし画像Embeddingは複雑で扱いが難しいため、モデルは学習しやすいカラーにばかり頼り、画像Embeddingを十分に活用しないことがあります。そこでカラーをマスクすると、モデルは画像Embeddingからも学ばざるを得なくなり、画像Embeddingの特徴が使われない状態を防げます。逆に、画像Embeddingに偏りすぎる場合も画像Embeddingをマスクすれば、カラーなどのテーブル特徴量から学べます。こうして、どちらか一方に偏らず、画像Embeddingも含めた幅広い手がかりをバランスよく使う、堅牢なモデルになります。 定量評価(オフライン) これらの工夫により、画像特徴量なしのベースラインと比べて、オフラインのRecall@100は段階的に改善しました。 構成 Recall@100(ベースライン比) ベースライン(画像特徴量なし) — + 画像特徴量あり(単純結合のみ) +1.06% + 画像特徴量あり(Feature/Modality Dropout) +11.3% + 画像特徴量あり(Feature/Modality Dropout + GMU) +12.0% 効果 「見た目の好み」の反映による主要指標の改善 構築したマルチモーダル推薦システムを、1配信あたり約700万人を対象とするメール配信施策のアイテム推薦ロジックに適用し、A/Bテストで効果を検証しました。Control(画像Embeddingなし)とTreatment(画像Embeddingあり)を比較し、CTR・CVRはz検定、経由売上はt検定を用いて有意水準5%で評価しました。 その結果、 CTR・CVR・経由売上のすべてで統計的に有意な改善 が確認され、TreatmentがControlを上回りました。以下はTreatmentのControlに対する相対改善率です。 指標 相対改善率 有意差 CTR(メール経由流入数 / 配信数) 約 9.9% あり(勝ち) CVR(メール経由購入数 / 配信数) 約 14.3% あり(勝ち) 経由売上(メール経由の受注金額 / 配信数) 約 10.3% あり(勝ち) ユーザーの「見た目の好み」を捉えた推薦が、実際の流入・購入・売上の改善に結びつくことを確認できました。この結果を受けて本番リリースを決定し、現在は本番環境で稼働しています。 全社共通の画像Embedding基盤の整備 共通基盤の構築により、画像Embeddingを使いたいチームは、生成パイプラインを自前で用意する必要がなく、VIEWを参照するだけで常に最新のEmbeddingを利用できます。これにより、検索や他の推薦面を担当するチームも、開発工数をかけずに効果検証を始められます。さらに、基盤側でモデルを改善すれば、利用側は追加対応なしでその精度向上を受けられます。モデルの差し替えやバージョン管理を基盤の内側に閉じ込めたことで、利用者は中身を意識せずに使い続けることができます。 まとめ 本記事では、商品画像の視覚情報を推薦に活かすマルチモーダル推薦システムの構築を紹介しました。事前学習済みモデルを活用し、少ない工数で画像特徴量を追加して、その有効性まで確かめられました。さらに、生成した画像特徴量を全社で利用できる資産として提供できたことも、大きな成果だと考えています。これにより、画像特徴量を試したい部署は、自分たちで実装しなくてもすぐに効果検証を始められます。そして「画像」という新しい特徴量の軸を手に入れたことで、ここを足がかりに推薦をさらに良くしていけるはずです。 今後の展望 画像Embeddingのさらなる活用と推薦の精度向上に向けて、次のような展開を考えています。 画像Embeddingの活用箇所の拡大 :整備した共通基盤を活かし、検索・他推薦面へも展開する 画像ベースの候補生成への活用 :閲覧・購入した商品と視覚的に似た商品を、推薦候補とする モデルの高度化 :事前学習済みモデルから、ZOZOのデータでファインチューニングしたモデルへ置き換え、ファッションに特化した表現の獲得を目指す 画像の前処理の工夫 :商品領域をバウンディングボックスで検出してクロップ(切り出し)し、周辺の背景ノイズを除いて視覚的特徴をより正確に捉える 最後に ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは、MA部SREブロックの片桐です。MA部ではメルマガやLINE、アプリプッシュ通知を配信するためのマーケティングオートメーションシステムを開発・運用しています。 MA部ではDBとして主にCloud SQL for MySQLを利用しており、調査や不具合対応のために開発メンバーがDBにログインして各種SQLを実行する場面があります。 このとき、共用の特権DBユーザーとパスワード認証を利用していました。しかし、この方式ではパスワード管理が必要になるほか、DB上のログイン主体も個人に紐づけにくい状態でした。 これらの課題を解決するために、人間によるDBへのログイン方式を、共用の特権DBユーザーとパスワード認証から個人のGoogle Cloudアカウントを使ったIAM認証へ移行しました。 あわせて、IAM認証でログインする各ユーザーには通常時は参照権限のみを付与し、書込系権限が必要な場合だけGitHub Actionsの承認付きワークフローから一時付与する運用にしました。 本記事では、共用DBユーザーによる運用から個人のIAM認証を使った運用へ移行した背景と、MySQLロールの一時付与を実現するための構成例を紹介します。 目次 はじめに 目次 従来の運用の課題 強い権限が常時使える 個人単位で追跡できない 目指した状態 全体構成 IAM認証で個人ログインにする IAM認証の有効化 IAMデータベースユーザーの作成 Cloud SQLへのログイン権限の付与 MySQLロールでDB内権限を分ける 通常時と一時付与用のロール ロールの作成 参照用ロールの付与 GitHub Actionsを承認ゲートにして書込系ロールを一時付与する GitHub Environmentで申請者以外の承認を必須にする 権限操作用サービスアカウントの作成 ワークフロー設定例 一時付与した書込系ロールを剥奪する 手動でロールを剥奪する 定期実行でロールを剥奪する 運用上の注意点と今後の改善 一時付与したロールを使うときの注意 ワークフロー入力値の検証 SQL本文のレビューと監査 一時付与した権限の失効タイミングの厳密化 MySQLロールの粒度 おわりに 従来の運用の課題 従来の構成ではデータベース操作用の共用特権DBユーザーを作成し、パスワード認証でCloud SQL for MySQLへログインしていました。 構成としては次のとおりです。 ここで利用している Cloud SQL Studio は、Google Cloudコンソール上からCloud SQLへ接続してSQLを実行できるWebベースの画面です。 この運用では、複数人が同じDBユーザーを使ってログインします。そのため、主に次のような課題がありました。 強い権限が常時使える 日常運用におけるデータ調査であれば、多くの場合は SELECT を実行できれば十分です。 しかし、共用の特権DBユーザーを使うと、参照だけで済む作業時でも特権によりデータ変更やDDL操作まで実行できてしまいます。 強い権限を持った状態でSQLを実行すると、誤操作時に本来不要だったデータ更新やスキーマ変更まで起きてしまう可能性があります。そのため、通常時は参照のみを許可し、必要なときだけ書込系権限を一時的に付与する運用にしたいと考えました。 個人単位で追跡できない 共用ユーザーでログインするため、データベースから見ると誰が操作しても同じユーザーに見えます。 そのため、DB上のログイン主体を開発メンバー個人のGoogle Cloudアカウントと結びつけにくい状態でした。 人間のDBログインを個人のIAM認証に寄せることで、少なくともDBへのログイン主体は個人単位で扱えるようになります。 目指した状態 共用特権DBユーザーの課題を踏まえ、今回の移行では次の状態を目指しました。 人間によるDBへのログインを、共用ユーザーではなく個人のGoogle Cloudアカウントに紐づける 通常時は参照権限のみを付与する 書込系権限は常時付与せず、必要なときだけ一時的に付与する 書込系権限の付与申請を簡単に行えるようにする 書込系権限の付与には、申請者以外の承認を必須にする 付与した書込系権限は、作業後または定期実行で剥奪する 今回の構成では、Cloud SQLへのログインにはIAM認証を利用します。一方で、ログイン後にどのSQLを実行できるかはMySQL側の権限で制御します。 さらに、書込系権限は常時付与せず、必要なときだけ承認付きで一時付与して、作業後または定期実行で権限を剥奪します。 そのため、今回の構成ではログイン可否、DB内権限、権限の一時付与、付与後の剥奪を次のように分けて考えました。 項目 役割 利用する仕組み 認証 誰がCloud SQLへログインできるかを制御する Cloud SQL IAM認証 認可 ログイン後に何を実行できるかを制御する MySQLロール 一時付与 必要時だけ書込系権限を付与する GitHub Actionsの承認付きワークフロー 剥奪 一時付与した権限を戻す 手動または定期実行のREVOKEワークフロー この構成により、通常時は参照権限のみを使い、書込系権限が必要な場合だけ承認付きで一時的に付与する運用にしました。 全体構成 今回構築した仕組みは、通常時のログイン経路、必要時における書込系権限の一時付与フロー、一時付与した権限の剥奪フローに分かれます。 通常時は、開発メンバーがCloud SQL Studioから自分のGoogle CloudアカウントでCloud SQL for MySQLへログインします。 本記事ではCloud SQL Studioから接続する例で説明しますが、接続元はこれに限りません。IAM認証に対応した接続方式であれば、同じ考え方を適用できます。 Cloud SQLへのログイン可否はIAMで制御し、ログイン後に実行できるSQLはMySQLロールで制御します。通常時は、開発メンバーに参照用のMySQLロールのみを付与します。 IAM認証へ移行した後の通常時の構成は次のとおりです。 この状態では、開発メンバーはCloud SQL Studioから SELECT を実行できます。一方で、書込系権限は通常時には付与しません。 データ修正などで書込系権限が必要な場合は、GitHub Actionsの手動ワークフローを実行します。ワークフローはGitHub Environmentの承認待ちになり、申請者以外のメンバーが承認すると、対象ユーザーに書込系のMySQLロールを一時的に付与します。 書込系権限を一時付与する流れは次のとおりです。 一時付与した書込系ロールは、作業後の手動実行または定期実行で剥奪します。剥奪の流れは次のとおりです。 剥奪は権限を戻す操作であるため、今回の例では付与時のような承認ゲートは設けていません。手動実行または定期実行でGitHub ActionsからSQL実行基盤を起動し、MySQLロールの REVOKE を実行します。 以降のコード例では、次のプレースホルダーを使います。 プレースホルダー 意味 YOUR_PROJECT_ID Google CloudプロジェクトID YOUR_PROJECT_NUMBER Google Cloudプロジェクト番号 YOUR_MEMBER_NAME 開発メンバーのメールアドレスの @ より前の部分 YOUR_MEMBER_DOMAIN 開発メンバーのメールアドレスのドメイン YOUR_SA_NAME 権限操作用サービスアカウント名 YOUR_DB_NAME 対象のデータベース名 YOUR_TABLE_NAME 対象のテーブル名 YOUR_COLUMN_NAME 対象のカラム名 YOUR_WIF_POOL Workload Identity Pool名 YOUR_WIF_PROVIDER Workload Identity Provider名 YOUR_GITHUB_ENVIRONMENT_NAME 承認ゲートとして利用するGitHubのEnvironment名 IAM認証で個人ログインにする まず、個人のGoogle CloudアカウントでCloud SQL for MySQLへログインできる状態を作ります。 Cloud SQL for MySQLでIAM認証を利用するため、主に次の項目を設定しました。 Cloud SQLインスタンスでIAM認証を有効化する 開発メンバーごとのIAMデータベースユーザーを作成する Cloud SQLへログインするためのIAMロールを付与する Cloud SQL Studioを利用するためのIAMロールを付与する これらの設定は、Google Cloudコンソール、gcloud CLI、Terraformなどで行えます。MA部ではインフラ設定をTerraformで管理しているため、以降ではTerraformでの設定例を示します。 IAM認証の有効化 Cloud SQL for MySQLで IAM認証 を有効化するには、インスタンスのデータベースフラグ cloudsql_iam_authentication を有効にします。 resource "google_sql_database_instance" "main" { # name, database_version, region などは省略しています settings { database_flags { name = "cloudsql_iam_authentication" value = "on" } } } IAMデータベースユーザーの作成 次に、開発メンバーをIAMデータベースユーザーとして作成します。 人間のGoogle CloudアカウントをIAMデータベースユーザーとして作成する場合は、 type に CLOUD_IAM_USER を指定します。 resource "google_sql_user" "member" { project = "YOUR_PROJECT_ID" name = "YOUR_MEMBER_NAME@YOUR_MEMBER_DOMAIN" instance = google_sql_database_instance.main.name type = "CLOUD_IAM_USER" } この例では、IAMデータベースユーザーを YOUR_MEMBER_NAME@YOUR_MEMBER_DOMAIN として作成しています。 Cloud SQL for MySQLでは、IAMデータベースユーザーのメールアドレスの @ より前の部分をMySQL上のユーザー名として扱います。そのため、後続の GRANT では YOUR_MEMBER_NAME を指定します。 Cloud SQLへのログイン権限の付与 IAM認証でCloud SQLへログインするには、 cloudsql.instances.login 権限が必要です。この権限は、事前定義ロールの roles/cloudsql.instanceUser に含まれています。 resource "google_project_iam_member" "cloudsql_login" { project = "YOUR_PROJECT_ID" role = "roles/cloudsql.instanceUser" member = "user:YOUR_MEMBER_NAME@YOUR_MEMBER_DOMAIN" } また、本記事では開発メンバーがCloud SQL Studioから接続する前提のため、Cloud SQL Studioを利用するためのIAMロールも付与します。 resource "google_project_iam_member" "cloudsql_studio" { project = "YOUR_PROJECT_ID" role = "roles/cloudsql.studioUser" member = "user:YOUR_MEMBER_NAME@YOUR_MEMBER_DOMAIN" } ここまでで、開発メンバーが自分のGoogle Cloudアカウントを使ってCloud SQLへログインするための準備が整います。 ただし、IAM認証はCloud SQLへログインする主体を制御する仕組みです。ログイン後にどのSQLを実行できるかはMySQL側の権限で制御します。 MySQLロールでDB内権限を分ける Cloud SQLへのログイン可否はIAMで制御しますが、ログイン後にどのデータベースやテーブルに対して、どのSQLを実行できるかはMySQL側の権限で制御します。 MySQLにおけるロールは、複数の権限をまとめて管理してユーザーへ付与するための仕組みです。本記事では、Cloud SQL for MySQLのMySQL 8.0系でロールを利用する前提で説明します。 通常時と一時付与用のロール 今回は例として、次の2種類のMySQLロールを用意します。 ロール 用途 権限 viewer 通常時の参照用ロール SELECT editor 承認後に一時付与する書込系ロール SELECT , INSERT , UPDATE , DELETE 通常時は、開発メンバーに viewer のみを付与します。これにより、Cloud SQL Studioへログインした直後は参照のみ実行できる状態です。 一方、 editor は通常時には付与しません。データ修正などで書込系権限が必要になった場合だけ、後述するGitHub Actionsの承認付きワークフローから一時的に付与します。 ロールの分け方、付与する権限、対象範囲は、実際の運用や対象データによって調整が必要です。本記事では通常時の参照権限と、承認後に一時付与する書込系権限とで2つに分ける例として説明します。 ロールの作成 MySQL上にロールを作成して、参照や更新に必要な権限を付与します。 この例では、 YOUR_DB_NAME.* に対して権限を付与しています。実際の運用では必要以上に広い範囲へ権限を付与しないよう、対象データや作業内容に応じて、データベース単位、テーブル単位、権限種別を調整してください。 CREATE ROLE ' viewer ' , ' editor ' ; GRANT SELECT ON YOUR_DB_NAME.* TO ' viewer ' ; GRANT SELECT, INSERT, UPDATE, DELETE ON YOUR_DB_NAME.* TO ' editor ' ; 参照用ロールの付与 開発メンバーには、通常時の権限として viewer ロールを付与します。 また、データベースへのIAMログイン直後から標準で有効になるように、 viewer をデフォルトロールとして設定します。 MySQLユーザーは 'user'@'host' の形式で扱われます。本記事のサンプルでは 'YOUR_MEMBER_NAME'@'%' としており、 % は任意の接続元を表すhost部です。 実際の運用では、Cloud SQLへの到達経路やネットワーク制御に応じてhost部を調整してください。 GRANT ' viewer ' TO ' YOUR_MEMBER_NAME ' @ ' % ' ; SET DEFAULT ROLE ' viewer ' TO ' YOUR_MEMBER_NAME ' @ ' % ' ; GitHub Actionsを承認ゲートにして書込系ロールを一時付与する editor ロールが必要な場合は、GitHub Actionsの手動ワークフローから申請するようにしました。 この仕組みにおけるGitHub Actionsの役割は、Cloud SQLへの接続経路そのものではなく、書込系ロールを一時付与するための承認ゲートです。実際に GRANT を実行する処理はCloud SQLへ接続できる実行環境で行います。本記事ではCloud Buildを利用した例として説明します。 GitHub Environmentで申請者以外の承認を必須にする 今回利用するGitHub Actionsのワークフローの中では、 GitHub Environment を承認ゲートとして利用します。 Environmentには Required reviewers を設定し、 Prevent self-review を有効にします。ワークフローのjobで対象Environmentを指定すると、そのEnvironment上での実行前に承認を要求できます。 これにより、申請者以外のメンバーによる承認を挟んでから後続の処理を実行できるようになります。今回の例では、この承認ゲートを使って承認後に editor ロールを一時付与するようにしました。 権限操作用サービスアカウントの作成 承認後に GRANT を実行するため、権限操作用サービスアカウントを用意します。 権限操作用サービスアカウントは、GitHub ActionsからWorkload Identity Federation経由で利用します。承認後は、SQL実行基盤がこのサービスアカウントでMySQLへ接続し、対象ユーザーへ editor ロールを付与します。 このサービスアカウントもCloud SQLへIAM認証でログインできるようにするため、IAMデータベースユーザーとして作成しておきます。 resource "google_sql_user" "grant_sa" { project = "YOUR_PROJECT_ID" name = "YOUR_SA_NAME@YOUR_PROJECT_ID.iam.gserviceaccount.com" instance = google_sql_database_instance.main.name type = "CLOUD_IAM_SERVICE_ACCOUNT" } この例では、権限操作用サービスアカウントを YOUR_SA_NAME@YOUR_PROJECT_ID.iam.gserviceaccount.com として作成しています。 サービスアカウントの場合も、MySQL上では @YOUR_PROJECT_ID.iam.gserviceaccount.com を除いた部分をユーザー名として扱います。そのため、後続の GRANT では YOUR_SA_NAME を指定します。 権限操作用サービスアカウントがMySQL上で editor ロールを他ユーザーへ付与できるように、MySQL側では WITH ADMIN OPTION 付きで editor ロールを付与しておきます。 GRANT ' editor ' TO ' YOUR_SA_NAME ' @ ' % ' WITH ADMIN OPTION ; WITH ADMIN OPTION を付与されたユーザーは、そのロールを他のユーザーへ付与できます。つまり、この権限操作用サービスアカウントは書込系ロールの付与経路です。 そのため、Workload Identity Federationの条件やサービスアカウントの利用権限を絞る必要があります。想定したGitHubリポジトリ、ブランチ、Environment以外から利用されないようにしておきます。 承認後に実行するSQLは、最終的には次のような形です。 GRANT ' editor ' TO ' YOUR_MEMBER_NAME ' @ ' % ' ; ワークフロー設定例 GitHub ActionsからGoogle Cloudへの認証には、 Workload Identity Federation を利用します。 サービスアカウントキーをGitHub Secretsに保存せず、GitHub ActionsのOIDCトークンを使ってGoogle Cloudのサービスアカウントを利用できます。 GitHub Actionsの ワークフロー構文 を使った設定ファイルの例は次のとおりです。 name : grant-cloudsql-db-role on : workflow_dispatch : inputs : target_user : description : 対象ユーザーのIAMアカウントメールアドレス type : string required : true role : description : 付与するMySQLロール type : choice options : - editor required : true jobs : grant : runs-on : ubuntu-latest # このEnvironmentにRequired reviewersとPrevent self-reviewを設定する environment : YOUR_GITHUB_ENVIRONMENT_NAME permissions : contents : read # OIDCトークンを発行するために必要 id-token : write steps : - uses : actions/checkout@v4 # Workload Identity FederationでGoogle Cloudへ認証する - uses : google-github-actions/auth@v2 with : workload_identity_provider : projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/YOUR_WIF_POOL/providers/YOUR_WIF_PROVIDER service_account : YOUR_SA_NAME@YOUR_PROJECT_ID.iam.gserviceaccount.com - name : Grant DB role run : gcloud builds submit --config=grant.yaml --substitutions="_TARGET_USER=${{ inputs.target_user }},_ROLE=${{ inputs.role }} " ここでは、Cloud SQLがPublic IPを持たず、プライベート経路でのみ到達できる構成として、Cloud Build上で権限操作SQLを実行します。 今回の構成では、Cloud SQLへ到達できるVPC内に中継用Compute Engineインスタンスを配置しました。Cloud Buildからは、そのインスタンス上の Cloud SQL Auth Proxy を経由してCloud SQLへ接続します。 なお、SQLの実行経路は環境に依存します。Cloud SQLへの到達方式や既存の実行基盤によっては、GitHub Actionsのself-hosted runnerやCloud Run jobsなども選択肢になります。 一時付与した書込系ロールを剥奪する editor ロールは一時的な付与を前提としているため、作業後には剥奪できるようにします。 editor ロールの付与は権限を強める操作のため、GitHub Environmentによる承認を必須にしています。一方、 editor ロールの剥奪は一時付与した権限を戻す操作のため、今回の例では承認ゲートを設けていません。 ロール剥奪の方法には、手動実行と定期実行の2つを用意しました。 作業後に任意のタイミングで剥奪するための手動ワークフロー 戻し忘れを抑止するための定期実行ワークフロー いずれの場合も、GitHub ActionsからCloud Buildへ処理を渡し、権限操作用サービスアカウントでMySQLへ接続して REVOKE を実行します。 手動でロールを剥奪する 手動剥奪では、対象ユーザーを入力として受け取り、次のようなSQLを実行します。 REVOKE ' editor ' FROM ' YOUR_MEMBER_NAME ' @ ' % ' ; GitHub Actionsのワークフロー設定ファイルの例は次のとおりです。 name : revoke-cloudsql-db-role on : workflow_dispatch : inputs : target_user : description : 対象ユーザーのIAMアカウントメールアドレス type : string required : true jobs : revoke : runs-on : ubuntu-latest permissions : contents : read id-token : write steps : - uses : actions/checkout@v4 - uses : google-github-actions/auth@v2 with : workload_identity_provider : projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/YOUR_WIF_POOL/providers/YOUR_WIF_PROVIDER service_account : YOUR_SA_NAME@YOUR_PROJECT_ID.iam.gserviceaccount.com - name : Revoke DB role run : gcloud builds submit --config=revoke.yaml --substitutions="_TARGET_USER=${{ inputs.target_user }} " 定期実行でロールを剥奪する 手動での剥奪漏れを防ぐため、 editor ロールを定期的にも剥奪するように設定しておきます。 定期剥奪では、MySQL上の現在のロール付与状態を確認し、 editor ロールを保持しているユーザーを対象に REVOKE を実行します。 GitHub Actionsのワークフロー設定ファイルの例は次のとおりです。 name : revoke-cloudsql-db-role-scheduled on : workflow_dispatch : schedule : # 毎日 00:00 JST に実行する # GitHub Actions の cron は UTC 基準のため、15:00 UTC を指定する - cron : "0 15 * * *" jobs : revoke : runs-on : ubuntu-latest permissions : contents : read id-token : write steps : - uses : actions/checkout@v4 - uses : google-github-actions/auth@v2 with : workload_identity_provider : projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/YOUR_WIF_POOL/providers/YOUR_WIF_PROVIDER service_account : YOUR_SA_NAME@YOUR_PROJECT_ID.iam.gserviceaccount.com - name : Revoke all temporary DB roles run : gcloud builds submit --config=revoke-all.yaml この例でCloud Buildに渡している revoke-all.yaml では、MySQL上で editor ロールを保持しているユーザーを取得します。そのうえで、権限操作用サービスアカウントのDBユーザーを剥奪対象から除外し、残ったユーザーに対して順に REVOKE を実行します。 権限操作用サービスアカウントから editor ロールを剥奪すると、以降の GRANT や REVOKE を実行できなくなるためです。 運用上の注意点と今後の改善 今回の構成により、人間によるCloud SQL for MySQLへのログインを個人のIAM認証に寄せ、通常時に書込系権限を持たない運用にできました。 一方で、この仕組みをより安全に、かつ便利に運用するには、権限付与後のロールの使い方、ワークフロー入力値の扱い、ロール粒度などを継続的に見直す必要があります。 ここでは、今回の構成における運用上の注意点と、今後の改善余地を整理します。 一時付与したロールを使うときの注意 今回の例では、 editor ロールはMySQLユーザーに対するデフォルトロールとして設定していません。そのため、Cloud SQL Studioなどから一時付与された権限を使う場合は、実行するSQLと同じセッション内で SET ROLE を実行します。 特に、実行単位によってセッションが変わりうる接続方式では、 SET ROLE と対象SQLを同じ実行単位にまとめます。 SET ROLE ' editor ' ; UPDATE YOUR_TABLE_NAME SET YOUR_COLUMN_NAME = ' XXXXX ' WHERE id = XXX ; なお、Cloud SQL for MySQLでは activate_all_roles_on_login フラグを有効にすると、ログイン時に付与済みのロールを自動的に有効化できます。ただし、その場合は一時付与した書込系ロールもログイン時に自動で有効化されるため、通常時に有効化したいロールと一時付与するロールの扱いを踏まえて設計する必要があります。 ワークフロー入力値の検証 本記事では、GitHub ActionsからCloud Buildへ target_user や role を渡し、SQL実行基盤側で GRANT や REVOKE を実行する構成を例にしています。 ワークフロー入力値をもとにSQLを組み立てる場合、想定外のユーザー名やロール名がSQLに含まれる可能性があります。 そのため、GitHub Actions側で入力形式を絞るだけでなく、SQL実行基盤側でも許可したユーザー名やロール名だけを扱うように制御する必要があります。 SQL本文のレビューと監査 今回の構成では、GitHub Actionsから対象ユーザーへの editor ロールの一時付与を申請し、承認を挟むようにしています。 ただし、承認対象は editor ロールの一時付与です。承認後に実行されるSQL本文そのものは、今回の仕組みではレビュー対象にしていません。 SQL本文まで事前に確認する場合は、申請時に実行予定のSQLや作業内容を添付し、承認者による確認を挟む運用も考えられます。 実行後の追跡性を高めるには監査ログやDB監査機能を組み合わせ、誰がいつ、どの操作をしたかを確認できる状態にしておくことも重要です。 一時付与した権限の失効タイミングの厳密化 今回の例では、手動剥奪と毎日0時の定期剥奪で editor ロールを戻す構成にしています。 より厳密に制御したい場合は、付与時刻や申請IDを記録してユーザー単位で失効時刻を管理する設計が必要になります。 MySQLロールの粒度 本記事でのMySQLロールの例としては、書込系権限を editor ロールにまとめました。 ただし、 INSERT 、 UPDATE 、 DELETE 、各種DDLでは影響範囲が異なります。特に DELETE 、 DROP 、 ALTER のような操作は対象データや運用ルールによっては別ロールに分けるほうが安全です。 例えば次のように、MySQLのロールを細分化する余地があります。 ロール 権限 用途 viewer SELECT 通常調査 data_writer INSERT , UPDATE など 手動データ補正 data_deleter DELETE 削除が必要な例外対応 schema_editor CREATE , ALTER など スキーマ変更 schema_dropper DROP 破壊的DDL ただし、ロールを細かく分けるほど、申請フローや承認基準も複雑になります。そのため、対象データ、作業頻度、レビュー体制に応じてロール粒度を調整していく必要があります。 おわりに 本記事では、人間によるCloud SQL for MySQLへのアクセスを、共用の特権DBユーザーとパスワード認証から、IAM認証とMySQLロールを使った運用へ移行した例を紹介しました。 今回の構成では、Cloud SQLへのログインは個人のGoogle Cloudアカウントに寄せ、DB内の権限はMySQLロールで制御しています。通常時は参照用の viewer ロールのみを利用し、書込系権限が必要な場合だけGitHub Actionsの承認付きワークフローから editor ロールを一時付与する形にしました。 これにより、共用特権DBユーザーに依存した人間によるアクセスをやめ、強い権限が常時使える状態を避けられるようになりました。 SQL本文のレビューや監査、ロール粒度の細分化、失効タイミングの厳密化などは、引き続き改善の余地があります。 今回の対応を足がかりとして、運用負荷と安全性のバランスを見ながら改善に取り組んでいきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com

動画

書籍