资讯动态

自建AB实验平台:分流、指标与统计工程实践

发布时间:2026/10/29 18:39:57 来源:尧图企业网站定制
1. 先算清楚到底要不要自建这笔账做 AB实验平台这件事十有八九不是从我想建一个平台开始的而是从这个功能到底有没有用谁说了算开始的。我在几年前接手过一个推荐改版的评估工作当时团队的做法是看上线前后两周的核心指标曲线改版上线后人均点击涨了 3.2%产品经理很高兴数据同学泼了盆冷水——那两周正好赶上一次运营活动还有一天是节假日。争到最后谁都说服不了谁结论只能挂起来。那之后我们才真正下定决心做 AB实验平台。所以先说明白这套东西解决的不是怎么算显著性这个数学问题它解决的是决策权归属问题——把我觉得这个改版更好变成数据表明这个改版在 95% 置信水平下更好让争论有个共同的裁判。AB实验平台本质上是三套系统的耦合一套分流系统决定谁看到哪个版本一套数据系统把用户行为和实验分组对上号一套统计系统把数字变成结论。除此之外还有一堆看起来不重要、实际上天天折磨人的外围模块——实验配置管理、权限审批、指标口径管理、实验生命周期治理。适合谁参考如果你所在的团队已经有稳定的日活、每个月要跑十几个以上的产品实验、并且已经受够了上线前后对比法带来的扯皮那这套东西值得认真投入。如果日活只有几千、一个月跑不了一次实验老老实实用最简单的分流加人工分析就行别一上来就搞平台。1.1 三种常见方案的适用边界行业里做实验能力大致三条路直接买第三方 SaaS、拿开源方案改、完全自建。这三条路没有绝对优劣只有匹配度。方案上手成本数据可控性定制空间适合的团队形态第三方 SaaS低几天接入数据出境或出域需评估弱统计口径按对方定义实验量小、无专职数据工程人力开源方案二次开发中1-2 个月数据在自己域内中核心逻辑可改有 1-2 名后端 1 名数据开发完全自建高3-6 个月起完全自主强有独立的数据平台团队我见过最典型的误判是一个三十来人的业务团队为了数据安全选了完全自建结果半年时间只做出一个能分流、能看转化率、但做不了方差缩减、做不了分层正交的半成品最后实验结论还是靠人肉 Excel 复核。自建的门槛不在分流在统计和数据链路的工程化。分流逻辑一个资深后端两周就能写出来但从曝光日志到指标聚合到显著性检验这条链路要处理埋点缺失、用户跨端、指标口径漂移、计算任务重跑等一堆问题这才是吃掉 80% 工期的部分。1.2 自建成本里最容易被低估的三块第一块是指标治理。平台刚上线时大家都很兴奋一周能建二十个实验三个月后发现同一个下单转化率在三个实验报告里算出三个数——有人算的是支付成功订单数除以曝光 UV有人算的是提交订单数除以进店 UV还有人把退款订单也扣了。这不是技术问题是治理问题但最后一定是平台来背锅。所以指标必须在平台侧统一定义、统一注册、统一计算业务方只能引用不能自己写 SQL。第二块是实验债。跑完不关的实验、长期挂着 5% 流量的实验、互相冲突的实验会随着时间累积成灾难。我见过一个系统里同时存在 40 多个运行中状态的实验其中一半根本没人在看数据。这些实验会互相抢流量、污染对照组让后续实验的基线漂移。第三块是人对平台的信任。平台第一版上线时只要能出现一次两个本该一样的分组跑出显著差异业务方就会永久性地不信任它。所以 AA 测试、SRM 校验、分流一致性校验这些防守型功能必须在第一版就有不能等出事再补。1.3 一个务实的最小可用版本长什么样如果让我重新排一次优先级第一版只做四件事确定性的哈希分流、曝光日志上报、一到两个核心指标的 T1 离线计算、一个能做 t 检验和卡方检验的报告页。这四件事跑通平台就已经能替代 80% 的扯皮了。分层正交、CUPED 方差缩减、实时护栏熔断这些等实验量上来、业务方开始抱怨流量不够用实验要跑一个月才有结论的时候再加那时候加是有明确痛点的推起来阻力小得多。反过来一开始就照着大厂的技术博客堆功能最常见的结局是架构图画得很漂亮分层、域、互斥、正交全都有但没人会用因为配置一个实验要填十几个字段业务方宁愿直接上线看大盘。平台的复杂度必须由使用者的痛感驱动而不是由架构的优雅程度驱动。2. 分流引擎平台所有正确性的地基分流引擎是整座楼的地基它出错的方式很隐蔽——不会报错、不会崩只会悄悄让两组人变得不可比。我在第二个项目里踩过一次分流用的哈希函数在客户端和服务端各实现了一遍两边的字符串编码处理不一样一个用了 UTF-8 的字节数组一个先把 ID 转成了小写结果同一批用户在两端的命中结果不一致实验跑了三周才发现对照组里混进了实验组的用户。这种错误从数据表面完全看不出来只有做分流一致性校验才能抓到。2.1 哈希分桶的确定性要求与盐值设计分流的核心就一句话把用户 ID 稳定地映射到[0, N)这个整数区间里的某个桶。要求是同样的输入永远得到同样的输出且分布尽量均匀。工程上基本都用 MurmurHash3 或者 xxHash取模 10000 作为桶空间。为什么是 10000 而不是 100因为桶空间太小时实验流量只能按 1% 的粒度切想开个 0.5% 的小流量实验就做不到10000 的桶空间支持 0.01% 的粒度足够用了。盐值salt是整个设计里最关键也最容易被忽略的参数。盐值决定打乱方式不同盐值下同一个用户会被分到完全不同的桶。设计原则是这样的同一个实验内所有分流入口必须用同一个盐值。服务端 SDK、客户端 SDK、离线回刷任务、数据侧的归因逻辑四处算出来的结果必须一致否则就会出现用户看到了实验版本但日志里记的是对照组这种情况。不同实验必须用不同的盐值。如果两个实验都用实验 ID 之外的固定盐值那所有实验的分组会完全重合——实验 A 的对照组永远是实验 B 的对照组两个实验的效果就纠缠在一起了。盐值应该由实验 ID 派生而不是随机生成后再存库。用salt hash(experiment_id)这种方式好处是配置同步出问题时可以用实验 ID 反推出盐值重算不用去数据库里捞。一个典型的服务端分流判断长这样import mmh3 def assign(user_id: str, experiment_key: str, total_buckets: int 10000) - int: # salt 由实验标识派生保证跨端跨语言可复现 salt mmh3.hash(experiment_key, seed0) 0xFFFFFFFF return mmh3.hash(f{user_id}, seedsalt) % total_buckets def in_traffic(user_id: str, experiment_key: str, traffic_pct: float) - bool: bucket assign(user_id, experiment_key) return bucket int(traffic_pct * 100) # 1% 100 个桶注意int(traffic_pct * 100)这个细节。用浮点乘法再取整在 0.07 这种数值上会出现7.000000000000001之类的问题稳妥做法是配置里直接存整数百分比比如 7 表示 7%避免浮点误差导致不同语言的 SDK 算出不同结果。这种坑非常典型Java 的(int)(0.07 * 100)是 7但某些语言环境下可能是 6。2.2 分层、域与正交解决流量不够用单个实验独占 10% 流量时一切都好办但当团队同时跑十几个实验、还都想拿 10% 流量时问题就来了。如果每个实验独立分流、互不感知同一个用户可能同时命中 A 实验的实验组和 B 实验的实验组两个实验的效果就互相污染。最朴素的处理是互斥——把流量切成若干段一个实验占一段段与段之间不重叠。但这样流量利用率极低100% 的流量切成 10 段同时最多只能跑 10 个实验。真正的解法是分层正交layered experimentation。原理不难给每个层layer一个独立的盐值同一层内的实验互斥用互斥区间划分流量不同层之间的实验正交因为盐值不同用户的命中组合在统计上独立。这样一来同一个用户可以同时参与 10 个不同层的实验只要这 10 个实验分别属于 10 个不同的层。层的划分不是随意的需要按可能互相影响的维度来组织。比如推荐策略层所有会改变推荐结果的实验放这一层互斥。UI/交互层按钮颜色、布局、文案类实验放这一层互斥。定价/优惠层涉及钱和权益的实验必须互斥而且通常单独一层加更严格的审批。后端性能层缓存策略、接口超时这类实验对业务指标影响小可以给很低流量。判断两个实验能不能放同一层标准很朴素如果一个用户同时受到两个实验的影响你还能不能把效果归因清楚如果两个实验都在改推荐排序那绝对不能同层如果一个改推荐、一个改支付页按钮影响路径基本不交叉就可以放不同层。实现上同一层内的互斥靠区间划分def in_layer(user_id: str, layer_key: str, start: int, end: int) - bool: b assign(user_id, layer_key) # 该层独立的盐值 return start b end # 实验 A 拿 [0, 1000)实验 B 拿 [1000, 2000)天然互斥这里有个必须提前想清楚的问题层内区间分配谁来管。如果让业务方自己填区间半年后必然出现区间重叠、或者大量碎片化的 0.3% 小段没人敢回收。稳妥做法是平台侧维护一个区间分配器按申请流量大小自动找空闲段实验结束后自动回收。2.3 分流位置服务端、客户端与边缘分流的调用点在哪直接决定了实验能做什么、不能做什么。服务端分流最稳一致性最好控制但受限于服务端下发的内容——你改不了一个已经打包在 App 里的按钮位置。客户端分流能覆盖 UI 层改动但风险在于版本覆盖老版本 App 没有新代码分流了也白分。边缘节点分流适合做地域、运营商维度的实验但对用户维度的精细控制能力弱。我的经验是主用服务端客户端分流只做补充并且必须带版本白名单。客户端实验配置里一定要有min_app_version字段低于这个版本的请求直接返回对照组避免用户在升级过程中反复横跳。还有一个更隐蔽的问题用户跨设备、跨登录态的分流一致性。用户在 Web 端看的是实验组登录后在 App 端被分到对照组体验上是断的。解决办法是把分流 ID 从设备 ID 换成登录后的稳定用户 ID未登录时用设备 ID 兜底并在登录事件发生时重新计算一次分组、记录一条分组变更日志。2.4 曝光上报谁才算进入实验这一节我建议所有做平台的人都认真读两遍因为它是最容易把整个实验数据搞脏的地方。分流assignment和曝光exposure是两件事。分流是系统决定了给你看什么曝光是你确实看到了。如果直接用分流日志算指标会有大量分了组但根本没进入页面的用户被算进分母导致转化率被稀释得面目全非。所以必须上报曝光日志并且用曝光的用户作为分析样本。但曝光上报本身也有坑。曝光时机怎么定页面渲染完成算曝光还是接口返回实验参数算曝光前者更贴近真实被看到但会漏掉那些参数下发了却没渲染出来的情况后者覆盖面广但会把请求了但页面跳走的用户也算进去。我倾向于在业务代码里显式打点位置放在实验内容实际对用户可见的那一行由业务方负责埋平台提供统一的 SDK 方法。这样责任清晰虽然会累一点。还有一个必须处理的情况曝光去重与首次曝光时间。同一个用户在一次实验里可能上报几万条曝光分析时只需要第一条。日志侧要做user_id experiment_id维度的去重取最小时间戳作为进入时间。指标计算的窗口期也从首次曝光开始算而不是从实验上线时间开始算——这个差别在长周期实验里能差出几十个百分点。提示分流日志和曝光日志一定要分开存、分开上报。把两者合并成一条日志看起来省事实际上会让漏斗分析和分流正确性核查都没法做。3. 实验配置模型与生命周期状态机平台做久了会发现真正让业务方抱怨的往往不是统计算错了而是建一个实验太麻烦。一个实验配置动辄二三十个字段产品经理填到一半就放弃转头去找研发你帮我配一下。所以配置模型的设计目标应该是让常见的实验三分钟配完让复杂的实验也能配得下。3.1 实验对象的核心字段怎么切我把实验配置拆成四组这个拆法直接决定了后续的存储结构和表单分步第一组是标识与归属实验 ID全局唯一一旦创建不可改、实验名称、所属业务线、负责人、关联需求单号。实验 ID 要能体现语义我一般用biz_layer_scenario_yyyymmdd_seq这种格式比如rec_layer_sort_20240315_01好处是翻日志时一眼能认出来坏处是改层就得重建实验——所以层信息不应该编进 ID只包含业务线和场景就够。第二组是分流配置层标识、流量区间、实验组与对照组数量、各组流量比例、分流单元用户 ID / 设备 ID / 会话 ID、白名单、黑名单。分流单元这个字段特别关键用会话 ID 做的实验同一个用户可能在不同会话里进不同组只适合做单次交互层面的实验用户维度的实验才能评估长期效果。第三组是版本配置对照组参数、实验组参数、参数类型字符串/布尔/数值/JSON、默认值。参数要有严格的 schema 校验否则上线后 SDK 解析失败会把线上打挂。第四组是指标与分析配置核心指标、护栏指标、分析周期、最小样本量、显著性水平、是否开启方差缩减。用一张表概括关键字段的约束字段类型必填约束说明experiment_idstring是创建后不可变全局唯一layer_keystring是决定互斥关系同层实验流量区间不可重叠unitenum是user/device/session创建后不可变traffic_range[int,int]是基于 10000 桶空间闭开区间groupsarray是至少 2 组流量比例和为 100%paramsjson是需通过 schema 校验start_time / end_timetimestamp是结束时间到点自动转已结束guardrail_metricsarray否触发阈值后自动熔断创建后不可变这个约束要严格执行。我见过有平台允许改unit字段结果一个实验从用户维度改成会话维度历史数据和新增数据混在一起整个结论作废。凡是会影响历史数据一致性的字段一律锁死要改就新建实验。3.2 状态机把谁能改什么固化下来实验的生命周期不是简单的开关而是一个带权限约束的状态机。我们最终定下来的状态是草稿 → 待审批 → 灰度中 → 运行中 → 已暂停 → 已结束 → 已归档其中灰度中和运行中之间的区别在于流量是否放满。状态迁移的规则里有几条是血的教训换来的只有草稿状态可以修改分流配置。一旦进入灰度分流参数锁死。想调整流量只能通过扩容操作且扩容必须是单调增加、不能缩容因为缩容会让已经进组的用户被踢出实验数据出现断层。任何状态都可以进入已暂停但暂停不计入分析周期。暂停期间的分流还在生效用户看到的版本不变只是不纳入统计。这个语义要写进文档不然业务方会以为暂停等于回滚。已结束之后不能再改任何配置只能查看。想复跑只能克隆一个新实验。删除是软删除。实验的配置、分流日志、指标数据至少要保留一年否则三个月后有人来问上次那个实验怎么得出这个结论的你什么都拿不出来。审批流的复杂度要克制。我建议只对两类实验强制审批涉及资金、权益、定价的以及流量占比超过 20% 的。其他实验走负责人自审 事后审计就行。审批链路拖得太长业务方会绕开平台直接上线那平台就形同虚设。3.3 配置下发推、拉、还是两者都要配置怎么送到业务代码里直接影响实验的生效延迟和线上稳定性。常见三种模式轮询拉取最简单SDK 每隔 30 秒到 1 分钟去配置中心拉一次全量配置本地缓存在内存里。优点是实现简单、容灾好配置中心挂了就用本地缓存缺点是生效有延迟而且 QPS 随实例数线性增长。上千个实例每分钟拉一次配置中心的压力不小需要加多级缓存。长连接推送生效快秒级下发但服务端要维护海量连接客户端断线重连的处理逻辑也很麻烦。适合对生效速度有强要求的场景比如紧急止损。本地文件 定时刷新适合离线任务和批处理场景配置从对象存储同步到本地磁盘任务启动时加载。我们的做法是拉模式为主推模式只用于紧急操作。日常配置变更走轮询30 秒生效业务方完全能接受当需要紧急关停某个实验时走推送通道让所有实例立即刷新。同时所有 SDK 都必须实现配置解析失败时退回默认值的逻辑默认值就是对照组参数——宁可实验不生效也不能让线上报错。// SDK 侧的容错骨架 ExperimentConfig cfg; try { cfg configLoader.get(experimentKey); } catch (Exception e) { log.warn(load experiment config failed, fallback to control, e); cfg ExperimentConfig.control(); // 永远返回对照组 }这段代码看起来平平无奇但它是线上稳定的最后一道防线。我见过一个 SDK 因为配置里多了一个字段导致反序列化异常整个接口 500影响面覆盖全站。实验平台的任何故障都不应该转化为业务故障。4. 指标与数据链路口径不统一统计就是摆设平台上线半年左右一定会有人跑来问为什么我在平台看到的转化率和数据看板上的不一样这个问题如果答不上来平台的可信度就崩了。答案必须是确定的、可复现的因为口径定义在这里计算逻辑是这样的差异来自这三个地方。4.1 指标分层原子指标、派生指标与指标注册我的做法是把指标拆成三层。最底层是原子指标直接对应数仓里的一张明细表和一个聚合表达式比如支付成功订单数 count(distinct order_id) where status paid。中间层是派生指标由原子指标组合而成比如支付转化率 支付成功 UV / 曝光 UV。最上层是指标集业务方建实验时选的是指标集而不是逐个挑指标。关键是所有指标必须在平台侧注册业务方不能自己写 SQL。注册时要填的信息包括指标 ID、中文名、英文名、口径描述、分子分母定义、数据源表、过滤条件、是否去重、统计周期。这份元数据是后续所有计算和解释的唯一来源。举个具体的口径差异例子说明为什么必须写清楚口径项写法 A写法 B结果差异曝光 UV有曝光日志的去重用户有页面 PV 的去重用户B 通常高 5%-15%支付转化率支付 UV / 曝光 UV支付订单数 / 曝光 UV前者是人数口径后者含复购统计窗口曝光后 7 天内实验期内长尾指标差异巨大这些差异没有对错只有是否统一。平台要做的是强制所有人用同一套而不是追求某个最正确的定义。4.2 从曝光日志到分析宽表数据链路大致是四层第一层原始日志。曝光日志、分流日志、业务行为日志分开采集落到日志服务或者 Kafka。这一层只保证不丢不做任何加工。日志里必须包含的字段user_id、experiment_id、group_id、timestamp、unit_type、app_version、platform。第二层明细层。把曝光日志和业务行为按用户和时间窗口关联形成用户 × 实验 × 行为的宽表。这一层是大头数据量可能是原始日志的好几倍必须做分区和裁剪——只保留实验周期内、且只保留实验涉及的用户。我见过不做裁剪直接把全量行为表 join 上去的任务跑了六个小时还超时。第三层聚合层。按实验 × 组 × 指标 × 日期聚合出分子分母。这一层的产出就是统计引擎的输入粒度要足够细能支持后续按天看趋势、按平台拆分。第四层结果层。存显著性检验的结果、置信区间、样本量、告警状态。一个简化版的聚合 SQL 大致长这样insert overwrite table ads_exp_metric_daily partition (dt ${bizdate}) select e.experiment_id, e.group_id, e.dt, count(distinct e.user_id) as exposure_uv, count(distinct case when b.paid 1 then e.user_id end) as pay_uv, sum(case when b.paid 1 then b.amount else 0 end) as gmv from ( select experiment_id, group_id, user_id, dt from dwd_exp_exposure where dt between ${start_date} and ${bizdate} group by experiment_id, group_id, user_id, dt ) e left join dwd_user_behavior b on e.user_id b.user_id and b.dt between e.dt and date_add(e.dt, 6) -- 7 天归因窗口 group by e.experiment_id, e.group_id, e.dt;注意left join加上归因窗口这个写法。窗口设多长是个业务决策电商类一般 7 天内容类可能 1 天就够订阅类可能要 30 天。窗口越长长尾效应越明显但也越容易被外部因素干扰。窗口一旦定下来就不能改改了就前后不可比。4.3 离线、准实时和实时各管一段不是所有指标都需要实时。我的划分是T1 离线承担 90% 的分析任务跑核心指标、显著性检验、生成报告。优点是数据完整、可以重跑、成本低缺点是第二天才能看到结论。小时级准实时用于实验早期的方向判断比如上线 4 小时后看看有没有明显的负向。这时候的样本量通常不够做显著性检验只能看趋势不能下结论。秒级实时只用于护栏指标监控和自动熔断。比如支付失败率、接口错误率、崩溃率这些指标一旦异常必须立刻动作等不到第二天。实时链路的指标要和离线口径严格对齐这个对齐工作比想象中麻烦。实时算的是最近 N 分钟的去重 UV离线算的是一天的去重 UV两者天然不等。做法是实时链路只做相对比较比较实验组和对照组在同一时间段内的相对差异而不是绝对值。这样口径上的细微差异对判断影响不大。注意实时数据只能用来决定要不要紧急停止不能用来宣布实验成功。我见过太多团队在实验跑了两小时、样本量几百的时候宣布提升了 30%第二天数据回归到 2% 的案例。5. 统计引擎把论文里的方法变成能跑的代码统计模块最容易出的问题不是算错公式而是用错了方法还不自知。比如对一个明显右偏的长尾指标人均消费金额直接上 t 检验p 值看着挺小实际上检验的前提根本不成立。这一章讲讲工程实现里几个必须处理的点。5.1 样本量先算再跑实验开始前必须回答一个问题这个实验要跑多久答案不该是两周吧而应该是算出来的。样本量公式两组等比例、双侧检验n_per_group 2 * (z_{1-α/2} z_{1-β})^2 * p(1-p) / δ^2拿个具体数基线转化率 5%想检测出 10% 的相对提升即从 5% 到 5.5%绝对差 δ 0.005α 0.05z 1.96β 0.2z 0.84代入n 2 × (1.96 0.84)² × 0.05 × 0.95 / 0.005² 2 × 7.84 × 0.0475 / 0.000025 ≈ 29,792 每人组每组差不多三万总共六万用户。如果每天能进实验的用户只有两千那这个实验要跑一个月。这时候产品经理通常会问能不能把效果预期调大一点可以但要说清楚代价把相对提升改成 20%δ 0.01样本量降到七千多但真实提升如果只有 8%这个实验大概率检测不出来。样本量、检测灵敏度、实验时长三者只能选两个这个三角关系必须在配置页面直观地展示出来让业务方自己权衡。对于比率型指标还要注意基线不能太极端。如果基线转化率是 0.1%那需要的样本量会大到不现实这种情况下要么提高检测粒度看更上游的指标要么接受更长的实验周期。from scipy.stats import norm def sample_size(p: float, rel_lift: float, alpha0.05, power0.8): delta p * rel_lift z_a norm.ppf(1 - alpha / 2) z_b norm.ppf(power) n 2 * (z_a z_b) ** 2 * p * (1 - p) / delta ** 2 return int(n) 1 # 向上取整别用 round # 基线 5%检测 10% 相对提升 print(sample_size(0.05, 0.10)) # 约 29792int(n) 1而不是四舍五入是个小细节但很关键——样本量估算只能向上取整向下取整意味着研究设计本身就达不到目标功效。5.2 检验方法选型别一把锤子敲所有钉子不同指标类型要用不同的检验方法这个表我贴在了团队的 wiki 首页指标类型示例推荐方法说明比率UV 口径支付转化率两比例 z 检验分子分母都是人数用卡方也等价均值连续人均停留时长Welch t 检验不假设方差齐性长尾指标需大样本计数含大量 0人均下单次数泊松 / 负二项回归方差远大于均值时用负二项长尾金额人均 GMVBootstrap 或 分位数检验均值不稳定看 P50/P90 更靠谱比率订单口径订单支付率Delta 法分子分母都是随机变量不能直接算方差其中Delta 法最容易被忽略。当指标是订单支付成功数 / 订单创建数这种两个随机变量相除时直接套比例检验的方差公式是错的因为分子分母不独立。Delta 法用泰勒展开近似出这个比值的方差实现不算复杂但如果不知道要用它算出来的置信区间会偏窄导致假阳性率飙升。还有CUPED方差缩减这是让实验周期缩短一半的最有效手段。原理是把实验前的指标作为协变量从实验后指标里减掉它带来的方差Y_adj Y - θ × (X - E[X]) θ Cov(Y, X) / Var(X)方差缩减比例大致是1 - ρ²其中 ρ 是前期指标和后期指标的相关系数。实际场景里用户的行为习惯很稳定ρ 常常能达到 0.6 到 0.8对应方差降低 36% 到 64%等价于样本量放大 1.5 到 2.8 倍。这个收益非常可观而且实现不难——只需要把实验前 7 天或 14 天的同指标值算出来存好。但 CUPED 有个隐患必须用实验前的数据做协变量。如果用实验期间的数据会把处理效应吸收掉得出没有显著差异的错误结论。实现时要在 SQL 里明确限定时间范围别写成dt end_date这种模糊条件。5.3 多重比较与序贯检验团队同时看 20 个指标是常态。在 α 0.05 下20 个指标里至少有一个显著的概率是1 - 0.95^20 ≈ 64%。这意味着如果你不处理多重比较几乎每个实验都能找到某个指标显著提升然后你就可以在产品复盘会上讲一个很好的故事——只不过这个故事是假的。处理方案按保守程度排Bonferroniα 除以检验次数最保守适合指标少 10且对假阳性零容忍的场景。Benjamini-HochbergFDR控制错误发现率比 Bonferroni 宽松很多适合指标多、需要一定检出力的场景。Holm介于两者之间逐步校正。我的实践是核心指标只有一个对它单独用 α 0.05 检验其余探索性指标统一走 BH 校正并且在报告页明确标注探索性指标仅供参考。这样既保证了主结论的严格性又不至于错过真实信号。平台侧的实现要注意校正必须基于该实验本次分析的全部指标来做不能让业务方自己挑选指标后再算——那就退化成 p-hacking 了。序贯检验解决的是另一个问题边跑边看。业务方每天点开报告看一眼看到显著就停这在统计上是严重违规的会让假阳性率从 5% 飙到 20% 以上。正确做法是引入 alpha spending把总的 α 预算按时间或样本量分配前期给的预算很少越靠近计划终点给得越多OBrien-Fleming 边界是最常见的形式。工程实现上平台应该在报告页明确标注当前建议观望 / 可以下结论并在用户试图提前结束时弹出提示。6. 让结论可信SRM、AA 测试与实验债治理平台做到这一步功能已经齐了但能不能被信任是另一回事。这一章讲三个防守型机制它们不产生新功能但决定了平台会不会在半年后被弃用。6.1 SRM 校验分流有没有出问题SRMSample Ratio Mismatch是分流系统出 bug 的最直接信号。设计上实验组和对照组各 50%实际跑出来 50.8% 对 49.2%如果是几万样本量卡方检验的 p 值会远小于 0.001说明分流有问题。实现很简单每天定时对每个运行中的实验做卡方拟合优度检验p 值小于 0.001 就告警。为什么用 0.001 而不是 0.05因为分流有轻微的随机波动是正常的阈值太低会天天误报用 0.001 能把误报率控制在一个月一次以内。SRM 出现后的排查路径我按经验整理成一条链路看分流逻辑是否按预期执行。对比分流日志的用户数和曝光日志的用户数如果差很多说明有大量用户被分流了但没上报曝光。看是否存在非随机缺失。比如某些老版本 App 的用户不支持实验功能被排除而老版本用户恰好集中在下沉市场样本就偏了。看上报链路是否丢数据。某个机房的日志采集挂了、某个埋点版本发错了都会造成某一组数据偏少。看是否有外部流量干扰。比如实验上线期间做了一次全量推送推送人群和实验分组的重叠不随机。SRM 一旦出现这个实验的结论就必须作废不管 p 值多漂亮。因为分流不随机两组之间的差异就无法归因于实验处理。这条规则要写进平台的硬性流程里不能让业务方先看看再说。6.2 AA 测试给平台自己做体检AA 测试是把流量随机分成两组两组跑完全一样的策略理论上不应该有显著差异。它的作用是验证整个链路——分流、曝光、指标计算、统计检验——没有系统性偏差。我们上线初期每周跑一次 AA 测试持续了一个月抓出来两个问题一个是某个指标的去重逻辑写错了导致实验组多算了重复下单另一个是分流用的哈希在某个特定长度的用户 ID 上分布不均某个尾号区间的用户被大量分到同一组。这两个问题从业务实验里根本看不出来只有 AA 才能抓到。AA 测试的正确跑法是同一批实验配置下跑足够长时间、足够大样本然后对所有核心指标做检验统计 p 值小于 0.05 的比例。如果这个比例在 5% 左右说明链路正常如果到 15%说明有系统性偏差。顺便还能画个 p 值分布图正常情况下应该接近均匀分布。平台成熟后AA 测试可以改成按季度例行跑或者每次统计模块有代码变更时跑。这个习惯一定要保持它是平台自我纠错的唯一手段。6.3 护栏指标与自动熔断不是所有实验都值得等到样本量跑满。有些实验在早期就会造成明显的业务损伤——支付失败率上升、页面加载变慢、崩溃率飙升。这些必须做成护栏指标设定阈值后自动熔断。熔断的设计要点指标选快慢结合的。秒级指标错误率、超时率、崩溃率用于实时熔断小时级指标转化率、退款率用于阶段性熔断。阈值设成相对对照组而不是绝对值。比如实验组错误率比对照组高 50% 以上且持续 10 分钟比错误率超过 0.5%更合理因为不同业务的基线差别很大。熔断动作要分级。一级是告警给负责人二级是把实验组流量降到 1%三级是直接下线实验回到对照组。前两级自动执行第三级需要人工确认——因为全量回滚本身也是风险动作。熔断记录必须留档。哪个实验、什么时间、因为什么指标、触发了什么动作这些要能查。事后复盘和定责都靠这个。6.4 实验债平台成熟后最大的敌人平台跑了两年之后真正的矛盾会从功能不够变成实验太多。我统计过一次某个季度运行中的实验有 60 多个其中 20 多个的最后一次查看记录在两个月前10 多个的负责人已经离职。这些实验占着流量、污染对照组、让新实验的基线不断漂移。治理手段有几条都需要产品机制配合强制结束时间。建实验时必须填计划结束时间到期前 3 天开始提醒到期后自动转已结束并把流量释放。不能无限期挂着。实验分层配额。每一层分配固定的流量总额申请时要说明为什么需要这么多流量超额的实验需要上一级审批。月度实验复盘会。不是走形式而是把当月所有实验的结论、样本量、是否达到预期效果拉个表过一遍。没结论的实验必须给出解释——是样本量不够、分流有问题还是业务方根本没看。实验结论沉淀。每个结束的实验必须填一行结论相对对照组提升 X%显著/不显著是否全量。这份沉淀后来会成为团队最有价值的资产比任何文档都有用——它记录了哪些改动真正有效哪些是拍脑袋想出来的。提示给实验打标签是个成本极低、收益极高的做法。推荐、UI、性能、商业化这几个标签加上去一年后你能按主题检索出过去两年推荐侧的所有实验这个价值远超你的想象。7. 一些不太上台面但很管用的实践先说一个反直觉的结论平台的功能数量和使用率经常是负相关的。我们花了一个季度做的多因素实验正交设计、能同时测多个因子的主效应和交互效应上线后一年只有三个实验用过。而同期花两周做的实验报告一键分享链接功能使用率高得离谱——因为业务方要拿着链接去各种群里汇报。几个具体的实践建议报告页第一屏只放三样东西。核心指标的绝对值对比、相对提升和置信区间、是否显著。其他的趋势图、分组拆解、明细表全部折叠到下面。业务方看报告的平均停留时间不超过 40 秒第一屏决定了他会不会继续看。给每个实验配一个人话解释。在显著性检验结果旁边用一句话写清楚结论比如实验组支付转化率相对提升 6.2%在 95% 置信水平下显著结论可信度为中等样本量达到计划的 82%。这一句话可以把业务方的提问量降低一半。埋点校验工具比埋点规范文档有用一百倍。我们做了一个小工具输入实验 ID自动检查要求上报的字段有没有缺失、曝光量和分流量的比例是否合理、各组样本量是否接近。这个工具上线后数据不对的工单量降了七成。分流逻辑的单元测试要覆盖边界值。用户 ID 为空、超长、包含中文、包含特殊字符、大小写混用这些都要有测试用例。我们踩过一次坑某个渠道带来的用户 ID 里带了空格哈希结果全部落在同一个桶里整整两万用户被分到同一组直到 SRM 告警才发现。给新人准备一份从零跑一个实验的清单。列清楚确认指标口径、算样本量、确认分流层有空闲流量、配置参数、白名单先跑通、灰度 1% 观察两小时、看护栏指标、正式放量、每天看 SRM、到期下结论。这份清单能挡掉 80% 的低级错误。最后说个我个人最看重的点平台要主动告诉业务方这个实验现在还不能下结论。人天生有确认偏误看到好看的数字就想停下来。平台如果不能拦住这种冲动那它做的所有统计严谨性都白费了。我们在报告页做了一个结论成熟度指示器综合样本量进度、SRM 状态、护栏指标、检验次数来给一个状态观望中 / 可初步参考 / 可下结论。当用户试图在观望中状态下点确认上线按钮时会强制要求填一段理由这段理由会进入审批流。这个设计不复杂但它把一个统计问题变成了一个流程问题效果比任何培训都好。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