ZOZOTOWNのマス配信バッチのリプレイス

ogp

はじめに

こんにちは、MA部の中原です。

MA部ではメルマガやLINE、アプリプッシュ通知を配信するためのマーケティングオートメーションシステムを開発・運用しています。

2022年からこのマーケティングオートメーションシステムをリプレイスするためのプロジェクトをMA部で進めています。リプレイス後の新しいマーケティングプラットフォームを「ZOZO Marketing Platform(略称:ZMP)」と呼んでいます。ZMPの概要については以下のテックブログをご覧ください。

techblog.zozo.com

本記事では、マス配信バッチのリプレイスについてご紹介します。

目次

配信の種類

ZOZOTOWNでは、マス配信とパーソナライズ配信の大きく2種類を配信しています。

マス配信 パーソナライズ配信
特徴 ・特定のセグメント(配信対象者)に対して特定のコンテンツ(届けたい情報)を特定日時に配信
・ユーザの利用状況に応じて表示するアイテムや並びなどをパーソナライズする場合もあり
・ある条件をトリガーとして一人ひとりにリアルタイムで配信
・ユーザの行動に応じて配信時間を最適化するものもあり
配信例 コスメに関心がありそうな世代・性別に対して、コスメに関するキャンペーン情報を配信 ・お気に入り登録したアイテムの在庫がわずかになった際の通知
・閲覧したアイテムの値下げ通知
など
マーケターの運用 キャンペーンの配信スケジュールやセグメント等を決め、CMSを使って運用 システムが特殊なつくりになっているため、設定を変更したい場合はマーケターからエンジニアに依頼

マス配信とパーソナライズ配信は基盤が分かれています。

システム全体の課題

システム全体で存在していた課題について以下のテックブログで詳しく紹介しています。

techblog.zozo.com

簡単にまとめると課題は大きく以下の3つがありました。

  • 配信の種類ごとに使用技術や基盤が異なり、運用・保守コストが肥大化
  • 運用・保守コストが大きいため、新規開発にリソースが割けない
  • マーケターのみでキャンペーン実施作業が完結しない

既存のマス配信バッチにおいては特に3つ目の課題が顕著でした。次の章以降では、既存のマス配信バッチに焦点を当て、システム内容、運用、課題、リプレイス内容を説明します。

既存のマス配信のシステムと運用について

既存システム

既存のマス配信の仕組みではDigdagを使用しています。Digdagとはオープンソースのワークフローエンジンで、複数のタスクをワークフローとして定義し、バッチ処理を行えるものです。キャンペーンの種類ごとにDigdagのワークフローが作成されており、クーポンメール用、タイムセールの告知メール用、アプリプッシュ通知用などがあります。これらのワークフローは、以下の大きく3つのステップで構成されています。

(1) 対象者抽出

対象者は基本的にアクティブな会員が対象です。BigQueryで作成したセグメントのビューを元に、特定の対象者に絞り込むこともあります。

(2) コンテンツの抽出

キャンペーンごとに様々な条件でコンテンツを抽出し、デザイン(お知らせ内容)を表示する上で必要なパラメータ(商品名や商品の値段など)を取得します。

(3) 配信処理

対象者とコンテンツのパラメータを組み合わせて配信リストを作成します。配信時にデザインのテンプレートにコンテンツ抽出時のパラメータが埋め込まれ、デザインが表示される状態で配信します。

運用

配信の設定は Google Sheets(スプレッドシート)Google Apps Script(GAS)を利用した簡易的なCMSから行います。マーケターはこのCMSを使って各キャンペーンの配信日時やセグメント、Digdagのワークフローに渡すパラメータ等を設定し、運用しています。Digdagのワークフローに渡すパラメータには、掲載商品をセール商品のみにするかどうかのフラグやBigQueryで作成しているビューなどがあります。これらのパラメータはDigdagのワークフローの処理内で、対象者や掲載商品の絞り込みに使用されます。以下に運用時の設定例を示します。

spread sheet

簡易的なCMSの詳しい仕組みについては以下のテックブログをご覧ください。

techblog.zozo.com

