当前位置: 首页 > 编程日记 > 正文

关于Titandb Ratelimiter 失效问题的一个bugfix

本文简单讨论一下在TitanDB 中使用Ratelimiter的一个bug,也算是一个重要bug了,相关fix已经提了PR到tikv 社区了pull-210。

这个问题导致的现象是ratelimiter 在titandb Flush/GC 生成blobfiled的过程中无法生效,也就是无法限制titandb的主要写 I/O。而我们想要享受Titandb在大value的写放大红利,却会引入巨量的写带宽(GC),这个时候对于大多数读敏感的场景 titandb GC出现时就是一场长尾灾难。

而ratelimiter则是这个场景的救星,它能够有效控制磁盘写入,降低了大量排队的写请求对读的影响。我们现在使用的非Intel Optane系列的ssd/NVM等设备,请求在磁盘内部其实都是顺序处理,也就是写请求多了,后续跟着的读请求延时必然会上涨。这个时候,我们能够有效控制磁盘的写入带宽,再加上titandb的GC并非持续性的波峰,而是间断性得调度,这样我们通过ratelimiter做一个均衡的限速,将GC出现时的磁盘I/O波峰打平到一段时间内处理完成,这样我们的读请求延时就很棒了。

然而,事与愿违,titan的ratelimiter有一些细节上的bug。

使用如下测试脚本:

./titandb_bench \--benchmarks="fillrandom,stats" \--max_background_compactions=32 \--max_background_flushes=4 \--max_write_buffer_number=6 \--target_file_size_base=67108864 \--max_bytes_for_level_base=536870912 \--statistics=true \--stats_dump_period_sec=5 \--num=5000000 \--duration=300 \--threads=10 \--value_size=8192 \--key_size=16 \--key_id_range=10000000 \--enable_pipelined_write=false \--db=./db_bench_test \--wal_dir=./db_bench_test \--num_multi_db=1 \--allow_concurrent_memtable_write=true \--disable_wal=true \ # 写入的过程中磁盘带宽仅由flush/GC产生--use_titan=true \ # 使用titandb--titan_max_background_gc=2 \--rate_limiter_bytes_per_sec=134217728 \ # 开启ratelimiter,限速到128M--rate_limiter_auto_tuned=false 

测试1:

测试rocksdb的ratelimiter是否生效 ,将--use_titan=false

能够看到磁盘写I/O行为非常稳定得被限制到128M左右: image

测试2:

测试titan的ratelimiter是否生效,将--use_titan=true直接打开

可以看到此时IO完全无法有效控制住,I/O线程有high和user线程池,也就是titandb的flush和GC

image

发现了问题,接下来看看问题原因:

我们上层传入了ratelimiter,而ratelimiter的调度则在数据写入具体文件之前调度的,titan这里复用了rocksdb的ratelimiter,也就是ratelimiter的调度最终都会通过同一个入口WritableFileWriter::Append函数。

Titan这里到达这个入口的途径就是在Flush/GC 创建Blobfile的时候进入的。

void BlobFileBuilder::Add(const BlobRecord& record, BlobHandle* handle) {if (!ok()) return;encoder_.EncodeRecord(record);handle->offset = file_->GetFileSize();handle->size = encoder_.GetEncodedSize();live_data_size_ += handle->size;// 写blobfilestatus_ = file_->Append(encoder_.GetHeader());if (ok()) {status_ = file_->Append(encoder_.GetRecord());num_entries_++;// The keys added into blob files are in order.if (smallest_key_.empty()) {smallest_key_.assign(record.key.data(), record.key.size());}assert(cf_options_.comparator->Compare(record.key, Slice(smallest_key_)) >=0);assert(cf_options_.comparator->Compare(record.key, Slice(largest_key_)) >=0);largest_key_.assign(record.key.data(), record.key.size());}
}

我们通过systemtap确认一下titandb这里的ratelimiter指针是否为空, 如果为空,那问题就不在writablefile那里了。

!#/bin/stapglobal timesprobe process("/home/test_binary").function("rocksdb::titandb::BlobFileBuilder::Add").call {printf("rate_limiter addr : %x\n ", $file_->ratelimiter_$)
}

发现有地址,且和LOG文件中打出的rate_limiter地址一样,说明ratelimiter确实是下发到了底层文件写入这里。

那就继续深入呗,看看是否执行到了ratelimiter逻辑里面。

这里被titan写的单测误导了很久blob_gc_job_test.cc,他们自己实现了一个ratelimiter的RequestToken,乍一看和rocksdb的RequestToken很像,但少了一个条件,一般人还看不出来:

size_t RequestToken(size_t bytes, size_t alignment,Env::IOPriority io_priority, Statistics* stats,RateLimiter::OpType op_type) override {// 少了一个对io_priority 的判断if (IsRateLimited(op_type)) {if (op_type == RateLimiter::OpType::kRead) {read = true;} else {write = true;}}return bytes;
}

