上海品茶

您的当前位置:上海品茶 > 报告分类 > PDF报告下载

腾讯云:腾讯云数据库技术实践精选集(2022年版)(232页).pdf

编号:122415 PDF 232页 14MB 下载积分:VIP专享
下载报告请您先登录!

腾讯云:腾讯云数据库技术实践精选集(2022年版)(232页).pdf

1、版权声明本册精选集版权属于腾讯云计算(北京)有限责任公司和 InfoQ 极客传媒,并受法律保护。转载、摘编或利用其它方式使用本报告文字或者观点的,应注明“来源:腾讯云计算(北京)有限责任公司和 InfoQ 极客传媒”。违反上述声明者,将追究其相关法律责任。参与编写单位及人员内容指导内容策划专家顾问作者(按拼音首字母)卷首语过去数年间,在云计算发展和国产化趋势的双重驱动下,国产数据库呈现出百花齐放、齐头并进的新格局。回望 2022 年,国产化替代进程加速,国产数据库投产的广度和深度持续增加。从广度来看,一方面,给国产数据库产品和服务能力带来了更大的挑战;另一方面,也给诸多数据库厂商提供了广阔的机

2、会;从深度来看,数据库开始大规模落地于银行、证券、保险等金融企业的核心业务系统。在数据库竞争与变化的新格局中,腾讯云作为数据库领域的前沿探索者,其自研数据库的历史可以追溯到 2006 年。历经数十年的磨砺,腾讯云在数据库核心层自研、生态工具打造,以及数据库在不同场景下的落地解决方案等方面,积累了足够多的经验,并持续向业界输送。在过去一年中,来自腾讯云的技术专家,通过 DBTalk 技术公开课、QCon 全球软件开发大会、ArchSummit 全球架构师峰会以及 DIVE 全球基础软件创新大会,分享了腾讯云数据库的技术探索与不同场景下的落地实践。腾讯云数据库技术实践精选集2022 版,收录了全年

3、 40 余场分享的内容,也涵盖了优秀企业数据库应用的成功案例,相信一定能给数据库领域的各位同仁带去一些思考和启发。展望未来,国产数据库的“进化”之路仍任重道远,愿与君携手,百尺竿头,更进一步!腾讯云技术实践精选集腾讯云技术实践精选集张 倩王鲁俊 潘怡飞 唐 彦 卢卫 刘家文 窦贤明孟庆钟 陈育兴 丁艳飞 孙勇福 杨亚洲 陈 昊王宇庭 田清波 贾瓅园 韩 硕 汪泗龙 韩 锋孙 亮 彭 涛南云鹏 刘 畅 雷海林刘 迪 伍旭飞 孙 旭 刘生洲 顾雅各 余成真胡锦前 杨珏吉 王宏博 胡 翔 谢灿扬 朱阅岸王义成贾瓅园 唐 彦 刘少蓉 伍鑫 潘安群 刘亚琼周璐璐 温晓桦 扶婕徐樱丹刘颖内容编辑任传英第

4、1 章:数据库架构设计CDW PG 大规模在线数仓技术构架分享腾讯云原生数据库 TDSQL-C 架构探索和实践腾讯云数据库云上 SaaS 生态演进基于 LSM-Tree 的分布式组件化 KV 存储系统探索分布式数据库的多级一致性及构建技术演进之路敏态扩展,灵活应变!TDSQL 新引擎 TDStore 技术探索Redis 如何实现多可用区?新一代云原生数据库畅想基于压缩数据直接计算技术,定义新型数据库处理第 2 章:数据库性能优化数据库事务一致性实现上的各种细节,你注意到了吗?还在为数据库事务一致性检测而苦恼?让 Elle 帮帮你向量化执行从理论到实现,仅需五步!节省 30%磁盘空间的同时如何保

5、障数据安全?一些有趣的 B+树优化实验基于 LSM-Tree 存储的数据库性能改进面向个性化需求的在线云数据库混合调优系统第 3 章:数据库运维管理云原生数据库管控探索和实践腾讯云 MongoDB 智能诊断及性能优化实践数据库纳管平台 DBhouse 的技术路线与实践从 0 到 1 实现智能化运维目 录2829573208221229腾讯云技术实践精选集腾讯云技术实践精选集第 1 章:数据库架构设计1第 1 章数据库架构设计金融级分布式数据库架构设计与对接实践国产金融级分布式数据库在金融核心场景的探索实践金融级分布式数据库 TD

6、SQL 升级版引擎架构和关键技术介绍深度解析金融级分布式数据库一致性技术金融业数据库选型之路金融应用对分布式数据库的诉求银行核心业务系统数据库主机下移架构与实践分布式数据库在金融场景下的实战TDSQL 破局敏态业务背后的技术演进TDSQL 在 TP 领域的技术探索和实践新一代云原生数据库 TDSQL-C 关键技术突破KeewiDB 轻 TP 技术实践只有时代的 Oracle,没有 Oracle 的时代-看国产数据库如何突出重围!得物数据库中间件平台“彩虹桥”架构演进及落地实践云原生下缓存架构如何演变?在敏态业务场景中,微盟数据库的应用实践之路50W+小程序开发者背后的数据库降本增效实践TDSQ

7、L-PG 数据库在微信支付的应用实践如何像用自来水一样使用数据库?HTAP 数据库存储引擎技术演进如何利用资源管理技术,让 HTAP 负载同时运行HTAP 数据库最佳实践HTAP 系统的问题与主义之争23824825626328529293393483553673773863923994064第 1 章:数据库架构设计第 1 章:数据库架构设计23CDW PG 大规模在线数仓技术构架分享张倩腾讯云数据库专家工程师腾讯云数据库专家工程师,中国人民大学博士,具备多年数据库内核研发经验,曾在 Teradata 天睿公司以及华为公司高斯实验室工作多年。加入腾

8、讯后,负责 CDW PG 数据仓库内核优化器、执行器等多项核心功能研发。腾讯云数仓产品 CDW PG 是一款面向超大规模集群海量数据分析的在线数仓。通过创新的数据转发平面、全自研列式存储、分布式延迟物化技术,以及向量化执行引擎等核心技术为用户带来极致的数据分析体验,可以真正将海量数据所蕴藏的价值释放出来。ArchSummit 2022 北京站上,腾讯云数据库专家工程师张倩带来题为 CDW PG 大规模在线数仓技术构架分享的分享,主要聚焦 CDW PG 在构架设计以及核心优化上面的一些经验和思路。CDW PG 发展历程介绍CDW PG 是腾讯基于 PostgreSQL 自主研发的分布式在线关系型

9、数据仓库。PostgreSQL 是一个单机版数据库,我们在这个基础上开发了一个无共享 MPP 架构,支持行列混合存储,同时支持超大规模集群,目前集群规模可以支撑到上千节点,同时可以支撑超高速的计算能力。整体架构演进大规模集群面临的一大挑战是集群扩展性挑战,分布式 JOIN 消耗大量网络连接和对应资源。对于分布式 JOIN,数据是分布在不同节点上的,数据的分布键和分布式 JOIN 的键并不是CDW PG 的整体架构如上图所示。完全一致,这时数据需要重分布,重分布之后再进行 JOIN 连接才能得到最后结果。数据的重分布通过 DN 节点,把本 DN 自身的数据通过模块连接发送到对应的其他 DN 上。

10、如果 DN 节点非常多,每个 DN 都要和 JOIN 建连,则一个数据重分布就会有很多连接。假如有 200 个 DN 节点,有 100 个并发查询,如果每个查询 5 个重分布,这种情况下可以计算出 10 万个连接。每个连接在每个节点上对应一个进程,对服务器的压力非常大,也是限制分布式数据库拓展性的问题之一。原来的集群架构显然不适合大规模集群,所以我们在这个基础上开发了异步执行框架,一个新型的分布式架构。在这个架构里,我们会在查询优化阶段分析物理查询计划,统一创建 DN 上的各层执行进程,各层执行进程之间不需要建立直接连接,不同层级进程可以异步启动执行。假设我们有 N 个节点,就会产生 N 个进

11、程数,这是进程数上的优化。我们在连接数上会进一步引入 FN,FN 来负责节点间的数据交互。每台物理机只需要布置一个 FN 节点,FN 节点会负责本台物理机和其他集群内其他物理机的数据交换。在本机节点上,这个 FN 与 CN 和 DN 之间是通过共享来进行数据交互的,本机和其他的物理机进行数据交互的时候是通过网络进行。在这样的分布式架构下,假设有 N 个节点,不管 JOIN 有多么复杂,查询可以有十几个 JOIN,它们只有 N*(N-1)个连接数。在优化器阶段会查询计划分片。优化器是根据代价来审核的最优计划。在单机上的代价是一些算子,每个算子有不同的代价;但在分布式的情况下,数据重分布也有自己的

12、代价。优化器会生成代价最优的计划,在分布式的计划里可以看到,Remote Subplan 会有数据重分布节点,数据重分布节点会把下层执行的数据按照一定规则进行数据重分布,生成分布式的计划之后会变成整个执行计划,对这个计划进行分片。分片的原理很简单,我们对每个数据分布节点下面的 Log 节点作为一个组数,划分成一个分片,把分片用 FID 编号。每个分片除了 Remote 第 1 章:数据库架构设计第 1 章:数据库架构设计45Subplan 节点,下面的计算都可以在本地完成。本地完成之后再通过 Remote Subplan 节点把数据发送出去,串联起整个分布式的计算。生成这样一个分布式计划之后,

13、会下发这个计划分片到对应的执行进程上,每个分片会在每个执行节点上创建一个进程来执行对应的计划片段。在这个 JOIN 里会分成两个 FN 片,会有 FID1 和 FID2,FID2 对数据重分布,然后再跟 FID1 做一个 JOIN,这样两个计划分片在 DN 片会有 4 个进程。这 4 个进程,每个进程会负责执行自己收到的进程,然后把数据通过 Remote Subplan 节点,不会直接建连,而是把数据发送给本机的 FN,FN 负责把数据转发。不同层级之间的进程是异步启动执行的,下面的分片和下面的分片是同时启动执行的,通过 FN 进行数字交互。自研列式存储我们自研的列式存储支持按照行存储和列存储

14、建表,列表和行表之间可以相互操作,行列表之间的混合查询保证事务一致性。很多查询都是点查询或者是只会操作比较少量的数据,在这种情况下,我们点查每次只出一行数据,在这个行数里面可以获取这一行数据进行处理。但是在 AP 场景中,对于宽表来说它的列会非常多,每次用户操作的时候只操作其中几列,比如说只查名字或者只查名字和部门,或者只查名字和年龄,后面的几百上千列都不会管。如果还按行存储就会浪费很多 IO。后面的列数都是我们需要的,每一列单独存储,多个列逻辑上组成一行,一次的磁盘 IO 只包含一列数据。这种情况下,因为这一个数据文件只存储单列的数据,它的数字类型都是固定的,很方便做数字压缩,也能够提供比较

15、高的数字压缩效果。在我们的数据库里支持行列存储,既可以在行存表和列存表,可以同时互相操作,也可以查询复杂的操作,行列表之间的混合查询能够保证数据的一致性。分布式集群里一个比较难的问题是保证分布式数字一致性。我们是基于 Timestamp 的分布式提交协议。我们会有一个 GTS 集群,GTS 集群包括 GTS 组和 GTS 位。GTS 集群是提供给数据控制节点,它是从内部开始,内部单向并且唯一由 GTS 维护,由硬件来保证使用源的稳定度。在这个情况下,加上数据库内部的 MVCC 的存储能力(MVCC 是整个并发控制的基础),这两个结合起来提供了分布式事务的一致性。GTM 单点可靠性是由 GTM

16、主备来支持的,也就是说主节点可以通过对外提供服务来保证分布式事务的一致性,然后主备之间是通过日志簿时间戳的状态来保证 GTS 的可靠性。对于单点可靠性,数据库里面如果是非常繁忙的业务,TS85 服务器每秒能够处理 1200 万的 GTS,可以满足绝大部分业务团队的需求。上图介绍了我们自研的列式存储,主要包含两部分。第一部分是左边的表,会有一些信息,有 C1 文件和 C2 文件,每个文件里面的数据是按照 Silo 来仓储的,对应的 Silo 信息会存储在这个表里,同时 Silo 的输出上,事务的信息也是通过这个表来保证的。在这样的基础上,可以看到这种数据结构比较适合大批量的数据导入,也就是说我们

17、在做大批量数据插入的时候很快会填满,再写入下一个 Silo,内存的效率最高。但是在很多业务场景中,这个场景并不是很单一,也有可能除了大批量的数据插入之外,还有一些小数据量的操作。这种情况下如果我们还用 Silo 来存储就会显得效率比较低。为此我们开发了一个 Stash 表,它是一个行情表,主要目的是在用户进行小数据量操作的时候,这些数据都会进入到 Stash 表里,提供一个像行存一样的性能和访问能力。在后台我们会有一个 Stash Merge 操作,会定期 Merge。数据积累到一定程度,我们有了整个 Silo 的数据,就会把它生成一个 Silo,从而获得批量性能提升,这对用户来说是完全无感知

18、的。Silo 头部会定义一些数据特征,包括 CRC 校验、压缩信息、Bitmap 信息、数据压缩文件,最后有一些页面对齐。我们提供了列存文件格式之后,由于每一列的数据格式是一定的,我们就可以提供一个比较高效的数据压缩算法。在这里我们提供了三层压缩级别,分别是 Low、Middle 和 High。选择了对应的压缩级别之后,数据库内部是根据这个列具体的数据类型来选择不同的压缩算法,这个压缩算法是内部选择,用户不用自己操心。我们写入的时候会有一个轻量级压缩,再一个是透明压缩,然后是写入 Silo 文件中,然后读取到内存中计算,这个过程对用户是完全透明的。High 的压缩比会达到 5.5,Low 是

19、3.5 左右。在列存的存储格式中,我们进一步提供了延迟物化扫描优化。传统的方式是把数据扫描上来做对应的计算,生成对应的结果。但在列存的数据格式下我们可以提供一个优化的方式,比如说有一个多列计算,可以先扫描其中一列,如果都不符合条件可以都不扫描,第二列直接跳过,或者我们扫描的这一列里面只有一个数据是符合条件的,在对第二列计算的时候,可以扫描对应的数据位置来计算,相比传统的方式需要扫描所有数据,这种方式可以减少很多 IO,提升扫描和查询计算的性能。同样这个列存也是支持索引的,我们现在支持的是两种索引,一个是 B-Tree 索引,一个是 Hash 索引。列存的 Stash 表后台的自动合并能力是为了

20、小数据量插入和小数据量更新准备的。在这种情况下,数据会进入到 Stash Table 里,然后 Stash Table 会自动做一个 Stash Merge 到 Silo 表里。第 1 章:数据库架构设计第 1 章:数据库架构设计67我们的列存表是支持创建配套的 Stash 缓存表,如果是批量的插入,中心会直接写到 Silo 表,如果是小数据量插入,中心会写入到 Stash 表,但这对于用户是完全透明的,是后台自动判断的。后台也会有一个 Stash Merge 的进程,自动判断这个 Stash 表里的数据大小,进行列存格式的整理。也就是说把 Stash 的表格数据 Merge 到 Silo 里

21、,通过列式加 Silo 表能够达到性能统一。执行引擎能力优化我们执行引擎的能力优化分为几个部分,首先是多级并行能力的提升。我们是分布式集群,天然就提供节点级的并行。在每个 DN 内部,我们又提供了进程级的并行。某个 DN 会有计划分片,计划分片在传统的数据里会启动一个进程完成它。在我们的集群里面,对于同样的一个计划分片,我们会生成多进程并行。一个简单的 Stash 操作,如果有两个进程并行 Stash,效率会提升 50%,每个进程只需要处理一半数据。上面做计算时也是两个进程并行执行,最后得到的结果是两个进程结果的叠加。在进程级的并行基础上,我们还提供了 SIMD 指令级并行。在做计算时可以通过

22、 SIMD 进行指令级并行。我们的向量化执行引擎也是基于自研的列式存储上开发的执行引擎。传统的执行引擎是采用火山模型,每次处理一个原子,逻辑比较简单,但效率比较低。数据量非常大的时候,比如我们有非常多的源组库的时候,函数调用和指令级调用都非常多,CPU 大部分时间中数据和指令的命中率都比较低,内存的命中率也很低,没办法利用 SIMD 能力。向量化产品执行引擎每次不是处理一条数据,是处理一组数据。它会显著减少函数调用的开销,提高 CPU 的执行效率。我们这个列存天然可以将一组原子变成一组列存向量,从列存里面,我们可以读取一个向量数据再发送给算子,算子就可以对这一组数据进行计算。这样我们既可以减少

23、函数的调用,又可以实现 SIMD 指令来加速算子执行。我们提供的另外一个能力是 Plan Hint 自动调优。很多场景都是非常复杂的查询,会有非常多层。在这种情况下,我们的顺序和对应的每个表选择什么,都会影响 Plan 的执行效率。传统的优化器是通过我们收集的表统计信息来决定查询的执行计划。但统计信息的收集是通过数据采样大概模拟出这个数据到底是什么样的,显然不太精确。而且由于统计信息收集不及时,会造成 Plan 的劣化,统计收集会有代价,在大数据量的情况下需要很长时间。在这种情况下,我们提供了 Plan Hint 的能力,会把这个计划先执行一遍,收集一下计划中执行的信息,生成 Row Set

24、Hint:每个表里选择了什么样的方式,顺序是什么样的,数据存储方式是什么样的,并行执行的并行度选择什么样的,每一步算子执行实际的数据量是什么样的。生成后还会把信息反馈到 Plan Hint 里面,根据这个信息生成一个新的执行计划,再对比新的执行计划和之前的执行计划,然后把新的执行计划再次通过 Plan Hint 执行,循环往复,最后能够生成比较优的执行计划。这个生成过程是完全自动的,用户可以在后台比较空闲的时间来执行,或者用户有一些调优需求的时候可以自动完成。第三是集群资源管理功能。在整个集群可能不会承载一个业务,有可能会有各种不同的各部门业务,都需要用这个集群的资源。在这种情况下,我们需要对

25、集群资源进行管理和划分。比如说我们做一些并发的控制、内存的控制、CPU 的控制,我们通过 Leader CN 节点统一规划资源组使用情况。资源组里会有三个定义,一个是我们的并发,指我可以同时发起多少个查询;内存控制,决定我可以使用多少内存;还有 CPU 控制,决定我可以使用的 CPU。这样一个资源组是在 Limit 节点上进行统一规划控制,优化器会根据 MEMORY_LIMIT 设置 Query 中不同算子的 work_mem 内存占用。GPU 通过 cgroup 来配置占用百分比或者绑核,所有当前资源组启动的后端进程会挂在对应的 cgroup 上。这是一个非常便利的操作。我们开发的 TDX

26、服务器负责外部数据源导入和导出的操作。CDW PG 引擎可以定义外部表与 TDX 服务器资源进行绑定。数据由 DN 节点并行导入与数据重分布,充分利用分布式系统资源。TDX 服务器支持并行多任务导入导出、管道、错误表等高级功能,提高用户体验,相比传统 Copy 入库出库功能有数倍提升。我们还有多平面的能力,意思是说我们可以提供两个平面,一个是读写平面,还有一个是只读平面。通过这样两个平面可以做到读写分离,也就是说读写请求可以进入到读写平面,大量读请求可以通过只读平面来完成。内部是通过 DN 之间的内部复制来保证两个平面之间的数据一致性。后续规划上述内容已经在腾讯云上线,包括异步执行框架、FN

27、能力提升、自研列存储,分布式延迟物化技术。我们后续在构架上会进一步优化,包括列存优化升级、向量化引擎深度优化、算子并行计算优化、SIMD 优化场景覆盖。我们还会持续打造生态,持续融合 PG 社区能力,在兼容性上会有持续的功能提升,还有大数据生态对接和机器学习算法支持。第 1 章:数据库架构设计第 1 章:数据库架构设计89腾讯云原生数据库 TDSQL-C 架构探索和实践产品架构介绍腾讯云原生数据库早期也曾采用传统架构实现,但实践中存在很多问题:传统架构的实例存储上限完全受限于本地磁盘容量,扩展成本高昂且操作复杂。用户数据较多时需要分库分表,带来很多分布式事务问题。而用户希望一个实例有 100T

28、 以上的容量,且存储容量可以快速透明扩展。传统架构基于 Block 复制,普通的异步或半同步复制方式可能丢失数据,同步复制方式的性能损失较大。用户要求数据不丢失(RPO=0),且数据有多副本容灾,可靠性满足要求。传统架构可用性不足,HA 或宕机重启后一段时间(分钟级)不可用,且主备副本延迟较高,有些达到小时级。用户期望能够快速切换 HA,实现秒级恢复、回档,且副本时延在毫秒级。王鲁俊腾讯数据库内核开发工程师曾就职于蚂蚁金服、阿里云等,在数据库内核领域有多年开发经历,对分布式系统、数据库存储引擎等方面有丰富经验。TDSQL-C 是腾讯打造的云原生数据库产品,其高可用、高可靠、高性能的特性已经被越

29、来越多的用户认可。在 DIVE 2022 全球基础软件创新大会(北京站)上,腾讯数据库内核开发工程师王鲁俊带来了主题为腾讯云原生数据库 TDSQL-C 架构探索和实践的分享,为大家介绍 TDSQL-C 总体架构和核心能力的持续演进现状,并重点介绍 TDSQL-C 在可用性、可靠性和性能方面的工作,同时结合用户场景分享 TDSQL-C 在实际应用中的实践经验。传统架构水平扩展时需要复制一份原有数据库实例数据,再建立同步关系。才能真正水平扩展;创建只读副本时要把原始数据复制过来,然后搭建主备同步,只读副本才能开始工作,过程可达小时级别。用户则希望实现秒级副本扩展。腾讯云针对这四方面的用户需求采用了

30、存储计算分离的架构,就是 TDSQL-C 的核心架构。它的存储是云存储,可以水平扩展,理论容量无限,对每一份数据都有多副本来保证可靠性。数据现在分散在云存储的各个节点上,可以做持续备份、运营回档等功能。在可用性方面,数据的分片可以并行恢复、并行回档,时延更低。可扩展性方面,新建只读副本时数据不需要复制,只需要对数据做共享操作,再构建增量的数据复制即可。TDSQL-C 架构整体分为上面的计算层和下面的存储层。计算层有一个读写节点可以提供读写请求,还有多个只读节点可以提供读请求,也就是上图中的 Master 和 Slave 节点。读写请求进入后,Master 节点产生数据修改,然后把修改产生的 I

31、nnoDB 的 Redo Log 下传到存储层,同时会把 Redo Log 分发到自己 RO 节点上。存储层负责管理所有数据,包括数据页面、InnoDB 层的 Segment。当 Redo 日志发送到存储层之后,Segment 负责 Redo 日志的回放。整个存储层架构在 COS 存储服务上。TDSQL-C 的存储可以自动扩容,最大提供超过 1PB 的容量,最大支持 96 CPU、768G 内存,只读性能超过 100 万 QPS,读写超过 45 万 QPS。该架构实现了秒级故障切换、秒级快照备份和回档,且存储和计算层有足够弹性,可以实现一定程度的 Serverless。需要扩展只读性能时,该架

32、构很容易增加只读节点。TDSQL-C 现在最多可以挂 15 个只读节点,且在读写节点和只读节点之间只有毫秒级延迟。整个工程是基于 MySQL 代码库演化来的,所以 100%兼容 MySQL。第 1 章:数据库架构设计第 1 章:数据库架构设计1011用户场景实践TDSQL-C 的典型客户场景如下:1.Serverless有时业务端预测未来的需求持续上涨,所以会提前准备数据库实例,但实际需求却偏小,最终会造成浪费。但如果突发情况下实际需求暴涨,实例资源无法立刻跟上,又会对业务造成严重影响。理想情况下,资源容量应该与真实业务需求同步变化,随时保持在比真实需求略高的水平。TDSQL-C 实现了智能化

33、的极致弹性,可以根据负载来快速启停实例。TDSQL-C 还实现了按需计费,可以按秒计量、按小时结算,避免浪费。2.弹性扩容有些业务每天都能产生大量数据,且对单库容量要求很高;有些业务如开发测试场景,某一次测试后数据库实例直接删除,生命周期非常短;有些历史库场景中历史数据只存储最近一段时间,老数据直接删除来节省成本。针对上述场景,TDSQL-C 支持按需扩容,不需要预先进行存储规划;还支持自动回收,按实际使用容量计费等。3.备份回档很多场景对备份回档要求较高,如金融行业对备份速度和备份时效性都有很高要求,涉及到频繁回档的游戏业务对备份回档速度要求较高。TDSQL-C 支持持续备份,存储分片可以根

34、据备份点独立备份,同时可以设定全局一致的备份点来备份。TDSQL-C 还支持每一个分片并行回档,各自的数据全量、增量备份,并且可以并行回放自己的日志。另外,TDSQL-C 还支持 PITR,可以快速恢复到数据库任意一个时间点的状态。系统关键优化以下是 TDSQL-C 支撑相关场景的关键优化:1.极速启停可以看到 TDSQL-C 对比传统 RDS 数据库,在停机时间、启动时间、事务恢复时间和性能恢复时间上都有巨大优势。2.二级缓存二级缓存是 TDSQL-C 在存储计算分离架构下所做的创新优化,也是对这种架构的重要补充。例如存储层水平扩展后数据量膨胀上百倍,但计算节点的 Buffer Pool 容

35、量没有太大变化,意味着相同大小的 Buffer Pool 要服务更多数据。由于 InnoDB 的 Buffer Pool 一定程度上承担了读缓存的作用,意味着读缓存的效率可能会下降,对性能影响较大。其次,传统 RDS 的 Buffer Pool 和本地的磁盘空间存储中间没有其他层次的硬件存储设备,但在存储计算分离架构下数据存在远端机器上,需要通过网络访问其他机器的 SSD。这之间有至少两个层次的硬件存储设备,一个是 SSD(本机硬盘),另一个是持久化内存。我们将这一类的存储用作二级缓存,能够有效减缓 IO bound 场景下命中率较低的问题。这种方式的运维成本也不高,因为 SSD 的成本远低于

36、内存。3.极致伸缩存储功能下放到存储层后,存储层有了存储池的概念。这样可以把存储空间扩展到整个存储层,所有存储空间都池化。这样页面回收之后可以在物理上真正删除,而不是只标记为删除,这样能降低客户的存储成本,实现按需计费。4.极速备份回档现在数据分散到了分布式存储上,分布式存储包含很多存储节点,本身还有一定计算能力。所以每个实例、存储节点都可以独自备份,也就是自治备份。有时候我们还需要全局一致的备份点,这时有计算节点负责协调,做统一备份。回档也是并行的,每个计算节点都可以独立做自己的回放。根据测试数据来看,1TB 数据的备份时间用 RDS 实例需要 61 分钟,用 TDSQL-C 只需 21 分

37、钟;1TB 的回档恢复时间,RDS 需要 168 分钟,TDSQL-C 也只需 22 分钟。5.Instant DDL 和并行构建索引Instant DDL 是 MySQL 8.0 的新增功能,是指用户在新增列、修改列类型、删除列时,实际上仅仅修改元数据就可以直接返回,这样注册时间非常短。原来重建表索引时都是单线程构建,先扫描所有主表数据,再根据每一行数据生成对应的第 1 章:数据库架构设计第 1 章:数据库架构设计1213索引行信息,生成临时文件。之后对这个临时文件按照索引行的索引键排序,再将数据导入,完成构建。我们针对这里做了并行优化,包括并行扫描、并行采样和并行导入,很多场景提升到 2

38、倍以上的性能。产品未来演进我们探索的第一个演进叫 Global Database:上图左边有一个 Primary 实例,现在新增了一个 Log Store 模块。它负责接收日志、分发日志,可以提升日志的响应速度和整体的 IO 吞吐量,进一步提升写性能。另外 Primary 实例与图右的 Standby 实例可以通过各自的 Log Store 来建立数据复制链路,这样数据就扩展出来一个只读节点,从而可以读扩展,而且是跨 Region 读。另外我们通过这种方式实现了跨 Region 容灾,这对很多金融业务来讲是刚需。第二个演进是计算下推。我们的存储实际上拥有一定的计算能力,所谓下推就是利用它的计算

39、能力。例如读下推(页面内计算下推),不用将索引的叶子节点读入 Buffer Pool,而是直接将下推条件发给存储,返回符合条件的 Record。又如读下推(undo 页面间的计算),对于数据的历史版本扫描下推到存储层完成,内存不读取页面,仅获取最终结果。还有写下推,特定页面的修改直接在存储层进行。总结腾讯云原生数据库 TDSQL-C 继承了传统架构下的云数据库 1.0 的优势和竞争力。我们面向云上用户场景分析其业务特性,持续提升产品的可用性、可靠性、可扩展性和性能,进一步提升了 TDSQL-C 产品的竞争力。未来我们还会继续面向客户需求,跟进新硬件的发展,再去探索表现更好的新型架构。腾讯云数据

