# 2022-45: 使用 sccache 加快 Rust 编译速度

[sccache](https://github.com/mozilla/sccache) 是由 [mozilla](https://github.com/mozilla) 开发的编译缓存工具，设计思路是作为一个编译的 wrapper，把编译产物放在保存到存储后端上，尽可能避免重复编译，从而加快整体的编译速度。sccache 支持的编译后端包括 gcc, clang, MSVC, rustc, NVCC 等等。以 Rust 编译为例，可以这样来使用它：

```shell
export RUSTC_WRAPPER=/path/to/sccache
cargo build
```

最近社区提出有没有办法加快 databend 的编译速度，我想到了可以使用 sccache 来加速，优势如下：

- Databend 依赖众多，而大部分依赖很少会变化，他们都能够被很好的缓存下来
- Databend 的 dev 构建跑在 AWS 的 Self-hosted Runner 上，而且本身就已经配置好了内网的 S3 bucket

## 开始踩坑

各方面条件都很完善，看来只需要写一个 Github Action 就好了。事实证明我太乐观了：

![](https://xuanwo.io/reports/2022-45/github-action.png)

主要坑的地方有两块：

- 为了方便测试环境的维护，Databend CI 统一使用了 build-tools 镜像来跑测试，搭配 sccache 起来需要额外做不少配置
- 云平台出于安全考虑启用了基于节点 Role 的认证，没有配置 AK/SK，sccache 没有支持 IMDSv2

第一个问题通过不停地骚扰 [@everpcpc](https://github.com/everpcpc) 得到了解决，后面这个问题相对更麻烦。我们首先给 sccache 提交了一个 Issue: [Support IMDSv2 for AWS IAM Role authentication](https://github.com/mozilla/sccache/issues/1386)，然后开始尝试自己解决问题。

## 调试 sccache

调试 sccache 的过程非常痛苦：sccache 是一个 CS 的架构，用户每次调用 sccache 的时候都会在后台启动一个 server，通过 client 和 server 之间的通信来决定是否使用缓存。所有跟存储后端的交互都是由 server 来执行的，在编译过程中没法直接看到日志，只能把日志重定向到某个文件，然后看文件的输出。此外 sccache 的历史比较悠久，很多的地方都不太利于调试，比如：

```rust
let res = self
    .client
    .execute(request)
    .await
    .with_context(move || format!("failed GET: {}", url))?;

if res.status().is_success() {
    let body = res.bytes().await.context("failed to read HTTP body")?;
    info!("Read {} bytes from {}", body.len(), url2);

    Ok(body.into_iter().collect())
} else {
    Err(BadHttpStatusError(res.status()).into())
}
```

请求失败的时候只会打出状态码，没有实际错误内容，在调试 IMDSv2 时走了很多弯路。为了尽快解决问题，我直接把 sccache fork 了过来，把底层的存储修改成了使用 opendal：

```diff
- let credentials = self
-     .provider
-     .credentials()
-     .await
-     .context("failed to get AWS credentials")?;
-
- let bucket = self.bucket.clone();
- let _ = bucket
-     .put(&key, data, &credentials)
-     .await
-     .context("failed to put cache entry in s3")?;
+ self.op.object(&key).write(data).await?;
```

这下舒服多了，很快定位到了问题并修复掉，现在 Databend 已经成功使用了 sccache 了。在安装好 sccache 之后，只需要配置如下：

```yaml
- name: Setup Build Tool
    uses: ./.github/actions/setup_build_tool
    with:
    image: ${{ inputs.target }}
    bypass_env_vars: RUSTFLAGS,RUST_LOG,RUSTC_WRAPPER,SCCACHE_BUCKET,SCCACHE_S3_KEY_PREFIX,SCCACHE_S3_USE_SSL,AWS_DEFAULT_REGION,AWS_REGION,AWS_ROLE_ARN,AWS_STS_REGIONAL_ENDPOINTS,AWS_WEB_IDENTITY_TOKEN_FILE

- name: Build Debug
    shell: bash
    run: cargo -Z sparse-registry build --target ${{ inputs.target }}
    env:
    RUSTC_WRAPPER: /opt/rust/cargo/bin/sccache
    SCCACHE_BUCKET: databend-ci
    SCCACHE_S3_KEY_PREFIX: cache/
    SCCACHE_S3_USE_SSL: true
```

## 总结

这里记录一些比较简单的数据：首次全量编译的时候需要花费一些额外的时间上传编译产物，后续的编译就可以复用这些产物，不再需要编译了。

- 没有任何 cache 的时候： 6m 20s
- 首次编译
  - `Finished dev [unoptimized + debuginfo] target(s) in 9m 04s`
- 第二次编译
  - `Finished dev [unoptimized + debuginfo] target(s) in 5m 35s`
- 去除 debug 日志后
  - `Finished dev [unoptimized + debuginfo] target(s) in 4m 38s`

这里的首次编译时间还需要考虑 sccache debug 日志对性能的影响，实际上应该不会增长那么多。不过 Databend 还是有很多地方没有办法缓存，后面可以看一些有没有能优化的地方。我给上游提交了一个 proposal: [Use opendal to handle the cache storage operations](https://github.com/mozilla/sccache/issues/1404)，看看能不能把 sccache 的存储后端访问改为使用 opendal，这样对接存储和调试起来就更方便了～
