广告投流第一战:如何用 StarRocks 搞定“起量监控”与“素材优选”?
在广告投放的全链路漏斗中,流量请求 → 流量识别 → 广告召回 → 出价竞价 → 胜出反馈 → 展示→ 点击→ 数据回收是完整闭环。 本篇作为广告投流系列第一篇,完整梳理整个漏斗,并聚焦展示→点击阶段的 StarRocks 实时分析能力,解决“出价/素材实时监控”与“素材创意优选”两大难题。同时结合 Pinterest 真实生产案例,展示方案在亿级规模广告场景下的落地效果。 广告投流本质上是多阶段漏斗,每一环都存在损耗, 本文 StarRocks 方案主要服务于阶段三的实时监控与决策,同时通过分钟级数据反馈反哺阶段一、二的召回与出价优化。 流量进来后,系统首先要做的是识别流量,匹配人群与定向,再进行广告召回和出价决策。 竞价成功后,DSP 会收到反馈,进入投放阶段的展示环节,但竞价成功与实际展示之间存在漏斗损失(fill rate、超时、可见性等)。 在投放交付阶段,展示后产生点击,数据实时回收,形成闭环监控与优化。 通过以上三个阶段的完整漏斗分析,可见展示→点击阶段的 CTR 直接影响 eCPM 排名,是起量成败的最关键变量。本文 StarRocks 方案主要针对阶段三的实时监控与素材优选,同时通过数据闭环反哺前序召回与出价,实现全链路提效。 广告系统的核心竞价公式: 在程序化广告(RTB/竞价)中: eCPM(千次展示有效收益)本质上是广告系统对单次展示价值的综合预估,它直接决定了广告在竞价排序中的竞争力(eCPM 越高 → 排名越靠前 → 曝光机会越多 → 起量越轻松)。 如上文完整漏斗所示,在展示-点击阶段,CTR 是最为关键的变量。提升 CTR 不仅能直接降低点击成本(CPC),还能显著提高 eCPM,从而在竞价队列中获得更多胜出机会。一份高 CTR 的广告足以证明其创意、文案、视觉设计或目标定向足够吸引用户。 CTR 诊断三类情况: 本方案主要面向广告主的投放团队、增长团队以及运营团队,特别适用于以下场景: 在流量获取阶段,运营人员通常面临三大核心痛点: (1)冷启动困难 新账户/新素材/新计划上线后,平台算法(如 oCPM/oCPC/oROI)需要积累足够样本(点击、转化数据)来学习和匹配人群。没有历史数据 → 系统不敢给优质流量 → 曝光少 → 起量慢 → 预算烧得慢或烧不出去。 这对广告主来说是典型的高成本低效率阶段:钱花了但没效果,测试期拉长,试错成本高。 (2)素材衰退 素材衰退:老素材 CTR 下滑(用户审美疲劳)→ eCPM 下降 → 排名掉 → 曝光减少 → 量级萎缩。广告主需要不断迭代创意,否则 ROI 崩。 (3)空跑浪费 空跑:高曝光但低点击/无转化 → 预算白烧(无效流量)。这直接导致广告主 CPA 暴涨、ROI 负值,甚至赔付(如果平台有成本保障)。优化师常见事故就是“账户总空跑”“计划前端转化好后端差”“预算瞬间跑超但没转化”。这些也都是广告主/代理商/优化师的普遍痛点。 通过数据驱动的方式,实现以下三个核心目标: 广告投放团队需要对不同 创意 × 渠道 × 地域 × 人群标签 的 CTR 趋势进行实时监控, 当某个创意的 CTR 突然下降超过 20%,或某个渠道的 CTR 异常上升但人群转化没有提升,系统应该在分钟级发出告警。 广告主投放团队在起量阶段常见的常见需求: StarRocks:把等数据、人工盯盘变成实时智能决策引擎 采用 Flink + StarRocks (明细模型 + 物化视图) 架构选型。 曝光和点击数据通过流式 append 写入 StarRocks 明细表,每条广告展示事件实时更新聚合表。 (1)明细表设计 (2)创建异步物化视图,每分钟刷新 (3)指标监控 1:素材优选, 高 CTR 新素材, 及时增加预算 DEMO 执行示意 (4)指标监控 2:查询近 60 分钟多维 CTR 趋势, 毫秒级查询响应 DEMO 执行示意 (5)异常监控 1: 使用 StarRocks 的窗口函数计算移动平均,识别 CTR 突降 20%异常 DEMO 执行示意 (6)异常监控 2:滑动窗口进行空跑预警, 一分钟刷新一次(曝光过万 + 点击极低) 广告主同时投放 3-5 个不同版本的创意素材,需要快速识别哪个版本的 CTR 最高。A/B 测试要求:流量均匀分配到各版本,实时对比各版本的曝光量、点击量、CTR,达到统计显著性(通常需要 1000+转化或 10000+曝光)后决定胜出版本,并关闭低劣版本,避免预算继续浪费在低效创意上。 用户核心诉求如下: StarRocks 核心优势:助力把握最佳决策期 传统方案要导出数据 → 用 Python/R 跑卡方 → 等半天 → 经常错过最佳决策窗口。StarRocks 支持将 SQL 一键计算,亚秒级出结果,物化视图每分钟自动刷新。 StarRocks 把 A/B 测试从“等数据、跑脚本”变成分钟级科学决策, 投放团队分钟级就能看到“版本 B 卡方值 18.7,已显著胜出”,立刻关停劣版。 采用 Flink + StarRocks(异步物化视图)架构: (1)异步物化视图定义 (2)实时多维对比看板 Demo执行示意 (3)统计显著性自动判断(卡方检验,胜出决策核心) Demo执行示意 (4)分时段细分分析 某美妆品牌发现创意 A 在晚上 8-10 点 CTR 最高,而创意 B 在中午时段表现更好。StarRocks 的分时段分析帮助实现 Dayparting 策略,实现不同时段投放不同创意: Demo执行示意 全球知名图片社交平台 Pinterest 在广告主报表场景中,也经历了类似的架构升级。 Pinterest 拥有海量广告主,其提供的 Partner Insights工具旨在帮助广告主通过自定义仪表板查看广告的实时表现(如曝光、点击、CTR)。在原有架构中,Pinterest 使用 Druid 来存储和提供实时洞察数据。随着广告业务规模扩大,广告商对实时性、自定义分析能力及成本效率的要求日益提升, 原架构因此面临严峻挑战: Pinterest 将 Partner Insights 的底层分析引擎迁移至 StarRocks,利用 StarRocks SQL 能力替代 Druid 的 JSON 查询 DSL,并构建了统一的低延迟分析层,直接接入广告曝光和点击流数据。核心优化点: 新的架构图如下: 迁移至 StarRocks 后,Pinterest 在广告报表场景取得了显著收益: 1. 性能提升:P90 查询延迟降低了 50%,广告主查看报表的体验更加丝滑。 2. 成本大幅降低:所需实例数量减少了 68%(仅为原来的 32%),性价比大幅提升。 3. 数据极速鲜活:数据新鲜度从分钟级/小时级提升至 10 秒级,广告主可以看到 10 秒前发生的广告曝光数据。 4. 流程简化:通过标准 SQL 进行数据提取,省去了 JSON 配置的麻烦,简化了客户 Onboarding 流程,广告主实时决策效率显著提高。 广告投放的本质是在不确定性中快速迭代决策。每一次的数据分析都应该转化为可执行策略,通过引入 StarRocks,在流量获取阶段把起量监控变成实时智能决策引擎,让 CTR 与 eCPM 真正跑赢竞品。 具体收益如下: 1. 冷启动成本大幅降低:向量召回 + 实时监控,把测试周期从“天”压缩到“分钟级”,大幅减少试错预算,提升 CTR。 2. 运营效率倍增:秒级监控 + 异常预警 + 精准人群分析,让团队从“拉数”中解放,专注策略与创意迭代。 下期预告:广告投流第二战——如何利用 StarRocks 解决“转化归因”与“ROI 实时计算”难题?敬请期待。一、广告投放完整漏斗分析
1.1 阶段一:请求阶段(流量进入→识别→召回→出价)

