混沌工程:是谁背着我偷偷写 Bug 🤸
前言
GreptimeDB 支持以单机和分布式的形式进行部署,但紧随而来一个尖锐的问题:我们对投入生产的这套复杂系统有多少信心?
在 0.3 到 0.4 的迭代过程中,我们引入了混沌工程(Chaos engineering)来提高系统的健壮性。
混沌工程是怎么实施的
我们选择了 Chaos Mesh 作为故障注入工具。我们在 Pod 中运行一个测试程序(Testcase),该程序通过定义 CR(Custom Resource)为 DB 集群中特定的 Pod 注入故障;并在转移故障后,对 DB 集群的可用性和数据完整性进行验证。下面是一个示例,向 greptimedb-cluster
命名空间中名为 greptimedb-datanode-1
的 Pod 注入一个 Pod Kill 的故障。
为了便于调试测试程序,测试程序在开发环境中,可以直接运行在主机上,并通过 Kubectl 的端口转发访问 K8s 中的 DB 服务。
一些探索和经验
1. 从最小场景开始
测试覆盖不高时,可能会有一些“负负得正”的情况,让整套系统看起来“正常”运行(实际上即使覆盖率较高,也依然可能存在这些问题)。所以我们可以从最小场景开始测试,这个阶段你需要非常清晰地知道系统的预期行为,并通过查看系统日志,判断系统的行为是否真的符合预期,以便发现问题后及时补相应的集成测试。
例如,我们需要验证系统是否能容忍 Datanode 节点被 Kill,并触发 Region Failover 流程(即将 Region 迁移到其他可用节点)。我们可以通过构建一个最小场景(少量表,少量数据)来进行验证,通常当故障被注入到故障真正发生会有一定的时间间隔,此时我们需要一些方法去判断系统是否真实发生故障了。
通常有几种做法:通过调用 Kube API 观察 ReplicatSet 的 Pod 副本数量少于预期;亦或是通过对目标节点进行读写来感知——在观测到故障前不间断的发起一些(少量的)将会路由到目标节点的读写操作,当客户端会返回故障时,即可视为目标节点不可用。随后等待集群恢复(例如调用 Kube API 等待 ReplicatSet 的 Pod 副本数量回到预期),我们就可以开始验证服务可用性和数据的完整性。
2. 尽可能贴近真实的场景
GreptimeDB 可以将数据保存在 AWS S3 或者阿里云 OSS 等等这样的廉价对象存储上。S3 这类存储相比本地存储来说,在测试中还是有不少差异的,主要体现在对 S3 访问的延迟上。**应尽早地贴近真实场景,即测试使用 S3 存储的 DB 集群。**我们在开发测试程序的时候,也要将被测试 DB 集群的数据存储在靠近开发环境的 S3 中。
3. 梳理思路 + 补充集成测试
在开始混沌测试前,尽可能地把思路梳理清晰,在集成测试中把将要测试到链路进行覆盖。混沌测试还是有不少额外工作量的,前期把测试做到位能节省不少时间。当然混沌测试和集成测试也可以说是相辅相成的,有些问题只有你真的在混沌测试中遇到了,才会打破你之前的一些判断:“哇*,原来这个不是我想的小概率事件🤣”。
4. 关注偶发的问题
任何偶然发生的问题都要深究,对于小概率发生的问题其实需要大量的重试,并通过日志精准地定位问题。
5. 构建可复用故障
构建一个混沌测试需要投入不少精力:通常注入一个故障,其中会涉及到多个步骤,例如注入故障,观察到故障发生,等待服务完全恢复等。通过对这些步骤进行抽象,我们可以做到实现注入一个故障,测试多个不同的场景。
在混沌工程中发现的 Bugs
我们在混沌工程中发现的 Bugs 主要集中在以下几类:
Trait 的不同实现,行为不一致;
字段或接口冗余;
多个 Bug 叠加,负负得正;
未使用别名造成的歧义;
Corner Case 中数据与缓存不一致;
迭代过程中多人协作造成的一些遗漏。
我们还很意外地发现了一个上游的 Bug,OpenDAL FsBackend 的异步写入在 sync_all 之前未调用 flush,导致其有潜在丢失数据的风险。我们在 0.4 版本中及时反馈并修复了这个 Bug,可以通过下面链接详细了解:
https://github.com/GreptimeTeam/greptimedb/issues/2382
https://github.com/apache/incubator-opendal/issues/3052
https://github.com/tokio-rs/tokio/issues/6005
评论