LSM 树详解
LSM树(Log Structured Merged Tree)的名字往往给人一个错误的印象, 实际上LSM树并没有严格的树状结构。
LSM 树的思想是使用顺序写代替随机写来提高写性能,与此同时会略微降低读性能。
LSM 的高速写入能力与读缓存技术带来的高速读能力结合受到了需要处理大规模数据的开发者的青睐,成为了非常流行的存储结构。
HBase、 Cassandra、 LevelDB、 RocksDB 以及 ClickHouse MergeTree 等流行的 NoSQL 数据库均采用 LSM 存储结构。
读写流程
具体来说 LSM 的数据更新是日志式的,修改数据时直接追加一条新记录(为被修改数据创建一个新版本),而使用 B/B+ 树的数据库则需要找到数据在磁盘上的位置并在原地进行修改。
这张经典图片来自 Flink PMC 的 Stefan Richter 在Flink Forward 2018演讲的PPT
写入
在执行写操作时,首先写入 active memtable 和预写日志(Write Ahead Logging, WAL)。因为内存中 memtable 会断电丢失数据,因此需要将记录写入磁盘中的 WAL 保证数据不会丢失。
顾名思义 MemTable是一个内存中的数据结构,它保存了落盘之前的数据。SkipList 是最流行的 Memtable 实现方式,Hbase 和 RocksDB 均默认使用 SkipList 作为 MemTable。
当 Active MemTable 写满后会被转换为不可修改的 Immutable MemTable,并创建一个新的空 Active MemTable。后台线程会将 Immutable MemTable 写入到磁盘中形成一个新的 SSTable 文件,并随后销毁 Immutable MemTable。
SSTable (Sorted String Table) 是 LSM 树在磁盘中持久化存储的数据结构,它是一个有序的键值对文件。
LSM 不会修改已存在的 SSTable, LSM 在修改数据时会直接在 MemTable 中写入新版本的数据,并等待 MemTable 落盘形成新的 SSTable。因此,虽然在同一个 SSTable 中 key 不会重复,但是不同的 SSTable 中仍会存在相同的 Key。
读取
因为最新的数据总是先写入 MemTable,所以在读取数据时首先要读取 MemTable 然后从新到旧搜索 SSTable,找到的第一个版本就是该 Key 的最新版本。
根据局部性原理,刚写入的数据很有可能被马上读取。因此, MemTable 在充当写缓存之外也是一个有效的读缓存。
为了提高读取效率 SSTable 通常配有 BloomFilter 和索引来快速判断其中是否包含某个 Key 以及快速定位它的位置。
因为读取过程中需要查询多个 SSTable 文件,因此理论上 LSM 树的读取效率低于使用 B 树的数据库。为了提高读取效率,RocksDB 中内置了块缓存(Block Cache)将频繁访问磁盘块缓存在内存中。而 LevelDB 内置了 Block Cache 和 Table Cache 来缓存热点 Block 和 SSTable。
Compact
随着不断的写入 SSTable 数量会越来越多,数据库持有的文件句柄(FD)会越来越多,读取数据时需要搜索的 SSTable 也会越来越多。另一方面对于某个 Key 而言只有最新版本的数据是有效的,其它记录都是在白白浪费磁盘空间。因此对 SSTable 进行合并和压缩(Compact)就十分重要。
在介绍 Compact 之前, 我们先来了解 3 个重要的概念:
- 读放大:读取数据时实际读取的数据量大于真正的数据量。例如 LSM 读取数据时需要扫描多个 SSTable.
- 写放大:写入数据时实际写入的数据量大于真正的数据量。例如在 LSM 树中写入时可能触发Compact操作,导致实际写入的数据量远大于该key的数据量。
- 空间放大:数据实际占用的磁盘空间比数据的真正大小更多。例如上文提到的 SSTable 中存储的旧版数据都是无效的。
Compact 策略需要在三种负面效应之间进行权衡以适应使用场景。
Size Tiered Compaction Strategy
Size Tiered Compaction Strategy (STCS) 策略保证每层 SSTable 的大小相近,同时限制每一层 SSTable 的数量。当某一层 SSTable 数量达到阈值后则将它们合并为一个大的 SSTable 放入下一层。
STCS 实现简单且 SSTable 数量较少,缺点是当层数较深时容易出现巨大的 SSTable。此外,即使在压缩后同一层的 SSTable 中仍然可能存在重复的 key,一方面存在较多无效数据即空间放大较严重,另一方面读取时需要从旧到新扫描每一个 SSTable 读放大严重。通常认为与下文介绍的 Leveled Compaction Strategy 相比, STCS 的写放大较轻一些[1][2]。
STCS 是 Cassandra 的默认压缩策略[3]。Cassandra 认为在插入较多的情况下 STCS 有更好的表现。
Tiered压缩算法在RocksDB的代码里被命名为 Universal Compaction。
Leveled Compaction Strategy
Leveled Compaction Strategy (LCS)策略也是采用分层的思想,每一层限制总文件的大小。
LCS 会将每一层切分成多个大小相近的SSTable, 且 SSTable 是在层内是有序的,一个key在每一层至多只有1条记录,不存在冗余记录。
LCS 层内不存在冗余所以空间放大比较小。因为层内有序, 所以在读取时每一层最多读取一个 SSTable 所以读放大较小。在读取和更改较多的场景下 LCS 压缩策略有着显著优势。
当某一层的总大小超过阈值之后,LCS 会从中选择一个 SSTable 与下一层中所有和它有交集的 SSTable合并,并将合并后的 SSTable 放在下一层。请注意与所有有交集的 SSTable 合并保证了 compact 之后层内仍然是有序且无冗余的。
LCS 下多个不相关的合并操作是可以并发执行的。
LCS 有一个变体称为 Leveled-N 策略,它将每一层分为 N 个区块,层内不再全局有序只在区块内保证有序。它是 LCS 与 STCS 的中间状态,与 LCS 相比拥有更小的写放大,与 STCS 相比拥有更小的读放大与空间放大。
RocksDB 的压缩策略
RocksDB 默认采用的是 Size Tiered 与 Leveled 混合的压缩策略。在 RocksDB 中存在一个 Active MemTable 和多个 Immutable MemTable。
MemTable 被写入磁盘后被称为 Level-0 (L0)。L0 是无序的,也就是说 L0 的 SSTable 允许存在重叠。除 L0 外的层都是全局有序的,且采用 Leveled 策略进行压缩。
当 L0 中文件个数超过阈值(level0_file_num_compaction_trigger)后会触发压缩操作,所有的 L0 文件都将被合并进 L1。
因为 L0 是 MemTable 直接落盘后的结果,而热点 key 的更新可能存在于多个 MemTable 中,所以 L0 的 SSTable 中极易因为热点 key 而出现交集。
关于 RocksDB 压缩的更多细节我们可以阅读官方文档中的Compaction和 Leveled Compacton 两篇文章。
LSM 树详解的更多相关文章
- 数据结构图文解析之:AVL树详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- trie字典树详解及应用
原文链接 http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用 一.知识简介 ...
- Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】
转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...
- JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删改查),事件
JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删 ...
- 线段树详解 (原理,实现与应用)(转载自:http://blog.csdn.net/zearot/article/details/48299459)
原文地址:http://blog.csdn.net/zearot/article/details/48299459(如有侵权,请联系博主,立即删除.) 线段树详解 By 岩之痕 目录: 一:综述 ...
- Linux dts 设备树详解(二) 动手编写设备树dts
Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...
- Linux dts 设备树详解(一) 基础知识
Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...
- AVL树详解
AVL树 参考了:http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html 修改了其中的错误,代码实现并亲自验证过. 平衡二叉树(B ...
- trie树--详解
文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 转载请注明,谢谢合作.关键词:trie trie树 数据结 ...
随机推荐
- Windows上使用jekyll+github搭建免费博客
jekyll+github搭建个人博客 (一)下载Ruby (二)安装jekyll (三)开启jekyll服务器 (四)使用github展示博客 一.下载Ruby Ruby,一种简单快捷的面向对象(面 ...
- KONGA下的HAMC插件功能 --JAVA代码实现
设置HAMC插件 postman模拟请发发送: Java代码: HMAC-SHA-256工具类 1 import java.security.InvalidKeyException; 2 impor ...
- (最新 9000 字 )Spring Boot 配置特性解析
爱生活,爱编码,微信搜一搜[架构技术专栏]关注这个喜欢分享的地方.本文 架构技术专栏 已收录,有各种JVM.多线程.源码视频.资料以及技术文章等你来拿 一.概述 目前Spring Boot版本: 2. ...
- TP 3.2 图片处理类
TP 3.2 图片处理类 <?php namespace Common\Common; class ImageEdit{ /** * [上传图片并生成缩略图] * @param [type] $ ...
- 海量数据分库分表方案(二)技术选型与sharding-jdbc实现
上一章已经讲述分库分表算法选型,本章主要讲述分库分表技术选型 文中关联上一章,若下文出现提及其时,可以点击 分库分表算法方案与技术选型(一) 主要讲述 框架比较 sharding-jdbc.zdal ...
- spring+springmvc+mybatis+shiro
创建maven框架https://blog.csdn.net/Ajax_mt/article/details/78549119 具体下边 https://blog.csdn.net/w2222288/ ...
- 使用AOP和Validator技术对项目接口中的参数进行非空等校验
javax.validation.Validator基础知识补充: validator用来校验注解的生效,如: @NotBlank(message = "地址名不能为空") pri ...
- 一篇文章搞定 Nginx 反向代理与负载均衡
代理 要想弄明白反向代理,首先要知道啥是正向代理,要搞懂正向代理只需要知道啥是代理即可.代理其实就是一个中介,在不同事物或同一事物内部起到居间联系作用的环节.比如买票黄牛,房屋中介等等. 在互联网中代 ...
- mysql-4-functions
#进阶4:常见函数(单行函数) /* 将一组逻辑语句封装在方法体中,对外暴露方法名 语法: SELECT 函数名() [FROM 表名] 分类: 1.单行函数:concat,length,ifnull ...
- matlab中fopen 打开文件或获得有关打开文件的信息
参考:https://ww2.mathworks.cn/help/matlab/ref/fopen.html?searchHighlight=fopen&s_tid=doc_srchtitle ...