当前位置: 首页 > article >正文

005.精读《B-Tree vs LSM-Tree》

文章目录

    • 1. 引言:
    • 2. 精读
      • 2.1 性能指标
      • 2.2 B-tree
      • 2.3 LSM-tree
      • 2.4 性能对比
    • 3. 写在最后

1. 引言:

在本期的技术深度解析中,我们将聚焦于数据领域的两个重要成员——B-TreeLSM-Tree。这两种数据结构在数据管理系统中最为普遍且广泛采用的数据结构。B-Tree以其在磁盘存储系统中的广泛应用而闻名,而LSM-Tree则因其在写入优化方面的表现而受到青睐。随着数据量的不断增长和数据访问模式的多样化,理解这两种数据结构的优势和局限变得尤为重要。本期精读:B-Tree vs LSM-Tree

通过本文,我们将带领读者:

  1. 探讨B-TreeLSM-Tree的起源,它们分别是为了解决什么样的问题而被引入的;
  2. 深入剖析B-TreeLSM-Tree的数据结构特点,以及它们如何在不同的应用场景中发挥作用;
  3. 解析在实际应用中,B-TreeLSM-Tree如何优化存储和检索效率,以及它们在不同数据库系统中的应用案例;
  4. 展望B-TreeLSM-Tree在未来的发展,以及它们在大数据时代中的潜在影响。

欢迎在评论区分享您的观点与见解,期待与您交流讨论!

2. 精读

2.1 性能指标

在评估数据结构的性能时,三个关键指标——写放大、读放大和空间放大——为我们提供了衡量效率和优化程度的量化手段。这些指标不仅帮助我们理解数据结构在实际应用中的表现,还能够指导我们对它们进行针对性的优化。以下是对这些指标重要性的详细阐述:

  1. 写放大(Write Amplification):写放大是指在数据写入过程中,由于数据结构的维护和管理,实际写入存储介质的次数超过了原始数据量的情况。在B树中,每次插入或更新操作可能涉及多次磁盘I/O操作,因为B树需要保持其平衡特性。LSM树虽然通过批量写入减少了磁盘I/O,但在合并过程中也会产生额外的写入。写放大对于理解数据结构的耐久性和性能至关重要,因为它直接关系到存储介质的寿命和系统的能耗。由于闪存的这种工作方式,必须擦除改写的闪存部分比新数据实际需要的大得多。写入放大的主要有以下几个因素:
因素描述类型关系*
垃圾回收用来挑选用于擦除和重写的最佳块的算法的效率变量反面(小为好)
预留空间分配到SSD控制器的物理容量百分比变量反面(小为好)
SATA的TRIM命令 或 SCSI中的UNMAP这些命令必须由操作系统(OS)发送,可以通知存储设备哪些扇区含有无效数据。可以处理这些命令的SSD能在擦除块时,将包含这些扇区的页作为空闲空间回收,而不是复制无效的数据到干净的页中。开关正向(小为好)
空闲用户容量没有实际用户数据的用户容量百分比;需要TRIM,否则SSD不会从任何空闲的用户容量中获得好处变量反面(小为好)
安全擦除擦除所有的用户数据和相关元数据,将SSD的性能重置到最初状态(直至重新开始垃圾回收)开关正向(小为好)
耗损均衡算法的效率,令每块的写入次数与其他的块尽可能相同变量正面(大为坏)
静动数据分离将数据按修改频率分组开关正向(小为好)
顺序写入理论上,顺序写入的写入放大为1,但其他因素仍会影响此值开关正向(小为好)
随机写入写入到非连续的LBA对写入放大的影响最大开关反向(大为坏)
数据压缩,包括重复数据删除数据压缩和重复数据删除能消除更多的冗余数据,降低写入放大,同时提升SSD速度。变量反面(小为好)
以SLC模式使用MLC NAND以每单元1位,而不是以设计的每单元位数(通常为每单元2位)写入数据,以加快读取和写入操作。如果临近SLC模式下的NAND容量限制,SSD必须把旧有用SLC模式写入的数据改写为MLC或TLC模式,才能让SLC模式的NAND空间释放出来,以便容纳更多的数据。然而,通过让经常更改的页面维持在SLC模式,而不是以MLC或TLC模式修改,这种做法也可以降低磨损,因为比用SLC模式,用MLC或TLC模式写入对闪存的伤害确实更大。因此,这种做法提高了写入放大,但当写入模式设为向常写页面上写入时,却可以减少磨损。然而,顺序和随机写入却会加剧磨损,因为这样就没有或少有频繁写入的页是SLC模式,迫使旧数据不断地从SLC模式改写到MLC或TLC。开关反向(大为坏)
  1. 读放大(Read Amplification):读放大是指为了获取所需数据,数据结构需要读取的数据量超过了原始请求的数据量。在B树中,为了找到一个键,可能需要访问多个节点,尤其是当树的高度增加时。LSM树在读取数据时,可能需要检查多个层级的SSTable,以找到最新的数据版本。读放大影响了数据访问的效率,尤其是在读取密集型应用中,它可能导致性能瓶颈。

  2. 空间放大(Space Amplification):空间放大是指数据结构为了维护其性能特性,如B树的平衡性或LSM树的写入优化,而额外占用的存储空间。B树由于其结构特性,通常有较低的空间放大,因为它不需要额外的空间来维护数据的顺序。相反,LSM树为了优化写入性能,可能会保留多个数据版本,从而导致空间放大。空间放大对于存储成本和数据压缩策略有着直接的影响。

