从制作九转大肠来谈起 | GreptimeDB 如何提高多步操作的容错能力

Procedure 是 GreptimeDB 最近正在开发中的一个新特性,通过引入 Procedure 框架,来帮助记录数据库中多步操作的进度以及对该操作自动进行失败重试,保证该操作能执行完成。这篇文章将简单介绍下 Procedure 框架是什么,以及我们实现 Procedure 的方式和未来规划。

为什么需要 Procedure

为了方便理解 Procedure 要解决的问题,不妨想象以下场景。假如你今天要做一道前段时间特别火的一道菜:九转大肠。这道菜的烹饪工序十分复杂,涉及了众多环节,包括:洗,焯,煮,汆,煸,烧,煨,㸆。每个环节包含了若干步骤。例如洗这个环节,就需要先后加入白酒,食用盐,白醋等调料多次揉搓抓洗,并去除淋巴等组织。感兴趣的读者不妨搜索相关的教学视频。
在这里插入图片描述
我们在做菜的时候,往往无法保证不被打断。有时候,我们可能还会提前一天准备部分材料。那么等到下次继续料理时,或许已经忘记操作到了哪一步,接下来应该做什么了。特别是像九转大肠这样的复杂菜品,烹饪过程中出错,不仅可能导致成品味道变差,甚至可能保留了大肠原本的味道,让食客露出痛苦的表情。

我们的系统其实也会遇到类似的问题。用户发起的一个数据库操作,特别是执行一条 DDL ,往往涉及数据库中多个状态数据的修改

  • 这些对状态数据的修改有先后顺序,好比做一道菜需要涉及多个环节
  • 操作需要执行完所有修改后,操作才算成功
  • 操作执行到一半就终止掉的话,数据库的部分数据就会一直处于不一致的状态

为什么 DDL 会涉及多个状态数据的修改呢?因为 GreptimeDB 从第一天起就以分布式为目标进行设计,一张表的数据是可以被划分成多个单元并分布到不同的机器上的。我们称这样的单元为一个 Region。我们使用以下组件记录系统中存在的表和每个表的元数据

  • CatalogManager 负责记录系统中存在的所有表
  • TableManifest 负责记录表的元数据,包括表结构,一张表有多少个 Region 等信息
  • RegionManifest 负责记录 Region 的元数据,如 Region 的结构,包含的数据文件等

    CatalogManager, TableManifest 和 RegionManifest 的关系

以建表操作CREATE TABLE 为例:建表的时候,数据库需要先后执行以下动作

  1. 为每个 Region 创建 RegionManifest 并持久化 Region 的元数据
  2. 创建 TableManifest 并写入表的元数据
  3. 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

Procedure 就是框架需要完成的一个多步操作,例如建表操作。

  • 每个 Procedure 拥有唯一的 ProcedureId 标识自身
  • Procedure 实际上类似一个状态机,每执行一步,就会进入下一个状态,直到状态机结束
  • 需要能够序列化自己当前执行的进度
  • 每一步都需要做到幂等,使得每一步都可以重试

一个 Procedure 也可以将步骤分解成多个子 Procedure ,又称 Subprocedure 来完成。这样允许我们通过组合的方式使用多个 Subprocedure 来执行更复杂的操作。例如我们在做菜的时候,清洗这一工序实际上可以包含若干个步骤。因此,我们可以将清洗看作是一个 Subprocedure。

ProcedureStore

ProcedureStore 用于记录 Procedure 的执行进度。ProcedureStore 类似一个对象存储,以下面的形式存储 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。它还需要负责:

  • 在遇到网络等可恢复的错误后,继续重试 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 TABLEDROP 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

[3]https://github.com/GreptimeTeam/greptimedb/blob/0ffa628c22fa1a34e244b33afd6fe85585b8824a/docs/rfcs/2023-01-03-procedure-framework.md

[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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值