本文属于 Bidder 竞价服务架构 系列。
总览篇把 Bidder 拆成 解析 → 定向过滤 → 召回 → 打分 → 出价 → 排序 → 填充 七级漏斗,上一篇深挖了第一级 解析(Parse)。本篇接着讲第二级 定向过滤(Targeting Filter):漏斗最前端、成本最低、削减流量最多的一道闸门,奉行「能便宜地砍掉的流量,就别让它往下走」。建议先读总览与解析篇再回到这里。
解析把原始字节流变成了一个带 deadline 的内部请求对象 BidContext。拿到它之后,Bidder 做的第一件「减法」就是定向过滤:判断这次曝光该不该竞价、有没有广告能投。它不打分、不出价,只做布尔层面的一票否决——用最便宜的判断,尽早把没希望的流量和候选砍掉,好让后面昂贵的召回与打分只作用于「有希望」的少数。
定向过滤子流水线(自上而下):请求级 gating(对整次曝光一刀切)→ 候选级布尔匹配(对每条广告求交)→ 频控粗筛(可本地布尔的那部分),产出候选集交给召回。蓝色为两级过滤主步骤,橙色为可前置的频控粗筛,黄色为产出的候选集,紫色为配置面(Nacos)热更新,红色为快速失败路径。
TL;DR
- 定向过滤是漏斗最前端的「一票否决」层:它只回答「能不能投」(布尔),不回答「值多少钱」(打分)。排布原则——成本最低、削减流量最多的判断放最前。
- 两类过滤,先请求级再候选级:先用请求级 gating(反作弊 / IVT、黑白名单、合规同意、底价)一刀切掉整次曝光——不合格就直接 no-bid,连候选都不召回;通过后才做候选级布尔匹配,把曝光上下文与每条广告的定向条件求交。
- 布尔匹配的语义里藏着最贵的坑:AND / OR / NOT 组合、正向定向 vs 排除定向、多值匹配都要想清楚;而**「未设定向 = 全通还是全不通」这个缺省语义一旦配错,轻则谁都投不出、重则把预算投给所有人**。
- 频次控制不能全放最前:用户级全局频控、已达标 campaign 的位图预排除这类「不依赖具体候选、纯本地布尔」的粗筛可以前置;但真正的 per-user × per-campaign 精细封顶必须在有候选之后逐条判定,常需一次带超时的 Redis 点查,因此更多是随排序闸门一并执行(分布式频控后续单独成篇)。
- 过滤依赖解析层的归一化:匹配用的地域、设备、币种等字段必须是解析篇里归一化过的内部字段,否则同一语义在不同 ADX 下判断不一致。
- 快速失败 + 热更新:过滤后无候选立即 no-bid、省下游算力;黑白名单与定向规则经 Nacos 热更新,新增规则不必发版。
- 与召回的边界:本篇讲语义与资格(该不该投、什么算匹配),召回篇讲数据结构与算法(怎么在百万候选里用倒排索引 / RoaringBitmap / Bloom Filter 毫秒级求交)。
Table of contents
Open Table of contents
一、定向过滤为什么值得单独一篇
在总览里,漏斗的排布只有一条原则:越便宜、能过滤越多流量的处理越靠前。解析是第 0~1 级(只做翻译、不做过滤),而定向过滤是第一个真正「做减法」的环节——它决定了后面昂贵的召回、打分要处理多少流量。它有三个特殊性,值得单独成篇:
- 它是「一票否决」,不是「打分」。后面的打分(CTR/CVR 预估)是把候选排序、算出「值多少钱」;定向过滤只做布尔判断——「能不能投」。二者性质完全不同:一个是排序问题、一个是资格问题。混淆它们,会把本该在最前端一刀切掉的流量,拖到昂贵的打分阶段才淘汰,白白浪费算力。
- 它削减的流量规模最大。在十万至百万 QPS 里,很大一部分请求根本不该竞价(无效流量、不合规、连底价都够不上),很大一部分广告根本不匹配这次曝光。把这些尽早砍掉,是整条链路能在 20ms 内跑完的前提。
- 它错一点,代价极大。定向过滤的 bug 往往不是「慢一点」,而是「投错了」——把广告投给了不该投的人,或把预算烧在了不匹配的流量上,直接是钱的损失(这一点在第五节的缺省语义陷阱里会具体展开)。
与「召回」的边界:本篇讲语义,召回篇讲数据结构
总览的七级漏斗里,定向过滤(第 2 级)和召回(第 3 级)是相邻的两级,且都涉及「把曝光上下文与广告定向条件求交」。它们到底怎么分工?本系列这样切:
| 定向过滤(本篇) | 召回(下一篇) | |
|---|---|---|
| 回答的问题 | 该不该投 / 什么算匹配(语义、资格) | 怎么在百万候选里毫秒级求交(数据结构、算法) |
| 关注点 | 定向维度、布尔语义、请求级 gating、频控顺序、缺省语义 | 倒排索引、RoaringBitmap、Bloom Filter、粗排截断 |
| 一句话 | 定义「匹配」的规则 | 实现「匹配」的引擎 |
换言之:定向过滤定义了「匹配」这件事的语义与边界,召回负责把这套语义在极限时间内高效执行。本篇会点到工程实现(第八节),但把数据结构的深挖留给召回篇——那是另一整篇的话题。
二、两类过滤:先请求级 gating,再候选级匹配
定向过滤内部其实是两层,顺序不能颠倒:
两层过滤:第一层对整次曝光一刀切(请求级 gating),任何一条不过就整请求 no-bid、省掉整轮召回;只有请求合格,才轮到第二层对每条广告逐条布尔匹配(候选级)。先做最省的一刀切,是漏斗「越便宜越靠前」原则在这一级内部的再一次体现。
- 请求级 gating(对整次曝光):判断「这波流量值不值得竞价」。它一票否决的是整个请求——只要反作弊、黑白名单、合规、底价里有一条不过,就整请求 no-bid,连广告都不用召回。这是最省的一刀切。
- 候选级布尔匹配(对每条广告):只有请求过了 gating,才轮到把曝光上下文与每条广告的定向条件求交,筛出「这次曝光能投哪些广告」。
为什么顺序是「先请求级、再候选级」?因为请求级一次判断就能砍掉整个请求的全部候选,成本最低、收益最大;而候选级要对成千上万条广告逐条判断,昂贵得多。把最便宜、削减面最大的判断放最前,正是漏斗原则的体现。
三、请求级资格判定:先看这波流量值不值得竞价
请求级 gating 处理的是「对整次曝光成立、与具体广告无关」的判断。四类最常见:
3.1 反作弊 / IVT 初筛
程序化流量里混杂着大量无效流量(IVT)——机器人、数据中心 IP、点击农场、伪造的 App bundle 等。对这类流量出价是纯粹的亏损。定向过滤最前端会做一层轻量初筛:已知的作弊 IP 段 / 数据中心 ASN、异常 UA、声明与实际不符的流量特征等,命中即整请求丢弃。
注意「初筛」二字:请求级只做能本地快速判断的部分;复杂的作弊识别(依赖行为序列、跨请求关联、模型判定)是离线 / 近线的事,其结果以黑名单形式回灌到这里(呼应总览「状态靠回灌」的数据面原则),在线路径上只做一次布尔查表。
3.2 黑白名单
最朴素、也最高频的一票否决,本质是几张本地内存里的集合 / 位图,命中即放行或拒绝:
- 媒体 / 域名 / App bundle:品牌安全黑名单(不投的媒体)、白名单(只投的媒体)。
- 地域:不投的国家 / 地区。
- 设备类型 / OS:只投 iOS、排除某些机型等。
- IP 段:内部测试 IP、已知欺诈段。
黑白名单的规模会随业务膨胀(见第九节),但判断本身必须是 O(1) 的本地查表,绝不能为一次黑名单判断发起远程调用。
3.3 合规与同意
这一层是法律红线,漏做的代价不是效果差,而是合规风险:
- 用户同意(consent):先看
regs.ext.gdpr(或regs.gdpr)判断 GDPR 是否适用;适用时解析 IAB TCF 的user.ext.consent字符串,逐个用途(Purpose) 校验授权——未授予「创建个性化广告画像 / 选择个性化广告」等用途,就不能使用该用户的个人数据做定向;未授予基本的数据处理,甚至不能竞价。美国侧对应 GPP / US Privacy 字符串(us_privacy)。这些都是请求级的硬闸门,且必须在任何『读用户特征做定向』之前执行——顺序错了,等于先违规再补救。 - 儿童与敏感类目:COPPA 标记(
regs.coppa)下禁止行为定向、禁用个人标识;赌博、医药、成人、政治等敏感类目受地域法规限制,命中即按地域策略拒投或降级。 - 数据边界(data residency):某些地区要求个人数据不出境,跨机房定向与特征读取要遵守——这也是总览 4.4 节跨数据中心部署的合规约束之一。
合规判断同样应配置化、可热更新(法规和媒体政策经常变)、可审计(每一次因合规拒投都要留痕),并作为一票否决置于最前——一旦不合规,后面算得再好也不能投。保守优先:consent 字符串解析失败 / 缺失时,应按「未授权」处理(宁可少投,不可违规),而非默认放行。
3.4 底价校验(bidfloor)
Bid Request 里的 bidfloor 是本次曝光的最低成交价。如果我方对这次曝光的出价上限(受 campaign 出价、pacing 系数约束)连底价都够不上,那么无论召回打分怎么算,最终都不可能赢——不如现在就 no-bid,省掉整条链路的算力。
这里有个和解析篇呼应的细节:bidfloor 带币种(bidfloorcur),必须用解析层归一化后的统一币种来比,否则会拿美元底价去比人民币出价,判断完全失真。
小结:请求级 gating 的四道闸门——反作弊、黑白名单、合规、底价——共同回答一个问题:「这次曝光,我方到底该不该参与竞价?」 任何一条不过,立即 no-bid,这是全链路最省的一次淘汰。
四、定向维度分类:候选级匹配的输入
请求过了 gating,才轮到候选级匹配。要把「曝光上下文」与「广告定向条件」求交,先得清楚定向有哪些维度。广告主在投放后台设置的定向条件,落到 Bidder 里大致分这么几类:
| 维度 | 曝光侧(来自 BidContext) | 广告侧(campaign 定向条件) |
|---|---|---|
| 地域 geo | IP / GPS 解析出的国家 / 省 / 市 | 投放地域集合(含排除地域) |
| 时段 dayparting | 请求到达的本地时间、星期 | 允许投放的时段(如工作日 9–18 点) |
| 设备 / 环境 | OS、机型、网络(WiFi/4G/5G)、运营商、语言 | 目标设备 / OS / 网络条件 |
| 媒体 / 版位 | 媒体分类、App bundle、广告位类型(banner/视频/原生)、尺寸 | 允许 / 排除的媒体与版位类型 |
| 人群包 / DMP | 用户 ID 命中的人群标签 | 定向 / 排除的人群包 |
| 上下文 / 内容 | 页面 / App 的内容分类、关键词 | 内容定向条件 |
两个贯穿所有维度的概念:
- 正向定向 vs 排除定向:同一维度既可以「只投满足条件的」(include),也可以「投所有但排除某些」(exclude)。例如地域可以是「只投北上广」,也可以是「投全国但排除某几个城市」。二者的布尔语义相反,实现时要分开表达(见第五节)。
- 多值命中:一次曝光在某维度可能命中多个值(如一个用户同时命中多个人群标签),此时匹配是「曝光的标签集合与广告定向的标签集合求交非空」,而非单值相等。
两个维度上的口径陷阱,务必与配置面对齐
频次也常被归为「定向」的一种,但它的判定顺序有特殊性——放到第六节单独讲。
五、布尔匹配的语义:最容易出错的地方
定向匹配本质是一个布尔表达式求值:把这次曝光的上下文代入每条广告的定向条件树,得到 true(可投)或 false(不可投)。看似简单,但语义细节里藏着几个最容易烧钱的坑。
5.1 定向条件树:AND / OR / NOT
一条广告的定向通常是多维度的组合:「投(北京 OR 上海)AND(iOS)AND(NOT 已排除媒体)AND(命中人群包 X)」。工程上把它表达成一棵布尔条件树,匹配即对树求值。关键约束:
- 维度间通常是 AND:地域要匹配、设备要匹配、人群包要匹配……任一维度不满足即整体不匹配。
- 维度内通常是 OR / 集合求交:地域「北京 OR 上海」、人群包「命中 X 或 Y 之一」。
- 求值要短路:任一 AND 分支为
false,立即返回,不再评估其余维度——这与漏斗「尽早淘汰」一致。
走一个具体例子,把「维度间 AND、维度内 OR、排除、多值、短路」一次看全:
曝光上下文(来自 BidContext,已归一化):
geo = CN-Shanghai
os = iOS
media = news.example.com
segments = {high_value, sports_fan} // 用户命中的人群标签(多值)
广告 A 的定向条件树:
geo ∈ {CN-Beijing, CN-Shanghai} // 维度内 OR
AND os ∈ {iOS}
AND media ∉ {gambling.example.com} // 排除媒体(NOT)
AND segments ∩ {high_value} ≠ ∅ // 命中人群包(集合求交非空)
逐维求值(任一为 false 立即短路):
geo CN-Shanghai ∈ {北京, 上海} → true
os iOS ∈ {iOS} → true
media news… ∉ {gambling…} → true(未命中排除集)
segment {high_value,sports_fan} ∩ {high_value} ≠ ∅ → true
───────────────────────────────────────────
整体 = true → 广告 A 进入候选集
把 os 换成 Android,第 2 维即 false,后两维直接不再求值——短路省下的,正是候选级最贵的那部分(人群包求交、内容匹配)。这也解释了排布顺序:把最便宜、最能砍量的维度(geo / os)放在条件树前面,让短路尽早生效。
5.2 正向 vs 排除:别让 NOT 悄悄反转
排除定向(NOT / exclude)是 bug 高发区。「投全国排除上海」和「只投上海」,在代码里可能只差一个取反,语义却完全相反。工程建议:
- 正向集合与排除集合分开存储、分开判断,不要用一个字段靠正负号或标志位混合表达。
- 求值语义固定为:命中正向集合(或正向为空表示不限)AND 未命中排除集合。
把这条语义摊成真值表(P = 正向集合,E = 排除集合,v = 曝光在该维的取值):
正向 P | 排除 E | 命中条件 | 含义 |
|---|---|---|---|
| 非空 | 空 | v ∈ P | 只投 P |
| 非空 | 非空 | v ∈ P 且 v ∉ E | 先入选、再剔除 |
| 空 | 非空 | v ∉ E | 投所有、排除 E(正向为空 = 不限) |
| 空 | 空 | 恒 true | 该维不限(缺省语义见 5.3 / 5.5) |
一句话:正向为空表示「不限」而不是「全不通」,排除永远是一次「减法」。把这两条钉死,NOT 就不会在某个分支被一个手滑的取反悄悄反转。
5.3 缺省语义:这一篇最贵的一个坑
「某个维度广告主没设定向条件,算什么?」 —— 这个缺省语义(default semantics)是定向过滤里最容易出事、代价最大的一处:
- 若约定 「未设 = 不限(全通)」:广告主忘了设地域,就会被投向所有地域——预算瞬间烧到非目标市场。
- 若约定 「未设 = 全不通」:广告主只设了地域、没设设备,结果任何设备都投不出去——有预算花不掉、白白欠投。
两种约定都「自洽」,但必须全系统统一、且与投放后台的语义严格对齐。真正的事故往往发生在边界:某维度在后台是「不限」、传到 Bidder 却被当成「空集」,于是「不限」被误判成「全不匹配」。这类 bug 不报错、不超时,只是悄悄地把钱投错或投不出去,极难在监控上第一时间发现。 务必用真实流量回放 + 明确的缺省语义单测来守住。
5.4 匹配依赖解析层的归一化
所有布尔匹配的正确性,都建立在字段已被解析层归一化的前提上(见解析篇 2.2 节):地域编码统一、设备 / OS 取值统一、币种统一、结构化 UA 与原始 UA 已收敛。否则同一个「iOS」在 A 家 ADX 叫 iOS、B 家叫 IOS、C 家靠 os=ios,匹配就会时对时错。定向过滤不负责归一化,但它是归一化质量的第一个「受害者」——上游归一化没做好,这一级的匹配全线失真。
5.5 把「缺省」与「缺失」收进一张决策表
5.3 的「广告主没设」(广告侧缺省)和 7.3 的「曝光字段缺失」(曝光侧缺失)是两个正交问题,却在同一处求值里相遇,最容易被混为一谈、在某个组合上「悄悄投错」。把二者摊进一张表,才能逐格约定清楚:
| 情形 | 属于 | 约定 | 该维求值 |
|---|---|---|---|
| 广告某维未设定向 | 广告侧 | 未设 = 不限(推荐,须与后台一致) | 恒 true |
| 广告某维未设定向 | 广告侧 | 未设 = 全不通 | 恒 false(极易欠投,慎用) |
| 曝光某维字段缺失 | 曝光侧 | 保守:缺失即不匹配 | false(合规 / 品牌安全场景) |
| 曝光某维字段缺失 | 曝光侧 | 激进:缺失视为不限 | 按「不限」放行(追量场景) |
两条约定各自独立、都要显式可配置,且缺省语义必须在「后台展示 = 编译定型 = 运行时匹配」三处对齐(呼应配置落地篇 3.2 节把「未设」在编译期落成具体集合)。一旦某维在后台是「不限」、编译时被当成空集、运行时又把空集判成「全不通」,这条线就会零投放且不报错——正是 5.3 那类最贵、最难查的事故。
六、频次控制的位置问题:为什么不能全放最前
总览 3.2 节埋了一个钩子:频次控制(Frequency Capping,限制「同一用户对某广告 / campaign 的曝光次数」)看起来是纯粹的过滤,似乎该和黑白名单一起放最前端;但真正的精细频控做不到全放最前。本节把这个顺序问题讲透。
频控的两半:左半(用户级全局频控、已达标位图预排除)不依赖具体候选、可纯本地布尔判断,能前置到定向过滤;右半(per-user × per-campaign 精细封顶)依赖「具体候选 + 用户历史」,只能在召回之后、随排序闸门执行,且常需一次带超时的 Redis 点查。
6.1 能前置的:不依赖具体候选的粗筛
这两类频控只需本地布尔判断,可以放在定向过滤最前端:
- 用户级全局频控:如「该用户今日总曝光已达上限」,与投哪条广告无关,命中即整请求丢弃。
- 已达标 campaign 的位图预排除:把「当前已封顶的 campaign」维护成一张本地位图,候选求交时直接把这些 campaign 剔除。这不需要知道「这个用户看过这条广告几次」,只需要「这条 campaign 是否已整体达标」,因此是纯本地布尔。
6.2 必须后置的:per-user × per-campaign 精细封顶
真正的精细频控是「这个用户,对这条具体广告 / campaign,已经看过几次」。它做不到前置,有两个硬原因:
- 依赖具体候选:在召回之前根本还没有候选广告,无从判断「用户对某条广告看了几次」——逻辑上就必须在有候选之后。
- 依赖用户历史、常需远程点查:per-user × per-campaign 的计数通常存在 Redis(数据量大、无法全量常驻内存),逐条候选查一次是一次带超时的远程点查。若对每个请求、每条候选都点查,会把 Redis 打成风暴(见第九节)。因此它更适合在候选已收窄到 Top-N 之后,随排序闸门、和预算 / pacing 闸门一起,对少数存活候选执行。
6.3 与预算闸门的类比
精细频控和预算闸门是同一类东西:都是「付费前对少数存活候选逐条施加的、依赖外部可变状态的最后一道闸门」。总览把它们一起放在「排序 + 预算/频控闸门」那一级(3.6 节),正是这个道理。它们同样面临分布式一致性问题——计数分散在多实例、真实曝光有延迟——解法也类似(本地计数 + 异步回灌 + 容忍少量超出)。分布式频次控制的一致性细节,本系列后续单独成篇。
一句话:频控里「与候选无关、可本地布尔」的部分尽量前置削流量;「per-user × per-campaign、需查历史」的部分只能后置、随闸门执行。把后者硬塞到最前端,要么逻辑上无候选可判、要么把 Redis 点查风暴提前引爆。
七、快速失败、缺省策略与热更新
7.1 快速失败:无候选立即 no-bid
定向过滤的每一步都可能把候选清空。一旦某步之后无任何存活候选,立即返回 no-bid,不再进入召回、打分。这与解析层的「非法即拒」一脉相承:越早失败,被浪费的下游算力越少。请求级 gating 不过是「整请求 no-bid」,候选级匹配后无候选也是「整请求 no-bid」——两处快速失败都要有明确的无出价原因码(NBR)便于归因。
7.2 过滤本身要轻量
有一个容易被忽视的悖论:过滤是为了省成本,但过滤本身也有成本。如果为了一次判断而发起远程调用、构建大对象、做复杂计算,就违背了「越便宜越靠前」的初衷。原则:
- 请求级 gating 全部走本地内存查表 / 位图,不发起同步远程调用。
- 候选级匹配走内存里的求交(数据结构细节见召回篇),复杂度与候选规模挂钩,靠索引压低。
- 需要远程状态的(如精细频控计数)后置、且只对少数存活候选做(见第六节)。
7.3 数据缺失时的策略:保守还是激进
现实里 BidContext 的某些字段可能缺失(上游没传、解析没解出)。这时匹配该算命中还是不命中?这是一个必须显式约定的策略,而非交给「恰好取到 null」的偶然行为:
- 保守(缺失即不匹配):宁可少投,避免把广告投给「信息不全、可能不符」的流量。适合合规敏感、品牌安全要求高的场景。
- 激进(缺失即视为不限):宁可多投,避免因个别字段缺失而错过可投流量。适合追量、预算充裕的场景。
没有普适答案,但必须统一约定、可配置,且与第五节的缺省语义区分清楚——「广告主没设定向」和「曝光字段缺失」是两回事,前者是广告侧、后者是曝光侧。
7.4 规则热更新:经 Nacos 下发
黑白名单、定向规则、合规策略、频控阈值都会频繁变动,绝不能靠发版更新。与解析层的适配规则一样,它们应经配置中心(Nacos)热更新下发到 Bidder 进程,原子切换生效(呼应总览「缓存更新须原子切换」——半新半旧的规则集会导致过滤行为不一致)。
八、工程实现要点(点到即止,深入留给召回篇)
本篇聚焦语义,工程实现只列几条关键约束,数据结构的深挖留给召回篇:
- 定向条件的存储与索引:把「广告 → 定向条件」倒过来建成倒排索引(「某个定向值 → 命中它的广告集合」),用位图(RoaringBitmap) 做集合求交,把布尔匹配变成毫秒级位运算。——这是召回篇的核心,此处只预告。
- 规则求交走本地内存、无远程调用:广告库、定向索引全量常驻内存,后台线程定时全量 + 增量更新、读写分离原子切换(呼应总览数据面原则)。
- 盯 P99:定向过滤同样在关键路径上、被大量请求执行,尾延迟同样受 GC、锁竞争影响。求交的实现要近乎零分配,避免为每次匹配产生大量临时集合对象。
九、生产实战:常见问题
- 缺省定向语义配错:最贵、最隐蔽的坑。「未设 = 不限」配成「未设 = 全不通」或反之,会造成大面积错投或欠投,且不报错。上线前务必用真实流量回放校验缺省语义,并覆盖「广告侧未设 × 曝光侧缺失」的每一个组合(见 5.3 / 5.5)。
- 黑白名单规模膨胀:名单随业务长期增长到百万级时,朴素的集合查找会变慢、占内存。应改用位图 / 前缀树(IP 段用 CIDR trie)/ 布隆过滤等结构,并定期清理失效条目。
- 频控顺序放错引发 Redis 点查风暴:把 per-user × per-campaign 精细频控误放到召回之前、或对每条候选都远程点查,会瞬间打爆 Redis。精细频控必须后置、只对 Top-N 候选做、批量 pipeline、并带超时降级(超时按「未达上限」放行还是拦截,也要显式约定)(见第六节)。
- 合规过滤漏做或顺序错:consent / COPPA / 敏感类目漏判不是「效果差」,而是合规事故;更隐蔽的是顺序错——先读了用户特征做定向、再判 consent,等于已经违规。合规闸门必须置于最前、在任何个人数据使用之前、可热更新、有审计(见 3.3)。
gdprApplies误判整区域熄火:把「GDPR 是否适用」判反、或 consent 解析库抛异常时默认拦截,会让整个欧盟区域集体不竞价、消耗骤降却不报错。应对解析失败按「未授权」保守处理,同时对「因合规拒投的比例」单独埋点告警,避免静默熄火。- 反作弊 / 已达标位图回灌延迟导致漏杀:请求级的 IVT 黑名单、已封顶 campaign 位图都靠近线回灌更新,回灌滞后会让刚被识别的作弊流量、刚封顶的 campaign 在窗口内继续被放行——高价值 campaign 应缩短回灌周期,或对临界项施加更保守的本地拦截。
- 定向字段与解析归一化不一致:匹配用的字段口径必须与解析层归一化后的口径完全一致,否则匹配时对时错、且难复现。新接入 ADX 时尤其要用真实流量回放对齐(见 5.4)。
十、速查表
- 定向过滤 = 漏斗第 2 级的「一票否决」:只判「能不能投」(布尔),不判「值多少钱」(打分)。
- 两层,先请求级再候选级:请求级 gating(反作弊 / 黑白名单 / 合规 / 底价)一刀切整次曝光;候选级布尔匹配逐条筛广告。
- 布尔语义要精确:维度间 AND、维度内 OR、短路求值(便宜维度前置);正向与排除分开存(正向为空 = 不限、排除永远是减法);缺省语义(未设 = 全通 / 全不通)必须全系统统一。
- 缺省 × 缺失一张表:广告侧「未设」与曝光侧「缺失」正交、各自显式约定;缺省语义须「后台 = 编译 = 运行时」三处对齐(见 5.5)。
- 合规先于一切:consent /
gdprApplies/ COPPA 必须在读用户特征之前判、保守降级、可审计,并对「合规拒投比例」告警防止静默熄火。 - 频控分两半:与候选无关的(全局频控 / 已达标位图)前置;per-user × per-campaign 的后置、随闸门、批量 + 带超时点查。
- 快速失败:无候选立即 no-bid,省下游算力;过滤本身要轻量、无远程同步调用。
- 依赖归一化 + 热更新:匹配依赖解析层归一化字段;规则经 Nacos 热更新、原子切换。
- 与召回的边界:本篇讲语义与资格,召回篇讲倒排 / 位图 / Bloom 的高性能实现。
定向过滤的本质,是一道用最便宜的布尔判断、尽早砍掉最多流量与候选的闸门。它不产生竞价价值,却决定了竞价价值的作用范围——把该砍的流量在这里砍掉,后面昂贵的召回与打分才跑得起、跑得快。理解它的分层(请求级 / 候选级)、语义陷阱(缺省语义)、频控的顺序,即掌握了漏斗「越便宜越靠前」原则最典型的一次落地。下一篇进入第 3 级 召回,看这套匹配语义如何用倒排索引与位图在毫秒级执行。
延伸阅读
- Bidder 竞价服务架构设计(总览):定向过滤所在的七级漏斗全景、20ms 延迟预算与「越便宜越靠前」的排布原则。
- 程序化广告投放层级与定向配置 与 配置落地:从投放后台到索引快照:本篇匹配的那份定向规则从哪来、怎么编译成快照——配置面的组织与生成两篇。
- Bidder 解析层(Parse):上一级——定向过滤的输入
BidContext由它产出,本篇匹配所依赖的字段归一化也在那里完成。 - 程序化广告生态全景:IVT / 无效流量、DSP / ADX 的位置,请求级 gating 的背景。
- IAB TCF 详解:合规同意(consent)闸门的协议背景。
- 程序化交易模式:RTB / PMP / PD / PDB:不同交易类型会改变定向与底价的判断口径。
规范与一手资料:
- IAB Tech Lab. OpenRTB 2.6 Specification:
bidfloor/bidfloorcur、regs.coppa、设备与地理字段的权威定义。 - RoaringBitmap. Roaring Bitmaps:高并发定向求交的核心数据结构(召回篇展开)。
- IAB Tech Lab. Transparency & Consent Framework:consent 字符串与用途授权。
附录:术语表
- 定向过滤(Targeting Filter):竞价漏斗第 2 级,用布尔判断决定「能不能投」,只做一票否决、不做打分。
- 请求级 gating:对整次曝光成立、与具体广告无关的过滤(反作弊 / 黑白名单 / 合规 / 底价),不过即整请求 no-bid。
- 候选级布尔匹配:把曝光上下文与每条广告的定向条件求交,筛出可投候选。
- 正向定向 / 排除定向:只投满足条件的(include)/ 投所有但排除某些(exclude),布尔语义相反。
- 缺省语义(default semantics):广告主未设某维度定向时的约定(未设 = 不限 / 未设 = 全不通),配错会造成大面积错投或欠投。
- dayparting(时段定向):按时段 / 星期限制投放的定向维度。
- 人群包 / DMP 定向:按用户 ID 命中的人群标签做定向。
- 频次控制(Frequency Capping):限制同一用户对某广告 / campaign 的曝光次数;粗粒度可前置、精细粒度须后置。
- 位图预排除:把已封顶 / 已达标的 campaign 维护成本地位图,求交时直接剔除。
- 快速失败:过滤后无候选立即 no-bid,不进入召回 / 打分,节省下游算力。
- IVT(无效流量):机器人、数据中心 IP、点击农场等无效曝光,请求级初筛的对象。