Rocksdb LSM Tree Compaction策略

news2024/11/16 7:40:00

RocksDB读写简介

直接画图说明。这张图取自Flink PMC大佬Stefan Richter在Flink Forward 2018演讲的PPT,笔者重画了一下。

RocksDB的写缓存(即LSM树的最低一级)名为memtable,对应HBase的MemStore;读缓存名为block cache,对应HBase的同名组件。

执行写操作时,先同时写memtable与预写日志WAL。memtable写满后会自动转换成不可变的(immutable)memtable,并flush到磁盘,形成L0级sstable文件。sstable即有序字符串表(sorted string table),其内部存储的数据是按key来排序的,后文将其简称为SST。

执行读操作时,会首先读取内存中的数据(根据局部性原理,刚写入的数据很有可能被马上读取),即active memtable→immutable memtable→block cache。如果内存无法命中,就会遍历L0层sstable来查找。如果仍未命中,就通过二分查找法在L1层及以上的sstable来定位对应的key。

随着sstable的不断写入,系统打开的文件就会越来越多,并且对于同一个key积累的数据改变(更新、删除)操作也就越多。由于sstable是不可变的,为了减少文件数并及时清理无效数据,就要进行compaction操作,将多个key区间有重合的sstable进行合并。本文暂无法给出"compaction"这个词的翻译,个人认为把它翻译成“压缩”(compression?)或者“合并”(merge?)都是片面的。

通过上面的简介,我们会更加认识到,LSM树是一种以读性能作为trade-off换取写性能的结构,并且RocksDB中的flush和compaction操作正是LSM思想的核心。下面来介绍LSM-based存储中通用的两种compaction策略,即size-tiered compaction和leveled compaction。

通用compaction策略

size-tiered compaction与空间放大

size-tiered compaction的思路非常直接:每层允许的SST文件最大数量都有个相同的阈值,随着memtable不断flush成SST,某层的SST数达到阈值时,就把该层所有SST全部合并成一个大的新SST,并放到较高一层去。下图是阈值为4的示例。

https://www.scylladb.com/2018/01/17/compaction-series-space-amplification/

size-tiered compaction的优点是简单且易于实现,并且SST数目少,定位到文件的速度快。当然,单个SST的大小有可能会很大,较高的层级出现数百GB甚至TB级别的SST文件都是常见的。它的缺点是空间放大比较严重,下面详细说说。

所谓空间放大(space amplification),就是指存储引擎中的数据实际占用的磁盘空间比数据的真正大小偏多的情况。例如,数据的真正大小是10MB,但实际存储时耗掉了25MB空间,那么空间放大因子(space amplification factor)就是2.5。

为什么会出现空间放大呢?很显然,LSM-based存储引擎中数据的增删改都不是in-place的,而是需要等待compaction执行到对应的key才算完。也就是说,一个key可能会同时对应多个value(删除标记算作特殊的value),而只有一个value是真正有效的,其余那些就算做空间放大。另外,在compaction过程中,原始数据在执行完成之前是不能删除的(防止出现意外无法恢复),所以同一份被compaction的数据最多可能膨胀成原来的两倍,这也算作空间放大的范畴。

下面用Cassandra的size-tiered compaction策略举两个例子,以方便理解。每层SST个数的阈值仍然采用默认值4。

  • 以约3MB/s的速度持续插入新数据(保证unique key),时间与磁盘占用的曲线图如下。

图中清晰可见有不少毛刺,这就是compaction过程造成的空间放大。注意在2000s~2500s之间还有一个很高的尖峰,原数据量为6GB,但在一瞬间增长到了12GB,说明Cassandra在做大SST之间的compaction,大SST的缺陷就显现出来了。尽管这只是暂时的,但是也要求我们必须预留出很多不必要的空闲空间,增加成本。

  • 重复写入一个400万条数据的集合(约1.2GB大,保证unique key),共重复写入15次来模拟数据更新,时间与磁盘占用的曲线图如下。

这种情况更厉害,最高会占用多达9.3GB磁盘空间,放大因子为7.75。虽然中途也会触发compaction,但是最低只能压缩到3.5GB左右,仍然有近3倍的放大。这是因为重复key过多,就算每层compaction过后消除了本层的空间放大,但key重复的数据仍然存在于较低层中,始终有冗余。只有手动触发了full compaction(即图中2500秒过后的最后一小段),才能完全消除空间放大,但我们也知道full compaction是极耗费性能的。

接下来介绍leveled compaction,看看它是否能解决size-tiered compaction的空间放大问题。

leveled compaction与写放大

