Rocksdb 的 rate_limiter实现 -- compaction限速
文章目录
- 前言
- 1. Compaction为什么会影响Client qps
- 1.1 基本LSM介绍
- 1.2 LSM internal ops
- 1.3 长尾延时的原因
- 2. Rate limiter 基本限速接口
- 3. Rate Limiter 限速原理实现
- 3.1 Rate Limiter的传入
- 3.2 Rate Limiter 控制 sync datablock的速率
- 3.3 Rate Limiter控制写入速率
- 4. rocksdb Rate Limiter的限制
前言
LSM 引擎针 的业界相关优化方案已经有很多了,优化的方向也是在不同workload纬度上进行取舍。
比如头条的Amap ,中科的dCompaction 是为了降低Compaction的写放大(写放大除了消耗ss),却在一定程度上降低了读性能;
奥斯汀大学 17年发表在顶会SOSP上的pebblesdb 较为有效得降低了写放大,写吞吐相比于原LSM实现提升2-2.7x,同时在range query有一定的优化,性能甚至超过了原有LSM的range query。
悉尼大学 19年发表在顶会UTC上的SILK 通过rocksdb的rate limiter来 通过客户端压力的监控来对 LSM内部整个IO进行限速控制,随未减少写放大,但却有效得降低了compaction对上层吞吐的qps和抖动的影响,尤其是长尾延时的优化上有效降低且极为稳定。
还有很多其他的优化方案,甚至专门有人整理了一篇LSM的优化论文,对业界数十篇LSM相关优化论文做了一个总结:
LSM-based Storage Techniques: A Survey
当然,rocksdb社区也在努力研究有效的compaction优化方案,本节想要介绍一下rocksdb 原生实现的compaction IO层的限速方案,通过compaction限速来降低compaction的IO对Client的 qps 和 波动的影响。
通过本文,你能够了解到如下关于rocksdb/LSM的知识点:
- compaction为什么会影响上层qps (纯写场景)?
- rocksdb rate limiter 基本限速接口
- rocksdb rate limiter 原理实现
- rocksdb rate limter 的限制
ps: 文中涉及到的源代码 都是基于rocksdb 6.4.6版本,不同的版本之间可能有实现细节上的差异,不过主体逻辑都是差不多的。
1. Compaction为什么会影响Client qps
1.1 基本LSM介绍
如下图 是一个基本的LSM 树的分层结构(以下的操作描述是针对传统LSM 的实现进行描述的,没有完整描述整个rocksdb的基本实现):
内存中有一个 write-buffer, 在rocksdb 上 也叫做 memtable
磁盘上从 L0-Ln也是一个分层结构: 每一层可以有很多有序字节表(sstables),其中L0层的sst之间可以有重叠,大于L0层的sst文件严格有序 且层内sst之间不能有重叠keys.
如下图 两种请求落到LSM 中:
Client 的update请求 会写入到内存中的write buffer之中即可返回,后续的写入由internal operation – flush and compaction来负责。
Client 的read 请求 会先读 write buffer,如果读不到则按照磁盘上的L0 - Ln逐层读取。
1.2 LSM internal ops
LSM三种internal操作:
- Flush 由write-buffer 写入 L0
- L0-L1 Compaction 因为L0 层文件之间允许有key的重叠(LSM 为了追求写性能,使用append only方式写入key,write-buffer一般是skiplist的结构),所以只允许单线程将L0的文件通过compaction写入L1
- Higher Level Compactions 大于L0层 的文件严格有序,所以可以通过多线程进行compaction.
Flush 操作如下:
- 请求写入到(write-buffer)memtable之中,当达到write_buffer_size大小 进行memtable switch
- 旧的memtable变成只读的用来 Flush,同时生成一个新的memtable用来接收客户端请求
- Flush的过程就是在L0 生成一个sst文件描述符,将immutable 中的数据通过系统调用写入该文件描述符代表的文件中。
L0–> L1 Compaction 操作如下:
- 将一个L0的sst文件和多个L1 的文件进行合并
- 目的是节省足够的空间来让write-buffer持续向L0 Flush
Higher Level Compactions操作如下:
对整个LSM进行GC,主要丢弃一些多key的副本 和 删除对应的key的values
这个过程并不如L0–>L1的compaction 紧急,但是会产生巨量的IO操作,这个过程可以后台并发进行。
1.3 长尾延时的原因
- L0满, 无法接收 write-buff不能及时Flush,阻塞客户端
因果链如下:
没有协调好internal ops – 》 Higher Level Compactions 占用了过多的IO --》L0–>L1 compaction 过慢 --》L0没有足够的空间 --》Write-buffer无法继续刷新。 - Flush 太慢,客户端阻塞
因果链如下:
没有协调好internal ops --》 Higher Level Comapction占用了过多的I/O --》 Flushing 过程中没有足够的IO资源 --》Flushing 过慢 --》Write-buffer提早写满而无法切换成immutable memtable,阻塞客户端请求。
综上我们知道了在LSM 下客户端长尾延时主要是由于三种 内部操作的IO资源未合理得协调好导致 最终的客户端操作发生了阻塞。
针对长尾延时的优化我们需要通过协调内部的internal 操作之间的关联,保证Flushing 优先级最高,能够占用最多的IO资源;同时也需要在合理的时机完成L0–L1的Compaction 以及 优先级最低但是又十分必要的Higher Level Compaction。
以下简单介绍一下Rocksdb 内部原生的Rate Limiter对这个过程的优化。
2. Rate limiter 基本限速接口
社区Rate Limiter 介绍
核心接口:
RateLimiter* rate_limiter = NewGenericRateLimiter(rate_bytes_per_sec /* int64_t */, refill_period_us /* int64_t */,fairness /* int32_t */);
将该接口加入到应用中的option的方式就是:
options.rate_limiter.reset(rocksdb::NewGenericRateLimiter(rate_bytes_per_sec /* int64_t */,refill_period_us /* int64_t */,fairness /* int32_t */));
核心参数如下三个:
rate_bytes_per_sec
这个是最常用也是大家使用起来最有效的一个参数,用来控制compaction或者flush过程中每秒写入的量。比如,设置了200M, 表示当compaction 累积的总写入token达到 200M /s 时才会触发系统调用的write.refill_period_us
用来控制 token 更新的频率;比如设置的rate_bytes_per_sec
是10M/s, 且refill_period_us
设置的是100ms,那么表示 每100ms即可重新调用一次compaction的写入。针对1M的大value 可以立即写入,而小于1M的数据则需要消耗CPU, 累积到1M 触发一次写入。fairness
表示低优先级请求获得处理的概率。
RateLimiter 支持接受高优先级线程 和 低优先级线程的请求,一半flush操作是最高的优先级,其次是 L0 --> L1 compaction优先级较高,最后则是Higher Level compactions 优先级最低。那么这个参数fairness
表示 即使现在有较多的高优先级任务在调度,低优先级的任务也有 1 / fairness 的机会能够被调度,从而防止被饿死。
3. Rate Limiter 限速原理实现
3.1 Rate Limiter的传入
先从我们的客户端入口来看 创建了RateLimiter 都做了一些什么?
rocksdb::NewGenericRateLimiter
接口主要是做一些初始化变量的工作:
以上类是继承自RateLimiter 类,能够提供更加精确的限速控制,比如初始化变量中的RateLimiter::Mode mode
来制定限速是针对只读 或者 只写 或者 所有的读写。
这个时候我们初始化好了rate_limter的对象,并将其传给options中,应用拿着options打开了rocksdb,具体的DB::Open
过程中会初始化DBImpl
对象
初始化该对象的过程中会拿着我们之前创建好的rate_limiter 进行全局option的初始化工作:
在DBOptions SanitizeOptions
函数中 能够看到通过rate_limiter 结合其他配置 或者交给指定的配置来达到后续限速的目的。
同时还会有在其他地方直接使用的rate_limiter对象 达到限速的目的:
在从sst file中调用read接口读取数据时能够通过rate_limiter 限制读请求的速率
同理当需要通过flush或者compaction 过程向sst文件中写入数据的时候可以通过rate limiter限制写入的速率
3.2 Rate Limiter 控制 sync datablock的速率
接着上文 RateLimiter 不为空时会将rate_bytes_per_sec
数值作为delayed_write_rate
的速率。
这个delayed_write_rate
参数会在rocksdb的Write Stall 的限速中进行描述,这里简单说一下 rocksdb 触发Write Stall
的几种原因:
- 过多memtables. 当此时内存中有
max_write_buffer_number
个memtables等待被flush,写会被完全Stall
在rocksdb的日志中会有如下记录:Stopping writes because we have 5 immutable memtables (waiting for flush), max_write_buffer_number is set to 5
- 过多的L0 SST files. 当L0的sst 文件个数超过
level0_slowdown_writes_trigger
个之后,会触发write stall;当L0文件个数达到level0_stop_writes_trigger
之后写入会完全阻塞。
在rocksdb的日志中会有类似如下记录:Stalling writes because we have 4 level-0 files Stopping writes because we have 20 level-0 files
- 过多的待处理 Compaction-bytes. 当预估的待处理Compaction 总大小达到了
soft_pending_compaction_bytes
会触发Stall,达到了hard_pending_compaction_bytes
会触发stop write。
在rocksdb的日志中会有类似记录如下:Stalling writes because of estimated pending compaction bytes 500000000 Stopping writes because of estimated pending compaction bytes 1000000000
回到上文中提到的bytes_per_sync
参数:
在rocksdb 进行Compaction或Flush过程中 ,写入数据之前 会通过OpenCompactionOutputFile
函数 创建WritableFileWriter 对象,来负责将即将写入的数据通过posix接口进行数据处理写入到文件系统的page cache或者 direct写入到磁盘之中。
创建WritableFileWriter 对象的过程中会将 通过option 传入的rate_limiter 传入到该对象之中。
到实际写入时,会通过BlockBasedTableBuilder::Add --> BlockBasedTableBuilder::Flush() --> BlockBasedTableBuilder::WriteBlock() --> BlockBasedTableBuilder::WriteRawBlock() --> WritableFileWriter::Append() --> WritableFileWriter::Flush() 以及WritableFileWriter::WriteBuffered()这两个函数
其中WritableFileWriter::Flush()
这个函数中会调用如下逻辑:
即当前写入到内存中缓存的数据偏移地址 相比于上一次的偏移地址 大小超过了1M,则触发一次RangeSync
RangeSync中也需要strict_bytes_per_sync_ 参数为1 才会是一次真正的sync系统调用。
这里为什么会有这样的配置呢,简单描述一下rocksdb写sst文件的逻辑:
原生配置是将所有的sst文件中的datablock,filterblock, index block,等所有的数据block写到内存(page cache)之后统一调用一次对当前文件句柄的sync操作,从而有效减少IO次数。
但是问题是类似这样大批量的累积sync 可能会导致compaction/flush 在每隔一段时间占用巨量的IO带宽,从而造成client的latency spike或者qps下降。
所以通过 rate_limiter配置 + bytes_per_sync + strict_bytes_per_sync_ 能够减少大批量的累积sync,而让整个IO均匀分不到整个compaction/flush的写入链路,可能client 的qps还是会有下降,但是不会出现过高的latency spike.
3.3 Rate Limiter控制写入速率
回到上文 Compaction / Flush的写入链条:
BlockBasedTableBuilder::Add --> BlockBasedTableBuilder::Flush() --> BlockBasedTableBuilder::WriteBlock() --> BlockBasedTableBuilder::WriteRawBlock() --> WritableFileWriter::Append() --> WritableFileWriter::Flush() 以及WritableFileWriter::WriteBuffered()这两个函数
看看WritableFileWriter::WriteBuffered() 函数,它是负责向缓冲区中添加数据:
通过 调用rate_limiter的RequestToken
函数 --》 调用GenericRateLimiter::Request
函数,来检测添加的数据大小left 是否满足开始配置的rate_limiter的rate_bytes_per_sec
限速大小,如果未达到,则会让当前线程休眠,并按照refill_period_us
频率来定时更新待写入的bytes是否满足写入的速率要求,并及时填充写入缓冲区。
此时当前的写入过程会阻塞,直到left累积到rate_limiter的写入限速阈值,才会继续向当前的文件句柄中正常写入。
4. rocksdb Rate Limiter的限制
静态的限速控制:
无法灵活变通, 后台internal ops的写入速率,比如偶尔Client 压力较大,需要降低internal ops写入速率对client的影响;偶尔Client 压力较小, 又可以增加internal ops,将之前累积的待写入的数据写入。
实际的测试,刚开始能够提供较为稳定的qps latency。但是在写入一段时间之后,随着数据量的增加,静态的Rate limter无法保证 internal ops(flush , L0–>L1 compaction 以及 higher level compactions) 在不影响client latency的情况下及时有效的处理,从而出现了较高的latency spike.
当然rocksdb后续推出的 auto tune 以及 业界的 TRIAD 和 SILK设计 看他们的描述 都能够有效的降低latency spike,后续会尝试有效测试 并发掘背后实现机理 之后分享出来。
相关文章:

