Procedure 是 GreptimeDB 最近正在开发中的一个新特性,通过引入 Procedure 框架,来帮助记录数据库中多步操作的进度以及对该操作自动进行失败重试,保证其能执行完成。
这篇文章将简单介绍下 Procedure 框架是什么,以及我们实现 Procedure 的方式和未来规划。
为什么需要 Procedure
为了方便理解 Procedure 要解决的问题,不妨想象以下场景。假如你今天要做一道前段时间特别火的一道菜:九转大肠。这道菜的烹饪工序十分复杂,涉及了众多环节,包括:洗,焯,煮,汆,煸,烧,煨,㸆。每个环节包含了若干步骤。例如洗这个环节,就需要先后加入白酒,食用盐,白醋等调料多次揉搓抓洗,并去除淋巴等组织。感兴趣的读者不妨搜索相关的教学视频。

我们在做菜的时候,往往无法保证不被打断。有时候,我们可能还会提前一天准备部分材料。那么等到下次继续料理时,或许已经忘记操作到了哪一步,接下来应该做什么了。特别是像九转大肠这样的复杂菜品,烹饪过程中出错,不仅可能导致成品味道变差,甚至可能保留了大肠原本的味道,让食客露出痛苦的表情。
我们的系统其实也会遇到类似的问题。用户发起的一个数据库操作,特别是执行一条 DDL ,往往涉及数据库中多个状态数据的修改。
这些对状态数据的修改有先后顺序,好比做一道菜需要涉及多个环节。
操作需要执行完所有修改后,操作才算成功。
操作执行到一半就终止掉的话,数据库的部分数据就会一直处于不一致的状态。
为什么 DDL 会涉及多个状态数据的修改呢?因为 GreptimeDB 从第一天起就以分布式为目标进行设计,一张表的数据是可以被划分成多个单元并分布到不同的机器上的。我们称这样的单元为一个 Region
。我们使用以下组件记录系统中存在的表和每个表的元数据:
CatalogManager
负责记录系统中存在的所有表TableManifest
负责记录表的元数据,包括表结构,一张表有多少个Region
等信息RegionManifest
负责记录Region
的元数据,如Region
的结构,包含的数据文件等

以建表操作 CREATE TABLE
为例:建表的时候,数据库需要先后执行以下动作:
为每个
Region
创建RegionManifest
并持久化Region
的元数据创建
TableManifest
并写入表的元数据往
CatalogManager
中写入表的记录
我们无法保证数据库在执行以上操作时不会出现进程 panic 或者重启,机器宕机,网络抖动导致请求失败等情况。当出现这种情况后,建表操作就会中断,使得数据库处于不一致的状态。例如 TableManifest
中已经写入了表的元数据,但是 CatalogManager
中却没有关于这张表的记录。当然,实际上还存在其他比建表更复杂的情况,比如 DROP TABLE
。
在分布式关系数据库中,我们可以通过分布式事务来解决上述问题。但现阶段 GreptimeDB 并不支持事务,同时我们也不希望为此而引入复杂的分布式事务以及类似 Google F1 的 schema 变更机制。因此,我们引入类似 HBase ProcedureV2 的 Procedure 框架来解决这个问题。
Procedure 框架
Procedure 框架的作用就是帮助执行系统中的多步操作,保证其最终能够执行完成或回滚。我们的 Procedure 框架借鉴了 HBase ProcedureV2 以及 Accumulo FATE 两个类似的框架。
Procedure 框架包含了以下三个组件:
Procedure
ProcedureStore
ProcedureManager

Procedure
Procedure 就是框架需要完成的一个多步操作,例如建表操作。
每个 Procedure 拥有唯一的
ProcedureId
标识自身。Procedure 实际上类似一个状态机,每执行一步,就会进入下一个状态,直到状态机结束。
需要能够序列化自己当前执行的进度。
每一步都需要做到幂等,使得每一步都可以重试。
一个 Procedure 也可以将步骤分解成多个子 Procedure ,又称 Subprocedure 来完成。这样允许我们通过组合的方式使用多个 Subprocedure 来执行更复杂的操作。例如我们在做菜的时候,清洗这一工序实际上可以包含若干个步骤。因此,我们可以将清洗看作是一个 Subprocedure。
ProcedureStore
ProcedureStore 用于记录 Procedure 的执行进度,它类似一个对象存储,以下面的形式存储 Procedure 的状态数据文件:
/procedures/{PROCEDURE_ID}_000001.step
/procedures/{PROCEDURE_ID}_000002.step
/procedures/{PROCEDURE_ID}_000003.commit
每个 step 文件记录了 Procedure 执行完一步后的状态。最后 Procedure 执行完后 ProcedureManager 会记录一个 commit 文件标识 Procedure 已经完成。
ProcedureManager
每执行 Procedure 中的一步,ProcedureManager 就将该 Procedure 状态持久化并存储到 ProcedureStore 中。
ProcedureManager 可以看作是 Procedure 的 runtime,它还需要负责:
在遇到网络等可恢复的错误后,继续重试 Procedur。
管理当前的所有 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,原型验证和单机 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 进一步了解。
关于 Greptime
Greptime 格睿科技专注于为可观测、物联网及车联网等领域提供实时、高效的数据存储和分析服务,帮助客户挖掘数据的深层价值。目前基于云原生的时序数据库 GreptimeDB 已经衍生出多款适合不同用户的解决方案,更多信息或 demo 展示请联系下方小助手(微信号:greptime)。
欢迎对开源感兴趣的朋友们参与贡献和讨论,从带有 good first issue 标签的 issue 开始你的开源之旅吧~期待在开源社群里遇见你!添加小助手微信即可加入“技术交流群”与志同道合的朋友们面对面交流哦~
Star us on GitHub Now: https://github.com/GreptimeTeam/greptimedb
Twitter: https://twitter.com/Greptime
Slack: https://greptime.com/slack