本文属于 Bidder 竞价服务架构 系列。
总览篇第六节把「预算与 Pacing」称作 Bidder 最难的一致性问题,并把它拆成两半:不超投(本地分片 + 异步回灌,那里已经讲了)与平滑消耗(Pacing 本身,当时明确说「后续单独成篇讲 PID」)。本篇就把欠下的这一篇补上——怎么让一笔日预算在投放时段内匀速花完,而不是前重后轻、上午花光下午停摆。建议先读总览第六节,再回到这里。
「不超投」只是预算控制的下限——它保证你不会花超。但一个只会「有钱就尽力花、花完就停」的 Bidder,会在流量高峰的上午把一天的预算烧光,下午和晚上彻底缺席。对广告主而言这几乎和超投一样糟:触达的人群被时段扭曲、单位成本被高峰期抬高、想看的晚间流量一个都没投到。Pacing(平滑投放)要解决的就是这件事:不仅花对总量,还要花对节奏。
这本质上是一个反馈控制问题——设定一条「此刻应该花到多少」的目标曲线,实时测量「实际花了多少」,用两者的误差去调节出价的快慢。而工业界最经典的反馈控制器,就是 PID。本篇会从「为什么需要闭环」讲到「PID 三项在 pacing 里分别对应什么」,再落到分布式 Bidder 上「谁来跑这个控制器、反馈延迟为什么是命门」。
累计消耗曲线:红线(无 Pacing) 在上午高峰把预算烧光(第 9h 撞顶)后一整天停摆;绿线(有 Pacing) 紧贴 灰色目标线平滑消耗、到收盘刚好花完。Pacing 要做的,就是把红线掰成绿线——同样花完日预算,但摊平到整个投放时段。
TL;DR
- Pacing 是预算控制的另一半:总览讲的「本地分片 + 异步回灌」解决不超投;Pacing 解决平滑消耗——让预算在投放时段内按目标节奏花,而非前重后轻。
- 先定目标曲线:
ASAP(尽快花,适合抢量/冷启动)、均匀(按时间线性)、流量加权(按各时段历史流量分布分配,最常用)。目标曲线是「此刻累计应该花到多少」,是控制的设定值 r(t)。 - 两种执行手段:节流(按投放概率
p抽样参不参与竞价,简单但样本有偏)与出价系数(最终出价= base × α,连续调节、更省预算,但与拍卖响应曲线、一价 bid shading 耦合)。多数成熟系统以出价系数为主、节流为辅。 - 为什么用 PID:开环(预先算好每小时花多少)扛不住流量波动、赢率漂移、模型偏差。闭环反馈才能自我纠偏。PID 是最成熟、可解释、易调参的闭环控制器。
- PID 三项映射:误差
e = 目标累计 − 实际累计;P 按当前偏差快速纠偏、I 累积历史偏差消除稳态误差(长期欠投/超投)、D 看偏差变化趋势抑制过冲。输出即出价系数α或投放概率p。 - 最大的坑是积分饱和(windup):
α被 clamp 到上下限时若继续累积积分,等流量回来会剧烈过冲。必须做 anti-windup(饱和时冻结/回退积分)。此外微分要对噪声滤波、系数要随预算规模归一。 - 分布式下 dead time 是命门:真实消耗经 Kafka 回灌有秒级延迟,这就是控制回路的纯滞后(dead time)。控制周期必须远大于回灌延迟,否则震荡。通常中心跑 PID 出全局
α下发(避免各实例各自抢跑),本地只保留令牌桶做亚秒级限速。 - Pacing 不是孤立的:它和预算熔断(硬上限)、频控、冷启动(还没有可靠 CTR 时怎么定速)、一价 bid shading(压价系数与 pacing 系数如何合并)都要协同。
Table of contents
Open Table of contents
一、Pacing 要解决什么:不做会怎样
先把问题定义清楚。一个 campaign 通常带两类预算约束:总预算(整个投放周期)与日预算(每天上限)。Bidder 要做的不只是「别花超」,还要回答一个时间维度的问题:这笔日预算,在今天这 24 小时里应该怎么分配着花?
如果完全不做 Pacing,Bidder 的行为是 ASAP(as soon as possible)——只要还有预算、只要能赢,就出价。后果是消耗曲线被流量分布牵着走:
- 前重后轻:广告流量在一天里不是均匀的(通勤、午休、晚间各有高峰)。ASAP 会在最早到来的高峰把预算迅速烧光。
- 下午/晚间停摆:预算一旦耗尽,campaign 当天就退出竞价,错过后面所有流量——包括往往转化更好的晚间流量。
- 成本被抬高:高峰期竞争最激烈、成交价最高。把预算集中砸在最贵的时段,等于用最差的单位成本买量。
- 人群被扭曲:只投到了「上午在线的人」,campaign 想覆盖的全天候人群结构被时段偏差破坏。
一句话本质
不超投保证你花得不多,Pacing 保证你花得是时候。前者是金额的一致性,后者是时间分布的一致性——而时间分布直接决定了触达质量与单位成本。
反过来,Pacing 也要防欠投:投放时段结束时预算没花完,等于广告主的钱和曝光机会双双浪费,也是要被考核的 KPI。所以 Pacing 的目标是一条双向贴合——既不提前花光(超前),也不结尾剩余(滞后)。
二、第一步:定义目标消耗曲线 r(t)
要「贴合」,先得有条线可贴。目标消耗曲线 r(t) 定义了「到时刻 t,累计消耗应该达到日预算的百分之多少」。它是整个控制问题的设定值(setpoint)。常见三种:
| 曲线 | r(t) 形态 | 适用 | 代价 |
|---|---|---|---|
| ASAP | 阶跃:能花就花到顶 | 冷启动抢量、限时活动、预算远小于可用流量 | 前重后轻、成本高、人群偏 |
| 均匀(Even) | 线性:r(t) = 预算 × t / T | 流量近似平稳、要求简单可解释 | 高峰期可能因目标太低而错过便宜量、低谷期可能追不上目标 |
| 流量加权(Traffic-weighted) | 按各时段历史流量/赢率分布加权累积 | 生产主流 | 依赖流量预测,预测偏差会传导成 pacing 偏差 |
流量加权是重点:如果晚上 8 点的可竞价流量占全天 10%,那么这一小时的目标增量就分配 10% 的预算,而不是按钟表均分。写成公式就是把目标累计占比 r(t) 定义为归一化的累计流量权重:
Σ(τ ≤ t) w(τ) w(τ) = τ 时段的预估可竞价流量(或流量 × 预估赢率)
r(t) = ────────────────── ∈ [0,1] 分母 = 全天权重之和 → 保证 r(T) = 1(收盘恰好花完)
Σ(all τ) w(τ)
这样目标曲线本身就「顺着流量走」,控制器要纠的偏差更小、更平稳。均匀曲线只是 w(τ) ≡ 常数 的特例。
目标曲线是「计划」,控制器是「执行纠偏」。 计划定得越贴合真实流量,执行阶段要纠的偏差就越小。很多 pacing 不稳,根子在流量预测差,而不在控制器。
w(τ) 该正比于什么(纯流量?流量 × 赢率?还是再乘上转化价值?)、怎么按日型/时区分别建模、怎么在「向价值倾斜」与「保覆盖」之间取舍——这本身是一个独立的预测问题,已单独成篇:Pacing 目标曲线:时段消耗权重怎么设计。本篇后面默认目标曲线已经画好,专注控制。
值得强调:r(t) 不必写死为「金额」。更稳的做法是把它表达成累计消耗占比,因为日预算可能被中途调整、可能跨机房再分配(见第五节)——用占比表达,预算变化时曲线自动伸缩。
三、第二步:两种执行手段——节流 vs 出价系数
有了目标,控制器算出一个「该快还是该慢」的信号后,得有个旋钮去真正影响消耗速度。有两个旋钮:
两个旋钮:节流调「参不参与竞价」(投放概率 p),出价系数调「出多少」(bid × α)。节流实现简单但丢流量是「无差别」的;出价系数顺着拍卖响应曲线走、更省钱,但更难调、且在一价下与 bid shading 纠缠。
3.1 节流(Throttling / Probabilistic Pacing)
最朴素:给每次竞价机会掷一次骰子,只有 rand() < p 才参与,其余直接放弃。控制器调的就是这个投放概率 p ∈ [0,1]。花太快就调低 p、太慢就调高。
- 优点:实现极简,不碰出价逻辑,对赢率与成交价的分布无扭曲——参与的那部分该怎么竞还怎么竞。
- 缺点:丢弃是无差别的。它按同一个
p丢掉高价值和低价值流量,没有「优先留下便宜/高转化流量」的能力;而且被丢的那些机会不产生任何反馈样本,对 CTR 模型和流量预测都是信息损失。
3.2 出价系数(Bid Modulation / Bid-based Pacing)
每次都参与竞价,但把最终出价乘一个pacing 系数 α:final_bid = base_bid × α。花太快就调低 α(出价变低 → 赢率下降 → 消耗变慢),太慢就调高。
- 优点:
α是连续旋钮,赢率随价格平滑变化;而且降价是「顺着拍卖响应曲线」的——你会优先保住那些本来就很便宜就能赢的流量,自动放弃需要高价硬抢的贵流量,单位成本更优。 - 缺点:需要理解赢率–出价响应曲线(同样降 10% 出价,不同流量赢率下降幅度差别很大),调节非线性;在一价拍卖下,
α还会和 bid shading(压价)纠缠——两者都在压出价,必须想清楚是先 shading 再乘 α还是合并成一个系数,否则会重复压价、赢率崩掉。
3.3 实践怎么选
| 维度 | 节流(Throttling) | 出价系数(Bid Modulation) |
|---|---|---|
| 旋钮 | 投放概率 p ∈ [0,1] | 出价系数 α(final = base × α) |
| 可调性 | 离散抽样,本质是开/关采样 | 连续,随赢率-出价曲线平滑变化 |
| 赢率分布 | 不扭曲(参与部分照常竞) | 主动重塑(低价可赢的流量被保住) |
| 单位成本 | 一般(无差别丢弃) | 更优(优先放弃要高价硬抢的贵量) |
| 反馈样本 | 被丢机会无样本 | 每次参与、样本完整 |
| 复杂度 | 极低 | 需懂响应曲线,一价下与 shading 耦合 |
生产系统通常以出价系数为主(省钱、平滑),节流为辅(在 α 已到下限仍嫌快时,用低 p 做粗粒度限速兜底)。两者可叠加:α 管「出多少」,p 管「参不参与」,控制器输出映射到二者的组合。
四、第三步:为什么是 PID——从开环到闭环
4.1 开环为什么不行
最容易想到的是开环方案:预先按目标曲线算好「每 5 分钟花多少」,到点就放行那么多预算。问题是它没有反馈——它假设「放行 X 元预算就真的会花掉 X 元」,但现实里:
- 流量在波动:预测的晚间流量高峰可能因为一场大促被稀释。
- 赢率在漂移:竞争对手涨价、ADX 底价变化,同样的出价今天赢率就是比昨天低。
- 模型有偏差:CTR 预估偏高会让出价偏高、消耗偏快。
任何一个偏差,开环都无法自我修正,只会一路跑偏。必须闭环:实时测量真实消耗,与目标比对,用误差反过来调旋钮。
4.2 PID 是什么,为什么用它
PID 是工业界最成熟的闭环控制器,输出由三项相加构成。设误差 e(t) = 目标累计消耗 r(t) − 实际累计消耗 y(t)(e>0 表示落后了、该加速):
u(t) = Kp·e(t) + Ki·∫e(τ)dτ + Kd·de(t)/dt
└ 比例 P ┘ └── 积分 I ──┘ └── 微分 D ──┘
看当前偏差 看历史累积 看变化趋势
| 项 | 看的是 | 在 pacing 里的作用 | 调大了会 |
|---|---|---|---|
| P(比例) | 当前偏差多大 | 落后越多,出价系数抬得越猛,快速纠偏 | 反应快但易在目标附近来回震荡 |
| I(积分) | 历史偏差累积 | 消除稳态误差——纠正「长期总是差一点没追上」的系统性欠投/超投 | 累积过头 → 过冲、windup |
| D(微分) | 偏差变化趋势 | 看到「正在快速逼近目标」就提前收力,抑制过冲、抗流量突变 | 放大高频噪声(消耗数据本就抖) |
经典闭环:误差 → PID → 出价系数 → 消耗 → 反馈。注意那条带延迟的反馈线——真实消耗要经日志回灌才可见,这段 dead time 是分布式 pacing 一切麻烦的根源(第五节)。
为什么是 PID 而不是更花哨的控制器(MPC、RL):可解释、易调参、无需模型、久经考验。pacing 的被控对象(出价→消耗)虽非线性,但在工作点附近用 PID 足够稳;工程上更看重「出问题能一眼看懂、能手调」,而不是理论最优。很多团队实际只用 PI(去掉 D,因为消耗信号噪声大、微分弊大于利),效果已经很好。
4.3 离散实现(工程上就是这么写的)
真实系统是离散采样的:每隔一个控制周期 Δt(比如 10~60 秒)算一次。位置式 PID 的骨架:
# 每个控制周期执行一次(campaign 粒度)
def pacing_tick(camp, now):
target = camp.target_spend_ratio(now) # r(t):目标累计消耗占比
actual = camp.actual_spend_ratio(now) # y(t):回灌来的真实累计占比(有延迟!)
e = target - actual # e>0 落后、该加速
camp.integral += e * DT
# ---- 抗积分饱和:见 4.4,这里先记住必须 clamp ----
d = (e - camp.prev_e) / DT
camp.prev_e = e
u = KP * e + KI * camp.integral + KD * d
# 输出映射到出价系数 α,并夹到合理区间
alpha = clamp(1.0 + u, ALPHA_MIN, ALPHA_MAX) # 例如 [0.1, 3.0]
camp.alpha = alpha
return alpha
上面是位置式(position form):直接累加积分、算出绝对输出 u。另一种常见写法是增量式(velocity form)——只算相邻两拍的输出增量 Δu = Kp·(e−e_prev) + Ki·e·Δt + Kd·(e−2e_prev+e_prev2)/Δt,再 α ← α + Δu。增量式不显式维护积分变量,对「无扰切换/热更新参数」更友好,但抗积分饱和要靠对 α 本身限幅来隐式实现。两种形式数学等价,pacing 里位置式 + 显式 anti-windup 更直观、更好排查,这里采用它。
在线出价路径只做一次乘法,零额外 IO:
final_bid = base_bid × campaign.alpha # α 由后台控制周期刷新,在线只读
这正好呼应总览的数据面原则——控制器在近线/后台跑,在线路径只读一个刷新好的系数,绝不在 20ms 预算里做反馈计算。
4.4 调参与三个必须处理的坑
坑一:积分饱和(integral windup)——最经典、最致命。
当流量极少、α 已经顶到 ALPHA_MAX 还是追不上目标时,误差 e 持续为正,积分项 ∫e 会一直往上累积成一个巨大的数。等晚些流量回来,实际消耗刚要追上,那个被撑大的积分项会让 α 继续飙高、剧烈过冲、瞬间超投。解法是 anti-windup:一旦输出被 clamp(饱和),就停止累积(甚至回退)积分项。
raw = 1.0 + (KP*e + KI*camp.integral + KD*d)
alpha = clamp(raw, ALPHA_MIN, ALPHA_MAX)
if alpha != raw: # 发生了饱和
camp.integral -= e * DT # 回退这一步的积分累积(clamping anti-windup)
坑二:微分放大噪声。 消耗是离散事件、抖动大,直接对 e 求微分会得到满是毛刺的 d。要么去掉 D 只用 PI,要么对误差/微分做低通滤波(EMA)后再用。
坑三:系数不归一。 Kp/Ki/Kd 的合适取值和「预算规模、控制周期、流量量级」强相关。大预算 campaign 和小预算 campaign 用同一套系数,一个纹丝不动、一个剧烈震荡。工程上应把误差归一化(用「占日预算比例」而非绝对金额),让一套系数能跨 campaign 复用。
一句话本质
PID 在 pacing 里不是在追求「控制理论最优」,而是在用三个可解释的旋钮,把「落后就加速、超前就收力、别过头」这套朴素直觉,做成一个能自动纠偏、还能手调的闭环。真正的工程难度不在公式,而在 windup、噪声、系数归一这三件让它在生产里不炸的事。
五、分布式落地:dead time 决定了谁来跑 PID
前面的 PID 像是「一个 campaign、一个控制器、消耗立刻可见」。但真实 Bidder(见总览第六节)是多实例、无会话状态、真实消耗延迟可见的。把 PID 塞进这个环境,最大的变量是反馈延迟。
分布式:中心跑 PID → 下发全局系数 α → 实例只执行。真实消耗经 Kafka 回灌有秒级延迟——这段 dead time 就是控制稳定性的命门。
5.1 dead time(纯滞后)是控制系统的头号杀手
真实消耗不是即时可见的:一次赢价(nurl)到真正产生曝光、再经计费事件(burl/曝光回传)回传、经 Kafka 聚合刷回控制器,中间有秒级到分钟级延迟。控制器此刻看到的 y(t),其实是过去某个时刻的消耗。
在控制理论里这叫纯滞后(dead time),是最难对付的一类扰动:你根据「过时的」测量去调节,很容易调过头——因为你的调节效果也要等一个 dead time 才反映到测量上,期间你可能已经连续加了好几次力。这会导致震荡甚至发散。
对策就一条核心原则:控制周期必须远大于反馈延迟。
- 回灌延迟 5 秒,控制周期就不该是 1 秒——那样你会在看到上一次调节效果之前就调了 5 次。周期取到几十秒量级,让每次调节的效果基本可见后再调下一次。
- 想更快,就得把 dead time 本身压下去(缩短回灌链路),而不是盲目提高控制频率。
5.2 谁来跑 PID:中心式 vs 本地式
| 方案 | 怎么做 | 优点 | 缺点 |
|---|---|---|---|
| 中心式(主流) | 中心服务聚合全局真实消耗、跑一个 PID、算出全局 α 下发到所有实例 | 全局视角,天然避免各实例「盲人摸象、各自抢跑」;系数一致 | 依赖下发链路;α 有下发延迟 |
| 本地式 | 每个实例用「本地分片消耗」各跑一个 PID | 无需下发、反应快 | 每个实例只看到局部消耗,流量倾斜时各实例目标感知不一致,容易合起来跑偏;实例扩缩容时状态漂移 |
主流选择是中心式跑 PID:由中心用全局消耗做闭环、产出 α,随预算分片一起周期性下发。实例端不跑控制器,只做两件事:在线出价乘 α;本地保留一个令牌桶做亚秒级的硬限速兜底(防止下发间隙内某实例被热点流量瞬间打爆本地分片)。这样:
- PID 在中心:全局视角、周期够长(> dead time)、稳定。
- 令牌桶在本地:亚秒级、无状态可重建、只做「别在这一瞬间超速」的粗保护。
这也顺带回答了总览留的一个扣子——令牌桶和 PID 不是二选一,而是分工:PID 管「中长期节奏对不对」,令牌桶管「这一秒别爆」。
5.3 和「不超投」的分片如何协同
Pacing 系数 α 控制速度,本地预算分片 + 熔断控制总量上限。两者叠加:α 让日常消耗贴合曲线;当某 campaign 真的临近耗尽,中心直接下发熔断(α→0 或停止参与),这是硬闸门,优先级高于 pacing 的平滑。换句话说,Pacing 是常态下的软调节,熔断是逼近边界时的硬保护。
六、Pacing 不是孤岛:与其它模块的耦合
- 与冷启动:新 campaign / 新创意还没有可靠的 CTR 与流量预测,目标曲线和赢率响应都不准。此时 pacing 常先走保守 ASAP 或固定慢速探索,攒够数据再切到流量加权 + PID。参见冷启动一文里「每层都在解同一个问题」的视角。
- 与出价/一价拍卖:
α与 bid shading 都在乘出价,必须约定合并顺序与边界,避免重复压价。一般把 pacing 系数作用在「愿意出的真实价值」上,shading 再在其后按拍卖机制压到成交价附近。 - 与频控:pacing 减少的是整体速度,频控限制的是单用户次数。两者独立但会叠加影响赢率——调 pacing 时要意识到频控已经在替你丢掉一部分流量。
- 与多 campaign 竞争同一流量:同一次曝光,你的多个 campaign 可能都想投(内部小拍卖)。各自的
α会改变它们的相对出价,从而改变内部胜者——pacing 因此会间接重排你自己的 campaign 优先级,需要监控是否有 campaign 被系统性挤出。
七、可观测性与生产实战经验
pacing 是那种「不看监控就不知道已经跑偏了」的模块,因为它的偏差是缓慢累积的。必看的曲线与经验:
- 目标 vs 实际消耗曲线(每 campaign):最核心的一张图。两条线该几乎重合;持续劈叉就是控制失灵的信号。
- α / p 的时间序列:系数长期贴着
ALPHA_MAX或ALPHA_MIN不动,说明旋钮已打满仍无能为力——问题不在控制器,而在预算/出价基线定错了或流量根本不够。 - windup 是最常见的线上事故:忘了 anti-windup,低流量时段积分被撑爆,流量一回来瞬间超投。任何 pacing 上线前,先专门压测「长时间低流量后突然放量」这个场景。
- 对账窗口越长,pacing 越飘:回灌延迟直接等于 dead time。高价值 campaign 应缩短回灌周期;监控里要把「回灌延迟」本身当一个指标盯,它一变大,pacing 质量必然跟着劣化。
- 控制周期别贪快:见过团队为了「更实时」把控制周期压到秒级,结果在 dead time 面前疯狂震荡、消耗曲线锯齿状。先量清楚回灌延迟,再定控制周期(数倍于它)。
- 收盘临界的处理:临近投放时段结束、预算还剩不少时,pacing 会拼命抬
α抢量,可能在最后一小时高价烂投。要设收尾策略(如最后阶段封顶α、或允许小额欠投而非高价清仓)。 - 系数要按 campaign 归一,别用全局一套:大小预算、宽窄定向的 campaign 动态特性天差地别,共用
Kp/Ki/Kd必有一批震荡、一批迟钝。用归一化误差 + 分档系数。 - 热点 campaign 的分片与 pacing 会打架:宽定向大预算 campaign 命中绝大多数流量,本地分片被迅速耗尽、频繁触发本地令牌桶限速,会让中心 PID 看到的「消耗」失真。热点要单独按更细粒度分片、更短回灌周期伺候。
- 别忘了 pacing 也要能一键关:出问题时,能把某 campaign 或全局切回 ASAP / 固定系数,是重要的止损开关。
八、速查表
- Pacing = 预算控制的时间维度:不超投管金额,pacing 管节奏(在投放时段内平滑消耗,别前重后轻)。
- 三步走:① 定目标曲线
r(t)(流量加权最常用)→ ② 选执行旋钮(出价系数α为主、节流p为辅)→ ③ 用 PID 闭环纠偏。 - 误差:
e = 目标累计 − 实际累计;e>0落后该加速,e<0超前该收力。 - PID 三项:P 快速纠偏、I 消稳态误差、D 抑过冲;消耗噪声大时常只用 PI。
- 头号坑:积分饱和(windup)→ 必做 anti-windup(饱和即冻结/回退积分);微分要滤波;系数要归一。
- 分布式命门:回灌延迟 = dead time,控制周期 ≫ dead time,否则震荡。
- 分工:中心跑 PID 出全局
α(全局视角、避免抢跑);本地令牌桶做亚秒级硬限速;熔断是逼近预算上限的硬保护,优先级高于 pacing。 - 在线路径零成本:控制器后台跑,20ms 内只读一个刷新好的
α做乘法。
Pacing 的本质,是把「预算不超投」这个静态约束,升级成一个带时间维度的动态控制问题:给定一条目标消耗曲线,用出价系数作旋钮、用真实消耗作反馈,靠一个 PID 闭环把实际消耗抹平到贴合目标。它的全部工程难度,都集中在两处——让控制器在生产里不炸(windup、噪声、归一),以及在一个反馈天然滞后的分布式系统里,让控制回路依然稳定(dead time 与控制周期的取舍)。理解了这两点,也就理解了总览第六节留下的那半个「最难的一致性问题」。
延伸阅读
- Pacing 目标曲线:时段消耗权重怎么设计:本篇的姊妹篇——
r(t)这条目标曲线本身怎么画(权重口径、构造管线、日型建模、预测与控制分工)。 - Bidder 竞价服务架构(总览):第六节讲「不超投」的本地分片 + 异步回灌,本篇是它的另一半「平滑消耗」。
- Last Look 与拍卖透明度:一价拍卖下 bid shading 与 pacing 系数如何协同、避免重复压价。
- 冷启动:程序化栈每一层都在解同一个问题:新 campaign 没有可靠 CTR 与流量预测时,pacing 如何先探索再收敛。
- Bidder 定向过滤(Targeting Filter):频控与 pacing 都在「减少参与」,两者如何叠加影响赢率。
- Grafana JVM 监控分析:pacing 是「不看监控就发现不了跑偏」的典型,指标看板与告警的通用思路。
一手资料与背景:
- Karlsson, N. & Zhang, J. Applications of Feedback Control in Online Advertising (ACC 2013):把在线广告投放建模成反馈控制问题的经典论文,pacing 用 PID 的理论出处之一。
- IAB Tech Lab. OpenRTB 2.6 Specification:
nurl/burl/ 价格宏的定义——它们决定了「真实消耗」何时可见,即控制回路的 dead time 起点。 - Åström, K. J. & Murray, R. M. Feedback Systems: An Introduction for Scientists and Engineers:PID、积分饱和、dead time 的标准教材参考。
附录:术语表
- Pacing(平滑投放):让预算在投放时段内按目标节奏均匀消耗,而非有钱就尽快花光。
- 目标消耗曲线
r(t):控制的设定值——到时刻 t 累计应消耗的预算占比;常按各时段历史流量加权。 - ASAP:as soon as possible,不做平滑、能花就花的消耗模式。
- 节流(Throttling):按投放概率
p抽样决定参不参与竞价的 pacing 手段。 - 出价系数
α(Bid Modulation):final_bid = base_bid × α,通过调出价快慢来调消耗速度的 pacing 手段。 - PID 控制器:由比例(P)、积分(I)、微分(D)三项相加构成的经典闭环反馈控制器。
- 误差
e:e = 目标累计消耗 − 实际累计消耗,PID 的输入。 - 稳态误差:系统稳定后仍存在的持续偏差,由积分项 I 消除。
- 积分饱和(windup):输出饱和(顶到上下限)期间积分项持续累积,导致后续剧烈过冲;用 anti-windup 处理。
- dead time(纯滞后):从调节动作发生到其效果在测量中可见之间的延迟;分布式 pacing 里等于消耗回灌延迟。
- 控制周期
Δt:控制器两次计算之间的间隔;必须远大于 dead time 以保稳定。 - 令牌桶:以固定速率发放令牌、无令牌即拒绝的限速结构;本地用作亚秒级硬限速兜底。
- 熔断:campaign 临近预算耗尽时直接停止/清零出价的硬保护,优先级高于 pacing 平滑。
- 超投 / 欠投:实际花费超过 / 少于预算;pacing 要同时防止提前花光与结尾剩余。