LSM树详解
简介
LSM树(Log-Structured Merge-Tree)是一种用于实现持久化存储的数据结构,通过将数据插入、修改、删除等操作记录保存在内存中,当这些操作达到一定数量后,再批量顺序写入到磁盘中,从而利用顺序写来提高写性能。这种结构特别适合于写入多于读取的应用场景,如大数据存储系统。
背景
LSM树的设计背景正是为了解决传统关系型数据库在读写性能方面的矛盾。
传统关系型数据库通常采用B+树等索引结构来支持高性能的读操作,这种结构在查询时能够快速定位到目标数据,但对于写操作来说,由于需要频繁地进行磁盘的随机写入,导致写操作的性能较低。因此,在需要高性能读写操作的情况下,传统数据库面临着一个取舍:要么追求高效的读操作,牺牲写操作的性能;要么追求高效的写操作,牺牲读操作的性能。
LSM树的设计就是为了解决这一问题。它采用了一种完全不同的存储策略:将数据追加到日志文件中,而不是按照特定的索引结构组织数据。这样的设计使得写操作变得非常高效,因为数据的写入是顺序追加的,不需要频繁地进行磁盘的随机写入。而对于读操作,虽然需要扫描多个文件来获取数据,但由于数据是按照时间顺序存储的,因此可以利用顺序读取的特性,从而提高读操作的效率。
核心思想
LSM树的核心思想是放弃部分读能力来换取写入的最大化能力,通过牺牲小部分读性能来换取高性能的写。
关键组件
-
内存表(Memtable):
内存表是一个可变的数据结构,通常使用有序数据结构(如平衡树或跳表)实现,以便快速插入、删除和查询操作。
所有写操作(插入、更新和删除)首先在内存表中进行。 -
不可变内存表(Immutable Memtable):
当内存表达到一定大小或其他条件时,它会变为不可变内存表。
不可变内存表是只读的,并且等待被刷新到磁盘上的SSTable。 -
磁盘表(SSTable,Sorted String Table):
SSTable 是有序的不变数据文件,存储在磁盘上。
数据一旦从不可变内存表刷新到SSTable后,SSTable不再变化。
每个SSTable文件通常有一个对应的元数据文件,记录该文件中数据的范围(最小键和最大键)以及其他信息。 -
汇总表(Summary Table):
汇总表存储每个SSTable文件的最小键和最大键范围。
它允许系统在查询时快速跳过不可能包含目标键的SSTable,从而加快查询速度。 -
布隆过滤器(Bloom Filter):
每个SSTable通常都有一个布隆过滤器,用于快速判断某个键是否可能存在于该SSTable中。
布隆过滤器通过多个哈希函数生成多个位,减少不必要的磁盘I/O操作。
功能
插入数据
- 写入到内存表:
- 新数据首先被插入到内存表中。
- 内存表是一个可变的数据结构,支持快速写入操作。
- 转换为不可变内存表:
- 当内存表达到预设的大小或其他条件时,它会变为不可变内存表,并启动一个新的内存表。
- 不可变内存表将不会被修改,只等待被刷新到磁盘。
- 刷新到SSTable:
- 不可变内存表被异步刷新到磁盘,生成一个新的SSTable。
- 刷新过程中,数据按照键的顺序写入磁盘,生成有序的SSTable文件。
- 更新汇总表和布隆过滤器:
- 在生成新的SSTable时,系统会更新汇总表和布隆过滤器,记录新的SSTable的键范围和过滤信息。
查询数据
- 在内存表中查找:
- 首先在内存表中进行查找操作。如果找到目标键,则直接返回对应的值。
- 在不可变内存表中查找:
- 如果在内存表中未找到目标键,则继续在不可变内存表中查找。
- 使用汇总表和布隆过滤器查找SSTable:
- 如果在不可变内存表中也未找到目标键,系统会利用汇总表中的信息快速判断哪些SSTable可能包含目标键。
- 使用布隆过滤器进一步过滤,判断目标键是否可能存在于这些SSTable中。
- 在SSTable中查找:
- 对于布隆过滤器判断可能存在目标键的SSTable,系统在这些SSTable中进行实际的查找。
- 查找过程中,使用SSTable的元数据进行二分查找或其他高效查找算法。
- 返回查询结果:
- 如果在任一SSTable中找到目标键,则返回对应的值。
- 如果在所有相关的SSTable中都未找到目标键,则返回不存在的结果。
删除数据
- 在内存表中标记删除:
- 删除操作通过在内存表中插入一个墓碑标记实现,表示该键的数据已被删除。
- 刷新到SSTable:
- 当内存表刷新到磁盘时,墓碑标记也会被写入SSTable。
- 在后续的合并操作中,墓碑标记可以用于删除对应的旧数据。
压缩
目的——
- 压缩操作的目的是减少磁盘上的数据量,从而节省存储空间。
- 随着LSM树中的写入和删除操作的持续进行,磁盘上的SSTable文件会逐渐增多,而其中可能存在大量重复、过时或已删除的数据,占用了宝贵的存储空间。
执行时机——
- 压缩操作通常由后台线程定期执行,也可以在写入操作达到一定条件时触发。
压缩操作也可以在合并操作中一并执行。
压缩策略——
- 压缩操作的策略可以根据实际情况选择,常见的压缩策略包括:
- 移除重复数据:将SSTable中相同键的多个条目合并为一个条目。
- 移除过时数据:将已过期或者不再访问的数据移除。
- 移除墓碑标记:清除已删除的数据和墓碑标记,释放磁盘空间。
合并
目的——
- 合并操作的主要目的是减少SSTable的数量,提高查询性能和节省磁盘空间。
- 随着LSM树中的写入操作的持续进行,会产生大量的SSTable,为了减少查询时需要遍历的SSTable数量,需要定期将多个SSTable 合并为一个新的SSTable。
执行时机——
- 合并操作通常由后台线程定期执行,也可以在写入操作达到一定条件时触发。
- 合并操作的触发条件可以根据SSTable的数量、大小或者其他指标来确定。
合并策略——
LSM树中的合并策略可以根据系统的需求和性能目标进行调整,常见的合并策略包括:
- 层级合并(Levelled Compaction):将相邻层级的SSTable 合并为一个新的SSTable,减少SSTable的数量。
- 大小合并(Size-Tiered Compaction):将大小相似的SSTable 合并为一个新的SSTable,以减少磁盘上的碎片和空间浪费。
- 时间段合并(Time-Windowed Compaction):将一段时间内的SSTable 合并为一个新的SSTable,以保持数据的时效性和减少SSTable的数量。
合并过程——
- 在合并操作中,LSM树会选择一些SSTable进行合并,然后将它们合并为一个新的SSTable。
- 合并过程可能会涉及大量的读取和写入操作,因此需要谨慎地执行以避免影响系统的性能。
- 合并操作还可以执行其他优化,例如压缩数据、优化索引等,以提高性能和节省空间。