1.2 阶段二:竞价反馈阶段(胜出通知 → 展示准备)

1.3 阶段三: 展示交互与数据回收阶段(展示→点击→转化)

1.4 漏斗整体损耗关键点分析
二、业务方案分析
2.1 经典永不过时:eCPM 公式拆解 (广告主视角)
eCPM (effictive cost per miile)=出价*CTR*CVR*10002.2 适用对象
2.3 业务痛点

2.4 业务目标
2.5 相关指标设计

三、场景实践 1:基于 StarRocks 构建实时指标监控
实现创意×渠道×地域×人群标签 CTR 趋势监控 + 分钟级智能告警
3.1 场景描述
3.2 架构设计

在广告实时监控这类高吞吐、Append-only 的场景中,我们选择 Duplicate Key(明细模型) + 异步物化视图 的组合方案。
ad_id、creative_id + 人群标签聚合 CTR 维度聚合曝光量和点击量,计算 CTR。3.3 方案设计
CREATE TABLE fact_ad_events (
event_time DATETIME NOT NULL,
ad_id BIGINT,
creative_id BIGINT,
campaign_id BIGINT,
creative_version STRING,
channel VARCHAR(64),
region VARCHAR(32),
user_tag VARCHAR(128), -- 人群标签(兴趣/年龄/性别/LTV分层等)
event_type VARCHAR(20), -- 'show' / 'click' / 'convert'
cost DECIMAL(18,6),
user_id BIGINT
)
DUPLICATE KEY(event_time, ad_id, creative_id)
PARTITION BY RANGE (event_time) ()
DISTRIBUTED BY HASH(ad_id) BUCKETS 64
PROPERTIES ("replication_num" = "3");CREATE MATERIALIZED VIEW agg_ad_realtime
PARTITION BY date_trunc('minute', event_time)
DISTRIBUTED BY HASH(creative_id)
REFRESH ASYNC EVERY (INTERVAL 1 MINUTE)
AS
SELECT
date_trunc('minute', event_time) AS event_minute,
event_time, -- 必须保留原始列,让分区能找到
ad_id,
creative_id,
creative_version,
channel,
region,
user_tag,
COUNT_IF(event_type = 'show') AS impression_count,
COUNT_IF(event_type = 'click') AS click_count,
COUNT_IF(event_type = 'convert') AS convert_count,
SUM(cost) AS cost
FROM fact_ad_events
GROUP BY
date_trunc('minute', event_time),
event_time, -- 对应上面保留的列
ad_id,
creative_id,
creative_version,
channel,
region,
user_tag;WITH ranked_new_material AS (
SELECT
creative_id,
creative_version,
channel,
user_tag,
SUM(click_count) AS click_counts,
SUM(impression_count) AS impression_count,
ROUND(SUM(click_count) * 100.0 / NULLIF(SUM(impression_count), 0), 2) AS ctr_percent,
RANK() OVER (PARTITION BY channel ORDER BY
ROUND(SUM(click_count) * 100.0 / NULLIF(SUM(impression_count), 0), 2) DESC
) AS ctr_rank
FROM agg_ad_realtime
WHERE event_minute >= NOW() - INTERVAL 1 HOUR
AND creative_version LIKE '%new%' -- 新素材标识
GROUP BY creative_id, creative_version, channel, user_tag
HAVING SUM(impression_count) >= 10 -- 先过滤低曝光素材,提升性能
)
SELECT
creative_id,
creative_version,
channel,
user_tag,
impression_count,
ctr_percent,
ctr_rank
FROM ranked_new_material
WHERE ctr_rank <= 5
AND ctr_percent >= 4.0 AND impression_count >= 20;
ORDER BY channel, ctr_rank; -- Top5 且 CTR≥4%(行业可调), 且曝光至少300才参与排名
SELECT
event_minute,
creative_id,
creative_version,
channel,
region,
user_tag,
impression_count,
click_count,
ROUND(click_count * 100.0 / NULLIF(impression_count, 0), 2) AS ctr_percent,
ROUND(convert_count * 100.0 / NULLIF(click_count, 0), 2) AS cvr_percent
FROM agg_ad_realtime
WHERE event_minute >= NOW() - INTERVAL 60 MINUTE
ORDER BY event_minute DESC, ctr_percent DESC;
WITH ctr_trend AS (
SELECT creative_id, creative_version,
ROUND(click_count * 100.0 / NULLIF(impression_count, 0), 2) AS ctr_percent,
AVG(ROUND(click_count * 100.0 / NULLIF(impression_count, 0), 2))
OVER (PARTITION BY creative_id ORDER BY event_minute ROWS BETWEEN 10 PRECEDING AND CURRENT ROW) AS moving_avg
FROM agg_ad_realtime
WHERE event_minute >= NOW() - INTERVAL 30 MINUTE
)
SELECT * FROM ctr_trend
WHERE (moving_avg - ctr_percent) / moving_avg > 0.20
ORDER BY (moving_avg - ctr_percent) DESC;
SELECT creative_id,
creative_version,
impression_count,
click_count,
ROUND(click_count * 100.0 / NULLIF(impression_count, 0), 2) AS ctr_percent
FROM agg_ad_realtime
WHERE impression_count > 10000
AND click_count < 50
AND event_minute >= NOW() - INTERVAL 10 MINUTE;四、场景实践 2:基于 StarRocks 创意 A/B 测试与多版本对比
4.1 场景描述