通过引入这些指标,我们能够更全面地评估和比较不同的数据结构,这意味着一种数据结构不太可能在所有三个方面都比另一种好。例如,B树的读放大比LSM树少,而LSM树的写放大比B树少。这些指标不仅帮助我们理解数据结构的工作原理,还能够指导我们如何针对特定的应用场景进行优化,以达到性能与资源利用之间的最佳平衡。从而在实际应用中做出更明智的选择。

2.2 B-tree

B树是一种自平衡的多路搜索树,它通过有序存储数据,确保查找、插入、删除和顺序访问操作的高效性,其时间复杂度为对数级别。B树节点分为内部节点和叶子节点,内部节点存储数据和子节点指针,而叶子节点仅存储数据。与二叉树不同,B树的每个节点可以拥有多个子节点,这使得它在处理大量数据时更为高效,特别是在磁盘存储系统中。AVL树作为自平衡二叉树的先驱,通过保持树的高度平衡,优化了二叉搜索树的性能。B树继承了这些优点,并在数据库和文件系统中得到广泛应用。

一棵m阶的B树是一种自平衡的树形数据结构,具有以下关键特性:

  1. 节点子节点限制:每个节点最多可以有m个子节点。
  2. 非叶子节点子节点最小值:除了根节点外,每个非叶子节点至少有m/2个子节点,如果m是偶数,则取m/2的上界,确保树的平衡性。
  3. 根节点子节点要求:如果根节点不是叶子节点,它至少需要有两个子节点,以维持树的结构。
  4. 键的有序性:在非叶子节点中,如果有k个子节点,则存在k-1个键,这些键按升序排列,即对于任意的i,都有 k[i] < k[i+1]
  5. 键的数量限制:每个节点包含的键的数量最多为2k-1,这是由子节点数量和键的有序性共同决定的。
  6. 叶子节点的同层性:所有的叶子节点都位于树的同一层,这有助于保持树的平衡和提高访问效率。

如上图展示了树的根节点位于顶部,在本例中恰好包含一个pivot(20),这表明键k满足k ≤ 20的记录存储在第一个子节点中,而键k满足k > 20的记录存储在第二个子节点中。第一个子节点包含两个pivot键(11和15),这表明键k满足k ≤ 11的记录存储在第一个子节点中,11 < k ≤ 15的记录存储在第二个子节点中,而k > 15的记录存储在第三个子节点中。最左边的叶子节点包含三个值(3, 5, 和 7)。

B+树是B树的增强版本,它在实际应用中,特别是在操作系统的文件索引和数据库索引方面,展现出比B树更高的适用性和效率。在现代关系型数据库中,B+树被广泛采用作为其索引结构的核心,这得益于其优化的数据存储和访问机制,使其成为处理大规模数据检索的首选数据结构。以下是B树和B+树的区别

特性B树B+树
数据存储位置内部节点和叶子节点都存储数据只有叶子节点存储数据,内部节点仅存储索引键
叶子节点链接叶子节点之间没有链接叶子节点之间通过指针相连,形成链表
查询效率适合随机访问,但范围查询和顺序访问效率较低适合范围查询和顺序访问,效率较高
磁盘I/O操作可能需要多次磁盘I/O操作来访问数据由于叶子节点形成链表,可以减少磁盘I/O操作次数
内部节点结构内部节点存储数据和子节点指针内部节点仅存储索引键和子节点指针
适用场景适用于需要随机访问和更新的场景适用于数据库和文件系统中的索引结构,特别是范围查询和顺序访问
节点分裂和合并当节点达到最大容量时分裂,容量过低时合并同样在节点达到最大容量时分裂,容量过低时合并
平衡性保持自平衡,通过旋转和重新分配节点来保持平衡同样自平衡,通过旋转和重新分配节点来保持平衡