また、以下のようにDigdagのワークフローの作成や修正が必要な場合マーケターだけではできないのでエンジニアへ依頼します。

  • 新規キャンペーンの追加
    • エンジニアにDigdagのワークフローの作成を依頼し、CMSからそのワークフローを指定する
  • 生成するコンテンツの内容を変更
    • 表示するおすすめ商品から特定の商品を除外する等
  • 機能の追加
    • ABテストの機能を追加する等

既存のシステムと運用の課題

既存のマス配信バッチと運用には以下の課題がありました。

マーケターの自由度が低い

紹介したように既存の仕組みはエンジニアありきの運用だったため、マーケターが主体となってキャンペーンの実施ができない状態でした。

既存のマス配信バッチでは、新規キャンペーンの開発や条件の変更が必要な場合、エンジニアに依頼する必要がありました。そのため、キャンペーン実施までのリードタイムが長くなってしまいます。

また、既存のシステムでは機能不足より実現できないことがありました。例えば既存のマス配信バッチには、ABテストの機能が標準で備わっていませんでした。ABテストとは、複数のバリエーションを用意し、それぞれの効果を比較することで最適な配信をするための手法です。各キャンペーンの処理が共通化されていなかったため、エンジニアはマーケターからの依頼がある度にそのキャンペーンに対してABテストのロジックを実装する必要がありました。また、バリエーションの数やそれぞれに対象者を振り分ける比率は外部(CMS)から指定できない仕組みだったため、マーケターが自由に設定できずABテストの実施のハードルが高くなっていました。

簡易的なCMSの限界

簡易的なCMSはあるものの、スプレッドシートとGASでは操作性やバリデーション等のできることが限られているため、マーケターの運用ミスが起きやすい状態でした。

配信処理が基盤ごとに分かれている

先述の通り、マス配信とパーソナライズ配信では基盤が分かれています。メール配信の場合は外部のサービスにリクエストを送って配信します。それぞれの基盤から1つのメール配信サービスにリクエストを送るため、大量に配信すると流量制限ができずメール配信サービスに負荷をかけてしまい配信の遅延やエラーが時折発生していました。そのため、配信基盤を1つにしてメール配信サービスへのリクエスト流量をコントロールすることが求められていました。

リプレイス概要

マス配信バッチのリプレイス概要は以下の通りです。

  1. キャンペーンごとのDigdagのワークフローの処理を共通化
  2. 新規開発した管理画面からのパラメータを使う
  3. ABテストの導入
  4. テスト配信の仕組みづくり

1. キャンペーンごとのDigdagのワークフローの処理を共通化

ZMPの概念モデルに沿ってキャンペーンごとのDigdagのワークフローの処理を共通化・汎用化しました。概念モデルとは、ZMPにおけるデータの構造やデータの流れ、データの管理方法などを定義したものです。以下に概念モデルの一部を示します(初期検討時のもの)。

concept model before

概念モデルの詳しい内容についてはこちらのテックブログをご覧ください。

以下はリプレイス前とリプレイス後のワークフローの実装イメージです。

common workflow

既存のマス配信システムでは、「クーポンメール用」「タイムセールメール用」のようにキャンペーンの種類ごとにDigdagのワークフローが分かれていました。共通化することで1つのワークフローでチャネルやキャンペーンに関わらずすべての配信が可能になりました。また、マーケターの自由度の高い設定内容で柔軟に処理できるようにしました。

2. 新規開発した管理画面からのパラメータを使う

既存のマス配信バッチでは、スプレッドシートとGASを使って設定内容をBigQueryに登録し、その情報を使ってDigdagのワークフローを実行していました。新基盤では、スプレッドシートではなく、ZMPの管理画面から設定された情報を元にします。その他の仕組みは基本的に既存と同じにしました。

新基盤の仕組み

新基盤の仕組みは以下の通りです。

ma-batch structure

(1) キャンペーン設定

マーケターがZMPの管理画面でキャンペーンの設定情報を登録します。

(2) 登録

管理画面で登録された情報をBigQueryに登録します。登録する情報は既存のスプレッドシートに似た内容です。例えば、キャンペーン名、配信日時、セグメント、コンテンツの内容などです。

(3) 配信データ取得

3分おきに動いているDigdagのスターターというワークフローがBigQueryに登録されているデータを参照し、配信日時を迎えたキャンペーンの設定情報を取得します。スターターの仕組みについてはこちらのテックブログで紹介しているものと同様です。

(4) 実行

