用于数据库索引的排序字符串表 (SSTable) 或 B+ 树?

Posted

技术标签:

【中文标题】用于数据库索引的排序字符串表 (SSTable) 或 B+ 树?【英文标题】:Sorted String Table (SSTable) or B+ Tree for a Database Index? 【发布时间】:2012-01-28 21:31:12 【问题描述】:

使用两个数据库来说明这个例子:CouchDB 和Cassandra。

沙发数据库

CouchDB 使用 B+ 树作为文档索引(使用 a clever modification 在其仅附加环境中工作) - 更具体地说,当文档被修改(插入/更新/删除)时,它们被附加到正在运行的数据库文件以及一个完整的叶子 -> 来自 B+ 树的所有节点的节点路径,该路径在文档之后立即受到更新修订的影响。

这些零散的索引修订与修改一起内联,以便完整索引是附加在文件末尾的最新索引修改以及数据文件中仍然相关的其他部分的联合并且尚未修改。

搜索B+ tree 是 O(logn)。

卡桑德拉

Cassandra 将记录键排序在内存中的表中(让我们将它们视为此问题的数组),并不时将它们作为单独的(排序的)sorted-string tables 写出。

我们可以将所有这些表的集合视为“索引”(据我了解)。

Cassandra 需要不时地compact/combine these sorted-string tables,创建一个更完整的索引文件表示。

搜索a sorted array 是 O(logn)。

问题

假设在 CouchDB 中维护部分 B+ 树块与在 Cassandra 中维护部分排序字符串索引之间的复杂程度相似,并假设两者都提供 O(logn) 搜索时间,您认为哪个可以更好地表示数据库索引以及为什么?

我特别好奇是否有一个关于另一个的实现细节使其特别具有吸引力,或者它们是否都是洗牌,而您只需选择您喜欢使用的任何数据结构/对开发者来说更​​有意义。

感谢您的想法。

【问题讨论】:

对于任何感兴趣的人,这里有更多关于 B+ 树、LSM 和分形树性能的信息:nosql.mypopescu.com/post/3063887666/… 【参考方案1】:

在比较 BTree 索引和 SSTable 索引时,应该考虑写入复杂度:

当随机写入写时复制 BTree 时,您将产生随机读取(复制叶节点和路径)。因此,虽然写入在磁盘上是顺序的,但对于大于 RAM 的数据集,这些随机读取将很快成为瓶颈。对于类似 SSTable 的索引,写入时不会发生此类读取 - 只会有顺序写入。

1234563如果密钥大小远小于块大小,这是非常昂贵的。对于类似 SSTable 的索引,每个写入 IO 都会包含尽可能多的新键,因此每个键的 IO 成本更像是 1/B。

在实践中,这使得类似 SSTable 的速度比 BTree 快数千倍(对于随机写入)。

在考虑实现细节时,我们发现实现类似 SSTable 的索引(几乎)无锁要容易得多,而 BTree 的锁定策略变得相当复杂。