Oracle --获取绑定变量的值.
SELECT * FROM DBA_HIST_SQLBIND WHERE SNAP_ID>67073 AND SNAP_ID<67079 AND SQL_ID3DR3410F086P4;SELECT * FROM v$sql_bind_capture where sql_id http://blog.itpub.net/22034023/viewspace-689802/ 通过v$sql_bind_capture视图,可以查看绑定变量…

计算机网络TCP/IP协议-从双绞线到TCP
消息响应也是同理,这种带端口的消息发送方式,其实就是UDP协议,UDP简单粗暴,但是UDP存在很多问题,所以我们需要设计一个稳定可靠的协议,TCP协议,首先,网络是不稳定的,我们发送的消息很有可能会在中途丢失,所以需要设置重试机制,当消息发送失败时重新发送,为了判断是否成功,还需要要求接收方收到消息后,必须发送确认消息,这样就可以保证消息必达,另外大段的内容发送,很容易造成部分丢失,导致全部内容都要重新发送,于是我们可以将数据分包,分成多个包发送。到这,也行你会发现了,演示中的IP地址是怎么设置的呢?

使用HTML CSS完成初步的页面,任务九:使用HTML/CSS实现一个复杂页面(示例代码)
任务目的通过实现一个较为复杂的页面,加深对于HTML,CSS的实战能力实践代码的复用、优化任务描述整个页面内容宽度固定,但头部的蓝色导航和浏览器宽度保持一致任务注意事项只需要完成HTML,CSS代码编写,不需要写JavaScri…

