不知大家是否已经留意到,在 TDengine 3.0 的官方文档(https://docs.taosdata.com/taos-sql/show/#)中,有了这样一个命令 :show transactions。顾名思义,这是“事务”。
在 Database 的语境中,满足 ACID 属性的数据库操作序列即可称为事务,它是数据库的一个不可拆分的工作单元,包含着一个数据库操作的序列,这些操作要么全部执行,要么全部不执行。换个角度也可以说,事务是为了实现 ACID 特性的一种工具。
不过在 TDengine 中,依托于“一个数据采集点一张表”的设计理念,针对表的操作都是以队列方式逐个进行的,所以在绝大多数情况下都不需要事务机制。那 TDengine 3.0 中的“事务”是用来解决什么问题的呢?答案就是:3.0 中的事务机制并没有应用在业务数据上,而是针对数据库的元数据的,它的目的是利用事务的 ACID 特性,来强化元数据的一致性,因此,普通用户对此是无感知的,但是对 TDengine 的运维人员而言,意义会更大一些。
大家都知道 TDengine 3.0 是一款高性能、云原生的分布式时序数据库(Time Series Database),甚至可以支持十亿级别的表数量,因此它的元数据量是十分庞大的。那么如果使用了事务,会不会影响 TDengine 的高性能呢?
这里就需要结合 TDengine 2.x 时代的元数据架构来说起了。
一、2.x 的元数据架构
在 2.x 版本中,管理节点 mnode 中的 sdb 模块存储了大量元数据,如集群信息、用户信息、数据库信息、超级表信息以及普通表信息等。这些不同类型的信息,会在内存中分别以一个 hash 表的形式来维护。其中,一些元数据需要在管理节点(mnode)和数据节点(dnode/vnode)中分别存储,比如超级表、普通表。所以,当对数据库执行 DDL 操作时,需要在 mnode 上对这个 hash 表操作一次,还要再去 dnode 中的 vnode 也做一次。这里为了性能的优化,TDengine 选择以异步的方式来完成操作。以删表为例,当 mnode 内存中的 hash 表删掉了这个表后,会立即返回成功。后续由 dnode/vnode 一侧删除自己的元数据,达成最终一致。
因为该流程的非强一致性,可能会导致在某些极特殊情况下(比如网络不稳定),出现 mnode 与 vnode 元数据不同步的情况(有的用户遇到过这个错误信息:“Invalid table id”)。但是如果为了强一致性而在 2.x 中引入事务机制,那么对于存在超大规模 DDL 操作的场景来说(如删除一张拥有百万千万子表的超级表),这种分布式的事务对数据库的性能损耗又将非常之大。
那么应该如何解决这个问题呢?我们的思路是:利用“分布式事务”,解决 mnode 和 dnode 同步时的问题,但是又不能让其影响到数据库的性能。
二、3.0 的事务引入
以此为依据,TDengine 在 3.0 中选择把开销最大的普通表元数据从 mnode 中移除,完全只放在各个 vnode 中分布式存储,这样除了防止了上述 mnode 和 vnode 普通表元数据不一致的现象发生,更使得 TDengine 不再具有单点性能瓶颈,解决了业界的“高基数”难题,从而可以支持十亿级别的时间线。在其余的元数据模块中,我们则引入了事务机制,确保了这部分元数据的 ACID 特性。
下图是一个两阶段提交的流程,其中 Coordinator(协调者)就是 mnode,而 Participants(参与者) 就是 dnode/vnode。
创建事务后,协调者本地准备事务所需的必要数据,redo、undo 日志等。然后向参与者们发送事务执行请求,如果参与者们的事务执行结果皆为成功,那么则进入提交阶段,由协调者提交事务。