从制作九转大肠来谈起 | GreptimeDB 如何提高多步操作的容错能力
Procedure 是 GreptimeDB 最近正在开发中的一个新特性,通过引入 Procedure 框架,来帮助记录数据库中多步操作的进度以及对该操作自动进行失败重试,保证该操作能执行完成。这篇文章将简单介绍下 Procedure 框架是什么,以及我们实现 Procedure 的方式和未来规划。
为什么需要 Procedure
为了方便理解 Procedure 要解决的问题,不妨想象以下场景。假如你今天要做一道前段时间特别火的一道菜:九转大肠。这道菜的烹饪工序十分复杂,涉及了众多环节,包括:洗,焯,煮,汆,煸,烧,煨,㸆。每个环节包含了若干步骤。例如洗这个环节,就需要先后加入白酒,食用盐,白醋等调料多次揉搓抓洗,并去除淋巴等组织。感兴趣的读者不妨搜索相关的教学视频。
我们在做菜的时候,往往无法保证不被打断。有时候,我们可能还会提前一天准备部分材料。那么等到下次继续料理时,或许已经忘记操作到了哪一步,接下来应该做什么了。特别是像九转大肠这样的复杂菜品,烹饪过程中出错,不仅可能导致成品味道变差,甚至可能保留了大肠原本的味道,让食客露出痛苦的表情。
我们的系统其实也会遇到类似的问题。用户发起的一个数据库操作,特别是执行一条 DDL ,往往涉及数据库中多个状态数据的修改
这些对状态数据的修改有先后顺序,好比做一道菜需要涉及多个环节
操作需要执行完所有修改后,操作才算成功
操作执行到一半就终止掉的话,数据库的部分数据就会一直处于不一致的状态
为什么 DDL 会涉及多个状态数据的修改呢?因为 GreptimeDB 从第一天起就以分布式为目标进行设计,一张表的数据是可以被划分成多个单元并分布到不同的机器上的。我们称这样的单元为一个 Region。我们使用以下组件记录系统中存在的表和每个表的元数据
CatalogManager
负责记录系统中存在的所有表TableManifest
负责记录表的元数据,包括表结构,一张表有多少个Region
等信息RegionManifest
负责记录Region
的元数据,如Region
的结构,包含的数据文件等
CatalogManager, TableManifest 和 RegionManifest 的关系
以建表操作CREATE TABLE
为例:建表的时候,数据库需要先后执行以下动作
为每个
Region
创建RegionManifest
并持久化Region
的元数据创建
TableManifest
并写入表的元数据往
CatalogManager
中写入表的记录
我们无法保证数据库在执行以上操作时不会出现进程 panic 或者重启,机器宕机,网络抖动导致请求失败等情况。当出现这种情况后,建表操作就会中断,使得数据库处于不一致的状态。例如 TableManifest
中已经写入了表的元数据,但是 CatalogManager
中却没有关于这张表的记录。当然,实际上还存在其他比建表更复杂的情况,比如 DROP TABLE
。
在分布式关系数据库中,我们可以通过分布式事务来解决上述问题。但现阶段 GreptimeDB 并不支持事务,同时我们也不希望为此而引入复杂的分布式事务以及类似 Google F1 的 schema 变更机制。因此,我们引入类似 HBase ProcedureV2 的 Procedure 框架来解决这个问题。
Procedure 框架
Procedure 框架的作用就是帮助执行系统中的多步操作,保证其最终能够执行完成或回滚。我们的 Procedure 框架借鉴了 HBase ProcedureV2[1]以及 Accumulo FATE[2]两个类似的框架。
Procedure 框架包含了以下三个组件:
Procedure
ProcedureStore
ProcedureManager
Procedure
Procedure 就是框架需要完成的一个多步操作,例如建表操作。
每个 Procedure 拥有唯一的 ProcedureId 标识自身
Procedure 实际上类似一个状态机,每执行一步,就会进入下一个状态,直到状态机结束
需要能够序列化自己当前执行的进度
每一步都需要做到幂等,使得每一步都可以重试
一个 Procedure 也可以将步骤分解成多个子 Procedure ,又称 Subprocedure 来完成。这样允许我们通过组合的方式使用多个 Subprocedure 来执行更复杂的操作。例如我们在做菜的时候,清洗这一工序实际上可以包含若干个步骤。因此,我们可以将清洗看作是一个 Subprocedure。
ProcedureStore
ProcedureStore 用于记录 Procedure 的执行进度。ProcedureStore 类似一个对象存储,以下面的形式存储 Procedure 的状态数据文件:
每个 step 文件记录了 Procedure 执行完一步后的状态。最后 Procedure 执行完后 ProcedureManager 会记录一个 commit 文件标识 Procedure 已经完成。
ProcedureManager
每执行 Procedure 中的一步,ProcedureManager 就将该 Procedure 状态持久化并存储到 ProcedureStore 中。 ProcedureManager 可以看作是 Procedure 的 runtime。它还需要负责:
在遇到网络等可恢复的错误后,继续重试 Procedure
管理当前的所有 Procedure
进程重启后从 ProcedureStore 中恢复尚未完成的 Procedure 并重新执行
协调 Procedure 对资源的访问
解决建表遇到的问题
为了协调 Procedure 对资源的访问, Procedure 框架还需要引入锁的机制,保证一个资源在同一时刻只能有一个 Procedure 在修改。例如,如果 Procedure 需要创建一张表,那么它需要先获取该表的锁,只有获取成功了才能继续操作,否则它需要等待持有该锁的上一个 Procedure 释放掉锁。如果有多个 Procedure 同时创建同一张表,那么只能有一个 Procedure 能创建成功。
在有了 Procedure 框架后,我们就可以通过将建表操作实现为一个 Procedure 提交给 ProcedureManager 执行。这个 Procedure 可以包含以下几步:创建 Region ,创建表,将表注册到 CatalogManager。在每一步里,我们需要注意实现的幂等性。例如如果 Region 已经创建好了则无需重复创建。
整个 Procedure 框架就好像一个强大的后厨:
Procedure 就是提交给后厨的一个菜品订单,而 ProcedureId 就是这个菜品的流水号
每个菜品都有清单追踪制作过程中的每个环节,而 ProcedureStore 就是用来记录进度的表单
ProcedureManager 就像是整个后厨的流水线,根据订单不断地制作菜品,更新表单
Procedure 框架已经完成了 RFC[3],原型验证和单机 Procedure 框架的开发。我们已经基于单机的 Procedure 框架实现了建表的 Procedure ,目前正在开发表结构表更的 Procedure。
后续工作
我们后续会持续迭代和改进 Procedure 框架,包括
实现其他 DDL 操作的 Procedure ,例如
ALTER TABLE
和DROP TABLE
等实现分布式 Procedure 框架。在分布式环境下,我们计划将 Procedure 框架运行在整个集群的“大脑” Metasrv 上,由 Metasrv 调度 Procedure 的执行
目前 GreptimeDB 建表的处理流程在单机和分布式下各不相同,也为实现者带来负担。我们将探索通过 Procedure 框架统一分布式和单机的处理流程
支持 Procedure 的回滚操作。HBase 的 ProcedureV2 框架还支持回滚,但目前 GreptimeDB 支持回滚 Procedure 的优先级并不高,因此暂时没有实现这一功能。
总结
GreptimeDB 通过引入 Procedure 框架,来帮助记录数据库中多步操作的进度以及对该操作自动进行失败重试,保证该操作能执行完成。当然, Procedure 并不是事务,无法提供事务的隔离性。 Procedure 执行过程中的影响对于其他 Procedure 是可见的。不过这一点对于我们来说是可以容忍的。
由于篇幅有限,本文只是简单的介绍了下 Procedure 框架,省略了不少细节。感兴趣的读者可以移步 Procedure 的 Tracking Issue[4]进一步了解。
参考
[1]https://github.com/apache/hbase/blob/master/src/main/asciidoc/_chapters/pv2.adoc
[2]https://accumulo.apache.org/1.8/accumulo_user_manual.html#_fault_tolerant_executor_fate
[4]https://github.com/GreptimeTeam/greptimedb/issues/286
[5]https://github.com/GreptimeTeam/greptimedb/pull/836
[6]https://developer.mozilla.org/zh-CN/docs/Glossary/Idempotent
[7]https://docs.greptime.com/developer-guide/meta/overview
关于 Greptime
Greptime 格睿科技于 2022 年创立,目前正在完善和打造时序数据库 GreptimeDB 和格睿云 GreptimeCloud 这两款产品。
GreptimeDB 是款用 Rust 语言编写的时序数据库。具有分布式,开源,云原生,兼容性强等特点,帮助企业实时读写、处理和分析时序数据的同时,降低长期存储的成本。
GreptimeCloud 基于开源的 GreptimeDB,为用户提供全托管的 DBaaS,以及与可观测性、物联网等领域结合的应用产品。利用云提供软件和服务,可以达到快速的自助开通和交付,标准化的运维支持,和更好的资源弹性。GreptimeCloud 已正式开放内测,欢迎关注公众号或官网了解最新动态!
公众号:Greptime
GitHub: https://github.com/GreptimeTeam/greptimedb
Twitter: https://twitter.com/Greptime
Slack: https://greptime.com/slack
LinkedIn: https://www.linkedin.com/company/greptime/
评论