WORT: Write Optimal Radix Tree for Persistent Memory Storage Systems

FAST17的一篇文章,介绍了内存索引中使用基数树保证数据一致性的方法。阅读本文的目的是通过该文章了解内存索引的基本知识。可以尝试将radix tree结构来解决文件数据一致性。

持久内存(Persistent Memory)=非易失内存(Non-Volatile Memory)

摘要

radix tree可能是更适合NVM的索引结构,因为不涉及tree rebalancing和节点粒度的更新。但是直接应用radix tree是不合适的(后文会讲)。本文提出了write optimal radix tree以及write optimal adaptive radix tree两种适用于NVM的树结构,每次更新使用8B原子写更新,因此在减少了数据重复复制的同时保证了数据一致性。

Introduction

最近的NVM内存索引结构研究基本都是B-tree的变体,块设备的failure atomicity unit大小等于disk block大小,但是对于通过内存总线访问的NVM设备,这个大小需要保证在8B或者不能大于一个cacheline的大小。(这里提到了PCI总线,需要了解一下)

小的failure atomicity unit虽然减少了数据持久化的开销,但是在数据一致性上的开销会变得很大。主要原因是现代处理器上会对写内存指令进行重排序(cacheline粒度),那么为了保证内存写操作的顺序,就需要使用memory fence和cacheline flush指令配合。

目前出现的基于索引结构的B-tree都是为了尽可能减少代价高昂的cacheline flush和memory fence指令,然而并不能保证b-tree的节点有序性,而且在节点分裂时会涉及多个节点的修改,这些修改也需要log起来。

那么虽然现在都是基于B-tree的设计,但是radix tree也不失为一个选择,那么优势在哪里?首先,radix tree的插入操作不需要比较,只需要插入的前缀即可;此外,不需要在节点粒度上进行rebalancing和更新;而且key的插入和删除操作需要一个8B的更新操作,完美契合NVM的更新粒度。然而原始的radix tree很少应用在memory或者cache管理中,因此radix tree通过一个路径压缩算法,将多个节点结合起来形成一个唯一的搜索路径。但是路径压缩涉及了节点的分裂和merge操作,并不适用于PM。

因此本文提出了三种radix tree结构:

  1. WORT

WORT实现了一个8B更新的路径压缩算法,与现有的路径压缩具有同样的效果。同时对于节点的split和merge操作,通过cacheline flush和memory fence指令实现。因此WORT对于PM进行了优化,因为每次更新只需要一个8B的原子写,因此无需任何副本。

剩下的两种基于Adaptive Radix Tree,ART通过自适应节点类型转换方案来tradeoff搜索性能(节点数量)和节点利用率,可以根据节点利用率动态改变节点的大小。但是比较麻烦的是,ART不保证8B更新。

  1. WOART

WOART重新设计了ART的自适应节点类型,同时补充了memory fence和cacheline flush指令。

  1. ART+CoW

针对一致性的问题,使用CoW机制进行处理,而且在radix tree中的CoW开销小于b-tree的CoW开销。

实验表明,本文提出的三种radix tree的变体在插入和搜索以及memcached的合成负载中的表现均优于B-tree的变体,但是在范围查询方面比B-tree的变体表现要差。

PM的一致性问题以及B-tree的变体

PM的一致性

在PM中的一致性需要额外的内存写顺序约束。不同于DRAM+disk的indexing,DRAM中的tree node副本可以不考虑内存写顺序对其随意修改,因为是一个易失性的副本,它的持久副本一直在disk中,而且通过disk block unit更新。也就是说,即使出现掉电等意外,DRAM中的数据直接就消失了,不会影响disk中的持久副本。

然而8B原子写的PM可以理解成会把更新操作分开,也就是说在一定程度上不能保证更新操作的原子性。因此才需要严格保证PM的数据一致性。

另一个重要的方面是write size,在传统的B-tree中,插入或者删除节点会导致大量的节点移动,因为节点之间需要保证顺序,因此这样涉及的数据更新可能会比failure atomicity unit的大小8B要大。因此有序性这个特点在B-tree的PM变体中并不能保证。

