论文阅读-FAST_FAIR

文章目录

打比赛的日子过去了,开始了科研生活,新的领域新的方向,于我而言充满未知,希望能尽快养成做这件事的一些基础能力,然后玩一些有意思的东西

论文:Endurable Transient Inconsistency in ByteAddressable Persistent B±Tree

https://www.usenix.org/system/files/conference/fast18/fast18-hwang.pdf

准备工作:装个zotero和翻译插件就够了,先用着,后面有需要再下其他

https://github.com/windingwind/zotero-pdf-translate/releases/download/v2.4.8/translate-for-zotero.xpi

conclusion

感觉似乎没有必要按文章结构顺着读,按给出的问题去看就行

论文产生的背景是什么?为了解决什么问题?

背景:

1:新型存储器PM的出现,产生了一些写操作的失效原子性的问题,举个例子,x86架构下一行cache是64字节,但是向内存里写操作的最小单位是8字节,也就是说可能会出现写到一半系统断电,恢复时处于中间状态的尴尬状态,而这样的状态是不被允许的。市面上的其他几种办法要么写日志,要么加大量的flush和fence指令,效率很低。这是要解决的第一个问题,即失效原子性

2:传统方法在B+树上读写都要加锁,导致性能的降低,作者提出了读可以不加锁的方法,这是要解决的第二个问题,即并发

解决方案是什么?核心卖点是什么?

我们还是从上面继承下来,针对两个问题

失效原子性和并发的问题,作者提出的FAST,利用了B+树的一个特点,即不会出现重复的指针,所以一旦读写操作发现有重复的指针,就能发现当前状态是中间状态。做法是参考了插入排序的思想,原地从后往前更新,既然我写操作的最小单位是8字节,那我每次就只写一个八字节以内的数据,把写分成多轮来完成,这样哪怕到其中某一轮断电,也可以识别出到了哪一轮,避免了大量的日志和fence等操作,大大提升了性能。具体的做法参考下面的FAST的图。

核心卖点是什么呢?

1:用较低的开销保证了失效原子性

2:实现了读不加锁的高效并行

方案的效果如何?开销(成本)如何?

方案的效果是在TSO架构下,达到了当时所有算法里的最优效果,不管是读写还是混合操作,并发上面的效果也是最佳的(用的是TPC-C benchmark)。

开销其实我不太理解,是指算法达到目的需要付出的时空成本吗,但这里算法做的就是一个时空的优化,原地更新,高效读写,所以开销是很小的

如果将问题交给你,你能有什么好的想法?如果让你拓展论文的方案,有什么想法?

思考一下,问题主要还是要解决失效原子性的问题,读锁是算法顺带的好处,算法本质上是利用了B+树指针不会重复这一特性,核心问题是在于一行cache要多次写操作,也就是说必定会出现中间状态,我们不考虑论文里提到的所谓FP-tree,wB±tree等方案是怎么做的,回归最原始的B+树去思考,在这个结构下,怎么去保证中间状态可处理,看论文的最初想法是写个日志记录状态,或者不原地修改,拿一片空间改好再放回来,但是简单阅读一下论文就不难发现,这样的开销是很大的,而论文之所以效率高,在于提出了容忍中间状态这样的想法,其次就是原地修改,原地修改能够减少写操作,容忍中间状态能大量减少fence和flash等指令的调用,其中最关键的是减少fence。

综上我们不难得出,至少要设计原地修改的算法,其次,最好能想办法减少fence和flash指令的调用。到这里其实就不难想了,我们先做一个筛选,以插入为例,筛选哪些算法能原地修改。或者我们能不能这样,在一个新节点上改,改完换个指针指向就完了。思考一下,不如FAST的原地更新,因为fast不是每次都要改所有数据,但我们新建节点就意味着每次都要改所有数据,如果节点内数据多代价就很大,还是回到原地多步更新来。

突然感觉学点算法也挺重要的。。。后面有空打打比赛去学吧