Yii-yiic使用
原文在:http://blog.sina.com.cn/s/blog_862b12fb0101n00v.htmlyii提供了强大的命令行工具来快速的创建相关组件和应用。下面就来讲解用yiic工具快速创建yii应用我的web目录在d:\ EasyPHP-DevServer\data\localweb下;yiiframework在D:\EasyPHP-DevServer…

nginx语法
一、 1. 配置文件由指令与指令块构成; 2. 每条指令以;分号结尾,指令与参数间以空格符号分隔; 3. 指令块以{} 大括号将多条指令组在一起; 4. include语句允许组合多个配置文件以提升可维护性; 5. 使用#符号添…

如何对 Rocksdb以及类似存储引擎社区 提出 有效的性能问题?
性能 是rocksdb的优点,活跃的社区十分欢迎大家对各自使用rocksdb 过程中性能相关的疑惑点进行提问。提问的时候如果能够提供更多,更详细的信息 是可以增加快速得到恢复回复的概率。当然,性能是一个非常广泛且有巨量影响因素的话题,…

我已经喜欢上了Python
早就听说了Python语言,今天试了试,挺喜欢她了。 Python 3.4.2 (v3.4.2:ab2c023a9432, Oct 6 2014, 22:15:05) [MSC v.1600 32 bit (Intel)] on win32Type "copyright", "credits" or "license()" for more information.&g…