因为这个单测除了少了一个判断之外,其他逻辑都没有问题,结果老是认为问题出在了RequestToken上某一个函数里,可能是从AppendRequestToken之间的某一个逻辑没有进入到,也就是无法进入到实际的RequestToken里面。

然而抓遍了中间部分函数的调用栈,人正常的逻辑,,,没有丝毫问题,通过Append进入之后需要不断填充一个buffer,当这个buffer达到1M之后(可以通过参数writable_file_max_buffer_size配置)会调用一次WritableFileWriter::Flush,没有direct_io的配置的话这里面必然会进入到WriteBufferred函数中,和rocksdb的逻辑一毛一样。。。wtf

万般无奈,只能回到RequestToken逻辑中了,stap打印了一下进入函数之后的各个参数的值。。。发现io_priority为啥大多数是2,偶尔是0/1。。。而rocksdb都是0/1,显然2肯定是无法进入到实际的Request逻辑的,被屏蔽在了外面。

如下是rocksdb的令牌桶限速入口:

size_t RateLimiter::RequestToken(size_t bytes, size_t alignment,Env::IOPriority io_priority, Statistics* stats,RateLimiter::OpType op_type) {// 必须保证io_priority < 2才能实际进入到 Request逻辑if (io_priority < Env::IO_TOTAL && IsRateLimited(op_type)) {bytes = std::min(bytes, static_cast<size_t>(GetSingleBurstBytes()));if (alignment > 0) {// Here we may actually require more than burst and block// but we can not write less than one page at a time on direct I/O// thus we may want not to use ratelimiterbytes = std::max(alignment, TruncateToPageBoundary(alignment, bytes));}Request(bytes, io_priority, stats, op_type);}return bytes;
}

问题显然出现在了io_priority这里,然后大概看了一下什么时候会对io_priority进行赋值。

它描述的是一个文件被ratelimiter拿到的时候该以什么样的优先级处理,如果设置的是高优先级IO_HIGH,则ratelimiter会优先满足这个文件的写入,不会限速得太狠;如果是IO_LOW则会尽可能得对它进行限速;而IO_HIGH则是文件创建时的默认优先级, 不会进行任何限速。

WritableFile(): last_preallocated_block_(0),preallocation_block_size_(0),io_priority_(Env::IO_TOTAL),write_hint_(Env::WLTH_NOT_SET),strict_bytes_per_sync_(false) {}

所以,rocksdb实际会在compaction/Flush 创建sst文件的时候对他们进行各自的优先级赋值,保证能够被限速。

逻辑分别在WriteL0Table–>BuildTableOpenCompactionOutputFile–>writable_file->SetIOPriority(Env::IO_LOW)中,然而我们在titan中的主体IO在blobfile的写入上,也就是创建Blobfile 的handle之后需要对blobfile的io_priority进行设置,才能保证ratelimiter能够拿到有效的I/O优先级。

看一下titan的FileManager::NewFile的逻辑:

  Status NewFile(std::unique_ptr<BlobFileHandle>* handle) override {auto number = db_->blob_file_set_->NewFileNumber();auto name = BlobFileName(db_->dirname_, number);Status s;std::unique_ptr<WritableFileWriter> file;{std::unique_ptr<WritableFile> f;s = db_->env_->NewWritableFile(name, &f, db_->env_options_);if (!s.ok()) return s;file.reset(new WritableFileWriter(std::move(f), name, db_->env_options_));}handle->reset(new FileHandle(number, name, std::move(file)));{MutexLock l(&db_->mutex_);db_->pending_outputs_.insert(number);}return s;}

到这里基本就清楚问题的原因了,显然titan创建blobfile并没有添加有效的io_priority,而且ratelimiter的单测写的不够严谨导致误导了很多人。

修复的话可以之间看这个pull-210 就可以了。

相关文章:

Java项目:前台预定+后台管理酒店管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能介绍&#xff1a; 前台用户端&#xff1a;用户注册登录&#xff0c;房间展示&#xff0c;房间分类&#xff0c;房间 按价格区间查询&#xff0c;房间评论&#xff0c;房间预订等等 后台管…

Solr初始化源码分析-Solr初始化与启动

用solr做项目已经有一年有余&#xff0c;但都是使用层面&#xff0c;只是利用solr现有机制&#xff0c;修改参数&#xff0c;然后监控调优&#xff0c;从没有对solr进行源码级别的研究。但是&#xff0c;最近手头的一个项目&#xff0c;让我感觉必须把solrn内部原理和扩展机制弄…

iOS :UIPickerView reloadAllComponets not work