我们看看Robin Hood hashing这个算法吧,就不全都研究了

  • home slot(家) :h(key) mod m,key 本该在的位置。
  • displacement / probe distance(位移,记 D) :key 实际位置离 home 的距离,D = (pos - home) mod m。
  • 富 / 穷 :D 小(离 home 近)= "富";D 大(被挤得远)= "穷"。Robin Hood 劫富济贫------让穷 key(D 大)抢富 key(D 小)的位置。

看看插入的伪代码

复制代码
insert(k):
  pos = home(k) % m
  while occupied(pos):
    k2 = table[pos]
    D  = (pos - home(k)) % m    # 携带者当前位移
    D2 = (pos - home(k2)) % m   # 占位者位移
    if D > D2:                  # 携带者更"穷"→ 抢
       table[pos] = k
       k  = k2                  # 被挤出的 key 换人携带
    pos = (pos + 1) % m
  table[pos] = k                # 遇到空位,落下

注意看距离,abcd的D值是不断增大不会递减的,这个特性保证了查找可以提前终止,这里要理解一下为什么会提前终止

把哈希表想成一排编号的座位 : 0 1 2 3 4 5 ...

每个人(key)都有自己本来该坐的座位 (home)。如果那个座位被占了,他就往右挪一格,挪了几格叫"走了几步"。

Robin Hood 规则就一句:谁走得更远,谁就更有资格抢座位。 新来的人走到某个座位前,如果自己已经走的步数 比座位上那个人多 ,就把那个人踢出去自己坐,被踢的人继续往右走。

理解这个算法之后,让我们回到一开始的分析,第一:他是可以原地多步修改的(这一部分可以直接用FAST来保证原子性,甚至搜索定位的效率还有一个小优化),第二:我们需要分析这个算法的fence和flush指令的开销

  • 在分析fast的插入时,发现了一个细节,因为fast是跨cache line才会调用flush,这就意味着他们的算法在头部插入时比在尾部插入时的开销大得多,会跨更多的cache line。但是,如果robin像fast一样从右向左复制,那他的优势在哪里?

所以其实Robin真正的优势是在于减少了shift的次数,从而减少了flush和fence指令的次数

但代价也很明显,哈希表的结构不支持范围查询和顺序扫描,这是致命的

那我们再思考一下,能不能参考robin的这种设计,将其放到B+树上,从而减少shift呢,或者结合B+树和哈希去做设计

第一个方向不太可行:B+树成立的底层依据还是有序,从而可以根据索引找节点,但哈希表是无序的,这就意味着强行维持B+树每次都要取出并重排,开销不太现实。而且哈希的优势主要还是再点查

第二个方向似乎已经有人做过了

abstract

With the emergence of byte-addressable persistent memory (PM), a cache line, instead of a page, is expected to be the unit of data transfer between volatile and nonvolatile devices, but the failure-atomicity of write operations is guaranteed in the granularity of 8 bytes rather than cache lines.

可按字节寻址:传统硬盘按块寻址,需要改一字节也需要读一块的内容出来(因为传统硬盘寻道和读取的开销很大,所以规定一次至少读一块出来),PM可以直接定位并修改某一字节

缓存行代替页成为新的数据传输单位:因为PM直接插到内存总线上,所以CPU可以用load/store指令直接去访存,在x86架构上最小数据单位是64字节(为什么不传输特定的几个字节呢,由cache的空间局部性原理不难想到,需要考虑一些开销的因素)

但是写操作的失效原子性是8byte,原子性说的是不会存在中间状态,失效原子性说的是在系统崩溃的情况下也不会存在改到一半的数据,这里不难想到,一次不全部改完,所以会有一些原子性的问题,这个问题应该也是论文诞生的背景

This granularity mismatch problem has generated interest in redesigning block-based data structures such as B±trees

操作的粒度产生的原子性问题,催生了一些修改的数据结构,但是修改的方法会降低B+树的效率,这里我想的是临时复制原数据的方法,但是似乎有其他人做过,时间开销大

作者提出了FAST和FAIR来解决这个粒度不匹配的问题

Every 8-byte store instruction used in the FAST and FAIR algorithms transforms a B±tree into another consistent state or a transient inconsistent state that read operations can tolerate. By making read operations tolerate transient inconsistency, we can avoid expensive copy-onwrite, logging, and even the necessity of read latches so that read transactions can be non-blocking.

