《Write Optimized B-Trees》读书报告

论文原作者:Goetz Graefe, Microsoft。我读完这篇论文后颇有收获,所以写了一篇论文报告,旨在更精炼准确地阐述论文核心思想。

摘要:论文提出了一种方法,这种方法可以优化B树索引写性能的同时尽可能不损失读性能。论文修改了传统B+树节点的数据结构,去除兄弟节点之间的指针链,同时显著优化了页面迁移事务,写优化B树的实质就是调用论文所优化的页面迁移事务,实现高效大规模写操作。论文还论述了写优化B树相对于传统B+树和日志结构文件系统的优势。

1 引言

在磁盘上,大规模的写操作是有好处的,也就是,写操作不会理解反映到磁盘上,而是积累在内存I/O缓冲区,当然重做日志仍然是需要的,否则事务的持久性就会成问题。本文修改了传统的B+树,实现了对于B+树内部节点以及叶子节点的大规模读写,同时支持直接更新磁盘或者仅仅在磁盘上写一个日志,论文提出的算法还简化了其他的B树操作,比如说范围锁,比如说数据压缩。论文提出的算法保留了各个不同粒度的锁操作,保留了事务的ACID,保留了搜索性能,最重要的是:它增加了高效的页面迁移操作以及写操作。

在(论文发表的)20年前,有9成操作是读操作,只有1成是写操作,但是现在,三分之一的操作都是写操作。日志结构文件系统曾经被发明出来,主要就是为了加快写操作,但是一个很严重的问题在于,日志结构文件系统提升写性能的同时,降低了读的性能,因为每次都操作都需要去读日志。更何况,日志结构文件系统有一个中间层,引入了多余的开销。我们希望找到一个平衡点:传统的算法优化读操作,日志结构文件系统优化写操作,而现在提出的算法支持混合策略。所谓混合策略,对于不同的节点,可以用不同的操作模式,比如一些比较热门的表或者节点就用写优化的方式来更新,B树的上层节点也用写优化的方式来更新。

2 数据结构与算法

2.1 新的设计

论文修改了传统B树的数据结构和以及更新算法,主要包括:1)去除兄弟节点之间的指针链,传统B树的指针链本来是用来方便游标操作的。2)对每个节点增加两个边界码“fence key”,这两个边界码定义了一个范围,表示这个节点中的每个搜索码都必须在这个范围之内。并且每个节点的边界码和父节点中的分割码是完全一样的,换句话说,引入便捷码相当于是引入了冗余。3)当一个内存缓冲区中的脏页被写回磁盘的时候,并不会更新旧的页面,而是把页面写到磁盘上的新的位置,这种方法可以显著减少日志操作,只需要单一的重做日志就可以记录整个页面迁移操作,本文将在这个新的数据结构下来探讨在如何进行写操作,分裂操作,合并操作。《Write Optimized B-Trees》读书报告

(1)写操作的过程是:首先把需要更新的节点读入内存缓冲区,然后在缓冲区中更新节点,这样缓冲区中的数据和磁盘上的数据就已经不一致了,但是我们不会急于更新磁盘上的节点,只有有重做日志,就不担心数据会丢失。在进行了很多很多次更新之后,在合适的时间,也就是下一个检查点,系统把内存中的节点写到磁盘上的另一个地方。注意这里有一个和普通缓存操作很不一样的地方,那就是一个节点被写到另一个位置,相当于页面被迁移了,写到另一个地方的原因是什么呢?等一下讨论迁移事务的时候会变得清楚。

(2)分裂操作的过程是:在内存I/O缓冲区创建一个新的节点,然后进行写操作,在合适的时间把内存节点写到磁盘上一个新的空间。当一个新的节点在内存中被创建的时候,它存在于一个虚拟的磁盘设备中,具有虚拟的页号,仍可以和其它页面一样调用页迁移事务。