40、库云上 SaaS 生态演进潘怡飞腾讯云数据库高级工程师腾讯云数据库解决方案架构师,拥有多年的数据库服务应用优化实践经验。近年来,腾讯云数据库产品在 SaaS 生态方向上积极探索与布局,在数据库服务平台化、自动化、智能化领域推出一系列优秀的 SaaS 产品和完善解决方案,助力互联网、金融、传统企业等客户架构升级,数字化转型,构建新一代的数据库服务平台。ArchSummit 2022 北京站上,腾讯云数据库高级工程师潘怡飞发表题为腾讯云数据库云上 SaaS 生态演进的演讲,介绍腾讯云 SaaS 产品矩阵、能力、生态和演进方向。腾讯云数据库 SaaS 生态矩阵腾讯云数据库 SaaS 生态分为基础能力

41、 SaaS 化、易用性 SaaS 化、智能化运维和模块化 SaaS 产品四个方向。总体来说,SaaS 产品的生态演进是基于 PaaS 产品的基础能力和衍生能力,提炼出更靠近业务属性的 SaaS 能力,帮助用户和客户更好更方便地使用数据库,创造业务价值。腾讯云 SaaS 生态矩阵的几个主要产品包括:数据传输服务 DTS,定位为数据迁移与数据流打通的服务,包含在线迁移同步、低时延和高可靠的特性,主要包含迁移、同步和订阅模块。数据智能管家 DBbrain,是定位于自动化、智能化运维统一管理平台,从前期的数据库巡检、故障发现、故障定位到后期的故障解决与系统优化,形成一套数据库全生命周期管理运维的工具。

42、第 1 章:数据库架构设计第 1 章:数据库架构设计1415数据库安全审计,是一个内核级别的安全审计平台。区别于一些需要旁路管控部署的方式,它对性能和收集完整度都支持得比较好。同时它跟 DBbrain 联动,对收集到的审批日志进行汇总、分析,这样可以看到具体的问题根因,发展出在运维或者台账过程中也可以使用审计日志的能力。腾讯云图,是一个定位于定制化大屏 BI 展示的平台。它可以快速通过可视化的图标管理来降低建立专业大屏的门槛。它内置预设了多个行业模版,可以用自由拖拉拽的方式快速形成一套炫酷大屏,包括前端数据展示。腾讯云数据传输服务 DTS数据传输服务 DTS 包括以下几个部分:数据迁移功能面向

43、单次的数据库迁移,支持多种网络链路,全量增量不停机迁移,最小化迁移过程中停机对业务造成的影响,全程自动化减少风险点,支持一致性校验能力。适用于一次性迁移数据库迁移,数据库迁移上云或迁移下云场景。数据同步功能指两个数据源之间的数据长期实时同步,具有多种高级特性,库表重映射、DML/DDL 过滤,Where 条件过滤,双向同步,丰富冲突策略。适用于云上云下多活、异地多活、跨境数据同步、实时数据仓库场景。数据订阅是指实时按需获取数据库中关键业务的数据变化信息,将这些信息包装为消息对象推送到内置 Kafka 中,方便下游实时消费。适用于异构数据更新、业务异步解耦、缓存更新,ETL 实时同步等场景。上图

44、是数据传输服务整体架构,主要有两个关键措施。第一个是基于原生解耦,采用统一的数据中间格式,导出和导入完全流程化。第二是插件式设计,方便数据库快速接入,解决历史遗留问题。数据传输服务应用的场景有:云数据库迁移,提供灵活的上云下云场景支持,同时支持下云到自建 IDC,有数据正确性保证。双向数据同步,支持异地多活、异地灾备,支持双向、环形数据同步,解决了同步数据重复写冲突问题,具备高性能、低延迟。多到一数据同步,包括多源数据聚合、汇总分析场景。支持多到一数据汇总、同步,支持分表聚合、一拆多、跨云账号。异构数据同步,包括同源异构同步与非同源异构同步。数据传输服务中,数据订阅适用于很多没有固定下游的场景

45、,例如数据仓库、自定义函数、云函数等。数据订阅的架构和同步类似,基于 Binlog 解析,将这些 Binlog 解析为正确的消息对象,基于客户配置的不同的表或库,再推送到内置的 Kafka 里。用户可以很方便地通过各种 Kafka 来消费不同的异构下游,实现了异构数据库之间或者复杂链路之间的同构。同时 Kafka 也支持多分区,可以设置不同的策略来优化大数据量同时传输效率。Binlog 本身是无法自解析的,解析出来的日志只有数值,没有对应的表结构字段。如果按这种方式推送到下游,其实数据是不可用的。所以我们需要自研的 Schema Tracker 组件来实时、无锁,动态获取维护表结构的变更,这样

46、就完成了整个链路的打通。另外考虑到数据订阅也支持到了更多数据格式,降低了用户切换和使用的成本,可以无损替换使用,方便打通不同链路。通过迁移、同步和订阅三种模块,可以充分打通数据链路之间的流转管理,构建双向、环形、异构、多合一等场景。用户不用自己开发工具或定时任务解决这些需求,可以更专注于业务。数据库智能管家 DBbrain数据库中统一管理时有许多难点和痛点,DBbrain 是应用于这个场景的一款 SaaS 工具。它有三大功能:智能优化。针对获取信息、分析信息、优化手段痛点,利用完善的云平台监控、智能算法与案例库沉 淀,针对问题根因形成优化手段。数据库安全。DBbrain 内置深度学习算法、海量

47、样本,提供的功能包括安全预警、敏感第 1 章:数据库架构设计第 1 章:数据库架构设计1617数据发现、数据脱敏、治理建议等。数据库管理。DBbrain 的功能包括云上云下混合云管理、无侵入安全链路、掌上运维。基于上述三大功能,DBbrain 适用于数据库日常运维、安全威胁识别、混合云管理数据库、掌上数据库运维等场景。上图是 DBbrain 的整体架构图与核心优势展示。基于 DBbrain,运维人员可以针对突发复杂告警问题做快速处理或提前规避。DBbrain 的全生命周期管理分为几个模块,一是监控、预防、分析、告警,主要用于事前分析。包括 70 余项监控项,监控大屏、秒级定期巡检,做到全程问题

48、把控,把问题扼杀在摇篮中。这一部分还可以进行性能预测、容量预测,避免因为存储问题影响性能。事中模块包括快速定位和应急管理,如果不幸发生问题可以快速止损。比如一键分析异常能力会给出具体分析,应急处理里的灵活限流可以避免问题扩散,一键 Kill 提供兜底能力。事后需要复盘,复盘后需要一键优化能力,DBbrain 会给出全盘报告对之前的问题复盘,包括自动调优、专家级建议,还有优化效果对比。DBbrain 的一个性能优化场景是慢 SQL 分析。它会基于多维度统计,包括用户和账号等信息,根据耗时区间来统一汇总 SQL 模版并自动排序,这样我们可以灵活判断出是哪个 SQL 导致的问题。下一步是性能优化,我

49、们可以进行编译器、优化器的改写,比如计算代价和成本,同时可以判断 SQL 是不是优质,是不是需要添加索引,可以针对性做优化,也减少了 DB 和研发的各种对接。甚至一些不必要麻烦可以在 DB 层面处理掉。它甚至可以做 API 的拉取,再通过开源组件进行存储分析。这样可以基于 DBbrain 的 SaaS 能力构建自己的运维平台,免费满足收集、分析、优化需求。第二个性能优化场景是异常诊断。日常诊断是根据秒级监控,对十几个维度的信息做汇总展示。诊断提示展示诊断事件的概要信息,包括等级、开始时间、诊断项、持续时长。DBbrain 会定期进行健康巡检。诊断事件详情可查看事件概要、现象描述,给出智能分析以

50、及专家建议。腾讯云 BI 云图腾讯云图可以零门槛构建 2D 和 3D 的前端大屏,预设有多个模版,不需要有代码门槛,可以快速构建出一个炫酷大屏,同时投屏到不同终端,后端数据源也支持多种。构建大屏只需要四步,首先创建一个大屏项目,再基于预设的模板形成及时预览和所见即所得的页面。第三步是配置数据源,然后预览发布。不管是运维大屏还是业务大屏都可以快速交付。云图的一些案例包括现在常见的疫情监控大屏,包括 3D 版的智慧校园、腾讯内部用的腾讯云大屏,还包括智慧城市、智慧零售等等。这些案例都可以用几步很简单地制作出来,大幅降低研发成本。第 1 章:数据库架构设计第 1 章:数据库架构设计1819总结和展望

51、SaaS 产品价值总结下来就一句话:可以降低用户在工具或者不必要的研发上的投入,更专注聚焦于自己的业务,不需要再去建数据库和前端,直接依赖 SaaS 就好,帮助业务更好地创造自己的价值,不需要分散精力。DTS 后续会在场景化和复杂拓扑场景深耕,可以一键创建用户需要的复杂拓扑链路,比如环形、双向等场景,而且不需要逐条配置冲突策略。DBbrian 后续会向智能化、AI 化方向发展,可以直接基于慢 SQL 自动创建索引,甚至数据库负载自动扩缩容,同时可以利用云原生数据库的快速缩扩容能力,充分结合更多产品进行场景联动,帮助客户创造业务价值。基于 LSM-Tree 的分布式组件化 KV 存储系统唐彦腾讯

52、云数据库专家工程师、浙江大学博士。研究领域主要关注分布式存储、大规模数据密集型系统相关的关键技术,曾以第一作者身份在领域 Top 类期刊和会议上发表多篇论文。博士毕业后来到腾讯从事基础研究与技术工程化工作,目前主要负责分布式数据库 TDSQL 的元数据管理与集群管控调度相关工作。随着云服务基础架构以及微服务技术的日益成熟,很多大型系统能够分解为根据应用 workload 需求的多个子系统,再通过网络交互组装在一起协同工作。Nova-LSM,一个将基于 LSM-Tree 的分布式 KV 存储系统分解为使用 RDMA 进行通信的组件的工作。这些组件将存储与处理分开,使处理组件能够共享存储带宽和空间

53、。处理组件将文件块(SSTable)分散到任意数量的存储组件中,并通过一定机制平衡它们之间的负载,在运行时动态构建范围以并行化压缩并提高性能。Nova-LSM 具有很好的可伸缩性,在一些场景下性能优于其单机版本(LevelDB 和 RocksDB)几个数量级。本期 DB洞见将由腾讯云数据库专家工程师唐彦,从前沿学术的角度深度解读 Nova-LSM,重点介绍 Nova-LSM 的特性、重要设计及带来的启发。以下为分享实录:LSM-Tree 基本概念1.1 LSM-Tree 存储系统第 1 章:数据库架构设计第 1 章:数据库架构设计2021LSM-Tree 全称为“Log Structured

54、Merge-Tree”,它是一种基于磁盘存储的数据结构。在以前,数据库的索引基本上采用 B+树方式来作为索引结构,在此情况下,随机写性能常被别人诟病。LSM-Tree 则主打将离散的随机写请求转换成批量的顺序写操作。而无论是在机械硬盘还是在固态硬盘,顺序的读写性能永远好于随机读写。因此 LSM-Tree 作为一种高效的 KV 存储结构,被许多业界的成熟系统所应用,比如腾讯云数据库 TDSQL 新敏态引擎也是基于 LSM-Tree 进行开发。LSM-Tree 结构有较多优点:写性能强大、空间利用率高、较高的可调参特性、并发控制简单、有完备的修复方案等。1.2 LSM-Tree 的历史在数据库更新

55、方面,LSM-Tree 与 B+树的区别可以理解为:一个是 in-place update,一个是 out-place update。基于 B+树的更新,我们称之为 in-place update。如下图所示,k1 本来的值是 v1,随后我们想在 k1 写下新值 v4,这时需要找到 k1,再把 v4 写进去。因此,在 B+树的索引结构里对数据进行更新或者插入,会引起实时的 I/O。在 B+树的场景下,写性能会受到一定影响,但由于 B+树可以支持较好的检索性能,因此读性能会较好。相比之下,在 LSM-Tree 结构里,如果这时对 k1 进行 v4 的更新,我们不会马上把 k1 改成 v4,而是将

56、它转化成顺序写,把它写到内存里,追加在(k3,v3)后面,因为顺序写的性能远比随机写高,但这种方式则会牺牲读性能及导致空间放大。下图展示的是 1996 年 LSM-Tree 最原始的结构。C0 代表的是在内存里的状态,每当有数据写入,它就会逐渐往下 merge。当第 i 层满时,它会把底下的叶子精简,从 Ci 到 Ci+1 去往下 merge。层数越大则表明数据写入越早。每一层最初的版本的头部是 B+树,C0 是在内存的节点,接受最新的数据更新,C1 层到 Ck 层都存在于磁盘。第 1 章:数据库架构设计第 1 章:数据库架构设计22231.3 LSM-Tree 基本结构目前主流的 LSM-T

57、ree 基本架构如图所示。我们会在内存中保留 memtable 结构,用于接受最新的数据更新操作,memtable 结构里的数据查找则通过跳表 skiplist 或者 B+树实现。当 memtable 达到一定大小时,我们会进行 flush 操作,停止写入,再把 memtable 刷到磁盘上,变成静态文件即 SSTable。SSTable L0 层与 memtable 保持一致,从 L0 层到 L1 层则会进行归并排序。排序意味着 L1 层到 Lk 层都处于有顺序的状态,因此在每一层往下沉时,内部的数据会在物理上保持有序。每个数据再往下沉时,会进一步根据不同的 key range 来转化成一个

58、个互相不重叠的 SSTable。1.4 LSM-Tree 在 RocksDB 中的实现下图展示的是基于 LSM-Tree 数据结构进行二次开发的 RocksDB。当遇到写请求时,RocksDB 会先写一个 log,即 Write-Ahead Log(WAL)日志。当 memtable 还没有刷到磁盘上时,如果机器发生故障,Write-Ahead Log(WAL)日志则可以用于故障恢复。这是非常重要的功能。对 TDSQL 等金融应用场景数据库而言,可靠性永远排在第一位,写日志必须成功,才能把最新的数据插入到内存(memtable)里。memtable 还有阈值控制机制。在实际的生产环境中,一般将

59、阈值设置为 1G,到 1G 后则会冻结成 immutable memtable。当活跃的 memtable 被冻结成 immutable memtable 后,原来的 memtable 则可以清空内存,重新接受数据的写入。immutable memtable 则不会再接受写入,会准备被 flush 到磁盘上。随着越来越多的 immutable memtable 被 flush 到 L0 层,L0 层的文件个数会逐渐达到一个阈值,这时会触发系统的 compaction。由于 L0 层的 SST 文件之间呈现的是无序的状态,它们包含的 key 范围有可能重叠,这时需要把 L0 层的文件从磁盘上重新

60、读取并进行归类排序,进而往下生成 L1 层的文件。从 L1 层到 Ln 层(生产环境中一般配置成 7 层),所有的文件的 key range 已经互不重叠,且按照 key 的顺序进行排放。当我们要读取一个比较旧的数据,如果该数据不在内存也不在 L0 层时,我们会从 L1 层到 Ln 层去读取该 SST 文件。因为 SST 文件数量较多,在实际中我们会采用 bloom filter 来加快读取。1.5 LSM-Tree 查询基于 LSM-Tree 的查询可分为点查与范围查询两大类,对应的执行方式如下:第 1 章:数据库架构设计第 1 章:数据库架构设计2425 点查:从上往下进行查询,先查 me

61、mtable,再到 L0 层、L1 层。因为上层的数据永远比下层版本新,所以在第一次发生匹配后就会停止查询。范围查询:每一层都会找到一个匹配数据项的范围,再将该范围进行多路归并,归并过程中同一 key 只会保留最新版本。1.6 LSM-Tree 之空间/读/写放大LSM-Tree 性能的衡量主要考虑三个因素:空间放大、读放大和写放大。一是空间放大。LSM-Tree 的所有写操作都是顺序追加写,对数据的更新并不会立即反映到数据既有的值里,而是通过分配新的空间来存储新的值,即 out-place update。因此冗余的数据或数据的多版本,仍会在 LSM-Tree 系统里存在一定时间。这种实际的占

62、用空间大于数据本身的现象我们称之为空间放大。因为空间有限,为了减少空间放大,LSM-Tree 会从 L1 往 L2、L3、L4 不断做 compaction,以此来清理过期的数据以及不同数据的旧版本,从而将空间释放出来。二是读放大。假设数据本身的大小为 1k,由于存储结构的设计,它所读到的值会触发多次 IO 操作,一次 IO 意味着一条读请求,这时它所读取到的则是在后端所需要做大的磁盘读的实际量,已经远大于目标数据本身的大小,从而影响到了读性能。这种现象我们称之为读放大。为了减轻读放大,LSM-Tree 采用布隆过滤器来避免读取不包括查询键值的 SST 文件。三是写放大。在每层进行 compa

63、ction 时,我们会对多个 SST 文件进行反复读取再进行归并排序,在删掉数据的旧版本后,再写入新的 SST 文件。从效果上看,每条 key 在存储系统里可能会被多次写入,相当于一条 key 在每层都会写入一次,由此带来的 IO 性能损失即写放大。LSM-Tree 最初的理念是用空间放大和读放大来换取写放大的降低,从而实现较好的写性能,但也需要做好三者的平衡。以下是两种假设的极端情况。第一种极端情况是:如果完全不做 compaction,LSM-Tree 基本等同于 log 文件,当 memtable 不断刷下来时,由于不做 compaction,只做 L0 层的文件,这时如果要读一条 ke

64、y,读性能会非常差。因为如果在 memtable 里找不到该条 key,就要去扫描所有的 SST 文件,但与此同时写放大现象也将不存在。第二种极端情况是:如果 compaction 操作做到极致,实现所有数据全局有序,此时读性能最优。因为只需要读一个文件且该文件处于有序状态,在读取时可以很快找到对应的 key。但要达到这种效果,需要做非常多的 compaction 操作,要不断地把需要删的 SST 文件读取合并再来写入,这会导致非常严重的写放大。第 1 章:数据库架构设计第 1 章:数据库架构设计2627Nova-LSM 的简介与特性2.1 Nova-LSM 架构设计一览Nova-LSM 是基

65、于 LSM-Tree 结构的架构体系,其主要组件包括三部分:第一部分是写日志的组件,将 WAL 写成功后再往 LSM-Tree 的 memtable 中查询新的数据。第二部分是本身处理 LSM-Tree 写入的线程,其缩写为 LTC(LSM-Tree Component),代表着将该线程单独组件化。第三部分则是底层的存储,负责把接收到的上层 LTC 组件下发下来,并提供标准的文件接口。Nova-LSM 是一个存算分离的架构。上面处理 LSM-Tree 的是计算节点,它们要写磁盘时,需要用 flush 操作将 memtable 写到磁盘,compaction 时要先从存储节点读取上来,接着进行归

66、并排序并再次写回,再写下去时则由底下的分布式存储节点来进行。它的架构借用了当下较好的数据库产品理念。在计算节点和存储里,存储节点会按照彼此的功能去划分独立的线程池,每个线程池的线程数可以配置。这相当于在计算节点里将线程的功能分为四种:第一种线程与 client 相关,负责收发客户请求;第二种线程负责 RDMA、网络 IO 的相关操作;第三种线程与 compaction 相关,会不断扫描当前的 SST 是否符合 compaction 及推动 compaction 的进行;第四种线程与 Drange 相关,负责不断整理当前 Drange 的重排、重组织。该工作的主要亮点之一,是在于把原本 LSM-

67、Tree 这样的单机系统明确地划分出计算层、存储层,通过一定方式解决了在计算层本来会发生的停写、缓写现象。2.2 Nova-LSM 所解决的核心问题Nova-LSM 所解决的核心问题主要有两个。第一个核心问题是:基于 LSM-Tree 结构的存储系统,例如 LevelDB、RocksDB 等,都会不可避免地遇到缓写或者停写的问题。比如内存里的 memtable,在配置时最多可以写 8 个,因为写入多,需要全部 flush 到磁盘上。与此同时,当前 L0 层的 SST 文件非常多,L0 层即将开始做 compaction。但 compaction 会涉及到磁盘 IO,在还没做完时,就会阻塞内存中

68、的 memtable 对 L0 层 SST 进行 flush 的过程。当 flush 无法进行时,就会发生缓写,随着阈值的推进,在实在写不进时甚至会停写,这种现象体现在客户端就是请求掉零。为了解决 LSM-Tree 结构存储系统中的缓写、停写问题,该文章提出了两个解决办法:第一种方法是设计了存算分离的架构体系,具体如上图所示。该架构的重要作用之一,是把处理写入和处理磁盘 IO 的两大主力模块拆分,计算存储分离,哪个部分慢就为哪个部分增加节点以此来提高该部分的能力,这是比较亮眼的突破。第二种方法是引入了动态分区,即 Drange 机制。该机制的目的是为了让业务的写入压力,在 LTC 即计算层的

69、memtable 上进行区间划分,每个 range 都有自己的 memtable,通过区间划分,从而实现多个 range 之间进行并行 compaction。以 L0 层为例,我们可以把 L0 层变成没有互相重叠的状态,这时我们就可以对 L0 层进行并行的 compaction,可以加快 L0 层的文件的消化,从而减轻对 memtable flush 到磁盘上的过程的影响。第 1 章:数据库架构设计第 1 章:数据库架构设计2829第二个核心问题是:在这种方式下需要划分很多不同的 Drange,每个 range 都会增加一定的 memtable 数量,memtable 数量的增加会影响 sca

70、n 和 get 的性能。假设有一个主请求,在原来所有数据都写在一个 memtable 里的情况下,在读取时,索引只需要面向这个 memtable,再根据跳表进行 get,如果 get 到则可以马上返回。现在划分成不同的 Drange,memtable 数量增加,因此需要查找的 memtable 以及 L0 层的 SST 也会变多。解决办法是:实现了一个索引,可以查询到一个 key 在 memtable 或 L0 SST 中的最新值(若存在)。2.3 Nova-LSM 论文主要成果这篇文章将原本独立的单节点存储系统做成了一个存算分离、可以任意扩展的分布式架构。这种存算分离的架构体系,在扩展时对总

71、吞吐量、总的响应和请求的能力有显著提升。下图是对该效果的具体展示。左下角采用了最原始的配置,只有 1 个存储节点和 1 个计算节点,计算节点只配置了 32M 的内存,这也意味着 memtable 相对较少,在这种情况下它的总吞吐量只有 9k,相对较低。然后我们从纵轴来看,把计算能力向上扩展,通过垂直扩容把内存从 32M 变成 4G,这时总吞吐量已经从 9k 提高到 50k。但从图中也可以看到,这时性能曲线中间有空隙的地方越来越多,这些就是前面所提到的请求掉零。计算能力的加强意味着可以进行更多的写入,内存变大意味着 memtable 的数量变多,L0 层的 SST 文件生成速度也会加快。当 L0

72、 层的生成文件速度加快后,就会对存储层 compaction 的能力造成压力,因为它在默认配置下只有 1 个节点。这时虽然它的峰值已经提高到了 5k,但请求掉零的情况也更多了,即发生了停写。因为 L0 SST 已经来不及 compaction,这时只能等待,相当于计算节点在等存储节点。为了解决这个问题,我们需要对存储节点进行扩容,比如将 1 个存储节点扩到 10 个。这时可以明显看到总吞吐量从 5 万提高到了约 250 万。虽然某些地方请求也会骤降,稳定性还有待提高,但从整体上看,几乎已经没有请求掉零现象出现了。这也体现了传统单机单节点 LSM-Tree 存储系统与 Nova-LSM 之间的区

73、别。在传统单机单节点 LSM-Tree 存储系统中,如果计算能力非常好但是磁盘能力不够,这时很难在单节点上进行扩展。但在 Nova-LSM 中,如果发现哪部分能力不够就可以进行扩展,计算能力不够就扩计算节点,存储能力不够则扩存储节点。这也遵循了当前分布式数据库里比较常见的存算分离、计算层和存储层可以独立扩容的理念。Nova-LSM 若干重要设计3.1 LTC 和 StoCs 之间的写数据流程第一个比较重要的设计是 LTC 和 StoCs 之间的写数据流程。该流程展示的是:当在客户端发起写请求时,计算节点和存储节点是以怎样的方式将数据写进去的过程。首先是计算节点的客户端发起一个新的写请求操作。存

74、储节点在接收到该请求后,基于 RDMA 交互,它会在 buffer 区域分配一个内存区域,并且为这块内存和偏移量(当前哪块内存可以写)分配一个 id,告知 StoC。客户端接到响应后就会开始写数据,完成后会通知存储节点。存储节点接收到信号后,将数据持久化并且再告知客户端。上述流程是写一个数据文件即 SSTable。写完后,我们要以同样的流程将元数据文件更新。因为底层是分布式架构,需要知道哪些文件写在哪里以及每个 SST 的范围、版本号。第 1 章:数据库架构设计第 1 章:数据库架构设计30313.2 动态区间划分第二个比较重要的设计是动态区间划分。假设业务的请求范围为 0-1 万,当前有 1

75、0 个计算节点,将这 10 个计算节点的区间划分为 10 等份,比如第一个 key 的空间范围为 0-1000。在负责 0-1000 的计算节点里,它会再进行划分,这一层划分业务无感知。这就叫动态区间划分,简称 Drange。其作用主要有以下几点:首先,每个 range 都是一棵 LSM-Tree,按照数据区间,不同的 Drange 都有自己的 memtables。比如 0-1000 区间又可以划分为 10 个 Drange,10 个 Drange 之间的 memtable 相互独立。这样做的好处是这些 Drange 之间的 key 互不重叠,例如 0-100、100-200、200-300。

76、其次,在 Dranges 下还有一层 Tranges。如果发现 Drange 里的部分 range 比如 890-895 存在热点现象,而旁边的 range 并非热点,则可以用 Tranges 进行细粒度的复杂重均衡,实现动态均衡负载。最后,在此基础上,因为 Drange 的 key 范围互不相交,当 memtable 变成 immutable,不可再写后,它们需要独立地 flush 到磁盘上。这时,在 L0 层的 SSTable 来自不同的 Drange,它们之间的 key 完全不相交,我们就可以进行并行的 compaction。3.3 Compactions文章还将没有 Drange 划分

77、和有 Drange 划分两种情况进行了对比。在没有 Drange 划分的情况下,L0 的 compaction 无法很好并行。在这种情况下,如果遇到最坏的情况,L0 层的某一个 SST 有可能覆盖了整个 key 空间,假设 key 范围为 0-600,L0 层的 SST 文件的范围是 0-1000,当发生 compaction 时,它必须要跟其他 4 个 SST 做归并,这时不但要把 L0 层的其他 SST 全部读取比较一遍,还要把 L1 层所有的 SST 都读一遍再做归并排序。这时写放大会较为严重,意味着 L0 层到 L1 层的 compaction 会变慢,flush 也会变慢,甚至 fl

78、ush 不了时,前端就会出现缓写、停写现象。有 Drange 划分后,相当于 compaction 可以分开区间,如下方的示意图所示。在 0-100 区间,L0 到 L1 可以独立去 compaction,100-200 区间也可以独立去 compaction,可以较好地实现并行 compaction。而在原生的 RocksDB 里,只有从 L1 开始 compaction,才能进行并行 compaction 操作。如果协调者发现当前存储层的节点资源非常充足,compaction 操作可以由存储层主动发起,不需要计算层去发现当前有哪些可以做 compaction,这是这篇文章中提到的另一个想法

79、。至于考虑下沉的原因,因为文章并未深入展开,个人猜测主要是考虑到在这种架构体系里,存储层比较容易扩展,而计算层较难扩展。因为计算层相当于分库分表,如果扩展则会涉及到一定的路由重分布,需要告诉前端请求路由的变化。但存储层则非常容易扩展,如果能将这些非常耗时的操作放到存储层,可以极大地减少在计算节点跟存储节点之间数据的开销。存储层做完后,可以直接把更新后的元数据告诉计算层。3.4 索引查找以及 Scan 操作因为划分了很多不同的动态区间,memtable 的数量也会增加,意味着查询操作的耗时也会增加。所以要如何在原来的基础上维护好读性能?这篇文章提出了以下解决思路:每个 LTC 维护了一个 loo

80、kup index。如果这些数据存在于 memtable 和 L0 层的 SST 上,通第 1 章:数据库架构设计第 1 章:数据库架构设计3233过 lookup index 我们就可以快速查找到想要的数据。当某一个 L0 层 SST 被 compaction 到 L1 层时,索引上就会移除掉对应的 key。LTC 同时还维护了一个范围索引即 range index。因为知道每个 Drange 的范围,所以当一个 scan 请求所涉及到的 key,都可以在 memtable 和 L0 层 SST 中找到时,该范围索引就能快速响应 scan 操作。3.5 SSTable 的分布最后一个比较重要

81、的设计涉及到存储层。当某个 SST 文件要写到存储节点时,分布式系统首先要保证负载均衡,要保证数据避免单点故障不可恢复的场景。该文章提出根据一定策略,将数据文件即 SST 打散写入到多个存储节点里。考虑到存储成本,每个 SSTable 采用纠删码(Erasure Coding)的方式进行编码然后分布式存放。默认情况下对每个 SSTable 采用“3+1”的 EC 配置,将一个 SSTable 切分为 3 个数据块,根据一定算法,在这 3 个数据块里去计算出一个校验块,变成了“3+1”的形式。这种方式比传统的多副本可以节省更多空间。假设一个 SSTable 是 3M,这种“3+1”的方式最终所占