要理解这里需要复习一下B+树是什么东西

B+ 树是一棵满足以下条件的多路平衡搜索树(假设它的"阶数"为 mm ,即一个节点最多有 mm 个孩子):

  1. 所有叶子节点都在同一层(绝对平衡,没有深浅不一的情况)。
  2. 内部节点(非叶子节点) :只存索引(键)和指针 ,不存真实数据。如果有 kk 个孩子,那么它存有 k−1k−1 个键。
  3. 叶子节点 :存真实数据 (或指向数据的指针),并且所有的叶子节点用双向链表按大小顺序连在一起。
  4. 节点容量 :除根节点外,所有节点至少半满(含有 ⌈m/2⌉−1⌈m /2⌉−1 到 m−1m−1 个键)。

B+树的优点

  1. 范围查询极其高效:因为叶子节点有链表,要找 10 到 50 的数据,只需要找到 10,然后顺着链表往后读就行了。
  2. 高扇出(High Fan-out),树高低:如果一个节点(比如 4KB)能存 1000 个索引,那么 3 层 B+ 树就能索引 10 亿条数据,查找只需 3 次 I/O。
  3. 适合块存储 :传统 B+ 树的设计是以"页(Page)"为单位的,一个节点通常正好是 4KB 或 8KB,和磁盘的块大小完美对齐。****

继续回去看论文,

Every 8-byte store instruction used in the FAST and FAIR algorithms transforms a B±tree into another consistent state or a transient inconsistent state that read operations can tolerate.

把B+树转到另一个一致的状态,或者读操作可以容忍的短暂的不连续状态,这是什么意思?

By making read operations tolerate transient inconsistency, we can avoid expensive copy-onwrite, logging, and even the necessity of read latches so that read transactions can be non-blocking.

读操作可以容忍这种瞬态不一致,就可以不用复制或者日志等操作,甚至读不用上锁

到这里不难看出论文还是在解决原子性的问题,用了一种开销更小,效率更高的办法,具体是怎么用的呢,继续往后看吧

有点意思,很聪明的设计,继续往后看看这两种算法是怎么做的吧

introduction

introduction真是个补前置知识的好地方

phase change memory48, spintransfer torque MRAM17, and 3D Xpoint2是推动持久化内存(PM)从概念走向现实的关键。相变存储器等新的半导体存储芯片。

似乎还有个创芯学院的联培,顺便了解了解芯片去。

  • PCM:在芯片里刻蚀出微小的相变材料(如 GST)单元。
  • STT-MRAM:在芯片里构建纳米级的磁性隧道结(MTJ)。
  • 3D XPoint:在芯片里构建交叉点阵列(Cross-point Array)。

集成电路是一种微型电子器件,它把成千上万甚至上百亿个电子元器件 (主要是晶体管、电阻、电容等),通过半导体工艺,全部"刻"在一小块半导体材料(通常是硅)上,形成一个完整的电路。

③ 制造过程:从沙子到芯片(晶圆制造)

  1. 提纯:把沙子(二氧化硅)提纯成高纯度的单晶硅,拉成圆柱状的"硅锭"。
  2. 切片 :把硅锭切成薄薄的圆片,这就是晶圆(Wafer)。一个 12 英寸的晶圆,最后可以切割出几百个 CPU 或内存芯片。
  3. 光刻(Lithography):这是最核心的步骤。像"洗照片"一样,用极紫外光(EUV)把设计好的电路图投射到晶圆上,然后通过刻蚀、沉积等工艺,在硅片上"刻"出立体的电路结构。
  4. 切割与封装:把晶圆切成一个个小方块(裸片),装入外壳,引出金属引脚,就成了我们看到的"芯片"。