layui跳转html如何带参数,Layui跳转页面代码(可携带复杂参数)
今天用了Layui的“数据表格 - 数据操作”示例代码,结果发现点击“编辑”按钮出出来一个弹出消息框,效果如下:虽然说也可以用“弹出层”做编辑页面,但是,由于我编辑的东西很多,用“弹出层”不太理想。我就想…

java获取日期
/* * 获取昨天日期 方法一,这个方法好像有点慢 */Date dt new Date(); Calendar cal Calendar.getInstance();cal.add(Calendar.DATE, -1);time new SimpleDateFormat( "yyyy-MM-dd").format(cal.getTime()); /* * 获取昨天日期 方法二 */Date as ne…

DRF (Django REST framework) 中的视图类
视图说明 1. 两个基类 1)APIView rest_framework.views.APIView APIView是REST framework提供的所有视图的基类,继承自Django的View父类。 APIView与View的不同之处在于: 传入到视图方法中的是REST framework的Request对象,而不是…

Go:分布式学习利器(1) -- 开发环境搭建 + 运行第一个go程序
文章目录为什么要学习 go开发环境搭建 -- MAC运行第一个go程序go 函数的返回值设置go 函数的命令行参数为什么要学习 go 在如下几个应用场景的需求下产生了go: 超大规模分布式计算集群多核硬件的架构web模式下的大规模开发和频繁的进度更新 所以go 语言有如下几个特点&#…

