Bw树:新硬件平台的B树

Bw树:新硬件平台的B树

1. 概述

1.1 原子记录存储(Atomic Record Stores)

1.2 新的环境

1.3 实现

2 Bwtree的体系结构

2.1 现代的硬件敏感性

2.2 Mapping Table

2.3 增量更新

2.4 bwtree结构修改

2.5 日志结构化存储(LSS)

2.6 管理事务日志

3 内存中Latch Free Page

3.1 灵活的虚拟页

3.1.1 更新

3.1.2 叶子级别的更新

3.1.3 page查询

3.2 页固化

3.3 区间扫描(Range Scans)

3.4 回收

4 bwtree的结构修改

4.1 节点分裂(Node Split)

4.1.1 子节点分裂(Child Split)

4.1.2 更新父节点

4.1.3 固化

4.2 节点合并(Node Merge)

4.3串行结构修改和更新

5 缓存管理

5.1 提前写日志协议和LSN

5.2 Flush页到LSS

5.2.1 page 排列

5.2.2 增量flush

5.2.3 Flush活动

6 性能评估

6.1 实现和安装

6.2 Bw-tree调整和属性

6.3 bw-tree和传统btree对比

6.4 Bwtree和Skip list比较

6.5 Cache的效率

参考:

1. 概述

1.1 原子记录存储(Atomic Record Stores)

很多现在讨论的NO-SQL本质上是原子记录存储,很多都是独立的产品,但是也可以使完整事务系统的一个组件。

ARS支持每个独立的记录的读写,记录都是以key来识别。基于树的ARS可以提供更快的key range 扫描。ARS不单单是访问方法,也包含了固态存储的管理,并且要求在系统奔溃之后可以恢复。Bw树就是利用ARS形成新的b树。

1.2 新的环境

处理器已经被修改,不再提高单个内核性能。已经有了以下一些修改:
1. 针对多核的设计:现在大多数的处理器都是高性能多核处理器。单核速度提升变慢,因此为了更好的内核,需要注意以下两点:
                a.多核cpu提高了高并发,并发的增加会导致latch的block,限制了可扩展性。
                b.好的多核处理器依赖于高cpu cache的hit率。
对于第一点,bwtree是latch-free,这样thread在碰到冲突的时候,不会被yield或者重定向。对于第二点,bwtree使用增量更新,避免page中的更新,保护之前cache line的地址。
2.针对现代存储的设计:磁盘的延迟是主要的问题,flash存储有很快的随机和顺序读取性能,但是因为在写入之前需要先擦除,所以随机写会比顺序写速度要慢。Bwtree会生产日志结构,这种方法可以避免FTL保证写入性能尽量的高。

1.3 实现

1.Bwtree通过mapping table来组织,page的大小和位置被虚拟化。实际上是对latch-free和日志结构的虚拟化。
2.通过在原来的page前加上一个增量记录来更新bwtree。
3.对page的splitting和merge做了设计。SMOs由多个原子操作实现,若thread发现有在处理的SMO操作,并不会堵塞而是来完成SMO操作。
4.日志结构存储(LSS),是名义上的page存储,实际上是通过post增量的修改来提高存储的效率。
5.根据LSS和bwtree来实现ARS。
会做这些实现是因为,认为latch
free技术和状态修改避免了update-in-place可以再当前处理器上得到性能提升。

2 Bwtree的体系结构

Bw-tree是典型的b+树,提供对数级的访问,如图:

Bw-tree层在最上面,和Cache层交互,cache管理建立在存储层之上,实现了LSS。现在LSS是使用flash存储,但也可以支持磁盘。

2.1 现代的硬件敏感性

