LSM-Tree 笔记

随着存储变得更便宜,现代应用程序需要高效的查找和更新,LSM-Tree 通过避免昂贵的就地更新在两者之间提供了良好的平衡。它们通过在主内存中缓存写入(插入、删除和更新)并将它们的持久化推迟到以后来工作。虽然这样的设计会稍微对读取不利,然后需要搜索各种不同 level 的数据结构,但它们允许高效地执行写入,并显着提高数据摄取率。

今天,LSM-trees 被用于各种系统,通常是键值存储,作为快速索引大量更新数据的一种方法。 Google 的 LevelDB 和 BigTable、Facebook 的 RocksDB、Apache 的 Cassandra 以及众多研究原型都使用 LSM-trees 或类似的变体作为存储的核心数据结构。
LSM-Tree 笔记_第1张图片
数据布局。 LSM-tree 由大小递增的存储级别层次结构组成。第一级称为缓冲区(在某些实现中称为 L0),通常是最小的并存储在主内存中,其目的是缓存更新以提高效率。通常,设计会为缓冲区分配一个单独的数据结构,仅用于内存中性能(例如跳表、堆等)。其余 level 存储在磁盘上。随着新写入的积累和缓冲区的填充,缓冲区内容必须作为文件保存到磁盘,内容按键顺序排序。Level 的概念是合乎逻辑的。单个 level 有两个基本属性,它们指导 LSM Tree 如何增长大小。第一个是容量或阈值大小(可以以字节或缓冲区大小的倍数表示),第二个是该 level 允许的最大 Run 数量的合并阈值;合并阈值定义 level 的合并策略 - leveling(允许一个 Run)或 tiering(允许多个 Run)。Run 是一个或多个具有非重叠键范围的已排序文件的集合。文件驻留在文件系统上,每个文件内部都包含按键顺序排序的条目。文件是不可变的,遵守特定于实现的格式(例如 RocksDB 的 SSTable);除了保存数据条目外,它们还可以保留较小的索引(例如栅栏)或过滤器(例如 Bloom)以帮助指导文件内的搜索(这在查找部分中有更多解释)。上图给出了 LSM 树的高级视觉效果。

写入。 LSM-trees 中的写入速度很快,因为它们总是首先在主内存(缓冲区)中执行,然后在稍后阶段批量移动到磁盘。更新和插入的处理方式相同,因为它们都将新的指定值绑定到键值存储中的特定唯一键。删除也以与更新相同的方式执行(首先添加到缓冲区),但使用特殊标记,表示此记录为“已删除”。由于 LSM-tree 的每个级别都使用阈值限制大小,因此这些操作中的每一个都以延迟和批处理的方法合并到 LSM-tree 的最大级别。此过程称为合并(或压缩),并在 level 达到其阈值大小时(容量)时触发。例如,缓冲区填满时需要进行合并。

合并。 合并过程将一组已排序文件作为输入,并创建一组新的在键范围内不重叠的已排序文件作为输出,称为 Run。在两种情况下可能需要合并。在第一种情况下,当前 level 已达到其阈值大小,因此需要将其内容作为合并过程的一部分推送到更大的 level,以清理当前 level 的更多空间。在第二种情况下,一个新的 run 被移动到已经达到其允许的 run 阈值的当前 level,因此任何合并到当前 level 的 run 都不能简单地添加,而是必须与一个当前 level的 run 合并来遵守阈值。以这种方式,合并有可能通过随后的更大级别引起进一步合并的级联。合并的不同实现可能使用不同的策略来选择内容(什么和多少)推入下一个级别。在这个基本描述中,合并在 level 之间一次推送一个 run(选择当前级别的全部内容作为 run 推送到下一个 level)。例如,最初在 L0 的数据上构建 run 并刷出到下一个 level。如果刷出的 run 现在也导致 L1 达到其容量阈值,则此 run 与 L1 的内容排序合并,形成一个更大的运行并以级联方式推大一级。当数据被排序合并时,执行删除和更新,当有条目修改相同的键时只保留最新的值。这个过程可以在右手边的上图中看到。现有文献包含合并的几种实现,例如优化 RocksDB 中的空间放大和外部排序。

查找。 首先在 L0 中执行单个键查找,如果未找到匹配项,则此查找将传播到树的更大 level,从最近(最小)到最旧(最大)。还针对树的所有 level 评估范围查询。为了避免在查询回答期间对每个 run 都进行二分搜索,可以通过维护辅助结构来执行各种优化。常见的有:栅栏指针和布隆过滤器。栅栏指针是由每页(或每 X 页)的最小和/或最大键形成的范围,允许查找仅访问具有与目标键相关的范围的运行部分。严格来说,当我们使用 leveling 合并策略时,对于栅栏指针,我们只需要最小值或最大值,因为所有键范围都已排序并且在一个 level 内不重叠。另一方面,Bloom 过滤器用于加速单键搜索:如果运行时的 Bloom 过滤器对给定的目标键返回 false,我们不需要搜索该运行以寻找目标键。一些实现选择在每次数据库启动时在内存中重建这些优化,而其他实现将这些辅助结构持久保存到磁盘并根据需要将它们加载到内存中。

一致性和 Level 管理。 虽然文件与底层文件系统有对应关系,但 level 是概念性的,必须由 LSM-tree 实现管理。为了保持不可变文件的一致视图,通常会在全局范围内维护一个目录和清单结构(内存中和持久化),它描述文件到 level 的关系并指示哪一组文件构成当前快照。这样,创建新文件的后台合并可以继续进行,同时保证正在进行的读者看到与 LSM-tree 的单个快照对应的文件集的一致视图,而不会中断查询的正确性。

你可能感兴趣的:(存储,lsm-tree,数据结构,数据库)