什么是灰度测试
A/B测试系统的一个常用场景是App/小程序/后端服务精细化运营过程中的上线迭代管理,通常被称为灰度测试或者灰度上线。
详细来说,如果软件产品要在不久的将来推出一个全新的功能,或者做一次比较重大的改版的话,要先进行一个小范围的测试工作,给一小部分用户先试用。通过埋点监控得到用户反馈,确认新功能的效果达到预期之后,再慢慢放量,直到这个全新的功能覆盖到所有的系统用户。也就是说在新功能上线的黑(没有用户)白(所有用户)之间有一个灰(部分用户),所以这种方法也通常被称为灰度测试。
在灰度测试的技术实现里,一个关键的部分是试用用户样本的选择策略。也就是说,新版本上线测试的首批(以及后续批次)灰度用户是怎么筛选出来,决定了灰度测试的技术选型和具体实现。从业务角度来说,我们要明确每一个新功能分别由哪些测试用户来试用,谁扮演“小白鼠”的角色。
被广泛使用的样本选择策略包括一下几种:
l 白名单:我们主动选取一批用户,打上白名单标签。灰度版本首先开放给这批用户。白名单用户通常是业务同事根据业务经验选取的,比如活跃的粉丝、喜爱尝鲜的用户、对产品问题比较包容的朋友等。当然也可以是随机选取的。
白名单最大的好处是主动可控,出现问题的时候可以及时定位具体用户,业务同事可以及时和这些具体客户进行沟通,获得深入的测试反馈。
但是白名单用户组由于不具有普遍代表性,一般不能实现A/B测试的效果预测能力。另外,技术上实现白名单通常会有性能损耗(对每一个用户都需要扫描一遍名单),再考虑到人工筛选的工作量,我们通常不会允许白名单里的用户数量太大。这种局限性对于用户量较大的业务来说,比较麻烦。
l 随机抽样:每个用户都有一定概率可以参与测试。
随机抽样原理简单,样本选取的代表性也比较好。多数时候随机抽样产生的试验组和对照组可以直接用于A/B测试检验。
但是随机抽样不可避免的会让试验管理陷入被动,万一灰度测试给一部分用户造成负面影响,很难挽回。这种不确定性的风险要求我们在测试阶段有强大鲁棒的干预技术手段,比如实时监控、实时回滚、实时捕捉到用户在各渠道的负面反馈等。另外,要实现真正符合统计科学的真随机(不是伪随机),技术上也有不少坑。
l 按照某种规则抽样:我们主动建立抽样规则,满足规则条件的用户就被指定参与测试。规则通常要考虑业务需求和测试用户覆盖。比如规则是“只选取手机尾号为9的用户”,那么测试用户覆盖可能在10%左右。另一种规则是“下过单的用户”,那么测试用户大概会覆盖已付费客户。
规则抽样比白名单更加灵活方便,通常能覆盖显著更多的用户,技术实现的性能也会比较好。
但是规则抽样的策略介于随机抽样和白名单之间,既不是完全可控,也不是均匀采样,并不是满足业务需求的最佳选择。
l A/B测试科学采样:试验组(和对照组)用户是通过具有代表性的随机抽样选取,可能还经过了前期干预,只有符合统计学检验标准的用户可以参与测试。
科学采样生成的试验组和对照组具有很高的用户代表性,测试得出的数据可以精确的预测新功能全面上线之后带来的整体效果。
和随机抽样一样,A/B测试需要我们具有强大的试验控制能力,能够实时回滚、实时开关试验功能、实时监控用户行为、实时调整分流比例。
如果有一个像AppAdhoc A/B Testing一样强大的科学A/B试验系统,每一个灰度测试都可以用A/B测试来实施。更重要的是,当我们有多个新功能一起研发推进的过程中,可以同时启动多组A/B测试,每组试验检验一个功能,实现更精细化的产品管理。
A/B测试抽样的长期问题
在实践之中,我们注意到一个用A/B测试来做灰度上线的技术问题:长期试验的科学性偏差。
举例来说,一个长期运营的App,每个月上线一版新功能,也就是每个月上线一个A/B测试。A版对照组采样5%的用户,B版体验新功能组采样5%的用户,同时开始试验控制。两周之后试验结束,用户喜爱的新功能全面发布,用户不喜爱的新功能全面回滚。再过两周,新的一个月迭代周期开始,新的A/B测试上线,5%对照组5%试验组……每个月一组A/B测试,长此循环。
假如A/B测试的样本筛选算法只考虑一次试验的科学性,那么只需要保证我们选出的5%对照组和5%试验组足够均匀即可。
假设试验组采样使用简单的哈希算法:
Group ID for Client cid <= Hash(A/B Testing Client ID cid)
那么每一个用户都会被分到固定编号的试验组里。
假设试验组资源分配使用简单的贪心算法:
Experiment Groups for X% <= FindFirstXGroups(Groups, X)
如果试验频率是一个月一次,测试流量5%,那么每次试验都会是前5个试验组(Group)被选中参与测试。也就是说,有一批用户,被固定分配到前5个试验组的用户,每个月都会被选中为“小白鼠”参与试验。
这会造成一个长期问题,就是这些留存很久的样本用户,比如使用App长达半年、一年、多年的老用户,作为试验样本,可能会出现“样本偏差”。
如果一批老用户总是在不停“尝鲜”,一批老用户总是体验稳定版本,他们的行为习惯可能被培养成不同的类型,他们对产品的期待和需求也可能逐渐异化。比如说,长期参与试验的“小白素”用户更喜欢新功能更不在乎bug,而长期在对照组的用户不适应新功能也对bug很敏感。一旦这种差异开始固化,这两组用户就异质了,不能拿来继续做新的A/B测试。如果继续用这些样本做试验,试验结果是不符合A/B测试要求,结论是不可信的,无法用于业务指导。
其实A/B测试结果的科学和准确只是一个小问题,这个问题的最大影响是在于这些被反复拿来做试验的“小白鼠”用户/客户,有可能产生不必要的流失,甚至个别用户被反向培养成“黑粉”,对我们的产品形成强化的坏印象。这种资深黑粉用户会严重影响产品的口碑,也几乎没有办法可以召回。
这个长期偏差问题对于白名单和规则抽样等类型的采样策略也都或多或少的存在。当然,白名单策略本身就是要筛选出特定的用户群长期做“小白鼠”,我们在选择白名单策略之初就得处理好这个问题。
理论上说,随机抽样不会出现长期偏差问题,因为每次参与试验的用户都是随机筛选的一批,通常同一个用户不会被不同的试验反复选中。但是在实践之中,也要注意实现的算法是否能支持真正的随机性。很多时候,伪随机数加上固定分支条件的代码实现会让某些用户有更大的可能被选中,这些用户还是会成为长期的“小白鼠”。
试验流量“洗牌”算法
意识到灰度测试的长期“小白鼠”问题,我们需要在A/B测试系统里引入“洗牌”机制,可以不断把预先分配好的试验组里的用户们重新打散,再重新分配到不同的试验组里。
“洗牌”机制有很多实现算法,针对A/B测试系统,我们可以采用的一个简单算法:对每一个用户,每过一段时间(比如30天),就给用户一个机会,可以换到其他试验组里去。
假设我们用简单的哈希算法来对用户做试验分组,那么可以用类似这样的实现:
// Runs every 30 days
Group ID for Client cid <=
Hash(A/B Testing Client ID cid + DateTime) % #Groups
注意不同的用户加入系统的时间是不同的,所以他们的“洗牌”机会也会出现在不同的时间。但是整体来说,试验组是每过一个月完成一次比较完整的“洗牌”。
引入“洗牌”机制之后,每个试验组在长时间尺度上是不断变化的,即便是同一个编号的试验组反复参与测试,也不会总是同一批用户。我们再也不会总是逮着同一群小白鼠打针喂药了。
总之,尊重用户/客户,不仅要用灰度A/B测试来避免有问题的产品迭代影响大面积的用户,还要避免同一批用户总是被拿来做试验,确保所有用户都能享受最佳的产品体验。