编辑信息页面用了很多选择栏&#xff0c;大部分都用 UIPickerView 来实现。在切换数据显示的时候&#xff0c; UIPickerView 不更新数据&#xff0c;不得其解。Google 无解&#xff0c;原因在于无法描述自己的问题&#xff0c;想想应该还是代码哪里写错了。 写了个测试方法&…

单相计量芯片RN8209D使用经验分享(转)

单相计量芯片RN8209D使用经验分享转载于:https://www.cnblogs.com/LittleTiger/p/10736060.html

git 对之前的commit 进行重新签名 Resign

在向开源社区提交PR的时候如果之前的提交忘记添加sign &#xff08;个人签名/公司签名&#xff09;&#xff0c;则社区的DCO检查会失败。 关于通过DCO检查能够确保以下几件事情生效&#xff1a; 你所提交的贡献是由你自己完成或者 你参与了其中&#xff0c;并且有权利按照开源…

【原创】linux命令bc使用详解

最近经常要在linux下做一些进制转换&#xff0c;看到了可以使用bc命令&#xff0c;如下: echo "obase10;ibase16;CFFF" | bc 用完以后就对bc进行了进一步的了解, man bc里面有详细的使用说明。 1.是什么,怎么用 bc - An arbitrary precision calculator language 一…

Java项目:学生信息管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 用户的登录注册&#xff0c;学生信息管理&#xff0c;教师信息管理&#xff0c;班级信 息管理&#xff0c;采用mvcx项目架构&#xff0c;覆盖增删改查&#xff0c;包括学…

MVC學習網站

http://www.cnblogs.com/haogj/archive/2011/11/23/2246032.html

数据导出Excel表格

