创新开发工具,提升测试体验,效果出乎意料!

发表时间: 2023-07-20 15:39

让我们回顾一下做项目的经历,当你改变了一些代码并准备提交一个 PR 时,你是愿意:

  • 在本地运行你项目的完整单元测试集?
  • 启动一个集群并在本地运行一个完整的集成测试集?
  • 对你的改动进行性能测试(例如,基准测试)?

在我们开始 RisingWave 项目时,开发人员总是抱怨测试速度慢,测试体验差。大家应该能懂:这真的很难受。

  • 单元测试的速度超级慢

我们有个 10 多个 crates 的工作空间和 700 个单元测试用例。用 cargo test 运行一个完整的测试集需要很长的时间,更不用说花在编译代码覆盖率上的时间。

  • 集成测试非常困难

RisingWave 从第一天起就是一个云原生流数据库,一个最小的 RisingWave 集群需要一个计算节点、一个元节点和一个前端节点,还要用许多参数和配置来启动它们。

因此,我们有一个很长的 bash 脚本来启动一个集群,必须等待 30 秒才能运行集成测试。有时,如果开发人员忘记停止他们之前启动的组件,就需要花几分钟时间来弄清楚为什么 e2e 测试没有产出。随着越来越多的组件(如多个计算节点、MinIO、Kafka)被添加到集成测试集中,甚至很少有开发者愿意自己启动集群。我们会简单地提交一个 PR,双手祈祷,希望这个变更能通过 CI 的 e2e 测试。

此外,每当我们对命令行界面进行修改时,我们都必须 hack 启动脚本,并让其他开发者知道这些修改,以便每个人都能在他们的本地机器上正确设置 RisingWave。当时,CI 的失败率很高。当时没有办法检测一个组件是否正确设置(例如通过 gRPC 健康检查)--我们只是使用 sleep 5s。有时 e2e 测试失败是因为 TCP 端口被关闭或服务没有完全初始化。

  • 性能测试很痛苦

开发人员不得不手动完成所有工作——编译 RisingWave,向 EC2 发送二进制文件,自己编写命令行参数,检查每个服务是否启动,配置 Grafana 仪表盘,运行 benchmark,最后,清理一切。

总之,没有人知道他们的 PR 是否会影响性能,因为(几乎)没有人会做性能测试。

RiseDev 的诞生

我厌倦了这些问题,所以我在 2021 年 12 月底启动了 RiseDev,即 RisingWave 的开发者工具

RiseDev 推出两周后,我们的所有开发人员都在使用它进行日常开发。它超级容易使用:只需一行命令 ./risedev d,它就能在 4 秒内启动一个 RisingWave 集群。它还提供了一些其他的选项来调整,但我们今天不会深入了解 RiseDev 的内部情况。

现在让我们来看看 RiseDev 如何简化开发过程,改善开发者的体验。

快速的单元测试

Rust 开发者通常使用 cargo test 来调用单元测试。让我们回顾一下单元测试是如何通过 cargo test 编译和运行的。

  • 工作区中的所有 crate 都会被编译成一个特殊的 test 配置,这样 #[cfg(test)] 里面的代码就会被编译。
  • 每个 crate(库)都会产生一个与 libtest 链接的二进制文件,它可以执行单元测试。
  • cargo test 将逐一调用每个单元测试二进制文件来完成单元测试过程。

问题出在最后一步——事实上,这些单元测试二进制文件是可以并行运行的!这就是 cargo-nexttest 的作用。它并行地运行来自不同模块的单元测试,以提高测试速度。此外,它还能很好地与 libtest-mimic 集成。我们有一些基于文件的测试,测试工具会扫描测试文件,并使用 libtest-mimic 动态生成单元测试。Nextest 很好地支持这种用例。迁移到 nextest后,我们可以在 10 秒内运行所有的测试案例。