(3)で取得した情報をパラメータとしてマス配信バッチ処理のDigdagのワークフローに渡して実行します。以下にパラメータ例を示します。

campaign:
  id: 1
  name: "クーポンメール"
  segment:
    id: 1
    query: "select * from `project.dataset.target_segment`"
  action:
    id: 1
    channel: "mail"
    ab_test:
      settings:
        salt: 100 # ハッシュ生成のためのsalt
        control_group:
          percentage: 20
        treatment_group:
          - contents_id: 1
            percentage: 40
          - contents_id: 2
            percentage: 40
    contents:
      - id: 1
        name: "クーポンメール_パターンA"
        template:
          subject: "クーポンメール_パターンAの件名"
          body: "クーポンメール_パターンAの本文"
      - id: 2
        name: "クーポンメール_パターンB"
        template:
          subject: "クーポンメール_パターンBの件名"
          body: "クーポンメール_パターンBの本文"
      jobs:
        - id: 1
          project: "sample-project" # Digdagのプロジェクト名
          workflow: "coupon_mail_job" # Digdagのワークフロー名

(5) 実績データ登録

BigQueryに実績データを登録します。実績データは配信したユーザの情報や配信したコンテンツの情報などです。既存のマス配信バッチでは細かい実績データは登録していませんでしたが、過去に配信したキャンペーンの分析や調査がしやすくなるように様々な実績を登録します。また、処理のステータス(実行前・実行中・成功・失敗)も書き込み、配信処理の状況を管理画面から確認できるようにします。

(6) 配信リクエスト

配信処理を行う配信基盤にリクエストして配信します。

3. ABテストの導入

既存のマス配信バッチで一部実装されていたABテストの仕組みを共通処理として導入しました。ABテストの設定がある場合、対象者を管理画面から指定された比率で振り分けます。具体的には、FARM_FINGERPRINTでハッシュを生成し、ABSで絶対値に変換し、MODで値が0から100に収まるように調整します。その値を使って、指定された比率でランダムに分割します。サンプルクエリを以下に示します。波括弧で囲まれた部分は管理画面から設定された値です。

WITH target_segment_with_seed AS (
  SELECT
    unique_id, -- ユニークID
    member_id, -- 会員ID
    email_id, -- メールID
    push_notification_uid, -- アプリプッシュID
    mid, -- LINE用のID
    CASE
      WHEN member_id IS NOT NULL THEN CAST(member_id AS STRING)
      ELSE
        COALESCE(CAST(email_id AS STRING), '') || '_'
        || COALESCE(push_notification_uid, '') || '_'
        || COALESCE(mid, '')
    END AS seed
  FROM
    `project.dataset.target_segment`
),

split_target_segment AS (
  SELECT
    unique_id,
    member_id,
    email_id,
    push_notification_uid,
    mid,
    MOD(ABS(FARM_FINGERPRINT(CAST(seed AS STRING) || CAST('{{ salt }}' AS STRING))), 100) AS treatment
  FROM
    target_segment_with_seed
)

SELECT
  unique_id,
  member_id,
  email_id,
  push_notification_uid,
  mid
FROM
  split_target_segment
WHERE
  {{ rate_from }} <= treatment
  AND treatment < {{ rate_to }}
;

4. テスト配信の仕組みづくり

既存のマス配信バッチでは、テスト配信対象者にしか配信されない設定になっているQA環境で本配信同等の配信設定をしてテスト配信を行なっていました。新基盤では、本番環境のZMPの管理画面からテスト配信ができるようにしました。具体的な仕組みは以下の2つです。

  • データセットを分ける
  • 配信リストを上書き

データセットを分ける

本配信とテスト配信でBigQueryのデータセットを分けました。これにより、本番環境で本配信とテスト配信のデータが混ざることを防ぎます。マス配信のバッチ処理のDigdagのワークフローにテスト配信かどうかを判別するためのパラメータを追加し、データの書き込みや参照するデータセットを切り替えるようにしました。

配信リストを上書き

テスト配信時は管理画面からテスト配信対象者を選択します。本配信と同じ処理を行い配信リストを作成した後、ランダムにデータを選び、テスト配信対象者のみになるように上書きします。この際、ユーザの名前やメールアドレスのような個人情報はテストアカウントのデータに置き換えます。

5. 配信処理を配信基盤に任せる