82、空间为 4M,并且能容忍一个节点的丢失,与占用 6M 空间的双副本方案拥有同样的故障容忍等级。而元数据文件因为体积比较小,所以直接采用多副本存储的方式,比如 1 个元数据文件可以写 3 个副本。Nova-LSM 性能效果展示 在本篇论文中,Nova-LSM 具有较好的性能数据表现。以自身调参测试为例,数据表明,Nova-LSM 可以通过调整不同的参数达到较好的扩展效果。文中分别使用 Uniform 均匀分布和 Zipf 分布来打散数据,存在热点(比如 80%的访问概率都集中在 20%的数据上)的情况下,实验结果数据表明,在读写比例、数据访问概率不一样的各种场景下,Nova-LSM 都能取得较好

83、的性能结果。下图所示为 Nova-LSM 在自身调参下几组不同参数的比较:第 1 章:数据库架构设计第 1 章:数据库架构设计3435下图展示了 Nova-LSM 自身扩展性的效果:下图所示为 Nova-LSM 吞吐量扩展性测试:以上测试是 Nova-LSM 自身不同参数下的对比。除此之外,该文章还将 Nova-LSM 与 LevelDB 以及 RocksDB 进行性能对比。在小数据场景下,Nova-LSM 的性能表现比 RocksDB 要更优异,特别在 Zipf 分布且热点数据存在、读写各占一半的情况下,测试出来的性能数据要比 RocksDB 高 4 倍。但随着数据量的扩大,在某些 work

84、load 下 Nova-LSM 的优势逐渐变得不明显。比如在上图中的(d)情况,一个 10 节点、2T 的数据库,RocksDB 将其分为 10 份,在这种写较多的场景下,Nova-LSM 与原生的 RocksDB 差距不明显。另外,上图中的蓝色数据项 RocksDB-tuned,它是 RocksDB 进行调优后产生的数据项,红色数据项则没有经过 RocksDB 调优,而红色项却取得了比蓝色项更好的性能数据。经过较多场景的验证,像 Nova-LSM 这种基于 LSM-Tree 结构的存储系统,实际上并不存在某一组参数能够让它在所有不同性质的 workload 下都取得较好性能。如上图(d)组,

85、即中间 100%写、均匀分布的测试组,RocksDB 经过调优后比没经过调优、用原始参数的对照组的吞吐量更低。因为 Nova-LSM 本身需要有非常多的调优参数,因此很难存在一套参数在所有的场景里都为最优。Nova-LSM 带来的启发和讨论一般情况下,基于 LSM-Tree 结构去进行优化的工作都面临以下问题读放大、写放大及空间放大。如果完全不做 compaction,LSM-Tree 将会退化为 Log 文件,此时读性能最差,需要扫描所有 SSTable 文件,但不存在写放大。如果通过 compaction 操作将所有 SSTable 文件维持为一个 sorted run,即始终保持所有 k

86、v 数据的全局有序,则退化为 sorted array,此时读性能最优,查第 1 章:数据库架构设计第 1 章:数据库架构设计3637询时只需读取一个 SSTable 中的一个数据块,但频繁的 compaction 操作会导致严重的写放大。所以我们不能走极端,需要在两者之间提出新的改进方法,在读放大、写放大及空间放大之间做好平衡。Nova-LSM 就是在这三个因素之间做取舍,它的设计原理之一是将原本单机单节点的系统用分布式组件化的方式,将原本一份代码里面的不同模块拆分出来,从而令每一个模块具有可扩展性,打破原先单机资源的限制。此外,该文章还创新性地提出,将不定期的 compaction 对磁盘

87、 IO 造成的短期冲击剥离出去。与此同时,该篇文章在实验验证及工程实践上仍有许多地方需要完善和优化。第一,实验使用的每个 KV 的默认大小是 1KB。根据原理判断,Drange 这种设计在该场景下比较占优势。在具体实现中,当一个 memtable 包含的 unique key 小于一定阈值时,不同的 Drange 之间会将 memtable 进行合并,其目的都是为了减少磁盘的写入。因此使用 1KB 这种不算小的测试数据,对它而言是占据优势的。而其他原生的 RocksDB 则需要不断地写磁盘,由于每一条 key 的体积都不小,1000 条可达到 1 兆,100 万条就能达到 1G,这时 Dran

88、ge 机制所带来的减少磁盘写入的优势就会被放大了。第二,Drange 和 Tranges 机制的设立,可以保证一个计算节点在不同的 memtable 写入之间存在动态均衡。从工业界落地的角度出发,这会涉及到较多的数据一致性保证。但文章并没有进一步论述数据的移动是否会造成没写或者双写。第三,工程实践上仍存在不少流程细节有待深入推敲,比如在 LTC 和 StoC 的写入流程交互中,文中提到先更新数据文件 block 再更新元数据文件 block 的流程,但如果在这两次写入中间发生了故障,如何处理?StoC 使用 erasure code 方式保证数据可靠性时,如何保证 n+1 个数据块和校验块写入

89、同时成功?故障时如何回滚?单个数据块发生丢失时如何发现以及重新生成?这些问题都值得我们进行推敲。第四,N 个 LTC 会负责 N 个区域数据的写入。比较传统的基于中间件的分布式数据库,会存在一个中间件,中间件知道其下的存储节点以及负责写入的节点分别负责哪一部分数据,此时路由变更的灵活性会存在一定限制。第五,所有的性能测试中基本只描述性能的最大值。最大值、最大吞吐量这些指标很重要,它代表着一个系统的能力上限。但在真实的业务场景中,除了能力的最大值,另一个非常重要的考察指标是稳定性。而 Nova-LSM 基于分布式架构,它所有的读写数据一直在进行网络交互。compaction 本身因为磁盘的 IO

90、,给总体性能带来了不稳定性,现在又加入了网络之间的开销,网络抖动就更加频繁,性能的抖动也是我们必须考虑的因素。Nova-LSM 并非只有理论,它在 LevelDB 的源码基础上新增了 2 万多行代码,实现了一套核心的设计,上图所示为其源码地址,感兴趣的同学可以尝试进行二次开发。第 1 章:数据库架构设计第 1 章:数据库架构设计3839探索分布式数据库的多级一致性及构建技术演进之路卢卫中国人民大学教授,博士生导师,中国计算机学会数据库专委委员。主要研究方向为大数据管理与分析,近五年来在 VLDB、ICDE、ATC、VLDB Journal、TKDE 等 CCF A 类会议或期刊上发表论文 10

91、 余篇,主持国家重点研发计划课题、国家自然科学基金面上项目等。主讲数据库系统概论、数据库系统实现等数据库相关课程,曾获首批国家级线上线下混合一流课程、北京市教学成果一等奖、中国计算机学会优秀博士学位论文奖,入选微软亚洲研究院青年教师铸星计划。近年来,凭借高可扩展、高可用等技术特性,分布式数据库正在成为金融行业数字化转型的重要支撑。分布式数据库如何在不同的金融级应用场景下,在确保数据一致性的前提下,同时保障系统的高性能和高可扩展性,是分布式数据库的一个核心技术挑战。针对以上分布式一致性的困境,中国人民大学-腾讯协同创新实验室研究提出“多级一致性”的事务处理理念。该技术包含严格可串行化、顺序可串行

92、化、可串行化三大隔离级别,可针对不同应用场景要求,极大地平衡性能与一致性要求,满足金融及各类企业场景的分布式事务处理需求。该项技术已应用于腾讯分布式数据库 TDSQL 产品中,确保 TDSQL 按需提供数据一致性,并确保数据无异常。TDSQL 是当前国内率先进入国有大型银行核心系统正式投产的国产分布式数据库,该项技术是其中的关键支撑。这次,中国人民大学教授、博士生导师卢卫老师为大家全面解锁分布式数据库的多级一致性及构建技术!背景从本质上看,数据库是长期存储在计算机内、有组织的、可共享的数据集合。当多个用户并发操作数据库时,事务调度的可串行化是并发控制的正确性理论。但该观点在当前却受到了挑战。D

93、aniel J.Abadi 在 2019 年发布的一篇博客中提到,以往学界普遍认为可串行化是数据库隔离级别的黄金标准,但经过研究,他发现实际上严格可串行化才是黄金标准。即在该理论中,可串行化仍存在一定的问题,只有严格可串行化才能做到没有问题。在过去,为什么可串行化不存在问题?原因有两方面:一是对集中式数据库而言,可串行化其实就是严格可串行化,两者之间并没有区别。二是对于分布数据库而言,如果数据库里有唯一的事务调度器或协调器,这两者之间也可等价。当来到去中心化的分布式数据库时代,我们希望分布式数据库产品可在全球部署。全球部署意味着范围更大,如果仍然依赖集中式调度,性能和可扩展性都无法满足应用的需

94、求,因此需要在系统当中安排多个事务协调者进行协调。第 1 章:数据库架构设计第 1 章:数据库架构设计4041回顾发展历程,20 年前的数据库的标注配置为业务系统+主库+备库。业务系统访问主库,主库通过同步协议使数据在主库和备库之间保持一致性。在这一阶段,集中式的 IBM 小型机、Oracle 数据库、EMC 存储(IOE)在处理小规模的数据场景时较为合适。但这种架构模式的问题在于,当数据量比较大或者业务场景比较密集时,集中式主库就会成为整个系统的负担。到了第二阶段,典型的做法是分库分表,将业务按照主库进行拆分,因为业务系统建立在主库之上,因此实现了业务的隔离,TDSQL 的早期版本也采用了这

95、种做法。这种做法的前提假设是数据/业务能够很好地进行切分,从而解决前一阶段业务不可扩展的问题。但当业务系统进行跨库访问时,就会带来新的问题。为了解决上述问题,我们来到了第三阶段,即去中心化的分布式数据库阶段。在该阶段,数据库中设置了更多的事务调度器,由调度器来对每个节点数据上的子事务进行事务提交,每个事务调度器都可以独立地去处理事务。但这也会产生新的问题,即不同协调者之间如何协调。问题与挑战我们以下图中的例子来说明分布式数据库中不同协调者之间如何协调的问题。假设有一个家庭账户,丈夫和妻子共用,都可以进行读和写。丈夫在 ATM 机上存了 100 块,存完后通知妻子,但妻子有可能看不到丈夫存的这笔

96、钱。因为这是一个多协调器的架构,设备 1 交由协调者 1 来进行协调,妻子发起的这个事务可能由另外一个协调者去发起,这就会出现协调者之间 AMG 时钟不统一的问题。第 1 章:数据库架构设计第 1 章:数据库架构设计4243该事务发起的时间虽然在 2:01 PM 后,但因为协调的时间偏慢,所以此时 1:59 PM 的这个时钟去读 2:01 PM 的时间戳提交的这个数据,就会出现读不到的情况。形象地说,即有两个协调器,其中一个协调器执行了事务 T0、T1,T1 事务已经提交成功。这时协调者 2 发起了事务 T2,当 T2 查询余额时,我们发现时钟比 T1 提交的时钟来得小,所以读不到 T1。但实

97、际上,是先执行 T0 再执行 T2 再执行 T1,属于可串行化。但这又会跟前述提到的执行相违背,因为既然 T1 已经提交,T2 理应可以读到,但结果没有读到。因此 Daniel J.Abadi 的可串行化存在一定的问题,读不到最新数据。这个问题的本质是保序,而严格可串化的本质是线性一致性加上可串行化。从事务角度来看,根据线性一致性要求,如果 T0 事务已经结束,T1 才开始,则 T1 要读到 T0 的写;同理,T1 已经完成了 T2 才开始,T2 要读到 T1 的写。虽然这里的 T0、T2、T1 是可串行化,但违背线性一致性的要求,只有 T0 T1 T2 时才是正确的,这就是保序。因此,这里的

98、实时序就是 T0 结束后开始 T1 事务,T0 排在 T1 的前面;T0 完成后 T2 才开始,T0 排在 T2 的前面;T1 结束后 T2 才开始,T1 排在 T2 的前面。因此核心理念就是保序,即在原来可串行化全序的基础上,对可串行化的序做约束,这个约束是线性一致性所造成的。严格串行化虽然能保证数据的准确性,但也带来了较多的问题。以 Google Spanner 为例,Google Spanner 支持严格可串行化,但是严格可串行化要求有一个原子钟,或者有一个中心授时器(本质上是因为协调器和协调器之间缺少一个协调),因而导致性能较低,难以被广泛应用于实际业务场景中。第 1 章:数据库架构设

99、计第 1 章:数据库架构设计4445多级可串行化建模基于上述情况,我们希望可以找到一个中间环节,在一致性上比可串行化级别高、比严格串行化级别低;在性能上接近可串行化、优于严格可串行化。针对这个需求,我们提出了多级可串行化建模,本质是在可串行化的基础上加了序。线性一致性是并发系统中一致性最强的,比它弱一点的有顺序一致性、因果一致性、写读一致性、最终一致性等。我们尝试将可串行化与它们进行结合,最终发现只有可串行化和线性一致性以及可串行化和顺序一致性可以实现结合。因为可串行化要求全序,但因果一致性不要求全序,因此无法结合。我们的做法是将可串行化+线性一致性,从而得到严格可串行化;可串行化+顺序一致性

100、,从而得到顺序可串行化。所以我们提出了严格可串行化、顺序可串行化、可串行化这三个隔离级别。多级可串行化实现的核心理念就是保序。我们定义了五个序:实时序,即原来的线性一致性要求。程序序,比如代码中的 session order,session 连接后,事务之间就变成了 T0,T0 提交后才能提交 T1,这就是程序序。写读序,即如果 T2 读取了 T1 的写,T1 必定排在 T2 之前。因果序,指写读序和程序序之间形成的闭包。写合法,假设有一个 x 数据项,T1 写了数据项 x1,T3 写了数据项 x2,但如果 T2 读了一个 x,就必须要求 T2 要紧跟 T1,因为它不能紧跟在 T3 后;如果它

101、排在 T3 后,则它读的应该是 x2,因此这时 T1 和 T3 形成了一个序,要求 T2 要排在 T3 前。第 1 章:数据库架构设计第 1 章:数据库架构设计4647有了序后,我们重新定义了事务的可串行化理论,即可串行化等于写读序的传递闭包+写合法;可串行化+顺序一致性,即写读序+程序序的传递闭包,再加写合法;严格可串行化就是写读序+实时序的传递闭包,加写合法。因此可以理解为所有的一致性模型就是保序。该理论成果已经应用于腾讯分布式数据库 TDSQL 产品当中,使得 TDSQL 成为全球范围内首个能够具备严格可串行化、顺序可串行化、可串行化三大隔离级别的国产分布式数据库,还可针对不同应用场景要

102、求,极大地平衡性能与一致性要求,满足金融及各类企业场景的分布式事务处理需求。并发控制算法并发控制算法-双向动态时间戳调整的实现如下图所示。图中有两个协调者 P1 和 P2,协调者 P1 有两个事务 T1 和 T3,协调者 P2 有 T2 和 T4。我们先定义顺序,T1 和 T3 之间有一个 session order 或 program order,T2 和 T4 也存在一个 program order,我们将它 preserve 出来。其中还存在写读序,像 T1 和 T4 之间,T1 写了 x1,T4 读了 x1,此时就存在一个写读序,所以要把 T1 和 T4 的 order preserv

103、e 出来。同时还存在写合法,因为 T3 读了 y 数据项,然后 T2 写了 y 数据项,但是基于可串行化理论,R3 读取的是 y0,没有读取到 y2,如果读到 y2,这时 T3 就必须排在 T2 后。因为此时读不到 y2,要排在 T2 前面,因此 T3 和 T2 之间存在写合法。在整个执行过程中,我们要保证必须存在保序。主要思想是每次事务提交时,都需要判断能否违背事务的先后顺序。比如 T1 开始提交,因为 T1 只包含自身,我们将它放到队列中时不需要回滚。T2 提交时,T2 和 T1 之间没有序,但 T1 和 T3 之间以及 T2 和 T3 之间都分别存在一个序,因为此时存在 y 数据项,所以

104、只有 T1、T3、T2 这样的序才能保证可串行化,否则 T 必须进行回滚。最后为保证协调器间能进行协调,我们还需要引入混合逻辑时钟,来保证因果序。第 1 章:数据库架构设计第 1 章:数据库架构设计4849实验评价我们以下图中的实验为例,来说明可串行化、严格可串行化和顺序可串行化之间的关系。如果在局域网情况下,在正确性方面,严格可串行化跟可串行化性能基本一致。但在广域网情况下,严格可串行化、顺序可串行化和可串行化之间的性能差异较大,所以导致在广域网上很难实现严格可串行化。如图所示,BDTA 算法比现有的算法如 MAT、SUNDIAL 等快了将近 1.8 倍,主要原因是 BDTA 中保序,但如果

105、用前面的方法实现并发控制,就会造成大量事务的回滚。总结与讨论本文提出了提出了面向分布式数据库的多级可串行化模型,将并发系统中的一致性要求结合到可串行化中,实现了多级可串行化原型系统,保证了去中心化的事务处理机制,并设计了双向动态时间戳调整算法(BDTA),可以在统一系统架构下支持多个可串行化级别。该技术已应用于腾讯云数据库 TDSQL 中,确保 TDSQL 无任何数据异常,且具备高性能的可扩展性,解决了分布式数据库在金融级场景应用的最核心技术挑战,使得国产分布式数据库实现在金融核心系统场景的可用。基于此,TDSQL 是当前国内唯一进入国有大型银行核心系统正式投产的国产分布式数据库。第 1 章:

106、数据库架构设计第 1 章:数据库架构设计5051敏态扩展,灵活应变!TDSQL 新引擎 TDStore 技术探索韩硕腾讯云数据库高级工程师,北京大学博士研究方向包括分布式数据库、图数据库、存储引擎优化等,在 SIGMOD 等数据库领域顶级会议上发表多篇论文。2019 年博士毕业后加入腾讯计费,主要负责 TDSQL 存储相关的研发。随着产业互联网发展,传统产业中业务爆发式增长与无限增长趋势愈加明显与普及。业务敏态发展对底层基础技术提出了具备敏态能力的要求。TDSQL 是腾讯云企业级分布式数据库,具有完全兼容 MySQL、分布式事务全局一致性、弹性扩缩容、智能调度管控、在线表结构变更等关键特性。金

107、融级分布式数据库 TDSQL 新引擎 TDStore 针对产业技术趋势需求,聚焦适配金融级敏态业务,在频繁进行模式变更、数据流量陡增等敏态场景下,实现弹性伸缩变更、对业务透明无感知。今天,腾讯云数据库高级工程师韩硕带领大家了解金融级分布式数据库 TDSQL 新引擎 TDStore 的特性与设计理念,揭秘金融级分布式数据库 TDStore 引擎弹性伸缩特性在存储层的实现原理。背景TDSQL 诞生自腾讯内部百亿级账户规模的金融级场景,目前已在众多金融、政务、电商、社交等客户应用案例中奠定了金融级高可用、强一致、高性能的产品特性和口碑,国内前 20 的银行近半都在使用 TDSQL,有力推动了国产数据

108、库技术创新与发展。随着业务场景的不断增长和复杂化,业务形态、业务量的不可预知性增大,业务的敏态发展对数据库底层技术提出了具备敏态变更能力的要求。目前已投产的 TDSQL 在应对业务的敏态变更时,有三个痛点需要改善:兼容性:在建表的过程中需要手工去执行 Shard key。运维:随着业务的不断增长,规模越来越大,面对扩容场景,目前还需要 DBA 来进行发起,导致部分输入会被阻断,使得升级过程不够平滑。模式变更:目前在线表模式变更更多依赖于 PT 等外围工具。因此,针对以上业务痛点,我们在 2021 年发布了 TDSQL 新引擎 TDStore,并于 2022 年 8 月开始投入公有云商业化运营。

109、TDStore 引擎针对不断变化的敏态业务,实现 PB 级存储的 Online DDL,可以大幅提升表结构变更过程中的数据库吞吐量,有效应对业务变化;其独有的数据形态自动感知特性,使数据能根据业务负载情况实现自动迁移,打散热点,降低分布式事务比例,获得极致的扩展性和性能。第 1 章:数据库架构设计第 1 章:数据库架构设计5253综上所述,TDSQL 新引擎 TDStore 的愿景,是希望可以让业务能够像使用单机数据库一样去使用分布式数据库。TDSQL 新引擎 TDStore 技术亮点 TDSQL 新引擎 TDStore 具备以下功能特性:一是 MySQL 兼容、分布式、低成本:兼容原生 My

110、SQL 语法,具有事务的全局一致性,无感知扩缩容。纯分布式引擎,数据以 KeyRange 来做组织和路由,业务层不再需要做分库分表。存储引擎模块采用 LSM-tree 结构,具备良好压缩比,适合大规模数据量的业务。二是高可扩展、计算/存储资源弹性扩缩容:计算层为多主模式,每个 SQLEngine 都是完全对等的节点,可读可写;无状态化设计,可根据业务流量灵活增加或减少计算层节点,从而适应业务的峰值或低谷。存储层可根据存储量需求,做弹性、对业务无感知的扩缩容,通过数据的添加或者迁移自动打散数据分布热点,进一步降低分布式事务比例,实现线性扩展比。三是全局读一致性:通过管控模块 TDMetaClus

111、ter 给计算节点、存储节点分配全局统一的事务时间戳。存储层基于 MVCC 和事务时间戳来做全局一致的可见性判定。四是 Online DDL:支持在线表模式变更的常见操作。支持在线加减索引。支持大部分 DDL 操作以 Online 方式执行TDSQL 新引擎 TDStore 架构设计TDSQL 新引擎 TDStore 架构分为三个核心模块,分别为计算模块 SQLEngine、存储模块 TDStore、管控模块 TDMetaCluster。3.1 计算模块 SQLEngine 第 1 章:数据库架构设计第 1 章:数据库架构设计5455 每个 SQLEngine 节点完全对等,都可读可写,且为无

112、状态节点,SQLEngine 之间会通过一定的方式来同步在线表模式变更的信息。TDSQL 完全兼容 MySQL8.0。我们通过移除各自有状态化的数据信息,从而实现无状态化设计,并将多线程框架替换为协程框架,实现更好的并发性能。3.2 存储模块 TDStore存储模块是 share nothing 架构,底层实现则是基于 LSM-tree 结构,这意味着每个数据分片的基本数据单元都有自己独一无二的 owner。我们以 Key Region 作为划分标准,将数据划分成一个个分片Region。同时为了满足灾备的需求,Region 的多个副本通过分布式一致性协议Raft 来提供一主多备方案。存储模块的

113、每个节点都会分布多个 Region,Region 有多个副本,在不同的 TDSQL 节点之间会随着数据量的变化进行性能、数据量的自动均衡。在自动均衡的过程中,需要上层 TDMetaCluster 的管控模块来配合完成。TDMetaCluster 模块通过下发基本的基于 Region 的任务,比如分列、合并、迁移、切主等,来实现弹性扩缩容。3.3 管控模块 TDMetaClusterTDSQL 新引擎 TDStore 的管控模块主要负责三方面工作:高效地生成和下发全局唯一的事务时间戳;管理各模块的元数据以及 Region 的路由信息;以 Region 为基本单位进行负载均衡和存储均衡调度。调度需

114、要考虑 Region 的热点,尽量将热点 Region 打散在不同存储节点上,还需要兼顾对性能的影响,通过对 Region 的合理划分和调度,尽可能将跨 Region 的 2PC 事务转化为 1PC,降低事务延迟。第 1 章:数据库架构设计第 1 章:数据库架构设计5657 TDSQL 新引擎 TDStore 分布式事务TDSQL 新引擎 TDStore 的分布式事务模型,遵从了 2PC(2 阶段)提交协议,来保证分布式事务提交的原子性。对于涉及多个 Region 的分布式事务,所有参与者要么全部提交成功,要么全部回滚,不允许半提交的发生。与经典的 Percolator 模型相比,我们的实现方

115、案做了部分改动。Percolator 模型的冲突检测、MVCC 机制等都是在上层维护,由计算层作为两阶段提交的协调者。在 TDSQL 新引擎中,我们将协调者下沉到存储层,即在多个 Region 参与者中选取一个作为协调者,由 TDStore 保证事务提交的原子性,同时将未提交事务数据的缓存也下沉到存储层 TDStore。这样做的好处是降低了数据落盘的次数。在 prepare 阶段,Percolator 方案需要将锁数据落盘,在 commit 阶段再把数据项落盘。但在我们的下沉方案中,数据项和锁缓存在 TDStore 中,只有在 commit 阶段将数据落盘一次,有效降低了 IO 开销。在实现层

116、面,我们对存储层 TDStore 的改动包括两方面:添加了对数据项加 memory lock 的逻辑,实现 prepare 时 lock、commit 时 unlock 的效果,相当于在存储节点上实现了 2PC 协议的协调者和参与者逻辑。历史版本的清除需要计算全局最小活跃事务时间戳,不能只考虑单个 TDStore,我们借助 compaction 机制进行自动的过期版本数据清除,不需要额外的 gc 模块。在分布式事务总体流程中,管控模块会周期性地去推动全局最小时间戳,即全局快照点,从而保证读的全局一致性以及过期数据的自动回收。在事务的开启阶段,首先 SQLEngine 会接收到来自 Client

117、 端的 SQL 语句,开启一个事务。开启事务后,我们会从管控模块拿到事务的开启时间戳 Start-ts,这时它会留意自身缓存中有没有 SQL 语句对应要查询数据范围 Region 的路由信息。如果没有,就会从 MC 重新拉入最新的路由。SQLEngine 会将事务的读写请求发送到 Region 的 Leader 所对应的 TDStore 上,再由 TDStore 的 Leader 节点作为两阶段事务的协调者来开启两阶段事务。进入到准备阶段后,我们会拿到一个 prepare-ts,再基于 prepare-ts 做并发事务的冲突检测。当所有的参与者都 prepare 就绪后,会生成一个 commi

118、t-ts 作为事务提交的时间戳。如果事务提交过程中发生了切主、分裂等 Region 信息的变更,这时我们需要重新去更新一下最新的 Leader 消息以及 Region 参与者列表,从而保证事务提交过程中的完整性。当事务提交后,我们会把结果通过 SQLEngine 再返回到客户端。TDSQL 新引擎 TDStore 无感知扩缩容无感知扩缩容是 TDSQL 新引擎 TDStore 最重要的特性,其通过存储层 TDStore 和管控模块 TDMetaCluster 的配合来完成。TDMetaCluster 会自动监测到每个 TDSQL 节点上数据分布的信息,包括查询热点的信息,这些都是通过 TDSt

119、ore 上报给管控模块后再进行处理。以扩容为例,数据调度通过 Region 来进行,主要分为三个任务,即分裂、迁移和切主。5.1 无感知扩缩容-分裂下图中有三个 TDStore 节点,属于一主两备三副本的 Region 的数据分布。假设 Region1 的数据量达到一定阈值,这时 MC 监测到 Region1 的数据量超过阈值,就会判定 Region1 需要进第 1 章:数据库架构设计第 1 章:数据库架构设计5859行分裂,将 Region1 分裂成 Region1 和 Region4。MC 会向 TDStore1 节点发送信息,让它在 Region1 节点下发一个分裂的任务请求。TDSto

120、re1 会计算 Region1 的合适分裂点,但合适分裂点的选择需要考量很多因素,比如希望 Region 分裂后的数据尽可能均匀或希望分裂后的新 Region 和老 Region 之间的事务热点尽可能均匀,针对不同的场景,我们也会有不同的分裂策略。整个分裂流程也遵循两阶段提交,MC 作为协调者,Region 的所有副本作为参与者,保证全员成功或者失败,避免部分副本分裂成功、部分副本分裂失败的半提交状态。分裂过程中,TDStore Region Leader 会计算出分裂点,将 Region 分裂地尽可能均匀。需要注意的是,分裂流程只是在元信息层面对 Region 一分为二,对于底层数据没有变动

121、。我们以 Split-key 的节点,来重新计算老 Region1 的区间。分裂结束后,数据在 TDStore 节点之间并无明显变化,只是多了一个 Region。这时并没有实现存储数据的扩容。5.2 无感知扩缩容-迁移迁移是让数据扩展出去的最关键操作。如下图所示,我们把数据分成更多的 Region 后,会将部分 Region 迁移到新增的存储节点如 TDStore4 上。该例子是把 Region2 进行迁移,迁移的整体流程使用 Raft 协议增减副本的方式,即先增加一个副本、再减少一个副本。比如 Region2 最开始是 3 副本,在 TDStore2 上的节点是 Leader,另外两个节点是

122、 Follower。在迁移时,我们先在 TDStore4 上创建一个 Region2 的 Follower 新副本,创建的新副本里的数据是空的,这时会触发 Raft 协议中快照安装的流程,会将快照的数据信息从 Leader 节点上传到新创建的副本上。第 1 章:数据库架构设计第 1 章:数据库架构设计6061在增加一个副本后,即由 3 副本变成了 4 副本,后续还要恢复成 3 副本,因此需要再删掉一个旧副本,即把 TDStore1 上的 Region2 Follower 节点删掉,这样就实现了 Region 副本的数据迁移,Region2 副本的数据也迁移到新的 TDStore4 节点上。在迁

