【翻译】Log Structured Merge Trees(日志结构化合并树)

自谷歌发布“大桌面”论文以来已近十年。该论文的许多很酷的方面之一是它使用的文件组织。在1996年的论文之后,这种方法通常被称为日志结构化合并树,尽管这里描述的算法与大多数现实世界的实现有很大不同。

LSM现在在许多产品中用作主要文件组织策略。在收购了Wired Tiger之后,HBase,Cassandra,LevelDB,SQLite甚至MongoDB 3.0都配备了可选的LSM引擎。

使LSM树变得有趣的原因是它们背离了二十年来主导空间的二叉树风格文件组织。当您第一次看到LSM时,LSM似乎几乎是反直觉的,只有当您仔细考虑文件在现代内存繁重系统中如何工作时才有意义。

一些背景

简而言之,LSM树旨在提供比传统B +树或ISAM方法更好的写入吞吐量。他们通过消除执行分散的,就地更新操作的需要来实现此目的。

ChartGo那么为什么这是一个好主意呢?从根本上说,这是一个老问题,即磁盘随机操作速度慢,但顺序访问时速度很快。这两种类型的访问之间存在鸿沟,无论磁盘是磁性还是固态,或者甚至在较小程度上是主存储器。
【翻译】Log Structured Merge Trees(日志结构化合并树)_第1张图片
这里 / 这里的 ACM报告中的数字 很好地说明了这一点。他们表明,在某种程度上直觉上,顺序磁盘访问比随机访问主内存更快。更相关的是,它们还显示对磁盘的顺序访问,无论是磁盘还是SSD,至少比随机IO快三个数量级。这意味着要避免随机操作。顺序访问非常值得设计。

因此,考虑到这一点,我们可以考虑一些思想实验:如果我们对写入吞吐量感兴趣,最好的方法是什么?一个很好的起点是简单地将数据附加到文件中。这种方法通常称为日志记录,日记记录或堆文件,是完全顺序的,因此可提供与理论磁盘速度相当的非常快的写入性能(通常为每个驱动器200-300MB / s)。

受益于简单性和性能,基于日志/日志的方法在许多大数据工具中都很受欢迎。然而,他们有一个明显的缺点。从日志中读取任意数据将比写入日志更耗时,包括反向时间顺序扫描,直到找到所需的密钥。

这意味着日志只适用于简单的工作负载,其中数据可以完整访问,如大多数数据库的预写日志,也可以是已知的偏移量,如Kafka等简单消息产品。

因此,我们需要的不仅仅是日志,以便有效地执行更复杂的读取工作负载,如基于密钥的访问或范围搜索。从广义上讲,有四种方法可以帮助我们:二进制搜索,哈希,B +或外部。

  1. 搜索已排序文件:将数据保存到文件,按键排序。如果数据已定义宽度,请使用二进制搜索。如果不使用页面索引+扫描。
  2. 散列:使用散列函数将数据拆分为存储桶,稍后可用于指导读取。
  3. B +:使用可导航的文件组织,如B +树, ISAM 等。
  4. 外部文件:将数据保留为日志/堆,并在其中创建单独的散列或树索引。
    所有这些方法都显着提高了读取性能(大多数情况下n-> O(log(n)))。唉,这些结构增加了顺序,并且该顺序阻碍了写入性能,因此我们的高速日志文件在此过程中丢失了。我猜你不能吃蛋糕。
    【翻译】Log Structured Merge Trees(日志结构化合并树)_第2张图片
    值得一提的是,上述所有四个选项都对数据施加了某种形式的总体结构。

数据是有意且专门地放在文件系统周围,因此索引可以在以后快速找到它。正是这种结构使导航变得快速。当然,在编写数据时,结构必须得到尊重。这是我们开始通过添加随机磁盘访问来降低写入性能的地方。
有几个具体问题。每次写入都需要两个IO,一个用于读取页面,另一个用于写入。我们的日志/日志文件不是这种情况,它可以在一个文件中完成。