public String exportInfoFr(String path,String name,String startdate,String enddate,SysUser user){List<Map<String, Object>> list this.esEntPermitErrDao.findListObjectBySql("select 字段值1,字段值2,字段值3,字段值4,字段值5 from 表名 where 字段…

Rocksdb 通过posix_advise 让内核减少在page_cache的预读

文章目录1. 问题排查确认I/O完全/大多数来自于rocksdb确认此时系统只使用了rocksdb的Get来读确认每次系统调用下发读的请求大小确认是否在内核发生了预读2. 问题原因内核预读机制page_cache_sync_readaheadondemand_readahead3. 优化事情起源于 组内的分布式kv 系统使用rocksdb…

[leetcode] Minimum Path Sum

Minimum Path Sum Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path. Note: You can only move either down or right at any point in time.分析&#xff1a;动态规划…

Java项目:在线小说阅读系统(读者+作者+管理员)(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 1:用户及主要操作功能 游客可以浏览网站的主页&#xff0c;登陆注册&#xff0c;小说湿度&#xff0c;下单购 买&#xff0c;订单查询&#xff0c;个人信息查询&#xf…

游戏中的脚本语言

本文最初发表于《游戏创造》(http://www.chinagcn.com)2007年8月刊。版权所有&#xff0c;侵权必究。如蒙转载&#xff0c;必须保留本声明&#xff0c;和作者署名&#xff1b;不得用于商业用途&#xff0c;必须保证全文完整。网络版首次发表于恋花蝶的博客(http://blog.csdn.ne…

mvn项目中的pom文件提示Error parsing lifecycle processing instructions解决

清空.m2/repository下的所有依赖文件&#xff0c;重新下载即可解决该问题。 如果本地用户下没有.m2/repository 目录&#xff0c;找到如下mvn 指定的repository&#xff0c;进去之后清空所有文件。 转载于:https://www.cnblogs.com/Hackerman/p/10736498.html

blktrace 工具集使用 及其实现原理

文章目录工具使用原理分析内核I/O栈blktrace 代码做的事情内核调用 ioctl 做的事情BLKTRACESETUPBLKTRACESTOPBLKTRACETEARDOWN内核 调用blk_register_tracepoints 之后做的事情参考最近使用blktrace 工具集来分析I/O 在磁盘上的一些瓶颈问题&#xff0c;特此做一个简单的记录。…

Java项目:教材管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 管理员可以增删改查教材、教材商、入库教材、用户(用 户包括学生和教师)可以对教材商、教材进行。xcel的导入 导出操作。教U阿以领取入库的教材&#xff0c;可以退还教材…

mysql更改数据文件目录及my.ini位置| MySQL命令详解

需求&#xff1a;更改mysql数据数据文件目录及my.ini位置。 步骤&#xff1a; 1、查找my.ini位置&#xff0c;可通过windows服务所对应mysql启动项&#xff0c;查看其对应属性->可执行文件路径&#xff0c;获取my.ini路径。 "D:\MySQL\MySQL Server 5.5\bin\mysqld&quo…

私有云管理-Windows Azure Pack

今天是2014年的第一天&#xff0c;今年的第一篇博客关于私有云&#xff0c;而我在2014年的主要目标也是针对私有云。随着Windows Azure在中国的落地&#xff0c;大家逐渐的熟悉了在Windows Azure中的云体验。而微软针对私有云、混合云推出了一个管理自助门户&#xff0c;Window…

面向对象(类的概念,属性,方法,属性的声明,面向对象编程思维

1 面向对象 1.1 你是如何认识新事物的&#xff1f; 从过往的事物中总结事物的特点(特征)&#xff0c;并比对新事物&#xff0c;把新事物进行归类。 1.2 类(Class)的概念(A) 类是对一组具有相同特征和行为的对象的抽象描述。 理解: [1] 类包含了两个要素:特性和行为 > 同一类…

cannot find main module 解决办法

做6.824 实验的过程中想要跑测试&#xff0c;发现go test -run 2A时 出现cannot find main module问题&#xff0c;测试跑不起来。 原因 这个原因是从GO1.11 版本开始引入了go.mod文件来对项目中的go源码的编译相关的内容进行管理&#xff0c;经常使用GO的同学可能深受go get…

Java项目:网上选课系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a; 系统分为三个角色。最高权限管理员&#xff0c;学生&#xff0c;教师&#xff0c;包括 学生管理&#xff0c;教师管理&#xff0c;课程管理&#xff0c;选课&#xff0c;退课…

C#中类的继承 override virtual new的作用以及代码分析

继承中override virtual new的作用 virtual 父类中需要注明允许重写的方法&#xff1b; override 子类中必须显示声明该方法是重写的父类中的方法&#xff1b; new 子类中忽略父类的已存在的方法&#xff0c;“重写该方法“&#xff1b; C#中不支…

spring手动代码控制事务

为什么80%的码农都做不了架构师&#xff1f;>>> DataSourceTransactionManager tran new DataSourceTransactionManager(vjdbcTemplate.getDataSource());DefaultTransactionDefinition def new DefaultTransactionDefinition();//事务定义类def.setPropagationB…

tar命令-压缩,解压缩文件

tar&#xff1a; -c: 建立压缩档案 -x&#xff1a;解压 -t&#xff1a;查看内容 -r&#xff1a;向压缩归档文件末尾追加文件 -u&#xff1a;更新原压缩包中的文件 上面五个参数是独立的&#xff0c;压缩解压都要用到其中一个&#xff0c;可以和下面的命令连用但只能用其中一个。…

MIT 6.824 Lab2A (raft) -- Leader Election

文章目录实验要求Leader Election流程 及详细实现介绍基本角色关键超时变量关键的两个RPC实现RequestVote RPCAppendEntries RPCGo并发编程实现leader election调度本节记录的是完成MIT6.824 raft lab的leader Election部分实验。代码: https://github.com/BaronStack/MIT-6.82…

Java项目:在线考试系统(java+springboot+vue+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 本系统主要实现的功能有&#xff1a; 学生以及老师的注册登录&#xff0c;在线考试&#xff0c;错题查询&#xff0c;学生管理&#xff0c;问题管理&#xff0c;错题管理&#xff0c;错题查询…

写给自己的web开发资源

web开发给我的感觉就是乱七八糟&#xff0c;而且要学习感觉总是会有东西要学习&#xff0c;很乱很杂我也没空搞&#xff0c;&#xff08;其实学习这个的方法就是去用它&#xff0c;什么你直接用&#xff1f;学过js么学过jquery么&#xff1f;哈哈&#xff0c;我没有系统的看完过…

虚拟机VMWare“提示:软件虚拟化与此平台上的长模式不兼容”的解决方法

虚拟机VMWare“提示&#xff1a;软件虚拟化与此平台上的长模式不兼容”不少童鞋反映&#xff0c;在使用Windows7 64位操作系统时&#xff0c;无法运行VMWare或MS Virtual server等软件虚拟操作系统。提示为“提示&#xff1a;软件虚拟化与此平台上的长模式不兼容. 禁用长模式. …

如何在Vue项目中使用vw实现移动端适配(转)

有关于移动端的适配布局一直以来都是众说纷纭&#xff0c;对应的解决方案也是有很多种。在《使用Flexible实现手淘H5页面的终端适配》提出了Flexible的布局方案&#xff0c;随着viewport单位越来越受到众多浏览器的支持&#xff0c;因此在《再聊移动端页面的适配》一文中提出了…

Jsoncpp 在C++开发中的一些使用记录

jsoncpp 是一个C 语言实现的json库&#xff0c;非常方便得支持C得各种数据类型到json 以及 json到各种数据类型的转化。 一个json 类型的数据如下&#xff1a; {"code" : 10.01,"files" : "","msg" : "","uploadid&q…