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 压缩的更多细节我们可以阅读官方文档中的CompactionLeveled Compacton 两篇文章。

LSM 树详解的更多相关文章

  1. 数据结构图文解析之:AVL树详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  2. trie字典树详解及应用

    原文链接    http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用   一.知识简介        ...

  3. Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】

    转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...

  4. JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删改查),事件

    JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删 ...

  5. 线段树详解 (原理,实现与应用)(转载自:http://blog.csdn.net/zearot/article/details/48299459)

    原文地址:http://blog.csdn.net/zearot/article/details/48299459(如有侵权,请联系博主,立即删除.) 线段树详解    By 岩之痕 目录: 一:综述 ...

  6. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  7. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  8. AVL树详解

    AVL树 参考了:http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html 修改了其中的错误,代码实现并亲自验证过. 平衡二叉树(B ...

  9. trie树--详解

    文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 转载请注明,谢谢合作.关键词:trie trie树 数据结 ...

随机推荐

  1. python安装scrapy库失败

    解决方法: 首先,下载Twisted.cp后数字为python版本,例如cp36为python3.6:amd则表示系统位数,例如amd64为64位.下载对应版本即可.点击打开链接 找到Twisted, ...

  2. SpringBoot-03-配置之yaml语法学习

    3. SpringBoot配置 3.1 yaml语法学习 配置文件 Springboot使用一个全局的配置文件,配置文件名称固定 spplication.properties 语法结构:key=val ...

  3. IOT(esp8266)

    今日工具: 硬件: esp8266 DHT11温湿度传感器 软件: Arduino ESP8266 是一款由乐鑫 Espressif 公司制作的低成本的 Wi-Fi 芯片,具有完整的 TCP / IP ...

  4. leetcode1558题解【贪心】

    leetcode1558.得到目标数组的最少函数调用次数 题目链接 算法 贪心 时间复杂度O(nlogN),N为数组中最大的那个数. 1.题意就是给定一个函数,该函数有两种功能,一种就是将数组中的所有 ...

  5. 主键生成器效率提升方案|基于雪花算法和Redis控制进程隔离

    背景 主键生成效率用数据库自增效率也是比较高的,为什么要用主键生成器呢?是因为需要insert主表和明细表时,明细表有个字段是主表的主键作为关联.所以就需要先生成主键填好主表明细表的信息后再一次过在一 ...

  6. React学习小记--setState的同步与异步

    react中,state不能直接修改,而是需要使用setState()来对state进行修改,那什么时候是同步而什么时候是异步呢? 基础代码: setCounter = (v) => { thi ...

  7. 【题解】小M的作物

    题目戳我 \(\text{Solution:}\) 这题要求最大收获,可以转化为所有可能的收益减去最小割. 单个点很好连边 \((S\to pos\to T),\) 问题在于如何处理组合的点. 观察到 ...

  8. Tensorflow学习笔记No.4.2

    使用CNN卷积神经网络(2) 使用Tensorflow搭建简单的CNN卷积神经网络对fashion_mnist数据集进行分类 不了解是那么是CNN卷积神经网络的小伙伴可以参考上一篇博客(Tensorf ...

  9. HarmonyOS 润和 HiSpark开发套件 免费领!

    让人期盼已久的HarmonyOS 2.0终于在9月10日正式上线啦! 这是一件让众多开发者关注的大事件! 相信不少开发者都已经迫不及待的想上手实操了, 为了满足大家的好奇心, 也希望能有更多开发者了解 ...

  10. 一文带你定制unittest测试用例的名称

    在之前的文章中,我在之前的文章中提到过,这里呢,考虑后,感觉之前的写法不够优雅,于是乎呢,我自己抽空去研究了下,主要是新写方法,这样呢,以后的要使用的时候,可以直接去使用,而不是每次换个环境就要修改环 ...