在bwtree中,threads基本不会block,消除latch是设计的目的之一。我们使用原子比较切换指令(CAS)来代替latch。Bwtree只会在从固态存储中获取page是才会block(也就是LSS)。持续的threads运行保障了内核指令cache,避免线程的空闲时间和上下文切换的开销。甚至使用增量来更新,而不是update-in-place,从来避免cpu cache的miss,提高cpu
cache的hit率。
数据管理系统的瓶颈往往是在IO上,所以我们选用了flash存储,使用SSD挂载,但是还是会限制性能。LSS存储启用了大的写入buffer来消除写入瓶颈,flash存储的高随机读取能力和大缓存组合何以最小化读取的block。大的多页buffer允许我们写入改变page的大小,不需要填充到一个统一的偏移大小。

2.2 Mapping Table

Mapping Table在Cache层维护,mapping table包含了物理页到逻辑页的映射,每个逻辑页都有一个PID来识别。PID可以通过mapping table翻译成内存的物理地址或者flash的偏移地址。Bwtree就是通过PID来构建一个b+树。
mapping table隔离了物理地址和bwtree节点,这样每次修改page或者写入到固态存储不需要把位子的修改传播到树的根部,即更新节点内部的连接。这样的重定向可以支持在内存中的增量修改可以支持在固态存储的LSS。
因为bwtree是逻辑的不是固定的物理地址,就可以根据自己需要制定node的page。

2.3 增量更新

Page的状态改变是通过在原来的page前加一个增量记录来实现的。使用CAS指令把增量记录的物理内存地址放到mapping table的page物理地址栏中。如果成功增量记录地址会变成的page 的新物理地址。这个策略被使用在数据修改,也被使用在树的结构修改。
间歇的来固化page(创建一个新的page把所有的增量修改合并),减少链的长度,提高查询的性能。固化的方式也是通过CAS指令实现,之前的page也会被回收。
在增量更新的同时,可以支持同时在bwtree中做latch-free的访问并且能够防止update-in-place保护cpu cache。Mapping table是bwtree重要的特性,可以隔离直接对node更新。

2.4 bwtree结构修改

Latch并不会对做结构修改的page进行保护,如page split。
为了解决这个问题,就把SMO放在一个顺序的原子化操作中,每个操作都是通过CAS进行。为了保证没有线程等待SMO。如果一个线程看到一个未完成的SMO,会在执行自己的线程前先去完成它。

2.5 日志结构化存储(LSS)

Page批量的顺序写入,大大减少了IO次数。因为回收机制,日志结构通常会有额外的写入来重定位page,用来回收日志的存储空间。
当刷新page的时候,只需要刷新增量部分,增量的部分表示从上次刷新到当前的增量修改。通过增加flush buffer的page数量来减少IO的消耗。但是这样会有读惩罚,因为page中的数据不是连续被存放的。
LSS会清理之前的部分flash,这部分flash表示之前的数据。通过清理,LSS把page和它的增量连续的存放,可以提高访问性能。

2.6 管理事务日志

和传统的数据库系统一样,ARS也要在系统crash时保持数据一致性。通过log sequence
number(LSN)来表示每个更新操作。
和传统系统一样,page的刷新的懒惰的,并且依赖于 write-ahead log协议(WAL)。不同的是在WAL之前不会堵塞page的flush。因为现在的update生成的基于之前page的增量。

3 内存中Latch Free Page

这里主要介绍内存中的bwtree page,主要讨论:
1.基本的page结构和如何以latch free 方式更新page
2.page 固化是的查询更加高效。
3.使用epoch的内存回收机制。

3.1 灵活的虚拟页

Bwtree存储的信息和b树类似,索引节点包含了以key排序的key,pointer数据对。数据节点包含key,record对。另外还保存了page中保存的最小的key和最大的key,和一个可以指向右边兄弟节点的指针。
bwtree的page是逻辑的,不是固定物理地址,大小也不是固定的,有了两个重要的特性让bwtree比较特殊:
1.使用PID来识别一个page,使用pid转化为物理地址来访问page。
2.page的大小是灵活的,没有限制page的大小。Page的增长通过增量记录来实现。

3.1.1 更新

更新不会是in-place更新,而是通过创建一个增量记录来描述更新,并放置在当前page之前:
1.创建一个增量记录,指向当前page
2.获取page在mapping table上的项。
3.增量记录的物理地址作为page新物理地址,使用CAS指令来实现install。