按照功能,集成电路主要分为两大类:

  • 逻辑芯片(Logic IC):负责"思考"和"计算"。例如 CPU、GPU、FPGA。它们由大量的逻辑门组成。
  • 存储芯片(Memory IC) :负责"记住"数据。这就是你论文的主战场,它又细分为:
    • 易失性存储(断电丢数据):DRAM(电脑内存条)、SRAM(CPU 内部缓存)。速度快,但一断电就全没了。
    • 非易失性存储(断电不丢数据):NAND Flash(SSD、U盘)、NOR Flash(BIOS固件)。
    • 新型持久化内存(PM):PCM、STT-MRAM、3D XPoint(傲腾)。它们试图结合 DRAM 的速度和 NAND 的持久性。

继续回论文,传统的块设备里B+树是比较受欢迎的数据结构,这里看看块设备是哪些,

块设备(传统 SSD):按 4KB 读写,有设备驱动,有文件系统。硬件自带闪存转换层(FTL),保证 4KB 块写入的原子性

B+树的设计是一个节点刚好等于块设备的一个块,这里有点不太理解,B+树不是由一堆值和指针组成的一个节点吗?这里继续看

从逻辑数据结构的角度看,B+ 树的节点确实是一个"容器",里面装着:

  • 键(Keys):用来比较和指路的索引值。
  • 指针(Pointers):指向子节点(内部节点)或真实数据(叶子节点)。
  • 头部信息(Header):记录这个节点当前存了多少个键、是否满了等元数据。

这些数据在内存里是一整块连续的结构体(Struct)或对象

这里的"是",不是说它们在概念上等同,而是指在物理存储上,它们被设计成"一一对应"的映射关系。

  • 数据库在设计时,会把节点结构体的大小严格限定在 4KB。一个节点的大小最多是 4KB,从而限制了节点里可以放的数据元素有多少"

  • 看一看这样的数据结构长什么样就能理解了

    struct Node {
    bool is_leaf; // 标记是不是叶子节点
    int num_keys; // 当前存了多少个键(比如 200 个)
    Key keys[250]; // 存放 250 个键的数组
    Pointer ptrs[251]; // 存放 251 个指针的数组
    // 可能还有一些用于崩溃恢复的元数据
    };

在传统的块设备里,B+树写回磁盘不用考虑原子性的问题,硬件会兜底维护状态,那为什么在PM里会失效,因为PM不认4KB,它只能保证8字节的写原子性,所以传统B+树的代码不适合PM

同时传统的DRAM掉电后数据会清空,所以不用考虑一致性的问题

To prevent the reordering of memory write operations and to ensure that the written data are flushed to PM without compromising the consistency of the tree structure, B±trees for persistent memory use explicit memory fence operations and cache flush operations to control the order of memory writes 5, 42

Cache flush :把 cache line 从 CPU cache 刷到持久内存,例如 clwb、clflushopt。

它解决的是"数据是否已经到达 PM"。

Memory fence :内存屏障,例如 sfence。

它解决的是"不同写操作之间的持久化顺序"。

我们先看看这两个操作是怎么实现的

其实fence有点像操作系统的信号量实现同步的机制

The recently proposed B±tree variants such as NVtree 49, FP-tree 34, and wB±tree有两个问题

第一是记录日志的开销是比较大的

第二是很多的flush和fence的开销也是比较大的

这是早期大家解决原子性的方案,继续看后期的演化

To resolve the first problem, previous studies have proposed a way to update tree nodes in an append-only manner and introduced additional metadata to the B+tree structures. With these augmentations, the size of updated memory region in a B±tree node remains minimal.

不原地更新,直接追加到节点后面的空间区域,并用metadata记录信息

但缺点是key变得无序,我们不再能二分查找,同时也会影响到cache的空间局部性

另外一种是对log的解决,叶子节点用pm记录,内部节点用DRAM记录

但缺点是系统出问题后需要重建树结构,即时恢复很难

接下来是作者的方法,核心idea是既然我很难维护一致性,那能不能让其他线程知道这是不一致性的状态,也就是所谓的tolerating transient inconsistency

这就是论文的核心卖点,没有日志的开销,也没有大量fence和flush的开销

第一个工作是通过重复指针来实现的一致性检测其实是这样的

第二个工作是实现并发,读操作不加锁,因为读操作可以识别出状态并正确找到结果