传统的解决方法是logging或者CoW(Copy-on-Write),CoW不在原数据上进行修改,而是在原数据的副本上进行处理,并且在副本处理完成一段时间后,把原数据无效化(这里是推理得出,因为如果把新数据同步到原数据上,这样无法从根本上解决一致性问题)。logging和CoW有效的本质在于可以使用连续的8B原子验证来检验数据有效性。虽然有效,但是明显的需要多余的copy操作(CoW)和重复的写操作(logging,NVM读写不均衡以及磨损性质)。

针对PM的B±trees

本章针对PM的关于B-tree的变体进行一个总结。

CDDS B-tree

CDDS B-tree在节点更新时创造一个副本,有点类似于CoW,保证recoverability和一致性,因此会出现非常多的dead节点,同时使用了大量的memory fence & cacheline flush指令,因此在插入和搜索性能上表现不佳。

S. Venkataraman, N. Tolia, P. Ranganathan, and R.H. Campbell. Consistent and Durable Data Structures for Non-Volatile Byte-Addressable Memory. In Proceedings of the 9th USENIX Conference on File and Storage Technologies (FAST), 2011.

NVTree

NVTree通过追加更新的方式减少了memory fence和cacehline flush指令的数目,同时所有的叶子结点都存在PM中,因此,NVTree只需要两个cacheline flush,一个flush entry一个flush count,因此带来了性能提升,这也带来了两个后果:第一,叶子结点是无序的;第二,系统故障是内部节点很可能丢失。NVTree的一些局限:第一,内部节点必须要求存在连续的内存块中来利用cache的局部性,第二,父节点的每次分裂都会导致内部节点的重构。

J. Yang, Q. Wei, C. Chen, C. Wang, and K. L. Yong. NV-Tree: Reducing Consistency Cost for NVM-based Single Level Systems. In Proceedings of the 13th USENIX Conference on File and Storage Technologies (FAST), 2015.

FPTree

FPTree也把内部节点存在易失性内存中,叶子结点存在PM中,FPTree有效的利用了硬件事务内存来处理内部节点的并发访问。FPTree提出了通过fingerprints来减少cache miss,fingerprints是每个叶子结点key的1Bhash值,在查询之前先扫描fingerprints可以减少key的访问因此减少了cache miss比例。局限性是一旦系统崩溃,还是需要重建内部节点。

I. Oukid, J. Lasperas, A. Nica, T. Willhalm, and W. Lehner. FPTree: A Hybrid SCM-DRAM Persistent and Concurrent B-Tree for Storage Class Memory. In Proceedings of the 2016 ACM SIGMOD International Conference on Management of Data (SIGMOD), 2016.

wB+Tree

追加更新,内部节点和叶子结点都存放在PM中,因为内部节点必须要求有序,所以wB+tree使用slot array来存储内部节点的顺序(index表示),相当于间接排序。因为index比较小,所以7个key index的更新可以通过8B原子写保证原子性,wB+Tree也通过使用8B的位图增加节点容量,如果使用位图,wB+Tree至少使用4次cacheline flushes,如果使用slot array排序,这个数字会减少到2。

虽然cacheline flush明显减少,但是会增加额外开销(slot array),同时,wB+Tree在节点分裂时需要logging或者是CoW机制保证,意味着大量的开销。

S. Chen and Q. Jin. Persistent B±Trees in Non-Volatile Main Memory. In Proceedings of the VLDB Endowment (PVLDB), 2015.

PM中的radix tree

Radix Tree一般分为两种:原始的和使用了路径压缩算法的。先介绍原始的radix tree。

这里看原文吧,比较好懂。

与B-tree的区别在于:

  1. radix tree的高度固定,但是高度一般情况下要高于B±tree;
  2. radix tree的结构与插入顺序无关,但是与key的分布有关。B±tree的变体会根据节点的数量动态调整树结构使其保持平衡状态,但是radix tree的结构以及节点数量是固定不变的。而节点的使用率也和key的分布有关系,如果key的分布稀疏,那么使用率肯定要降低。

因为这些问题,决定了radix tree在索引数据结构中并不流行(因为有B-tree),但是看起来在PM中大有可为。首先,radix tree支持遍历树结构而不执行任何的key比较操作;也就是说key决定了唯一的搜索路径;相比来说B±tree需要将每个访问节点的搜素key与其他节点进行比较,这样可能会影响cache的性能,进而导致性能表现上的不同。