如图,多更新几次之后会形成增量记录链。

3.1.2 叶子级别的更新

在叶子级别,增量分为3种:isnert,update,delete。所有的增量都包含LSN。使用LSN来做还原机制涉及到日志的管理方式必须是WAL。Insert和update增量包含了新的记录,但是delete只要含了要删除的key。

3.1.3 page查询

叶子级别的page查询涉及到增量链的遍历。查询会在第一个查询到的地方停止,若key出现在update,insert就返回,如果是delete那么就查询失败。如果增量链不包含key,查询执行在base page 上的binary查询。

3.2 页固化

随着增量链变成,查询性能会降低,为了避免这种情况,会间歇的执行页固化。会根据增量记录和base page重组生成新的page。页固化会在增量链长度超过阀值是触发。
当固化是线程会做以下操作:
1.创建一个新的page,然后把最新的记录版本写入到page中
2.使用CAS指令install新的page。若成功请求回收老的page,若失败释放新的page。失败并不会重试,后面的线程会继续执行直到完成。

3.3 区间扫描(Range Scans)

区间扫描是对一个指定key区间进行扫描。扫描可以指定顺序还是倒序来获取记录。
扫描维护了一个游标标记扫描的进度。对于新的扫描记录的是lowkey。当扫描碰到第一个在区间内的记录会构建一个向量,并放入向量内。向量用来保存扫描的结果。获取下一行有原子性,但是整个扫描没有。
事务锁会阻止能够看到的记录,但是我们并不知道没有传输的记录。所以在传输之前我们会检查是否有修改。如果有修改,我们会重新构建一个记录向量。

3.4 回收

Latch-free环境不允许排它的访问一个共享数据结构,也就是说即使page在被修改,也可以被访问。我们并不想释放一个任然被访问的内存。比如在固化的时候,线程交换老的page和新的page状态,并回收新状态,但是不能再有线程访问老的page的时候释放。
epoch机制就是为了保护之前有访问的又要释放的对象。

4 bwtree的结构修改

4.1 节点分裂(Node Split)

分裂是由一个后台线程在page的大小超过系统设置的阀值的时候就进行分裂。整个过程分为2个阶段:
1.现在叶子上做split.
2.然后更新父的节点.
如果有必要可以一直向上递归。因为有了blink指向兄弟节点,就可以把split分为2个独立的操作。

4.1.1 子节点分裂(Child Split)

为了分裂节点P,大概分为2步:
1.btree层先分配一个节点Q,然后找到合适的key的位置Kp,把大于Kp的key复制到Q,然后Q的side link指向R。
2.在P之前加入一个Split delta记录,记录包含了2个信息,a.在P中key大于Kp的都不可用。Side
link指向Q。这个时候索引还是可用的,尽管父节点O中没有指向Q。所有包含在Q的key都会被指引到P上。到了Split Delta发现key大于Kp那么就会从 side link被指引到Q上。

4.1.2 更新父节点

为了能够直接从父节点O指向到Q,需要在O上加一个delta record,这个delta record包含3个信息:
1.Kp,P和Q的分隔key。
2.一个指向Q的指针
3.Kq,Q的分隔key,原来是P的分隔key。
在delta record出现边界key Kp,Kq是一种优化可以提高查询速度,因为查询必须遍历index节点上的delta chain,若发现要查询的key在Kp和Kq之间就可以马上指向Q没必要在去遍历整个index节点了。

4.1.3 固化

相对于增加delta来说,使用新的base page在split的时候延迟会加大。减少延迟也减少了split的错误。

4.2 节点合并(Node Merge)

和分裂类似,节点合并是当node低于一个值的时候就会发生。