其实看到这里有个问题,这里只保证读和写之间能协调工作,那写和写之间呢?先留个疑问在这里

接下来是论文的其他部分

In Section 2, we present the challenges in designing a B±tree index for PM. In Section 3 and Section 4, we propose the FAST and FAIR algorithms. In Section 5, we discuss how to enable non-blocking read transactions. Section 6 evaluates the performance of the proposed persistent B+tree and Section 7 discusses other research efforts. In Section 8 we conclude this paper.

B±tree for Persistent Memory

Challenge: clflush and mfence

A popular solution to guarantee transaction consistency and data integrity is the copy-on-write technique, which updates the block in an out-of-place manner. The copyon-write update for block-based data structures can be overly expensive because it duplicates entire blocks including the unmodified portion of the block

这也是为什么一开始想到的复制的办法不可行的原因,简单,但开销太大

Inserting a new key-pointer entry in the middle of an array will shift on average half the entries.

as the main reason why B±trees call many cache line flush and memory fence instructions

B+树里原地更新主要的问题

Reordering Memory Accesses

In order to resolve the performance gap between CPU speed and memory access time, memory is divided into multiple cache banks so that the cache lines in the banks can be accessed in parallel. As a result, memory operations can be executed out of order.

多级cache导致了一些内存操作是无序的

To design a failure-atomic data structure for PM, we need to consider both volatile memory order and persist order.

CPU向cache写数据的顺序,和cache向PM写数据的顺序

先看写cache会遇到的问题

为什

么要强调写的顺序,举个例子,更新B+树一定是先写数据再改指针,如果两个操作反过来,写完指针就断电,那么算法就会遇到问题

However, most archi tectures arbitrarily reorder stores-after-loads, loads-afterstores, and loads-after-loads unless dependencies exist in them.

即使是在x86下,这几种指令也会被随意重排,但是we consider that all modern processors do not reorder dependent loads.

Failure-Atomic ShifT (FAST)

Shift and Memory Ordering

The process of shifting array elements is a sequence of load and store instructions that are all dependent and must be called in a cascading manner.

B+树里的这些指令是由依赖关系的,通过级联调用的方式进行

The dependency between the consecutive move operations, ei + 1 ← ei and ei ← ei − 1, prohibits the CPU from performing Out-Of-Order writes38 and guarantees that the records are moved while satisfying the prefix constraint 45, i.e., if ei ← ei − 1 is successful, all preceding moves, e j ← e j − 1, j = i + 1, . . . , N have been successful.

禁止CPU不按顺序来

our proposed FAST algorithm calls a cache line flush instruction when we shift an array element from one cache line to its adjacent cache line.

Even if we do not call a cache line flush instruction, a dirty cache line can be evicted from CPU caches and be flushed to PM. In FAST, such a premature cache line flush does not affect the consistency of a sorted array.

FAST在cache元素换行时刷新,虽然可能会被CPU自己刷新,但移位操作的依赖关系天然不需要加fence

Since there exists a dependency between the load and the store instructions, they will not be reordered and we do not need to explicitly call a memory fence instruction

所以FAST实际上只是把移位之间的fence去掉,因为天然存在依赖关系,但是写PM和写cache之间还是需要加fence

Endurable Inconsistency during Shift

接下来讨论的是容忍短暂不连续的问题

我们知道,对于这种移位的插入排序,可能会出现两个相同数据出现在同一行的情况,怎么去容忍这种短暂的不一致呢?利用B+树的一个特性,指针不会重复,所以将指针也复制一份,树遍历时遇到重复指针事务自动识别状态。

This modification in traversal algorithm requires only one more compare instruction per each tree node visit, which incurs negligible overhead to transactions.

访问一个节点只加一条比较指令,对开销的影响不大

Insertion with FAST for TSO

先看看伪代码

这里为什么会强调TSO呢,前面也提到过,这种结构会天然保证写后写有序

看19-21行,保证先更新指针,再更新key

为什么先是指针,因为算法需要通过识别重复指针去判断状态

写加锁,但可并发读,这也是算法核心卖点之一