2.3 LSM-tree

LSM树(Log-Structured Merge Tree)是一种高效的数据结构,专为优化数据的存储和检索而设计。它通过创新的数据组织方式,实现了高吞吐量的写入操作和高效的读取性能。LSM树的诞生旨在解决传统B树在面对大量写入操作时的性能限制,尤其适用于高写入负载的环境,如分布式数据库系统、日志记录系统和缓存机制等。

LSM树的核心理念是将随机写入操作转换为顺序写入,这一策略显著提升了写入效率。在LSM树中,所有新的数据首先被写入内存中的写入缓冲区,随后定期批量写入到一个有序的日志文件中。由于这些写入操作主要在内存中完成,因此大大减少了对磁盘的随机写入需求,从而极大提高了整体写入性能。随着时间的积累,内存中的数据会通过合并过程逐步迁移到磁盘上的多个层级,这样做不仅保持了数据的有序性,也确保了数据的持久化存储。

内存中的数据结构

  1. Memtable
  • 描述:内存中的可变数据结构,存储最新的写入数据。
  • 实现:常用跳表、红黑树或AVL树,以支持快速的插入、删除和查找操作。
  1. Immutable Memtable
  • 描述:当Memtable达到设定的大小限制后,转变为不可变状态,等待刷写到磁盘的SSTable中。
  • 特点:不再接受新的写入,保持数据的稳定性。

磁盘上的数据结构

  1. SSTable
  • 描述:Sorted String Table,磁盘上的持久化数据结构,存储有序的键值对。
  • 特点:一旦写入,SSTable是只读的,按时间顺序组织。
  1. 布隆过滤器(Bloom Filter)
  • 描述:一种概率性数据结构,用于快速判断某个键是否可能存在于SSTable中。
  • 特点:高效减少不必要的磁盘I/O操作。
  1. 写前日志(WAL,Write-Ahead Log)
  • 描述:用于故障恢复的日志结构,记录所有写入操作。
  • 特点:提供持久性保障,以确保数据在崩溃后可恢复。

LSM-tree 写操作

在写入数据到 LSM 树时,主要流程包括 MemtableImmutable MemtableSSTable 的生成以及 Compaction 操作。

  1. Memtable:内存中的可变部分,用于快速写入新的键值对。
  2. Immutable Memtable:当 Memtable 达到大小限制后,转换为不可变状态,用于后续的持久化操作。
  3. SSTable:磁盘上的持久化文件,存储有序的键值对,通常伴随索引和布隆过滤器等辅助数据结构。
  4. Compaction:定期将多个 SSTable 合并,以优化存储和查询性能。
  1. 写入数据到 Memtable

    • 新的键值对首先写入内存中的可变 Memtable
    • Memtable 通常是一个有序的数据结构,例如跳表或红黑树。
Memtable:
key1 -> value1
key2 -> value2
key3 -> value3
  1. Memtable 达到大小限制并转换为 Immutable Memtable

    • Memtable 达到预设的大小限制(如 1MB),会被转换为 Immutable Memtable
    • 当前的 Memtable 被清空,并创建一个新的可变 Memtable,以接收新的写入。
Memtable 达到大小限制:
- 当前 Memtable 被转换为 Immutable Memtable:
  Immutable Memtable:
  key1 -> value1
  key2 -> value2
  key3 -> value3
- 创建一个新的空的 Memtable:
  Memtable: (空)
  1. Immutable Memtable 数据写入到 SSTable

    • Immutable Memtable 的数据会被刷新到磁盘,生成新的 SSTable
    • 数据从内存中按顺序写入 SSTable 文件,同时生成元数据(如索引和布隆过滤器)。
Immutable Memtable 数据被写入到新的 SSTable:
- 从 Immutable Memtable 读取数据
- 将数据以有序方式写入 SSTable 文件:
  SSTable 0:
  key1 -> value1
  key2 -> value2
  key3 -> value3
  1. 继续写入新数据到新的 Memtable

    • 清空后的 Memtable 继续接收新的数据写入。
    • 上一个 Immutable Memtable 已经开始持久化为 SSTable
写入操作:
应用程序 -> Memtable:(key4, value4)
应用程序 -> Memtable:(key5, value5)

Memtable:
key4 -> value4
key5 -> value5
  1. 定期执行 Compaction 操作

    • 随着 SSTable 文件的增加,系统定期执行 Compaction 操作,以合并多个 SSTable 文件。
    • Compaction 有助于移除重复或过期的数据,提升查询效率。