1.删除节点,如要合并R,先在R上标记删除,添加一个delta record,表示R已经被删除。如果一个线程要读取R中的数据碰到Remove Node Delta Record,就会应用在左边的兄弟上。
2.合并孩子节点,在L上添加一个node merge delta物理的指向R(使用内存地址),这样就表示R中的数据是在节点L中。R的存储状态被传换成了L,只有当L固化的时候page R才会被回收。当对L查询时,变成对树的查询,可以再L中访问L中和R中的key。为了让这个可用正常,merge delta上会有key的分隔,用于访问正确的节点。
3.更新父节点,通过使用delete
delta删除父节点上关于R的索引信息和L的Key信息。
一旦在父节点加上了delta,所有到R的路径就全部被堵塞了。就可以启动机制来回收R了,因为epoch的保护机制,所有直到所有的线程访问完之后才会被回收。

4.3串行结构修改和更新

为了正确的序列化SMOs和数据修改,SMOs和SMOs,我们需要构建一个在bwtree上的序列化调度。我们想要把SMO当成一个原子化操作,并且没有Latch。为了满足这个需求,所以线程必须在更新数据,或者执行自己的SMO之前完成之前的SMO操作。

5 缓存管理

Cache层是负责对读取,刷新和内存与flash之间page交换。维护mapping table提供抽象的逻辑页给bwtree。在mapping table上要不就是内存地址,要不就是flash的偏移。如是flash偏移那么就从LSS上读入到内存中。所有涉及到内存的操作都是通过CAS来完成。
有很多原因会导致内存被刷入到flash中。如,因为bwtree是事务系统的一部分所以flush update可以在事务日志上checkpoint。Page flush也处理page 换出吧flash偏移install到mapping table上,并且回收内存减少内存的使用。
为了跟踪固态存储中page 的版本和位置,我们使用flush delta record,还记录了那些修改被flush了,之后的flush只对应增量即可,若flush page成功,flush delta就会包含新的flash offset,page的状态会被设置为flushed。

5.1 提前写日志协议和LSN

Bwtree是ARS(原子记录存储),可以被包含在事务系统里,若被包含就在事务方面被强化。LSN:记录的插入和修改都会使用LSN来标记,而被flush的最大LSN被记录在flush delta上。事务日志协作:不管什么时候TC(事务控制器)flush到固态存储上,左右一个LSN被称为ESL。所有小于ESL的LSN都会被刷新到固态存储中。然后TC定期的把ESL发送到DC,根据提前写日志协议,DC不能刷新大于ESL的数据。
DC中的page flush是由TC根据redo-scan-start-point(RSSP)来要求的,这样RSSP之前的日志都可以被截断。TC会等待来自DC的通知,表示LSN<RSSP的数据都已经被固化了,因为这样数据被固化,在redo 的时候就没必要再去redo这些日志了。

5.2 Flush页到LSS

LSS提供了一个很到的buffer,里面存放了bwtree的结构修改和数据修改。

5.2.1 page 排列

Cache manager把page中的byte以线性方式写入flush buffer。Page的状态在试图刷新的时候被获取。这个很重要,因为之后的修改可能会和split或者固化冲突。

5.2.2 增量flush

当flushpage,缓冲管理器只排列那些ESL>LSN的增量的记录。增量flush表示刷新的消耗要比刷新整个page 小。日志结构存储有2个好处:
1.flush buffer可以包含更多的update
2.回收不需要很频繁因为消耗不是很大。

5.2.3 Flush活动

Flush buffer聚合了LSS到达一个阀值(1MB)然后写入,这样减少了IO的开销。并且使用双buffer,这样当前的buffer还在处理时,可以准备下一个进程了。
flush buffer IO完成之后,相关page 的状态就会被修改。Mapping table获取flush的结果然后使用flush delta来描述。
缓冲管理监控bwtree的内存使用,超过配置的阀值,会视图把page切换到lss上,一旦page被清空,就会从缓存中被牺牲。被牺牲的page 通过epoch被回收。

6 性能评估

主要介绍 bwtree和其他存储结构的性能对比

6.1 实现和安装

