7 个版本 (4 个破坏性更新)
0.5.1 | 2021年8月6日 |
---|---|
0.5.0 | 2021年5月1日 |
0.4.0 | 2021年1月13日 |
0.3.1 | 2021年1月12日 |
0.1.0 | 2020年12月10日 |
#1007 在 数据库接口 中
26KB
393 行
论文
受 https://github.com/github/scientist 启发
论文提供了 Experiment
结构体,它表示一个要运行的实验,用于比较完成相同任务的多种方法的返回值。
假设我们已经有了一个名为 load_data_from_db
的函数,该函数从数据库中加载数据。我们想要重构它,使其从 Redis 中加载数据。我们编写了一个名为 load_data_from_redis
的新函数来完成相同的任务,但使用 Redis 而不是数据库。我们只想在非常小的流量上使用 Redis 版本,比如 0.5% 的 incoming 请求,如果 Redis 数据与数据库数据不匹配,我们希望将其记录下来,并将数据库数据视为准确数据,丢弃 Redis 数据。以下是使用 Experiment
来实现此操作的示例。
use thesis::{Experiment, rollout::Percent};
async fn load_data_from_db(id: i32) -> i32 { id }
async fn load_data_from_redis(id: i32) -> i32 { id }
let id = 4;
let result = Experiment::new("load_data_from_db => load_data_from_redis")
.control(load_data_from_db(id))
.experimental(load_data_from_redis(id))
.rollout_strategy(Percent::new(0.5))
.on_mismatch(|mismatch| {
eprintln!(
"DB & Redis data differ - db={}, redis={}",
mismatch.control,
mismatch.experimental,
);
// the `control` value here comes from the DB
mismatch.control
})
.run()
.await;
assert_eq!(result, 4);
监控
由于论文旨在用于生产系统中的重构操作,因此内置了一些监控和可观察性功能。一些上下文信息通过使用 tracing
crate 创建的 span 提供,以及一些通过 metrics
crate 提供的指标。
提供的指标(带有标签)
thesis_experiment_run_total
- 每次调用run
函数时增加的计数器name
- 构造函数提供的实验名称
thesis_experiment_run_variant
- 每次运行一个变体(定义为控制 vs 实验)时增加的计数器name
- 实验的名称kind
-control、
experimental、
experimental_and_compare
之一
thesis_experiment_outcome
- 每次实验有可观察的结果时增加的计数器name
- 实验的名称kind
-control、
experimental、
experimental_and_compare
之一outcome
-ok、
error、
mismatch 之一(仅通过
Experiment::run_result
产生 ok/error)
结果处理
如果您的实验(或控制)方法可能会返回错误,您应该在 Experiment
构建器上使用 run_result
方法。该方法对 Result
类型有特殊的处理和指标报告。当使用 RolloutDecision::UseControl
并未调用实验方法时,run_result
与 run
的行为相同。即使控制方法返回错误,也不会发生任何特殊的事情。以下是使用 RolloutDecision::UseExperimentalAndCompare
时的行为。
控制 | 实验 | 返回值 | 指标(thesis_experiment_outcome 的标签值) |
日志 |
---|---|---|---|---|
Ok(X) |
Ok(X) |
Ok(X) |
{kind=control, outcome=ok} ,{kind=experimental, outcome=ok} |
|
Ok(X) |
Ok(Y) |
on_mismatch 的结果 |
{kind=control, outcome=ok} ,{kind=experimental, outcome=ok} ,{kind=experimental_and_compare, outcome=mismatch} |
|
Ok(X) |
Err(e) |
Ok(X) |
{kind=control, outcome=ok} ,{kind=experimental, outcome=error} ,{kind=experimental_and_compare, outcome=mismatch} |
"thesis experiment error"类型=实验,错误=e |
Err(e) |
Ok(x) |
on_mismatch 的结果 |
{kind=control, outcome=error} ,{kind=experimental, outcome=ok} ,{kind=experimental_and_compare, outcome=mismatch} |
"thesis experiment error"类型=控制,错误=e |
Err(e) |
Err(f) |
Err(e) |
{kind=control, outcome=error} ,{kind=experimental, outcome=error} |
"thesis experiment error" kind=control, error=e , "thesis experiment error" kind=experimental, error=f |
局限性
- 控制组和实验的未来都必须具有相同的
Output
类型 - 未提供控制组、实验或
rollout_strategy
的默认值,所有这些方法都必须被调用,否则实验将无法编译。 control
和experimental
都必须是未来。可以编写一个非异步版本的Experiment
,但这个库目前不提供。- 提供给实验的
name
必须是&'static str
。我们使用metrics
库来报告度量信息,这要求我们每次创建Experiment
时都使用一个拥有自己的String
,或者要求一个静态字符串。为String
分配内存似乎比限制动态创建的实验名称更浪费。 - 当使用
run_result
时,两个Result
类型必须具有相同的Err
类型。
依赖项
~3–5MB
~81K SLoC