#重构 #实验 #实验 #redis #监控 #路径 #比较

论文

Rust 库,用于控制与监控实验性代码路径

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数据库接口

MIT 许可证

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 - controlexperimentalexperimental_and_compare 之一
  • thesis_experiment_outcome - 每次实验有可观察的结果时增加的计数器
    • name - 实验的名称
    • kind - controlexperimentalexperimental_and_compare 之一
    • outcome - okerrormismatch 之一(仅通过 Experiment::run_result 产生 ok/error)

结果处理

如果您的实验(或控制)方法可能会返回错误,您应该在 Experiment 构建器上使用 run_result 方法。该方法对 Result 类型有特殊的处理和指标报告。当使用 RolloutDecision::UseControl 并未调用实验方法时,run_resultrun 的行为相同。即使控制方法返回错误,也不会发生任何特殊的事情。以下是使用 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的默认值,所有这些方法都必须被调用,否则实验将无法编译。
  • controlexperimental都必须是未来。可以编写一个非异步版本的Experiment,但这个库目前不提供。
  • 提供给实验的name必须是&'static str。我们使用metrics库来报告度量信息,这要求我们每次创建Experiment时都使用一个拥有自己的String,或者要求一个静态字符串。为String分配内存似乎比限制动态创建的实验名称更浪费。
  • 当使用run_result时,两个Result类型必须具有相同的Err类型。

依赖项

~3–5MB
~81K SLoC