leveled compaction的思路是:对于L1层及以上的数据,将size-tiered compaction中原本的大SST拆开,成为多个key互不相交的小SST的序列,这样的序列叫做“run”。L0层是从memtable flush过来的新SST,该层各个SST的key是可以相交的,并且其数量阈值单独控制(如4)。从L1层开始,每层都包含恰好一个run,并且run内包含的数据量阈值呈指数增长。

下图是假设从L1层开始,每个小SST的大小都相同(在实际操作中不会强制要求这点),且数据量阈值按10倍增长的示例。即L1最多可以有10个SST,L2最多可以有100个,以此类推。

https://www.scylladb.com/2018/01/31/compaction-series-leveled-compaction/

随着SST不断写入,L1的数据量会超过阈值。这时就会选择L1中的至少一个SST,将其数据合并到L2层与其key有交集的那些文件中,并从L1删除这些数据。仍然以上图为例,一个L1层SST的key区间大致能够对应到10个L2层的SST,所以一次compaction会影响到11个文件。该次compaction完成后,L2的数据量又有可能超过阈值,进而触发L2到L3的compaction,如此往复,就可以完成Ln层到Ln+1层的compaction了。

可见,leveled compaction与size-tiered compaction相比,每次做compaction时不必再选取一层内所有的数据,并且每层中SST的key区间都是不相交的,重复key减少了,所以很大程度上缓解了空间放大的问题。重复一遍上一节做的两个实验,曲线图分别如下。

持续写入实验,尖峰消失了。

持续更新实验,磁盘占用量的峰值大幅降低,从原来的9.3GB缩减到了不到4GB。

但是鱼与熊掌不可兼得,空间放大并不是唯一掣肘的因素。仍然以size-tiered compaction的第一个实验为例,写入的总数据量约为9GB大,但是查看磁盘的实际写入量,会发现写入了50个G的数据。这就叫写放大(write amplification)问题。

写放大又是怎么产生的呢?下面的图能够说明。

可见,这是由compaction的本质决定的:同一份数据会不断地随着compaction过程向更高的层级重复写入,有多少层就会写多少次。但是,我们的leveled compaction的写放大要严重得多,同等条件下实际写入量会达到110GB,是size-tiered compaction的两倍有余。这是因为Ln层SST在合并到Ln+1层时是一对多的,故重复写入的次数会更多。在极端情况下,我们甚至可以观测到数十倍的写放大。

写放大会带来两个风险:一是更多的磁盘带宽耗费在了无意义的写操作上,会影响读操作的效率;二是对于闪存存储(SSD),会造成存储介质的寿命更快消耗,因为闪存颗粒的擦写次数是有限制的。在实际使用时,必须权衡好空间放大、写放大、读放大三者的优先级。

RocksDB的混合compaction策略

由于上述两种compaction策略都有各自的优缺点,所以RocksDB在L1层及以上采用leveled compaction,而在L0层采用size-tiered compaction。下面分别来看看。

leveled compaction

当L0层的文件数目达到level0_file_num_compaction_trigger阈值时,就会触发L0层SST合并到L1。

L1层及以后的compaction过程完全符合前文所述的leveled compaction逻辑,如下图所示,很容易理解。

多个compaction过程是可以并行进行的,如下图所示。最大并行数由max_background_compactions参数来指定。

前面说过,leveled compaction策略中每一层的数据量是有阈值的,那么在RocksDB中这个阈值该如何确定呢?需要分两种情况来讨论。

  • 参数level_compaction_dynamic_level_bytes为false
    这种情况下,L1层的大小阈值直接由参数max_bytes_for_level_base决定,单位是字节。各层的大小阈值会满足如下的递推关系:

target_size(Lk+1) = target_size(Lk) * max_bytes_for_level_multiplier * max_bytes_for_level_multiplier_additional[k]

其中,max_bytes_for_level_multiplier是固定的倍数因子,max_bytes_for_level_multiplier_additional[k]是第k层对应的可变倍数因子。举个例子,假设max_bytes_for_level_base = 314572800,max_bytes_for_level_multiplier = 10,所有max_bytes_for_level_multiplier_additional[k]都为1,那么就会形成如下图所示的各层阈值。

可见,这与上文讲leveled compaction时的示例是一个意思。

  • 参数level_compaction_dynamic_level_bytes为true
    这种情况比较特殊。最高一层的大小不设阈值限制,亦即target_size(Ln)就是Ln层的实际大小,而更低层的大小阈值会满足如下的倒推关系:

target_size(Lk-1) = target_size(Lk) / max_bytes_for_level_multiplier

可见,max_bytes_for_level_multiplier的作用从乘法因子变成了除法因子。特别地,如果出现了target_size(Lk) < max_bytes_for_level_base / max_bytes_for_level_multiplier的情况,那么这一层及比它低的层就都不会再存储任何数据。

