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

TitanDB 中使用Compaction Filter ,产生了预期之外几十倍的读I/O

Compaction过程中 产生大量读I/O 的背景

项目中因大value 需求,引入了PingCap 参考Wisckey 思想实现的key-value分离存储 titan, 使用过程中因为有用到Rocksdb本身的 CompactionFilter功能,所以就直接用TitanDB的option 传入了compaction filter。

使用过程中,单纯的通过db->Put接口写入 就会发现磁盘上大量的读I/O。

Ps : 相关的现象产生时的基本配置就不贴上来了,这个现象用过titan的compaction filter的同学应该都会比较清楚。

如果没有用过,但也发现了一些异常,也可以直接向后看。

我们数据写入量是 key:10B, value: 8K , 磁盘上的读本身是由于compaction引起的,compaction过程中需要将选择的sst文件中的key-value通过迭代器一个一个读取上来做堆排序。这个过程会产生读I/O,也就是只有Compaction 本身会有读I/O。

问题现象是单次compaction的量也就几十M,但磁盘上却产生了数百M的读I/O

更加直观的体现就是通过命令sudo iotop,可以看到此时大量的compaction 线程产生了读IO

在这里插入图片描述

问题分析

这里显然不合理,rocksdb的日志打印出来的LOG 中总共的compaction的带宽也就几十M,因为在titandb的key-value分离存储之后LSM-tree中仅仅存储了key和key-index,所以单次compaction的过程中理论上并不会携带着value参与,这样的大量I/O不太合理。

继续向下看,从iotop的输出中取出来一个compaction的线程ID,sudo strace -ttt -T -p 209278 抓取它的系统调用,可以看到大量的pread64系统调用

1617853714.972743 pread64(14224, "\203\250\206p\20/\0\0\0\fuid:11288154\201^\365.\0\0\n\362]\22"..., 8190, 13057621) = 12057 <0.000445>
1617853714.973241 pread64(13772, "\357\212\255y\20/\0\0\0\fuid:11288198\201^\365.\0\0\n\362]\22"..., 8190, 3267429) = 12057 <0.000013>
1617853714.973284 pread64(15591, "\343\3602\373\20/\0\0\0\fuid:11288239\201^\365.\0\0\n\362]\22"..., 8190, 3279482) = 12057 <0.000230>
...

可以看到pread64读到的数据大小是8190B,显然是我们写入的value大小,这货肯定读了存放value的blobfile