对于插入操作也是一样,因为radix tree的节点数量固定,因此并不需要涉及比较操作,而且radix tree节点不存储key,因此天然不需要排序。而相比B-tree来说,需要保持key的顺序,而且需要涉及节点的分裂和合并操作,需要一致性机制保证机制,同时也不适合PM的物理性能(读写不均衡,写磨损)。

radix tree中的原子写

作者对radix tree做出了两点修改,通过8B原子写保证了数据一致性。

第一是保证对pointer的写按照一个特定顺序完成。这个特定顺序就是把第一个遇到的NULL指针替换成下一层节点地址的操作放在最后来做,相当于我把后面的节点都建好,最后在commit到可能改变树结构的那个位置。因为赋值操作是一个8B的原子写,所以不需要任何的log。需要注意的是,这里我们还是需要使用cacheline flush & memory fence,因为必须要保证后面的节点已经建立完,才能把建好的子树连接到原来的树上。

radix tree中的路径压缩

本节介绍了radix tree中的路径压缩,以及作者对于PM上应用radix tree的修改。

虽然确定的树结构会带来良好的性能表现,但是key分布对树结构和内存利用率也有非常大的影响,如果key的分布稀疏,那么会浪费大量的内存空间。假设一个key的chunk size是8bit,那么一层的一个节点就会对应256个pointer(8B),所以如果使用一个没有相同前缀的节点,它的所需要的空间就是8B * 256,那么简单的思路是减少chunk size,但是这样会导致搜索路径变长。

假设key的前缀唯一,那么实际上后面的中间节点完全不必创建。因此也是一个“懒加载”思想的应用,就是说只有在共同前缀出现的时候,我们才去创建多余的中间节点。而具体的实现方法有以下三种:悲观法,乐观法以及混合法。

路径压缩会在中间节点前面加上一个数据结构Header

struct Header {unsigned char depth;        // 中间节点所在的深度unsigned char PrefixLen;    // 乐观路径压缩使用的前缀长度unsigned char PrefixArr[6]; // 悲观路径压缩使用的前缀数组
};

悲观法的好处是可以立刻删除不匹配的key,而乐观法的优点在于更少的内存占用,但是推迟了key的比较,在插入和搜索的性能上会有欠缺。混合法则是当共有前缀长度小于特定长度时使用悲观法,反之使用乐观法。

作者给出了一个混合模式的路径压缩步骤,这篇文章写的很清楚了,可以参考,本文就不再赘述。

会发现使用的还是8B的Header


上图是使用路径压缩的radix tree的插入例子(原文的例子)。

WORT: Write Optimal Radix Tree

在NVM上应用路径压缩的radix tree实际意义不大,因为涉及节点的频繁分裂以及合并。因此作者提出了一个适用于NVM的radix tree路径压缩算法。使用了一个所谓的’node depth information’来通过8B原子写来实现路径压缩算法。下面是一个例子:


上图是在节点更新时如果出现crash导致数据不一致的情景。

原始的radix tree和B-tree变体保证一致性的要求是,节点的更新必须是原子的,但是8B原子写不能cover这种情况,就需要通过额外的机制保证(logging等)。但是radix tree在一定程度上可以容忍这种情况。具体的实现思路是:保证depth和prefix_len一起更新(8B),那么这样有以下的公式:

depth_parent + prefix_len_parent + 1 = child_depth

也就是说如果这个公式不成立,那么就认为出现了不一致的问题,这个时候,可以从这个节点出发随便找两个key来确定最长前缀。此时新建一个节点恢复源节点的状态,在替换的时候使用cacheline flush & memory fence指令保证数据正常刷回,保证数据以及radix tree结构的一致性。

相关阅读

PCI和PCIe总线