123、移过程中,Region 分裂只是在元数据层面,最关键的是要把数据通过快照的方式传输且装载到新的节点上,这就是性能瓶颈。为了突破这个瓶颈,我们在迁移的过程中结合 Raft 协议以及 LSM-tree 结构,具体的实现步骤如下:根据上文例子,我们需要把 Region2 迁移过去,MC 会不断下发迁移任务,慢慢使三个节点之间的数据规模达到均衡,将其他的三个 Region 中的某个副本逐渐迁移过来。迁移结束后,这 4 个 Region 在新增的 TDStore4 以及之前的三个 TDStore 之间,Region 的数量都已经达到均衡。数据存储在不同的 TDStore 节点上达到均衡,并不意味着负载就

124、真正均衡。因为 TDStore 是基于一主多备的形态,而 Raft 协议的原则是读写请求都会由 Leader 节点来承担,TDStore1 上现有两个 Leader,分别是 Region1 和 Region4 的 Leader,但 TDStore4 没有 Leader。假设 4 个 Region 上的读写请求的流量都一致,这时 TDStore1 上承载的读写请求会多于 TDStore4,因为 TDStore4 是一个单纯的 Follower 节点,它只需要从各个 Leader 节点上同步最新的 log 以及事务数据信息。值得一提的是,TDSQL 新引擎 TDStore 在迁移过程中是天然地对事

125、务透明,迁移只是 Follower Region 副本在不同 TDStore 节点上的变动,对事务完全不阻塞。第 1 章:数据库架构设计第 1 章:数据库架构设计62635.3 无感知扩缩容-切主出现负载不均衡的原因是 Region1 上的 Leader 较多。如果要达到近似的负载均衡,就依赖于下一个任务切主。切主的目的是均衡 Region 在不同 TDStore 存储节点的 Leader 分布,从而达到调整读写事务的热点,实现最终负载的均衡。在切主过程中,部分事务还在活跃阶段,这时我们需要进行界定,因为事务的推进都是由 Leader 节点来进行,我们以 2PC 的准备阶段作为分界点。如果进入

126、 prepare 阶段的事务,我们就会将事务的上下文信息在切主过程中同时传递到它的新 Leader 上,由新 Leader 推进事务后续提交的执行流程;如果还没有进入到 prepare 阶段的两阶段事务,我们就会把这个信息做拷贝,传递过来。5.4 Region 调度与事务并发TDSQL 新引擎 TDStore 最重要的设计目标,是让业务在敏态变化时无感知。因此在数据进行分裂迁移时,不影响业务事务的正常进行就显得尤为重要。但数据分裂迁移涉及到 Region 元数据的变化以及底层数据的变动,要在工程上进行实现是一个较大的挑战。第 1 章:数据库架构设计第 1 章:数据库架构设计6465迁移场景对于

127、事务执行透明,而分裂和切主任务都是在 Leader 上执行,因此存在事务的并发,对此我们需要保证事务的生命周期跨越分裂和切主流程。以分裂为例,图中的 Region1 分裂前的数据范围是 A 到 Z,这时它上面有活跃的事务 T,之前写入两条数据,分别是 put A=1、put H=5。分裂前事务 T 在事务缓存中保留 A=1、H=5 的数据。我们以 G 为分裂点进行分裂,把它分裂成 Region1、Region2,其中 Region1 的范围变成了 A 到 G,Region2 的范围变成了 G 到 Z。我们发现 H 数据项在分裂后属于 Region2。在分裂过程中,如果事务变成一个跨 Regio

128、n 的事务,就会把它所属的 Region 上的数据项 H=5 分裂传输到新的 Region 节点上。即 Region 上活跃的私有事务,在分裂过程中要做事务上下文数据的迁移。迁移后,除事务信息的分裂和传输,事务也从最开始只涉及到一个 Region,优化到一个一阶段提交。分裂后,也由单一的一阶段事务变成两阶段提交的事务。因为在分裂过程中事务没有提交,因此在后续的提交过程中,就需要进行参与者列表的更新。这时在事务准备和提交阶段,我们会检查旧 Region 有没有分裂。如果 Region 发生分裂,我们还需要更新事务参与者列表,从而使得事务在最终提交时,能够让新增的参与者 Region2 也能一同提

129、交,保证分布式事务的原子性。TDSQL 新版本数据存储与迁移本部分主要介绍存储节点中数据库具体的存储步骤以及突破迁移的瓶颈实现方式。下图展示了 TDStore 中数据的持久化流程。持久化数据可分为 Raft log 和 kv 数据,分别对应左侧的 Raft log 存储和右侧的 kv 数据存储。假设我们开启两个 DB 实例,事务写入的数据以 Raft log 的形式同步到 Region 的不同副本上,当一条 log entry 在多数副本上都落盘到 Raft log file 中,就认为这条 log entry 已提交,将它进行 apply 执行或者 replay 回放,其中的事务数据会暂存在

130、 write batch 中。当事务提交时,write batch 中的数据会写入到 memtable 中,如果事务失败 abort,write batch 中的数据就会直接丢弃。当 memtable 达到阈值时,就会变为一个不可变的 immutable memtable。当 immutable memtable 的数量满了以后,就会触发 flush 操作,将 memtable 中的数据落盘到 LSM-tree 的 L0 层,形成 SST 文件。随着 L0 层的 SST 文件数量增加,会触发后台 compaction 操作,来维持 LSM-tree 的形状。总体而言,越新的数据通常处于 LSM

131、-tree 的上层,越老的数据,通过 compaction 不断往 LSM-tree 的下层沉积。第 1 章:数据库架构设计第 1 章:数据库架构设计6667无论是 Raft log file 还是 SST 文件,都不应该允许数据的无限增长,需要对过期数据进行及时清理。对于 Raft log file 中持久化的 log entry 数据,通过 save snapshot 即生成快照的形式进行清理。一个 snapshot 由原数据和状态机存储数据构成。当一个 snapshot 生成以后,它所覆盖到的 Raft log entry 都可以被安全地清理掉。当发生重启时,如果发现已存在 snapsh

132、ot,会先加载该 snapshot,再去回放剩余的 Raft log,避免每次都从头回放所有 Raft log。对于 Rocksdb 磁盘中的数据,会通过 compaction 操作来合并和清理过期版本数据。整个集群会维护一个全局最小 seqno,在 compaction 过程中,对于早于全局最小 seqno 的数据,仅需要保留最新的一条。下图右边的例子中,L0 的两个 SST 合并到 L1,全局最小 seqno=s5,因此 SST_0_1 中的 put:k1-v2-s4 和 put:k1-v3-s1,只需要保留 s4 这条。我们可以发现,一个 Region 中的持久化数据由三部分组成,分别位

133、于 Raft log db、snapshot data 和 data_db 中的 SST 文件。其中 Raft log 和 snapshot data 两者是互补的关系,因为 Raft log file 中的部分 log entry 被存到 snapshot data 中,所以这部分 log entry 可以从 Raft log db 删除。SST 中则保存了 Region 所有通过 flush memtable 落盘的数据,这意味着 SST 与 snapshot data 中的数据存在重复。为此,我们想到了可以用 flush memtable 作为 save snapshot 的触发点。当

134、memtable 中所包含的 Raft log 中的数据落盘到 SST 时,进行一次 save snapshot。这次 save snapshot 只写一个 snapshot meta,即包含了 snapshot 位点元信息,但不再生成 snapshot data,因为该 snapshot data 中的数据已经随着 flush memtable 存储在 SST 中。这样就可以节省掉生成 snapshot data 的开销,让 save snapshot 过程变得轻量。第 1 章:数据库架构设计第 1 章:数据库架构设计6869下面介绍数据是如何以 Region 为单位在不同的 TDStore

135、 之间进行迁移。下图是一个基于快照传输和装载的 Region 数据迁移的流程。第一步:Raft 协议触发由 Leader 发向 Follower 的 Install Snapshot RPC,告知 Follower 所需同步的下一条 Raft log 在 Leader 上已被清理,因此需要开启 install snapshot 流程。第二步:Follower 在接收到 install snapshot RPC 请求后,向 Leader 发送一条 download snasphot data 的 RPC,用于向 Leader 请求 Region 的 snapshot data。第三步:Leade

136、r 会在 LSM-tree 中找到该 Region 的范围对应的 SST,每个涉及到的 SST 会生成一个 SSTIterator。我们在上层实现一个 MergeIterator,将这些来自不同 SST 但是属于同一个 Region 中的 kv 数据进行合并排序,顺序按照 key 从小到大的顺序,对于相同的 user key,seqno 更大的即版本更新的排在前面。再通过流式 RPC 将 Region 的这些 kv 数据按顺序发送到 Follower 上。Follower 接收到数据后,按照顺序写入到 external SST 中,每写满一个就会开启一个新的 SST。第四步:Follower

137、将这些 extrenal SST 注入到 LSM-tree 中合适的位置。合适位置指的是在不与已有 SST 发生范围重叠的前提下,尽量往下面的层去放。例如图中的第二个 external SST,它的范围是 151 到 176,与 L0 层、L1 层的 SST 都不重叠,但是与 L2 层的 0 到 175 这个 SST 发生重叠,我们就会把它放在 L1 层。原因是为了让 isnstall snapshot 迁移过来的 kv 数据可以覆盖掉其残存的数据,从而保证被访问到的数据版本是最新的。这就是数据层面迁移的全流程。总结与未来规划下一阶段,TDSQL 新引擎 TDStore 将会实现以下三方面特性

138、:数据地理位置感知,进一步降低分布式事务比例,从而使性能进一步提升。对等架构,将计算模块和存储模块做对等架构的设计和一定程度上的重构,主要是为了适用于公有云上的小规模集群,达到更高的资源利用率。管控模块实现更加智能化的负载均衡调度策略。作为领先的国产分布式数据库,TDSQL 致力于将数据库打造成一种服务,用户随取随用,把简单留给用户,把复杂留给自己。未来,TDSQL 将持续推动技术创新,释放领先技术红利,继续推动国产数据库的技术创新与发展,帮助更多行业客户实现数据库国产化替换。第 1 章:数据库架构设计第 1 章:数据库架构设计7071Redis 如何实现多可用区?刘家文腾讯云数据库高级工程师

139、先后负责 Linux 内核及 redis 相关研发工作,目前主要负责腾讯云数据库 Redis 的开发和架构设计,对 Redis 高可用,内核开发有着丰富的经验。在如今的业务场景下,高可用性要求越来越高,核心业务跨可用区已然成为标配。腾讯云数据库高级工程师刘家文结合腾讯云数据库的内核实战经验,给大家分享 Redis 是如何实现多可用区,内容包含 Redis 主从版、集群版原生架构,腾讯云 Redis 集群模式主从版、多 AZ 架构实现以及多 AZ 关键技术点,具体可分为以下四个部分:第一部分:介绍 Redis 的原生架构,包含主从版及集群版;第二部分:介绍腾讯云 Redis 架构,为了解决主从架

140、构存在的问题,腾讯云使用了集群模式的主从版。其次为了更好的适应云上的 Redis 架构,引入了 Proxy;第三部分:分析原生 Redis 为何不能实现多 AZ 架构的高可用以及腾讯云是如何实现多可用区;第四部分:分享实现多可用区的几个关键技术点,包含节点部署、就近接入及节点的选主机制。Redis 原生架构Redis 的原生架构包含主从版及集群版。主从版是由一个 master,多个 slave 组成。主从的高可用一般采用哨兵模式。哨兵模式在节点故障后,能自动把相应的节点进行下线处理,但是哨兵模式无法补节点。为了配合补从,需要管控组件进行协助,因此一般会将监控组件和管控组件配合完成主从版的一个高

141、可用。无论是哪种方式,主从版的可用性依赖外部的仲裁组件,存在恢复时间长及组件本身的高可用问题。其次主从版还会导致双写的问题及提主有损的功能缺陷。第 1 章:数据库架构设计第 1 章:数据库架构设计7273Redis 的集群版中的每一个节点相互独立,节点之间通过 Gossip 协议来进行通信,每一个节点都保存了集群中所有节点的一个信息。集群版的数据基于 slot 进行分片管理,slot 总共有 16384 个,节点的 slot 并不是固定的,可以通过搬迁 key 的方案来完成 slot 的迁移,有了 slot 的搬迁功能,集群版可以实现数据均衡及分片的动态调整。Redis 集群版内部集成了 Go

142、ssip 协议,通过 Gossip 协议能完成节点的自动发现和自动成灾的功能。自动发现是指一个节点要加入到集群中,只需要加入集群中的任何一个节点,加入后,新节点的信息会通过 Gossip 推广到集群中的所有的节点。同理,当一个节点故障后,所有节点都会把故障信息发送给集群其它节点,通过一定的判死逻辑,它会让这个节点进行自动下线,这个也就是 Redis 集群版的自动容灾功能。为了说明单可用区是如何部署的,我们需要进一步了解 Redis 集群版的自动容灾。自动容灾总共分为两个步骤,第一个就是我们的判死逻辑,当超过一半的主节点认为该节点故障,集群就会认为这个节点已经故障。此时从节点会发起投票,超过一半

143、的主节点授权该节点为主节点时,它会将角色变为主节点,同时广播角色信息。根据上面这两点分析,不难发现 Redis 集群版有两个部署要求,一个是主从不能同机,当主从同机的机器故障后,整个分片就相当于已经故障了,集群也就变为一个不可用的状态。其次是我们的节点数不能超过分片数的一半,这里要注意的是节点数,而不是只限制主节点数。上图的右边部分是错误部署方式,在集群节点状态没有变化的情况下,是能够满足高可用的,但集群的主从发生切换后,一个机器上的主节点已经超过大多数,而这个大多数机器故障后,集群无法自动恢复。因此三分三从的集群版,要满足高可用总共需要六台机器。腾讯云 Redis 架构为了解决双主的问题及支

144、持无损提主的操作,腾讯云上使用了集群模式的主从版。实现集群模式的主从版,先要解决三个问题:第一个是集群模式需要至少 3 个投票(仲裁)节点的问题,由于主从版本只有一个 Master,为了达到 3 个仲裁节点,我们引入了两个 Arbiter 节点,Arbiter 只有投票权,不存储数据,通过这个改造后,就能够满足了集群版的高可用。第二个是多 DB 问题,由于腾讯云上引入了 Proxy,减少了对多 DB 管理的复杂,因此可以放开单 DB 限制。最后一个是需要启用跨 slot 访问,在主从版中,所有的 slot 都在一个节点上面,不存在跨节点问题,因此可以取消跨 slot 限制。解决完这几个主要问题

145、后,集群模式可以达到完全兼容主从版,同时拥有集群版的自动容灾、无损提主及可以在业务支持的情况下,无缝升级为集群版。第 1 章:数据库架构设计第 1 章:数据库架构设计7475由于 Client 版本比较多,为了兼容不同的 Client,腾讯云引入了 Proxy。Proxy 除了屏蔽 Client 的差异外,也屏蔽的后端 Redis 的版本差异,业务可以使用主从版的 Client 去使用后端的集群版。Proxy 也补齐了 Redis 缺少的流量隔离及支持更丰富的指标监控,还能将多个连接的请求转换为 pipeline 请求转发到后端,提升 Redis 的性能。Redis 的多 AZ 架构部署高可用

146、的多可用区架构,需要至少满足两个条件:主从不能部署到同一个可用区;一个可用区的节点数不能超过分片数的一半。如果我们部署一个三分片的实例,那应该需要个 6 个可用区才能真正保证它的高可用。即使可用区充足,它也会有性能的抖动,访问本可用区,性能和单可用区相同,但如果跨可用区访问,至少出现 2ms 延迟,因此原生的 Redis 是不适合多可用区的部署,为了实现高可用的部署,我们需要更深入的分析它的问题所在。这种场景的高可用不满足主要是由于主节点漂移,而投票权和主节点又是绑定关系。当投票权在不同可用区间切换后,导致超过大多数投票节点在该可用区,此时该可用区故障后就会出现集群无法恢复的情况。从上面分析可

147、以看出,高可用的问题是由于投票权发生漂移导致的。假如能把投票权固定在某些节点上面,这样投票权就可以不再漂移。当然这里无法将投票权固定在从或者主节点上,对于多可用区,最好的方式就是引入了一个 ZoneArbiter 节点,它只做节点的判死及选主,不存储任何数据。这样投票权就从存储节点中分离出来。在投票权分离后,即使数据节点的 Master 可以位于一个可用区,从位于不同的可用区也能满足高可用。业务在主可用区中访问和单可用区访问性能是相同的。多 AZ 的关键技术保证高可用后,接下来介绍多可用区的三个关键的点:高可用如何部署、性能如何达到最优、可用区故障后保证集群自动恢复。第 1 章:数据库架构设计

148、第 1 章:数据库架构设计7677节点部署同样需要满足两个点:第一是主从不能同可用区,这个比较容易满足,只要有 2 个可用区即可,第二点是至少三个 ZoneArbiter 节点位于不同的可用区,第二个条件需要三个可用区,如果没有三个可用区的地域也可以将 ZoneArbiter 部署于就近的地域,因为数据节点和仲裁节点是分离的,位于其它可用区的节点只会出现判死及提主有毫秒级延迟,对性能和高可用不会有任何影响。分析完部署后,再来看下数据的存储链路,存储链路分为读和写链路,写链路是从 client 到 LB,再到 Proxy,最后将数据写入到相应的 Master。在读的时候,开启就近读的特性后,链路

149、从 client 到 LB,再到 Proxy,最后选择一个就近的节点读取数据。就近路径选择包含 LB 的就近选择及 Proxy 的就近选择,LB 要根据 Client 的地址选择相对应的 Proxy。如果是读,Proxy 要跟据自身所在可用区信息选择同可用区的节点进行读访问。如果是写,Proxy 需要访问主可用区的 Master 节点。能实现就近访问,最关键的一个点就是要 LB 及 Proxy 要存储相关后端的可用区信息,有这些信息后,就能实现就近的路由选择。单可用区和多可用区故障的最大区别是:首先多可用区的某一节点故障后,主节点有可能切到其它可用区会导致性能波动。其次对于多可用区的实例,整个

150、可用区故障后,需要投票的节点比单可用区的节点多。在多节点故障的场景测试中,128 分片,63 节点同时故障,99%以上都无法正常恢复集群。而无法恢复的关键就是 Redis 的选主机制导致。因此我们需要更深入的理解 Redis 的选主机制。第 1 章:数据库架构设计第 1 章:数据库架构设计7879首先看下选主机制的授权机制,当主节点收到一个 failover 信息后,核对自身节点为 Master,然后检查投票的这个节点集群的 epoch 是不是最新的,并且在这个 epoch,并没有投票任何的节点,为了防止一个节点的多个从节点重复发起投票,这里在 30s 内不允许重复发起。最后再核对这个 slo

151、t 的归属权是否属于发起 failover 的这个节点,如果都没有问题,那么就会投票给该节点。综上,允许该主节点投票的条件是:发起投票的节点的集群信息是最新的;一个 epoch 只能授权一个节点;30s 内同一分片只能授权一次;slot 的归属权正确。看完主节点的授权机制后,再看下从节点发起投票的机制。发起投票的流程是先核对 1 分钟内没有发起过投票,再核对该节点数据是否有效(不能和主断开 160s)。从节点是有效的,就开始计算发起投票的时间,当投票时间到后,将集群的 epoch+1,然后再发起 failover,如果主节点的授权超过分片数的一半,则自身提为主节点,并广播节点信息。这里从节点投

152、票有两个关键的点,一分钟只能重试一次投票,最大重试 160s,从节点最多可以投票 3 次。当超过一半的 master 授权提主,提主成功,否则超时。在某一主节点故障后,集群的选主尽量在同可用区中选择。一个分片不同从节点之间的选主时间由节点的 offset 排名及的 500ms 的随机时间决定。在写少读多情况下,offset 排名大多时间是相同的。在单可用区场景下,随机选择一个节点本身无任何影响,但多可用区就会出现性能的抖动。因此这个就需要在排名中引入同可用区的排名。而同可用区的排名就需要要每个节点都知道所有节点的可用区信息。在 Gossip 中刚好有一个预留字段,我们将可用区信息存储在这个预留

153、字段中,然后将这个节点的可用区信息会广播到所有节点中,这样每个节点都有所有节点的可用区信息。在投票的时候我们按照 offset 和可用区信息排名综合考虑来保证同可用区优先提主。第 1 章:数据库架构设计第 1 章:数据库架构设计8081一个节点的选主分析完后,我们来分析下多节点故障的投票时机。多个节点发起投票时,会随机选择 500 毫秒内的一个时间点,然后发起投票。假如集群有 100 个主节点,500 毫秒发起完投票,每个节点投票时间是 5 毫秒,5 毫秒肯定是不够一个投票周期。在之前的多节点故障投票成功率测试结果也就证明了这种情况几乎不能成功。投票不能成功的关机是集群不同节点投票是随机发起导

154、致的,既然随机存在冲突,最直接的解决办法就是按顺序来投票。按顺序投票可以简单分为两种,一种是依赖外部的控制,引入外部依赖就需要保证它的高可用,一般情况下,存储链路的高可用最好不要依赖外部组件,否则会导致整体的可用性受外部组件加存储节点的高可用的影响。那再考虑下集群内部实现顺序投票,集群内部实现顺序投票也有两种方式,一个是仲裁节点按顺序来授权。但是这种方式很容易打破一个 epoch 投一个从节点的原则,打破后可能会导致投票结果不符合预期。还有一种解决办法是由从节点来顺序发起投票。从节点要保证顺序发起投票,那就需要每个节点的排名是保证相同的,而节点 ID 在生命周期中是唯一的,且每个节点都有其它节

155、点的 ID 信息,因此这里选择节点 ID 的排名是比较好的一种方案,每个从节点 ID 发起投票前,首先核对自己的节点 ID 是不是第一名,如果是就发起投票,如果不是就等待 500ms。这个 500ms 是为了防止队头投票失败的场景。按这种方式优化后,投票都可以成功。由于本身是分布式的,这里还是存在着小概率失败,在失败后就需要外部监控,强行提主,保证集群的尽快恢复。专家答疑1.通过 sentinel 连接 redis 也会出现双写么?答:双写是对存量的连接来说的,如果存量的连接没有断开,它会写入到之前的 master 节点,而新的连接会写入到新的 master 节点,此时就是双写。而集群模式出现

156、双写最多 15s(判死时间),因为 15s 后发现自身已经脱离大多数,会将节点切换为集群 Fail,此时写入及读取出错,而规避了双写的问题。2.固定节点投票,这几个节点会不会成为单点答:单点是不可规避的,比如 1 主 1 从,主挂了,那么从提主后,这个节点就是单点,出现单点是需要我们进行补从操作。而这里仲裁节点出现故障,补充一个节点即可,只要保证大多数仲裁节点正常工作即可,由于仲裁和数据访问是分离的,故障及补节点对数据访问无任何影响。3.整个可用区故障和可用区内节点故障 failover 的处理策略是什么?答:不管是整个可用区故障还是单机故障导致的多节点故障,都应该采用顺序投票来完成,减少冲突

157、,而如果同可用区有从节点,该节点应该优先提主。4.想问下写请求,要同步等从复制完吗?Redis 不是强同步,Redis 强同步需要使用 wait 命令来完成。5.最大支持多少 redis 分片呢,节点多了使用 gossip 有会不会有性能问题?答:最好不要超过 500 个,超过 500 个节点会出现 ping 导致的性能抖动,此时只能通过调大 cluster_node_timeout 来降低性能抖动。6.多区主节点,写同步如何实现?答:可能想问的是全球多活实例,多活实例一般需要数据先落盘,然后再同步给其它节点,同步的时候要保证从节点先收到数据后,才能发送给多活的其它节点。还需要解决数据同步环路

158、,数据冲突等问题。7.当前选主逻辑和 raft 选主有大的区别吗?答:相同的点都需要满足大多数授权,都有一个随机选择时间,不同的点 Redis 是主节点有投票权(针对多分片情况),而 raft 可认为是所有节点(针对单分片情况)。Raft 数据写入要超一半节点成功才返回成。Redis 使用弱同步机制(可以使用 wait 强势主从同步完返回),写完主节点立即返回,在主故障后,需要数据越新的节点优先提主(数据偏移值由 Gossip 通知给其它节点),但不保证它一定成功。第 1 章:数据库架构设计第 1 章:数据库架构设计8283新一代云原生数据库畅想窦贤明腾讯云数据库专家工程师从事云数据库产品研发

159、多年,从零到一主导研发多款云数据库、云原生数据库。目前负责 TDSQL-C PostgreSQL、TencentDB For PostgreSQL 的全栈研发工作,和 TDSQL-C MySQL 的产品化研发工作。云与分布式是数据库发展的两大趋势,那云时代下新一代数据库会是什么样的呢?腾讯云数据库专家工程师窦贤明讲师给大家分享了自己的畅想,基于冷热分级存储与 ServerlessDB 结合的新一代数据库。基于腾讯云数据库 TDSQL-C PostgreSQL 在云原生这条路上的一些探索,重点从 Serverless 数据库和分级存储(冷热分离)的设计与实现两个方面进行分享。内容主要分为四个部分

160、:第一部分:介绍云时代的数据库的背景;第二部分:探讨云上数据库进化的逻辑是什么、方向是什么;第三部分:描述 Serverless 数据库具体是什么样子;第四部分:云原生数据库在存储方向上的进一步演进,分级存储所达到的效果、在冷热分级等场景下的一些用例。云时代的数据库在过去一二十年间,整个关系型数据库行业最大的变化只有两个,一个是云,一个是分布式。云时代,对数据库产生了一个非常大的一个冲击,或者说变化。就 Gartner 近期数据,云上数据库的份额也已经赶上并即将超过 on promise(传统方式)的产值。那云时代的数据库是一个什么样子呢?以腾讯云的 TencentDB for Postgre

161、SQL 为例。云数据库的第一个阶段,其实是在将传统数据库搬到云上来。首先,最开始部分是基础环境,比如说硬件资源、网络、存储之类;在此基础上,进行一些虚拟化,这些是云本身具备的能力;在用户侧,资源准备好了之后,是一个资源的管理分配和运行环境的准备;然后是做云数据库托管的一些事情,包括部署、HA、备份恢复等,还有监控这些;那整体上还是一个 PaaS 平台这么一个事情。这个过程中,整体来讲还是将传统的主备搬到一个云环境这么一个过程,其实并没有对数据库的内核或者说数据库的架构产生一些根本性的变化。这其实是在过去几年云数据库或云的一个演进的第一阶段,把原来的数据库运维,以云的方式托管起来、在云上使用起来

162、。那在这期间是怎样一个实现方式呢?首先,会有一个云数据库的一个部署,即实例进程运行在虚拟机里面、存储则放在一个云盘存储上;然后会建立一个备库,主备之间通过 Replication 实现数据的复制,通过两份完整的数据节点实现高可用。这一点,与当前物理环境并没有什么本质上的变化,即一主一备的方式。即使如此(简单的实现),云数据库相比传统数据库仍然会有很多的优点。第一,成本更低,当前主要是云本身的特质带来的;第二点就是扩容能力更好,比如说它第 1 章:数据库架构设计第 1 章:数据库架构设计8485扩容能力好的是在于说存储这里可以动态扩容,计算这里可以更为轻量的扩缩容;第三点,是体验上也有非常大的提

163、升。然而,仍然有些问题没有解决,尤其是在站在云的视角来看。当有越来越多的数据库实例跑在一起的时候,发现这个成本的增长是线性的,成本上是没有去得到一些根本性的降低。就一主一备的情况来说,有两份完全一样的数据;当我们再新加一个 RO(Read Only 节点,仅用于读请求)的时候,还会再加一份完整的数据。存储成本没有根本性的收敛,根据 RO 的数量线性增长。而另外一种情况,网络存储成为一个瓶颈。再拿这个例子来说,每次读写都会对网络形成一个吞吐,且随着 RO 数量增加也是一个线性增长,对网络总流量造成较大的压力。RO 节点(Replica)的建设时间成本也是一个问题。多添加一个 RO 节点,就是多一

164、份数据,是完整的一个复制(对应存储上的三副本),复制时间较长。云原生进化云原生数据库(Cloud Native Database)的提出,是为了解决原来传统架构下无法解决的这些问题,基于云基础设施重新设计与实现所提出的一个解决思路。那其中核心的解决思路是什么?答案就是存算分离。可以思考一个问题:主节点和 RO 节点各自有一份完整的存储,且各自都有三副本。为什么我们这些节点不共用同一份数据呢?这里很容易就想到一点,就是我们把所有节点的数据都放在远端分布式存储上,不再本地存放。这样的话,存储就可以实现仅有一份数据(三个副本),而计算仍然是多节点。把数据库内部拆开来看,基本上来讲分为两部分:一个是存

165、储层,一个是计算层。计算层一般包含词法解析、语法解析、语义解析、查询重写、优化器等。下一层则是数据缓存,相对设计逻辑比较简单一些,这部分一般不单独列;中间这个事务层,保证数据存储、读取的正确性;最下层 storage 层负责跟实际的存储硬件打交道。既然要把所有节点的数据放在同一个分布式存储(租户)上,就需要将数据库内核的引擎和存储部分拆开到两个地方运行,存储的部分放在分布式存储系统内运行,把引擎的部分放在计算节点中运行。表现为所有的 RW、RO 都没有实际的数据,只需要内存和 CPU 的资源拉起来即可,这个时候 RO 启动的效率就非常高,本身就很轻。RO 则通过 Replication 从 R