随机抽样几个fd ,也就是pread64(14224, "\20...)的第一参数,从进程的fd列表中看看它链接得是哪个文件ls -l /proc/xxx/fd | grep 209278

lr-x------ 1 kiwi2 kiwi2 64 Apr  8 11:49 /proc/209235/fd/10029 -> /mnt/db/14/titandb/000681.blob

果然是从blobfile中读取的数据,到这里我们就知道为什么compaction线程会有这么多的读,因为compaction过程中竟然读了blob file中的value。。。陷入沉思,梳理一下titan的写入逻辑。

在这里插入图片描述

  1. Key-value 都和以前rocksdb一样,先写入memtable
  2. 在Flush过程中形成sst文件的时候,通过titan自己的table-builder add的过程中来做区分,大于一个阈值时 分离value写入到blobfile中,key+key-index 存放到LSM-tree 的sst文件中
  3. 后续LSM-tree继续自己的compaction, blobfiles 则在达到触发gc条件的时候由一个线程池的一个线程调度blobfile的过期清理

也就是titan compaction过程中理论上仅仅是sst文件中的key + key-index参与,并不会涉及blobfiles 中的value,要不然key-value分离的意义何在?带宽还是没有降下来。

接下来的分析就更加明了了,看看这个时候大量读的compaction线程调用栈,直接上命令sudo pstack 209278(pstack底层也是调用gdb 执行的,不过是quiet指令执行,并不会阻塞线程),最后能看到如下调用栈

#0  0x00007faa05d93f73 in pread64 () from /lib64/libpthread.so.0
#1  0x000000000095f85e in pread (__offset=16132142, __nbytes=12057, __buf=0x3c074a000, __fd=<optimized out>)
#2  rocksdb::PosixRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const ()
#3  0x0000000000a0c0b1 in rocksdb::RandomAccessFileReader::Read(unsigned long, unsigned long, rocksdb::Slice*, char*, bool) const ()
#4  0x000000000081b197 in rocksdb::titandb::BlobFileReader::ReadRecord(rocksdb::titandb::BlobHandle const&, rocksdb::titandb::BlobRecord*, rocksdb::titandb::OwnedSlice*) ()
#5  0x000000000081ba21 in rocksdb::titandb::BlobFileReader::Get(rocksdb::ReadOptions const&, rocksdb::titandb::BlobHandle const&, rocksdb::titandb::BlobRecord*, rocksdb::PinnableSlice*) ()
#6  0x00000000008428e3 in rocksdb::titandb::BlobFileCache::Get(rocksdb::ReadOptions const&, unsigned long, unsigned long, rocksdb::titandb::BlobHandle const&, rocksdb::titandb::BlobRecord*, rocksdb::PinnableSlice*) ()
#7  0x00000000008396b8 in rocksdb::titandb::BlobStorage::Get(rocksdb::ReadOptions const&, rocksdb::titandb::BlobIndex const&, rocksdb::titandb::BlobRecord*, rocksdb::PinnableSlice*) ()
#8  0x00000000007f4a3b in rocksdb::titandb::TitanCompactionFilter::FilterV2 (this=0x3ceb05b00, level=0, key=..., value_type=<optimized out>, value=..., new_value=0x1be783cf8, skip_until=0x1be783d18)
#9  0x0000000000a2fa1a in InvokeFilterIfNeeded (skip_until=0x7fa9f786e730, need_skip=0x7fa9f786e72f, this=0x1be783b00)
#10 rocksdb::CompactionIterator::InvokeFilterIfNeeded (this=0x1be783b00, need_skip=0x7fa9f786e72f, skip_until=0x7fa9f786e730)
#11 0x0000000000a3039a in rocksdb::CompactionIterator::NextFromInput() ()
#12 0x0000000000a31c5a in rocksdb::CompactionIterator::Next (this=0x1be783b00)
#13 0x0000000000a39658 in rocksdb::CompactionJob::ProcessKeyValueCompaction(rocksdb::CompactionJob::SubcompactionState*) ()
#14 0x0000000000a3aa1c in rocksdb::CompactionJob::Run() ()
#15 0x0000000000887a5b in rocksdb::DBImpl::BackgroundCompaction(bool*, rocksdb::JobContext*, rocksdb::LogBuffer*, rocksdb::DBImpl::PrepickedCompaction*, rocksdb::Env::Priority) ()
#16 0x000000000088ab44 in rocksdb::DBImpl::BackgroundCallCompaction(rocksdb::DBImpl::PrepickedCompaction*, rocksdb::Env::Priority) ()
#17 0x000000000088b028 in rocksdb::DBImpl::BGWorkCompaction (arg=<optimized out>)
#18 0x0000000000a1437c in operator() (this=0x7fa9f7870370)
#19 rocksdb::ThreadPoolImpl::Impl::BGThread(unsigned long) ()
#20 0x0000000000a144d3 in rocksdb::ThreadPoolImpl::Impl::BGThreadWrapper (arg=<optimized out>)

这个调用栈中可以看到

#11 0x0000000000a3039a in rocksdb::CompactionIterator::NextFromInput() ()
#12 0x0000000000a31c5a in rocksdb::CompactionIterator::Next (this=0x1be783b00)
#13 0x0000000000a39658 in rocksdb::CompactionJob::ProcessKeyValueCompaction(rocksdb::CompactionJob::SubcompactionState*) ()
#14 0x0000000000a3aa1c in rocksdb::CompactionJob::Run() ()

这一些都是正常的compaction逻辑,但是再往上走就进入了compaction filter之中,使用了Titandb的filter函数,并且调用了rocksdb::titandb::BlobStorage::Get,确实,我们用户态用了compaction filter,但不应该调用到blob的Get,好吧。。。
直接看Titan的源代码。

Titan的Compaction Filter实现

在打开TitanDB的时候会将用户传入的compaction_filter作为一个子filter传进来,并且交给titan自己的TitanCompactionFilterFactory来处理

Status TitanDBImpl::OpenImpl(const std::vector<TitanCFDescriptor>& descs,std::vector<ColumnFamilyHandle*>* handles) {......std::vector<ColumnFamilyDescriptor> base_descs;std::vector<std::shared_ptr<TitanTableFactory>> titan_table_factories;for (auto& desc : descs) {......if (cf_opts.compaction_filter != nullptr ||cf_opts.compaction_filter_factory != nullptr) {std::shared_ptr<TitanCompactionFilterFactory> titan_cf_factory =std::make_shared<TitanCompactionFilterFactory>(cf_opts.compaction_filter, cf_opts.compaction_filter_factory,this, desc.options.skip_value_in_compaction_filter, desc.name);cf_opts.compaction_filter = nullptr;cf_opts.compaction_filter_factory = titan_cf_factory;}}// Open base DB.s = DB::Open(db_options_, dbname_, base_descs, handles, &db_);\......
}

进入TitanCompactionFilterFactoryCreateCompactionFilter函数
之前介绍Rocksdb的ComapctionFilter实现的时候知道,引擎对外暴漏了这一些接口,能够由用户来指定自己想要过滤什么样的key。

Rocskdb CompactionFilter实现

std::unique_ptr<CompactionFilter> CreateCompactionFilter(const CompactionFilter::Context &context) override {assert(original_filter_ != nullptr || original_filter_factory_ != nullptr);std::shared_ptr<BlobStorage> blob_storage;{MutexLock l(&titan_db_impl_->mutex_);blob_storage = titan_db_impl_->blob_file_set_->GetBlobStorage(context.column_family_id).lock();}if (blob_storage == nullptr) {assert(false);// Shouldn't be here, but ignore compaction filter when we hit error.return nullptr;}const CompactionFilter *original_filter = original_filter_;std::unique_ptr<CompactionFilter> original_filter_from_factory;if (original_filter == nullptr) {original_filter_from_factory =original_filter_factory_->CreateCompactionFilter(context);original_filter = original_filter_from_factory.get();}return std::unique_ptr<CompactionFilter>(new TitanCompactionFilter(titan_db_impl_, cf_name_, original_filter,std::move(original_filter_from_factory), blob_storage, skip_value_));
}

Factory会将TitanCompactionFilter返回,且这个filter也携带着用户自定义的Filteroriginal_filter。也就是comapction 过程中会先执行TitanCompactionFilterFilterV2函数,接着看一下titandb 的FilterV2函数:

Decision FilterV2(int level, const Slice &key, ValueType value_type,const Slice &value, std::string *new_value,std::string *skip_until) const override {......BlobRecord record;PinnableSlice buffer;ReadOptions read_options;// 问题源头s = blob_storage_->Get(read_options, blob_index, &record, &buffer);if (s.IsCorruption()) {// Could be cause by blob file beinged GC-ed, or real corruption.// TODO(yiwu): Tell the two cases apart.return Decision::kKeep;} else if (s.ok()) {// 用户自定义的Filter逻辑auto decision = original_filter_->FilterV2(level, key, kValue, record.value, new_value, skip_until);...}
}

可以看到这里会有一个blob_storage_->Get,到此我们就知道为什么会有一个blobfile 的Get了。

因为用户在回掉使用original_filter_->FilterV2逻辑的时候需要知道具体的value,所以Titan这里需要将blobfile中的value传回去。

OK。。。这样啊,那确实没有办法,毕竟想要拥有灵活性,代价是必不可少的。

解决

如果业务中针对compaction filter的需求是不需要value的数据,这里可以避免掉blobfile的Get,设置titan options skip_value_in_compaction_filter = true 即可。

相关文章:

Java项目:前台+后台精品图书管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 登录注册&#xff0c;办理借阅。借阅记录&#xff0c;预约借阅&#xff0c;借出未还, 借阅逾期&#xff0c;学生管理&#xff0c;图书管理&#xff0c;书库分类查询搜索…

消除 activity 启动时白屏、黑屏问题

默认情况下 activity 启动的时候先把屏幕刷成白色&#xff0c;再绘制界面&#xff0c;绘制界面或多或少有点延迟&#xff0c;这段时间中你看到的就是白屏&#xff0c;显然影响用户体验&#xff0c;怎么消除呢&#xff1f; 在 Activity theme 设置style 即可 1 <style na…

理解并实施:HSRP(CCNA200-120新增考点)

理解并实施:HSRP思科热备路由器协议HSRP&#xff08;HotStandby Router Protocol&#xff09;是企业级网络路由器的故障冗余服务。如图9.116所示&#xff0c;192.168.2.0/24的子网需要与目标192.168.5.2的计算机通信。192.168.2.0/24的子网有两台出口路由器&#xff0c;一台是R…

使用机智云APP控制战舰V3 (转)

源&#xff1a;使用机智云APP控制战舰V3 转载于:https://www.cnblogs.com/LittleTiger/p/10725586.html

从JoinBatchGroup 代码细节 来看Rocksdb的相比于leveldb的写入优势

文章目录1. Rocksdb写入模型2. LevelDB写入的优化点3. Rocksdb 的优化1. Busy Loop2. Short Wait -- SOMETIMES busy Loop3. Long-wait4. 测试验证4. 总结1. Rocksdb写入模型 本节讨论一下Rocksdb在写入链路上的一个优化点&#xff0c;这个优化细节可以说将Rocksdb这个存储引擎…

Java项目:嘟嘟网上商城系统(java+jdbc+jsp+mysql+ajax)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a; 商品的分类展示&#xff0c;用户的注册登录&#xff0c;购物车&#xff0c;订单结算&#xff0c; 购物车加减&#xff0c;后台商品管理&#xff0c;分类管理&#xff0c;订单…

SOAPUI请求及mockservice 使用

1、新建soap Project&#xff0c;输入wsdl的地址&#xff0c;运行request 2.邮件Project&#xff0c;建立mockservice&#xff0c;建立多个response&#xff0c;选在mock operation&#xff0c;选择response dispa…

空间直角坐标系与球面坐标互转

空间直角坐标系与球面坐标互转 1 using System;2 using System.Collections.Generic;3 using System.Linq;4 using System.Text;5 6 namespace AppSurveryTools.SphericalAndCartesian7 {8 class CartesianCoord9 { 10 public double x; 11 public dou…

Ajax 的优势和不足

Ajax 的优势 1. 不需要插件支持 Ajax 不需要任何浏览器插件&#xff0c;就可以被绝大多数主流浏览器所支持&#xff0c;用户只需要允许 JavaScript 在浏览器上执行即可。 2. 优秀的用户体验 这是 Ajax 技术的最大优点&#xff0c;能在不刷新整个页面的前提下更新数据&#xff0…

BitCask 持久化hash存储引擎 原理介绍

文章目录前言引擎背景引擎原理1. 磁盘数据结构2. 内存数据结构3. 读流程4. 数据合并总结前言 最近工作中部分项目中&#xff0c;对存储引擎的需求希望高性能的写、点查&#xff0c;并不需要Range。这里看到大家总会提到BitCask这个存储引擎方案&#xff0c;并不是很了解&#…

C# Socket系列三 socket通信的封包和拆包

通过系列二 我们已经实现了socket的简单通信 接下来我们测试一下&#xff0c;在时间应用的场景下&#xff0c;我们会快速且大量的传输数据的情况&#xff01; 1 class Program2 {3 static void Main(string[] args)4 {5 TCPListener tcp n…

Java项目:CRM客户管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 用户管理&#xff0c;系统管理&#xff0c;客户管理&#xff0c;客户服务&#xff0c;客户关怀, 销售机会&#xff0c;统计管理等等。 二、项目运行 环境配置&#x…

Android 获取标题栏的高度

2019独角兽企业重金招聘Python工程师标准>>> 通过获取内容区域的 rect 的 top 值就是状态栏和标题栏的高度&#xff0c;也就可以得到标题栏的高度了&#xff0c; [java] view plaincopy int contentTop getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTo…

力扣—— 三维形体投影面积

在 N * N 的网格中&#xff0c;我们放置了一些与 x&#xff0c;y&#xff0c;z 三轴对齐的 1 * 1 * 1 立方体。 每个值 v grid[i][j] 表示 v 个正方体叠放在单元格 (i, j) 上。 现在&#xff0c;我们查看这些立方体在 xy、yz 和 zx 平面上的投影。 投影就像影子&#xff0c;将…

一图带你入门Linux 存储I/O栈

发现了一个内核大佬 的 Linux 存储I/O栈&#xff0c;很清晰&#xff01;&#xff01;&#xff01; 原地址如下&#xff1a; http://ilinuxkernel.com/?p1559 【侵删】

Java项目:在线美食网站系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a;用户的注册登录&#xff0c;美食浏览&#xff0c;美食文化&#xff0c;收藏百 科&#xff0c;趣味问答&#xff0c;食谱等等功能等等。 二、项目运行 环境配置&#xff1a;…

性能测试中传——lr理论基础(四)

转载于:https://blog.51cto.com/fuwenchao/1346435

滑动定位的三种方法,以及热启动(五)

from init_driver.Init_driver import init_driverdriver init_driver()# 坐标-->坐标&#xff0c;定位滑动 driver.swipe(309, 1353, 537, 511, duration3000)# 元素-->元素&#xff0c;定位滑动 start_ele driver.find_element_by_xpath("//*[contains(text, 通…

TitanDB GC详细实现原理 及其 引入的问题

文章目录1. 为什么要有GC2. GC的触发条件3. GC的核心逻辑1. blob file形态2. GC Prepare3. GC pick file4. GC run4. GC 引入的问题5. Titan的测试代码通过本篇&#xff0c;能够从TitanDB的源代码中看到 key/value 分离之后引入的一些复杂性&#xff0c;这个复杂性很难避免。 …

Java项目:医院住院管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 住院病人管理&#xff0c;住院病房管理&#xff0c;医生管理&#xff0c;药品管理&#xff0c;仪 器管理等等。 二、项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.…

1m网速是什么意思,1m带宽是什么意思

1M网速下载速度应是多少&#xff1f;我怎么才50多KB&#xff1f;&#xff1f; 建议: 一般来说是90到100算正常。最高能达到120 带究竟该有多快 揭开ADSL真正速度之谜 常常使用ADSL的用户&#xff0c;你知道ADSL的真正速度吗&#xff1f;带着这个疑问我们将问题一步一步展开。…

泛型实体类List绑定到repeater

泛型实体类List<>绑定到repeater 后台代码&#xff1a; private void bindnewslist(){long num 100L;List<Model.news> news _news.GetList(out num);this.newslist.DataSource news;this.newslist.DataBind();} 说明&#xff1a;Model.news是实体类&#xff0c…

Qt4.8.5移植

这两天搞了Qt移植 因为不小心 耽误了挺多时间 但是也比较好的掌握了 现在记录一下 准备工具&#xff1a; tslib-1.16 qt-everywhere-opensource-src-4.8.5.tar 下载路径&#xff1a; tslib-1.16下载&#xff1a; https://github.com/kergoth/tslib/releases/download/1.16/t…

Rocksdb 通过ingestfile 来支持高效的离线数据导入

文章目录前言使用方式实现原理总结前言 很多时候&#xff0c;我们使用数据库时会有离线向数据库导入数据的需求。比如大量用户在本地的一些离线数据&#xff0c;想要将这一些数据导入到已有的数据库中&#xff1b;或者说NewSQL场景中部分机器离线&#xff0c;重新上线之后的数…

Java项目:企业人事管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能介绍&#xff1a;员工管理&#xff0c;用户管理&#xff0c;部门管理&#xff0c;文档管理, 职位管理等等。 二、项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 mysql Eclispe (I…

XCODE 6.1.1 配置GLFW

最近在学习opengl的相关知识。第一件事就是配环境(好烦躁)。了解了一下os x下的OpenGL开源库&#xff0c;主要有几个&#xff1a;GLUT&#xff0c;freeglut&#xff0c;GLFW等。关于其详细的介绍可以参考opengl网站(https://www.opengl.org/wiki/Related_toolkits_and_APIs)。由…

SpringCloud远程调用为啥要采用HTTP,而不是RPC?

通俗的说法就是:比如说现在有两台服务器A和B,一个应用部署在A服务器上,另一个应用部署在B服务器上,如果A应用想要调用B应用提供的方法,由于他们不在一台机器下,也就是说它们不在一个JVM内存空间中,是无法直接调用的,需要通过网络进行调用,那这个调用过程就叫做RPC。建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket ,套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

vs快捷键及常用设置(vs2012版)

vs快捷键&#xff1a; 1、ctrlf F是Find的简写&#xff0c;意为查找。在vs工具中按此快捷键&#xff0c;可以查看相关的关键词。比如查找哪些页面引用了某个类等。再配合查找范围&#xff08;整个解决方案、当前项目、当前文档等&#xff09;&#xff0c;可以快速的找到问题所在…

python_day10

小甲鱼python学习笔记 爬虫之正则表达式 1.入门&#xff08;要import re&#xff09; 正则表达式中查找示例&#xff1a; >>> import re >>> re.search(rFishC,I love FishC.com) <re.Match object; span(7, 12), matchFishC> >>> #单纯的这种…

Graphics2D API:Canvas操作

在中已经介绍了Canvas基本的绘图方法,本篇介绍一些基本的画布操作.注意:1、画布操作针对的是画布,而不是画布上的图形2、画布变换、裁剪影响后续图形的绘制,对之前已经绘制过的内容没有影响。