算法与数据结构 / B-tree、B+ tree 与 LSM-tree · 磁盘与页的世界 待审核 7 页

B-tree、B+ tree 与 LSM-tree · 磁盘与页的世界

内存里的查找树按比较次数计费,磁盘上的不是。块设备的最小读取单位是——4 KB 或 16 KB,读一个字节和读一整页的代价几乎相同,而这一次读的延迟比一次内存访问高三到五个数量级。代价模型一换,最优结构随之改变:要压的不再是树高的对数常数,而是树高本身。

本系列分三条线。第一条把账算清楚:同样 1000 万条记录,完全平衡的二叉查找树高 24,而 16 KB 页、8 字节 key 的 B-tree 扇出 1170、树高只有 3。代价从 24 次随机读降到 3 次,付出的是节点内的比较次数上升——100 万条记录的实测里,阶 4 的树每次点查比较 21.8 次,阶 256 的树是 197.6 次。省访存的手段就是多比较。

第二条线是 B-tree 家族本身:插入时节点满则分裂、中位 key 上提,树高只从根往上长;删除要在借 key 与合并之间选,比插入麻烦得多。B+ tree 把数据全部下沉到叶子、内部节点只留路标,于是同样的页装得下更多路标,叶子之间还能用链表串起来,让范围扫描从随机读变成顺序读。数据库索引选 B+ 而不是 B,理由全在这两条上。

第三条线走向另一种取舍。LSM-tree 不再原地改页:写入先追加 WAL 再进内存表,满了整块冻结成不可变的 SSTable 刷盘,后台 compaction 分层归并。读因此要摸多层,靠 Bloom filter 把「这个文件肯定没有该 key」判掉。写放大、读放大、空间放大三者不可能同时最优,leveled 与 tiered 两种 compaction 各占三角形的一边。

页作为代价的单位

一次随机读的代价与读多少字节几乎无关,于是「访问了几个节点」取代「做了几次比较」成为主指标。这一条改写了整个结构设计:节点被撑到一整页大,扇出从 2 变成上千,树高塌成个位数。

与平衡 BST 的分界线

树 · 遍历、平衡 BST 与前缀 / 度量树 里的红黑树、AVL、treap 全都在压同一个量:树高的常数因子。它们的分歧是「用颜色还是用高度还是用随机 priority 维持平衡」,而扇出恒为 2 从不在议题上。 B-tree 换掉的正是这个默认值。扇出一旦可调,树高就从 log2n\log_2 n 变成 logfn\log_f nff 由页大小除以 entry 大小决定,通常是几百到一千多。同样 1000 万条记录,二叉树 24 层、B-tree 3 层。代价模型的改变比算法的改进更能压低代价,这是本系列反复出现的一条。

页、代价模型与 B-tree 起源 · 延伸阅读

B-tree 家族

同一套阶 mm 的约束长出两种树。B-tree 的 key 与数据散布在全部层级,B+ tree 把数据压进叶子、内部节点只当路标。分裂与合并的规则两者共享,范围查询的代价则天差地别。

删除比插入难,难在修复的分支数

插入的修复只有一种形态:节点溢出就分裂,中位 key 交给父节点,父节点若也溢出再分裂,一路到根。整条链上每个环节的动作完全相同。 删除不是。一个下溢的节点有三条出路——从左兄弟借、从右兄弟借、与某个兄弟合并,且必须按这个顺序试;B-tree 还多一种情形:被删的 key 落在内部节点上,得先用左子树的最大 key 顶替,把问题转成删叶子里的那个 key。实测 m=5m = 5 的 B-tree 装 1000 个 key 再删光,插入侧只有 365 次分裂,删除侧是 356 次借 + 365 次合并 + 296 次前驱顶替。

B+ tree 与数据库索引 · 延伸阅读

LSM-tree 与写优化

原地更新意味着随机写。LSM-tree 换成「只追加、后台归并」,把随机写摊成顺序写,代价是同一个 key 的多个版本散在各层,读要逐层找、空间要为旧版本买单。三个放大之间的取舍由 compaction 策略决定。

三个放大不可能同时最优

写放大、读放大、空间放大构成一个取舍三角,任何 compaction 策略只能占其中两个角。同一份负载(20 万次写、10 万个 key、T=10T = 10)在本系列引擎上的实测: leveled 每层是一条不重叠的有序 run,点查每层至多摸一个文件——不带 Bloom filter 的未命中读放大 2.85,空间放大 1.19,代价是写放大 56.61。 tiered 每层攒够 TT 个 run 才整层合并,写放大只有 4.77,代价是未命中读放大 14.92、空间放大 1.93。 两者的读放大差 5.2 倍、写放大差 11.9 倍。选哪个不取决于哪个「更好」,只取决于负载是写多还是读多。

LSM-tree 与 compaction · 延伸阅读