这里修改时候可能会有一个问题,就是key的大小不一定只有8字节,所以会破坏这种原子性。但是不会影响,因为算法的判断是看指针,哪怕key坏了也能通过指针识别出来,这是算法设计的一个很聪明的点,将这种失效原子性转为多次分步写,且中间状态都可以识别出来,并且中间状态不会影响读

FAST for Non-TSO Architectures

把上面搞清楚之后,我们不难猜到这里是为了保证算法在NTSO架构下依然能保证先改指针再改key(读完才意识到这种先入为主的想法有点问题,真正要保证的不是先改谁,而是能不能识别出中间状态)

考虑第一种情况,key和ptr都小于八字节,那二者都可以保证原子性同时处于部分更新的状态(但不能保证先改ptr,也就意味着可能先改key,然后系统重启后无法识别到底是中间状态还是重复key)

第二种情况,key大于八字节,一种简单的方式就是加个fence指令,强制让ptr先写,尽管这种方法会用很多的fence指令,但是fence指令的开销是比flash小很多的,而在这种架构下flash的指令数不会变多。另外一种方法就是可以把大key放到内存堆里,b+树只存指针,这样就可以避免大量fence指令,但这样的方法无疑会影响空间局部性,同时也会增加一次寻址,从而降低性能

第一种情况论文里没有提到,我们去拉github代码看看是怎么处理的

遗憾的是代码里实际上没有对NTSO的处理,也就是说理论上我们考虑的第一种情况是可能发生的

Deletion with FAST

插入是从后往前复制并右移,删除就是反过来,从前往后复制,是一样的

Failure-Atomic In-place Rebalancing(FAIR)

FAIR: Node Split

回顾一下,FAST其实是插入和删除,通过移位操作来保证B+树的原子性

FAIR要做的事情,是保证树节点分裂和合并过程的原子性

传统方法用日志,记录写的开销大

In our B±tree design, we store a sibling pointer not just for leaf nodes but also for internal nodes as in the B-link tree 25.

注意这里的B+树实际上是加上了兄弟指针的

这幅图可以看成两行,第一行讲的是分裂兄弟节点和用fast安全插入数据

第二行讲的是不使用日志将新节点挂载到父节点上

看伪代码不难发现FAIR修改兄弟指针时用的时fence,插入新数据时用的是FAST

接下来举例分析其搜索的有效性,就不赘述了

The basic assumption that makes this redundancy and inconsistency tolerable is that the right sibling nodes always have larger keys than the left sibling nodes.

也就是说,如果存在冗余数据,这一假设自然不成立,所以能依赖这一假设识别冗余状态,冗余状态下B节点是不会被访问的

FAIR: Node Merge

接下来是节点合并

Once we detach the underutilized node from the parent node, we check whether the underutilized node and its left sibling node can be merged. If the left sibling node does not have enough entries, we merge them using FAST. If the left sibling node has enough entries, we shift some entries from the left sibling node to the underutilized node using FAST, and insert the new smallest key of the right sibling node to its parent node

先移除父节点的指针,防止并发线程访问。再检查左兄弟,加起来够放就放过去,不够就把左兄弟的分一点过去

背景:多核系统的普及,增加了并发访问,所以数据结构必须以线程安全的方式去管理。

前面的FAST和FAIR能保证8字节的写原子性,同时读事务也可以并发访问

However, the B-link tree has to acquire an exclusive lock to update a single tree node atomically. However, leveraging the FAST algorithm, we can design a non-blocking lock-free search algorithm to allow concurrent accesses to the same tree node as described below.