更糟糕的是,我们现在需要更新散列或B +索引的结构。这意味着更新文件系统的特定部分。这称为就地更新,需要缓慢的随机IO。 这一点很重要: 像这样的就地方法分散文件系统执行就地更新*。这是限制性的。

一种常见的解决方案是使用方法(4)将索引编入日志 - 但将索引保留在内存中。因此,例如,哈希表可用于将键映射到日志文件(日志)中最新值的位置(偏移量)。这种方法实际上运行得很好,因为它将随机IO划分为相对较小的东西:内存中保存的键到偏移映射。查找值只是一个IO。

另一方面,存在可伸缩性限制,特别是如果您有许多小值。如果您的值只是简单的数字,那么索引将大于数据文件本身。尽管如此,这种模式仍然是一种合理的折衷方案,可用于从Riak到Oracle Coherence的许多产品中。

因此,这将我们带入Log Structured Merge Trees。LSM对上述四种方法采取不同的方法。它们可以完全以磁盘为中心,几乎不需要内存存储来提高效率,但是我们还要将其与大部分写入性能挂钩,这些性能与简单的日志文件有关。与B + Tree相比,一个缺点是读取性能稍差。

从本质上讲,他们尽其所能使磁盘访问顺序。这里没有分散枪!

*存在许多不需要就地更新的树结构。最受欢迎的是 仅附加的Btree,也称为写时复制树。这些工作通过在每次写入发生时按顺序覆盖文件末尾的树结构来工作。旧树结构的相关部分(包括顶级节点)是孤立的。通过这种方法避免了就地更新,因为树随着时间顺序重新定义自身。然而,这种方法需要付出代价:在每次写入时重写结构都很冗长。它会产生大量的写入放大,这本身就是一个缺点。
【翻译】Log Structured Merge Trees(日志结构化合并树)_第3张图片

基本LSM算法

从概念上讲,基本LSM树非常简单。而不是拥有一个大的索引结构(将分散文件系统或添加显着的写入放大),批量写入将按顺序保存到一组较小的索引文件中。因此,每个文件都包含一小段短时间内的更改。每个文件在写入之前都会进行排序,因此稍后进行搜索会很快。文件是不可变的; 它们永远不会更新。新更新将进入新文件。读取检查所有文件。定期将文件合并在一起以保持文件数量下降。

让我们更详细地看一下这个。当更新到达时,它们被添加到内存缓冲区中,该缓冲区通常保存为树(红黑等)以保留密钥排序。在大多数实现中,这种“memtable”在磁盘上作为预写日志进行复制,仅用于恢复目的。当memtable填充时,已排序的数据将刷新到磁盘上的新文件。随着越来越多的写入进入,该过程重复进行。重要的是,系统仅执行顺序IO,因为文件未被编辑。新条目或编辑只是创建连续文件(参见上图)。

因此,当更多数据进入系统时,会创建越来越多这些不可变的有序文件。每个代表一个按时间顺序排列的小的变化子集,按照排序。

由于旧文件未更新,因此会创建重复条目以取代先前的记录(或删除标记)。这最初会产生一些冗余。

系统定期执行压缩。 压缩选择多个文件并将它们合并在一起,删除任何重复的更新或删除(更多关于以后如何工作)。这对于消除上述冗余既重要,但更重要的是,保持对读取性能的处理,随着文件数量的增加而降低。值得庆幸的是,因为文件是排序的,所以合并文件的过程非常有效。

当请求读取操作时,系统首先检查内存缓冲区(memtable)。如果未找到密钥,将按相反的时间顺序逐个检查各种文件,直到找到密钥。每个文件都保持排序,以便可以导航。但是,随着文件数量的增加,读取将变得越来越慢,因为每个文件都需要进行检查。这是个问题。

因此,LSM树中的读取速度比其原地兄弟慢。幸运的是,有一些技巧可以使模式具有高效性。最常见的方法是在内存中保存页面索引。这提供了一个查找,让您“接近”目标键。您在数据排序时从那里进行扫描。LevelDB,RocksDB 和BigTable通过在每个文件末尾保存的块索引来完成此操作。这通常比直接二进制搜索更好,因为它允许使用可变长度字段,更适合压缩数据。