166、W 拿到更新的修改,去更新它内存中的缓存。那前面也有讲到网络成为瓶颈的问题,需要分别针对 RW 和 RO 来说明。针对 RW,有一个 WAL 的概念,即 Write Ahead Log,所有数据的变更都会记录在这里。第 1 章:数据库架构设计第 1 章:数据库架构设计8687这里做一个极端假设,假如从数据库被初始化之后所有的 WAL 都被保留、且有一台非常非常强大的计算机,在每次读取数据时,都将最开始到最新的所有 WAL 全部重放到当前时刻,其实是可以得到与当前数据库中完全一致的数据。这一点,可以描述为 Log is Database。如果 WAL 能够对应一份完整的数据,那么在写入存储时,可

167、以不写入数据部分、仅写入 WAL。存储层在收到 WAL 之后,基于存储上已有的数据部分,对 WAL 进行不断的重放,就可以实现数据不断的更新,保证数据的一致性。这里具体的实现较为复杂,不再作展开。达到的效果就是,至少减少一半的网络流量。RO 刚才已经简单说过一些,在通过 Replication 获取到 WAL 之后,会对缓存中数据进行更新。当需要从存储上读取数据时,根据该数据页的时间戳,将其后的 WAL 更新上去,也因此,RO 需要在内存中缓存一份近期的 WAL,得到一个保证一致性的最新数据页。整体来讲,通过以上的机制,实现数据存储在分布式存储上主备节点采用同一份存储,达到 RO 节点增加存储

168、成本不变的效果。也因此,RO 的数量支持最大到 15 个。存储计算分离之后,刚开始优势未必特别明显,在使用上和前面的云数据库没有本质的区别,用户体验基本一致。效果会随着使用的加深而逐步体现出来。第一,扩缩容的效率完全不一样,原本在扩容的时候,有可能出现空间不够需要迁移,导致时间成本非常高。计算变得特别轻,可以快速拉起、关闭,迁移效率极高,不用担心扩容时需要迁移的情况。可用性、可靠性等方面,因为所有的状态都存储在分布式存储中,计算节点的启停(可用性),不会影响存储上数据的可靠性,因此在计算节点出现故障时,可以随意关闭老节点、启动新节点,不用担心会丢失数据,可用性方面达到了较高的保证;存储上数据有

169、三副本,保证了数据的可靠性;且多个节点共用同一份存储,整体成本也更低。针对计算来说,计算节点被做得轻量、可以便捷地启停,当这份便捷性达到一定水平的时候,就会引起质变。极致弹性 正常来讲,业务有高峰低谷,比如订餐系统。一家餐饮公司的订单系统,肯定是有高峰和低谷,基本上是三餐的时候比较高,夜里几乎为零。那没有业务在运行的时候,为什么还要这个计算节点付钱?在传统方式下,需要准备一台物理机;在云数据库形势下,则以实例为单位,计费的粒度仍然较粗。那怎么去做呢?第 1 章:数据库架构设计第 1 章:数据库架构设计8889这里可以考虑这样的产品形态,在用户端呢,计算节点在不用时不计费,在真正使用时再拉起并开

170、始计费;且扩容也不再需要用户操作,由后端自动根据负载来做调整,在不用时自动将资源占用降为 0。存储空间也类似。原本客户在购买了一个 100GB 的存储空间,会一直按 100GB 来计算费用,但实际可能并没有真正用到那么多。这里可以实现为仅仅为实际占用的空间付费。正常情况下,数据库实例处于休眠状态;当 SQL 到达时,将计算节点拉起来,然后执行 SQL。同时,RO 也可以加进去一起参与,就是可以一次拉起一个或者多个计算节点,在这些节点上做负载均衡。当没有 SQL 在运行超过一定时间,比如超过五秒钟、十秒钟没有 SQL 在运行,计算节点可以直接关闭,也因为业务没有在运行,也感知不到负面影响。而计费

171、成本上,则获得较大的降低。举例来说,比如餐厅运营的高峰时间段(如中午时间),SQL 会一直进来一直使用,后端计算节点则持续存在。当资源用满达到一定阈值,将自动升配;那中午过后,不再有请求,计算节点则会被关闭、也不再计费,计算成本降为 0。演进到当前,计算节点最细粒度可以做到一秒级别的计费。据内部数据来看,有不少客户的业务一天只花可能不到几块钱、甚至几毛钱都有,一个月可能就花几十块钱。而像传统物理机的方式起码十万起,云数据库也要每月几百、几千块。这是一个巨大的飞越!分级存储花分两朵,各表一支。前面是计算节点的演进,存储节点又该如何演进?存储的演进持续在进行,比如性能、压缩等。此处仅针对数据的存储

172、成本。数据大致可以分为几层:热数据、温数据、冷数据。热数据放在内存缓存里,价格最贵、性能最好。然后,大量的数据放在分布式存储上,那冷数据该如何处理?热、温、冷的划分,主要基于的是访问频率、量和性能要求。冷数据,一般定义不经常访问的数据,比如历史订单、历史日志等。当前 TDSQL-C PostgreSQL 的存储规模支持最高 第 1 章:数据库架构设计第 1 章:数据库架构设计9091128TB,绝大部分情况下足够业务使用。冷数据比较多、不常访问,放在 TDSQL-C PostgreSQL 的存储中又较贵,该如何处理?答案是 COS。COS 是腾讯云的对象存储服务,数据存储成本要低于 TDSQL

173、-C PostgreSQL 的高性能存储盘。温热的数据,或者说时刻需要用到的数据,还是要放在分布式存储上正常使用,但针对时间较老、不大访问的冷数据,可以放在 COS 中,并通过同样的一套 SQL 语言、表结构来访问。为实现这一点,TDSQL-C PostgreSQL 封装了 COS 的访问接口,通过与常规数据表完全一样的使用方式,达到对 COS 上数据文件进行访问的目的。这里举一个具体的例子:第一步,创建一个 Server,指定 COS 的信息,包括 id、key、endpoint、文件等;第二步,在该 Server 上创建表;然后就可以通过读这张表实现对 COS 上文件的读取。异构存储的访问

174、,基本上分为三部分,计算、元数据(表结构等)、存储格式。Server/表定义这里,即实现了元数据的维护,存储格式上当前支持 CSV。具体的执行逻辑可以通过执行计划查看。另外一个例子,是更复杂的一些用法,比如:Table a 和 Table b 都是本地表,数据存放在存储上,再建一张 COS 表,就可以直接对这三张表做关联运算。这比较适合报表类的场景,比如以账户信息为维度统计历史订单类的报表。第 1 章:数据库架构设计第 1 章:数据库架构设计9293还有一个例子,就是分区表。还是 Table a 和 Table b,放在分布式存储上,一张 COS 表,三张表放在一起作为一张分区表的三张子表。P

175、ostgreSQL 有非常强大的分析能力,当基于分区字段做过滤的时候,会根据分区字段做裁剪;再配合分区表的 Time to live 的功能,比如 Table a 和 Table b 是正常的按时间分区,Table c 则是 COS 表,可以实现同样一张(分区)表,部分放在存储、部分放在 COS 上的效果。专家答疑1.存储计算分离之前 PostgreSQL 用的什么高可用方案?答:原先一主一备形态下,高可用方案是內部自研的一套,基本分为探测、策略和执行三个步骤。2.主库和备库要做到同步,那资源岂不是很浪费?答:主库和备库的数据同步,主要是为了解决 RO、备库上內存中热数据的持续更新,一定程度上

176、是为了提升备库的性能和数据一致性。如果不在 RO 上保持缓存,性能会较为糟糕;若保留了缓存,则需要对这些缓存持续更新,以保证数据一致。3.更新操作多的情况呢?答:主备间复制采用的是物理复制的方式,只是将数据页上的变更部分发送到备库上,在数据页上重做,这种方式较为高效、延迟较低。4.RO 先读取数据页,再应用 WAL,会不会出现读延迟?答:会有一定延迟,但这不会成为一个问题。主备方式在当前硬件、网络情况下,必然会存在一定延迟,收敛在一定范围內即可。若要保证完全无延迟,也不是做不到,代价过于高昂、不值得;WAL 的重放改为以页为单位,相比原来的方式更为高效,延迟反而更低;主备间应该通过 Proxy

177、 实现全局一致性;通过以上措施综合解决主备延迟带来的影响。5.目前是一个 RW,多个 RO,怎么才能扩展写能力呢?答:当前写能力很多时候不会成为一个瓶颈。在仅写入 WAL 之后,网络 IO 是制约写能力的核心因素之一,当前最新可以到达 100Gb/s 网络,在存储侧又被分发到多台机器上,少有多业务能将这两个地方都打到瓶颈上。当趋势出现的时候,我们也可以考虑采用多写的方式来承接,这就是另外一个故事了。6.既然是 share 存储了,为什么还要 master 到 replica 的复制?答:参考问题二,主要是为了解决 RO 上的查询性能问题,需要在缓存中保持数据。7.存算分离后怎么解决谓词下沉技术

178、和存储节点计算瓶颈问题?所有数据都从存储拉到计算节点计算?答:这里其实不用过于担优性能的问题。比如,现在有一个 RW15 个 RO,一台机器机器大约 96vCore,16 台加起来一共 1536,很难有业务能吃掉这么多 CPU,计算资源在很多时候是足够的。但谓词下推确实也适合这种架构,减少网络开销,实现的方式是将 where 条件的描述下发到存储层,存储层在返回数据之前对数据进行过滤。其中比较难的课题是存储上计算资源的调度问题,这时候要看存储机的硬件条件。这也是存储计算分离一个价值点,存储层可以针对存储层定制相关的机型,可能就要去配合一些新硬件,比如 FPGA、GPU 之类的8.没明白存算分离

179、的意思,存储单独存储,计算的话指的是每次查询都需要新拉起一个实例去存储中拉取数据么?答:是也不是。这里可以保持一个实例一直运行,这样有部分数据是在内存中,没有用到存储上的数据时不用从存储上读取,没有的时候从存储上读。缺点是,计算节点会持续计费。核心还是成本。这里比较好的玩法,是在每次执行 SQL 的时候才拉起实例,在超过阈值时间不再使用后,将其关闭,以节省资源。Serverless 形态解决的是计费和调度的问题,存储和计算分离之后,存储层可以单独计费、计算成本按实际用量计算,可以极大节省成本。对于很多业务来讲,尤其是有高峰低谷的业务,有非常大的成本降低的效果。并且,也解决了云数据库架构上没有解

180、决的问题,从而实现更高可用性、更好的用户体验。9.分级存储,如果数据放到对象存储上面,这种存储方式只适合存储归档数据,不适合第 1 章:数据库架构设计第 1 章:数据库架构设计9495有 update,delete 的数据。答:这个问题比较专业。分级存储只解决一类的场景,数据量比较大、不改不删、偶尔读取,比如归档类的数据,放在数据库內这种高性能存储上成本又高,说到底还是成本问题。比如有很多行业有安全性的要求,要求至少保留两年以上的历史数据;而这些数据都不会访问,但时不时的还会查一下。这个时候,如果用另外一套存储、另外一套引擎、另外一套机器,维护成本是相当高的。我们提供这种方式,一部分相对冷的数

181、据放到 COS 来,但数据的管理和维护还是在数据库中。因此,不适合删改较多的场景。10.计算节点下的存储是不是有优化的方向,比如 WAL 放在计算节点上,用高速设备进行优化?答:存算分离方案实现的一个核心思想,就是 Log is Database。通过将 WAL 写到存储、并在存储中存放,达到数据写入存储的目的。我理解这里的问题是,是否可以先把 WAL 写入本地、然后异步化写入存储中?这是一个有意思的思路,但这假设了一个前提,就是本地盘比分布式存储的吞吐能力更强。在当前的网络、硬件条件下,这个假设已经不一定成立了。毕竟现在 100Gb/s 的网络已经可以作为标配,再有 RDMA 的加持,网络吞

182、吐已经高于本地盘;且存储侧的写入会分布在多个节点上,也提升了吞吐。腾讯云数据库 TDSQL 与中国人民大学最新联合研究成果被 SIGMOD 2022 接收并将通过长文形式发表。SIGMOD 是国际数据管理与数据库领域顶尖的学术会议之一,腾讯云数据库 TDSQL 论文已连续多年入选 VLDB、SIGMOD、ICDE 等国际顶级会议。本次入选论文题目为:CompressDB:Enabling Efficient Compressed Data Direct Processing for Various Databases。论文针对压缩数据的直接操作与处理,提出一项新型数据库处理技术Compress

183、DB。本研究提出并实现了新型数据库技术,利用上下文无关文法来压缩数据,通过新的数据结构和算法设计实现对语法规则进行解析,CompressDB 支持直接对压缩后的数据进行数据查询和操作,并且支持各种数据库系统。SIGMOD 评委对 CompressDB 的创新性给予了高度评价:在本文中,作者提出了一个支持直接对压缩数据进行更新和计算的系统 CompressDB,是一个优秀的系统工作。其作为文件系统层实现,可以被现有的数据库系统直接使用,作者通过在其上运行一系列关系数据库和 NoSQL 数据库来证明了这一点。同时作者在实现中表明,启用 CompressDB 可以让数据库系统实现更高的吞吐量和更低的

184、延迟,同时减少存储空间。论文概述在如今的数据管理系统中,处理大数据时直接在压缩数据上进行操作被证明是一种很成功的方式。这类系统展示了较大的压缩优势和性能提升。但是,当前此类系统只关注数据查询,而一个完整的大数据管理系统必须支持数据查询和数据操作。我们开发了一个新型存储引擎 CompressDB。CompressDB 支持压缩数据上的直接数据处理,有以下优点:基于压缩数据直接计算技术,定义新型数据库处理第 1 章:数据库架构设计第 1 章:数据库架构设计9697第一,CompressDB 利用 context-free 语法来压缩数据,并且同时支持数据查询和数据操作。第二,我们将 Compres

185、sDB 集成到文件系统中,使很多数据库系统可以在不做任何改变的情况下使用 CompressDB。第三,我们将算子操作下推到存储层,使得可以直接在存储系统中执行数据查询和数据操作,而不需要把大数据转移到内存中,这提高了系统效率。我们验证了 CompressDB 可以支持多种类型的数据库系统,包括 SQLite、LevelDB、MongoDB 和 ClickHouse。我们用真实应用中的数据集测试了 CompressDB 在单机和分布式环境下的性能,这些数据集有不同的数据量大小、结构和内容。实验表明 CompressDB 平均带来 40%的吞吐量提升和 44%的延迟缩短,并实现 1.81 倍的压缩

186、比。研究动机现代大数据系统面临指数级增长的数据量,并使用数据压缩来减少存储空间。为了避免频繁的压缩和解压缩操作的开销,现有的研究探索了直接对压缩数据执行大数据操作。这些系统在数据分析应用程序中表现出优秀的压缩效率和性能提升。由于大数据通常存储在磁盘中,我们的想法是基于规则在存储层对压缩数据进行随机更新。现有技术的局限性现有的压缩技术在只读的查询处理方面显示出巨大潜力,但功能完整的大数据系统必须同时支持数据的读和写。这就需要系统支持随机更新以及数据的插入和删除。现有的压缩技术并不支持在压缩数据上直接修改数据,因此每次修改时都必须解压缩和重新压缩相对较大的数据块,从而导致巨大的开销。重要发现本研究

187、希望开发一种高效的技术来填补现有技术的不足,以支持直接对压缩数据进行更新、插入和删除,从而实现一个支持数据查询和数据操作的高效的大数据系统。这是一项具有挑战性的任务,因为现有的压缩技术大多仅针对压缩效率或读数据操作进行了优化。此外,现有技术的压缩数据结构不能修改。例如,一些压缩技术基于索引和后缀数组,其中压缩元素相互依赖,如果一小部分数据需要更新,则效率极低。系统设计本研究开发了一个新的存储引擎 CompressDB,支持直接对压缩数据进行数据查询和数据操作,并且支持各种数据库系统。我们发现,如果基于规则的压缩方法的 DAG(directed acyclic graph,有向无环图)深度被限制

188、在较小的数值,那么这种压缩方法开销较小,并适用于数据操作。基于此,CompressDB 采用基于规则的压缩技术并限制其规则生成深度。同时,CompressDB 可以通过操作语法规则对数据进行实时压缩和操作。与之前基于规则的压缩方法相比,我们开发了一系列新的设计:在元素级别,我们提出了一种新的数据结构数据洞(block hole)。在规则级别,我们为随机更新启用了有效的规则定位和规则拆分方案。在 DAG 级别,我们降低了规则的深度以提高效率。通过利用新的数据结构和算法设计,CompressDB 无需解压即可高效处理数据。系统设计图 1 展示了 CompressDB 的系统结构。CompressD

189、B 由三大模块组成:1)数据结构模块,2)压缩模块,3)运算模块。这三个模块支持基于 CompressDB 的数据库系统。数据结构模块为压缩模块和运算模块提供必要的数据结构,包括三种:blockHashTable 表示数据内容到块位置的映射关系,blockRefCount 记录块被引用次数,blockHole 是更新操作引起的存储空洞。压缩模块支持文件系统中的分层压缩,可应用于各种基于块的文件系统。操作模块可以将用户操作下推到文件系统。图:CompressDB 系统结构第 1 章:数据库架构设计第 1 章:数据库架构设计9899操作下推为了降低数据传输成本,本研究将算子操作下推到存储层。算子下

190、推是指数据处理直接发生在文件系统层(较低的软件层),使处理操作发生在更接近数据的地方。基于这种技术,CompressDB 可以显著减少对磁盘的数据访问量,并加速所有上层数据库应用程序。与数据库系统的交互为了使 CompressDB 能够支持各种数据库,本研究在文件系统中开发 CompressDB。在文件系统层,CompressDB 可以处理读取和写入等系统调用,因为它们可以通过“提取”“替换”“添加”等操作实现。因此,CompressDB 可以支持在文件系统上运行的不同类型的数据库系统(例如,SQLite、MySQL、MongoDB 等)。这些数据库系统依赖于 CompressDB 提供的系统

191、调用。因此,CompressDB 可以支持数据库系统的各种数据类型(例如,整数、浮点数、字符串等)和操作(例如,连接、选择、插入等)。此外,我们为 CompressDB 开发了一些文件系统不支持的操作,例如“插入”和“删除”。因为这些操作没有对应的 POSIX 接口,我们提供了一组单独的 API,可以有效地使用。适用性CompressDB 是一个存储引擎,主要应用是支持各种数据库系统,而无需修改代码。用户唯一需要做的就是将系统存储目录设置为 CompressDB 的存储目录。通常来说,CompressDB 适用于有大量冗余数据,并需要进行数据分析和操作的数据库系统。对于其他应用场景,它可能仍然

192、有效,但尚未验证。主要成果和贡献为了验证 CompressDB 的性能,本研究使用 CompressDB 支持多个数据库系统,包括 SQLite、LevelDB、MongoDB 和 ClickHouse。本研究分别在单节点和集群环境中,使用多个具有不同尺寸、结构和内容的真实数据集来评估性能。集群环境中的实验使用五节点集群和一种高性能网络分布式文件系统 MooseFS。MooseFS 在集群中传输数据并提供对数据的高吞吐量访问。与 MooseFS 的原始版本相比,CompressDB 平均带来了 40%的吞吐量提升、44%的延迟减少和 1.81 的压缩比,这证明了本研究的有效性。本研究做出以下主

193、要贡献:本研究直接在压缩数据上开发高效的数据操作,例如插入、删除和更新。除了随机访问,本研究还支持数据查询和数据操作。本研究开发了 CompressDB,这是一种集成到文件系统中的存储引擎。CompressDB 可以无缝支持各种数据库系统,而无需修改数据库。本研究将数据算子操作下推到存储系统,避免了内存和磁盘之间不必要的数据移动,从而提高了压缩数据的处理效率。本次研究成果面向的领域本研究成果面向同时支持数据查询和数据操作的大数据管理系统。CompressDB 提供的压缩能力使数据管理系统能够存储较大的数据量,同时支持压缩数据上高效的数据查询和操作。存储效率和数据查询、操作效率在当今大数据时代是

194、至关重要的性能指标,CompressDB 可以帮助现有的数据库系统同时提升这些指标的性能。第 2 章:数据库性能优化101第 1 章:数据库架构设计100第 2 章数据库性能优化数据库事务一致性实现上的各种细节,你注意到了吗?数据库的事务包含原子性、一致性、隔离性、持久性四个特性。隔离性与一致性紧密相连,它们也容易让人迷惑。SQL 标准定义了 4 个隔离级别,但由于定义使用的是自然语言,而非形式化语言,导致人们对隔离级别的理解有所差异,各个数据库系统的实现方式也有所不同。然而在分布式的场景下,又面临新的问题。探索前沿研究,聚焦技术创新。本期由腾讯云数据库高级工程师孟庆钟为大家介绍数据库事务一致

195、性的实现,内容包括事务的基本概念以及特性、主要的隔离级别及实现、TDSQL 事务一致性的实现。事务的基本概念及特性1.1 事务的基本概念及特性事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。事务具有四个特性即 ACID:孟庆钟腾讯云数据库高级工程师,中国人民大学博士深耕存储和优化器设计研发领域多年,熟悉 PostgreSQL 源代码、Linux 操作系统内核代码,目前在腾讯从事 TDSQL 存储引擎的开发。研究领域包含数据库存储引擎、查询优化等。第 2 章:数据库性能优化第 2 章:数据库性能优化102103 原子性(A):事务中包括的操作要么都做,

196、要么都不做;一致性(C):事务执行的结果必须使数据库从一个一致性状态转移到另一个一致性状态;隔离性(I):并发执行的事务之间不能互相干扰;持久性(D):事务一旦提交,它对数据库的改变应该是永久性的。原子性和持久性比较直观易懂,但是一致性和隔离性则较为复杂,不同人有不同的理解。1.2 一致性的理解一致性是偏应用角度的特性,每个应用程序需要自己保证现实意义上一致。数据库在一致性方面对应用程序能作出的保证是:只要事务执行成功,都不会违反用户定义的完整性约束。在执行事务的过程中,只要没有违反约束,那么数据库内核就认为是一致的。常见的完整性约束有主键约束、外键约束、唯一约束、Not-NULL 约束、Ch

197、eck 约束。只要定义了这些约束,数据库系统在运行时就不会违反;只要没有违反,数据库内核就认为数据库是一致的。至于现实意义上是否一致,需要由应用程序自行判断。1.3 导致不一致的原因为什么数据库可能会不一致呢?其实是由冲突所导致的。应用程序对数据的读写操作,最终体现为数据库内核中的事务对数据库对象的读写操作。如果不同事务对相同的数据进行操作,并且其中一个操作是写操作,则这两个操作就会出现冲突。如果不能正确处理这些冲突,就会出现某些异常。常见的异常主要有脏写、脏读、不可重复读、幻读等。并发执行的事务产生冲突,其实可以理解为科幻小说里两个不相容的物体进入了同一时空。因为是在时空上产生冲突,所以我们

198、可以从时间和空间两个维度解决:时间维度:把两个操作从时间维度隔开,禁止同时访问。这其实是基于锁实现的并发控制思想。空间维度:把这两个操作从空间维度隔开,禁止访问同一份数据。这其实是基于多版本实现的并发控制思想。1.4 隔离性的理解有些应用程序的执行逻辑,永远不会导致某些异常的产生。在这种前提下,即使数据库允许某些异常,实际上永远也不会产生这些异常,数据库仍然是一致的。我们用一个简单的比喻来进行理解。底层模块看作是数据库,上层模块看作是应用软件,当上层软件模块调用底层模块时,即使底层模块有 BUG,但如果不踩这个坑就永远不会触发 BUG,则应用软件和数据库组成的成体看起来并没有 BUG,数据库则

199、会一致。隔离性是指并发执行的事务之间不能互相干扰。为了提高系统运行效率,SQL 标准允许数据库在隔离性上进行妥协,即允许数据库产生某些异常。那到底需要隔离到什么程度呢?这需要由隔离级别来确定。根据需求的不同,我们可以选择不同的隔离级别。第 2 章:数据库性能优化第 2 章:数据库性能优化104105主要的隔离级别及实现2.1 SQL 标准定义的隔离级别我们所理解的隔离级别是指并发执行的事务能看到对方的多少。下图是 SQL 标准给出的定义,根据数据库禁止哪些异常来进行划分。SQL 标准定义了四种隔离级别:Read Uncommitted、Read Committed、Repeatable Rea

200、d、Serializable。Read Uncommitted 为读未提交,所以三种异常都有可能发生。比 Read Uncommitted 更严格一级的是 Read Committed,即不能读未提交的事务,但可以出现不可重复读和幻读。更严格的是 Repeatable Read,只允许出现一种异常。最严格的是 Serializable,这三种异常都不允许发生。但该定义也有不足。一方面,上述定义来源于锁实现的并发控制,但是又想摆脱对锁实现的依赖,所以根据数据库不允许哪些异常来定义数据库的隔离级别。基于锁实现的并发控制可以完美匹配上面的定义,但是其它实现方式不一定匹配这个定义。比如常见的快照隔离(

201、Serializable Isolation),不会出现这三种异常,按定义属 Serializable,但它可能出现其它异常(写倾斜),所以快照隔离并非真正的 Serializable。另一方面,该定义使用的是自然语言,而非形式化的语言,导致人们理解有差异,有些系统因此直接把快照隔离称为可串行化。2.2 基于锁实现的并发控制锁可以分为多种类型,包括读锁、写锁和谓词锁。读锁、写锁锁单个数据对象,谓词锁锁一个范围。持锁的时长也不相同,可以操作完数据直接放锁,也可以等事务结束才放锁,比如读数据或写数据前先把锁拿到手,一直持着不放直到事务结束。下图中的 Well-formed Reads/Writes

202、 是指读/写数据之前都要加锁,非 Well-formed Reads/Writes 就是不加锁而直接对数据进行读/写。基于上述三个前提,我们可以看到视图中的隔离级别,关于写的操作基本相同,都是写数据前要先拿到写锁,写锁等事务结束后再放,主要区别在于读锁。Read Uncommitted 的读不需要加锁。事务写数据要加写锁,事务结束后才放锁。虽然读锁和写锁互斥,写加写锁,但读时不加锁就可以直接读到。比 Read Uncommitted 更严格一级的是 Read Committed,读时需要加读锁。只要能加读锁就代表没有其它事务在写,写该数据的事务一定已经提交。因为未提交的写事务其写锁还没有放,读

203、锁和写锁互斥,读事务无法加读锁,因此用该隔离级别读到的都已提交事务的数据。读事务的读锁在读完后就放锁,下次再读该数据时要重新加读锁。放锁后再加锁,该数据可能在读事务未持读锁的期间被其它事务修改,因此读到的数据可能有变化。第 2 章:数据库性能优化第 2 章:数据库性能优化106107在 Serializable 下,读要加读锁,到事务提交时才放。这就保证了数据不会在读事务执行期间被修改。因为如果其它事务要改就需要加写锁,写锁读锁互斥,因此其它事务的写锁加不上。括号中的 Both 指的是谓词锁和一般的读锁都到最后才放。Repeatable Read(RR)比 Read Committed(RC)

204、强,比 Serializable 弱。RR 比 RC 强在读锁,RR 比 RC 多一个读锁最后放。RR 比 Serializable 弱在谓词锁,RR 比 Serializable 少一个谓词锁最后放,所以 RR 可能出现幻读。2.3 基于多版本实现的并发控制一个数据库对象有多个不同的版本,每个版本关联一个时间戳。事务在访问数据时,会使用一个快照选出合适的版本。这就是多版本并发控制(MVCC),好处是读写互不堵塞,读时可在多版本中读合适的版本,写时追加一个版本。时间戳的选择有两种主流的方式:使用事务的开始时间:PostgreSQL 属于这类系统。大多数情况下,事务开始的时间越晚,则产生的版本越

205、新,但是存在特例。为了排除这些特例,PostgreSQL 的快照中有一个活跃事务列表,列表中的事务对快照不可见。使用事务的结束时间:TDSQL 属于这类系统,事务结束的早,就可以对后续事务可见,所以这类系统的快照只需要一个时间戳,通常只是一个整数。典型的隔离级别有三种,Read Committed(RC)、Snapshot lsolation(SI)以及 Serializable Snapshot Isolation(SSI)。2.3.1 Read committed 的实现理论上,Read Committed 有三种实现方式:对每行数据来说,随意读取一个已经提交的版本;对每行数据来说,读取数

206、据最后提交的版本;每条 SQL 语句开始时,获取一个最新的快照,使用这个快照选择数据合适的版本,修改数据时修改最新版本。第一种实现方式满足定义,但可能因为读取的数据太老,导致现实中无意义,因此实际系统里基本不用这种实现方式。第二种实现方式也满足 RC 定义,但会存在读偏序问题。读偏序是指只读取到某个事务的部分结果,比如 T1 更新了两行数据,但是 T2 只读到其中一行的更新。如果对每行数据都只读最新提交的版本,就会存在读偏序问题,实际系统中也较少使用这种实现方式。第三种则是实际系统里较为常用的实现方式。具体实现方式为:每条 SQL 语句开始时都会获取一个最新的快照,用该快照选择合适的版本,用同

