SSTable 的分层管理设计

简介: SSTable分层管理通过将文件按层组织,控制每层容量并逐层归并,避免大规模合并带来的高IO开销。Level 0层来自Immutable MemTable,最多4个文件;后续各层容量逐层翻倍,并限制跨层合并的文件数不超过10个,确保查询与Compaction效率。

我们知道,SSTable 文件是由 Immutable MemTable 将数据顺序导入生成的。尽管 SSTable 中的数据是有序的,但是每个 SSTable 覆盖的数据范围都是没有规律的,所以 SSTable 之间的数据很可能有重叠。

比如说,第一个 SSTable 中的数据从 1 到 1000,第二个 SSTable 中的数据从 500 到 1500。那么当我们要查询 600 这个数据时,我们并不清楚应该在第一个 SSTable 中查找,还是在第二个 SSTable 中查找。最差的情况是,我们需要查询每一个 SSTable,这会带来非常巨大的磁盘访问开销。

因此,对于 SSTable 文件,我们需要将它整理一下,将 SSTable 文件中存的数据进行重新划分,让每个 SSTable 的覆盖范围不重叠。这样我们就能将 SSTable 按照覆盖范围来排序了。并且,由于每个 SSTable 覆盖范围不重叠,当我们需要查找数据的时候,我们只需要通过二分查找的方式,找到对应的一个 SSTable 文件,就可以在这个 SSTable 中完成查询了。

但是要让所有 SSTable 文件的覆盖范围不重叠,不是一个很简单的事情。为什么这么说呢?我们看一下这个处理过程。系统在最开始时,只会生成一个 SSTable 文件,这时候我们不需要进行任何处理,当系统生成第二个 SSTable 的时候,为了保证覆盖范围不重合,我们需要将这两个 SSTable 用多路归并的方式处理,生成新的 SSTable 文件。

那为了方便查询,我们要保证每个 SSTable 文件不要太大。因此,LevelDB 还控制了每个 SSTable 文件的容量上限(不超过 2M)。这样一来,两个 SSTable 合并就会生成 1 个到 2 个新的 SSTable。

这时,新的 SSTable 文件之间的覆盖范围就不重合了。当系统再新增一个 SSTable 时,我们还用之前的处理方式,来计算这个新的 SSTable 的覆盖范围,然后和已经排好序的 SSTable 比较,找出覆盖范围有重合的所有 SSTable 进行多路归并。这种多个 SSTable 进行多路归并,生成新的多个 SSTable 的过程,也叫作 Compaction。

随着 SSTable 文件的增多,多路归并的对象也会增多。那么,最差的情况会是什么呢?最差的情况是所有的 SSTable 都要进行多路归并。这几乎是一个不可能被接受的时间消耗,系统的读写性能都会受到很严重的影响。

那我们该怎么降低多路归并涉及的 SSTable 个数呢?在 第 9 讲 中,我们提到过,对于少量索引数据和大规模索引数据的合并,我们可以采用滚动合并法来避免大量数据的无效复制。因此,LevelDB 也采用了这个方法,将 SSTable 进行分层管理,然后逐层滚动合并。这就是 LevelDB 的分层思想,也是 LevelDB 的命名原因。接下来,我们就一起来看看 LevelDB 具体是怎么设计的。

首先,从 Immutable MemTable 转成的 SSTable 会被放在 Level 0 层。Level 0 层最多可以放 4 个 SSTable 文件。当 Level 0 层满了以后,我们就要将它们进行多路归并,生成新的有序的多个 SSTable 文件,这一层有序的 SSTable 文件就是 Level 1 层。

接下来,如果 Level 0 层又存入了新的 4 个 SSTable 文件,那么就需要和 Level 1 层中相关的 SSTable 进行多路归并了。但前面我们也分析过,如果 Level 1 中的 SSTable 数量很多,那么在大规模的文件合并时,磁盘 IO 代价会非常大。因此,LevelDB 的解决方案就是,给 Level 1 中的 SSTable 文件的总容量设定一个上限(默认设置为 10M),这样多路归并时就有了一个代价上限。