Compaction 之前:
SSTable 0:
key1 -> value1
key2 -> value2
key3 -> value3

SSTable 1:
key4 -> value4
key5 -> value5

Compaction 操作:
1. 选择 SSTables 0 和 1 进行合并。
2. 读取数据进行合并:
   合并后的数据:
   key1 -> value1
   key2 -> value2
   key3 -> value3
   key4 -> value4
   key5 -> value5
3. 写入合并后的数据到新的 SSTable:
   SSTable 2:
   key1 -> value1
   key2 -> value2
   key3 -> value3
   key4 -> value4
   key5 -> value5
4. 删除旧的 SSTables 0 和 1。

LSM-tree 读操作

LSM树的读操作依赖于有序查找和布隆过滤器的结合,逐级递进以提高查找效率并减少I/O操作。在读取某个键对应的值时,LSM树会从最新的数据开始查找,以确保返回最新的值。这个过程通常包括以下几个步骤:

  1. 检查 Memtable(C0)

首先,查找内存中的 Memtable(C0),因为它包含所有最新的写入。

value = C0.get(key)
if (value exists):
    return value
  1. 检查 Immutable Memtable

如果在 Memtable 中未找到对应的键,接着检查 Immutable Memtable。该 Memtable 是在最近写操作后达到一定大小而转为不可变状态,等待刷入磁盘。

value = ImmutableMemtable.get(key)
if (value exists):
    return value
  1. 检查 SSTables

如果上述步骤均未找到所需的值,则依次检查存储在磁盘上的多个层级 SSTables(C1, C2, …)。采用按时间顺序从最早到最近的方式进行搜索,以确保最新的更新覆盖旧值。

  • 布隆过滤器(Bloom Filter):在每个 SSTable 上使用布隆过滤器,可以快速判断一个值是否不在该文件中,从而减少不必要的磁盘 I/O 操作。
for level in SSTable_levels:
    for sstable in level:
        if sstable.BloomFilter.mightContain(key):
            value = sstable.get(key)
            if (value exists and not tombstoned(value)):
                return value
  1. 数据合并与删除标记

    • 删除标记(Tombstone):如果有删除操作,某些值会带有特殊标记。继续检查以下层级以确保数据删除的幂等性与正确性。
    • 合并读取(Merge Read):当查询经过多个 SSTable 文件时,需要实时合并以确保返回的值是最新的更新。

故障处理与缓存优化

  • WAL(Write-Ahead Log):用于故障恢复,保证数据在崩溃后的正确恢复。
  • 缓存机制:频繁访问的键会缓存在内存中,提高查询性能。有效的缓存策略有助于减少读取操作的延迟。

LSM-tree 更新和删除操作

LSM树的更新和删除操作实质上是写入操作的特例。在LSM树中,更新一个键值对意味着添加一个新的键值对,而删除则是添加一个带有删除标记的键值对

更新操作

  1. 写入Memtable:新的键值对首先被写入内存中的Memtable。如果键已经存在,旧的键值对会被新的键值对替换。
C0.put(key, newValue)
  1. 触发Compaction:当Memtable达到一定大小后,它会被转换为Immutable Memtable,并最终被写入磁盘上的SSTableCompaction过程会合并包含相同键的SSTable,确保最新的值被保留。

删除操作

  1. 标记删除:删除操作不是从MemtableSSTable中物理移除键值对,而是在Memtable中添加一个带有删除标记的键值对。
C0.put(key, tombstone)
  1. Compaction过程中的处理:在Compaction过程中,带有删除标记的键值对会被识别并忽略,从而实现数据的逻辑删除。

更新和删除的Compaction过程

Compaction过程中,LSM树会合并多个SSTable,包括MemtableImmutable Memtable。这个过程会处理所有的更新和删除标记:

  1. 合并数据Compaction会合并所有包含特定键的键值对,确保最新的更新被保留,删除标记的键值对会被忽略。

  2. 生成新的SSTable:合并后的数据会生成新的SSTable,旧的SSTable会被标记为可以被删除。

  3. 优化存储:通过CompactionLSM树可以优化存储空间的使用,移除过时的数据和删除标记,同时保持数据的有序性。

2.4 性能对比

为了解释如何得出B+树和基于级别的LSM树的写放大和读放大的具体计算过程,我们需要考虑它们的数据结构和操作特性。

B+树的读写放大