207、一快照选择就不存在读偏序问题。2.3.2 Snapshot lsolation 的实现第二种比较典型的隔离级别是 Snapshot lsolation(SI),它并不在 SQL 标准定义的四种隔离级别中。其实现方式是:事务开始时获取一个最新的快照,事务的整个执行过程中使用同一个快照,保证可重复读。修改数据时,如果发现数据已被其它事务修改,则 abort。在 SI 中,上述提及的三个异常即脏读、不可重复读、幻读都不存在,但存在写偏序问题。如果两个事务读取了相同的数据,但是修改了这些数据中的不同部分,就可能导致异常,这种异常叫写偏序。2.3.3 Serializable Snapshot Isol

208、ation 的实现第三种比较典型的隔离级别是 Serializable Snapshot Isolation(SSI),其实现方式为:事务开始时,获取一个快照(通常是最新的。为了降低事务 abort 的概率,某些只读事务可能拿到非最新快照)。修改数据时,如果发现数据已经被其它事务修改,则 abort。SSI 跟 SI 的不同在于:在读数据时,SSI 记录事务读取数据的集合,再使用算法进行检测,如果检测到可能会有不可串行化的发生,则 abort。这种算法可能会有误判,但不会有遗漏,因此 SSI 不存在写偏序问题。SSI 是真正的 Serializable 隔离级别。2.3.4 写写冲突的处理对于

209、写写冲突的处理,基于多版本实现有两种实现方式:第 2 章:数据库性能优化第 2 章:数据库性能优化108109 First commit win,谁先提交谁赢。谁最先把数据提交到数据库里谁就胜出。First write win,谁先写谁赢。谁先往数据库里写(不一定提交),就会阻塞后面的写事务,从而更有可能赢。2.4 MySQL 的隔离级别在分析 MySQL 的隔离级别之前,我们需要先了解两个概念:当前读和快照读。当前读是指读取数据的最新版本,读写时都需要加锁。快照读是指使用快照读取合适的版本,快照读不加锁。MySQL 支持 SQL 标准定义的四种隔离级别,具体实现方式如下:MySQL 在 Re

210、ad Uncommitted 直接读,不加锁,可能出现脏读。MySQL 在 Read Committed 下,对于 select(非 for update、非 in share mode)使用快照读,每个 SQL 语句获取一个快照;对于 insert、update、delete、select for update、select in share mode 则使用当前读,读写都加锁,但不使用 gap 锁,读锁用完就释放,写锁等到事务提交再释放。MySQL 的 Serializable 属于纯两阶段锁实现,所有 DML 都使用当前读,都读最新版本,读写都加锁,使用 gap 锁,锁都到最后再放。My

211、SQL 的 Repeatable Read 在 Serializable 的基础上,放松了对 select 的限制,select(非 for update、非 in share mode)使用快照读,其它场景则与 Serializable 相同。MySQL 在 Repeatable Read 下,混用了当前读和快照读,可能会导致看起来比较奇怪的现象,但只要理解它的实现方式,就可以对这些行为做出分析,这些都是可解释的。2.5 PostgreSQL 的隔离级别MySQL 更像是基于锁和多版本的结合。而 PostgreSQL 则是基于多版本的实现,写时有行锁。PostgreSQL 支持三种隔离级别,

212、即 Read Committed、SI、SSI,PostgreSQL 里没有当前读,都是快照读,三种隔离级别的实现方式如下:在 Read Committed 中,每条 SQL 语句都会使用一个最新的快照。对于 update,如果发现本事务将要修改的行已经被其它事务修改了,则使用数据最新的版本重新跑一遍 SQL 语句,重新计算过滤条件、计算投影结果等,再尝试更新最新的行,如果不满足过滤条件则直接放弃更新。这个过程在 PostgreSQL 中被称为 EPQ(EvalPlanQual)。在 SI 中,整个事务使用同一个快照,更新时如果发现数据已经被其他事务修改,则直接 abort。这在 Postgr

213、eSQL 代码里有较为直观的呈现,发现数据被改后,判断当前隔离级别是否大于等于 SI,如果是则直接 abort,如果小于则会跑 EPQ。SSI 在 SI 的基础上,使用 SIREAD 锁(PG 内部也称为 Predicate Locking),记录本事务的读集合。如果检测到可能导致非可串行化,则将事务 abort。实际加锁的数据范围,与执行计划相关,比如 seqscan 对整个表加锁,index scan 只需要对部分行加锁。需要注意的是,SIREAD 锁是一套独立于 PG 读写锁之外的机制,与 PG 中读写锁均不冲突,它只是起到记录作用,用于写倾斜的检测。PostgreSQL 里面关于写写冲

214、突的处理方式是谁先写谁胜出,具体实现机制为给行加上行锁,这时其它事务就无法修改。PG 的行锁几乎不占内存,本文不详细展开。第 2 章:数据库性能优化第 2 章:数据库性能优化110111下面介绍下 PostgreSQL 中 SSI 的检测原理。SSI 检测是为了消除 SI 里的写偏序异常,主要检测系统里是否存在下图中的危险结构。PostgreSQL 把下图中的结构称为危险结构,具体如图所示。T1 是第一个事务,T1 读取数据后,T2 对读取的数据进行修改,这就形成了一个读写依赖。同理,T2 和 T3 也形成了读写依赖关系。在所有可能导致不可串行化的调度里,必定会存在该结构,PostgreSQL

215、 通过检测这种危险结构从而避免写偏序。TDSQL 事务一致性的实现3.1 TDSQL 的架构TDSQL 的目标是让业务能够像使用单机数据库一样使用分布式数据库。其功能特性主要有 MySQL 完全兼容、全局一致性、扩缩容业务无感知、完全原生的在线表结构变更,其存储引擎为分布式的 KV 系统,提供事务和自动扩缩容能力。TDSQL 是存储计算分离架构,有三个组件,分别是计算层 SQLEngine,分布式存储层 TDStore、元数据管理层 TDMetaCluster。计算层完全无状态,所有数据都存储在存储层。业务通过 MySQL 协议接到 SQLEngine,SQLEngine 在计算时如果需要数据

216、就从存储层里读取。存储层是分布式的 KV 系统,用 Region 进行划分,一个 Region 代表一个 Key 的范围。以下图为例,图中有四个 Region,分别是 Region1、Region2、Region3、Region4,每个 Region 都有三个副本,副本之间用 Raft 协议保证一致性。3.2 多副本的一致性TDSQL 使用 Raft 协议保证高可用以及多副本间的一致性。在 Raft 协议中,比较重要的内容主要是选主、日志复制、安全和配置变更。选主。Raft 协议是一个强主的协议,集群中必须要有一个 leader,系统才能对外提供服务,要保证选出来的 leader 唯一。日志复

217、制。日志只会从 leader 到 follower 单向复制,日志没有其它流动方向。安全。保证选出来的 leader 包含最多的日志,避免 leader 因为日志不全而需要到其它节点上拉取日志。如果日志不够多,就不可能成为 leader。配置变更。Raft 协议中,系统只能有唯一的 leader,不能产生双主。为了避免产生双主,增减节点时使用两阶段完成。整体流程为:先是旧配置生效,再到新配置和旧配置同时生效,最后新配置生效。第 2 章:数据库性能优化第 2 章:数据库性能优化1121133.3 TDSQL 的并发控制TDSQL 的并发控制是基于时间戳的多版本变化控制。通过提供全局时间戳服务的

218、TDMetaCluster,保证时间戳全局单调递增。每个事务开始时会拿一个 start-ts,即快照。读数据时,因为数据项上有关联时间戳,我们就读取数据所有版本中关联时间戳小于等于 start-ts 且最大的那个版本。start-ts 是事务开始时拿的时间戳,只要数据版本关联的时间戳比 start-ts 大,就代表该版本是后面事务产生的,不应该可见。TDSQL 使用 write on commit。事务对数据的修改,会先缓存到本地的事务空间,在事务运行过程中只要不提交,就不会往存储上面写。事务的 commit-ts 写到数据项上,这是关联数据项的时间戳。我们以 update A=A+5 为例。

219、事务开始后先拿时间戳为 4,再选择应该读取哪一行。这个例子中有两个 key 但有三个版本,A 有两个版本,时间戳分别为 1 和 3。我们用 start-ts=4 的时间戳去取,因为要读最新版本的值,1 为旧版本,所以读取到的是时间戳为 3 的版本即 A=10。再进行计算 10+5=15,所以 A=15。update A=A+5 事务对数据的修改,会先缓存到本地的事务空间而非存到公共存储,等到提交后拿到时间戳,关联新产生的版本后再进行存储。我们以下图中的例子来说明 TDSQL 的并发控制。左边事务读到时间戳为 3 的版本即 A=10,通过计算 A=A+5 得到 15,缓存到本地事务空间再开始提交

220、。右边事务在完成后准备提交,会先到存储里检查是否有其它事务先于自己往里面插入时间戳大于 4 的版本,读取后发现最新版本关联的时间戳为 3,因为 3T2,不允许出现 T2-T1。第 2 章:数据库性能优化第 2 章:数据库性能优化130131上述情况只涉及部分级别,我们还可以根据实际情况细分出更多的级别和一致性模型,用 Elle 进行更多的验证。最后,我们还可以从以下四方面对 Jepsen/Elle 事务一致性测试框架进行优化:框架本身并不简单,需要很多参数调节才能达到效果。比如有些情况压测不充分,小概率异常难于发现,有时不易复现。不支持 Delete,不支持谓词范围查询。无法检验幻读(Phan

221、tom),无法判别 Repeatable Read 和 Serializable 之间的区别。采用新数据格式,而非传统回归测试的 SQL 语句,分析和 debug 都比较困难。Jepsen 有额外错误注入功能,但作者在 Elle 文章中并没有细说。比如很多异常是因为某些节点重启断联导致的,正常情况下允许并无异常。参考文献1 Atul Adya,Barbara Liskov,Patrick E.ONeil:Generalized Isolation Level Definitions.ICDE 2000:67-782 Peter Alvaro,Kyle Kingsbury:Elle:Inferr

222、ing Isolation Anomalies from Experimental Observations.Proc.VLDB Endow.14(3):268-280(2020)向量化执行从理论到实现,仅需五步!胡翔腾讯云数据库高级工程师博士毕业于中国科学院软件研究所,加入华为高斯实验室工作多年,加入腾讯后主要负责 TDSQL PG 版数据库向量化执行引擎等相关特性的设计开发工作。随着硬件技术的不断发展,数据库系统也需要进行相应的优化,以便可以充分发挥出底层硬件提供的能力。以查询计划执行为例。原有的数据库执行一个查询计划,往往采用火山模型的方式。这种上层算子递归调用下层算子获取并处理元组的方

223、式,存在虚函数调用次数较多、指令或数据 cache miss 率高的缺陷,并且这种一次处理一个元组的方式无法使用 CPU 的 SIMD 指令进行优化,从而造成查询执行效率低下的问题。向量化执行就是解决上述问题的一种有效手段。探索前沿研究,聚焦技术创新。本期 DB洞见由腾讯云数据库高级工程师胡翔为大家介绍向量化执行的基本原理、技术创新以及向量化引擎的相关实现。论文解读1.1 论文简介经研究,论文作者发现数据库系统的 CPU 使用率(IPC 0.7)较低。原因在于,火山模型的一次处理一个元组的执行方式,导致了较高的解释执行代价,阻止了可以允许 CPU 并行执行的编译优化,这对性能的影响至关重要。第

224、 2 章:数据库性能优化第 2 章:数据库性能优化132133而 MonetDB/MIL 使用一次处理一个列的执行方式,避免了上述问题,但是数据的全部物化导致内存带宽受限,进而影响 CPU 效率。最终作者在两个模型之间找到了一个折中点,为 MonetDB 设计实现一个新的执行引擎 MonetDB/X100,使用向量化执行的方法,提高 CPU 使用率,在实际验证中性能提升较为明显。下图为该论文的目录:1.2 CPU 是怎么样工作的?本论文发表于 2005 年,它列举了 10 年内 CPU 的发展状态,具体如下图右上角的折线图所示。我们可以看见 CPU 的发展速度越来越快,CPU 晶体管数目每隔两

225、年会增加一倍,在当时是符合摩尔定律的。下方的点线相当于晶体管的长度,每隔两年有 1.4 倍的减少,相当于整个容积提升了两倍。中间的虚线表示的是使用了 pipeline 技术的 CPU,它具体指的是把一个指令划分成多个阶段,多个阶段之间可以并行,带来了较为可观的性能提升。下图中的左下图描述的是一个简单的 pipeline,它划分成了 5 个阶段,中间指令执行的时候,不同指令的不同 stage 可以并行执行。Pipeline 的运行会受某些条件约束,最主要的两个影响因素是依赖关系和分支预测。依赖关系是指如果一个指令依赖前一个指令,就必须等待前一个指令执行结束之后才能放入 pipeline。相当于两

226、个指令并不是独立的,必须有一个多余的等待动作,因此并发粒度会降低。分支预测指的是 CPU 会预测程序将要执行的分支,并将其放入到 pipeline 中,但是如果预测失败,之前执行的 pipeline 都会废弃,因此会对 pipeline 的效率有较大影响。另外,超标量 pipeline 提供多个 pipeline,允许多个指令并行,从而使 IPC 大于一。具体示例如下方右下图所示。我们编程时不需要特别关注哪些指令是独立的或是相关的,实际上这些工作是通过编译优化自动实现的。编译优化中有一个比较重要的技术,即 loop pipeline,可以把一个循环里的多次迭代进行 pipeline,从而允许并

227、发执行,可以极大提高循环的执行效率。下图左下角是执行一个循环的示例,该循环有 3 次迭代,顺序执行则需要消耗 9 个 circle(假设 1 个 stage 执行时间为 1 个 circle)。通过编译优化将其组成 pipeline 形式后(假设指令之间相互独立),就可以在 5 个 circle 内完成整个三次迭代,提升了将近一半的效率。有些特定的 CPU 也会做一些特定的优化,如下图所示的这款 CPU Itanium2,它会主动将指令进行划分,再将相互依赖的指令拆解到不同的 pipeline 里,这样就可以分别进行并发执行。而其他 CPU 使用较多的是乱序执行,即 CPU 自己需要承担部分调

228、度任务,需要将某些独立的指令拆分出来,再放到 pipeline 里。但 Itanium2 使用了指令划分,这样可以减轻自身负担,因此可以把更多精力投入到 pipeline 中,提高了 pipeline 的运行能力。同时 Itanium2 对分支预测也做了对应优化,把 if then else 中的 then 和 else 这两个分支都执行一遍。在后续执行时,会根据 if 的结果来确定抛弃对应分支获得的结果。这是较早的 CPU 处理方式,但应用较为广泛,目前某些硬件 CPU 上也会有类似的应用。第 2 章:数据库性能优化第 2 章:数据库性能优化134135上图右上表研究了分支预测对性能的影响。

229、一个带 Filter 条件查询的两种不同实现在两种不同 CPU 的执行时间对比,其中,数据列均匀分布在 0100 区间内,故可以根据 X 来表示查询筛选率。带分支的实现将满足条件的数据放到结果数组里面,而不带分支的实现先把条件赋给一个布尔值,然后将数据放到结果数组里面,但是结果数组序号由自增变成对布尔值做加法,从而把条件去除,但指令数会增加。可以看出,针对于 CPU Athlon,使用带分支的实现,在选择率较低或筛选率较高时,执行时间较短,表明分支预测误判率较低时执行效率较高,而在中间位置筛选率中等时耗时较长,表明分支预测误判率较高时执行效率较低。使用不带分支的实现,执行时间比较稳定,因为没有

230、分支,不受分支预测的影响,所以是一条比较直的线。而针对于 CPU Itanium,无论使用带分支或者不带分支的实现,执行时间都比较稳定,表明其 CPU 独有的优化生效,但是带分支的实现性能更好,而不带分支的实现由于指令数更多性能变差。此外,CPU 芯片上的 cache 对 CPU 性能影响较大。部分研究表明 cache miss 对数据库系统影响非常大。一些 cache 对齐的 btree,或者列式内存数据结构,以及限定 cache 上的算法也有助于提高性能。从上图右下角可以看出,距离 CPU 越近计算速度越快,主存延迟是 cache 的 20 倍到 200 倍。如果将计算都集中在 cache

231、,则有利于发挥 CPU 的实际算力。总的来看,对 CPU 性能影响较大的主要是 cache 命中率、分支数目、分支预测是否成功,还有指令之间是否独立。数据库系统的 CPU 使用率接近 0.7,低于其他系统,比如科学计算相关的系统的 CPU 使用率可以达到 2。数据库系统不应该有如此的表现,特别是针对数据量和计算量都特别大的分析型任务。作者指出,需要充分利用好 CPU 的 pipeline 并行能力来提高 CPU 的利用率,这也是论文的主要优化目标。1.3 TPC-H Query 1 Benchmark论文选取 TPC-H 作为测试集,将 Query 1 作为研究对象,进行 profile 性能

232、测试,深入研究定位性能瓶颈。Query 1 扫描了几乎所有的数据,同时包含多种计算,分别为列与常量减法 2 个、列与常量加法 1 个、列与列乘法 3 个、聚集函数 8 个(sum 4 个、avg 3 个、count 1 个)。此外,比较特殊的是分组键为两个单字节的字符。论文逐个分析了在传统关系型数据库、MonetDB/MIL 以及手写程序上 Query 1 的性能。第 2 章:数据库性能优化第 2 章:数据库性能优化136137作为传统的关系型数据库,查询执行严格按照关系代数来实现,具体的执行过程,则按照火山模型 pipeline 的处理方式。因为其关系代数自由度较高,带的参数比较多,需要实现

233、一个可以处理表达式所有情形的解释执行器,这个会更加复杂。论文对 MySQL 进行了性能 profile,第二列表示当前函数占用的百分比(除去调用的部分),第一列是第二列累积的百分比,第三列是调用次数,第四列是每次函数调用执行的指令数,第五列是 IPC。论文发现,所有的实际工作只占了整个语句执行时间的一小部分。实际的计算只占了 10%,而 28%用来创建探测哈希表,其他 62%消耗于元组解析、数据拷贝等,锁和 buffer 管理等其他因素的影响很小。另外,实际计算的效率与 CPU 能力差异大,主要原因是一次处理一个 tuple,无法进行 loop pipeline 优化,从而增加函数调用次数,进

234、而降低了 CPU 执行效率。其他数据库也出现了类似的问题。论文接着对 MonetDB/MIL 执行引擎进行了 benchmark。MonetDB 是一个列存数据库,相当于将数据进行垂直划分再逐列存储,每列存储形式为 BAT 形式。其使用的代数查询语言叫做 MIL,可以列式地处理输入的多个 BAT,并输出一个 BAT。另外,MonetDB 的自由度较低,针对特定的数据类型进行区分实现,而不是通用的实现,从而减少部分解释代价。Benchmark 结果如右图所示。前四列是不同数据量的对比,包括执行时间和带宽。第五列是内存占用情况,以 1M 为单位。第六列是结果集大小。可以看到,MonetDB/MIL

235、 解决了执行率低的问题,20 个调用执行占比达到 99%,比其他数据库都要高。但与此同时,每个调用也都变成了内存密集型。前述提到在 MonetDB/MIL 中会将数据全部物化,物化的数据量太大,导致内存带宽受限,进而影响 CPU 效率。作者还使用 MonetDB 的 UDF 获取性能的基准。基准程序把涉及的列都作为参数,以 BAT 形式的数组表示,添加 restrict 关键字,用来告诉编译器这个数组里的元素都是独立不相关的,以便进行编译优化。作者还利用了 group 列的特征,即单字节字符,直接按照 bit 位组合来获取数组的序号,避免创建一个复杂的哈希表。另外,还有部分子表达式的优化。第

236、2 章:数据库性能优化第 2 章:数据库性能优化1381391.4 X100:A Vectorized Query Processor作者在前述优化的基础上再进行优化,提出了 MonetDB/X100。测试显示,其性能更强,超过基准性能。本部分将详细介绍 MonetDB/X100 的有关细节。其设计目标是:能够在执行大量的查询时达到较高的 CPU 使用率;可以扩展到其他应用领域,如数据挖掘和多媒体检索,并实现同样的高效率可扩展性代码;还能根据底层存储规模大小进行伸缩。为了达到性能目标,作者梳理了计算机架构中可能出现的瓶颈点。磁盘:ColumnBM I/O 子系统面向高效的顺序数据访问来进行设计

237、,同时采用列式存储,解决多余的数据存储代价,减少带宽压力。另外,基于列式存储还可以做一些轻量级压缩,进一步减少带宽压力。内存:设计跟磁盘类似,也采取了列式存储的组织形式,目的也是为了减少内存占用和带宽压力。Cache:把数据组织成 vector 形式,再把 vector 完全放入 cache 中,使得计算都在 cache 内进行,这样可以减少数据到内存的换入换出,从而提高计算效率,而不必考虑内存带宽问题。CPU:操作或算子都使用向量化原语,目的是便于编译器优化成 loop pipeline 的高效代码。右下图为架构示意图,上半部分是 MonetDB/X100 与原先的 MonetDB、Mone

238、tDB/MIL 之间的依赖关系,下半部分是更直观的整体结构。可以看到,执行引擎部分,处理单元都是一个方块,即代表一个向量,按向量力度来进行处理。这些向量能够直接放到 cache 里进行计算。在查询语言方面,MonetDB/X100 与 MonetDB/MIL 不同,可以生成多个列向量(仍然是 BAT 形式),以作为其他操作或上层算子的输入。具体语法如右上角图所示,上面是标准的 SQL 语句,这实际上是 Q1 的简化版本,而下面是 MonetDB/X100 代数的形式。执行流程仍然按照火山模型 pipeline 的方式,但是以 vector 为粒度。右下角的图是简化版本的 TPC-H Q1 在

239、MonetDB/X100 的执行流程。图中包含了 4 个算子,即 Scan、Select、Project 和 Aggregate。Scan 每次从 MonetDB BATs 中获取多个列对应的 vector,图中有三列。Select 创建一个 selection-vector,在满足谓词条件的元组位置进行标记。这个数组在后面的计算过程中也同样会用到。引入 selection-vector 的好处在于,不必将筛选出来的数据进行拷贝和重新编排,允许在原来的输入向量上直接计算,效率会更高。Project 主要是完成表达式计算并为最终聚集计算做准备。Project 时会使用 selection-vec

240、tor 跳过之前被筛选掉的元组,避免不必要的计算。Aggregate 计算主要包含两部分:计算每个元组在 HashTable 中的位置,计算聚集函数并将结果更新到对应的位置。新的位置需要在 HashTable 中创建。所有下层算子执行结束,不再生产新的 vector 后,遍历 HashTable 获取最终结果。Aggregate 时也会使用 selection-vector。以上就是 MonetDB/X100 的查询执行流程,整体流程类似于原来的火山模型,主要区别在于执行粒度从原来的一个元组变成一个 vector,函数调用次数大幅减少,同时对一个 vector 进行循环计算时可以用编译优化来提

241、高 CPU 运行效率。另外,具体实现时需要增加一些辅助数组,以及对原有的执行逻辑的改造等。第 2 章:数据库性能优化第 2 章:数据库性能优化140141与标准的 SQL 语言相比,MonetDB/X100 的代数形式是比较特殊的。它包含两种数据输入,Dataflow 表示在 pipeline 中流转的元组,Table 是特殊的 Dataflow,表示物化的表。MonetDB/X100 的向量化原语部分,主要用来进行批量快速计算。以右图为例,这是一个 double 类型的数据在做加法。函数的参数包括一些输入和输出的向量化,还包括一个类似于 section-vector 的辅助数组。如果 sel

242、ection-vector 不为空,就需要参考数组得到有效元组的位置,然后进行计算,而如果为空,就直接计算即可。每个输入都是向量,可以完全放入到 cache 里面进行计算。批量处理的循环可以进行 loop pipeline 的优化,提高 CPU 的使用效率。另外,MonetDB/X100 针对每个数据类型都有单独的实现,可以避免元组解析的代价。按照原语模板自动生成的方式,MonetDB/X100 包含了上百个向量化原语。MonetDB/X100 还运用到组合向量化原语,它也可以进一步提高执行效率。以做加法为例,以往可能需要两个操作数先读取数据,最后写入数据,中间才是一条加法的指令,数据的读写代

243、价太高,就导致了实际计算工作占比较小。MonetDB/X100 对向量化原语进行组合后,一次执行中实际工作做的更多,这样就均摊了数据读写的代价。在数据存储方面,MonetDB/X100 采用列式存储。但每列单独存储的方式一般会有更新删除等代价,比如更新一行可能会涉及修改多个列文件。MonetDB/X100 通过经典的 delta 结构来解决列存更新/删除代价增加的问题。比如 Delete 直接使用 deletion list 来记录 tupleid;Insert 并没有在原来的数据上增加,而是直接放在另一个 delta 结构区域,用行存方式进行存储;Update 采用 delete+inser

244、t 的执行方式;Delta Merge 则是将 delta 结构区域合并到原列存上。另外,还有一些索引信息用于汇总局部的最大值和最小值,从而可以用于数据筛选。这些都是比较通用的列存实现方式。1.5 TPC-H 实验作者在论文中将 MonetDB/X100 和 MonetDB/MIL 进行对比,在不同的处理器、不同的数据量上,MonetDB/X100 的性能都明显更优。作者还在 Q1 上做了性能 profile 测试。在内存带宽上,MonetDB/X100 的带宽比较高,内存占用较少,另外有些列也采用 enum 类型进行了优化。第 2 章:数据库性能优化第 2 章:数据库性能优化142143作者

245、还对向量大小进行测试,即不同向量大小对性能的影响,从上图中可知,大小适中时性能最优。过大过小都不是最优结果。过大无法放入 cache,会有额外的从内存读写数据的代价。过小则类似于原来的火山模型,无法做编译优化,无法使用 CPU 并发能力的优化,而且函数调用次数增加,实际的工作占比则会变小。1.6 论文总结这篇论文基于经典火山模型和 MonetDB/MIL 的列式查询执行模型做了进一步优化,从而得到性能极大提升。以往的火山模型一次处理一个 tuple 的方式造成过大的解释执行代价,阻止了对性能影响极大的编译优化。MonetDB/MIL 不存在上述问题,它可以让 CPU 高效运行,但是全部物化的执

246、行方式会造成内存密集型的问题,内存带宽会受限。通过向量化执行方式,使用较小数量的可放入 cache 的列式数据,即 vector,进行批量计算,则可解决上述两个问题。验证结果显示,性能与其他相比有两个数量级的提升。向量化执行引擎实现详解2.1 如何实现向量化执行引擎我们结合 TDSQL 的具体实现,来详细介绍向量化的实现过程。如何实现向量化执行引擎,其核心工作主要包括四个部分:向量化执行框架:向量化执行计划的生成和执行以及与非向量化执行计划的兼容。向量化数据结构:合理设计向量的内存组织形式,尽可能使用 cache 资源,减少内存拷贝。向量化算子实现:批量计算改造,拆分成小的循环来执行简单的操作

247、,便于编译优化成高效程序。向量化函数实现:与算子实现类似,还需要对表达式计算框架进行调整,简单的计算函数可以通过 SIMD 显式向量化。2.2 向量化执行框架向量化计划生成的方式,采用贪婪的方式,尽可能将计划路径中涉及的算子转换成向量化执行的方式。不支持向量化的计划节点,可以通过在其上添加一个行转向量的算子,相当于把输出从行元组变成了向量,从而支持上层算子的向量化执行。我们以一个简单的 SQL 为例,原有的火山模型执行流程和向量化模型执行流程如右图所示。2.3 向量化执行数据结构向量化执行数据结构的原则有两个:一个是尽可能将数据连续存储在更靠近 CPU 的位置,如 cache;另一个则是列式组

248、织形式,方便对单个列进行快速计算。在以往,一个元组用一个 TupleTableSlot 来表示。为了便于向量化计算,我们把它改造成一个包含多个元组的结构,通过 VectorTableSlot 来表示。它实际上不是简单地把元组合并起来,第 2 章:数据库性能优化第 2 章:数据库性能优化144145而是对数据进行垂直划分,每列数据放在一起。相同列的数据组织在一起就称为列向量,通过 ColumnVector 来表示,为实际计算的操作数。具体包括:元数据,如行数、列表示信息等;标记数组 flags,如标记 null 信息等;值数组 vals;内存挂载位置 bufs。数据则区分为定长和非定长存储。定长

249、数据直接存储在 vals 数组中。变长数据因为不能直接存在上面,需要分配非固定大小的内存,挂载在 bufs 上,并把地址存在 vals 数组中,内存可以快速复用。2.4 向量化算子实现向量化算子实现也有类似的原则:一个是尽可能地将复杂的循环处理过程拆解成多个简单的小循环,以便批量地对同种类型的数据进行快速循环处理;另一个是减少分支以及数据依赖等。我们以 HashAgg 算子、HashJoin 算子为例,来介绍如何实现向量化改造。下图实际上是一个简化版的 Query 1,两列做分组,再分别进行 HashAgg 操作。具体改造过程分为五步:1.对输入的元组向量在分组列上批量计算 hash 值;根据

