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