即使使用每个文件索引,读取操作仍会随着文件数量的增加而减慢。通过定期将文件合并在一起来控制这一点。这样的压缩使文件数量和读取性能保持在可接受的范围内。

即使使用压缩,读取仍然需要访问许多文件。大多数实现通过使用Bloom过滤器使其无效 。Bloom过滤器是一种记忆文件是否包含密钥的内存有效方法。

所以从“写作”的角度来看; 所有的写操作是成批并书面只有在连续的块。压缩回合还会产生额外的定期IO惩罚。但是,在查找单行时(即读取时分散枪),读取可能会触及大量文件。这只是算法的工作方式。我们在写入时随机IO交换随机IO。如果我们可以使用诸如布隆过滤器之类的软件技巧或像大文件缓存这样的硬件技巧来优化读取性能,那么这种权衡是明智的。
【翻译】Log Structured Merge Trees(日志结构化合并树)_第4张图片

基本压缩

为了使LSM读取速度相对较快,管理文件数量非常重要,因此让我们更深入地了解压缩。这个过程有点像分代垃圾收集:

当创建了一定数量的文件时,比如五个文件,每个文件有10行,它们被合并为一个文件,有50​​行(或者可能略少)。

此过程继续创建更多10个行文件。每次第五个文件填满时,它们会合并为50行文件。

最终有五个50行文件。此时,五个50行文件合并为一个250行文件。该过程继续创建越来越大的文件。见图。

这种一般方法的上述问题是创建了大量文件:必须单独搜索所有文件以读取结果(至少在最坏的情况下)。
【翻译】Log Structured Merge Trees(日志结构化合并树)_第5张图片

水平压实

较新的实现(例如LevelDB,RocksDB和Cassandra中的实现)通过实现基于级别而非基于大小的压缩方法来解决此问题。这减少了最坏情况读取时必须查阅的文件数量,以及减少单个压缩的相对影响。

与上述基本方法相比,这种基于级别的方法有两个主要差异:

1.每个级别可以包含许多文件,并且整体上保证其中没有重叠的密钥。也就是说,密钥在可用文件之间进行分区。因此,要在某个级别找到密钥,只需要查阅一个文件。

第一级是上述财产不成立的特殊情况。密钥可以跨多个文件。

2.文件一次合并到一个文件的上层。作为级别填充,从中提取单个文件并合并到上面的级别,创建空间以添加更多数据。这与基本方法略有不同,其中几个类似大小的文件合并为单个较大的文件。

这些变化意味着基于水平的方法会随着时间的推移传播压实的影响,并且需要更少的总空间。它还具有更好的读取性能。但是,对于大多数工作负载而言,总IO更高,这意味着一些更简单的面向写入的工作负载将无法获益。

总结

因此,LSM树位于日志/日志文件和传统的单一固定索引(例如B +树或哈希索引)之间的中间位置。它们提供了一种管理一组较小的单个索引文件的机制。

通过管理一组索引而不是单个索引,LSM方法交换与B +或Hash索引中的就地更新相关联的昂贵的随机IO,以实现快速的顺序IO。

支付的价格是读取必须处理大量索引文件而不仅仅是一个。压缩还有额外的IO成本。

如果那仍然有点模糊,那么这里和这里还有其他一些好的描述 。

关于LSM方法的思考

那么LSM方法真的比传统的基于单树的方法更好吗?

我们已经看到LSM具有更好的写入性能,尽管成本。LSM还有其他一些好处。LSM树创建的SSTable(排序文件) 是不可变的。这使得对它们的锁定语义更加简单。通常,争用的唯一资源是memtable。这与需要精心设计的锁定机制来管理不同级别的变化的奇异树形成对比。

所以最终问题可能是关于面向编写的预期工作负载是如何进行的。如果你关心写性能,LSM给出的节省可能是一个大问题。大型互联网公司似乎对此问题非常满意。例如,雅虎报告称,从读取重量级到读写式工作负载的稳步发展,主要是由于事件日志和移动数据的消耗增加。许多传统的数据库产品似乎仍然倾向于更多的读取优化文件结构。