当时 nextest 的一个问题是缺乏覆盖率工具。幸运的是,我发现了 cargo-llvm-cov,并发送了一个 PR 来增加与 nextest 的集成。现在,只需一个命令 cargo llvm-cov nextest,我们就可以使用 nextest 运行带有覆盖率报告的单元测试。与我们之前使用的覆盖率工具 cargo tarpaulin 相比,这个解决方案也提高了构建速度。

简单的集群设置

RiseDev 的核心功能是组装和启动一个测试集群。RiseDev 实现了一个灵活的 yaml 配置引擎,使开发人员能够按照自己的意愿组装集群。让我们看一下下面的使用例子。

默认情况下,RiseDev 只在内存模式下启动 RisingWave:一个元节点在内存中存储元数据,一个计算节点在内存中存储状态,以及一个前端节点。集群配置存储在 GitHub 仓库根目录下的 risedev.yml 中。默认的配置是这样的。

yaml复制代码risedev:  default:    - use: meta-node    - use: compute-node    - use: frontend

只需一个命令 ./risedev d,所有这些组件就都会逐一启动。RiseDev 通过做健康检查来确保每个组件在启动下一个组件之前被完全初始化,以避免启动一个错误配置的集群。

less复制代码✅ tmux: session risedev prepare: all previous services have been stopped meta-node-5690: api grpc://127.0.0.1:5690/, dashboard <http://127.0.0.1:5691/> compute-node-5688: api grpc://127.0.0.1:5688/ frontend-4566: api postgres://127.0.0.1:4566/ playground: done bootstrapping with config default---- summary of startup time ----meta-node-5690: 0.83scompute-node-5688: 1.37sfrontend-4566: 0.83s-------------------------------

RiseDev 调用 tmux 命令在后台生成进程,这为开发者提供了对每个组件的最大控制。开发人员可以附加到 tmux 控制台,管理集群内的每个运行进程。

现在,当开发者想在磁盘上持久化状态存储数据时,他们可以这样修改 risedev.yml。

yaml复制代码risedev:  default:    - use: minio    - use: meta-node    - use: compute-node    - use: frontend    - use: compactor

这就是神奇的地方 —— RiseDev 自动提供可用的服务。现在我们在集群配置文件中加入了minio,RiseDev 在启动计算节点的时候会自动将其选中。

less复制代码✅ tmux: session risedev prepare: all previous services have been stopped minio: api <http://127.0.0.1:9301/>, console <http://127.0.0.1:9400/> meta-node-5690: api grpc://127.0.0.1:5690/, dashboard <http://127.0.0.1:5691/> compute-node-5688: api grpc://127.0.0.1:5688/ frontend-4566: api postgres://127.0.0.1:4566/ compactor-6660: compactor 127.0.0.1:6660 playground: done bootstrapping with config default---- summary of startup time ----minio: 0.38smeta-node-5690: 0.82scompute-node-5688: 1.41sfrontend-4566: 0.83scompactor-6660: 0.69s-------------------------------

在启动服务时,RiseDev 会自动为 MinIO 创建桶和配置策略。在启动计算节点时,RiseDev添加了一个命令行参数--state-store hummock+minio://...,以便计算节点使用 MinIO 来存储数据。最终,两行的改动就能实现 RisingWave 的数据持久化!

常见的情况是,开发人员可能想测试一个 3 节点的集群,并确保他们的变更在分布式环境下工作。RiseDev 也通过这个灵活的 yaml 配置引擎支持这一点。

yaml复制代码risedev:  default:    - use: minio    - use: meta-node    - use: compute-node      port: 5687    - use: compute-node      port: 5688    - use: compute-node      port: 5689    - use: frontend    - use: compactor

而当开发者想用 Kafka 等外部数据源测试 RisingWave,并在 Grafana 中查看指标时,可以简单地运行./risedev d full。RiseDev 会自动配置一切!只要导航到 localhost:3001,我们就可以找到我们想要的指标。

听起来很酷,对吗?启动集群后,我们现在可以调用 sqllogictest,它是一个 SQL 测试框架,能来运行源自 SQLite 项目的集成测试。我们使用的是 sqllogictest-rs,它是 Rust 下对 sqllogictest 的重新实现,由 RisingLightDB 社区维护。我们贡献了很多功能,使其使用起来更简单、更方便。

