数据库原理 LSM vs BTree

  • 1. 数据库的本质
  • 2. Hash索引
  • 3. Segment
  • 4. SSTable 和 LSM-Tree
  • 5. 总结
  • 参考文档

1. 数据库的本质

数据库的本质就是帮助我们存储和获取数据个中间件。 最简单的数据库可以通过下面两个脚本实现。

  • set.sh 存储
[qa@qaserver1 db]$cat set.sh
#!/bin/bash

echo "$1,$2" >> database
  • get.sh 获取
[qa@qaserver1 db]$cat get.sh
#!/bin/bash

cat database | grep "$1," | tail -n 1 | awk -F, '{print $2}'

这个实现写入十分高效,所有的写入都是顺序写入,查询性能就比较糟糕,时间复杂度O(n),每次都是全部扫描。

数据库设计者们通过修改数据的存储结构,或者在数据外额外的维护一份元数据来加速查询过程,这一份额外的元数据被称为索引,最常见的索引是Hash索引、LSM-Tree、 B+Tree。

2. Hash索引

以我们上面举例的最简单Key-Value数据库为例,最直观的索引结构就是Hash索引,在内存中维护一个HashMap,HashMap的Key就是数据库的Key, HashMap的Value是文件内位置(offset)。

image.png

有了Hash索引后,每次我们可以通Key找到记录地址,seek到特定位置,直接读取数据返回。 每次写入数据时,需要同时更新Hash索引。

Hash索引有两个明显的缺陷:

  • 内存需要能过放下索引的整个HashMap
  • Range Scan效率低下

3. Segment

数据文件如果只是不断的追加,即使数据库系统不间断正常运行,文件会越来越大,文件里的无用信息越来越多(一个Key的值修改了N次,文件里记录了N条,只有最后一条有意义),如果从文件从新构建索引,耗时也会很长。

于是数据库设计者们想到了将数据文件切分,切分的每一段被称为一个Segment,只有当前Segment会有数据Append动作,历史Segment都可以认为是固定的。

那我们就可以启动后台线程对历史Segment做合并压缩(Compaction),一个Key的多次操作只保留最后一条,之前已经标记为删除的数据移除。

image.png

每个Segment都有对应的Hash索引,查找特定Key时,我们需要依次查询Segment对应的Hash索引。

这类存储引擎称为 Append-Only Log-Structed。 它们的优点是:

  • 永远只做顺序写入,写入效率极高

确定也很明显:

  • Hash索引庞大,需要存入到内存,然而内存大小永远是受限的
  • 一个Key可能在多个Segment里,需要遍历每个Segment(至少检查每个Segment索引是否包含Key)

4. SSTable 和 LSM-Tree

前面提到用Hash索引是需要将每个Key保存到内存中,但是内存可能无法放下所有的Key。 SSTable完美解决了这个问题, SSTable全称Sorted String Table, 要求每个Merged-Segment里Key只出现一次,且Segment内Key是有序的。

相比之前简单的拆分Segment,SSTable有如下优势:

  1. SSTable内的Segment是有序的,所以合并Segment要高效的多,也不需要完整的加载两个Segment排序然后合并
  2. Segment有序,我们的索引里不需要完整的保存索引Key,而至少维护一个稀疏索引
  3. Segment有效,在Range-Scan的只需要顺序扫描,十分高效

那如何做到Segment内部有序呢? 因为数据写入会先经过内存,我们可以在内存维护一个红黑树(Red-Black Tree)或者平衡二叉树(AVL Tree)。

写入SSTable的工作流程:

  1. 写入Write-Ahead-Log,用于避免宕机导致Memtable丢失,通过重做日志恢复Memtable
  2. 数据插入到平衡二叉树中,内存中的这棵树我们称为Memtable
  3. 当Memtable超过一定阈值后,将Memtable Flush到磁盘,输出的文件被称为SSTable,也是之前说的一个Segment
  4. 清除已经写入SSTable的Memtable数据,比如新生成一个Memtable接收新数据,老的Memtable用于写入SSTable的,写完后丢弃
  5. 记录已经固化的Write-Ahead-Log的Position,下次重做日志从这个Position开始

读取SSTable的工作流程:

  1. 查询每一个SSTable的索引,确认是否包含给定Key,包含则读取对应Value
  2. 后面读取的SSTable的Value值,覆盖之前的值
  3. 读取当前正在写入Memtable,如果读到覆盖之前的值

==LSM-Tree的三大核心主键就是: Write-Ahead-Log、 Memtable 、 SSTable, 上面我们讲的其实就是LSM-Tree的核心思想。==

LSM-Tree已经被广泛使用, 如LevelDB、 RocksDB、 Cassandra 、 HBase都采用这个核心架构。 SSTable的概念也被广泛借鉴,如ClickHouse、 Palo里都有相关概念。

5. 总结

LSM-Tree的优点:

  • 高吞吐的写
  • 可以高效的压缩,更节省磁盘(B-Tree一般会为Page的分裂预留一些空间)

LSM-Tree的缺点:

  • Compaction会影响正常数据的读取,读取性能略有影响。
  • 数据量越大,Compaction需要的磁盘带宽就越多。
  • B-Tree中Key只在一个Page中,LSM-tree可能出现在多个Segment中,影响读取性能,想要事务加锁也更困难一些。

 
 
 


参考文档

  • https://blog.bcmeng.com/post/lsm-tree-vs-b-tree.html

 
 
 

你可能感兴趣的:(数据库原理 LSM vs BTree)