您还应该重新考虑阅读成本。对于随机点读取,您比 BTree 是 O(log_b N) 随机 IO 是正确的,但类似 ​​SSTable 的索引实际上是 O(#sstables . log_b N)。如果没有合适的合并方案,#sstables 与 N 成正比。有各种技巧可以解决这个问题(例如,使用 Bloom Filters),但这些对小的随机范围查询没有帮助。这就是我们在 Cassandra 中发现的:

Cassandra under heavy write load

这就是为什么我们的 (GPL) 存储引擎 Castle 的合并略有不同,并且可以在写入性能(O(log ^2 N / B))。在实践中,我们发现它也比 Cassandra 的 SSTable 索引更快。

如果你想了解更多,我已经讨论了它是如何工作的:

podcast slides

【讨论】:

汤姆,非常详细的回复。谢谢你。我想从你那里得到一个 B+ 树的想法,即仅在拆分时以仅附加格式编写的 B+ 树,否则 B+ 树索引会就地更新。所以你会预先分配节点,然后就地填充它们。在拆分时,您可以像 CouchDB 一样重写树,方法是将其附加到文件中并使旧的未拆分节点过期。这避免了 SSTable 可能需要依赖的复杂压缩的需要,并避免了 CouchDB 现在对节点进行的常量重写......想法? FWIW,Cassandra 在最新版本中似乎也改变了合并策略datastax.com/dev/blog/leveled-compaction-in-apache-cassandra @tom.wilkie 锁定 B 树有多复杂?我不知道sstable,但我最近实现了一个并发保存B+-Tree。在 B-Link-Tree 中,您最多只需要锁定三个节点(如果您的 B-Tree 足够大,这几乎是无锁的)。 我知道这是一篇很老的帖子,只是有类似的问题,并在谷歌上搜索得到这个答案。我的问题是,由于现在越来越多的NOSQL数据库添加到本地二级索引中,那么二级索引不是也在BTree中实现吗?这是否会损害 sstable 的原始设计决策?【参考方案2】:

我认为Tokutek 使用的分形树是数据库的更好索引。与 b 树相比,它们提供了 20 到 80 倍的实际改进。

对于分形树索引的工作原理有很好的解释here。

【讨论】:

我认为他们应该将它们称为 B++ 树而不是分形树。感谢您的链接。【参考方案3】:

LSM-Trees 在存储引擎结构上优于 B-Trees。 它以某种方式将随机写入转换为 aof。 这是一个 LSM-Tree src: https://github.com/shuttler/lsmtree

【讨论】:

【参考方案4】:

关于每种方法还应提及的一些事项:

B 树

读/写操作应该是对数O(logn)。但是,一次数据库写入可能会导致存储系统中的多次写入。例如,当一个节点已满时,它必须被拆分,这意味着将有 2 个新节点的 2 次写入和 1 次额外的更新父节点的写入。如果父节点也已满,您可以看到它会如何增加。 通常,B 树的存储方式是每个节点都有一个页面的大小。这会产生一种称为写入放大的现象,即使需要更新单个字节,也会写入整个页面。 写入通常是随机的(不是顺序的),因此速度较慢,尤其是对于磁盘。

SSTables

SSTables 通常用于以下方法。正如您所描述的,有一个内存结构,称为 memtable。每隔一段时间,这个结构就会被刷新到磁盘到一个 SSTable。因此,所有写入都进入内存表,但读取可能不在当前内存表中,在这种情况下,它们会在持久化的 SSTable 中进行搜索。 因此,写入为O(logn)。但是,请始终牢记它们是在内存中完成的,因此它们应该比 B 树磁盘中的对数运算快几个数量级。为了完整起见,我们应该提到写入也写入预写日志以进行崩溃恢复。但是,鉴于这些都是顺序写入,预计它们将比 B 树的随机写入更有效。 当从内存(从 memtable)提供服务时,读取速度预计也会快得多。但是,当需要查看旧的、基于磁盘的 SSTables 时,读取可能会变得比 B-tree 慢得多。对此有一些优化,例如使用布隆过滤器,在不执行磁盘读取的情况下检查 SSTable 是否包含值。 正如您所提到的,还有一个称为 compaction 的后台进程,用于合并 SSTable。这有助于删除已删除的值并防止碎片化,但可能会导致大量写入负载,从而影响传入操作的写入吞吐量。

很明显,这两种方法之间的比较要复杂得多。在提供具体比较的极其简化的尝试中,我认为我们可以这样说:

SSTables 提供比 B 树更好的写入吞吐量。然而,由于持续的压实,预计它们的行为不太稳定。在this benchmark comparison 中可以看到这方面的一个示例。 对于需要事务语义的用例,通常首选 B 树。这是因为,每个键只能在一个地方找到(与 SSTable 相比,它可能存在于多个 SSTable 中,其中一些值已过时),还因为一个键可以表示一系列值作为树。这意味着更容易执行键级和范围级锁定机制。

参考文献

[1]A Performance Comparison of LevelDB and mysql

[2]Designing Data-intensive Applications

【讨论】:

以上是关于用于数据库索引的排序字符串表 (SSTable) 或 B+ 树?的主要内容,如果未能解决你的问题,请参考以下文章

什么是 SSTable?

MySQL 索引

MySQL索引

索引为何不可用

LevelDB 源码剖析SSTable模块:SSTableBlock布隆过滤器LRU Cache

LevelDB 源码剖析SSTable模块:SSTableBlock布隆过滤器LRU Cache