WORT: Write Optimal Radix Tree for Persistent Memory Storage Systems相关推荐

  1. Persistent Memory编程简介

    Persistent Memory编程简介 编程 libpmem 持久化函数 libpmemobj 跟对象 root object 例程 事务支持 type safety 线程安全 管理工具 ipmc ...

  2. 原始Radix Tree与路径压缩

    原始Radix Tree与路径压缩 Radix Tree简介 Radix tree 是一种前缀字典树,它的主要特点是树的高度不随数据库大小和节点数量改变,而是由 key 的长度决定.B树需要根据数据量 ...

  3. Persistent Memory优化实践

    Persistent Memory优化实践 Persistent Memory的优缺点 相关论文 A Study of Application Performance with Non-Volatil ...

  4. 【页高速缓存】radix tree 源码解析

    项目要在内核做和页高速缓存相类似缓存机制,在写内核代码之前必须先搞清楚页高速缓存源码是什么情况. 之前有一篇博客分析过了页高速缓存的基础,但是远远没有达到动手写代码的基础.这几天端午节假期集中精力,搞 ...

  5. Evaluating Persistent Memory Range Indexes

    (一)研究目的 针对基于DRAM模拟非易失的索引进行评测,以指导以后基于PM的索引结构的制定. (二)研究背景 (1)Optane DC PM 的属性 Performance 优点 每 DCPMM 的 ...

  6. Redis radix tree源码解析

    Redis实现了不定长压缩前缀的radix tree,用在集群模式下存储slot对应的的所有key信息.本文将详述在Redis中如何实现radix tree. 核心数据结构 raxNode是radix ...

  7. redis radix tree的简单解释

    所有例子均出自源码. Radix tree压缩前缀树,是redis在5.0新加入的用来存储key的数据结构. 前缀树的节点结构如下. typedef struct raxNode {uint32_t ...

  8. Oracle 20c 新特性:持久化内存数据库 - Persistent Memory Database

    导读:随着硬件技术的不断进步,PMEM (Persistent Memory)已经足够成熟,开始进入到数据库加速领域,在 DRAM 和 Flash 之间提供能更强的 IO 层支撑.自 Oracle 2 ...

  9. 持久内存开发套件(Persistent Memory Development Kit-PMDK) - pmem.io: PMDK

    目录 libpmemobj libpmemblk libpmemlog libpmem libpmem2 libvmem libvmmalloc libpmempool pmempool librpm ...

最新文章

  1. 将HLSL射线追踪到Vulkan
  2. 自定义Spark Partitioner提升es-hadoop Bulk效率——续
  3. 如何不屏蔽Android系统的返回按键
  4. python pandas csv时间聚合_Python通过pandas操作excel常用功能
  5. 案例演示Python二维列表与Java二维数组
  6. Flash发布iOS应用全攻略(二)——如何成为一个合法的iOS开发者
  7. 非常好的Java反射例子
  8. 用计算机画函数图象,信息技术应用 用计算机画函数图象优秀公开课教案
  9. python画动态图-Python使用matplotlib画动态图
  10. uooc c语言作业测验答案,UOOC优课在线组织行为学测验作业答案
  11. Linux重启tomcat服务
  12. css中怎么改变图片尺寸,CSS也可以改变图片幅面尺寸
  13. NLP、CV、语音相关AI算法工程师面试问题、代码、简历模板、知识点等资源整理分享
  14. 【阅】天才在左 疯子在右
  15. linux系统bcast,关于linux的Bcast的疑问.请大家帮忙看看,谢谢啦
  16. 微信小程序案例php,微信小程序实现删除处理的案例
  17. 【田姓】宗谱——【郡望堂号】
  18. 【技巧】我是如何 搜索 到想要的信息的
  19. QT程序退出后托盘图标不消失问题
  20. Excel自学笔记 第五节 怎么在列后加统一固定字符?

热门文章

  1. Gin Mode的选择
  2. Kotlin-简约之美-基础篇(五):data类和enum类
  3. BakAndImgCD 24.0发布:轻松备份和克隆磁盘驱动器
  4. Bash漏洞那些事儿
  5. Houdini通过随机UV实现无限不重复贴图
  6. python怎么遍历文件_Python 遍历文件夹所有文件并查找文件中的字符串
  7. 全文翻译:EDPB数据保护影响评估(DPIA:Data Protection Impact Assessment)指南
  8. 第九章 深入拨号方案
  9. CAD.NET 缩放窗口\设置窗口大小
  10. unity3d开发 打飞机小游戏(二)(飞机动画设置)