(3)合并操作过程实际上就是写操作,把一个页面的内容写到兄弟节点中,然后回收此节点的空间即可。

2.2 设计探讨

前文中的写操作会带来的一个很严重的问题:“涟漪效应”。设想在某一个检查点,一个叶子节点被写到磁盘上的新的位置,父节点的指针需要更新,所以父节点又需要被写到磁盘上新的位置,如果兄弟节点之间有指针链存在的话,那么兄弟节点的指针也要更新,那么兄弟节点也需要迁移到磁盘上的新位置。这是一个递归的过程,导致的结果就是:整颗树中的每个节点都要迁移一遍,开销非常大。缓解这个问题的办法自然就是,去除兄弟节点的指针链,这样至少就不用更新兄弟节点了。

此外还有其他的一些问题需要讨论。

(1)首先是引入边界码“fence key”带来的空间开销,因为一个指针是很短的,但是一个key可能是很长的字符串。一个解决的办法是后缀截断,就是说不需要存储整个“fence key”,只需要存储一个前缀,就可以大致表示出这个节点的范围。

此外边界码的存在有利于前缀的截断。由于边界码定义了搜索码的范围,如果两个边界码具有相同的前缀,那么也就意味着页面内的每个搜索码都具有相同的前缀,也就方便了前缀截断操作。

(2)游标扫描操作,这的确是一个问题,如果有一个很长的游标操作,由于兄弟节点的指针链被去掉了,所以如果一个游标读完了一个节点,想要跳到下一个节点的时候,那么它就不得不利用 “fence key”,重新从根节点重新搜索下一个节点的位置。这是本方法的一个很重要的缺点。

不过论文中也提到,对于一个高性能的数据库系统而言,预读是很重要的操作,也就是说当游标访问某个叶子节点的时候,附近的节点往往已经在内存当中了,所以游标扫描的性能并不会下降太多。

(3)一致性检查。指针链还是这里的“fence key”,它们实际上都是冗余,有一个重要作用是检查硬件有没有错误,比如说一个“fence key”,在正常情况下在一对兄弟节点中,左边节点的上边界应该完全等于右边节点的下边界,并且这个边界码应该和父节点中的分割这两个节点的separator key完全相等,如果程序发现不相等了,就说明数据已经不一致了。

(4)范围锁。这是这个方法的优点,由于“fence key”本身已经定义了一个范围,它可以简化并加速范围锁的操作,避免了传统B树范围锁操作中需要的复杂代码。

3 页面迁移

3.1 页面迁移事务

接下来探讨页面迁移事务,传统数据库中页面迁移主要用来清理碎片。所谓页面迁移,实际上就是把页面从一个位置移动到另一个位置,就像前文中描述的操作。无论是传统的数据库还是现在的方法,都需要定期页面迁移操作,一个原因就是要尽可能保持叶子节点的逻辑顺序和物理顺序相同,因为磁盘有一个寻找时间,逻辑顺序和物理顺序相同,扫描的速度才能比较快。页面迁移也可以被看作事务,也需要ACID,需要日志。本文的一个重要的论点就是如何减少页面迁移操作的日志,从而提高页面迁移的性能。

先不考虑日志的问题,页面迁移的具体过程为::将磁盘上的页面复制到内存I/O缓冲区,交换两个内存页面的内容(或者仅仅交换两个页面的描述符),然后分别将两个页面写到磁盘上,最后再更新父节点以及兄弟节点上的指针。

3.2 迁移日志

接下来探讨页面迁移过程中的日志,本文探讨了三种记录迁移事务日志的方法。

(1)“全日志”方法:这种方法基本等同于写前日志,意思就是说在页面迁移过程中,对于每一个数据项的更新都需要一个日志,当系统从异常中恢复的时候,如果日志存在,系统就可以把写过但是还未提交的内容从日志中复制出来。

