Garland +

实验数据平台设计

实验

一般我们上线一个用户端的新功能,比如按钮颜色、展示逻辑这种对多个选择带来的具体效果没什么概念的时候就会选择做一个 A/B实验,一般以用户ID/设备ID 将用户分成多个组,一组为对照组(可选),其他为实验组,对照组保持原样,实验组分配不同的策略上线,上线一段时间后跑一下各个组的数据,点击率、转化率这种 根据数据选择一个该用哪组策略,这样一个 A/B 实验就完成了,这种算是最基础的 A/B 实验。

问题

Q: 同时支持多个实验

上面👆的实验相当于让所有用户都参与了这个实验,想支持多个实验可以把所有用户分成 n 份, 实验1从里面挑选一部分,一直到实验 k 从里面挑选一部分,这样就支持同时多个实验了。

那么问题又来了

Q: 想要更多的实验

上面👆的分发再怎么分,能支持的实验也是有限的,要想支持多个实验,可以把实验分层,也就是一个用户同时参与到多个实验,也就是上面的 n 份可以重复使用, 但是这样操作有一个大前提,有冲突的两个实验是不能分层放的,一个很经典的例子是页面背景颜色和字体颜色实验,比如同一拨用户是不能同时参与这两个实验的, 分层似乎能支持扩展更多的实验,那么问题又来了

Q: 第0组用户总是对照组

上面👆的分法,一直在一开始就分成 n 组的用户上做实验,随着时间的增长可能中间某几波用户就被养刁钻了, 也就是用户组之间慢慢的就会变成同一种类型的人,那么在这种用户组上做的实验的结果可能就不一定有普遍性。

另外一个问题就是,尽管有直接冲突的实验在同一层,但是没法保证不同层的实验之间没有影响,比如用户在实验1 的 bad 组已经跳出,就根本没法进入到实验2的 good 组,那么以此来判断实验2的 good 组效果不好显然是 不客观的。

所以这里需要把实验1的用户再均匀分成 n 份放在下一个实验层中,也就是层1的均匀和层2的均匀是不一样的均匀

分层实验

为了解决这些问题,Google 提出了分层实验的概念,也就是把实验分为多个层,互相之间有影响的实验会放在同一层,层与层之间的流量是正交的,这里就产生了一些概念

实验的三要素

实验的基本原则

重叠实验的概念

域是对流量的一个大的划分,层是在离自己最近的一个父域上的概念,实验是在层上的对桶进行的操作

数据统计

实验上线后,需要查看一些实验用户分组对应的基本指标(这里不包括显著性检验这种指标),比如某些位置的 pv、uv、点击率、下单数量等等,这些需要具体的打点信息支持。 一个实验看 m 个指标,n 个实验可能就要看 m * n 个指标,所以需要对指标进行整理和抽象。

打点流程

打点多种多样,实验也多种多样,最理想的情况是每条打点日志都带上实验及其分组信息,但是这样是不可能也是不现实的,原因如下:

  1. 打点和实验过于耦合:打点日志是持久的,实验是一个临时性的,这样打点日志里多出的实验信息就属于脏数据了
  2. 量太大:最坏情况下一条日志后面跟几千几百个实验分组信息,这对存储是个挑战,而且信息利用率极低

所以对实验分组信息进行单独的打点

  1. 用户请求 API 进行实验分组打点
  2. 天级 ETL 任务对实验分组打点进行清洗

第二步的目的主要是对实验分组信息按实验名和日期进行 partition 操作,以提高后期计算效率,另外做的一个事情就是对于一个用户/设备的某天的某个实验某个组只保留第一条数据, 这样对于某一天某个用户的某个实验,能得到一个按用户分组变迁序列(如果发生分组跃迁的话),最后一条日志的 end_time 即为下一天的 00:00:00,其他日志的 end_time 为下一条日志的 start_time

指标计算

这里对具体的计算指标的 SQL 语句进行了抽象,SQL 语句的执行过程其实包含了两个部分,一部分是筛选用户,一部分是筛选事件,对于实验来说,筛选用户这部分已经在上面👆的实验分组实现了, 筛选事件就是用这部分筛选出来的用户去和事件表按 user_id/pdid 进行 join 来得到在这个实验内每条事件日志对应的用户的分组信息,然后再进行各种聚合操作得到最终的指标数据。

所以指标计算这部分的设计要尽量实现自动化,一劳永逸,而对于具体的指标,简单划分的话又分为三类

简单指标

复合指标

留存指标

REF

言:

Blog

Thoughts

Project