当 Level 1 层的 SSTable 文件总容量达到了上限之后,我们就需要选择一个 SSTable 的文件,将它并入下一层(为保证一层中每个 SSTable 文件都有机会并入下一层,我们选择 SSTable 文件的逻辑是轮流选择。也就是说第一次我们选择了文件 A,下一次就选择文件 A 后的一个文件)。下一层会将容量上限翻 10 倍,这样就能容纳更多的 SSTable 了。依此类推,如果下一层也存满了,我们就在该层中选择一个 SSTable,继续并入下一层。这就是 LevelDB 的分层设计了。

尽管 LevelDB 通过限制每层的文件总容量大小,能保证做多路归并时,会有一个开销上限。但是层数越大,容量上限就越大,那发生在下层的多路归并依然会造成大量的磁盘 IO 开销。这该怎么办呢?

对于这个问题,LevelDB 是通过加入一个限制条件解决的。在多路归并生成第 n 层的 SSTable 文件时,LevelDB 会判断生成的 SSTable 和第 n+1 层的重合覆盖度,如果重合覆盖度超过了 10 个文件,就结束这个 SSTable 的生成,继续生成下一个 SSTable 文件。

通过这个限制,LevelDB 就保证了第 n 层的任何一个 SSTable 要和第 n+1 层做多路归并时,最多不会有超过 10 个 SSTable 参与,从而保证了归并性能。

相关文章
|
2月前
|
编解码 算法 前端开发
java后端开发学习路线+避坑指南
java后端开发学习路线+避坑指南
|
2月前
|
存储
链表在检索和动态调整上的优缺点
链表因无法随机访问,检索效率低,尤其在无序或有序情况下均难以实现快速查找。但其优势在于动态调整:插入和删除节点仅需O(1)时间,远优于数组的O(n)移动开销,适合频繁修改的场景。
|
10月前
|
存储 分布式计算 大数据
数据湖——大数据存储的新思维,如何打破传统束缚?
数据湖——大数据存储的新思维,如何打破传统束缚?
397 16
|
2月前
|
数据采集 存储 机器学习/深度学习
搜索引擎的整体架构和工作过程
搜索引擎由爬虫、索引和检索三大系统构成:爬虫负责抓取网页并存储;索引系统对网页去重、分析并构建倒排索引;检索系统通过查询分析、相关性排序等技术,返回精准结果。全过程融合文本分析、机器学习与大规模计算,确保高效准确搜索。
|
2月前
|
存储 负载均衡 索引
简单的分布式结构是什么样的?
简单分布式结构通过分发服务器将请求分配给多台具备完整索引的索引服务器,实现负载均衡与高吞吐。虽不减少单次查询时间,但可通过拆分索引、分散内存加载来降低检索规模,提升单次效率,是分布式检索优化的关键思路。(238字)
|
2月前
|
自然语言处理 搜索推荐 大数据
增量索引空间的持续增长如何处理?
为应对增量索引持续增长导致的内存压力,常用全量与增量索引结合策略。通过完全重建、再合并或滚动合并法,定期将增量数据融入全量索引并释放内存。其中滚动合并法通过多级索引逐层合并,显著降低大规模系统中的冗余读写开销,是工业界高效处理索引更新的核心方案。(238字)
|
2月前
|
存储 索引
如何利用四叉树动态调整查询范围?
四叉树通过层次化划分空间,根节点代表全区域,子节点编码组合形成区域码。检索时沿路径查找,不足K个结果则回溯父节点扩大范围,实现动态调整查询范围,提升效率。
|
2月前
|
存储 自然语言处理 运维
如何基于关键词进行拆分?
基于关键词拆分可减少搜索请求复制,提升效率。将词典分片存储于不同服务器,查询时按关键词定位分片,避免全量请求。但存在管理复杂、高频词性能差、负载不均等问题,多用于高性能场景,通用系统仍倾向文档级拆分以保障可维护性与扩展性。
|
2月前
|
索引
使用非精准检索的思路实现「查找附近的人」
通过非精准Top K检索思路实现“查找附近的人”,借鉴网页搜索的近似匹配逻辑,优先筛选同城或同区域用户以缩小检索范围。将地理空间划分为带编号区域并建立索引,先定位用户所在区域,再计算范围内用户距离,提升查询效率。
|
2月前
|
存储 NoSQL 关系型数据库
什么是 Geohash 编码?
Geohash编码将经纬度转换为字符串,通过不断二分地球坐标区间,交叉合并经纬编码,再转为Base32简化表示。它用短字符串标识位置,支持高效空间索引与查询,广泛应用于Redis、MySQL等系统。