[转帖]从SSTable到LSM-Tree之二
https://zhuanlan.zhihu.com/p/103968892
背景
LSM-Tree (Log Structured Merge Tree),日志结构合并树。它在 1996 年由论文《The Log-Structured Merge-Tree (LSM-Tree) 》[1]首次提出,但真正得到广泛应用是在 2006 年Google Bigtable 论文之后,论文中提到 Bigtable 使用的数据结构就是 LSM-Tree。
目前,LSM-Tree 已经被广泛应用于一些流行的的 Key-Value 型存储引擎中,如 Bigtable、Cassandra、HBase、RocksDB[2]、LevelDB、ScyllaDB[3]等。LSM 已经成为基于磁盘的 Key-Value 型存储引擎的标配。
为什么需要 LSM-Tree ?
我们知道,磁盘的顺序读写性能要比随机读写性能高2个数量级以上。日志结构指的就是日志形式的追加写(Append Only),它是顺序写。
LSM-Tree 相比于传统的 B+ 树存储引擎,具有更高的写入性能。通过把磁盘随机写转换为顺序写,来大幅提升写入性能。代价是,牺牲了部分读性能及写放大问题。这么说,并不是说 B+ 树不存在写放大,它实际也存在写放大问题。我们稍后再研究这个问题。
LSM-Tree 结构
我们先看看原始论文中描述的 LSM-Tree 结构,再看看实际存储引擎中的实现。
LSM-Tree 由两个或甚至更多的分层数据结构组成[1]。一种比较简单的两层 LSM-Tree 结构如下[1]:

Two Level LSM-Tree
图中,C0 层常驻内存。 C1 层在磁盘上。C0 层保存最近更新的索引项,通常可以使用红黑树、跳表、B 树等实现。当插入操作导致 C0 的大小达到一定的阈值之后,它会与 C1 合并到磁盘上。LSM树的性能源于这样一个事实,即每个组件都根据其底层存储介质的特性进行了调优,并且使用一种类似于归并排序的算法,以滚动批次的方式有效地跨介质合并数据[1]。
多层 LSM-Tree 结构如下:

C0 层常驻内存,保存了所有最近写入的 Key-Value,这个内存结构是有序的,并且可以随时原地更新,同时支持随时查询。C1 ~ Ck 层都在磁盘上,每一层都是一个在 key 上有序的结构。
- 写流程
写请求首先被写入 WAL(Write Ahead Log)日志(通常用于故障恢复),再写入 C0,如果 C0 大小达到阈值,就会把 C0 层与 C1 层合并写入新的 new-C1,类似于归并算法,这个过程就是合并(Compaction)。合并的 new-C1 顺序写入磁盘。同理,C1 达到一定阈值之后,会与下层 C2 合并,依次逐层合并。合并过程可以异步进行,这样不用阻塞写操作。
- 读流程
由写入可以知道,最新的数据在 C0 层,最旧的数据在 Ck 层。查询指定的 key 时,会先查 C0 再查 C1,最后查 Ck。所以,LSM-Tree 对读不太友好,也就是存在读放大。
原始论文中,在磁盘上使用类 B 树来组织文件。但是实际系统,大多采用
来组织文件。
下面以经典的 LevelDB[4]为例,看一下 LSM-Tree 的实际实现。
LevelDB LSM 结构
LevelDB LSM-Tree 结构如下图所示。LSM-Tree 由三个数据结构组成:内存表分为 Memtable 和 ImmutableTable,磁盘上的 SSTable 文件。