举个例子,假设现在有7层(包括L0),L6层已经存储了276GB的数据,并且max_bytes_for_level_base = 1073741824,max_bytes_for_level_multiplier = 10,那么就会形成如下图所示的各层阈值,亦即L5~L1的阈值分别是27.6GB、2.76GB、0.276GB、0、0。

可见,有90%的数据都落在了最高一层,9%的数据落在了次高一层。由于每个run包含的key都是不重复的,所以这种情况比上一种更能减少空间放大。

universal compaction

universal compaction是RocksDB中size-tiered compaction的别名,专门用于L0层的compaction,因为L0层的SST的key区间是几乎肯定有重合的。

前文已经说过,当L0层的文件数目达到level0_file_num_compaction_trigger阈值时,就会触发L0层SST合并到L1。universal compaction还会检查以下条件。

  • 空间放大比例
    假设L0层现有的SST文件为(R1, R1, R2, ..., Rn),其中R1是最新写入的SST,Rn是较旧的SST。所谓空间放大比例,就是指R1~Rn-1文件的总大小除以Rn的大小,如果这个比值比max_size_amplification_percent / 100要大,那么就会将L0层所有SST做compaction。

  • 相邻文件大小比例
    有一个参数size_ratio用于控制相邻文件大小比例的阈值。如果size(R2) / size(R1)的比值小于1 + size_ratio / 100,就表示R1和R2两个SST可以做compaction。接下来继续检查size(R3) / size(R1 + R2)是否小于1 + size_ratio / 100,若仍满足,就将R3也加入待compaction的SST里来。如此往复,直到不再满足上述比例条件为止。

当然,如果上述两个条件都没能触发compaction,该策略就会线性地从R1开始合并,直到L0层的文件数目小于level0_file_num_compaction_trigger阈值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1131095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于QT的图书管理系统

获取代码&#xff1a; 知识付费时代&#xff0c;低价有偿获取代码&#xff0c;请理解&#xff01; (1) 下载链接: 后发(2) 添加博主微信获取&#xff08;有偿&#xff09;,备注来源: mryang511688(3) 快速扫码咨询&#xff1a; 项目描述 技术&#xff1a;C、QT等 摘要&#…

(1)(1.8) Hondex声纳

文章目录 前言 1 推荐的硬件 2 连接和配置 3 参数说明 前言 Hondex HE-8S 是一款回声测深仪&#xff08;又称水下声纳&#xff09;&#xff0c;测深范围 100m&#xff0c;内置 GPS 和 NMEA 输出&#xff0c;可由 ArduPilot 使用。其他 Hondex 声纳也可以使用&#xff0c;但…

redis学习(三)——java整合redis

Jedis Jedis可以用于java连接redis数据库 新建一个maven项目&#xff0c;导入Jedis依赖 <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>RELEASE</version><scope>test…

mcgsTpc屏与施耐德TM218PLC通讯说明

一、 硬件连接 1、PLC通讯接口说明&#xff1a; 2、通讯电缆图&#xff1a; 二、PLC设置 1. 配置端口&#xff1a; 双击串行线路—弹出右侧设置窗口---设置串口通讯参数 2. 添加MODBUS协议。 ① 右击串口线路&#xff0c;选择添加设备&#xff1a; ② 选择现场总线&#xf…

Studio One6.5中文版本更新下载

供所有 Studio One 6 和 Studio 用户下载&#xff0c;以下是部分改进&#xff01; Studio One 6.5 更新功能&#xff1a; 空间音频制作工作流程与杜比全景声集成 支持杜比全景声双耳耳机监听 字幕和功能铺增强功能 整合杜比全景声的空间音频制作工作流程 Studio One 6.5…

损失函数总结(三):BCELoss、CrossEntropyLoss

损失函数总结&#xff08;三&#xff09;&#xff1a;BCELoss、CrossEntropyLoss 1 引言2 损失函数2.1 BCELoss2.2 CrossEntropyLoss 3 总结 1 引言 在前面的文章中已经介绍了介绍了一系列损失函数 (L1Loss、MSELoss)。在这篇文章中&#xff0c;会接着上文提到的众多损失函数继…

关于路由转发

路由表的作用 路由表的作用&#xff1a;目标网络匹配路由表&#xff0c;从相应网络转发&#xff1b;不匹配路由表&#xff0c;丢弃或转发至默认路由器。 路由转发的原理 根据IP地址找到目标网络&#xff0c;由应路由器解封装查看目标网络是否可达&#xff0c;重新封装进行转…

什么是数据中心的测试负载?

数据中心的测试负载是指在数据中心环境中进行的负载测试&#xff0c;以评估数据中心的性能、可靠性和可扩展性。负载测试是通过模拟实际使用情况&#xff0c;向数据中心的系统和组件施加各种类型的负载&#xff0c;以确定其在不同负载条件下的表现和响应能力。 通过模拟高负载情…