html生成的超级链接预览功能,超链接特效
功能说明超链接特效功能是基于报表特殊效果功能的一种扩展实现。报表特殊效果功能的作用是为单元格添加一些特殊的显示效果。超链接特效可以给超链接添加特殊显示效果,实现超链接功能的扩展增强。当产品默认生成的超链接显示效果不能满足用户的个性化需求时…

ROOT android 原理。 基于(zergRush)
出自: http://bbs.gfan.com/android-2996211-1-1.html 须要ROOT的同学请去上面的地址下载。 a.控制手机创建个暂时目录,然后把zergRush脚本写入此目录,并改动此文件权限使之能够运行(这一步无需ROOT权限); adb shell rm -r /data/local/tmpadb shell mkdir /data/lo…

Oracle数据库日常维护知识总结
DBA要定时对数据库的连接情况进行检查,看与数据库建立的会话数目是不是正常,如果建立了过多的连接,会消耗数据库的资源。同时,对一些“挂死”的连接,可能会需要DBA手工进行清理。首先要说的是,不同版本数据…

JAVA 第五周学习总结
20175304 2018-2019-2 《Java程序设计》第五周学习总结 教材学习内容总结 Java为什么要定义接口:接口的作用是实现多重继承,因为一个子类只能继承一个父类,但是可以实现一个或多个接口。使用关键字interface来定义一个接口,定义方…

Go: 分布式学习利器(2)-- Go中的变量,常量 以及与其他语言变量之间的差异
文章目录1. Go 语言编写测试代码2. Go 的变量3. Go 常量定义1. Go 语言编写测试代码 源码文件以 _test结尾: xxx_test.go测试方法名需以Test开头: func TESTXXX(t *testing.T) {..} ,且参数列表直接用go 默认的test参数即可 如下first_test…