LevelDB-LSM
LevelDB LSM-Tree 性质[4]
内存表:
- 默认 4MB 左右内存块(可调,后面会说到)
- 存放有序 Key-Value
- 内存结构为跳跃表
- 数据先写入 Memtable,达到指定大小后,把它变成 ImmuableTable,之后异步 Compaction 落盘到 Level-0
SSTable 文件:
- SSTable 文件是层次结构,每层按 key range 分区存放在多个 SSTable 中
- Level-0 层 SSTable 的 key range 会存在重叠,其它层 key range 不重叠
- Level-i(i > 0)的大小呈指数增长,Level-i 层 SSTable 文件数量量级 10�
- Level-i 层中的每个 SSTable 最多与 Level-(i+1) 的 10 个 SSTable 存在交集
- 数据新鲜度:Memtable > ImmutableTable > Level-0 > Level-1 > Level-2 > ...
实际上,LevelDB 在写 Memtable 之前,会先写 log 文件(大小也为 4MB,它并不是无限增长),log 文件以日志追加(Append Only)的方式保存最近的更新,Memtable 相当于 log 的副本,log 文件可用于故障恢复。LevelDB 官方文档有几处混淆不清,比如 Level-0 层的 SSTable 文件并不是由 log 文件直接转换而来,而是由 Memtable 写入,不然如何保证 SSTable 文件中键的顺序。
SSTable 文件存储按 Key 排序的条目(Entry),每一个 Entry 是 Key-Value 对或指定 Key 的删除标记(Deletion Marker)。
特殊的 Level-0 层[4]
当 Level-0 文件个数超出指定阈值(默认 4 个,每个 1MB 大小)时,将所有 Level-0 的 SSTable 文件 与 Level-1 有交集的 SSTable 文件进行合并,然后生成新的 Level-1 SSTable 文件(每 2MB 生成一个新 SSTable)。
Level-0 层 SSTable 的 key range 会存在重叠。原因是,Level-0 的 SSTable 文件由 ImmutableTable 直接生成,但是不同的 ImmutableTable 之间没办法保证 key 不重叠。
合并(Compaction)
对于 Level-i (i > 0) 层,每一层的大小超过 10� MB(Level-1 层 10MB,Level-2层 100MB...)时,选择 Level-i 层中的一个 SSTable 文件与 Level-(i+1) 层所有存在交集的 SSTable 文件进行合并,生成新的 Level-(i+1) 层 SSTable 文件(有交集的文件不超过 10 个),合并完成之后,丢弃掉旧的 SSTable 文件(包括 Level-i 层和 Level-(i+1) 层的)。合并过程中,会处理删除和覆盖旧值的情况。
在合并过程中,有两种情况会触发生成一个新的 SSTable 文件:
- 第一种情况是新生成 SSTable 文件大小达到 2MB;
- 第二种情况就是新生成的 SSTable 与下一层存在重叠的 key range 的 SSTabe 个数超过了 10 个,这个约束是为了保证在合并过程中不至于读取太多的SSTable文件,以致于影响合并性能;
Compaction的时间性能[4]
对于合并性能,我们看下官方文档中对性能的描述。
对于进行 Level-0 合并时,最多从 Level-0 读取 4 个 1MB 大小的文件,以及最坏情况下从 Level-1 层读取 10MB 大小。那么读是 14MB,写也是 14MB。
对于 Level-i (i > 0) 合并时,会从 Level-i 层选择一个 2MB 大小的文件,以及最坏情况下从Level-(i+1) 层选择 12 个 2MB大小的文件,其中 10 个是因为上下层最多重叠10个文件,额外2个文件是由于上下两层文件边界往往不对齐导致的。那么读是 26MB,写也是26MB。
假设磁盘 IO 速率为 100MB/s (现代驱动器的大致范围),最坏的情况下合并成本大约为 0.5s。如果限制一下写入磁盘速率到 10%,那么合并可能需要 5s。又或者用户以 10MB/s 的速率写入 Memtable,那么 Level-0 每秒需要大约 50 个 1MB的文件,Level-1 则需要生成 5 个(5 x 10MB)文件,这势必会大大增加 Compaction 的代价。
如果我们将后台写入限制为很小的值,比如100MB/s速度的10%,那么压缩可能需要5秒。如果用户以10MB/s的速度写入,我们可能会构建许多0级文件(~50来容纳5*10MB)。由于每次读取时合并更多文件的开销,这可能会显著增加读取的成本。
LSM-Tree 虽然写入性能很好,但也存在一些问题。
写放大
Write Amplification (WA),写放大是指实际写的数据量大于用户需要的数据量。写放大是由于合并引起的。
写放大是 LevelDB 的硬伤,RocksDB 通过把参数 10 变成动态可调来缓解写放大问题。
写放大会降低 Flash 磁盘(SSD)的寿命。
读放大
Read Amplification (RA),读放是指实际读的数据量大于用户需要的数据量。LevelDB 查询数据时,会按照数据新鲜读排序,依次从 Memtable > ImmutableTable > L0 > L1 > ...中查询,尤其是查询的 key 不存在时,会导致每次都会查询到最后一层。一般结合布隆过滤器 (bool filter) 来降低读放大问题。
空间放大(Space Amplification)
所有的写入都是顺序写 (Append-Only) 的,不是原地更新 (in-place update) ,所以过期数据不会马上被清理掉。LSM-Tree 是通过合并来减少空间放大,但是引入了写放大问题。
由于本人的理解能力有限,如理解有误,请大家多多指正。
参考
- ^abcdThe Log-Structured Merge-Tree (LSM-Tree)
- ^A Tutorial of RocksDB SST formats https://github.com/facebook/rocksdb/wiki/A-Tutorial-of-RocksDB-SST-formats
- ^Scylla SSTable Format https://docs.scylladb.com/architecture/sstable/
- ^abcdLevelDB https://github.com/google/leveldb
[转帖]从SSTable到LSM-Tree之二的更多相关文章
- 数据映射-LSM Tree和SSTable
Coming from http://blog.sina.com.cn/s/blog_693f08470101njc7.html 今天来聊聊lsm tree,它的全称是log structured m ...
- LSM Tree 学习笔记——本质是将随机的写放在内存里形成有序的小memtable,然后定期合并成大的table flush到磁盘
The Sorted String Table (SSTable) is one of the most popular outputs for storing, processing, and ex ...
- LSM Tree 学习笔记——MemTable通常用 SkipList 来实现
最近发现很多数据库都使用了 LSM Tree 的存储模型,包括 LevelDB,HBase,Google BigTable,Cassandra,InfluxDB 等.之前还没有留意这么设计的原因,最近 ...
- Log-Structured Merge Tree (LSM Tree)
一种树,适合于写多读少的场景.主要是利用了延迟更新.批量写.顺序写磁盘(磁盘sequence access比random access快). 背景 回顾数据存储的两个“极端”发展方向 加快读:加索引( ...
- 【万字长文】使用 LSM Tree 思想实现一个 KV 数据库
目录 设计思路 何为 LSM-Treee 参考资料 整体结构 内存表 WAL SSTable 的结构 SSTable 元素和索引的结构 SSTable Tree 内存中的 SSTable 数据查找过程 ...
- LSM Tree存储组织结构介绍
LSM Tree(Log Structured Merge Trees)数据组织方式被应用于多种数据库,如LevelDB.HBase.Cassandra等,下面我们从为什么使用LSM tree.LSM ...
- LSM Tree解析
引言 众所周知传统磁盘I/O是比较耗性能的,优化系统性能往往需要和磁盘I/O打交道,而磁盘I/O产生的时延主要由下面3个因素决定: 寻道时间(将磁盘臂移动到适当的柱面上所需要的时间,寻道时移动到相邻柱 ...
- [CareerCup] 4.5 Validate Binary Search Tree 验证二叉搜索树
4.5 Implement a function to check if a binary tree is a binary search tree. LeetCode上的原题,请参见我之前的博客Va ...
- [LeetCode] Verify Preorder Sequence in Binary Search Tree 验证二叉搜索树的先序序列
Given an array of numbers, verify whether it is the correct preorder traversal sequence of a binary ...
- [LeetCode] Binary Search Tree Iterator 二叉搜索树迭代器
Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the ro ...
随机推荐
- root无法下载
在 Linux 中,root 用户已经拥有系统中的最高权限.然而,如果你需要确保 root 用户具有所有权限,可以通过以下步骤来进行:(这是不对的) sudo chown -R root:root / ...
- 知道Python中的字符串是什么吗?
摘要:本文将告诉您Python中的字符串是什么,并向您简要介绍有关该概念的所有知识. 本文将介绍以下内容: 如何创建一个字符串? 如何从字符串访问字符? 格式化字符串 因此,让我们开始吧. 什么是Py ...
- makefile:带你了解一种常用于GNU gcc编译的工具语言
摘要:该文章主要介绍makefile,一种常用语GNU gcc编译的工具语言,同时LiteOS也是利用该文件对工程项目进行make构建生成执行文件的. LiteOS源码中使用makefile进行文件的 ...
- Karmada v1.2发布:开启全文本搜索新纪元
摘要:Karmada v1.2 版本对调度器能力做了较大增强,初步提供了分布式搜索引擎支持,此外还借助聚合API提供了诸如 logs, watch等实用的命令行工具,资源解释器(Resource In ...
- “互联网+”大赛之智慧校园 赛题攻略:你的智慧校园,WeLink帮你来建
摘要:本赛题的核心就是借助华为云WeLink的中台服务能力/开发工具等,结合学校的具体的高价值场景,开发出WeLink小程序,方便师生的学习与生活. 本文分享自华为云社区<"互联网+& ...
- 41. 干货系列从零用Rust编写负载均衡及代理,websocket与tcp的映射,WS与TCP互转
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...
- WebRTC 的现状和未来:专访 W3C WebRTC Chair Bernard Aboba
WebRTC 无疑推动和改变了互联网视频,而这仅仅是刚刚开始,除了大家熟悉的 WebRTC-PC.Simulcast 和 SVC,有太多的新技术和新架构出现在 WebRTC 新的标准中,比如 WebT ...
- 工程开发 | CMake工程目录结构和多线程
CMake工程目录结构 lib: 生成的库文件 src: 源文件(.cpp .cc) include: 头文件(.h .hpp) build: 一般在这个文件夹下执行cmake ..(..之前有一个空 ...
- MAVEN 配置和基本使用
Maven 安装配置 解压 apache-maven-3.6.1.rar 既安装完成 配置环境变量 MAVEN_HOME 为安装路径的 bin 目录 在 Path 下添加 %MAVEN_HOME%\b ...
- UVA - 1594 :Ducci Sequence (set应用)
给定n元组(a1,a2,...,an),ai均为整数,得到下一个序列为(|a1-a2|,|a2-a3|,...,|an-a1|),如此循环下去,必定会出现全零序列或重复序列. 现要求判断给定序列是全零 ...