インプレッショントラッカー実装リファレンス
このページは、フリークエンシーキャップデータフロー 境界の背後に位置するインプレッショントラッカーの 非規範的リファレンスコンテンツ です。プロトコルは次のみを制約します:- ワイヤー仕様 — TMP 仕様 を参照。
- Identity Match サービスが満たさなければならない適合性不変条件 — TMP 仕様 でも規範的。
- キャップ発火境界コントラクト — フリークエンシーキャップデータフロー で定義。
adcp-go/targeting で実装されたもの — を文書化し、他の実装者が実践的なリファレンスを持てるようにします。
クロスアイデンティティ重複排除問題
ユーザーへの単一のインプレッションは、しばしば同じ TMPX 内で複数のアイデンティティ(RampID、ID5、MAID、UID2、パブリッシャー発行トークンなど)に解決されます。アイデンティティごとにカウントする素朴なインプレッショントラッカーは、1 つのインプレッションをユーザーのキャップに対して 2〜3 としてカウントします。バイヤーがアイデンティティグラフを実行する場合、バイヤーはカウント前にアイデンティティを正準化できます。バイヤーがグラフなしまたは部分的にグラフ化されている場合(一般的 — Scope3 のホストされた Identity Match はグラフなし)、正準 id は存在しません。 カウンターベースのアプローチは、アイデンティティごとのカウンターを読むときmerge_rule(MAX / OR / SUM)でこれを取り繕います。どのマージルールも一般には正しくありません。病理的なケースは、インプレッションをまたいだアイデンティティ解決のトグルです: 一部のインプレッションは rampid のみを解決し、一部は rampid と id5 の両方を解決します。MAX マージされたカウンターは過少カウント、SUM は過剰カウント、OR は 1 より多くを表現できません。どちらにせよキャップが誤ったタイミングで発火します。
リファレンス実装は、impression_id スキームで merge-rule 問題を完全に回避します: インプレッションごとに 1 つの id、すべての解決されたアイデンティティのログに書き込み、読み取り時に id で重複排除。カウントは、アイデンティティが上流で正準化されているかにかかわらず正確です。
impression_id ルール
インプレッショントラッカーはインプレッションごとに 1 つのimpression_id を維持し、すべての解決されたアイデンティティのログに書き込みます。読み取り時に、ユーザーのすべてのアイデンティティログをスキャンし impression_id で重複排除すると、distinct-impression カウントが正確に復元されます。
必要なプロパティ:
- すべてのセラー、ソース、時間にわたってグローバルに一意。 バイヤーエージェントは多くのセラーから供給されるインプレッションを提供します。セラーをまたいだ衝突は distinct なインプレッションを黙ってマージしキャップを過少カウントします。十分なエントロピーを持つ任意の衝突耐性のある識別子スキームが許容されます — UUID(任意バージョン)、ULID、snowflake、または同等物。プロトコルは形式をピン留めしません。任意の層(パブリッシャー、決定層、またはデコード時のバイヤー)で鋳造された値は、他のすべての当事者にとって不透明な文字列です。
- 値の 3 つの有効なソース、優先順位順。
impression_idは (a) パブリッシャー自身のファーストパーティコード、(b) 広告決定層(Prebid TMP モジュール、アドサーバー、SSP)、(c) TMPX デコード時のバイヤーのインプレッショントラッカーによって生成されます。層 (a) と (b) は、{IMPRESSION_ID}ユニバーサルマクロ経由でピクセル URL に値を代入することでバイヤーに値を配信します。それらの違いは運用上 — パブリッシャーのスタックで誰が鋳造するか — で、ピクセルが到着するとバイヤーには不透明です。 - バイヤー消費ルール。 バイヤーは、存在するとき
{IMPRESSION_ID}をピクセル URL から消費しなければならず(MUST)、欠如のときのみデコード時鋳造にフォールバックします。バイヤーがデコード時に鋳造するとき、TMPX nonce をimpression_idとして再利用してはなりません(MUST NOT) — TMPX nonce は Identity-Match-評価ごとで、サーブウィンドウ内のすべてのインプレッションで共有されるため、衝突します。 - コンテキストのみの要件。 コンテキストのみのインプレッション(ピクセルに
{TMPX}代入なし)については、デコード時のバイヤー側鋳造は不可能 — 層 (a) または (b) からの{IMPRESSION_ID}が唯一の利用可能なソースです。パブリッシャーと決定層は、TMP コンテキストのみのインプレッションに{IMPRESSION_ID}を含めなければなりません(MUST)。インプレッショントラッカーは、その欠如を統合エラーとして扱い、ログし、露出書き込みをスキップするか、ピクセル発火ごとのワンショット識別子にフォールバックすべきです(SHOULD)。フォールバックは、クロスアイデンティティ重複排除を保持できないため劣化しています。 - インプレッションごとに 1 つの id、そのインプレッションのユーザーのすべての解決されたアイデンティティログに書き込む。 アイデンティティごとに異なる id を生成すると重複排除コントラクトが壊れます — 同じインプレッションが解決されたアイデンティティごとに 1 回カウントされます。
- ピクセルリトライは別の関心事。 同じピクセルが 2 回発火する(ネットワークリトライ、ページリフレッシュなど)ことは、2 つの
impression_idを鋳造してはなりません — 2 つを鋳造するとピクセルリトライがキャップに対して二重カウントします。ピクセル URL の冪等性キーまたはIdempotency-Keyヘッダーでインバウンドリクエストを重複排除するか、リトライからの小さな過剰カウントを fcap 目的で無害として受け入れるかのいずれか。クロスアイデンティティ重複排除とピクセルごとの冪等性は、異なる緩和を持つ異なる問題です。(小文字の表現: このページは非規範的です。フリークエンシーキャップデータフロー ページの境界コントラクトが適合性テストが引用するものです。)
fcap_keys ラベルモデル
キャップは、インプレッション書き込み時にdimension:value ラベルでタグ付けされます。パッケージはどのラベルにマップするかを宣言し、fcap ポリシーは各ラベルに window と max_impression_count を付けます。
fcap_keys は ["campaign:42", "campaign_group:7", "advertiser:13"] です。キャップが発火したかを評価するとき、そのポリシーのウィンドウ内で各ラベルに一致するエントリをログでスキャンします。
ウィンドウ unit は負荷を担う、単なる人間可読の省略形ではありません。リファレンス実装は unit をスライディングウィンドウのバケットサイズとして使います: unit: "hours" は時間単位のバケットに対して評価し、unit: "minutes" は分単位のバケットに対して評価します。duration 的に等価に見える 2 つのポリシー — {interval: 2, unit: "hours"} 対 {interval: 120, unit: "minutes"} — は 同じウィンドウ長 だが 異なるキャップ後再評価ケイデンス を持ちます。ユーザーが 2 時間バケットキャップに達した後、新しいトラフィックを許可する次の適格性チェックは次の時間バケット境界で起こります。120 分バケットポリシーでは、次の分バケット境界で起こります。より小さい数字に収まる duration ではなく、望むケイデンスに合わせて unit を選んでください。
文字セット制約。 各セグメントは [a-zA-Z0-9_-]+ に一致するため、: デリミタは曖昧でありません。URL を運ぶまたは他にコロンを運ぶ値はハッシュ化または短縮されなければなりません。
マルチテナントオペレーター は通常、共有状態上の広告主組織をまたいだキー衝突を防ぐデプロイ慣例として、テナントプレフィックス(buyer-acme:campaign:42)を採用します。これはオペレーターポリシーであり、プロトコルではありません。
なぜ階層ではなくラベルか。 キャップ次元は顧客をまたいで異種です — 一部はクリエイティブでキャップし、一部はラインアイテムで、一部は広告主ロールアップで。固定スキーマは過剰規定するか過少提供するかのいずれかです。ラベルはクロスセラーキャップも自動にします: キーがセラーをまたいで共有される任意のポリシー(例: buyer-acme:advertiser:13)は、追加モードなしにそれらすべてにわたって強制します。横断的なポリシーは明示的です — キャンペーンごとと広告主ごとの両方のキャップが必要なキャンペーンは、両方のキーを宣言し 2 つのポリシールックアップを得ます。
リファレンスデータモデル(valkey 裏付け、ログベース)
下のレイアウトはadcp-go/targeting が使うものです。任意のバックエンド(Aerospike、DynamoDB、インメモリ、何でも)が問題ありません。データ形状はリファレンスであり、要件ではありません。
露出ログ(アイデンティティごと)
HashToken は 16 バイトの SHA-256 プレフィックス、16 進エンコード。バイナリエントリエンコーディングがログをコンパクトに保ちます(exposure_binary.go) — 典型的なユーザーの 30 日ログは数 KB です。
各エントリは記録します:
impression_id— TMPX デコード時に生成。このインプレッションのすべてのアイデンティティログで同じ値。fcap_keys[]— このインプレッションがカウントするラベル。timestamp— unix 秒。
Fcap ポリシー(fcap_key ごと)
window.unit(minutes/hours/days/weeks/months)から導出され、ウィンドウ長は interval × unit です。エントリタイムスタンプの秒ごとの >= フィルターではなくバケットレベルのフィルターが、本番が使うものです — これがキャップ発火後の再評価ケイデンスをポリシーの unit から予測可能にします。
パッケージ設定(パッケージごと)
書き込みパス: ピクセル → ログ
ピクセル発火時に、インプレッショントラッカーは:- アイデンティティとパッケージコンテキストを解決する。
{TMPX}が存在するとき、TMPX をデコード(HPKE 復号 + バイナリパース) → 解決されたアイデンティティ +(seller_agent_url, package_id)。{TMPX}が欠如するとき(コンテキストのみのインプレッション)、他のピクセルパラメーターからパッケージコンテキストを解決し、解決されたアイデンティティセットは空 — 露出は、アイデンティティごとではなく上流で鋳造された(パブリッシャーまたは決定層)impression_id でキー付けされた単一のコンテキストのみのログに書き込まれる。 - パッケージの
fcap_keysをルックアップする。 impression_idを取得する。 ピクセル URL が{IMPRESSION_ID}(パブリッシャーまたは広告決定層によって上流で鋳造 — バイヤーはどちらかを区別できず、する必要もない)を運ぶ場合、その値を使う。そうでなければ 1 つ鋳造する — ただし{TMPX}が存在するときのみ。コンテキストのみのインプレッションでは、劣化モード動作について上のルールセクションのルール #2 を参照。- 各解決されたアイデンティティについて、
{impression_id, fcap_keys, timestamp}をuser:exposures:{hash(identity)}に追加する。最も長いアクティブウィンドウ(デフォルト 30 日)より古いエントリを刈り取る。解決されたアイデンティティのないコンテキストのみのインプレッションについては、エントリは適用される配信カウントログにのみ書き込まれる — 重複排除するアイデンティティがないとき、クロスアイデンティティ重複排除は意味を持たない。
engine.go:478) — 同じユーザーの並行書き込みは露出を失いうる。リファレンス実装はこれを明示的に受け入れます。競合下の過少カウントは fcap 目的で無害です。Lua または Store.Append 拡張経由のアトミック追加は延期された最適化です。
このインプレッションがキャップを使い果たしたかの評価
露出を書き込んだ後、インプレッショントラッカーは任意のキャップがちょうど発火したかを決めます。パッケージは通常複数のfcap_keys(campaign、campaign_group、advertiser、…)にマップし、それぞれ独自のポリシーを持ちます。ポリシーは独立して評価され、そのうち いずれか 1 つ がそのウィンドウ内で max_impression_count に達したときキャップが発火します。 ユーザーは、広告主ごとのポリシーに一度も近づかずにキャンペーンごとのポリシーでパッケージをキャップされうるし、逆も同様です。
露出の各 fcap_key について、インプレッショントラッカーはユーザーのアイデンティティログをスキャンします:
- すべての解決されたアイデンティティについて
user:exposures:{h}を読む。 - エントリを、
policy.windowをまたぐ現在+以前のバケットに落ち、fcap_key ∈ entry.fcap_keysのものにフィルターする。 - ユーザーのすべてのアイデンティティログにわたって
impression_idで重複排除する。 - 重複排除されたカウントを
policy.max_impression_countと比較する。
>= max_impression_count なら、このインプレッションでキャップが発火しました。インプレッショントラッカーは次に、パッケージが使い果たされた fcap_key にマップするすべての (user_identity, package_id) について、Identity Match キャップ状態ストアにキャップ発火エントリを書き込みます。有効期限は policy.window の現在のバケットの終わり(バケットセマンティクスの下で最も古いスコープ内露出が期限切れになるとき)です。
複数のセラーの複数のパッケージにマップする広告主レベルのラベル(advertiser:13)のキャップについては、インプレッショントラッカーは影響を受ける (user_identity, seller_agent_url, package_id) ごとに 1 つのキャップ発火エントリを発します — main の 境界コントラクト はパッケージスコープなので、クロス次元のキャップは書き込み時にファンアウトします。
SDK プリミティブ
SDK は、インプレッション処理を 1 つのバンドルされた呼び出しではなく、2 つの合成可能な関数として出荷します。本番トラッキングエンドポイントは通常、取り込み時にデコードし、下流のワーカーに独自のペースでストアを書かせます。decode+write を単一の関数にバンドルすることは、同期トポロジーを強制しバッファリングを妨げます。mode_base 準拠の HKDF-SHA256)。暗号化は TMPX を発する Identity Match サービスが必要とし、復号は decodeTmpx を呼ぶインプレッショントラッカーが必要とします。
同じサーフェスが @adcp/client(TS)、adcp-go、adcp(Python)で出荷されます。
プリミティブ名は説明的です。decodeTmpx、writeExposure、upsertPackage、upsertFcapPolicy、inspectExposuresは SDK サーフェスの形状を記述します。正準署名は対応する SDK RFC とともに着地し、命名や引数順で異なる場合があります。このセクションを API コントラクトとしてではなく、インプレッショントラッカーの分解として扱ってください。
本番トポロジーパターン
典型的な Scope3 スタイルのデプロイ:適合性シナリオ
これらはインプレッショントラッカーの動作をエンドツーエンドで説明します。それらはバイヤー内部のメカニクスです。ワイヤー上の観測可能なものは、Identity Match キャップ状態ストアに着地するキャップ発火エントリで、後のidentity_match_request 呼び出しで適格性決定としてサーフェスします。
両シナリオのセットアップ: seller-a.example の package = "pkg-42"、fcap_keys: ["campaign:42"]、policy campaign:42 = {window: {interval: 1, unit: "days"}, max_impression_count: 5}。
シナリオ A — マルチアイデンティティ重複排除
ユーザーはインプレッションストリームにわたって 2 つの解決されたアイデンティティを持ちます:rampid:abc と id5:def。アイデンティティ解決はトグルします — ほとんどのインプレッションは両方を解決するが、1 つは rampid のみを解決します。
imp-001、imp-002、imp-003 — TMPX が両方のアイデンティティを解決。各インプレッションが同じ impression_id を両方のログに書き込む:
impression_id で重複排除:
max_impression_count → キャップがちょうど使い果たされた。imp-005 で両方のアイデンティティが解決されているため、インプレッショントラッカーは両方のキャップ発火エントリを発する:
- 重複排除が重要。 アイデンティティごとのカウントを素朴に合計すると
5 + 4 = 9—max_impression_countを大幅に超える。impression_idによる重複排除が正しいカウント 5 を復元する。 - アイデンティティ解決の安定性は不要。 imp-004 は id5 のログエントリを完全に逃した。両方のアイデンティティが次に一緒に解決されたとき、評価時の重複排除が依然として正しい答えを生成する。
max(rampid=5, id5=4) = 5 を見る — この時点では偶然正しいが、それは分岐がたまたま単一の逃した書き込みだったからにすぎない。2 番目の id5 を逃したインプレッション(imp-006 スタイル)は rampid を 6 に押し上げ id5 を 5 に残す。MAX は依然として 5 と言い 1 つ過剰提供する。SUM(= ここで 9)は反対方向に過剰カウントする。ログ + impression_id 重複排除は構成上正しい。
実装者のためにフラグを立てる帰結: 将来のクエリが id5:def のみを解決する場合、キャップ状態ルックアップは imp-005 で書き込まれた id5:def エントリにヒットし、ユーザーは正しく抑制される。将来のクエリでどちらのアイデンティティも解決されない場合、キャップ状態ルックアップは全く起こらない — それは fcap の上流のアイデンティティ解決問題であり、fcap の正しさの問題ではない。
シナリオ B — クロスセラー広告主キャップ
異なるセラーの 2 つのパッケージ、両方が同じ広告主レベルのラベルにマップ:seller-a からの pkg-A の 10 インプレッション。各露出エントリの fcap_keys は advertiser:13 を含む。10 番目の書き込みで、advertiser:13 の重複排除されたカウントが max_impression_count に一致する。インプレッショントラッカーは、すべてのセラーにわたって advertiser:13 にマップするすべてのパッケージ について、すべての解決されたアイデンティティに対してキャップ発火エントリを発する:
pkg-B の seller-b からの後続の identity_match_request は、キャップ状態エントリが存在するため eligible_package_ids: [] を返す。fcap_key が共有されているため、広告主レベルのキャップはセラーをまたいで強制する。IdentityMatch サービスでクロスセラー協調は不要 — バイヤーエージェントのインプレッショントラッカーが単一の真実の源泉で、キャップ状態ストアが公開チャネル。
パフォーマンスリファレンス
下の数字はtargeting/scale_test.go からの、インメモリモックストアに対する単一 goroutine のものです。CPU をネットワークから分離しています。それらは インプレッショントラッカーの 評価コスト — ログをスキャンしこのインプレッションがちょうどキャップを発火したかを決めるコスト — を記述します。Identity Match サービスのクエリ時コストは、別個のはるかに小さいキャップ状態存在チェックです。
書き込み時の eval ごと、ログサイズ変動、単一アイデンティティ、単一 fcap_key:
バイナリ lazy 重複排除を伴う線形スキャン。10K エントリでミリ秒未満。
結合負荷(マルチアイデンティティ、マルチパッケージ eval)、すべての次元変動:
CPU は
packages × log_entries × identities でスケールします。病理的な裾野は adcp-go#103 のアルゴリズム最適化(ヒューリスティックゲートのプレフィルターバケット。小さいリクエストでの回帰を避けるため numPackages > 50 でゲート)で対処されます:
本番のサイジングは、valkey ラウンドトリップレイテンシー、負荷下の裾野の動作、ヘビーユーザーのインプレッション分布形状にも依存します。モックストア CPU は下限であり、本番の数字ではありません。
関連項目
- フリークエンシーキャップデータフロー — このページが背後に位置するキャップ発火境界コントラクト
- TMP 仕様 — ワイヤー仕様、適合性不変条件
adcp-go/targeting— このページのモデルのリファレンス Go 実装adcp-go/targeting/fcap— 境界の反対側のリファレンスキャップ状態ストア