(2)“强制写”方法:不会针对每一个数据项都记录一个日志,仅仅是记录旧节点的位置,当系统从异常中恢复的时候,如果日志存在,那么就重新执行一次页面迁移操作。

(3)“无日志”方法:这是论文中认为最好的办法,并不是说完全没有日志,而是说只需要单一日志就可以记录整颗B树的迁移操作。我们会在旧页面保存一个映像,包含一个日志顺序号,当系统从异常中恢复的时候,系统会检查日志中的日志顺序号和页面中的日志顺序号是否相等,如果不相等,那么整颗树的页面迁移操作重新开始。

4 写优化的B树

回到本文所提出的写优化B树,写优化的B树的实质上就是调用前文所讲到页面迁移事务,并且采用第三种日志方法,从而提高页面迁移的效率,从而也就提高了B树的写操作效率。具体来说就是,写操作不断地在内存缓冲区中积累,直到某个检查点,我们就调用页面迁移事务,把页面写到新的位置,因此我们可以明白写到新的位置的原因:可以用单一日志记录整个事务,从而避免了大量的写前日志,可以提高写的性能。

在具有大量写操作的场景下,设想在传统B树中采用大规模写操作的策略,也就是等待写操作在I/O缓冲区中积累而不急于写入磁盘(当然仍然需要重做日志),知道在某个检查点才将更新同步到磁盘上,但是更新磁盘数据的时候不调用页面迁移事务,而是直接更新原来的页面,那么在更新的时候就不可避免要写前日志,对每一个数据项都需要记录日志,这样才能保证系统正确从停机中恢复,写前日志就成为了限制写操作性能的瓶颈,于是论文选择不采用这种更新磁盘数据的方式,转而借助于传统数据库中本来用于碎片整理的页面迁移操作。

但是页面迁移操作可能也需要大量的日志。如果仍然采用日志来记录每个数据项的写入,那么页面迁移操作依然是一个非常费时的过程,也就是去了调用它的意义。论文建议采用单一日志来记录页面的迁移操作,从而大幅提升性能,并且如果页面从内存写回磁盘的操作可以通过迁移事务来完成,那么页面的写回操作性能自然可以获得极大的提升。这也就是论文的中心思想。

5 页面分配与回收

论文还讨论了页面的分配和回收以及相应的开销,文章建议应该避免使用传统数据库位图索引的方式来管理空间的分配。在页面分配的时候,更准确的说是在一个B树节点分裂的时候,在还没有到达下一个检查点之前这个页面当然不会被写入磁盘(当然需要记录一个重做日志),系统会创建一个虚拟磁盘空间,这个新的页面可以被看作是存放在虚拟的磁盘空间中,因此也会具有一个虚拟的页面描述符(一般用位置来表示就可以了),直到下一个检查点,系统调用迁移事务将它从虚拟磁盘迁移到实际磁盘。至于页面的回收,最好的办法就是在建立检查点的时候,快速找到等待回收的页面,然后调用页面迁移事务,还没有迁移的页面迁移到待回收页面。

6 总结

论文了提出了一种优化写操作而又尽可能不损失读性能的方法,主要是通过调用优化后的页面迁移事务来实现。优化后的页面迁移事务只需要单一日志就可以记录整颗B树的迁移过程,因此可以显著提高在大规模写操作的情景下的系统性能。为了避免页面写到磁盘新位置造成了涟漪效应,传统B树中兄弟节点之间的指针链被取消了,取而代之的是两个表示页面搜索码范围的Fence Keys,它们在游标操作、一致性检查、范围锁等操作中起到重要的作用。但实际上这种方法对读操作的性能还是有影响的,比如说一个很长的游标操作,一个问题是,无论是写操作还是读操作的性能到底如何,论文没有实验的证明,只有理论上的假设和讨论。

7 PPT

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

《Write Optimized B-Trees》读书报告

上一篇:Scapy介绍官方文档翻译


下一篇:ASP.NET Core 2.0系列学习笔记-NLog日志配置文件