既存のマス配信バッチの中で行っていたメールの配信処理を配信基盤に任せます。先述の通り、メール配信サービスへのリクエスト流量をコントロールするためにどの基盤からも使えるメール配信基盤が必要になり作成したので、それを使うことにしました。ZMPではメールの配信基盤を新規で作成し、以下のテックブログで紹介しています。

techblog.zozo.com

リプレイスの課題

前の章で述べた通り、キャンペーンの種類ごとに分かれていたDigdagのワークフローは、概念モデルに沿って抽象化することで共通化しました。これにより、1つのワークフローでチャネルやキャンペーンに関わらずすべての配信が可能になり、管理画面でワークフローを意識する必要がなくなりました。また、ABテストの導入やテスト配信の仕組みも組み込まれたことで、マーケターの自由度が高い設定内容で柔軟に処理できるようになりました。しかし、任意のタイミングで特殊な処理をしたい場合があります。例えば以下のような場合です。

  • 特定のセグメントを抽出する前にセグメントのビューの中で参照しているテーブルが更新されるのを待ちたい
  • コンテンツ抽出時にキャンペーンによって異なる方法で抽出したい
  • 配信前に特定の外部サービスやZOZOTOWN本体にデータ連携やAPIリクエストをしたい

1つ目について例をあげて説明します。例えば、セグメントのビューの中で別のバッチによって更新されるテーブルを参照する場合、そのテーブルが更新された後に対象者を抽出しないと古いデータを参照してしまい、送りたい対象者に送ることができません。そのため、セグメント抽出前に「テーブルの更新を待つ」という処理が必要になります。

上記以外にも今後任意のタイミングでしたい処理が出てくる可能性があるため、別の概念を導入して補う必要がありました。

解決策

ジョブの導入

concept model after

ZMPの概念モデルに初期検討時にはなかったジョブという概念を導入することで、必要に応じて任意のタイミングで特殊な処理ができるようになりました。ジョブとは指定されたDigdagのワークフローを実行できるようにするための概念です。特殊な処理が必要な場合、事前にジョブとしてその処理を行うDigdagのワークフローを作成しておきます。ジョブの情報はZMPの管理画面から設定され、指定されたタイミングでそのワークフローを実行することで、特殊な処理が必要な場合に補うことができます。

バッチ処理の流れ

コンテンツ抽出時にジョブ(特定のセグメントを抽出する前にセグメントのビューの中で参照しているテーブルが更新されるのを待ちたい)を設定した場合を例に、バッチ処理の流れを以下に示します。

batch flow segment job

  • 対象者抽出
    • 管理画面から指定されたジョブを実行し、セグメントのビューの中で参照しているテーブルが更新されるのを待ちます。
    • ジョブ完了後、管理画面から指定されたセグメント(BigQueryのビュー)で対象者を抽出します。
  • コンテンツ抽出
    • 管理画面から指定されたコンテンツ情報に基づいてコンテンツを抽出します。
    • 対象者とコンテンツのパラメータを組み合わせて配信リストを作成します。
  • 配信処理
    • 配信リストを使って配信基盤にリクエストして配信します。

例としてあげた「特定のセグメントを抽出する前にセグメントのビューの中で参照しているテーブルが更新されるのを待ちたい」の場合、事前に対象の更新バッチを待つジョブを作成することになります。管理画面からはそのジョブを選択できます。また、ジョブはセグメント抽出の前か後かなど、実行するタイミングも指定できます。セグメント抽出の前処理として作成したジョブを指定することで、セグメント抽出前にジョブが実行され、セグメントのビューの中で参照しているテーブルが更新されるのを待つことができます。

まとめ

マス配信バッチのリプレイスについてご紹介しました。任意のタイミングで特殊な処理をするための仕組みが必要でしたが、特殊な処理が必要な場合でもジョブという概念で補うことができました。汎用的で柔軟なシステムの概念モデルを構築できたと思います。汎用化したことでデータ管理が統一でき、統一した方法で管理画面でのキャンペーン登録ができるようになりました。本記事が皆様の参考になりましたら幸いです。

さいごに

ZOZOでは一緒にプロダクトを開発してくれるエンジニアを募集しています。ご興味のある方は下記リンクからぜひご応募ください!

hrmos.co

hrmos.co

カテゴリー