GCE的安装和使用

GCE的安装和使用 GCE的安装使用1. GCE的安装2. GCE的使用补充&#xff1a;一个简单的R脚本——kmerpdf.R&#xff0c;用于绘制kmer的种类和数量分布图 GCE的安装使用 一个基因组评估软件。其他同类型软件Genomescope 1. GCE的安装 Github官网&#xff1a;https://github.com…

css-渐变色矩形

效果图&#xff1a; 代码&#xff1a; html: <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"initial-scale1.0, user-scalableno" /><title></title><link …

一文讲解电源技术中专为准谐振转换器供电 高性能电流模式控制器NCP1380BDR2G

NCP1380BDR2G是一款高性能器件&#xff0c;旨在为准谐振转换器供电。该控制器基于专属的谷锁闭系统&#xff0c;可以在功率负载变轻时进行切换并降低开关频率。这样将产生稳定的运行&#xff0c;即使在漏极-源极谷中总是触发的开关事件下也是如此。此系统可在低至第 4 个谷的条…

Zip密码忘记了,如何破解密码?

Zip压缩包设置了密码&#xff0c;解压的时候就需要输入正确对密码才能顺利解压出文件&#xff0c;正常当我们解压文件或者删除密码的时候&#xff0c;虽然方法多&#xff0c;但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么&#xff0c;忘记了zip压缩包的密…

YOLOv5 添加 OTA,并使用 coco、CrowdHuman数据集进行训练。

YOLO-OTA 第一步&#xff1a;拉取 YOLOv5 的代码第二步&#xff1a;添加 ComputeLossOTA 函数第二步&#xff1a;修改 train 和 val 中损失函数为 ComputeLossOTA 函数1、在 train.py 中 首先添加 ComputeLossOTA 库。2、在 train.py 修改初始化的损失函数3、在 train.py 修改一…

.net 支付宝 应用网页验签

验证签名接口 /// <summary>/// 验证网关/// </summary>/// <returns></returns>[Route("gatewayVerify"), HttpPost, AllowAnonymous, NonUnify]public async Task<dynamic> gatewayVerify(){var Request App.HttpContext.Request;…

文件恢复怎么做?学会这3招!快速恢复文件!

“大家平常文件丢失时有什么简单的方法来恢复文件吗&#xff1f;我总是很粗心会将文件丢失&#xff0c;但是又不知道应该如何恢复&#xff0c;请大家给我出出主意吧&#xff01;” 如果我们保存在电脑上的文件丢失了&#xff0c;可能会影响到我们正常工作的进度。但在实际使用电…

有了for循环 为什么还要forEach?

js中那么多循环&#xff0c;for for…in for…of forEach&#xff0c;有些循环感觉上是大同小异今天我们讨论下for循环和forEach的差异。for循环和forEach都是用于遍历数组或类数组对象的工具。它们之间有一些区别&#xff0c;使用哪个取决于具体的需求。 本质区别: for 循环是…

cleanmymacX4.14免费版mac清除浏览器缓存软件

当我们使用浏览器访问网站时&#xff0c;浏览器会自动缓存一些数据&#xff0c;比如网页缓存、DNS缓存、插件缓存、SSL证书缓存和Cookie缓存等。虽然有些缓存可以提高浏览器的使用体验&#xff0c;但是缓存过多也会导致一些问题&#xff0c;比如网页更新后浏览器仍然显示旧的内…

渗透攻击漏洞——原型链污染

背景 2019年初&#xff0c;Snyk的安全研究人员披露了流行的JavaScript库Lodash中一个严重漏洞的详细信息&#xff0c;该漏洞使黑客能够攻击多个Web应用程序&#xff0c;这个安全漏洞就是一个“原型污染漏洞”&#xff08;JavaScript Prototype Pollution&#xff09;&#xff…

浅谈 MySQL 主从复制,优点?原理?

目录 1. 主从复制概述 2. 主从复制有什么优点&#xff1f; 3. 主从复制的原理 4. 数据一致性问题 4.1 同步复制 4.2 异步复制 4.3 半同步复制 1. 主从复制概述 既然是主从复制&#xff0c;那么至少就应该有两台服务器&#xff0c;一台作为主库(Master)&#xff0c;一台…

串口调试助手 WIN11 应用商店工具 界面美观 功能丰富

串口调试助手 win11 应用商店自带的工具&#xff0c;我用过最好用的串口调试助手。 功能以及特性 界面简洁美观;高速接收稳定、流畅、不卡顿、不丢数据;支持字符串和十六进制两种显示方式;可以正确显示中文字符;支持多种波特率&#xff0c;同时支持自定义波特率;自动断帧&am…