总的来说,RiseDev 提供了一个灵活的配置框架来启动一个 RisingWave 集群。它运行健康检查,配置服务,并为所有组件生成命令行参数。只需稍作改动,我们就可以根据自己的需要组装 RisingWave 集群,并运行集成测试!

如丝般顺滑的性能测试

在开发 RisingWave时,我们非常关心性能问题 —— 如何更快地摄取数据,如何更有效地从S3 读取和写入数据,等等。但在很长一段时间里,我们不知道每次提交会对性能产生什么影响。经过一些调查,我们在 EC2 上设置了一些标准的 RisingWave AMIs,并安装了所有需要的工具。开发人员只需要根据模板创建一个 EC2,他们就可以立即开始用 RiseDev 对他们的修改进行基准测试 —— 不需要自己安装 Rust 编译器或其他依赖性。

但单节点设置只适用于快速验证 —— 所有组件共享相同的磁盘和 CPU,而且数据集的大小一般都很小。我们最终需要的是一个完整的分布式 RisingWave 配置,并有大量的数据来进行基准测试。

我们开发了两个独立的工具来设置测试环境和部署 RisingWave。

用一个命令设置基准环境

感谢 Terraform 让我们可以使用 HCL 代码描述 AWS 上的基准基础设施。在 RisingWave Labs(曾用名 Singularity Data)内部,我们有一个私有的 Terraform 模块,为每个 RisingWave 组件启动 EC2,在一个 VPC 中连接它们,并设置一个 S3 桶和 ECR(elastic container registry)。每个 EC2 都将获得一个 IAM 角色,该角色具有访问每个实验的 S3 桶和 ECR 的必要权限,无需任何手动配置。

部署 RisingWave

RiseDev 已经可以在本地集群中启动,但我们可以在远程集群中部署它吗?答案是 YES!RiseDev 支持生成 docker-compose 配置并使用 docker-compose 部署 RisingWave。它将从 Terraform 读取部署信息,并在每个 EC2 上生成一个 docker-compose 配置。例如,我们可以指示 RiseDev 将一个计算节点部署到名为 rw-compute-0 的 EC2 上。

yaml复制代码- use: compute-node  id: compute-node-0  listen-address: "0.0.0.0"  address: ${dns-host:rw-compute-0}

而用./risedev compos-deploy <profile>,RiseDev 将为该节点生成一个 docker-compose配置。然后,我们可以执行 ./risedev apply-compos-deploy 来启动部署过程。在一分钟内,开发者就可以让他们的集群启动并运行了

总结

RiseDev 是 RisingWave 的开发者工具,它彻底改变了 RisingWave 的开发过程。开发人员可以轻松地使用 RiseDev 来运行本地测试和性能测试。它使启动和部署一个完整的 RisingWave 集群的过程自动化,它使 RisingWave 的开发过程更加顺畅和高效。

在 RisingWave 和 RiseDev 中还有很多值得探索的地方,比如 jaeger tracing,自动生成 Grafana 仪表盘,使用确定性测试来加速基于时间的测试,等等。我们不可能在一篇文章中全部涵盖。我们一直在不断改进开发体验,所以开发者会发现在 RisingWave 项目上工作很容易。

  • 注意一:RiseDev 部署仅用于测试,不应使用于生产环境。请查看我们的文档,了解如何将 RisingWave 用于测试目的,如何在 Kubernetes 上部署 RisingWave,并继续关注我们路线图中的 RisingWave cloud。
  • 注意二:我们没有公布 RisingWave 的任何官方基准数据--在不同的使用情况下,其性能各不相同,您应该自己尝试一下。

作者:Chi 是 RisingWave Labs的一名工程实习生。他对数据库内核的开发很感兴趣,同时,他也很关心开发体验,并试图让 RisingWave 的每一个开发者都能在编码中获得快乐。

Chi 目前是卡内基梅隆大学的一名硕士生,于上海交通大学获得计算机科学学士学位。