青训营 LSMT 存储引擎浅析
概述
2022の夏天,半壶水响叮当的我决定充实一下自我
一、内容介绍
青训营
总述
本节课程主要分为 4 个方面:
- 介绍 LSMT 与存储引擎
- 分析 LSMT 存储引擎的优势与实现
- LSMT 模型理论分析
- LSMT 存储引擎调优以及案例介绍
最早提出 Log-Structured Merge-Tree(LSMT) 的论文
www.cs.umb.edu/~poneil/lsm… in 1996
相较而言 B-Tree 就早得多了,1970 提出
这在一定程度上可以解释,较早的数据库产品,如 MySQL,PostgresQL 默认均采用 B+Tree(B-Tree 变种)索引。较新的数据库产品,如 TiDB,CockroachDB,默认均采用 LSMT 存储引擎(RocksDB / Pebble)。LSMT 和 B-Tree 看起来是一个时代的产物,其实二者差了 26 年。对技术历史想「考古」的同学可以自行搜索相关资料。
从趋势来看,LSMT 模型变得越来越流行。LSMT 模型广泛应用于目前的数据库系统,例如 Google BigTable,HBase,Canssandra,RocksDB 等,可以说是数据库存储子系统的基石之一。
LSMT 是如何工作的?
一言以蔽之,通过 Append-only Write + 择机 Compact 来维护索引树的结构。
数据先写入 MemTable,MemTable 是内存中的索引可以用 SkipList / B+Tree 等数据结构实现。当 MemTable 写到一定阈值后,冻结,成为 ImmemTable,任何修改只会作用于 MemTable,所以 ImmemTable 可以被转交给 Flush 线程进行写盘操作而不用担心并发问题。Flush 线程收到 ImmemTable ,在真正执行写盘前,会进一步从 ImmemTable 生成 SST(Sorted String Table),其实也就是存储在硬盘上的索引,逻辑上和 ImmemTable 无异。
新生成的 SST 会存放于 L0(Layer 0),除了 L0 以外根据配置可以一直有 Ln。SST 每 Compact 一次,就会将 Compact 产物放入下一层。Compact 可以大致理解为 Merge Sort,就是将多个 SST 去掉无效和重复的条目并合并生成新的 SST 的过程。Compact 策略主要分为 Level 和 Tier 两种,会在课中进行更详细的描述。
为什么要采用 LSMT 模型?
All problems in computer science can be solved by another level of indirection
From Butler Lampson
在计算机存储乃至整个工程界都在利用 Indirection 处理资源的不对称性,比方说内存价格上升了,就要想办法 offload 数据到别的廉价介质上。存储引擎面对的资源不对称性在不同时期是不同的。
HDD 时代:
顺序操作和随机操作的不对称性
机械硬盘的读写依赖于磁盘的旋转和机械臂移动。工程上一般估计机械硬盘的点查(主要开销是 Seek 寻道)延迟是 1ms。即使每次点查都读 4KB(对于点查来说相当大了),也就只能输出约 4MB/s。
反观顺序写,由于不需要寻道,磁头始终能处在工作状态,基本都能做到至少 100MB/s 写吞吐,是点查的 25 倍!
SSD 时代:
顺序写和随机写的不对称性
SSD 是基于 NAND Flash 颗粒的构建的,称之为 DIE,DIE 上有多个 Plane,每个 Plane 能单独提供读写能力,Plane 包含多个 Block,Block 包含多个 Page。擦除的电路实现比较复杂,出于成本的考量,写入的最小单位是 Page,而擦除的最小单位是 Block。
随着用户不断写入和删除,有可能出现有很多 Page 已经被删除了,逻辑上有可用空间,但是物理上 Block 还有别的有效 Page,无效 Page 无法回收。这样用户就写不进数据了。因此,SSD 主控必须执行 GC(Garbage Collection),将有效的 Page 从要回收的 Block 中挑出来,写到另一个 Block 上,再整体回收旧 Block。因此如果用户长期都是随机写,大量 Block 都会处于一部分 Page 是有效,一部分 Page 是无效的状态,SSD 主控不得不频繁 GC。
以经典服务器 SSD,Intel P4510 2TB 为例,根据官方 spec,随机写吞吐是 318MB/s,顺序写则高达 2000MB/s 是随机写的 6 倍多!
简单总结一下,无论对于 HDD 还是 SSD,顺序写都是一个很好的特质,LSMT 符合这一点,B+Tree 则依赖原地更新,会导致随机写。
存储引擎如何与数据库结合?
上文大致介绍了存储引擎,逻辑上大致可以看作一个持久化的索引,那么这是怎么和数据库结合起来的呢?
以单机数据库 MySQL 为例,
传统数据库大致可以分为
- 计算层
- 存储层(存储引擎层)
介于二者之间还有一些界限比较模糊的组件,比如 Replication,MySQL 是用 bin log 独立于存储引擎,而对于一些 NoSQL 数据库(字节 Abase 1.0)来说,Replication 直接基于存储引擎的 WAL。
计算层主要负责 SQL 解析/ 查询优化 / 计划执行。我们重点关注存储层提供了什么能力。数据库著名的 ACID 特性,在 MySQL 中全部强依赖于存储引擎。
ACID 定义参见:en.wikipedia.org/wiki/ACID
- Atomicity
原子性依赖于存储引擎 WAL(Redo Log)
- Consistency (Correctness)
一致性需要数据库整体来保证
- Isolation
隔离性依赖于存储引擎提供 Snapshot(有时候会直接说 MVCC)能力。如果上层没有单独的事务引擎的话,也会由存储引擎提供事务能力。一般的是实现是 2PL(2 Phase Lock) + MVCC。2PL 可以简单理解为对所有需要修改的资源上锁。
- Durability
持久性依赖于存储引擎确保在 Transaction Commit 后通过操作系统 fsync 之类的接口确保落盘了
回顾
在上节课《Parquet 与ORC:高性能列式存储》中,我们学习了√行存与列存的差别
- Parquet 格式的原理
- ORC格式的原理
- 列存的演进
二、LSMT与存储引擎介绍
2.1 LSMT的历史
- LSMT是 Log-Structured Merge-Tree 的缩写,由Patrick O 'Neil etc.在1996 年的论文,The Log-Structured Merge-Tree (LSM-Tree),提出。
- 相较而言,B-Tree出现就早得多了,在1970年由Bayer, R.; McCreight, E.提出。
- 早期的数据库系统一般都采用B-Tree家族作为索引,例如MySQL。2000年后诞生的数据库大多采用LSMT索引,例如Google BigTable,HBase,Canssandra等。
2.2 LSMT是什么?
- 一言以蔽之,通过Append-only Write +择机 Compact来维护结构的索引树。
2.3 存储引擎是什么?
以单机数据库MySQL为例,大致可以分为:
- 计算层
- 存储层(存储引擎层)
- 计算层主要负责SQL解析/查询优化/计划执行
- 数据库著名的ACID特性,在MySQL中全部强依赖于存储引擎
2.3.1 ACID是什么/存储引擎哪些组件保障了这些特性?
- Atomicity/原子性 Write-Ahead Log(WAL)/ Redo Log
预写日志(WAL)/重做日志
-
Consistency(Correctness)/一致性(正确性) 依赖于数据库整体
-
solation/快照 Snapshot / 2PL(Phase Lock)
快照/2PL(锁相环)
- Durability/耐久性 Flusher遵循Sync语意
2.3.2 此外
除了保障ACID以外,存储引擎还要负责:
- 屏蔽IO 细节提供更好的抽象
- 提供统计信息与 Predicate Push Down/谓词下推 能力
存储引擎不掌控IO细节,让操作系统接管,例如使用mmap,会有如下问题:
- 落盘时机不确定造成的事务不安全
- IO Stall / 失速
- 错误处理繁琐
- 无法完全发挥硬件性能
三、LSMT 存储引擎的优势与实现
3.1 LSMT 与 B+Tree 的异同
先简单回顾下经典 B+Tree 写入流程
有一 Order 为 5 的 B+Tree,目前存有 (10, 20, 30, 40),继续插入 15,节点大小到达分裂阈值 5,提取中位数 20 放入新的内部节点,比 20 大的 (30, 40) 移入新的叶节点。这个例子虽然简单,但是涉及了 B+Tree 最核心的两个变化,插入与分裂。
在 B+Tree 中,数据插入是原地更新的,装有 (10, 20, 30, 40) 的节点在插入和分裂后,原节点覆写成 (10, 15)。此外,B+Tree 在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡。
反观 LSMT,数据的插入是追加的(Append-only),当树不平衡或者垃圾过多时,有专门 Compact 线程进行 Compact,可以称之为延迟(Lazy)的。
思考一个问题,B+Tree 能不能把部分数据采用追加写,然后让后台线程去 Compact 维护树结构呢?或者 LSMT 能不能只有一层 L0,ImmemTable 给 Flush 线程之后,立马 Compact 呢?
答案是都可以。前者的做法叫做 Fractal tree(分型树)应用在了 TokuDB 中。后者的做法在 OceanBase 或者类似对延迟有严格要求的在线数据库中得到了应用,因为 LSMT 层数越少,读取越快。
所以从高层次的数据结构角度来看,B+Tree 和 LSMT 并没有本质的不同,可以统一到一个模型里,根据 Workload 的不同互相转换。这是 CIDR19 论文「Design Continuums and the Path Toward Self-Designing Key-Value Stores that Know and Learn」,www.cidrdb.org/cidr2019/pa…
B+Tree 中内部节点指向其它节点的指针,被称之为 Fence Pointers。在 LSMT 也有,只不过是隐式表达的。B+Tree 直接通过 Fence Pointer 一层一层往下找,而 LSMT 是有一个中心的 Meta 信息记录所有 SST 文件的 Key 区间,通过区间大小关系,一层一层向下找。
再看 LSMT 的 SST,其实和 B+Tree 的 Node 也没有本质差别,逻辑上就是一个可查询的有序块,统一模型中称之为 Run。B+Tree 为了支持随机修改,结构会比较松散和简单,LSMT 则因为不需要支持随机修改,利用压缩技术,结构可以更紧凑。
更详细的统一模型描述,请同学们参见论文。尽管 LSMT 和 B+Tree 可以用一个模型描述,工程实践上我们还是用 LSMT 来表示一个 Append-only 和 Lazy Compact 的索引树,B+Tree 来表示一个 Inplace-Update 和 Instant Compact 的索引树。Append-only 和 Lazy Compact 这两个特性更符合现代计算机设备的特性。
3.2 为什么要采用LSMT模型?
All problems in computer science can be solved by another level of indirection
计算机科学的所有问题都可以通过另一个层次的间接解决
From Butler Lampson巴特勒·兰普森
-
在计算机存储乃至整个工程界都在利用 Indirection/间接 处理资源的不对称性
-
存储引擎面对的资源不对称性在不同时期是不同的
3.2.1 HDD时代/硬盘时代
- 顺序与随机操作性能不对称
- 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25倍。 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25倍.
3.2.2 SSD时代
- 顺序写与随机写性能不对称
- 由于SSD随机写会给主控带来GC压力,顺序写吞吐是随机写的6 倍
3.2.3 小结
- HDD时代,顺序操作远快于随机操作
- SSD时代,顺序写操作远快于随机写操作
- 这二者的共性是顺序写是一一个对设备很友好的操作,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写
3.2.4 存储引擎在数据库中做了什么?
在课前部分,我们以 ACID 特性为切入点,大概了解存储引擎在数据库系统中的定位,现在让我们来学习下除了保障 ACID 以外,存储引擎究竟还在数据库中做了什么。
- 屏蔽 IO 细节提供更好的抽象
IO 是一种具体实现很复杂,但是逻辑边界很清晰的任务,存储引擎需要屏蔽不同 IO 硬件设备(HDD,SSD,PMem etc),不同系统 API(pread / libaio / iouring)的差别,给出统一的抽象。
对于不同硬件设备,存储引擎要能选择最合适的数据分块尺寸,例如 SSD 中一般 Page 大小为 4KB,但在 PMem 中,读写单位就是 256Bytes 了。
对于不同的系统,最佳的读写 API 也不同。例如在 Linux 系统中提供了 libaio 和 iouring 这样的异步 IO 接口,可以避免多线程 + 同步接口带来的频繁线程 context swtich 开销,读写引擎要能在上层 API 不变或者很小的情况下充分利用这些特性。
存储引擎更好的利用这些环境的差异就能更好地服务上层。还有一种相反的做法是这些细节都由操作系统来屏蔽,比如使用 mmap 接口。由于操作系统并不完全感知数据库任务的特性,会造成以下问题:
- 事务不安全,因为操作系统并不知道具体的事务 commit 时机,有可能事务还没 commit,但数据已经落盘了
- IO Stall,mmap 在发生 page fault 的时候,用户态程序是没办法插手的,也没办法预期什么时候完成,不可控
- 错误处理,mmap 在发生硬件错误的时候,并没有办法通知用户态程序,每次读取都要进行校验
- 性能,mmap 触发 page fault 的成本很高,无法完全发挥硬件性能
具体可以参见论文,www.cidrdb.org/cidr2022/pa… zhuanlan.zhihu.com/p/470109297
- 提供统计信息与 Predicate Push Down 能力
数据库绝大部分持久化状态和数据都存放在存储引擎里,因此存储引擎相比于上层有着对数据更准确的信息。例如 RocksDB 提供了 ApproxSize 接口,可以让优化器在估算代价的时候,得到区间内大概有多少元素,生成更优的执行计划。
存储引擎是数据读取的源头,因此还可以将一些过滤条件下推引擎,避免无意义的 IO,例如在 Parquet 数据格式中,数据是分 column 存储的。如果只需要部分 column,就可以把这个信息传给存储引擎,不用去读不需要的 column。
3.2.5 LSMT 存储引擎的优势
- 相对于 B+Tree 的优势
我们在前文已经阐述了 LSMT 与 B+Tree 的异同,在这里总结下 LSMT 的优势。
- 顺序写模型对于 SSD 设备更友好
- SST 不可修改的特性使得其能使用更加紧凑的数据排列和加上压缩
- 后台延迟 Compact 能更好利用 CPU 多核处理能力,降低前台请求延迟
- 相对于 HashTable 的优势
LSMT 存储引擎是有序索引抽象,HashTable 是无序索引抽象。无序索引是有序索引的真子集。LSMT 相比于 HashTable 更加通用。HashTable 能处理点查请求,LSMT 也能,但 LSMT 能处理 TopK 请求,但 HashTable 就不行了。为了避免维护多套存储引擎,绝大多数数据库都直接采用一套有序的存储引擎而非针对点查和顺序读取分别维护两个引擎。
3.3 LSMT存储引擎的实现,以RocksDB为例
- RocksDB是一款十分流行的开源LSMT存储引擎,最早来自Facebook (Meta) ,应用于MyRocks, TiDB 等数据库。
- 在字节内部也有Abase, ByteKV, ByteNDB, Bytable 等用户。
- 因此接下来将会以RocksDB为例子介绍LSMT存储引擎的经典实现。
3.3.1 LSMT存储引擎的实现- Write
- RocksDB写入流程主要有两个优化,批量WAL写入(继承自LevelDB)与并发MemTable更新
- RocksDB在真正执行修改之前会先将变更写入WAL, WAL写成功则写入成功。
- 多个写入者会选出一个Leader, 由这个Leader来一次性写入WAL,避免小10
- 不要求WAL强制落盘(Sync/同步) 时,批量提交亦有好处,Leader可以同时唤醒其余Writer,降低了系统线程调度开销
- 没有批量提交的话,只能链式唤醒
- 链式唤醒加大前台延迟
- 写完WAL还要写MemTable。
- RocksDB在继承LevelDB的基础.上又添加了并发MemTable写入的优化
- WAL一次性写入完成后,唤醒所有Writer 并行写入MemTable
- 由最后一个完成MemTable写入的Writer执行收尾工作
3.3.2 LSMT存储引擎的实现- Snapshot & SuperVision/快照和监督
- RocksDB的数据由3部分组成,MemTable / ImmemTable / SST。持有这三部分数据并且提供快照功能的组件叫做 SuperVersion/超级版本
- MemTable和SST的释放依赖于引用计数。对于读取来说,只要拿着SuperVersion,从MemTable - -级一级向下,就能查到记录。拿着SuperVersion不释放,等于是拿到了快照
- 如果所有读者都给SuperVersion 的计数加1,读完后再减1,那么这个原子引用计数器就会成为热点。CPU在多核之间同步缓存是有开销的,核越多开销越大。
- 为了让读操作更好的scale, RocksDB做了一个优化是 Thread Local SuperVersion Cache/线程局部超级版本高速缓存
- 没有 Thread Local/线程本地 缓存时,读取操作要频繁Acquire和Release(频繁获得和发布)SuperVersion/超级验证
- CPU缓存不友好
- 有Thread Local缓存,读取只需要检查一下SuperVersion并标记Thread Local缓存正在使用即可
- cpu缓存友好
3.3.3 LSMT存储引擎的实现- Get & BloomFilter
- RocksDB的读取在大框架上和B+ Tree 类似,就是层层向下。
- 相对于B+ Tree, LSMT点查需要访问的数据块更多。为了加速点查,一般LSMT引擎都会在SST中嵌入BloomFilter。
- [1,10]表示这个索引块存储数据的区间在1-10之间。查询2,就是顺着标绿色的块往下
3.2.3.1 除了 BloomFilter 外,BlockBasedTable 还有额外两个值得提的实现。一个是两层索引:
浅黄部分是 DataBlock,绿色部分是 IndexBlock。DataBlock 记载实际数据,IndexBlock 索引 DataBlock。假如要查询 3,先从 IndexBlock 中找到 >= 3 的第一条记录是什么,发现是 4,对应的 value 是 data_block_0 的 offset,直接定位到 Data Block 0。然后可以在 Data Block 0 中进行搜索。
另一个是前缀压缩,RocksDB 源代码中的注释已经写得很明白了。
// BlockBuilder generates blocks where keys are prefix-compressed:
//
// When we store a key, we drop the prefix shared with the previous
// string. This helps reduce the space requirement significantly.
// Furthermore, once every K keys, we do not apply the prefix
// compression and store the entire key. We call this a "restart
// point". The tail end of the block stores the offsets of all of the
// restart points, and can be used to do a binary search when looking
// for a particular key. Values are stored as-is (without compression)
// immediately following the corresponding key.
//
// An entry for a particular key-value pair has the form:
// shared_bytes: varint32
// unshared_bytes: varint32
// value_length: varint32
// key_delta: char[unshared_bytes]
// value: char[value_length]
// shared_bytes == 0 for restart points.
//
// The trailer of the block has the form:
// restarts: uint32[num_restarts]
// num_restarts: uint32
// restarts[i] contains the offset within the block of the ith restart point.
复制代码
Ref: RocksDB Source Code
每 k 个元素会共享前缀。每个元素会用 varint32 记录和前一个元素有多少前缀重合。
3.3.4 LSMT存储引擎的实现Compact
Compact 在 LSMT 中是将 Key 区间有重叠或无效数据较多的 SST 进行合并,以此来加速读取或者回收空间。Compact 策略可以分为两大类。
3.3.4.1 LSMT存储引擎的实现Compact - Level
Level 策略直接来自于 LevelDB,也是 RocksDB 的默认策略。每一个层不允许有 SST 的 Key 区间重合。当用户写入的 SST 加入 L0 的时候会和 L0 里区间重叠的 SST 进行合并。当 L0 的总大小到达一定阈值时,又会从 L0 挑出 SST,推到 L1,和 L1 里 Key 区间重叠的 SST 进行合并。Ln 同理。
由于在 LSMT 中,每下一层都会比上一层大 T 倍(可配置),那么假设用户的输入是均匀分布的,每次上下层的合并都一定是一个小 SST 和一个大 SST 进行 Compact。这个从算法的角度来说是低效的,增加了写放大,具体理论分析会在之后阐述,这里可以想象一下 Merge Sort。Merge Sort 要效率最高,就要每次 Merge 的时候,左右两边的数组都是一样大。
实际上,RocksDB 和 LevelDB 都不是纯粹的 Level 策略,它们将 L0 作为例外,允许有 SST Key 区间重叠来降低写放大。
- Compact/紧凑型 在LSMT中是将Key区间有重叠或无效数据较多的SST进行合并,以此来加速读取或者回收空间。Compact策略可以分为两大类,Level 和Tier。下图是Level策略
- Level策略直接来自于LevelDB,也是RocksDB的默认策略。每一个层不允许有SST的Key区间重合
3.3.5.2 LSMT 存储引擎的实现Compact - Tier
Tier 策略允许 LSMT 每层有多个区间重合的 SST,当本层区间重合的 SST 到达上限或者本层大小到达阈值时,一次性选择多个 SST 合并推向下层。Tier 策略理论上 Compact 效率更高,因为参与 Compact 的 SST 大小预期都差不多大,更接近于完美的 Merge Sort。
Tier 策略的问题在于每层的区间内重合的 SST 越多,那么读取的时候需要查询的 SST 就越多。Tier 策略是用读放大的增加换取了写放大的减小。
- Tier策略允许LSMT每层有多个区间重合的SST
四、LSMT模型理论分析
4.1. Cloud-Native LSMT Storage Engine - HBace
- RocksDB 是单机存储引擎,那么现在都说云原生,HBase 比 RocksDB 就更「云」一些,SST 直接存储于 HDFS 上,Meta 信息 RocksDB 自己管理维护于 Manifest 文件,HBase 放置于 ZK。
- 二者在理论存储模型上都是 LSMT。
4.2 LSMT 模型理论分析
4.2.1 LSMT 模型理论分析 - level
- Write:每条记录抵达最底层需要经过 L 次 Compact,每次 Compact Ln 的一个小 SST 和 Ln+1 的一个大 SST。设小 SST 的大小为 1,那么大 SST 的大小则为 T,合并开销是 1+T,换言之将 1 单位的 Ln 的 SST 推到 Ln+1 要耗费 1+T 的 IO,单次 Compact 写放大为 T。每条记录的写入成本为 1/B 次最小单位 IO。三者相乘即得结果。
- Point Lookup:对于每条 Key,最多有 L 个重叠的区间,每个区间都有 BloomFilter,失效率为e−MNe^{- \frac{M}{N} } e−NM,只有当 BloomFilter 失效时才会访问下一层。因此二者相乘可得读取的开销。注意,这里不乘 1/B 的原因是写入可以批量提交,但是读取的时候必须对齐到最小读取单元尺寸。
4.2.2 LSMT 模型理论分析 - Tier
Write:每条记录抵达最底层前同样要经过 L 次 Compact,每次 Compact Ln 中 T 个相同尺寸的 SST 放到 Ln+1。设 SST 大小为 1,那么 T 个 SST Compact 的合并开销是 T,换言之将 T 单位的 Ln 的 SST 推到 Ln+1 要耗费 T 的 IO,单次 Compact 的写放大为 T / T = 1。每条记录的写入成本为 1/B 次最小单位 IO。三者相乘即得结果。
Point Lookup:对于每条 Key,有 L 层,每层最多有 T 个重叠区间的 SST,对于整个 SST 来说有 T *
L 个可能命中的 SST,乘上 BloomFilter 的失效率即可得结果。
总结,Tier 策略降低了写放大,增加了读放大和空间放大,Level 策略增加了写放大,降低了读和空间放大。
4.3 LSMT模型算法复杂度分析
Online Quiz/在线测验
- Short Range Scan/短程扫描 复杂度是如何推导的?
- Space Amplification/空间放大 复杂度是如何推导的?
T: size ratio,每层 LSMT 比上一层大多少,L0 大小为 1,则 L1 大小为 T,L2 为 T^2,以此类推
L: level num,LSMT 层数
B: 每个最小的 IO 单位能装载多少条记录
M: 每个 BloomFilter 有多少 bits
N: 每个 BloomFilter 生成时用了多少条 Key
e−MNe^{- \frac{M}{N} } e−NM是 BloomFilter 的 false positive rate
S:区间查询的记录数量
总结,Tier 策略降低了写放大,增加了读放大和空间放大,Level策略增加了写放大,降低了读和空间放大
五、LSMT存储引擎调优案例与展望
5.1 LSMT 引擎调优案例
TerarkDB aka LavaKV 是字节跳动内部基于 RocksDB 深度定制优化的自研 LSMT 存储引擎,其中完全自研的 KV 分离功能,上线后取得了巨大的收益。
KV 分离受启发于论文 WiscKey: Separating Keys from Values in SSD-conscious Storage,www.usenix.org/system/file… 较长的记录的 Value 单独存储,避免 Compact 过程中频繁挪动这些数据。做法虽然简单,但背后的原理却十分深刻。存储引擎其实存了两类数据,一类是索引,一类是用户输入的数据。对于索引来说,随着记录不断变更,需要维护索引的拓扑结构,因此要不断 Compact,但对于用户存储的数据来说,只要用户没删除,可以一直放着,放哪里不重要,能读就行,不需要经常跟着 Compact。只要 Value 足够长,更少 Compact 的收益就能覆盖 KV 分离后,额外维护映射关系的开销。
这里分享两个字节内部真实案例。
5.1.1 Abase 图存储场景使用 TerarkDB
-
图存储场景描述
- Key size :20B ~ 30B
- Value size:数十 KB 级别
- 写多读少
- 收益结论:
延迟大幅度降低,长尾消失,扛住了比 RocksDB 高 50% 的负载。
RocksDB Max Read Lat
TerarkDB Max Read Lat
5.2 Flink 流计算场景使用 TerarkDB
- 收益结论:
- 平均 CPU 开销在 3个作业上降低了 26%~39%
-
峰值 CPU 开销在广告作业上有明显的收益,降低了 67%
- live_feed_head 作业上峰值 CPU 开销降低 43%
- multi_trigger 受限于分配的CPU 资源,没有观察到峰值 CPU 收益( 平均 CPU 开销降低 39% )
- 平均容量开销在 3 个作业上降低了17%~31.2%
- 直播业务某集群容量不收缩,TerarkDB 的 schedule TTL GC 彻底解决了该问题
- 收益说明:
- 平均 CPU 收益主要来自于,开启 KV 分离,减少写放大
- 容量收益主要来自于 schedule TTL GC,该功能可以根据 SST 的过期时间主动发起Compaction,而不需要被动的跟随 LSM-tree 形态调整回收空间
| multi_trigger 4620 corelive_feed_head (3600core : 3000core)ad_online_joiner (786core ) | RocksDB | TerarkDB | 收益描述 | |||
|---|---|---|---|---|---|---|
| Max | Average | Max | Average | |||
| multi_trigger(4/12 15点~4/13 15点) | Application Used CPU | 4.68K | 2.51K | 4.76K | 1.54K | 平均 CPU 开销降低 39% |
| Application Used Memory | 10.4TB | 10.1TB | 9.97TB | 9.35TB | 内存开销降低 3.1% | |
| Total CheckPoint State Size | 19.3TB | 12.7TB | 16.3TB | 8.74TB | 平均容量降低 31.2% |
5.2.1 Flink 使用 FIFO 和 TerarkDB 对比
线上 live head 作业,FIFO 和 TerarkDB 通过不同的方式减少了 Compaction ,相比于 Leveled Compaction, FIFO CPU 收益 29%, 略低于 TerarkDB 的 34% 。
FIFO 存在较严重的空间放大,live head作业上, FIFO 容量峰值 比 Leveled 大30%,而 TerarkDB 容量峰值比 Leveled 小 15%。
5.3 存储引擎最新发展趋势-新硬件
随着硬件的发展,软件设计也会随着发生改变。近年来,出现了许多新的存储技术,例如SMRHDD, Zoned SSD / OpenChannel SSD, PMem等。如何在这些新硬件上设计/改进存储引|擎是一大研究热点。
e.g. MatrixKV: Reducing Write Stalls and Write Amplification in LSM-tree Based KV Stores with Matrix Container in NVM
例如MatrixKV:在基于LSM树的KV存储中减少写暂存和写放大NVM中的矩阵容器
这篇论文中的设计将LO整个搬进了PMem,降低了写放大。
5.4 存储引擎最新发展趋势新模型
-
经典LSMT模型是比较简单的,有时候不能应对所有工况,可以提出新的模型来解决问题。
-
e.g. WiscKey: Separating Keys from Values in SSD-conscious Storage
- 例如 智慧键:在SSD意识存储中将键与值分离
- 通过额外增加一个 Value Store/值存储 来存储大Value的记录来降低总体写放大
-
e.g. REMIX: Efficient Range Query for LSM-trees
- 例如 混合:高效的LSM-树范围查询
- 通过额外增加一种SST的类型来加速范围查询的速度
5.5 存储引擎最新发展趋势-新参数/新工况
-
已有的模型,在新的或者现有工况下,参数设置的不合理,可以通过更精确的参数设置来提升整体性能
-
e.g. The Log-Structured Merge-Bush & the Wacky Continuum
- 例如:原木结构的合并-布什和古怪的连续体
- 在最后一层使用 Level Compaction/级压缩 ,之.上使用 Tier Compaction/层压缩 ,通过在除了最后一层以外的SST加大BloomFilter的bits数来规避Tier Compaction带来的点查劣化。
总结
晚安玛卡巴卡
快乐暑假