4.2 架构设计
4.3 方案设计
CREATE MATERIALIZED VIEW agg_ad_abtest
PARTITION BY event_minute -- 直接用别名分区(3.1+ 支持)
DISTRIBUTED BY HASH(creative_id, creative_version)
REFRESH ASYNC EVERY (INTERVAL 1 MINUTE)
AS
SELECT
date_trunc('minute', event_time) AS event_minute,
campaign_id,
creative_id,
creative_version,
channel,
region,
user_tag,
COUNT_IF(event_type = 'show') AS impression_count,
COUNT_IF(event_type = 'click') AS click_count,
COUNT_IF(event_type = 'convert') AS convert_count
FROM fact_ad_events
GROUP BY
date_trunc('minute', event_time), -- 对应 event_minute
creative_id,
creative_version,
campaign_id,
channel,
region,
user_tag;SELECT
creative_version,
channel,
region,
user_tag,
SUM(impression_count) AS impressions,
SUM(click_count) AS clicks,
ROUND(SUM(click_count) * 100.0 / NULLIF(SUM(impression_count), 0), 2) AS ctr_percent,
ROUND(SUM(convert_count) * 100.0 / NULLIF(SUM(click_count), 0), 2) AS cvr_percent
FROM agg_ad_abtest
WHERE event_minute >= NOW() - INTERVAL 24 HOUR
GROUP BY creative_version, channel, region, user_tag
ORDER BY ctr_percent DESC;
WITH stats AS (
SELECT creative_version,
SUM(impression_count) AS imp,
SUM(click_count) AS clk
FROM agg_ad_abtest
WHERE campaign_id = 1234
AND creative_version IN ('V1_old','V1_new','V2_old','V2_new')
AND event_minute >= NOW() - INTERVAL 12 HOUR
GROUP BY creative_version
)
SELECT a.creative_version AS winner, b.creative_version AS loser,
ROUND(a.clk*100.0/a.imp,2) AS ctr_a,
ROUND(b.clk*100.0/b.imp,2) AS ctr_b,
POWER(a.clk - a.imp*p,2)/(a.imp*p) + POWER(b.clk - b.imp*p,2)/(b.imp*p) AS chi_square
FROM stats a CROSS JOIN stats b
CROSS JOIN (SELECT SUM(clk)*1.0/SUM(imp) AS p FROM stats) total
WHERE a.creative_version != b.creative_version
HAVING chi_square > 0.003 AND ctr_a > ctr_b;
SELECT
HOUR(event_time) as hour_of_day,
creative_version,
COUNT(CASE WHEN event_type='impression' THEN 1 END) as impressions,
COUNT(CASE WHEN event_type='click' THEN 1 END) as clicks,
ROUND(clicks * 100.0 / NULLIF(impressions, 0), 2) as ctr_percent
FROM fact_ad_events
WHERE campaign_id = 1234
AND event_time >= date_sub(NOW(), INTERVAL 7 DAY)
GROUP BY hour_of_day, creative_version
ORDER BY hour_of_day, creative_version;
五、案例:Pinterest 广告主报表重构
5.1 背景与痛点
5.2 解决方案

5.3 方案收益
总结