250、计算的 hash 值批量计算 hash bucket 值。2.构建 hash table,采用 Open-addressing 的冲突处理方式,记录元组对应的 hash entry;如果内存占用超过内存限制,元组则需要下盘。3.计算聚合结果,并将其更新到对应的 hash entry 上。4.遍历 hash table,拼接成向量输出。5.如果存在下盘的数据,重新构建 hash table 并执行上述步骤。我们以下图为例来介绍 HashJoin 算子向量化的过程。具体改造过程分为五步:1.对 Scan 内表得到的元组向量进行批量计算,得到 hash 值和 hash bucket 值。2.对内表构

251、建 hash table。3.对 Scan 外表得到的元组向量批量计算 hash 值和 hash bucket 值。4.使用外表元组向量探测内表构建的 hash table,再进行批量的匹配操作,如果匹配则进行标记,如果不匹配就去找下一个位置进行匹配。5.根据标记数组将匹配成功的行进行对应的 Proj 列输出。第 2 章:数据库性能优化第 2 章:数据库性能优化146147节省 30%磁盘空间的同时如何保障数据安全?对于数据库系统而言,存储容量和数据安全是影响用户系统的成本及信息安全的重要因素。TDSQL PG 版通过支持压缩来减少磁盘空间使用以此来节省成本,同时支持对表内容加密来保证用户数据

252、的安全性。探索前沿研究,聚焦技术创新。本期 DB洞见由腾讯云数据库高级工程师丁艳飞从工程创新的角度,为大家介绍 TDSQL PG 版透明压缩和透明加密的相关实现,主要包括整体架构介绍、压缩方法简介、透明压缩实现、加密脱敏介绍四个部分。TDSQL PG 版整体架构 TDSQL PG 版是一款分布式数据库,在内核层面由 GTM、CN、DN 节点组成。其中 GTM 是事务管理器,负责协调整个集群的事务,并管理全局的对象。CN 节点是协调节点,是业务访问入口,每个节点之间相互对等,都能对外提供一致性视图。DN 节点是数据节点,业务运行过程中产生的数据都会在 DN 节点上进行存储。数据库内核配合 OSS

253、 系统可以对外提供指标监控、运维管理、实时告警、安全审计、数据治理等运维管控能力。随着业务的不断进行,所产生的数据会越来越多,对 DN 节点的磁盘丁艳飞腾讯云数据库高级工程师目前主要负责腾讯分布式数据库 TDSQL-PG 内核以及 Oracle 兼容开发方面的工作,包括 Oracle 兼容性、执行引擎等内容。2.5 向量化函数实现我们具体实现时没有增加新的类型,只是对原有版本的函数进行改造,支持各种通用的数据类型。在具体执行时需要进行函数的替换。以右图为例,这是一个 intel4 类型的判断,左边是比较简单的判断,右边的输入则是列向量。需要注意的是,左边的判空逻辑实际上是在函数外面,右边因为要

254、对每行进行判空,所以这里涉及的函数比较多。总结和展望本期分享中,我们通过一篇有关向量化执行的经典论文介绍了向量化的基本原理,并结合 TDSQL 的实现详细阐述了向量化的实现过程。为了进一步提升查询执行效率,我们正在对算子的向量化算法进行更加深入的优化,尽可能方便编译器对程序自动地进行向量化编译。另外还通过 SIMD 显式指令进一步提高向量化的粒度。编译执行也是解决类似问题的有效手段,特别是对于表达式计算、元组解析等通用模块尤为有效,该部分工作也正在进行中。未来我们会带来更多的优化,以轻松应对各种不同复杂业务的需求。第 2 章:数据库性能优化第 2 章:数据库性能优化148149存储也造成越来越

255、大的压力。数据节点的存储容量也越来越成为制约整个 IT 系统部署密度、影响系统成本的主要因素。所以目前大多数客户都会要求数据库系统具备数据压缩存储的能力。压缩方法简介2.1 存储介质压缩针对压缩需求,在数据库领域出现了一些特定的压缩方法,其中一种就是在存储介质中进行压缩。这种方式会在原有的 SSD 内部引入一个计算引擎。数据按照常规方式来写入 SSD,盘内的计算引擎可以对写入的数据进行压缩处理,再把压缩后的数据写入到存储物理介质中。整个压缩过程对业务透明,上层业务基本无感知,应用方面也可以不进行修改。此外,这种方式相当于零拷贝技术,不需要做额外的数据传输,当数据量增长较多时,也可以很方便地增加

256、多块盘来实现压缩能力的线性扩展。传统的关系型数据库(使用 B-Tree 数据模型,如 Oracle/MySQL/SQL Server/PostgreSQL)可以直接部署在这种智能存储介质上使用,且不存在占用业务 cpu 的问题,使用方便,对性能影响小。但目前而言,很少有客户使用,主要原因是这些存储介质的价格昂贵,整体部署成本大,基本上很难实现大批量使用。2.2 数据编码另一种常用的压缩方法是使用数据编码。数据库关系表中存放的一般是结构化数据,同一列中字段具有相同的数据类型且有明确的数据边界。对于不同的列,如果它们之间有相互关系,在这些列之间可能也会存在较多相似的数据。所以我们可以利用数据明确的

257、字段边界和丰富的类型信息,采用一定的编码技术来实现数据库的压缩。下表中罗列了部分常见的编码方式,包括字典编码、RLE 编码、常量编码、差值编码、前缀编码等。不同的编码有不同的适配场景,字典编码是较为通用的编码方式,RLE 编码则主要用于连续相等的数据,常量编码主要针对近似性常量化的数据,差值编码适用于在小值域范围内分布均匀的整数型数据,前缀编码则适用于前缀相同的字符型数据。第 2 章:数据库性能优化第 2 章:数据库性能优化150151右边是一个数据库使用数据编码的简单例子,是用字典编码对页面进行压缩的一个实例。当页面满或者页内行的数量达到阈值后,通过数据编码进行压缩,较为常用的做法是把重复性

258、较高的数据进行去重,将去重后的数据建立字典存储在页面固定区域,而把原来存放数据的地方存成指向特定字典下标的引用。但这种方案最大的问题是频繁更新对性能影响较大。频繁更新会导致页级字典的频繁重建,与此对应的页面数据的引用也需要频繁变动。除此之外,在实现上还需要在页内增加字典区域,对现有页面冲击大,实现复杂。2.3 通用压缩第三种方法是使用通用的压缩算法对数据库中的数据进行压缩。常见的压缩算法会把输入的数据当作连续的字节流进行处理,再基于一些经典的编码算法比如串编码、嫡商编码来实现对信息的压缩。下表中列举了部分常见的压缩算法,不同的压缩算法追求的目标不同,比如 lz4 算法追求快速的压缩和解压的速度

259、,而 zstd 算法则追求更高的压缩比。在数据库中,buffer 里存储的原始页面一般具有固定大小。这种方式的优点是,在将页面写入到磁盘中时能较好地计算每一个页面在文件中的偏移位置,以方便对这个页面读或者写。但对页面引入压缩算法后就会出现另一种情况,因为不同的页面存储的文件内容不同,所以采用压缩算法后,实际压缩后的大小是不固定的。即使对于同一个页面,随着增删改的进行,压缩之后的数据大小也有可能变化。所以当引入压缩后,再重新去磁盘上读取相同的页面时,页面的偏移位置就会发生变化,这样会对整个数据库层面的读写产生巨大的冲击。为了解决这个问题,通常在数据库领域会新增一个新的压缩页面类型。这个新的压缩文

260、件里每个页面的大小都是固定不变的,一般会配置成原始页面的二分之一、四分之一或者八分之一,以此来存储压缩之后的数据。但这种实现方式也存在问题,它的压缩主要受限于压缩页面大小的配置。如果压缩页面配置较大,当前的数据量比较小,压缩之后所占用的空间也比较小,但由于压缩页面比较大,所以写入到磁盘文件中时还是要占一个压缩页面大小,这就达不到节省磁盘空间的目的。但如果把压缩页面设置的比较小,则会存在另外一个问题。原有的页面有可能需要多个压缩页面进行存储,所以一个原始的页面会分裂成多个压缩页面,需要额外的原数据信息进行额外的存储。在读取时,需要在原有的压缩页面解压后,再把多个页面重新拼装成为原始页面,因此又增

261、加了实现上的复杂度。透明压缩实现我们的目标是实现数据文件的压缩以此来节省存储空间,且方案实现要简单且适合 OLTP 场景。通过前文的介绍,我们先把智能存储介质排除,因为它属于磁盘层面的压缩方案,不属于数据库本身,暂时不作考虑。同时我们也可以排除数据编码,它不适合于频繁更新的场景,且会对现有页面的结构造成较大的冲击,不满足代码低侵入性的要求,因此我们把实现基调定在通用压缩方法上。通用压缩方法存在前文提到的问题,即页面压缩后大小不固定,因此偏移也不固定。如果存在一种方法能使页面压缩后,还能按照原页面固定大小进行偏移的查找,又能把压缩后节省的空间释放出来,整个方案的实现会简单很多。这时我们可以采用文

262、件系统本身提供的打洞能力。文件系统的打洞能力是保证文件其他属性不变的情况下,告知文件系统在指定偏移的位置上有一段空间不再需要,可以主动释放该文件所占用的物理空间。第 2 章:数据库性能优化第 2 章:数据库性能优化152153找到这种方式后,我们把最终的压缩方案定为使用通用压缩加文件打洞来实现。整个方案以页为压缩粒度,在页面落盘时先使用压缩算法对页面进行压缩,压缩完后按照原有的页面偏移的计算方式找到页面应该写入的位置,将压缩后的数据写到固定的位置中。对于固定页面的大小,除去压缩数据后剩余部分所占用的空间,我们可以利用打洞工具将剩余磁盘空间释放。在读取页面时,从磁盘上读取后先进行数据的解压,再将

263、解压后的页面放入 buffer 中。因此整个压缩解压在 buffer 层向上完全透明,在代码实现层面满足低入侵要求。为了提高压缩速度,在刷脏页时我们采用多进程并行的方式进行压缩。在 syncbuffer 时,轮询压缩进程,将脏页 id 加入待压缩队列,压缩进程的主循环发现队列中有数据,就会对相应页面进行压缩,再将压缩后的页面存储在 slot 中。Syncbuffer 将脏页 id 加入待压缩队列后不会等待压缩完成,而是去访问已压缩完成队列,将队列里对应的页面加入 buffer id list 中,再对 list 中的页面进行刷盘。因此页面压缩采用多线程并行,而压缩和落盘可以看作是一个 pipe

264、line 的过程,以此来加快速度。除此之外,我们也对 Vacuum 整理页面时的压缩进行了优化。因为元组更新时不是原位更新,会在原有的元组上做删除标记,再插入一个新的元组。这样在页面里就会存在 dead tuple,因此 auto Vacuum 的进程会定期对页面进行整理,将页面中有效的元组按照原有的组织方式从页面尾部进行排列和插入。对其他的 dead tuple,则会在 line pointer 上做标志,表示它们可以重用。在页面整理后,没有用的空闲空间可以进行置 0 操作,以此提高整个页面中的数据重复度。右图是一个简单的测试。我们对表进行不同程度的更新,从 20%逐渐到 100%。我们发现

265、在全表更新的情况下,优化后比优化前可以节省 30%的磁盘空间。为了让备机也支持压缩,我们也做了相应适配。备机的信息来源渠道主要是主机的日志,因此备机获取压缩信息的场景之一就是 FULL PAGE IMAGE。在页面第一次做修改前,在 FULL PAGE WRITE 的机制下,我们会把页面记录到日志里。备机在应用到这种类型的日志时,可以获取到页面上记录的压缩信息,在备机页面落盘前根据压缩信息进行页面的压缩,再进行打洞处理。另一种场景是备机在回放其他日志时可能产生一些新的页面。假如在回放一个插入操作时,当前页面空间不足,需要新增一个页面来进行插入,这时备机会初始化一个新的页面。或者当备机在回放新建

266、索引日志、索引分裂、元数据初始化的操作时,都会涉及到页面初始化动作。备机本身没有压缩信息,因此要想得到压缩信息,就需要在主机里对相应日志类型增加压缩信息,给备机进行应用。这样备机在初始化页面后,就可以把压缩信息设置在页面的页面头上,后续就可以按照正常流程进行操作,在实际的落盘前再根据页面头上的压缩信息进行压缩处理。第 2 章:数据库性能优化第 2 章:数据库性能优化154155除了备机外,我们也需要对数据库相对应的配套做压缩的支持。一是备机重建。备机重建是使用 pg_basebackup 工具,读取对应的主节点上的数据文件,再把该数据文件备份到指定目录下。因为数据文件经过打洞处理,在读取这个数

267、据文件时,它会把每个页面对应的压缩信息读取上来,对空洞部分进行零的填充,这样一来,备份好的文件每个页面的大小就等于没有压缩前的大小。为了应对这种重建的压缩情况,在获取每个表文件后,我们会把该表文件的每个页面读取上来,根据页面上记录的压缩后的大小,找到空洞对应的偏移位置,在指定的偏移位置上重新进行打洞处理。为了提高处理的速度,我们对多个文件采取了并行处理。二是压缩信息查看。为了方便查看压缩信息,我们对 pageinspect 进行了相应的适配。它能够读取指定页面上的信息。在读取时先将文件读取,再读到 buffer 里进行解析和显示。对压缩而言,我们不需要 buffer 里的信息,我们真正需要的是

268、磁盘中实际存储压缩的信息,因此我们在插件里增加了一些函数,可以支持从磁盘中直接把相应页面读上来,再去解析页面头,再显示对应的压缩信息。三是压缩率获取。因为我们通过文件系统的打洞功能来实现压缩,而文件经过打洞后,使用普通的 stat 或者 ll 命令读取的大小是包含空洞大小的,而真正存储所占用的大小则不包括空洞大小,因此可以利用这两个的实际比值来获取压缩比。四是参数设置。我们可以对表和索引分别指定不同的压缩算法,同时也可以查看整个表的信息。在信息显示里,我们的 option 选项能列出压缩的情况。如果想要查看每个页面的情况,我们也提供了函数支持,可以列出对应页面里压缩算法和压缩后页面的大小。如果

269、想要查看整个表的压缩概述,我们也提供了函数支持,可以看出当前表有多少页面是压缩的,有多少页面没有压缩。我们对压缩和非压缩情况做了 tpcc、pgbench 测试,可以对比压缩前、压缩开启后产生的影响。我们在测试过程中把 shared buffer 设置得比较小,构造出页面频繁换入换出的场景,在这种情况下对页面做频繁的压缩和解压操作。从测试结果可以看到,在 tpcc 模型下压缩比可以达到 2.3,ptmC 有 15%的新的下降。在 pgbench 模型下,压缩比能达到 6.8,tpcB 有约 17%的下降。以我们目前采取的压缩策略来看,采用通用压缩算法,本质上是以时间换空间,因此性能劣化在我们预

270、期之内。第 2 章:数据库性能优化第 2 章:数据库性能优化156157加密脱敏介绍TDSQL PG 版整体的安全体系是以数据库三权分立为基础的。我们把传统的 DBA 角色分解成安全管理员、审计管理员、数据管理员。在安全规则方面,我们针对安全管理员增加了安全规则和数据透明加密、脱敏的规则;在审计方面,我们结合业界的审计标准和业务的场景需要,增加对象审计、用户审计、SQL 审计;数据管理员则履行之前 DBA 数据权限管理和数据库运维的职能。通过三个角色的划分,我们从根本上杜绝了系统的安全死角。安全管理员负责整体安全规则的制定,通过这种方式来约束系统中所有的角色。审计管理员则负责制定审计规则、设计

271、系统中包括审计管理员在内的所有角色,做到系统的所有操作都具有可追溯性。数据管理员负责数据库的日常运维。这三个管理员之间相互约束,相互监督。在这部分我们主要分享数据透明加密、脱敏的规则。加密主要针对数据文件本身的泄露问题。目前常见的数据库的存储格式基本属于半公开状态,只要拿到了数据文件,再配合相应的数据程序,他人就可以解出里面对应的数据。在这种情况下,我们需要通过数据中心对一些有保密要求的文件进行加密,防止泄漏,因此需要把存储的文件进行加密。TDSQL PG 版支持表级加密和列级加密两种方式。通过下图,我们可以看到表级加密的流程和压缩的流程非常相似。一般是在页面进行落盘前,先进行加密处理,再进行

272、落盘,在磁盘里存储的都是经过加密后的文件。当需要读取时,我们会从磁盘上读取后先进行解密,再把解密后的数据放在 buffer 里,所以用户在读取时可以直接从 buffer 里获取明文数据。列加密的不同之处在于,列加密会在一行数据进行插入时将这一列值先进行加密,加密完后再插入到 buffer 的页面中,所以 buffer 里是加密的状态,磁盘里存的也是加密的文件。当用户需要使用这一列或这个页面时,我们就会对相应的这列先进行解密,再给用户进行明文显示。脱敏对应的使用场景是,在访问数据时,部分用户没有权限看到这些数据的明文,但为了进行运维操作或其他工作需要访问这些数据,但他们不需要知道这些数据真正的存

273、储结果。针对这种场景,我们可以在磁盘里存储正常的明文数据,在用户进行读写时进行处理。如果在用户读取时,发现是授权用户,就可以直接从 buffer 里拿到正常的数据进行显示;但如果发现是非授权用户,就需要先对数据进行脱敏处理,把脱敏后的结果展示给非授权用户。加密和脱敏之间互不冲突,可以灵活地搭配使用,既可以对存储内容进行加密,也可以针对不同的用户,采用用户自己设置的脱敏规则来保证数据访问的隔离性。下图是加密和脱敏的简单示例。在这个例子中,非授权用户是分公司经理和人力资源经理,授权用户是董事长。对同样的表格而言,董事长能看到所有信息的原始状态,薪酬和家庭信息也能正常显示。但对非授权的分公司经理而言

274、,在薪酬和家庭信息方面,他查看到的都是脱敏后的结果,例如他只能看到薪酬为 0,家庭信息为空。通过这样的方式,系统里不同等级的用户对同样的数据会看到不同的视图,以此达到隔离的效果。第 2 章:数据库性能优化第 2 章:数据库性能优化158159加密和压缩流程相似,因此我们也支持加密和压缩的同时使用。在这种情况下,我们整体处理逻辑不变,在原始页面进行落盘前先进行压缩和加密处理,在读取时进行解密和解压的处理。但需要注意的是,我们要保证先进行压缩再进行加密。这主要考虑到压缩对于结构化的数据能够达到较好的效果,而数据页面本身存储的就是相对结构化的数据,如果把原页面作为输入源,压缩效果会相对较好。但如果把

275、原页面先进行加密,加密后得到的数据是相对杂乱的数据,再把相对杂乱的结果作为压缩的输入源,压缩效果就会大打折扣。因此为了保证压缩和加密的正常使用,我们在处理时需要先进行压缩再进行加密。与此相对应,读取时也是从磁盘读取后先对页面进行解密,再把解密后的数据进行解压,再放入到 buffer 里,以此保证数据正常的读和写。TDSQL PG 版起源于技术成熟、功能强大的 PostgreSQL,在此基础上腾讯云数据库构造和发行了功能更丰富、稳定性更好、兼容性更广、安全性更高、性能更强、扩展性极好的分布式数据库 TDSQL PG 版产品。腾讯公司对 TDSQL PG 版具有完全自主知识产权,实现安全可控,具备

276、在中高端市场规模化替代国外数据库的能力,在数据库基础软件层面有力支撑了国家安全可控战略发展。当前 TDSQL PG 版已经在金融、保险、通信、税务、公安、消防、政务等多个行业的核心交易系统上线运行,为众多行业客户提供优质服务。一些有趣的 B+树优化实验作为目前数据库引擎的两种主要数据结构,LSM-tree 和 B+-tree 在业界已经有非常广泛的研究。相比 B+-tree,LSM-tree 牺牲一定的读性能以换取更小的写放大以及更低的存储成本,但这必须建立在已有的 HDD 和 SSD 的基础上。探索前沿研究,聚焦技术创新,本期 DB洞见由腾讯云数据库高级工程师王宏博进行分享,主要介绍一篇 2

277、022 年 FAST 的论文,主题为“基于硬件透明压缩的 B+树优化”。本次分享的论文针对可计算存储 SSD(支持硬件透明压缩)提出了三种有趣的设计方法,从而极大地减少了 B+-tree 的写放大(10X)以使其接近甚至超越 LSM-tree。以下为分享实录:论文简介本期分享的是 2022 年 FAST 的一篇论文,主题是基于硬件透明压缩的 B+树优化,该论文在内容上主要分为三个部分:第一部分是背景介绍,作者分别介绍了现有的 B+树及其软件压缩、存储硬件透明压缩、B+树和 LSM-tree 的简要对比以及 B+树写放大的构成分析。王宏博腾讯云数据库高级工程师研究领域包括分布式事务、分布式存储、

278、副本一致性及高可用等方面,目前主要负责腾讯分布式数据库 TDSQL-PG 内核开发以及性能优化方面的工作。第 2 章:数据库性能优化第 2 章:数据库性能优化160161第二部分是算法部分,作者针对性地提出了三种设计来优化 B+树的写放大:确定性的 page shadowing 来解决页面原子写引入的写放大。通过页面本地增量日志来减少刷脏页引入的写放大。通过稀疏日志来减少 redo 日志引入的写放大。第三部分是实验部分,作者通过前面所述的三个方法实现了一个新的 B+树(我们称之为 B-tree 为以示区别),并详细比较了多种条件下的测试结果。背景部分2.1 现有的 B+树及其软件压缩我们熟悉的

279、开源数据库有很多都使用 B+树作为存储引擎,比如 MySQL、MongoDB、PostgreSQL 等,腾讯云数据库 TDSQL-PG 也是基于 B+树来实现的。B+树的结构如下方的左图所示,它通过一个个的数据页面构成一个树状结构以提供索引加速数据查找。常见的数据页面大小有 8k 和 16k,通过数据页面压缩可以减少 B+树的空间占用以降低成本,此外还可以减少 B+树的写放大。在前面所提到的基于 B+树实现的数据库中,MySQL、MongoDB 都实现了 B+树的软件压缩特性,TDSQL-PG 的商业化版本也有实现。软件压缩具有不依赖于特定硬件、灵活性高的优点,但美中不足的是,受限于现有的 I

280、O 4K 对齐的约束会产生一些额外的空间浪费。比如下方右图,一个 16K 的数据库页面经过压缩后产生了 5K 的数据。受限于 IO 4K 对齐的要求,当落盘时这 5K 的数据实际上要占用 8K 的空间,产生了额外的 3K 的空间浪费。常 见 的 B+树 压 缩 算 法 有 Lz4、zlib、ZSTD,后 面 所 提 的 LBA 则 是 指 logical block adressing,即逻辑上的磁盘块大小。2.2 存储硬件透明压缩我们先简单回顾下闪存的特性。如下图所示,闪存支持三种基本操作:读、写、擦除。闪存的读、写都以 page 为单位,一般是 4K,擦除则是以 block 为单位,一个

281、block 一般包含多个 page,比如包含 64 个 page。之所以闪存需要支持擦除,是由于闪存的写比较特殊,一个 page 只能在擦除之后一次写入,而不能更新。但从用户的角度来看,SSD 需要支持更新,因此我们在 SSD 里会有一个 FTL 层来实现该逻辑,即下图中间蓝色部分所示。CSD,顾名思义是一种带有计算能力的存储。如下图所示,一个通用的 CSD 在存储层可以支持 API 调用来进行计算下推。硬件透明压缩存储作为 CSD 的一种,支持在其内部对数据进行压缩和解压,后续我们提到的 CSD 主要是指带有硬件透明压缩的 CSD。对比最下方的两张图,左边是普通的 SSD,右边是 CSD。可

282、以看到普通 SSD 的 LBA 和闪存都是以页面为基础,且都是 4K 大小,可以由 FTL 直接进行映射。CSD 和 SSD 类似,都对外提供了 4K 的 LBA 接口,但 4K 的数据经过压缩后长度不一定,所以在 CSD 内部,FTL 的闪存管理需要支持对变长数据的管理,这也使得 CSD 的 FTL 变得更加复杂。如此一来 CSD 能带来什么好处呢?第 2 章:数据库性能优化第 2 章:数据库性能优化162163总体来看,CSD 的好处体现在它有更优的成本、更好的性能,但在此不做详细讨论,我们主要关注 CSD 的技术特点。CSD 可以实现逻辑地址和物理地址的解耦。如下方左图所示,对一个实际闪

283、存大小只有 4T 的 CSD 来说,它可以对外提供远大于闪存大小的 LBA。另外,如下方右图所示,对于稀疏数据的写入可以进行透明压缩而不产生空间浪费。基于 CSD 的这两个特性,我们在上层应用如数据库中,可以做一些针对性的设计,以进一步利用 CSD 的特性。2.3 B+树和 LSM-tree 的简要对比我们先回顾 LSM-tree 的写入流程。如右上图所示,一条数据记录的写入,会先写 log,再进入 memtbale,memtable 写满之后会变成 immutable memtable,再与 L0 的 SSTable 进行合并,上层的 SSTbale 也会在合适的时机与下层 SSTable

284、进行合并。我们再简要回顾 B+树的写入流程。一条记录的写入也是先写入 log,再写入 B+树的数据页面,当数据页面写满时就需要进行页面分裂,所以大部分时间 B+树的页面其实是不满的。另外,由于 B+树是直接操作目标页面,当内存不够时就会涉及到页面的淘汰问题,这时就需要刷脏页。极端场景是每写一条记录就需要刷一次脏页,如果一条记录是 128 字节,页面是 8k,那么对应的写放大就是 64 倍。B+树和 LSM-tree 在写方面的主要区别是:LSM-tree 是一个紧密排布的结构,可以占用更小的写空间以及产生更小的写放大。B+树的写放大与数据集的大小成正相关,与单条记录的大小负相关。对于数据完全可

285、以放入内存的场景,B+树可以产生比 LSM-tree 更小的写放大。因此这篇论文聚焦在大数据集以及小数据记录的场景,也就是说,B+树在该场景下写放大明显劣于 LSM-tree。作者通过在 CSD 上执行一个随机写入的测试,初步展示了 B+树和 LSM-tree 的差异。结果如右下图所示,可以看到 LSM-tree 占用的逻辑空间更小,这与 LSM-tree 的数据紧密排列有关,经过 CSD 压缩之后,B+树的物理空间占用也大幅度减小。此外,通过对比发现,LSM-tree 的写放大显著小于 B+树,因此本论文的目标就是需要通过改进 B+树的实现来大幅度减小 B+树的写放大。2.4 B+树写放大的

286、构成分析B+树的写放大构成主要包含三个部分:第 2 章:数据库性能优化第 2 章:数据库性能优化164165 为了保证事务原子性采用 redo log 引入的写放大;由于 lru buffer 淘汰或者 checkpoint 刷脏页而引入的写放大;为了保证页面写的原子性而引入的写放大,如 MySQL 的 double write。当考虑 CSD 的压缩特性时,公式 1 所列的写放大会引入表示压缩比的系数 ,其含义为 压缩后的数据大小比压缩前的数据大小,其位于 0-1 之间,也就是说,CSD 会降低写放大。算法部分3.1 Deterministic Page Shadowing算法 1 是通过确

287、定性的 page Shadowing 来减少 page 原子写引入的写放大,其本质上是 copy on write,一次页面刷盘写只需要写一次即可。具体实现如右图所示,1 个数据页面在磁盘上会有 2 倍的连续 LBA 空间与之对应,这个 LBA 被划分为两个 slot,两个 slot 轮流写入以实现 copy on write,从而保证每次写入不管是否成功,页面的前镜像总是保持稳定。在页面写入成功后,可以通过 trim 操作清理前一个 slot 的物理空间占用。在页面的写失败时,我们可以通过 checksum 进行检测,并通过页面前镜像+redo 恢复出最新的页面。对于写成功后 crash 保

288、留两个完整页面的场景,则可以通过比较页面的 LSN 找到最新的页面,并 trim 掉旧的页面。本方法带来的一个负面影响就是每次读需要产生额外的数据传输,但是作者认为 pcie 的传输能力远大于 flash 的速度,因此不成问题。此外,对于支持计算下推的 CSD,我们可以将该逻辑下推,也可以避免掉额外的数据传输。3.2 Localized Page Modification Logging算法 2 是通过本地修改日志来减少刷脏页引起的写放大。如右图所示,一个数据页面在存储层对应一个数据页面+一个 4K 增量 log 页面,两者的逻辑地址连续。在内存中数据页面被分为 k 个 segment,每个

289、segment 大小是 Ds,每次页面修改所涉及到的 segment 会在 bitmap f 里面记录。当需要刷脏页时,我们可以通过 bitmap f 找到所有的修改增量,并把它记为,再把、bitmap f、补零后的数据一起写入盘中。以一个 16K 的数据页面为例,对应的写放大会减少 75%。经过 CSD 透明压缩后,写放大可进一步减小。在该方法中,由于额外引入了增量日志,会造成一定的物理空间的放大,我们可以通过参数 T 来控制物理空间的放大。当增量日志大于 T 时直接刷脏页并重置增量日志,T 越大写放大越小,但是对应的物理空间放大就越大。读取页面时我们需要读原始数据页面+增量日志部分,再构造