Scala:Functions and Closures
1 object Functions {2 def main(args: Array[String]) {3 // 本地函数4 def localFun(msg: String) println(msg)5 localFun("Hi")6 7 // 函数对象8 var list List(1, 2, 3)9 list.foreach((x: Int) > println(x)) 10 list.fore…

云计算机机房怎么样,如何知道云电脑配置多少?怎么选择云电脑机房?
一般在玩一款游戏时,需要考虑玩游戏的配置,云电脑帮助我们实现配置的需求,那如何才能知道云电脑配置是多少,该怎么选择云电脑机房。在使用云电脑时,我们不用考虑自己的手机、平板和电脑的硬件,只要设备能正…

eclipse中新建android项目,不自动生成R.java
http://huyuantai000.iteye.com/blog/1681582转载于:https://www.cnblogs.com/wmm3416/p/3386698.html

获取子iframe的属性
第一种方法: <iframe name"iframeName" src"http://www.test.com"></iframe> // name"iframeName" 取值 window.frames[iframeName] 第二种方法: <iframe id"iframeId" src"http://www.t…

Go: 分布式学习利器(3) -- Go的数据类型和运算符
文章目录1. Go的数据类型1.1 类型转化1.2 类型的预定义1.3 指针类型2. Go 的运算符1. Go的数据类型 GO的基本数据类型如下: bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // 基本和uint8 类型一样 rune // 代表unicode …

云职教课堂计算机文化基础,2020智慧职教云课堂计算机文化基础答案最新最全单元测试答案...
参考答案如下智慧职教最新最全【判断题】两种气体在相同温度下放入同一个容器中,测得的压强分别为 和 ,则把它们同时放入容器中后的总压强为 .云课【单选题】根据物质的导电能力,将物质分为哪些类别?A. 导体 B. 半导体 C. 绝缘体 D. 以上都对堂计【单选题】按照数的进位制概念…
测试用例挑选策略
在软件开发过程中,无论是在feature testing还是在final regression testing中,测试策略的好坏在整个质量保证过程中起着至关重要的作用,尤其是在测试资源有限的情况下,影响更为突出。好的测试策略能够更快速的发现软件最有value的…

php json josn_decode()返回的是对像,如何把对像转成数组
php json josn_decode()返回的是对像,如何把对像转成数组 a.php传值页面,使用 json_encode($array)对数组进行加密码. b.php页面在接收a.php传过来的页面的值使用的是 json_decode($array),发现解密出来的数据是对象形式的: array(2) {[0]>…

【题解】黑格覆盖
题目描述 在一张由MN个小正方形格子组成的矩形纸张上,有k个格子被涂成了黑色。给你一张由mn个同样小正方形组成的矩形卡片,请问该卡片最多能一次性覆盖多少个黑格子? 输入输出格式 输入格式 输入共k1行: 第1行为5个整数M、N、m、n…

Go 分布式学习利器(4)-- 条件和循环
文章目录1. 循环语句2. 条件语句2.1 if...else 条件2.2 switch 条件1. 循环语句 Go语言和其他语言在循环语句上的主要差异是 Go语言仅支持for关键字。 语法形式:for i : 5; i > 0; i--,不像其他语言有两个小括号左右围着。 两种不同的循环体语法如下࿱…

php 对象的执行
1.BNF范式 %token T_OBJECT_OPERATOR "-> (T_OBJECT_OPERATOR)"unticked_statement: | expr ; { zend_do_free(&$1 TSRMLS_CC); }expr:r_variable { $$ $1; }| expr_without_variable { $$ $1; } ;r_variab…

golang不编译.html,golang之条件编译
Go语言能够经过go/build包里定义的tags和命名约定来让Go的包能够运行不一样的代码。html标签编译在源代码里添加标注,一般称之为编译标签(build tag)。编译标签采用靠近源代码文件顶部用注释的方式添加。go build在构建一个包的时候会读取这个包里的每一个源文件而且…

深入理解Struts2中的OGNL表达式
Struts 2中的表达式语言Struts 2支持以下几种表达式语言: OGNL(Object-Graph Navigation Language),可以方便地操作对象属性的开源表达式语言; JSTL(JSP Standard Tag Library),JSP …

[ZJOI2010]网络扩容
Description 给定一张有向图,每条边都有一个容量C和一个扩容费用W。这里扩容费用是指将容量扩大1所需的费用。 求: 1、在不扩容的情况下,1到N的最大流; 2、将1到N的最大流增加K所需的最小扩容费用。 Input 第一行包含三个整数N,M,…