Proctor: Indeed's AB Testing Framework
原文链接:https://engineering.indeedblog.com/blog/2014/06/proctor-a-b-testing-framework/
1. Indeed AB测试
Indeed是一个招聘网站(译者注:类似国内的拉勾、智联之类的网站)。我们时常反思一个问题,“什么是对求职者最好的”?我们通过测试、测量一切来回答这个问题。我们努力测试Indeed每个产品的每个新功能、改进,并且度量这些改变所带来的影响,以此确保这些功能迭代帮助我们达成使命(帮人找工作)。
2013年10月,Tom Bergman和Matt Schemmel做了一个主题演讲:“Proctor,Indeed的AB测试框架”。演讲上,我们宣布Proctor开源。此后不久,我们又开源了Proctor Webapp,方便Proctor测试定义的管理。
10月份的那场演讲上,Tom举了一个“通过简单AB测试决定是否修改按钮背景颜色”的例子。图1 展示了对照组A,不改变“Find Jobs”背景色;测试组B,改成蓝色背景。
图1
我们会以日志的形式记录Indeed的一切,以便于我们分析、学习、改进产品。比如这个简单的测试,我们记录了每个用户登录的组(A or B),以及他们的点击序列。然后我们用分析工具来确定,测试组导致了更多搜索和更大的整体用户参与度。
上面的例子只有一种测试行为,更典型的场景下,我们会在给定测试中尝试多种行为。如我们可能会尝试多种不同的背景色。
我们也可以同时尝试不同的idea,比如图2 所示,一个测试按钮文字,一个测试背景颜色。在特定功能区域,针对多个变量(比如文字、颜色)进行的测试,被称为“多变量测试”。
图2
我们已经在Indeed做AB测试好几年了,学到的许多经验教学用到了Proctor上。十月份的演讲囊括了很多详细的设计决策和将其用于AB测试之外的方法。这篇博文中,我们集中讲述Proctor的关键特性和概念,解释使用Proctor的官窍。
2. Proctor特性与概念
2.1 标准表示
Proctor提供了测试定义的标准JSON表示方式,允许定义调整独立于代码部署。我们将完整的测试定义集称为测试矩阵。测试矩阵可以作为单个文件分发到多个应用程序,从而在管理测试时提供更大的灵活性,并可以在多个应用程序之间共享一致的测试定义。图3 展示了一个简单版本的按钮测试定义,50%用户被分配到对照组A(bucket 0),50%分配到测试组B(bucket 1)。
"buttontst": {
"description": "backgroundcolortest",
"salt": "buttontst",
"buckets": [
{
"name": "control",
"value": 0,
"description": "current button treatment (A)"
},
{
"name": "altcolor",
"value": 1,
"description": "test background color (B)"
}
],
"allocations": [
{
"ranges": [
{
"length": 0.5,
"bucketValue": 0
},
{
"length": 0.5,
"bucketValue": 1
}
]
}
],
"testType": "USER"
}
图3
为快速理解这个例子,先来看一下Proctor的一些术语:
- 每个测试都有一个测试类型(testType)。最常见的是USER,意味着使用用户ID映射到一个测试变量。后续还有更多的测试类型。
- 每个测试由一组“桶”和一组“分配”(allocations)组成。
- “桶”是Proctor测试定义中的一个变量或组。每个桶有一个简短的名字、一个整数值和一个易于理解的描述。
- “分配”指定桶的大小,是一个范围数组。范围数组 0 ~ 1之间,还有一个bucketValue指向特定bucket。范围数组的和,必须是1。如果你使用规则的话,可以有多个allocation。
2.2 Proctor Webapp
TODO
2.3 由Json测试规范生成Java代码
TODO
2.4 基于规则的上下文分配
使用Proctor的规则定义语言,你的系统可以通过针对运行时上下文评估规则来应用测试和测试分配。例如,你可以将整个测试定义为仅对特定用户分片可用,或者根据分片调整测试组的分配。你的测试可以是某个国家的用户 50% A,50% B;其他国家用户A/B/C/D各25%。基于规则的组分配为你在展开、评估测试方面提供了极大的灵活性。
"allocations" : [
{
"rule" : "'US' == country && 'en' == userLanguage",
"ranges": [
{
"length": 0.5,
"bucketValue": 0
},
{
"length": 0.5,
"bucketValue": 1
}
]
},
{
"rule" : null,
"ranges" : [
{
"length" : 1.0,
"bucketValue" : -1
}
]
}
]
图6
2.5 Payloads
测试定义中,允许为测试组附加数据的功能,能极大简化你的代码。图7和图8中,我们演示了如何为按钮测试的颜色可以在测试定义中指定为有效负载并在模板中进行访问。尽管在本例中,模板代码的数量没有减少,但如果有多个测试变量,每个都有不同的颜色,有效负载的使用就显示出威力了。
"buckets": [
{
"name": "control",
"value": 0,
"description": "current button treatment (A)",
"payload": {
"stringValue": "#dddddd"
}
},
{
"name": "altcolor",
"value": 1,
"description": "test background color (B)",
"payload": {
"stringValue": "#2164f3″
}
}
]
图7
<style> .searchBtn { background-color: ${groups.buttontstPayload}; } </style>
2.6 灵活的测试类型
Proctor具有灵活的测试类型概念,允许根据用户(cookie)、账号ID(跨设备)、电子邮件地址或者根据请求随机,来确定分桶。你也可以用自己的测试类型来扩展Proctor。定制测试类型很有用,比如,当你想要基于上下文、内容属性来分组的时候,像URL或内容分类。
2.7 无偏独立测试
要为测试分桶,Proctor使用均匀分布哈希函数(uniformly distributed hash function)将输入标识映射到一个整数值。桶的范围分配确定了定义每个桶的整数范围。图9 展示了一个简单的 50/50 分布的例子。由于hash函数是均匀的,分桶应该是无偏的。
图9
更进一步,Proctor测试是独立的,意味着一个测试中的组成员关系与另一个组中的成员关系无关。这个独立性是通过向每个测试中加不同的盐(salt)来实现的。盐和 id 一起作为hash函数的输入。将盐加入测试定义中,可以实现两个高级功能:
- 你可以通过共享盐(共享盐必需以“&”开头)来有意地实现在不同的测试中对齐分桶。不过实践中,很少看到需要以这种方式对齐两个测试。
- 你可以通过更改盐 来 改变测试的分布,从而导致哈希函数的输出完全不同。这种改组可以用来使自己确信测试中没有偶然的偏差。
3. Indeed Proctor
Proctor已经成为Indeed数据驱动设计的关键部分,目前产品中有超过100个测试、300多个测试变量。下一篇博文,我们将提供更多的使用细节。