290、最新的数据页面。由于读页面需要额外读所以会造成一定的读放大,但由于数据页面的地址和增量日志 LBA 是连续的,因此一次读请求即可解决。另外,增量日志相对较小,所以可以认为这部分的读放大影响较小。第 2 章:数据库性能优化第 2 章:数据库性能优化1661673.3 Sparse Redo Logging算法 3 是稀疏日志,用于减少 Redo log 造成的写放大。目前常见的 Redo log 的实现如左图所示,日志采用连续紧密排列,事务 1 提交时日志 L1 落盘,事务 2 提交时由于日志 buffer 未满,因此会在 L1 的 buffer 中追加 L2,L1 和 L2 一起落盘,事务 3

291、 提交时,L1、L2 需要连同 L3 再落盘一次,直到当前日志 buffer 页面被写满,才会开启新的页面。在该例子中,从 flash 的角度看,L1 被写了 3 次,后两次都是额外的写放大。当采用右图所示的稀疏日志时,每次日志写入都是用一个新的页面,页面经过透明压缩后落盘,则完全可以避免写放大的问题,另外由于透明压缩的存在也不会产生额外的空间占用。此外稀疏日志也是可以用于 LSM-tree 的。实验部分在实验部分,作者通过前述的三个优化方法实现了一个新的 B+树(命名为 B-树以示区别),同时还实现了一个 Baseline 的 B+树,并且与 Rocksdb、WiredTiger 做了比较全

292、面的实验对比。4.1 Experiments with Log-Flush-Per-Minute 在实验 1 中,Redolog 的刷盘频率被设置为每分钟 1 次,以屏蔽掉 Redo log 写放大的影响。通过不同的数据集大小、单条记录的大小以及 B+树页面大小设置,作者比较全面地评估了 B-树的特性,得出初步的结论:B-树的写放大已经比较接近于 LSM-tree,即右图中所有的橙色、绿色和紫色的部分比较接近。而 Baseline B+tree 和 WiredTiger 这两种标准的 B+Tree,它们的写放大都比较大。通过实验数据的对比,我们还可以发现:B+树的写放大与 Record siz

293、e 负相关,即 Record size 越小其写放大越大。以前面提到的极端例子为例,每条 Record 都会引起一次刷脏页,这时的写放大是 Page size 比 Record size。如果 Page size 固定,当 Record size 越小,其写放大会越大。B+树的写放大与并发数负相关,即并发数越大其写放大越小。我们可以理解为,当并发数比较高时,一个页面里可能会产生更多的更新,一次刷脏页就会有更多的 Record 进来。LSM-Tree 的写放大与数据集正相关。当数据集比较大时,做不同层之间的 merge,它的写放大会变大。4.2 Experiments with Log-Flus

294、h-Per-Commit 在实验 2 中,Redo log 的刷盘频率被设置为每次事务提交,这也是我们在实际生产中为保证不丢数据而常用的方法。作者用一个单独的图来展示 log 部分的写放大,我们可以看到 LSM-tree 和 B+树的 Redo log 的写放大都跟 Record size 相关,Record size 越小,写放大越大,同时与并发数负相关,随着并发数的上升写放大会减小。这是由于并发数的上升实际上会产生更多日志的合并,即每次写盘时会有更多的事务的提交日志一起写入,因此写放大减少了。对 B-树而言,由于采用了稀疏日志,所以其在不同 Record size 大小和不同并发数下都保持

295、很小的日志写放大。图 12 展示了一个考虑日志写放大后的整体写放大。我们可以看到,B-树相对 LSM-tree 而言,它的写放大进一步降低,除了图中红框部分外,其他场景都比 LSM-tree 更优。第 2 章:数据库性能优化第 2 章:数据库性能优化168169如果把两个不同的日志刷盘方式放在一起对比,我们可以发现,Redo log 的写放大对 B-树而言总体保持稳定,因为它使用了稀疏 Redo log。当其他 B+树和 LSM-tree 采用每事务提交时,两者的写放大显著提高,尤其是在并发数比较小的情况下,Redo log 日志所引起的写放大占比较高。4.3 Impact of Thresh

296、old T在实验 3 中,作者对算法 2 中提到的本地页面修改增量日志的算法做了验证。参数 T 表示当增量日志达到多少量时直接刷数据页面并重置增量日志,参数 Ds 表示页面分割的 segment 的大小。如图 14 和表格 2,我们可以看到当 T 越大时,B-树的写放大会越小,但物理空间占用则会越大,这与我们之前的理论分析一致,所以这两个参数在实际中需要 trade off。图 13 展示了压缩对于物理空间占用的影响。由于 B-树需要额外的 4k 页面占用,所以 B-树的逻辑空间占用显著高于其他 B+树,但在经过 CSD 压缩后,实际物理空间的占用放大则缩小了,相比 B+树已经不明显了。4.4

297、 Speed Performance Evaluation在实验 4 中,作者对比了三种场景下的 B-树相对于其他几种树的性能。在随机点查场景中,B-tree 和 LSM-tree 相当,但两者都小于 B+树。这与 B-tree 采用本地页面的增量日志刷脏页有关,因为每次读取需要重新通过增量日志来构造最新的数据页面,这会引入额外的开销。对随机范围查询而言,B-树和 B+tree 的性能相当,优于 LSM-tree。作者认为,由于一次查询多条记录,B-树引入的读开销占比变小了,所以其性能比较高。在随机写场景中,B-树稍微优于 LSM-tree,两者都显著优于 B+树,这是由于前两者都拥有更小的写

298、放大。但需要强调的是,由于 B-树和 B+树都涉及到 buffer 淘汰问题,所以虽然是只写负载但实际的 IO 却是读写混合的,因为 B-树需要读取额外的 4k 增量日志页面,因此读 IO 比 B+tree 有所放大,其相对 B+树的性能提升也就没有像写放大减少得那么显著。第 2 章:数据库性能优化第 2 章:数据库性能优化170171拓展与总结最后分享一个 CSD 和 PG 结合起来的有趣实验。PG 中有个名为 heap only tuple update 的机制,用于优化非索引列更新。如下图所示,当更新一个元组时,如果当前元组所在的 heap 表页面有空闲空间,就可以直接在当前页面插入一条

299、新的元组,再把老元组的 ctid 指向新元组即可,这样可以避免去找新的数据页面写入以及在索引页面中写入新的记录。PG 中还有一个 fillfactor 的参数来控制 heap 表的填充率,我们可以利用较小填充率来换取非索引列更新的性能。当引入 CSD 之后,还可以进一步避免造成物理空间占用的放大,比较完美地解决了以往需要进行物理空间占用和性能 trade off 的问题。综上所述,这篇论文提供了一个不同的视角去比较 B+树和 LSM-tree,让我们更深入地理解 B+树和 LSM-tree。作者通过三种实现比较简单但却非常巧妙的方法来改进 B+树,在 CSD 上也取得了很好的效果,为我们带来了

300、很好的启发。参考文献1 Closing the B+-tree vs.LSM-tree Write Amplification Gap on Modern Storage Hardware with Built-in Transparent Compression2 The True Value of Storage Drives with Built-in Transparent Compression:Far Beyond Lower Storage Cost3 Understanding Flash:Blocks,Pages and Program/Erases4 B+Tree inde

301、x structures in InnoDB5 PostgreSQL 与透明压缩6 The Internals of PostgreSQL7 WiscKey:Separating Keys from Values in SSD-Conscious Storage第 2 章:数据库性能优化第 2 章:数据库性能优化172173基于 LSM-Tree 存储的数据库性能改进韩硕腾讯云数据库高级工程师,北京大学博士研究方向包括分布式数据库、图数据库、存储引擎优化等,在 SIGMOD 等数据库领域顶级会议上发表多篇论文。2019 年博士毕业后加入腾讯计费,主要负责 TDSQL 存储相关的研发。LSM-T

302、ree(Log Structured Merge Tree)是数据库领域内较高效的 key-value 存储结构,被广泛应用于工业界数据库系统,如经典的单机 kv 数据库 LevelDB、RocksDB,以及被诸多分布式 NewSQL 作为底层存储引擎。本期将由腾讯云数据库高级工程师韩硕来为大家分享基于 LSM-Tree 存储的数据库性能改进,重点介绍近年来学术界对 LSM-Tree 的性能改进工作,并探讨这些改进措施在工业界数据库产品中的应用情况以及落地的可能性。以下是分享实录:LSM-Tree 基本结构LSM-Tree 全称为“Log Structured Merge Tree”,是一种基

303、于磁盘存储的数据结构。1996 年 Patrick O Neil 等人在信息系统期刊上发表了一篇题名为“Log Structured Merge Tree”的论文,首次提出 LSM-Tree 结构。相比于传统的 B+树,LSM-Tree 具有更好的写性能,可以将离散的随机写请求转换成批量的顺序写操作,无论是在 RAM、HDD 还是在 SSD 中,LSM-Tree 的写性能都更加优秀。作为高效的 key-value 存储结构,LSM-Tree 已被广泛应用到工业界数据库系统中,如经典的单机 kv 数据库 LevelDB、RocksDB,以及被诸多分布式 NewSQL 作为底层存储引擎,近日发布的

304、 TDSQL 新敏态引擎存储模块也运用了 LSM-Tree 结构。LSM-Tree 结构有较多优点:写性能强大、空间利用率高、较高的可调参特性、并发控制简单、有完备的修复方案等。LSM-Tree 的本质是基于 out-place update 即不在原地更新。下图展示了 in-place update 即原地更新与 out-place update 即不在原地更新的区别。在 in-place update 中,数据更新操作是将数据的新值直接覆盖旧值,但会带来随机读写问题。在 out-place update 中,数据更新操作则是将新值按时间顺序追加到文件末尾,通常会带有版本号用于标记新值或旧值

305、,一般为顺序写,因此写性能更好,但缺点是会产生冗余数据,在读性能和空间利用率上也无法与 in-place update 相比。当前主流的 LSM-Tree 基本结构如下图,分为内存和磁盘两部分。内存采用 MemTable 数据结构,可以通过 B+树或跳表实现。MemTable 用于接受最新的数据更新操作,维护内部数据 in-tree 逻辑上的有序。磁盘中的部分数据存储物理有序。数据按照层进行堆放,层次越往下,数据写入的时间就越早。每层内部按是否有序可划分为一个或多个 sorted run。一个 sorted run 内部的数据必定有序(通常指物理上有序)。sorted run 数据可进一步划分

306、为不同的 SSTable。当 MemTable 中的数据写满时,其会向 L0 进行落盘操作即 Flush 操作。如果 LSM-Tree 的某一层也达到容量阈值,就会向下合并,将同样的 key 的过期版本清除,即 compaction 操作。第 2 章:数据库性能优化第 2 章:数据库性能优化174175RocksDB 是基于 LSM-Tree 的存储引擎,其基本结构如下图。它与 LSM-Tree 基本结构在主体上保持一致,不同点在于 RocksDB 的内存中增加了 Immutable MemTable 部分,这是用于 Flush 操作的写缓存机制。当 MemTable 中的数据写满时,会先暂存

307、到 Immutable MemTable 中,再通过后台的异步线程将 Immutable MemTable 慢慢落盘到磁盘中的 SST 基本件。RocksDB 中还增加了 WAL 日志,用于 crash 时的数据恢复。LSM-Tree 支持的查询可分为点查与范围查询两大类,对应的执行方式如下:点查:先查 MemTable,再从 SST 中的 Level0、Level1.一层层向下探查,找到数据就返回。由于上层数据必定比下层数据的版本新,因此返回的都是最新数据。范围查询:每一层都会找到一个匹配数据项的范围,再将该范围进行多路归并,归并过程中同一 key 只会保留最新版本。LSM-Tree 性能的

308、衡量主要考虑三类因素:空间放大、读放大和写放大。第一类因素是空间放大。在 LSM-Tree 中所有写操作都是顺序追加写,数据的更新操作则是通过创建一个新的空间来存储新值,即 out-place update。与此同时,因为旧值不会立即被删除,因此会占用部分空间。实际上这部分冗余数据占用空间的大小要远大于有效数据本身,这种现象被称为“空间放大”。LSM-Tree 会利用 compaction 操作来清理旧数据从而降低空间放大。第二类因素是读放大。在 LSM-Tree、B+树等外存索引结构中,进行读操作时需要按从上到下的顺序一层层去读取存储节点,如果想读一条数据,就会触发多次操作,即一次读操作所读

309、到的数据量实际上要远大于读目标数据本身,从而影响读性能,这种现象被称为“读放大”。第三类因素是写放大。在 LSM-Tree 中,compaction 操作会将多个 SST 文件反复读取,合并为新的 SSTable 文件后再次写入磁盘,因此导致一条 kv 数据的多次反复写入操作,由此带来的 IO 性能损失即写放大。LSM-Tree 的初衷是想通过空间放大和读放大来换取写放大的降低,从而达到极致的写性能,但也需要做好三方面因素的权衡。EDBT 2016 的一篇论文首先提出 RUM 猜想(R 为 read,U 为 update,M 为 memory usage,RUM 为三者缩写)。该论文认为,三者

310、之间存在权衡关系,无法使得三个方面的性能都达到最优,因此必须在三者之间进行有效权衡。以 compaction 操作为例,其目的是保证数据的局部有序和清理数据旧值,即一个 sorted run 内部的多个 SST 文件中的数据是有序的,从而降低读放大。对一个查询而言,在一个 sorted run 中至多只需要读取一个 SST 中的一个数据块。如果完全不做 compaction 操作,即一直顺序写,LSM-Tree 就会退化为 log 文件,这时写性能达到最佳。因为只需要顺序写 log 即可,不需要做任何操作。但读性能将会处于最差状态,因为在没有任何索引、无法保证有序性的情况下,每次想读到固定的数

311、据项,就需要扫描所有的 SST 件。如果 compaction 操作做到极致,实现所有数据全局有序,此时读性能最优。查询时只需要通过索引和二分法即可迅速找到要读的键值的位置,一次 IO 操作即可完成,但代价是需要频繁进行 compaction 操作来维持全局有序状态,从而造成严重的写放大,即写性能变差。这就延伸出两种 compaction 策略:Tiering compaction:较少做 compaction 操作,有序性较弱,每一层允许有多个 sorted run。Leveling compaction:更频繁的 compaction 操作,尽可能增强有序性,限制每一层最多只有 1 个 s

312、orted run(L0 层除外)。第 2 章:数据库性能优化第 2 章:数据库性能优化176177Compaction 优化策略与分析Leveling compaction 策略中,每层只有一个 sorted run,sorted run 内部的数据保持物理有序。具体实现上我们以 RocksDB 为例。一个 sorted run 可以由多个 key 不重叠且有序的 SSTable files 组成。当第 L 层满时,L 层会选取部分数据即部分 SSTable,与 L+1 层有重叠的 SSTable 进行合并,该合并操作即 compaction 操作。Tiering compaction 策略

313、中,每层可以有至多 T 个 sorted run,sorted run 内部有序但每层不完全有序。当第 L 层满时,L 层的 T 个 sorted run 会合并为 L+1 层的 1 个 sorted run。因为每层允许有多个 sorted run,因此 SST 文件间可能会存在数据范围的重叠,compaction 操作的频率会更低,写性能也会更强。两种 compaction 策略各有优劣。Tiering compaction 因为 compation 操作频率低,过期版本的数据未能得到及时清除,因此空间利用率低,由此带来的查询操作的代价比较高。在极端情况 log file 即完全不做 co

314、mpaction 操作时,写入性能最优。Leveling compaction 则会更频繁地做 compaction 操作,因此数据趋向更有序。极端情况 sorted array 即数据达到全局有序时,此时查询性能和空间利用率最优。SIGMOD 2018 的一篇论文对上述两种 compaction 策略进行了详细的理论分析,分析结果归纳为下表,其分析结果与前述一致。理论分析过程如下:第 2 章:数据库性能优化第 2 章:数据库性能优化178179先从写入操作复杂度进行分析。I/O 以 block 为基本单位,一次 I/O 平均读写 B 条 kv,因此一条 kv 写一次磁盘的代价可以表示为 1/

315、B。在 leveling compaction 策略中,第 i 层向第 i+1 层最多合并 T 次,在第 j 层合并到第 i 层时,其可被后续的数据项反复进行 t-j 次合并。由此可得,每条 KV 在第 i 层即任意一层被平均 IO 的读写次数为 t/2 次,即 O(T)。从 MemTable 一直向下合并到最后一层即第 L 层,会得到平均写磁盘数即“TL/B”。以下图为例,每合并一次,第 i 层的增量不会超过 1/T。第 i 层通道从空到满,其最多向下合并 t 次,因此每条 kv 在每层平均被合并 2/T 次。在 Tiering compaction 策略中,每条 KV 在第 i 层时只会被

316、合并一次,因此从 MemTable 一直向下合并到最后一层即第 L 层,会得到平均写磁盘数即 L/B。以下图为例,在第 i-1 层向第 i 层合并时,会在第 i 层产生一个新的 sorted run,平均每条 kv 在每层只会被合并一次。第 2 章:数据库性能优化第 2 章:数据库性能优化180181再从空间放大率进行分析。space-amplification 定义为:过期版本的 kv 数与有效 kv 总数之比。在 Leveling compaction 策略中,最坏的情况为前 L-1 层的每条数据在最后一层即第 L 层中都各有一条未被清除的过期版本数据。每层容量为比值为 T 的等比数列,根

317、据等比数列求和公式,第 L-1 层的 kv 数量占总 kv 数的 1/T,最后一层则占(T-1)/T,因此空间放大为 1/T,空间放大率较低。在 Tiering compaction 策略中,允许每层最多有 T 个 sorted run,最坏情况下最后一层的 T 个 sorted run 之间都是每个 kv 的不同版本,即一条 kv 在最后一层的每个 sorted run 都出现一次,共 T 个不同版本,因此空间放大为 O(T)。下图将两者进行对比。Leveling compaction 的空间放大系数为 1/T,因此空间利用率较高。Tiering compaction 在最坏情况下,最后一层

318、的每个 sorted run 都是冗余数据,空间利用率较差。再从查询角度进行分析。点查使用 Bloom filter 进行过滤,将其分成两类:读穿透,即查询数据不存在。假设知道每次发生假阳性的概率,如果判定结果为假阳性,则需要读一次磁盘。在 Leveling compaction 策略中,每层只有一个 sorted run,实现完全有序,因此每层只需要查询一次 Bloom 过滤器。根据二项分布期望公式,推导出的期望读盘次数为 Le 的-m/n 次方。第 2 章:数据库性能优化第 2 章:数据库性能优化182183在 Tiering compaction 策略中,每层最多有 T 个 sorted

319、 run,最多可能查询 T 次 Bloom filter,在前述结构基础上乘以 T 的系数,根据推导出的期望读磁盘次数,其查询性能落后于 Leveling compaction。如果查询数据存在,因为发生假阳性的概率远小于 1,因此大部分情况下,无论是 Tiering compaction 还是 Leveling compaction,都只需要读一次盘。再从范围查询角度进行分析。该论文将范围查询分为短范围查询和长范围查询。短范围查询指返回 block 数量的大小不超过最大层数范围的两倍;反之则为长范围查询。通过统计公式发现,短范围查询在归并前的各版本数据趋向于均匀分布在各层,因此 Leveli

320、ng compaction 和 Tiering compaction 的查询复杂度分别为 O(L)和 O(TL)。长范围查询在归并前各版本数据大部分来自最后一层,因此上述两种策略的代价分别为返回 block 数的大小、返回 block 数的大小再乘以 T,因此 Leveling compaction 比 Tiering compaction 的读性能更好。第 2 章:数据库性能优化第 2 章:数据库性能优化184185 通过上述理论分析,可以得到如下结论:Tiering compaction 的写放大低,compaction 频率低,其缺陷为空间放大高、查询效率低,更利于 update 频繁的

321、 workload;Leveling compaction 的写放大高,compaction 操作更频繁,但空间放大低,查询效率高。尽管 Tiering compaction 和 Leveling compaction 的空间放大不同,但导致空间放大的主要原因相同,即受最下层的过期版本数据影响。越往下的层,做一次 compaction 的 I/O 代价越高,但发生的频率也更低,不同层之间做 compaction 的期望代价大致相同。点查、空间放大、长范围查询的性能瓶颈在 LST-tree 的最下层,而更新操作则更加均匀地分布在每一层。因此,减少非最后一层的 compaction 频率可以有效降

322、低更新操作的代价,且对点查、空间放大、长范围查询的性能影响较小。降低写放大基于上述理论分析,该论文提出混合 compaction 策略即 Lazy Leveling。它将 Leveling 与 Tiering 进行结合,在最后一层使用 Leveling 策略,其他层使用 Tiering 策略,即最后一层只能存在唯一的 sorted run,其他层允许存在多个 sorted run,从而有效降低非最后一层做 compaction 的频率。下表是采取 Lazy Leveling 策略后的性能汇总表,其中,绿色部分表示效果较好,红色部分表示较差,黄色部分代表适中。从下表可以看出,Lazy Level

323、ing 的更新操作(update)性能优于 Leveling,接近于 Tiering。这是由于在前 L-1 层维持 Tiering 策略,做 compaction 的频率更低,写放大低。但 Lazy Leveling 的空间放大接近于 Leveling,远好于 Tiering。这相当于结合了两种策略的优势。对于点查(point lookup),论文中分别分析了查找不存在 kv 和 kv 在最后一层两种情况,并基于论文 Monkey 的思路对每层的 bloom filter bit 进行了优化,可以达到与 Leveling+Monkey 策略相匹配的点查性能。对于长范围查询,Lazy Level

324、ing 可以做到与 Leveling 一致的性能,而短范围查询则退化至接近 Tiering 的水平。论文对此进行总结:使用一种单一 compaction 策略,不可能在上述所有操作中做到性能最优。Lazy Leveling 本质上是 Tiering 与 Leveling 策略的折衷加调优,在侧重于更新操作、点查和长范围查询的 workload 上性能较好;Leveling 适用于查询为主的 workload;Tiering 则适用于更新操作为主的 workload。论文通过建立代价模型将 Lazy Leveling 进行推广,将其称为 Fluid LSM-Tree。假设有两个参数,分别为 Z

325、和 K。Z 表示最后一层允许有最多 Z 个 sorted run;其他层则允许有多个 sorted run。K 为限制参数,表示每层最多有 K 个 sorted run。当每个 sorted run 的值达到 t/k 时会向下做 compaction。通过这样的方式,将 Lazy Leveling 进行泛化推广,通过不同的 workloads 来调节参数 Z 和 K。下表是将参数 Z 和 K 添加进去后的 Fluid LSM-Tree 的代价利用分析结果。但这在实际操作中会遇到问题,即如何根据 workloads 来选取参数 Z 和 K。论文采取搜索方式去找到针对某个 workload 代价模

326、型最小的参数 K 和 Z。在真正实现时,我们对 workload 的认知相对有限,想要找到最优的参数 K 和 Z 会较为困难,且该模型总体比较复杂,实践性有限。RocksDB 在某种意义上也采取了混合 compaction 策略。RocksDB 的 L0 层的 SST 之间,K 允许互相重叠,即允许多个 sorted run。这实际上是 Tiering 策略,因为其落盘的 Flush 的性能第 2 章:数据库性能优化第 2 章:数据库性能优化186187更优。在 L1-Ln 层中采取 Leveling 策略,能够更及时地清理过期数据,提高空间利用率。RocksDB 将每个 sorted run

327、 切割为多个 SST 文件,每个 SST 文件都有大小阈值。其优点在于每次发生 compaction 时,只需要选取一个或者少量的 SSTable 与下层有 KV 重叠的 SSTable 进行合并,涉及到合并的有效数据量处于可控范围。SOSP 2017 中有一篇名为 PebblesDB 的论文,提出了 compaction Guard 概念。每层分割为多个分片,分片间的分割称之为 compaction Guard,即一次 compaction 操作不会跨越该分界线,每个分界线内部 SST 之间允许重叠,可理解为 Tiering compaction。这种做法最大的好处是可以保证数据项从第 i

328、层向第 L+1 层进行 compaction 时,写入只有一次。因为它将原生的 LSM-Tree 进行分隔,形成 FLSM 结构。其坏处是读性能会变差。因为找到对应分片后,分片内部如果存在多个 SST,我们就不知道数据的真正存放位置,这时需要借助 Bloom 过滤器来对每个 SST 进行探查,且即使使用 Bloom 过滤器,其发生假阳性的期望次数也会增加。在 RocksDB 实践过程中,我们发现它实际上提供了一个 SST Partitioner 的类,通过类可以在代码实现上保证分片,但与 PebblesDB 不同的是,其在分片内部保持 SST 不重叠,仍然采取 Leveling 策略。其好处是

329、读性能不变。虽然写性能没有得到提升,但更加适配于扩容场景,便于迁移及迁移后数据的物理删除操作。以 TDSQL 新敏态引擎架构为例。每层的存储节点称为 TDstore,一个 TDstore 真实的数据存储相当于维护一个 LSM-Tree 结构来存储 KV 数据,再将 KV 数据按照区间划分成不同 Region。需要扩容时,我们会增加若干个存储节点,再将原来节点上的部分 Region 迁移过去。Region 迁移涉及到 Region 数据的迁移。如果采用前述的分割策略,将 LSM-Tree 的每一层基于 Region 边界进行分割,将 Region 从相对完整的 SST 文件中捞取出来,并插入到新

330、增的 TDstore 存储节点中。因为 SST 根据边界进行分割,我们可以相对完整地将 Region 内部的数据迁移或删除,因此 Region 数据迁移的性能会得到提升。第 2 章:数据库性能优化第 2 章:数据库性能优化188189降低读放大降低读放大必须结合布隆过滤器。具体实现为:每层设置一个布隆过滤器,通过布隆过滤器进行过滤,减少无效读磁盘 block 的次数。下图为前述的结论表。当数据查询不存在即发生读穿透时,发生假阳性的概率为 e 的-m/n 次方。只要发生假阳性,就必须去读数据块,进行一次读磁盘操作。在 Leveling 中,每层只有一个 sorted run,查一次 Bloom

331、filter 的概率为 e 的-m/n 次方,根据期望公式推导出的期望读盘次数为 L 乘以 e 的-m/n 次方。Tiering 中则是 T 乘以 L 再乘以 e 的-m/n 次方。假设点查时使用布隆过滤器进行过滤,每层都建立一个布隆过滤器即固定的 bits 团,读性能就可得到提升。2017 年一篇名为 Monkey 的论文对布隆过滤器进行优化。在 LSM-Tree 不同层设置不同的布隆过滤器的 bits,可以有效降低 IO 开销,从而减少读穿透的期望次数。其结论分析如下:第 i 层设置(a+b)(L-i)的 bits。由于最后一层数据量最大,只用一个 bit 来 hash 到布隆过滤器中。相

332、比于前述公式,这可以减少一个 L 系数。因为 Bloom filter 要在内存中持有,而每层的 bits 不同,因此总体上降低了布隆过滤器的内存开销。其点查的读性能和整体 Bloom filter 的空间利用率都会得到提升。SIGMOD 2021 一篇名为 Cuckoo 的论文,采用布谷鸟过滤器代替布隆过滤器,从而优化读性能。布隆过滤器的优化方式为:LSM-Tree 的每层甚至每个 SST 文件都会维护一个 Bloom filter,查询时需要从 MemTable L0 到 Ln 一层层向下探查,每次探查时先走一遍相应的布隆过滤器。该论文提出替代方案,不需要每层都维护一个布隆过滤器,而是选择

333、维护一个全局的布谷鸟过滤器。先查全局的布谷鸟过滤器,如果反馈不存在则表示数据不存在,如果反馈存在,考虑到可能发生假阳性,我们再去查数据文件。实现原理为:全局的布谷鸟过滤器里记录了 Level ID。如果在布谷鸟过滤器中有 mash,则不需要继续向下探查,可以直接找到其对应的 Level ID,找到对应层、对应的 sorted run 去读磁盘,看数据是否存在。布谷鸟过滤器对接两部分:其 hash map 或签名,再加上位置 ID 即 level ID。布谷鸟过滤器的工作原理如下图,相当于维护下图右上角中的两个桶。我们通过两次哈希算出 key 所要插入的具体位置,所对应的两个桶称为 b1 和 b2。b1 直接拿 key 进行擦洗,b2 不需要用 key 的原值,在算出签名指纹后再做哈希,因此每个 key 会有两个可以存放的位置。布谷鸟过滤器哈希的数据插入过程如下:假设要插入 item X,通

友情提示

1、下载报告失败解决办法
2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
4、本站报告下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。

本文(腾讯云:腾讯云数据库技术实践精选集(2022年版)(232页).pdf)为本站 (gary) 主动上传,三个皮匠报告文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知三个皮匠报告文库(点击联系客服),我们立即给予删除!

温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。
会员购买
客服

专属顾问

商务合作

机构入驻、侵权投诉、商务合作

服务号

三个皮匠报告官方公众号

回到顶部