写放大(Write Amplification):
B+树的写放大主要来自于节点分裂和合并操作。当一个节点达到其最大容量时,它需要分裂成两个节点,这涉及到写入两个新节点的数据。因此,写放大与节点的大小B有关,即每次写入操作可能涉及到多个块的写入。在最坏的情况下,写放大可以达到Θ(B),即每个写入操作都需要写入整个节点的数据。

读放大(Read Amplification):
B+树的读放大取决于树的高度。在最坏的情况下,读取一个数据项可能需要从根节点遍历到叶节点,这涉及到树的高度的磁盘I/O操作。如果每个内部节点包含Θ(B)个孩子,那么树的高度是O(log_B N / B),其中N是数据库的大小。因此,读放大是树的高度,即O(log_B N / B)

LSM树的读写放大

写放大(Write Amplification):
LSM树的写放大较高,因为数据首先写入内存中的Memtable,然后定期刷新到磁盘上的SSTable。随着时间的推移,SSTable会经历多次合并(Compaction)操作,以优化存储和查询性能。每次合并操作都会涉及到写入多个SSTable的数据。如果每个级别的数据大小是前一个级别的k倍,那么写放大包括将数据从每个级别移动到下一个级别,以及在每个级别中重复合并数据。因此,写放大是Θ(k log_k N / B),其中k是每个级别的增长因子,N是数据库的大小。

读放大(Read Amplification):
LSM树的读放大涉及到从多个SSTable中读取数据,以确保获取到最新的数据项。在最坏的情况下,可能需要从所有级别中读取数据。对于最高级别,数据大小是O(N),因此需要O(log N / B)次磁盘I/O操作。对于前一个级别,数据大小是O(N/k),因此需要O(log (N/kB))次磁盘I/O操作。以此类推,对于第i个级别,需要O(log (N/k^i B))次磁盘I/O操作。将所有级别的磁盘I/O操作相加,总的读放大是Θ((log^2 N / B) / log_k)

综上所述可以得到:

Data StructureWrite AmplificationRead Amplification
B+ treeΘ(B)O(log_B N / B)
Level-Based LSM-treeΘ(k log_k N / B)Θ((log^2 N / B) / log_k)

其中,Θ表示大O记号中的紧确界(Theta notation),O表示大O记号,用于描述算法的渐进上界,log_B N表示以B为底N的对数,log_k N表示以k为底N的对数。

3. 写在最后

通过比较B+树和基于级别的LSM树在各种性能方面的表现,我们可以得出结论:基于级别的LSM树在写入性能上优于B+树,而在读取性能上则不如B+树。但是大多数组件选择使用LSM树而不是B树作为其底层存储引擎的主要原因是,利用缓存技术来提升读取性能要比提升写入性能容易得多

如果你想参与讨论,请 点击这里👉https://github.com/hiszm/BigDataWeekly,每周都有新的主题,周末或周一发布。

大数据精读,探索知识的深度。

关注 大数据精读周刊

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)


http://www.kler.cn/a/388284.html

相关文章:

  • dapp获取钱包地址,及签名
  • 第8章 利用CSS制作导航菜单
  • Flink CDC(SQL Client)连接 MySQL 数据库教程
  • STM32学习笔记------GPIO介绍
  • 《基于深度学习的车辆行驶三维环境双目感知方法研究》
  • 【ubuntu】单进程申请4GB内存
  • Linux 内核中断描述符 (irq_desc) 的初始化与动态分配机制详解
  • vue全家桶都有哪些?
  • C++ STL CookBook 6:STL Containers (I)
  • web语义化
  • leetcode hot100【LeetCode 79.单词搜索】java实现
  • 数据库基础(5) . DCL
  • 笔记整理—linux驱动开发部分(7)misc类设备(杂项设备)
  • nginx的相关命令
  • Nginx(编译)+Lua脚本+Redis 实现自动封禁访问频率过高IP
  • Type-C转DP线方案
  • 性能调优专题(7)之Innodb底层原理与Mysql日志机制深入剖析
  • 比流计算资源效率最高提升 1000 倍,“增量计算”新模式能否颠覆数据分析?
  • 学SQL,要安装什么软件?
  • Dart中List API用法大全
  • 帝国CMS7.5仿模板堂柒喜模板建站网 素材资源下载站源码
  • [产品管理-64]:如何通过开放式创新提升产品的创新能力?
  • 动态规划理论基础和习题【力扣】【算法学习day.24】
  • 向日葵软件Windows系统连接苹果系统(MacOS)的无反应问题解决办法
  • 基于python的天气数据采集与可视化分析,对20个城市的天气适宜出行度分析
  • Spring声明式事务 编程式事务