BWtree:bwtree以独立的记录原子存储实现,借用了win32原生的CAS
interlockedCompareExchange64。
BerkeleyDB:BerkeleyDB是k-v数据库,我们使用c语言实现了btree模式。
Skip list:跳表可以实现latch-free,可以实现快速插入以及对数级的查询。
测试环境:Intel Xeon
W3550,24GB内存
测试数据库:XboxLive,Storage deduplication trace,Synthetic
默认:主要性能的单位是million/sec。为每个工作负荷提供8个工作线程和逻辑内核数一样。

6.2 Bw-tree调整和属性

这节主要介绍2方面对bwtree的性能影响:
1.delta chain的长度对性能影响

如图,bwtree运行在Synthetic和Xbox不同的delta chain对性能的影响。即consolidation阀值。对于小的阀值,consolidation会频繁出现,影响性能。对于xbox,查询性能在阀值大于4之后开始恶化。虽然顺序扫描,分支预测性能良好,因为xbox行100byte,使得L1的填充率下降。synthetic只有8byte大小,因此delta chain可以更长,性能也不受影响。
2.latch free使用CAS,CAS的错误率:bwtree原生latch-free,在某些情况下会出现更新page状态的错误。

更新错误率很低大概占用0.02%的工作负荷。split,consolidation比update错误率大得多,这个也是在预料中的,因为他们会和update是竞争关系。synthetic是一个极端场景,因为update性能很好,导致split,consolidation的错误率很高。

6.3 bw-tree和传统btree对比

主要导致性呢过问题的有2个原因:
1.latch-free,bwtree没有latch,但是berkelydb确有性能问题。
2.cpu,cache效率,bwtree使用delta record来进行更新,base page不变,cpu cache很少出现不可用的情况。但是BerkeleyDB是in-place update,会导致更多的cpu cache不可用的情况。

6.4 Bwtree和Skip list比较

Skip list可以提供有序的对数查询,并且很容易实现latch free。

如图比较了在bwtree和skiplist下的性能,bwtree比skiplist性能要好。是因为bwtree的cpu cache的效率比skiplist 要搞。

6.5 Cache的效率

为了更深入的测量和比较bwtree和skiplist,我们使用intel VTune来获取cpu cache的hit。

通过如图,会发现bw-tree的cache hit率比skiplist要高,这个也就是为什么bwtree性能比skiplist好的原因。

参考:

The
Bw-Tree: A B-tree for New Hardware

Bw树:新硬件平台的B树(内存数据库中的b树索引)的更多相关文章

  1. lintcode :前序遍历和中序遍历树构造二叉树

    解题 前序遍历和中序遍历树构造二叉树 根据前序遍历和中序遍历树构造二叉树. 样例 给出中序遍历:[1,2,3]和前序遍历:[2,1,3]. 返回如下的树: 2 / \ 1 3 注意 你可以假设树中不存 ...

  2. 有趣的线段树模板合集(线段树,最短/长路,单调栈,线段树合并,线段树分裂,树上差分,Tarjan-LCA,势能线段树,李超线段树)

    线段树分裂 以某个键值为中点将线段树分裂成左右两部分,应该类似Treap的分裂吧(我菜不会Treap).一般应用于区间排序. 方法很简单,就是把分裂之后的两棵树的重复的\(\log\)个节点新建出来, ...

  3. LintCode-73.前序遍历和中序遍历树构造二叉树

    前序遍历和中序遍历树构造二叉树 根据前序遍历和中序遍历树构造二叉树. 注意事项 你可以假设树中不存在相同数值的节点 样例 给出中序遍历:[1,2,3]和前序遍历:[2,1,3]. 返回如下的树:    ...

  4. LSM树——放弃读能力换取写能力,将多次修改放在内存中形成有序树再统一写入磁盘

    LSM树(Log-Structured Merge Tree)存储引擎 代表数据库:nessDB.leveldb.hbase等 核心思想的核心就是放弃部分读能力,换取写入的最大化能力.LSM Tree ...

  5. 树:BST、AVL、红黑树、B树、B+树

    我们这个专题介绍的动态查找树主要有: 二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree).这四种树都具备下面几个优势: (1) 都是动态结构.在删除,插入操 ...

  6. 谈谈InnoDB中的B+树索引

    索引类似于书的目录,他是帮助我们从大量数据中快速定位某一条或者某个范围数据的一种数据结构.有序数组,搜索树都可以被用作索引.MySQL中有三大索引,分别是B+树索引.Hash索引.全文索引.B+树索引 ...

  7. C#中的表达式树简介

    表达式树是.NET 3.5之后引入的,它是一个强大灵活的工具(比如用在LINQ中构造动态查询). 先来看看Expression类的API接口: using System.Collections.Obj ...

  8. python数据结构之树和二叉树(先序遍历、中序遍历和后序遍历)

    python数据结构之树和二叉树(先序遍历.中序遍历和后序遍历) 树 树是\(n\)(\(n\ge 0\))个结点的有限集.在任意一棵非空树中,有且只有一个根结点. 二叉树是有限个元素的集合,该集合或 ...

  9. 如何将数据库中存的树转化为树形列表(以easyui的tree为例)

    很多时候,我们会把一棵树存放到数据库中,当前台需要展示一个树形列表时,将这棵树读取出来并显示,这个过程是怎么实现的呢? 这篇文章是以构造一棵easyui前台框架的一个树形列表为例,后台框架是sprin ...

