uTree: a Persistent B+-Tree with Low Tail Latency (VLDB 2020)

(一)研究目的

通过减少 SROs 和并发干扰来减少 tail latency。

(二)研究背景

现有的持久性索引主要旨在通过减少持久性开销(减少刷新操作)或减少一致性开销(避免日志记录)来提高吞吐量。

本文考虑了另一个性能指标:tail latency

尾部延迟(也称为高百分比延迟)是指客户端很少看到的高延迟。例如:“我的服务通常在10毫秒左右响应,但有时需要100毫秒左右”。
有1%的请求耗时高于99%的请求耗时,影响用户体验,甚至拖垮服务。

引起 tail latency 的两个原因:
(1)结构优化操作(Structural Refinement Operations,SROs)

B+树中插入或删除操作会导致结构优化操作,结构优化操作包括排序和平衡等导致额外数据修改、移动的操作。PM 的写入带宽较低,并且 SROs 中的更新需要同步持久化,所以 SROs 通常会导致更高的数据持久化开销。

之前的工作如何减少 SROs?

  • NV-Tree、wB+Tree 和 FPTree 在叶节点中使用未排序的键(降低搜索/扫描效率换取更少的 SROs 开销)。
  • 采用混合 DRAM/PM。NV-Tree 和 FPTree 将非叶节点放置在 DRAM 中,消除了非叶节点的持久性开销。

然而,这些变体仍然受到叶节点中排序和平衡开销的影响,这是造成开销的主要原因。

(2)并发的干扰开销

条目之间的依赖性使得很难支持对同一节点内的条目的并发访问。
之前的工作提出的并发控制策略在易失性内存中运行良好(DRAM 写入延迟低),但 PM 的写延迟延长了每次更新的时间,导致访问同一节点的其他线程可能遇到额外的延迟。

写冲突更为明显,更新同一个节点时,前一个写入操作本身可以很快完成,但 PM 的高写入延迟可能会延迟后续写入操作的执行。

(三)研究概述

3.1 µTree 的结构

在这里插入图片描述
µTree 的内部节点直接放置在 DRAM 中。叶节点由两层组成:数组层和列表层,数组层(树叶节点)存在 DRAM 中,列表层(列表叶节点)存在 PM 中。树叶节点包含多个键指针对,每个指针指向一个列表叶节点。列表叶节点存储键值,保持排序,通过 8 字节的 next 指针链接。
也就是把整棵B+树放在 DRAM 中,所有的实际数据被持久化在 PM 中,系统崩溃时利用 PM 中的数据在 DRAM 中重构B+树。
下面是单线程情况下的几个基本操作:

3.1.1 Put operation

在这里插入图片描述
插入新键值对的步骤(两次刷新):

  • 遍历B+树找到目标节点的前置节点(红色虚线)
  • 创建新的列表叶节点(刷新)插入在 PM 中的列表层(刷新)
  • 修改 DRAM 中的数组层,指针指向

3.1.2 Get operation

首先使用给定的键在树叶节点中查找条目,然后使用存储在树叶节点中的指针获取目标列表叶节点。

3.1.3 Delete operation

在这里插入图片描述

  • 1.遍历B+树找到目标节点的前置节点(红色虚线)
  • 2.前置节点的 next 指针直接指向后置节点
  • 3.删除 DRAM 中的数组层中对应的条目

3.1.4 Scan operation

µTree 首先遍历B+树找到数组层中大于等于 Key 的条目指向的列表叶节点,然后通过列表层中的 next 指针遍历列表层来处理范围查询。

3.1.5 为什么这样设计可以减少 tail latency?

减少 tail latency 的核心就是减少在 PM 上的 SROs。

  • 由于 DRAM 的写性能比 PM 好,在 DRAM 中放置内部树节点和数组层,可以减少 SROs 开销。
  • 链式存储保持有序的开销比顺序存储小。链表插入只要修改指针,而数组插入需要移动元素以保持有序。

3.2 协调并发控制

首先了解一下并发控制:
什么是乐观锁,什么是悲观锁
深入理解CAS(乐观锁)

CAS(Compare And Swap)比较并交换,如果要修改位置的值与预期原值相同,那么该位置的值更新为新值,否则重试。

本文提出了协调并发控制机制来处理写冲突和读写冲突。

不能依赖B+树中的现有锁来协调对 µTree 的并发访问,因为

  • (1)在 B+ 树中,同一叶节点中的条目通常共享同一个锁。
  • (2)SROs 会阻止正常操作的执行。

3.2.1 Write-Write Conflicts

(1)两个插入操作的冲突

在这里插入图片描述
图5(a)中T1、T2两个线程同时在一个叶节点中插入值,同样是 put 操作的三步:

  • (1)两个线程乐观地找到 PM 中的前置列表叶节点(无锁读取)
  • (2)将新创建的列表叶节点插入到列表层中。使用 CAS 指令修改前置节点中的 next 指针,如果 CAS 失败,表明前置节点已过时(比如在它们之间已插入了另一个列表叶节点)。
  • (3)两个线程尝试获取对应的树叶节点的锁,然后向 DRAM 叶节点中对应的位置插入条目并指向新列表叶节点。

图 5(b)中,i)是悲观锁,ii)是乐观锁。文中说 i) 是依赖于B+树现有的锁来进行并发控制,B+树有哪些锁?

下图 CAS failure 所示,当在当前节点之前的列表层中插入另一个节点时,CAS 可能会失败。这种情况的发生是因为在 put 操作的第一步得到了一个过时的前置节点,在此期间插入了另一个新的列表节点,但树叶节点尚未插入。为了解决这个问题,这个线程需要在数组层重试,以找到最新的前置节点,然后完成剩余步骤。
在这里插入图片描述

(2)插入与删除操作的冲突

在这里插入图片描述
线程 T1 删除了列表叶节点 b,但还未删除数组层中的节点和指针。此时线程 T2 想要插入列表叶节点 c,通过数组层找到前置节点 b,然后插入在b和d之间,但是b实际上已经被删除了,所以列表层访问不到c。这导致了插入与删除操作的冲突。

解决这个问题使用了一个经典的方法:在每个列表叶节点的 next 指针中添加一个 Deleted 位,线程在实际删除叶节点之前修改该位。因此,其他线程可以识别其前一个线程的状态,在其被删除时重新找前置节点。

3.2.2 Read-Write Conflicts

在这里插入图片描述
8byte 指针实际有效位只有 6byte,还有 2byte 的保留位。

许多B+树在每个叶节点中封装一个 Version 字段来支持乐观读取。uTree 也采取了类似的操作:将 Version 字段放在列表叶节点的 netx 指针的保留位中,支持更细粒度的乐观读取。

Version字段包含15位,在并发线程更新列表叶节点之前和之后都会增加。更新时先检查 Version,如果是奇数,则说明有线程正在对节点进行修改,重试等待;如果是偶数,则将 Version+1(置为奇数),随后进行修改,修改完成后再 Version+1(置为偶数)。

进行 get 操作时,先检查列表叶节点的 Deleted 和 Version 位,如果设置了 Deleted 则表明该节点已删除,如果 Version 是奇数,则等待该节点更新完成。

(四)实验

与 wB+Tree、NV-Tree、FAST&FAIR、FPTree 进行比较。

(五)总结

持久性B+树存在 long tail latency,有两个根本原因:
1)内部结构优化操作(SROs)
2)线程间干扰
根据这两个原因本文提出了µ树。首先,在B+树的叶节点中引入阴影链表层,以最小化 SROs 开销。其次,提出了一种协调的并发控制机制来减少线程之间的干扰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值