当初、popunderの頻度には分かりやすいトレードオフがあると考えていた。広告を減らせばユーザー体験を守れ、増やせば収益は伸びるが、どこかでエンゲージメントが落ち始める。ところが本番では、そこまで単純な関係にはならなかった。
本番運用中のWebアプリで頻度を上げたとき、エンゲージメントに明確な境目が出ると予想していた。しかし実際には、集計指標は従来の日々の変動幅の中をほぼ同じように推移した。良い日も悪い日もあったが、新しいポリシーを境に始まった明確で継続的な低下は確認できなかった。
最終的に残した設定は次のとおりだ。
初回遅延: 45秒
成功したpopunderの後の最小クールダウン: 4分
さらに、同じ4分間に成功イベントを1回までに制限するfrequency windowも維持した。
const policy = {
initialDelaySeconds: 45,
cooldownSeconds: 240,
windowSeconds: 240,
maxEventsPerWindow: 1,
};
これは説明用の擬似コードであり、特定の広告事業者、SDK、アプリ、デプロイ構成には依存していない。重要なのは実装そのものより、その背後にあるポリシーだ。
頻度には実際には二つの判断がある
最初は「popunder同士を何分空けるべきか」という一つの問題として考えていた。だが、その抽象化は適切ではなかった。少なくとも、最初の広告機会までの待ち時間と、その後の広告機会同士の最小間隔という二つの判断がある。
この二つのタイミングは同じではない。新規訪問者はまだ製品に価値があるか判断していない。一方、継続して使っているユーザーはすでにある程度判断している。そこで、最初の中断には後続より強い保護が必要だと考えるようになった。
最初の数秒を強く収益化しなくなった理由
初回遅延を短くすれば、より多くの訪問者が早く対象になり、潜在的な広告イベントも増える。機械的には正しい。しかし、その考え方は収益化が「いつ」起きるかを無視している。
到着直後に広告が発生すれば、それはサイトに対する第一印象の一部になる。先に製品を理解し、使うための中断されない時間があれば、同じ広告機構でも受け取られ方は変わる。
以前はもっと短い遅延を使っていたが、最終的に45秒へ延ばした。これは依然として比較的早く、保守的な設定を目指したものではない。訪問開始と最初の収益化機会を少し分離するためだ。45秒を「失った広告在庫」ではなく、製品が価値を示す時間と考えるようになった。
45秒はしきい値であって、広告の予約時刻ではない
遅延が終わるのは、ユーザーが対象になるという意味にすぎない。00:45ちょうどに広告が自動表示されるわけではない。実際のイベントには、条件を満たすユーザー操作が必要だ。00:45で対象になり、次の適格な操作が01:03なら、その時点で最初のpopunderが起きる可能性がある。
クールダウンも同じだ。4分というのは、前回の成功イベントから4分経つまで次のイベントが対象にならないという意味で、4分ごとに自動表示されるという意味ではない。設定値は最小間隔であり、実際の間隔はさらに長くなり得る。
初回遅延より再表示間隔の方が重要だった
初回遅延を決めた後は、繰り返しの間隔がより重要な変数になった。長いクールダウンは広告圧を下げる一方、継続利用しているユーザーの多くが2回目の機会に到達しない可能性もある。
私が求めたのは二つの条件だ。使い続けるユーザーには再度収益化イベントが起こる現実的な機会を残すこと。そして、イベント同士の間に十分な中断なしの利用時間を確保すること。
最終的に4分を維持した。初回45秒なら、理論上もっとも早い対象時刻はおよそ00:45、04:45、08:45、12:45となる。実際には適格な操作が必要なので、イベントはもっと遅くなることがある。
4分が単なるインプレッション最大化と違った理由
クールダウンを短くすれば理論上の機会は増える。指標が「ユーザー1人あたりの広告機会」だけなら、短い方が明らかに有利だ。しかし、製品に広告価値があるのは、製品自体にユーザー価値がある間だけだ。
追加の収益化
-
行動への悪影響
=
純価値
4分あれば、次のpopunderが絶対に起きない通常利用のまとまった時間を確保できる。私にとって重要だったのは「4分ごとに1回」という表現より、この保証だった。
収益化イベントの後には、次のイベントが絶対に起きない数分間がある。
エンゲージメントは明確に落ちると思っていた
デプロイ前の主な仮説は、広告頻度を上げればanalyticsで明確な前後差が出るというものだった。そうなれば、ポリシーを弱める強い理由になっていた。
実際には、集計エンゲージメントは以前とほぼ同じように変動し続けた。頻度変更後に新しい低い基準線ができたわけでも、低い値が持続したわけでもない。大きければ明確に見えると思っていた失敗パターンは、はっきりとは現れなかった。
実際に言えること
「広告に影響はなかった」「ユーザーは気にしていなかった」「リテンションは完全に不変だった」「この設定が最適だ」とは言えない。集計平均だけではどれも証明できない。
観測から言える最も強い表現は、もっと限定的だ。
頻度を上げた後も、以前から見られた通常の変動を超える、明確で継続的な集計エンゲージメントの低下は観測しなかった。
この表現なら、小さな影響、ユーザー群ごとの差、平均に隠れる行動変化を排除しない。検証したのは「維持できるほど許容可能か」であり、「普遍的な最適値を発見したか」ではない。
安定した平均値でも行動は大きく異なり得る
集計平均がほとんど変わらなくても、実態はいくつも考えられる。ほぼ全員が同じ行動をしているかもしれない。早く離脱する人がいる一方で長く残る人がいるかもしれない。小さな集団だけが悪影響を受けているが平均を動かすほどではない可能性もある。あるいは実際の悪影響が日々の通常変動より小さいだけかもしれない。
そのため、安定した平均値は便利な警告指標としては使うが、影響ゼロの証明とは考えない。
エンゲージメントは訪問時間そのものではない
analyticsのエンゲージメント指標を、セッションの正確な平均滞在時間として扱うことも避けた。アクティブエンゲージメントは計測システムの定義によるもので、タブが開いている経過時間と一致するとは限らない。フォーカスを切り替えたり、後で戻ったりするため、単純な「サイト滞在時間」モデルは誤解を招く。
そのため「平均エンゲージメントがXだから次のpopunderはXより前に出す」といった式は使わなかった。指標は方向性を見るために使った。変更後の製品は以前より実質的に弱く見えるか。監視していた集計レベルでは答えはノーだった。
Cooldownとfrequency windowは関連するが同じではない
実装には240秒のcooldownと、240秒のwindow内で成功イベント最大1回という両方の制約があった。大部分は重複するが、少し異なる意味を表している。
Cooldownは「前回から十分な時間が経つまで次を許可しない」。Windowは「定義された期間内で成功イベントを1回より多く許可しない」。integration側の解釈が異なっても意図する保証が明確になるよう、両方を残した。
成功したpopunder同士の間隔は、必ず4分以上でなければならない。
1 windowにつき1 eventは、1 visitにつき1 eventではない
maxEventsPerWindow = 1は、訪問全体で広告1回という意味ではない。対象範囲は設定されたwindowであり、終了後は新しいwindowが始まり得る。また1はユーザー操作1回ではなく、その期間内の成功した収益化イベント1回を指す。
今では頻度ロジックを見るとき、三つを確認している。カウンターは何を数えるのか。何でリセットされるのか。リセットwindowはどれくらいか。この三つが分からなければ、1という数字だけではほとんど何も分からない。
ポリシーを事業者固有の実装から分離した
再利用したい動作は、特定ベンダーのscript、identifier、parameter名を出さなくても表現できる。
function isEligible(state, now) {
if (!state.hasShownFirstEvent) {
return now - state.arrivalTime >= 45_000;
}
return now - state.lastEventTime >= 240_000;
}
これは説明用の擬似コードであり、本番ソースではない。Provider、SDK、deployment architectureは変わる。より長く使えるのは、訪問開始を守り、実際の利用が始まってから収益化し、hard cooldownを強制し、その後にだけ再度許可するというポリシーだ。
Build成功と動作の正しさは別物
Typechecker、linter、production buildは多くの技術的問題を検証できるが、ブラウザ上の広告タイムラインが正しいことまでは証明しない。この機能には行動テストも必要だ。
1. クリーンなブラウザ状態で開く。
2. 初回遅延前に操作する: popunderは出ない。
3. 初回遅延を超えるまで待つ。
4. 適格な操作を行う: 最初のpopunderが起こり得る。
5. Cooldown終了前に操作を続ける: 2回目は出ない。
6. Cooldown終了まで待つ。
7. 再度操作する: 次のpopunderが起こり得る。
成功したbuildと成功したbehavioral testは異なる問いに答える。どちらも必要だ。
個別のplacementより広告体験全体を見るべき
独立した広告システムは、それぞれ単独では妥当に見えても、組み合わせると攻撃的な体験になり得る。4分制限のフォーマットが二つ交互に動けば、00:45、02:45、04:45、06:45付近で中断が起こることもある。
そこから、より良いルールにたどり着いた。
Frequency capは広告script単位ではなく、ユーザー体験全体で評価するべきだ。
ユーザーが感じるのは中断の合計であり、どの設定ファイルが生成したかではない。本番監査では、一定時間内に1人のユーザーが実際に何回の中断型広告イベントを経験し得るかを見るべきだ。
生のインプレッション数は最適化目標として適切ではない
Cooldownを短くして広告機会を増やすのは簡単だ。難しいのは、ユーザーの総経済価値を減らさずにどこまで追加収益化できるかという問題だ。
1 visitあたりの広告が少ない設定でも、再利用を守ることで長期的にはより多くの収益を生む可能性がある。そのため私はimpressions per visitよりrevenue per user over timeを重視する。前者は簡単に最大化できるが、後者が本当のビジネス課題だ。
より強い実験なら一つの集計グラフだけを見ない
今回の本番観測で、恐れていた壊滅的な結果が明確には起きていないことは確認できた。ただし、あらゆる下流影響を証明するには足りない。
より強い実験では、ユニークユーザーあたりの収益、visitあたりの収益、ユーザーあたりの広告イベント、active engagement、意味のある製品操作、returning-user rate、時間経過でのretention、長期的なuser valueを比較する必要がある。
両側を同時に測らなければならない。広告出力だけ見れば「多いほど良い」という結論になりやすい。Engagementだけ見れば、行動コストがほとんどない有意な収益増を見逃すことがある。
ポリシーを単純に保った理由
例外を増やし続ける理由は見つからなかった。最終ロジックは単純だ。45秒待ち、次の適格操作で収益化し、4分のhard cooldownを置き、その後の適格操作で再度収益化できるようにする。
少数の強いinvariantは、例外だらけのポリシーより理解、テスト、デバッグが容易だ。
この非対称性は意図的だ
新規ユーザーと継続利用ユーザーは同じ扱いではない。新規訪問者には強めの保護を与え、使い続けるユーザーには時間とともに追加の収益化機会が発生する。
つまり広告圧は最初から最大にするのではなく、確認されたエンゲージメントに合わせて増える。この考え方の変化は、実験で得た最も有用な学びの一つだった。
この事例から結論づけないこと
45秒が普遍的に正しい、4分が最適、頻度の高いpopunderはエンゲージメントを傷つけない、平均が安定していればユーザーは無影響だ、とは言わない。一つの本番ワークロードをすべてのWeb製品に一般化もしない。
製品ごとにユーザー、期待、獲得経路、利用パターン、経済性は異なる。この結果に価値があるのは具体的な一例だからであって、普遍則を証明したからではない。
考えを変えた部分
実験前は、広告圧とエンゲージメントの関係をほぼ機械的に考えていた。一方を十分に増やせば、もう一方は目に見えて落ちるはずだと。ところが本番はそこまで整然としていなかった。
私は頻度を、自分としては比較的高いと考える水準まで上げた。ユーザーは利用を続け、集計エンゲージメントは従来の日々の範囲内にとどまった。これはコストがゼロだったという意味ではない。監視していた指標に、期待していた明確な信号を出すほど大きなコストではなかったという意味だ。
「影響がない」と「この測定では明確な影響が見えない」は違う。本番システムが、欲しいほどきれいな因果関係を示してくれることは少ない。
今のルール
今は「技術的にどれくらい早く次のpopunderを出せるか」とは考えない。新規ユーザーが収益化開始前にどれだけ中断のない製品体験を得るべきか、継続ユーザーが次のイベントまでにどれだけ中断のない利用時間を得るべきか、そして追加広告が総user valueを増やしているのか、単にimpression counterを増やしているだけなのかを考える。
このアプリで残した値は次のとおりだ。
初回eligibilityまで45秒
成功イベント同士は最低4分
数値は一つの実装にすぎない。別の場所でも再利用したいのは、より広いルールだ。
ユーザージャーニーの始まりを守り、初期の好奇心よりも実際に示されたエンゲージメントを強く収益化し、生の広告量ではなくユーザー単位の価値で結果を判断する。