作者马浩翔日前,字节跳动技术社区ByteTech举办的第四届字节跳动技术沙龙圆满落幕。本次沙龙的主题是《字节云数据库架构设计与实战》。沙龙中,字节跳动基础设施数据库开发工程师马浩翔与大家共同探讨《从单机到分布式数据库存储系统的演进》。本文基于分享整理。存储系统概述存储系统是指一系列能够高效存储和持久化用户数据的系统软件。在众多存储系统中,以下是三类主流存储产品及其特点分析:块存储底层语义,基于块编程;接口简单:在LinuxIO软件栈中,如果要直接使用块存储,必须基于LBA编程来使用,所以接口比较简单,块存储本身是整个存储软件的底层stack,这使得块存储使用起来不是很友好;追求低延迟和高吞吐量:开发块存储系统,在设计目标上,我们往往追求超高性能,体现在超低延迟和超高吞吐量上。但是考虑到块存储的接口确实过于简单,往往只有一些追求超高性能的系统会直接基于块存储构建,然后构建自己的应用层缓存。对象存储公有云上的王牌存储产品:IT时代,“一切都是数据”的趋势迅速催生了对象存储系统。尤其是字节跳动业务,使用对象存储系统处理视频、图片、音频等非结构化数据,最具有自然语义;非结构化数据提供了不可变的语义:图片或视频等非结构化数据一旦上传到对象存储系统成功,就无法就地修改,只能通过删除旧数据,重新修改来完成“修改”逻辑上传新数据;成本优先:一般对单个操作的延迟要求不高,但非常注重系统吞吐量&存储成本。文件系统接口语义丰富,通用性强:遵循POSIX/weakPOSIX语义,文件系统中可以找到Open、Write、Read等多种数据操作接口。它有更多的开源分布式实现和良好的生态。一般对时延要求不高,但关注系统吞吐量和存储开销。单机数据库存储分析单机数据库存储需要从两个方面来分析:内存层和持久层。在内存层,我们只讲关系型数据库。其内存数据结构的特点可以概括为:万物皆“树”。我们以最常见的B+树为例。B+树有以下突出特点:内存操作效率非常高:B+树查找时间复杂度为日志级别;并且B+树的叶子结点组成了一个链表,非常有利于在内存中对数据进行Scan。磁盘操作效率高:B+树的Fanout足够大,树层数少,chunky,可以减少磁盘IO次数;同时,B+树的非叶子节点只存储索引数据,叶子节点存储实际数据,可以大大压缩树的高度,进一步减少磁盘IO次数。高度统一的数据结构:数据和索引都可以直接组织成B+树,代码的可维护性、可读性和开发效率更好。当然,只有内存数据结构是不够的。我们还需要设计一个高效的磁盘数据结构。下图是从内存数据结构到磁盘数据结构的数据持久化过程:左边的虚线框是InMemory结构的示意图。比如我们要修改A页的一行数据,对其中一个字段自增,自增值为2。自然会产生一个数据库的物理操作日志,即RedoLog,用来描述我们对A页面的修改。同时,数据库在事务执行过程中,可能会产生大量的临时数据(图中的Temp数据),需要持久化当内存不够时。右侧虚线框为InPersistentLayer示意图。假设我们使用一个友好的文件系统来持久化内存数据,我们需要设置不同的文件让它们各司其职。比如图中蓝色文件存放Page,绿色文件存放RedoLog,粉色文件存放临时数据。如果数据库崩溃了,我们会在恢复阶段将数据定位到各个文件中,结合RedoLogFile和PageFile来恢复数据库数据。另外,如果持久化直接基于块存储,数据库本身的存储引擎需要管理LBA,buffercache等逻辑需要在用户态实现,也是可行的。那么基于单机FS/块存储做持久化会遇到哪些问题呢?通过下面典型的单机数据库系统架构图,我们可以发现三个问题:单机容量瓶颈:数据库进程在Database层运行在单机服务器上,大量的本地磁盘占用安装在服务器上以实现数据持久化。然而,物理服务器上能够挂载的磁盘容量总是有限的,这就导致了单台服务器的容量瓶颈。扩容难:当容量、CPU、内存等资源不够用时,就需要扩容。在单机时代,扩容意味着将数据从一个磁盘迁移到另一个磁盘。但是无论我们使用网络还是有线连接,都需要一定的时间,可能会导致业务长期停顿(不可用),所以扩容非常困难。多个独立的数据,成本高:如果我们要在“复制集”之间或者主备机之间做数据冗余或者数据同步,那么每增加一个算力(增加一个新的计算节点)就需要增加一个存储点冗余,会导致存储成本的增加。分布式数据库存储分析为解决单机数据库存储系统面临的问题和挑战,字节跳动数据库团队调研了业界主流的分布式数据库解决方案。MyRocks:MySQL+RocksDB需要注意的是,MyRocks不是分布式数据库或分布式解决方案,它是单机SQLoverkv的典型代表。核心思想:用RocksDB替代InnoDB。使用RocksDB可以有效缓解单机容量瓶颈问题;特点:第一,数据可以进行高比例压缩。RocksDB实现了比较优秀的压缩算法。根据实际研究结果,在关系型数据库场景下,基本可以达到2-4倍的压缩比,可以有效缓解单机容量瓶颈。比如单机本来挂载10块盘,只能承载10TB的数据。使用RocksDB可以帮助单机在不改变硬件条件的情况下承载更多的数据,比如20TB或者30TB;其次,顺序写入性能更好,这也是HDD时代出现LSM-Tree数据结构的核心原因。难度:Compaction会造成性能抖动,兼容性一般。众所周知,RocksDB是基于LSM-Tree构建的,不可避免地会遇到一些典型的基于LSM-Tree的系统的问题。RocksDB虽然对顺序写特别友好,但一定程度上牺牲了读性能——RocksDB在读过程中会触发Compaction,可能会造成性能抖动,造成前台写停滞;同时,这类SQLoverkv解决方案的兼容性表现也比较一般。AmazonAurora:Computingandstorageseparation核心概念:计算和存储分离,LogisDatabase。特点:架构灵活,存储层有特定的数据库计算逻辑。除了存储能力,它还具备解析RedoLog、重放生成数据库页面、维护多版本数据的能力。优点:兼容性强,读扩展能力优秀。基于共享存储系统的特点,AmazonAurora的读计算节点具有更好的可扩展性,可以实现一主十五从的部署形式,兼容性和读可扩展性更好。Spanner系:Shared-Nothing核心理念:计算存储分离,Share-Nothing。特点:Spanner系统的数据库系统一般是基于分布式k-v存储构建的。存储层保证交易特性,计算层做成无状态的节点进行纯计算。难点:解决当前数据库生态兼容问题和分布式k-v系统的热点问题比较麻烦。最佳实践:veDB分布式存储系统基于上述字节数据库团队的研究成果,我们设计了veDB分布式存储系统,以解决单机数据库存储系统所面临的问题和挑战。本节将主要介绍veDB分布式存储系统。系统目标和核心技术特征。系统目标在设计理念上,我们希望存储系统实现以下四个主要目标:极致弹性:存储节点与计算节点解耦,随时弹性伸缩;ExtremeEaseofUse:构建一个一刀切的存储系统,而不是专用存储,必须兼容多个主流数据库(MySQL&PostgreSQL&Mongo...);极高的成本效益:低延迟和低成本;极致可靠:高性能、高可靠的备份和恢复能力。基于以上系统目标,数据库团队设计开发了veDB分布式存储系统,如下图所示:从图中可以看出,分布式存储层建立在LogStore和PageStore两个子系统的基础上,其系统特性与我们的设计目标相呼应。高弹性:存储层可独立扩缩容,计算层完全无感知;性价比高:LogStore高性能Log存储,PageStore低成本Page存储;兼容性好:LogStore和PageStore都支持多种DBEnginesPlug-in;高可靠性:PageStore支持Segment级别的PITR功能。以下核心技术主要从研发背景(Problem)、解决思路(Solution)、解决效果(Outcome)三个方面介绍veDB分布式存储系统的五项核心技术。分布式数据模型问题:从单机FS到分布式存储,需要一个高效的数据布局模型。基于单机文件系统或块存储系统实现数据持久化相对简单。我们可以直接申请一批LBA或者一批文件来存储数据,然后控制并发,但这对于分布式存储来说并不容易。从上图可以看出,最顶层的Tablespace代表一个数据库表,其中可能包含数百万甚至上千万的Page数据(数据库的基本管理单位)。但是存储系统的管理单位不能是Page——Page的粒度太小,往往只有KB级别。如果存储层管理数据的粒度太小,可能会导致元数据膨胀,增加管理成本。解决方案:Tablespace->Segement分布式映射。基于以上问题,我们可以在存储层使用比较大的管理单元Segment进行数据管理。此时数据库的管理单位是Page,存储系统的管理单位是Segement。Tablespace和Segement之间必须存在映射关系。这种映射关系可以根据不同数据库引擎的数据管理空间大小要求来设置。MySQL和PostgreSQL的映射规则可能大不相同。上面的示意图展示了最简单的模2规则,我们还可以开发其他更复杂的破解规则,这里不再赘述。当我们将Pages拆分成对应的Segment时,数据库就不需要管理数据复制的逻辑了。不管底层存储是多副本还是EC策略,透明复制完全可以由存储系统完成。数据库就像使用一个独立的文件系统一样简单。Outcome有天然的负载均衡;分布式分散可以最大程度地实现并行计算;Scalein/out简单,只需要部分Segement数据rebalance,免去了整个数据库表TB级数据在硬盘间搬迁的复杂过程。Log-OnlySegementProblem:数据冗余成本高,需要降低存储成本。解决方案:开发Log-OnlySegement,节省不必要的Page拷贝空间。什么是Log-only段?关系数据库通常包含日志数据和页面数据。在存储层,在存储多份Log数据后,我们可以有选择地只重放一部分Log数据来生成Pages,而其他部分Log数据不变,不生成任何Page数据。以上图为例,Rep_0和Rep_1都是由Log数据生成的各种版本的Page数据,而Rep_2是Page数据的空副本,只包含Redolog。我们都知道Redolog和Page数据的数据大小比例被夸大了,Page数据的大小可能是Redolog的几倍甚至十倍。因此,上述方法可以大大节省单机的Page存储空间。成果:结合单机引擎的压缩算法,可以将存储空间从3.x->1.x放大,可以更好的缓解成本问题。高性能IO引擎问题:存储层的写入性能很容易成为系统性能的瓶颈。如何解决?解决方案:全异步IO+无锁结构+并发分解。当数据库向LogStorage提交一个Redolog时,LogStorage中会有一个无锁的Ringbuffer来对Redolog进行有序的组织,然后我们将RedoLog的Ringbuffer线性切割成固定长度并同时分解它。到底层存储的blob单元。Outcome:4KB+depth8,writelatency~100+us,更好的支持了数据库传递日志的性能需求。PITRPITR(Point-in-timeRecovery)是指我们可以快速恢复过去某个时间点的数据库快照。问题:如何快速备份和恢复,减少对前端业务的影响?解决方案:基于Segement的高并发PITR机制,Segement之间互不影响。前面提到,存储层的管理单元是Segement,我们也可以基于Segement进行备份和恢复。这样做有两个好处:第一,计算层完全透明,计算层完全不知情,计算层的性能不会抖动。其次,基于Segment,可以实现自然的并发分散,所以备份恢复也可以实现并发恢复。Outcome性能优异,可在~15分钟内恢复1TB数据;可扩展性强,不受数据大小的影响。与数据大小分离。因此,无论数据大小,只要备份和恢复资源充足,都可以达到恒定级别的备份和恢复性能。多计算引擎插件问题:数据库团队希望统一存储层能够支持不同的数据库引擎,实现100%的兼容和快速访问。解决方案:WriteAheadLog+LogReplay=anyPageData。基于本地存储引擎的k-v结构,或者基于裸块设备抽象出一个比较通用的数据结构,可以高效的存储Page数据。同时我们在SDK端和Server端都做了Log解析插件。接入一个新的数据库引擎,只需要提供适配存储接口的日志插件,即可快速对接各种数据库计算引擎。成果:基于统一接口,计算引擎只需要提供Logparse+Replaylib即可访问veDB存储层。统一存储层已经支持MySQL、PostgreSQL、MongoDB计算引擎,还在不断扩展中。数据库存储系统:What'sNext在谈到数据库存储的未来演进时,我们首先可以思考哪些因素会引发数据库存储架构的变化和演进?答案可能包括:存储架构本身的革命,数据库理论的突破,或者新硬件的冲击引发的存储系统架构的迭代。基于这三个方向的思考,我们总结了以下数据库存储系统的演进趋势:HTAP/HSAP我们总结的第一个趋势是HTAP/HSAP系统将逐渐爆发。在HTAP/HSAP系统中,“实时”是第一个关键字。为了支持实时性,存储系统可能会发生架构演化和变化,因此我们需要探索:行列存储一体机:既要存储行型数据,又要存储列型数据。近实时,写时计算:我们需要在存储层实现写时计算逻辑来支持实时性能。AIEnhancementAI技术被广泛应用于各个领域。具体在数据库存储领域,我们可以利用AI技术完成以下工作:存储参数调优;智能存储格式:利用AI技术进行智能行存和列存格式转换,AI可以提醒我们什么时候转换,什么时候绝对不转换,避免格式转换给前台业务带来的性能开销。硬件革命在硬件革命趋势方面,我们总结了三个革命方向:存储介质革命:在过去的几年里,我们可能更关注SSD和HDD。目前,我们正处于SSD向持久内存过渡的风口浪尖,那么如何利用持久内存来定制软件架构呢?目前在文件系统端已有一些研究,但在数据库端公开实践的并不多。计算单元变化:CPU产品从多核变为众核(从96c到192c、384c)。如何利用多核能力?对于非计算密集型存储系统,多余的计算能力是否可以用来加速数据库运算?是否需要重新设计一些无锁数据结构?以上都需要认真考虑。网络设施的变化:RDMA、可编程P4交换机等新的网络设施可能会对我们的软件架构,尤其是分布式存储架构产生更大的影响。因此,我们需要在存储方面进行调整。
