entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

「鮮度」と「精度」を両立させる広告データ基盤のつくり方

はじめに

アドプラットフォーム事業部でアナリティクスエンジニアをしているucchi-です。

ピクシブは、2022年11月24日に「pixiv Ads」という広告ネットワークをリリースしました。広告主は、pixivに広告を少額予算から簡単に出稿できます。

ads.pixiv.net

pixiv Adsのデータ基盤では、大きく分けて以下の課題と向き合っています。

  • 鮮度:広告の配信状況をリアルタイムに見たい
    • クライアントは、ユーザーに広告を届けるため、常にお金を払い続けます。広告配信条件を誤ると、それだけお金を無駄にしてしまうため、少なくとも数十分以内に配信状況を確認できる必要があります
  • 精度:広告の支払い金額は1円の誤差も許さない
    • クライアントは、広告の配信ログから集計した配信実績に基づきお金を支払います。請求金額が絡むため、非常に高い精度のデータ品質が求められます

これらの課題に対し、データチームがどのような施策を打ってきたのかを紹介します。

鮮度の維持

データの鮮度を保つためには、閲覧やクリックといったイベントが発生してから、クライアントが管理画面上でデータを参照するまでの段階の数を減らしたり、各段階の遅延時間を短くしたりできるかが鍵です。

アーキテクチャの工夫による遅延時間の短縮

pixiv Adsでは、BigQueryにデータ基盤を寄せることで遅延時間を短縮しています。

まずは、データソースについてです。広告配信ログの取り込みにはCloud Loggingのシンク、DBの同期にはDatastream for BigQueryを使っています。どちらもイベントをほぼリアルタイムに取得できます(おおむね数秒〜数十秒の遅延)。

次に、データの利用についてです。pixiv Adsでは、Lookerのダッシュボードを広告管理画面に埋め込んでいます。クライアントは、Lookerを操作してBQにクエリを直接発行するので、BQからDBへのデータ書き込みによる更新を待たされることなくデータを取得できます。

バッチ処理における遅延時間の削減

最後に、BigQueryで行う処理について見ていきます。閲覧ログやクリックログなどの生ログはそのままだと扱いづらいので、集約して広告配信ログを作ります。また、配信ログをさらに集約して配信サマリを作り、テーブル読み込みコストを下げます。

多くの場合では、配信サマリを日別で作成し、配信ログや配信サマリの更新をデイリーバッチですませると思います。しかし、pixiv Adsのクライアントは最新の値を求めています。そこで、日毎に実行する低頻度バッチとは別に、10分毎に速報値を計算する高頻度バッチを用意します。

高頻度バッチを用いて速報値を作る

高頻度バッチの実行頻度を上げるほど、遅延時間は短くなります。一方で、広告配信ログのサイズは膨大(種類によっては1日あたり2億行を超える)なので、やみくもにクエリを叩くわけにはいきません。

そこで、HOURでパーティションを切った時間別の配信サマリと、DATEでパーティションを切った日別の配信サマリを用意します。10分ごとに以下を実行し、速報値を更新します。

  1. 生ログを用いて配信ログを更新。期間は2時間前から現在まで
  2. 配信ログを用いて時間別サマリを更新。期間は、2時間前の00分から現在まで
  3. 時間別サマリを用いて日別サマリを更新。期間は当日0時から現在まで

ポイントは、速報値の生成には直近2時間分しか生ログを参照しないことです。読み込みコストを許容可能な範囲に抑えつつも、大半のクリックログやCVログを配信実績に考慮できます。高頻度バッチは精度より鮮度を重視します。

精度の担保

高頻度バッチは、直近2時間分しか生ログを参照しません。なので、2時間以上後に発生するクリックやCVを配信実績に反映できません。また、2時間以内にイベントが発生しても、ログの到着が遅延してしまうと、配信実績に反映できないことがあります。

低頻度バッチを用いて確定値を作成する

そこで、低頻度バッチを実行して広告配信ログの値を確定させます。この値をSSOT、つまり「信頼できる唯一の広告配信実績」であると定め、これを基準に配信サマリを更新します。低頻度バッチは鮮度より精度を重視します。

以下の手順で示される低頻度バッチを、毎朝実行します。

  • 生ログから配信ログを更新。期間は8日前から前日まで
  • 配信ログから時間別サマリを更新。期間は8日前から前日まで
  • 時間別サマリから日別サマリを更新。期間は8日前から前日まで

ここで8日間のデータを参照するのは、pixiv Adsが7日後までに到着するCVを考慮するからです。

確定値の一意性を計測する

広告の配信実績の精度を高めるために、一意性をテストします。主キーに対してログが重複しないかを調べます。一意性の計測は、SSOTである広告配信ログに対してのみ行います。

SELECT
  primary_key
  , COUNT(*) AS count_log
FROM
  `log`
GROUP BY
  primary_key
HAVING
  IF(count_log >= 2, ERROR("Error: ログが重複しています"), true);

確定値の正確性を計測する

広告配信ログが正確でも、配信サマリや分析用のログ、サマリを更新するときに数値が異なってしまうかもしれません。そこで、更新する値が生成元と一致することを確かめ、一致しなければ更新を中断します。正確性の計測は、配信サマリや分析用の配信ログなど、SSOTである配信ログから見て下流のファクトテーブルを更新するときに行います。

SELECT
  IF(
    (
      SELECT SUM(value) FROM `source` WHERE {更新範囲}
    ) = (
      SELECT SUM(value) FROM `target` WHERE {更新範囲}
    ), "OK", ERROR("NG")
  );

更新バッチを冪等に作り、耐障害性を向上させる

どれだけ厳密にデータ更新ワークフローを組んでも、想定外の事象は起こります。更新バッチを冪等に組むことで、データを再生成する際の手間や人為的なミスを減らし、データの精度を更に上げることができます。

具体的には、マルチステートメント・トランザクションを用いて、データの更新をアトミックにcommitまたはロールバックします。

BEGIN TRANSACTION; -- トランザクションの開始

DELETE FROM `target` -- 更新範囲の古いデータを削除
WHERE {更新範囲};

INSERT INTO `target`( -- 更新範囲の新しいデータを作成・追加
  {更新クエリ}
);

-- 必要に応じて、更新データが正しいかテストを実行
SELECT
  IF("何かしらのテスト", "OK", ERROR("エラー")) AS some_test

COMMIT TRANSACTION; -- テストが正常終了すれば実際に値を更新

まとめ

pixiv Adsでは「鮮度」と「精度」を両立させる広告データ基盤を作っています。

BigQueryにデータソースをリアルタイムに繋ぎこみ、高頻度バッチで速報値を計算することで、最新のデータをクライアントに提供しています。一方で、低頻度バッチで確定値を計算し、それを信頼できる唯一の情報源とすることで、データの精度を担保しています。

おわりに

pixiv Adsはリリースしたばかりのプロダクトです。データチームは、データ基盤やBIダッシュボード、広告オークション、MLなど、データの上流から下流までを総合的に改善することで、今後もデータに基づく意思決定や広告効果の改善に取り組んでいきます。

icon
ucchi-
アド・プロダクト部のデータチームでアナリティクスエンジニアとして働いています。百合とデータ分析と美味しいご飯が趣味。