前提是同一方向的读和写,即从左边往后读到一半挂起线程,新写的节点只能在未读的部分,才能正常进行(这就是所谓的方向一致性,即读操作跟着写操作的尾巴走

算法的优势在于,传统b-link tree需要一个锁来更新节点,而这种算法在搜索时时可以不需要锁的,他有三重检查机制

这样的一个无锁搜索算法,确实让并行程度加大了,但是同时也会导致一些幻读和脏读的问题

Suppose a write transaction inserts two keys - 10 and 20 into a tree node and another transaction performs a range query and accesses the tree node concurrently. The range query may find 10 in the tree node but may not find 20 if it has not been stored yet

In the database community, various levels of isolation such as serializable mode, non-repeatable read mode, read committed mode, and read uncommitted mode

这里提到数据库的四种隔离级别,简单了解一下

  1. Read Uncommitted(读未提交)
    • 含义:一个事务可以读取到另一个事务**尚未提交(Commit)**的数据。
    • 解决的问题:什么都不解决,因为它是级别最低的。
    • 缺陷 :会发生脏读 。比如前文所说的,写事务刚插入了 10 还没插入 20,读事务就读到了 10。如果写事务随后失败回滚了,读事务拿到的 10 就是假数据(脏数据)。
    • 与本文的联系:前文提到,由于底层使用的是无锁搜索,读操作可以直接看到写操作的一半结果,因此它在语义上就是"读未提交"级别。
  2. Read Committed(读已提交)
    • 含义 :一个事务只能读取到另一个事务已经提交的数据。
    • 解决的问题:解决了"脏读"。
    • 缺陷 :会发生不可重复读。比如你在同一个事务里查了两次数据,第一次查完,另一个事务修改并提交了那条数据,你第二次查的时候发现数据变了。
    • 常见应用:Oracle、SQL Server、PostgreSQL 的默认隔离级别。
  3. Repeatable Read(可重复读 / 不可重复读模式)
    • 含义:保证在同一个事务内,多次读取同一个数据的结果是始终一致的。
    • 解决的问题:解决了"脏读"和"不可重复读"。
    • 缺陷 :可能会发生幻读 (Phantom Read)。即你在事务里进行范围查询(比如 age > 20),查到了 5 个人;此时另一个事务插入了 1 个 age=25 的人并提交;你再次范围查询时,突然变成了 6 个人,就像出现了幻觉。
    • 常见应用:MySQL (InnoDB 引擎) 的默认级别。
  4. Serializable(串行化)
    • 含义:最高级别。所有事务强制排队,一个接一个地串行执行,相当于完全不并发。
    • 解决的问题:彻底解决"脏读"、"不可重复读"和"幻读"。
    • 缺陷:并发性能极差,容易发生锁超时和死锁。

所以这里是一个很奇怪的地方,尽管作者做了一个高并发效率很高的东西,但是竟然只处在隔离的第一个级别,看上去并不实用,为什么呢?

常见的几种解决这种冲突的方案是这样的

To support higher isolation levels, we must resort to other concurrency control methods such as key range locks, snapshot isolation, or multi-version schemes

然而这样的方式会带来更大的开销,毕竟不管是大范围key加锁,快照,还是版本控制,听着就是很大的实现和管理机制

作者提出的方案是这样的

叶子节点加读锁,内部节点不加锁

叶子节点加锁有助于避免脏读和幻读的问题,而内部节点不需要加锁,因为FAIR的算法机制决定了哪怕我找不到这个key要做的就是多走一步去兄弟节点里找罢了,加锁会大大降低并发度

急性恢复是说停止服务,修复好之后再开始,而惰性恢复说的是一边提供服务一边恢复

We do not let read transactions fix tolerable inconsistency because read transactions are more latency sensitive than write transactions.

读操作如果被拖延,感觉是很明显的,比如页面卡顿,相对来说写操作的敏感性就要差点,而且大多数系统都是读多写少,让读事务去修复还要额外去加锁

因此不能让读事务去修复不一致

In the FAIR scheme, the role of adding a sibling node to the parent node is not confined to the query that created the sibling node。Even if a process that split a node crashes for some reason, a subsequent process that accesses the sibling node via the sibling pointer triggers a parent node update.

这里很有意思,FAIR会在弄好兄弟节点后就释放锁,而不急着挂到父节点上,而其他处理到这里的进程会顺手把这件事做了。我理解的为什么不直接做呢,可能是因为要等待父节点的锁,也是一笔额外的开销。

Experiments

  • wB±tree:插入太慢,依赖日志,违背了 PM 免日志的初衷。
  • FP-Tree:把索引放 DRAM,根本做不到 PM 的"瞬间恢复",是个假 PM 索引。
  • WORT:范围查询太烂,缓存不友好,不通用。
  • SkipList:并发和免日志做得好,但牺牲了 B+ 树强大的范围查询能力(且作者暗示其查路径更长)。

以上是当时业内主要的几种方法

图三说的主要是随着节点增大插入时间增长,但搜索时间降低,因为节点增大树高也降低了,比较次数减少。这里的线性或者二分查找说的都是五并发的情况,因为FAST不支持二分

作者想说的其实是,在一定的条件下,线性搜索不会比二分搜索慢多少,所以我们的FAST是合理的

Range Query

哈希表直接找东西很快,但是不能范围查询

图四说的是在不同数据结构下用该算法进行范围查询的表现。

PM Latency Effect

图五说的是在差一点的设备,读写都很慢的情况下,该算法依然具有优势

图六更进一步,分别看读写延迟对search和write的影响

Performance on Non-TSO

最关键的来了,这是论文最主要的缺点,在这上面的表现不如WORT等算法,以下是作者提出的缓解方案

To evaluate that the performance of FAST on non-TSO architectures, we add dmb instruction as a memory barrier to enforce the order of store instructions and measure the insertion performance of FAST and FAIR on a smartphone,

这里dmb指令相当于x86架构的fence指令,主要是用于写顺序的

our proposed FAST+FAIR shows worse performance than FP-tree although FP-tree calls more cache line flush instructions. This is because FAST+FAIR calls more memory barrier instructions than FP-tree on ARM processors (16.2 vs. 6.6). However, as the write latency of PM increases, FAST+FAIR outperforms other indexes because the relative overhead of dmb becomes less significant compared to that of the cache line flushes (dccmvac). In our experiments, the performance of FAST+FAIR is up to 1.61 times faster than wB±tree.

关键还是fence的问题,哪怕用的flash更少,但是停一下对整体的影响是更大的

TPC-C Benchmark

TPC-C benchmark is an OLTP benchmark that consists of 5 different types of queries (New-Order, Payment, Order-Status, Delivery, and Stock-Level). We varied the percentage of these queries to generate four different workloads so that the proportion of search queries increases from W1 to W4.

Concurrency and Recoverability

以上其实都是单线程下的测试,没有到算法的核心卖点,接下来看并发的情况

Although wB±tree and WORT are not designed to handle concurrent queries, they can employ well-known concurrency control protocols such as the crabbing protocol

原来还可以用并发控制协议

Instead, we present the performance of B-link tree for reference because it is known that B-link tree outperforms the crabbing protocol

但作者用B-link树,因为效果更好

相关推荐
Rocky Ding*20 小时前
深入浅出GroundingPI技术解析:精确空间感知如何迁移到机器人动作
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·具身智能
m4Rk_1 天前
【论文阅读】Agent 记忆机制(116):Hybrid Memory——用互补策展与离线巩固构建长期记忆
论文阅读·人工智能·学习·开源·github
Rocky Ding*1 天前
【三年面试五年模拟】2026-10-26 携程 AI Agent一面面经全解析
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·ai agent
Rocky Ding*2 天前
深入浅出Xiaomi-Lens-0技术解析:像素监督如何改变视觉推理
论文阅读·人工智能·深度学习·机器学习·aigc·多模态·ai-native
天树星智源2 天前
静态物理满分、交互 11.54%:3D 场景生成系统的幻觉
论文阅读·人工智能·3d·机器人
m4Rk_4 天前
【论文阅读】Agent记忆机制(114):MemoryTax——当 Agent 能自主搜索历史,专门的记忆系统还划算吗?
论文阅读·人工智能·学习·开源·github
Rocky Ding*4 天前
【三年面试五年模拟】2026-10-25 华为大模型算法岗一面面经:13道题解析与算法实现
论文阅读·人工智能·深度学习·机器学习·大模型·aigc·ai-native
m4Rk_5 天前
【论文阅读】Agent 记忆机制(113):Agentic Memory Management——从“检索相关信息”走向“管理 Agent 内部记忆”
论文阅读·人工智能·学习·开源·github
Curious*5 天前
意识障碍--论文阅读5
论文阅读