MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)

TiDB 处理各种灾难故障可谓轻车熟路,但是常言道“天灾易躲,人祸难防”,对于各种误操作、bug 写入错误数据、甚至删库跑路,目前还没什么招。我们项目最初也是为了处理这些“意料之外”的事故。项目最初的名字叫 TiDB Flashback,后来又觉得这个名字过于贫瘠,无法体现项目内容的优越性,后来索性改成了“MVCC 时光机”。
——渡渡鸟复兴会战队
在 TiDB Hackathon 2021 赛事中,渡渡鸟复兴会赛队的作品“MVCC 时光机”充分利用 MVCC 特性,加强 MVCC 数据的查询、整理、恢复的能力,提高问题处理的效率,摘得了赛事的 “三等奖” 。
一个非常 smart 和轻量级的实现,效果很不错,期待尽快发布上线。
——评委冯光普
这个项目是给运维同学在某些时候救命的功能。它通过 SQL 很好地解决了运维的操作问题。更赞的是,该项目引入了多 Safepoint 机制,也就是可以给 TiDB 集群定期做一些全局 Snapshot,进行快速轻量级的逻辑备份。
——评委唐刘
关于项目
天灾易躲,人祸难防
作为一款分布式数据库,完善的高可用和容灾机制可以说是 TiDB 的核心特性。然而在生产中,理论上的灾难恢复和实际上要面对的却可能大相径庭。不可预知的硬件故障、自然灾害导致的断网断电固然可怕,而把生产环境当成测试环境的误操作、有漏洞被黑客攻击、业务出 Bug 写坏数据反而可能是更高频的事故。
队员之一 @disking 曾经就职于游戏公司,对这种“人为的失误”感受颇深:业务同事写的代码中出了一个 bug,在版本上线后的几个小时内被别有用心玩家利用获得了许多不正当的收益,这时候就要进行回档操作,让游戏数据回到某个具体的时间点,需要实现的就是类似“时光机”的功能。手动回档、数据丢失造成的损失都会给团队带来很大的麻烦。
现在物理上的故障处理 TiDB 已经给出了像 BR 工具、两地多中心方案等解决措施,而一句rm -rf /*,或者“实习生误操作”导致的错误数据写入带来的风险却更加不可控,这也是 TiDB 缺失的一大块拼图。
Make MVCC Great Again!
因为 TiDB 的事务是基于 MVCC 的,所以一段时间内的旧版本都在,理论上对于上述人祸,都是可以进行手动恢复的。但是现有的功能和计划中的功能相对较弱:
很可能需要排查数据损坏的情况,目前只能指定 ts 去读一个时间点的数据,要查看某条记录的变化历史太麻烦
RECOVER TABLE 只能恢复 DROP/TRUNCATE 这种 DDL 操作,对 DML 没招
GC Safepoint 之前的数据恢复不了,如果想保留长时间的数据,又太费空间了
恢复数据要先把数据 dump 出来再重新写入,太慢了
因此充分利用 MVCC 特性,加强 MVCC 数据的查询、整理、恢复的能力,就能提高问题处理的效率。MVCC 不只是可以用来暂时性地处理事务隔离,也完全可以做为冷备,相比于外部的备份,其优点是可以更省空间,恢复数据也更方便更快。
@disking 其实最早在第二届 Hackathon 时就有了这个想法,灵感来源于 Oracle 的 Flashback 功能。当时他就把这个想法放到了研发的讨论群里面却没有得到回应,而当时的他还没有足够的精力来组建自己的战队。虽然不至于说耿耿于怀,但是这么有趣、有用的想法当然要找个机会实现一下,这次的 Hackathon 就是一个实现想法的好机会。
渡渡鸟复兴会就这样开始组建了。
MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)
文章图片

【MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)】团队用20个渡渡鸟的对话情况来进行 demo
关于比赛
如何制造一个时光机?
谁掌握了过去,谁就掌握了未来;谁掌握了现在,谁就掌握过去。
——乔治·奥威尔《1984》
总的来说就是围绕 TiDB 的 MVCC 机制做了一些(乍)看起来比较 fancy 的操作,比如查询表记录的 MVCC 历史、篡改 MVCC 记录以及根据 MVCC 记录做到瞬时版本回退(Flashback 操作)等。
整个项目比较核心,或者说比较实用的点还是 GC SavePoint 和 Flashback 功能的组合拳,通过设置定期的快照备份,利用 TiDB MVCC 的机制做到 TiDB 存储内部的“冷备“,在一些关键时刻还是有救命效果的,毕竟只需要一条 SQL 就可以达成整表数据的 Flashback。
整个项目分为了三个相对独立的模块:
1.MVCC Query in SQL -> 操纵过去
参考 _tidb_rowid的实现,增加 _tidb_mvcc_ts,_tidb_mvcc_op虚拟列。
当查询虚拟列时,TiDB 发送给 TiKV 的请求中要带上标记,指明要查询 MVCC 虚拟列。
修改 TiKV 的 MVCC 读取逻辑,当需要查询虚拟列时,需要扫描所有版本,而不是只扫描最新版本。然后设置每条数据对应的虚拟列值。 _tidb_mvcc_ts为事务的 commit_ts,_tidb_mvcc_op为事务的操作类型,可以是 PUT或 DELETE。
MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)
文章图片

下面是一次演示,可以看到我们可以能用各种姿势来查询 MVCC 记录!
MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)
文章图片

甚至还可以去篡改某一次的 MVCC 记录。
MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)
文章图片

2.GC Savepoint -> 掌控现在
添加 gc_savepoint系统表,可以通过 SQL 增删改查来进行管理。
GC 进行时,需要将 gc_savepoint表的数据,与原本的 gc_safepoint一同存放到 PD。
修改 GC 逻辑,回收数据时考虑 gc_savepoint。因为 GC 有传统 GC 和 compaction GC 两种,时间关系可以只做一种。 在设置 gc_save_point_interval = ‘5m’后,在 gc_safe_point之前,本来会被回收 MVCC 记录每 5 分钟保留一个版本。
MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)
文章图片

3.Subsecond Flashback -> 着眼未来
添加 flashback table tsSQL 语句,用于指定 table 进行数据还原。意义是将表还原至不超过 ts 时间戳指定的版本。
将时间范围写入 table schema 并触发 DDL 操作,DDL 同步完成即可返回操作成功。
TiDB 请求 TiKV 时,需要将要忽略的 ts 区间放在请求中发给 TiKV。
修改 MVCC 读取逻辑,要根据指定的区间跳过对应的版本。
当 ts 区间超出了 GC 范围后,需要被清理。
结合上面的 MVCC 查询,我们可以看到现在表数据中的“变化渡渡鸟”记录在某个时间节点前还是“时间渡渡鸟”,通过 Flashback 操作,我们成功让数据变回了曾经的模样,把“时间渡渡鸟”召唤了回来。
MVCC 时光机(在 TiDB 的时空自由穿梭丨渡渡鸟复兴会赛队访谈)
文章图片

在实际的灾难恢复场景中,如果我们一不小心错误地修改了某个表的几条数据,甚至是误删了整个表,都可以通过 Flashback SQL 来将其一键恢复到任意 MVCC 记录版本。
未来展望
目前的实现仅基于 TableScan 进行了 Demo,还有一些 IndexScan 和点查询的适配工作没有进行;有些 TiDB 的生态工具是越过 SQL 层进行数据查询的,这方面的兼容性也是接下来需要考虑的问题。
除此之外,如果能够扩展 Flashback 操作和 MVCC Query 的组合拳,就能够实现更多的功能,比如查看 Flashback 记录、撤销 Flashback 操作、修改 Flashback 记录等等。
关于队员
怎么来到 TiDB 的世界的?
@JmPotato 刚刚结束了自己的学生生涯,现在是一名 PingCAP 的研发工程师,@RinChanNOW 是他的本科室友,也加入到了本次的项目之中。他们的另一位室友也在字节跳动分布式系统研发的部门实习。
——一个宿舍的基础软件工程师!
对于很多学生来说,成为一名应用开发者或者算法工程师或者进入 AI 行业都是更加主流的选择,为什么这么“巧”,他们不约而同地加入这个行业?
@JmPotato 表示,是他开了这个“头”。2019 年年底,他在听播客的时候偶然听到了东旭关于分布式数据库的分享,那个时候也不知道 PingCAP。后来进行相关的学习,在学习 Raft 的过程中接触到了 TiKV、TiDB 这些项目,才慢慢了解到原来它们都是 PingCAP 的产品。那时候 PingCAP 刚好在招聘实习生,自己也觉得心驰神往,做了很多相关的准备就开始面试、进入公司实习直到现在正式加入。
@RinChanNOW 也表示,是 @JmPotato 的这份实习为整个宿舍都打开了一个全新的世界,还记得当时大家一起实现一个简单的 Raft 协议,从那个时候就能感受到分布式系统的奇妙,不是被动的内卷,而是一种发自内心的热爱和向往。
不懂 TiDB,怎么快速加入 Hackathon 并且开始工作的?
虽然 @RinChanNOW 之前学习过分布式系统的相关知识,但是本身在学习和工作中都没有实际接触 TiDB 的经验。作为一名外部开发者,如何加入到这场关于 TiDB 的 Hackathon 中?这也是很多同学对于 Hackathon 活动望而却步的重要原因。@RinChanNOW 就完全没有这样的担心。一方面 TiDB 的文档丰富,体系化的学习起来并不费力;另一方面 TiDB 的社区非常活跃,无论是 AskTUG 还是 TiDB internals 或者说就是 GitHub 上,都能遇到很多志同道合的伙伴,他们也都愿意帮助 new TiDBer 快速融入社区。
@RinChanNOW 也与大家分享了一些具体的学习经验:
除了 TiDB 与 TiKV 的开发环境准备之外,需要做的一个准备工作就是了解 TiDB 和 TiKV 的代码结构与它们的数据流,也就是要去大致了解它们的源码,而这也是最耗时间的一个过程,所以我的代码量并不大,但是却花了很长时间才写完。 我根据需要改动的部分,结合 PingCAP 的官方博客,对源码进行了一波学习:
Select 流程:
TiDB 源码阅读系列文章(三)SQL 的一生
TiDB 源码阅读系列文章(六)Select 语句概览
如何将查询下推到 TiKV 并执行:
TiKV 源码解析系列文章(十四)Coprocessor 概览
TiKV 源码解析系列文章(十六)TiKV Coprocessor Executor 源码解析
Insert 流程:
TiDB 源码阅读系列文章(四)Insert 语句概览
TiDB 源码阅读系列文章(十六)INSERT 语句详解
一条 SQL 语句的具体执行流程:
TiDB 源码阅读系列文章(二十三)Prepare/Execute 请求处理
TiKV MVCC 读写流程:
TiKV 源码解析系列文章(十三)MVCC 数据读取
玩真的:TiDB Hackathon 有什么不同?
队伍中的 @disking 可以算是 Hackathon 的老玩家了,除了这次的 TiDB Hackathon,@JmPotato 和 @RinChanNOW 也都程度或深或浅地参与到了其他类似的编程竞赛中,谈及不同赛事体验的差异,他们有着统一的看法:很多编程竞赛更多是面向学生的,留一些有着明确目标甚至是标准答案的作业,更像一场检验编程能力的考试,比拼的可能是谁的实现更优雅,效果更佳。而参与 TiDB Hackathon 就是一种完全不同的体验。 TiDB Hackathon 更偏向实操,没有明确的选题,是一场未知的冒险,比起代码实现更重要的是创造力和思考,只有真正在用、真正参与到产品迭代中的开发者才能感受到其中的乐趣。
但是受到疫情的影响,近两年的 TiDB Hackathon 虽然热闹,但还是少了一点气氛。如果有机会,还是期待明年的 Hackathon 能够和各位选手来到同一空间现场交流,来一场真正的 48 小时密集开发。

    推荐阅读