与日志结构化文件系统[见脚注]一样,关键参数源于内存可用性的增加。通过更多可用内存,可以通过操作系统提供的大型文件缓存自然地优化读取。因此,写性能(哪种存储器不会随着更多而改善)成为主要关注点。换句话说,硬件进步对于读取性能的影响要大于写入。因此,选择写优化文件结构是有意义的。

当然,像LevelDB和Cassandra这样的LSM实现定期提供比基于单树的方法(此处和此处分别)更好的写入性能。

超越Level LSM

在LSM方法上已经有了相当多的进一步工作。雅虎开发了一个名为Pnuts的系统 ,它将LSM与B树结合起来,表现出 更好的性能。我没有看到这种算法的公开可用实现。IBM和谷歌已经以类似的方式开展了近期的工作,尽管是通过不同的途径。还有相关的方法具有相似的属性但保留了总体结构。这些包括分形树 和分层树。

这当然只是一种选择。数据库利用了大量微妙的不同选项。越来越多的数据库为不同的工作负载提供可插拔引擎。 Parquet是HDFS的流行替代品,并且推动了相反的方向(通过柱状格式的聚合性能)。MySQL有一个存储抽象,可以插入许多不同的引擎,如 Toku的基于分形树的索引。这也适用于MongoDB。Mongo 3.0包括有线老虎引擎,它提供B +和LSM方法以及传统引擎。许多关系数据库具有可配置的索引结构,它们使用不同的文件组织。

它也值得考虑使用的硬件。昂贵的固态硬盘(如FusionIO)具有更好的随机写入性能。这适合就地更新方法。更便宜的SSD和机械驱动器更适合LSM。LSM避免了小型随机访问模式,这些模式会使SSD陷入遗忘**。

LSM并非没有它的批评。与GC一样,最大的问题是收集阶段及其对宝贵IO的影响。在这个黑客新闻主题上有一些有趣的讨论。

因此,如果您正在查看数据产品,无论是BDB还是LevelDb,Cassandra与MongoDb,您可能会将其相对性能的某些部分与其使用的文件结构联系起来。测量似乎支持这一理念。当然值得了解您使用的系统选择的性能权衡。


**在SSD中,每个写入都会对整个512K块产生一个明确的重写周期。因此,小写可能会在驱动器上引起不成比例的流失。对块重写具有固定限制,这可能会显着影响其生命。

进一步阅读

  • 有一个很好的介绍后这里。
  • 在此的LSM描述 文件 是伟大的,它也讨论了有趣的扩展。
  • 这三个帖子提供了算法的整体覆盖: 这里, 这里 和这里。
  • 原来的日志结构合并树纸这里。在我看来,这有点难以理解。
  • 大表纸 在这里 是极好的。
  • LSM与分形树的高可扩展性。
  • 最近关于Diff-Index的工作,它基于LSM概念。
  • 关于SSD的Jay和LSM的好处
  • 关于指数结构的黑客新闻的有趣讨论。

日志结构化文件系统的脚注

除了名称和关注写入吞吐量之外,据我所知,LSM和日志结构化文件系统之间没有那么多关系。

今天使用的常规文件系统往往是“日记”,例如ext3,ext4,HFS等是基于树的方法。固定高度的inode树表示目录结构,日志用于防止故障情况。在这些实现中,日志是合乎逻辑的,这意味着它只会记录内部元数据。这是出于性能原因。

日志结构化文件系统广泛用于闪存介质,因为它们具有较少的写入放大。由于文件缓存在更一般的情况下开始主导读取工作负载,因此写入性能变得越来越重要。

在日志结构化文件系统中,数据只被写入一次,直接写入日志,该日志 表示 为按时间顺序排列的缓冲区。定期对缓冲区进行垃圾收集以删除冗余写入。与LSM一样,日志结构化文件系统写入速度更快,但读取速度比双写,基于树的对应文件慢。同样,这是可以接受的,因为有大量的RAM可用于提供文件缓存,或者媒体不能很好地处理更新,就像闪存一样。

你可能感兴趣的:(LSM,结构化合并树)