随机推荐

  1. 负margin的原理以及应用

    负margin在布局中往往起到意想不到的效果,比如在多栏等高布局中就是用该技巧. 虽说网络上关于负margin的实践有很多,但对margin负值为什么会出现这样的效果却没有多少讲解,本篇的目的就是阐述 ...

  2. ASP.NET通过递归添加树(Treeview)

    先来看看效果,基本上就是这样的. 所谓树,无非就是2点,第一个:根节点,第二:叶子节点,其中叶子节点中还可能有叶子节点,但是根节点始终只有一个. 下面贴上 各部分的代码 1.PAGE_LOAD载入事件 ...

  3. 搭建OpenGL环境-Windows/VS2013

    对于opengl的环境,简单搭建的话其实和opencv差不多,你会看到下面的过程与opencv类似,不同的就是某些文件需要自己找(因为不是集成的,各个拓展需要单独下载) 1.首先,对于opengl头文 ...

  4. 【Android】[转] Android Codec默认profile使用的是Baseline

    关于Android默认Codec使用的Profile找了半天没发现,还是Google的时候发现了开源中国有网友写的这边博客,相关的内容很少,便贴了过来做个笔记. 以下内容转自Android Media ...

  5. LeetCode刷题系列

    LeetCode 我们工作面试和提高自身数据结构和算法能力的时候往往需要刷刷题,我选择LeetCode是通过一个留学论坛了解的.专业,覆盖语种全面. 提前说说刷题的心得: 尽量手写代码,少使用IDE的 ...

  6. ECMAScript 5中属性的特性值

    这是<JavaScript高级程序设计(第三版)>第六章相关内容的总结. ECMAScript中有两种属性:数据属性和访问器属性.每种属性都有四个特性值. 数据属性的四个特性值: [[Co ...

  7. Halcon11与VS2010联合开发

    刚开始学习Halcon,需要使用Halcon与C++联合开发软件,查了网上的资料都是Halcon10的,我用的是Halcon11和VS2010的开发环境,实践了一下发现有一些问题,于是把自己的配置的过 ...

  8. 30行代码让你理解angular依赖注入:angular 依赖注入原理

    依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖 ...

  9. C#获取图片的后缀名

    最近在学习过程中遇到一个问题,就是如何获取图片的格式,对于一张知道全路径的照片,如果其路径包含后缀名的话,要取得后缀名,只需要一行代码即可: var ext = System.IO.Path.GetE ...

  10. Windows Server配置Jenkins,实现监测SVN提交自动构建.net4.5的项目

    1,持续集成 概念:http://baike.baidu.com/view/5253255.htm,对于大多数人而言,自动构建/自动测试/自动部署,已经很发挥它的价值了.选择上: 推荐jenkins, ...