存储引擎 K/V 分离下的index回写问题
前言
近期在做on nvme hash引擎相关的事情,对于非全序的数据集的存储需求,相比于我们传统的LSM或者B-tree的数据结构来说 能够减少很多维护全序上的计算/存储资源。当然我们要保证hash场景下的高写入能力,append-only 还是比较友好的选择。
像 Riak的bitcask 基于索引都在内存的hash引擎这种,在后期的针对data-file 的merge 完成之后会将新的key-value-index回填到内存中的hash索引中,这个过程在实际的生产环境对性能有多大的影响还不太好确定。但是,很明显的一点是正确的hash引擎索引在高并发场景中的更新是需要加锁的。而一旦有了排他锁,也就意味着CPU的独占,这个时候用户的读取和插入 就会和merge 之后的index回填发生锁的竞争,从而影响引擎的外部性能。
而同样的问题在以 Wisckey 为首的 LSM-tree key-value 分离策略中尤为明显,包括Titan, rocksdb的BlobDB,BadgerDB 都会面临这样的问题,他们在compaction 之后的回填 大value-index 还需要产生I/O操作,这个代价可能会更高,那他们是怎么解决这个问题的呢?
探索他们的解决办法不一定完全能够借鉴到hash 引擎的实现中,不过是可以提供一个解决思路。
Titan 的回写策略
关于Titan的 GC 策略介绍可以参考:Titan GC策略实现
Titan 是 pingcap 早期基于wisckey 做出来的key-value 分离存储引擎,可以作为rocksdb 的一个插件来使用。
它的解决办法是提供一个可配置项gc_merge_rewrite
:
- 关闭:会在GC 过程中将key-value写入新的blobfile 之后,通过正常的Write with Callback + Put 接口回写blob-index到lsm-tree。这个也就是默认回写的方式,Titan的Callback 是一个Get操作,在写入之前会先尝试读一下这个key 是否在lsm-tree中,如果不在就不会写入了。而且会将新的key + key-index 完全写入。
- 开启:则是一个回写产生的性能问题和读性能之间的一个trade-off。开启之后直接写入 一个
kMergeType
blob-index,这种情况下不需要去执行Callback
了,而是直接写入Merge操作,后续通过compaction 进行 key的blob-index的合并 或者 读请求命中这个key的时候会进行merge。merge请求本省不会携带原本大小的value,所以不会产生较大的写放大,只是在读的时候需要将当前key之前的merge都进行合并,对读性能可能有较大的影响。
相关的实现代码可以参考:
void BlobGCJob::BatchWriteNewIndices(BlobFileBuilder::OutContexts& contexts,Status* s) {...// 关闭merge,调用默认的写入方式if (!gc_merge_rewrite_) {merge_blob_index.EncodeToBase(&index_entry);// Store WriteBatch for rewriting new Key-Index pairs to LSM// 在这个策略下,rewrite_batches_ 最后的消费是通过 Rocksdb::WriteWithCallback实现// 的,在写入的时候会执行 Callback,里面会去查一下key是否存在。GarbageCollectionWriteCallback callback(cfh, ikey.user_key.ToString(),std::move(original_index));callback.value = index_entry;rewrite_batches_.emplace_back(std::make_pair(WriteBatch(), std::move(callback)));auto& wb = rewrite_batches_.back().first;*s = WriteBatchInternal::PutBlobIndex(&wb, cfh->GetID(), ikey.user_key,index_entry);} else { // 开启,rewrite_batches_without_callback_ 的消费过程是 直接写入Merge 类型的keymerge_blob_index.EncodeTo(&index_entry);rewrite_batches_without_callback_.emplace_back(std::make_pair(WriteBatch(), original_index.blob_handle.size));auto& wb = rewrite_batches_without_callback_.back().first;*s = WriteBatchInternal::Merge(&wb, cfh->GetID(), ikey.user_key,index_entry);}...
}
最终对两个数据结构的消费逻辑统一是在RewriteValidKeyToLSM
函数中。
BlobDB 的回写策略
BlobDB 的大体特性可以参考BlobDB 特性及性能测试结果。
因为BlobDB 新版本是社区比较推荐的一个k/v分离的稳定版本,基本的Rocksdb特性都已经支持了,包括trasaction/checkpoint/backup 等这一些不常用但很重要的功能都已经支持了。除了像merge/ingest等更为偏的能力暂时还不支持。
BlobDB的在GC上的一个考虑就不想因为后续频繁的回写处理影响正常的请求。
如果开启了GC enable_blob_garbage_collection
:
- 则在compaction过程中,迭代器 处理 类型
kTypeBlobIndex
的key时会进入到GarbageCollectBlobIfNeeded
,因为分离存储的时候lsm中存放的value 是key-index,即这个value能够索引的到blobfile的一个index。 - 确认当前blob能够参与GC 且 当前key需要被保留,则根据key-index 读取到blob_value 并 直接写入到新的blob-file中。并且将新的blob-index 作为当前key的value,提取出来。
- key 和 新的key-index 继续参与compaction后续的落盘行为。
主体第二步,也就是想要GC的话会在compaction过程中直接将过期的blob-value直接回收,compaction完成之后 lsm的sst 以及 blob都会被更新到,只需要维护后续的旧的blob回收即可。
代码实现如下:
- compaciton过程中(迭代器按key处理阶段) 调度GC
void CompactionIterator::PrepareOutput() {if (valid_) {if (ikey_.type == kTypeValue) {ExtractLargeValueIfNeeded();} else if (ikey_.type == kTypeBlobIndex) {// 调度GCGarbageCollectBlobIfNeeded();}... }
- 按照上面的步骤进行处理:
void CompactionIterator::GarbageCollectBlobIfNeeded() {...// 开启GCif (compaction_->enable_blob_garbage_collection()) {BlobIndex blob_index;{// 1. 获取blobindexconst Status s = blob_index.DecodeFrom(value_);if (!s.ok()) {status_ = s;valid_ = falsereturn;}}if (blob_index.IsInlined() || blob_index.HasTTL()) {status_ = Status::Corruption("Unexpected TTL/inlined blob index");valid_ = false;return;}// 2. 确认当前blob-index 允许参与GCif (blob_index.file_number() >=blob_garbage_collection_cutoff_file_number_) {return;}const Version* const version = compaction_->input_version();assert(version);{// 3. 解析读出来当前blob数据const Status s =version->GetBlob(ReadOptions(), user_key(), blob_index, &blob_value_);if (!s.ok()) {status_ = s;valid_ = false;return;}}value_ = blob_value_;// 4. 将读出来的blob数据写入到新的blob file,并构造新的 value-index 作为当前lsm-tree// 即将存储的key的value.if (ExtractLargeValueIfNeededImpl()) {return;}ikey_.type = kTypeValue;current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);return;}... }
问题1: compaction过程中读取大value和我们rocksdb 未k/v分离 场景下的读取有什么区别?
这里的读取只会是保留的key的real value,对于那一些要清理的key,则不会读取。为了避免业务峰值触发大量的compaciton以及 GC的读取,GC的触发可以通过SetOption
来动态调整。
问题2: 相比于 Titan GC 调度的优劣?
个人觉得,BlobDB的GC调度更为简洁高效低成本。
来,我们对比一下GC过程中产生I/O的步骤:
- TitanDB,通过EventListener 在compaction过程中拿到需要参与GC 的blobfile 集合,compaction完成之后 对待GC的 blobfile 进行iter 迭代。
a. 拿到每一个key 去 LSM 点查 是否存在。
b. 存在,则读取其所在blobfile 的 大value,写入到新的blobfile
c. 写入key 以及 新的value-index 到 LSM -tree(伴随着后续的逐层compaction,或者 merge的合并)。 - BlobDB,直接在compaction过程中一起调度GC。
a. 不需要反查,compaction过程中知道这个key是要keep还是要skip,直接对keep下来的key 读取blobfile的大value,写入到新的blobfile.
b. 继续compaction时直接将当前要keep下来的key 以及 新的 value-index 写入 lsm即可。
可以看到,blobdb 的第二个步骤是正常的compaction写入逻辑,相比于Titan来说,其实也就只进行了 Titan有效的第二步,少了第一步的点查和第三步的回写。除此之外,Rocksdb的可调性更高一些,可以针对必要的GC时的大value读写进行控制,允许动态调整,从而最大程度得减少了GC对上层请求的性能影响。
具体在 GC 过程中的性能差异会在后续补充上。
BadgerDB 的回写策略
Badger 作为 dGraph 社区备受 cgo 折磨之后推出的自研k/v 分离存储引擎,在go 语言中还是非常受欢迎的。
本文仅讨论BadgerDB 在k/v 分离场景的回写策略,对于其测试优于Rocksdb(rocksdb的默认参数) 以及 其相比于Rocksdb 的其他优秀设计暂不展开讨论。
Badger的大value是存放在value log文件中,它很聪明的一点是GC 接口只交给用户来调度,而不是自己内部自主触发,这样的责任划分就非常清晰了,用户自己选择开启关闭GC,来自己承担GC引入的读写问题,真是机智。
当然BadgerDB 这里的GC回写并没有看到太亮眼的设计,就是在对 value log 进行GC的时候和Titan不开启gc_merge_rewrite
逻辑差不多。
- 选择好了待GC的value-log文件,先从lsm中尝试读取key,存在则需要将value写入到新的value log中。
- 完成写入新的value-log之后,会将最终的key, value-index 更新到lsm-tree中。
回写源代码基本在RunValueLogGC
函数中的rewrite
处理逻辑中,感兴趣的可以看一下。
总结
可以看到为了解决在LSM-tree中大value 不随着compaction一起调度而造成的性能问题,大家可谓是煞费苦心。Titan 尝试做了一些优化,但整体来看还是不尽人意。Rocksdb 的 Blobdb 还是更加成熟,可以说是考虑得很全面了,从实现上看确实有很明显的效果。而BadgerDB的做法更为彻底,这个问题我们不管,交给用户自由调度,因为用户大多数情况还是知道自己的业务什么时候处于高峰,什么时候处于低谷,产生的I/O竞争问题那是你们自己调度造成的,自己解决哈,🐂。
而回到最初的我们 hash-engine 的 hash-index回写问题,其实可以考虑借鉴一下 BlobDB的做法,不过需要接口做的更灵活一些。
相关文章:

经典贪心法:时间序列问题及其全局最优性证明
贪心算法是指在对问题求解时,总做出在当前看来是最好的选择。也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解。一旦贪心算法求出了一个可行解,就要确定这个算法是否找到了最优解。为此,要么证…

Java项目:在线水果商城系统(java+JSP+Spring+SpringMVC +MyBatis+html+mysql)
源码获取:博客首页 "资源" 里下载! 一、项目简述 功能: 区分为管理员用户和普通用户,普通用户:用户注册登录,首页水果展示,商品分类展示,购物车添加,下单&…

曲苑杂坛--收缩数据库文件
很多人在删除大量数据后收缩数据库,却发现没法收缩到预期效果。 由于使用DBCC SHRINKFILE来收缩数据文件时,是针对数据区来收缩,因此可以先使用DBCC SHOWFILESTATS来查看文件中未使用的分区数(TotalExtents-UsedExtents),如果删除…

python字典去重
今天实习的web大表哥说帮我看环境不过前提是要我帮他写个python合并列表的demo,大概思路就是利用zip库进行keys和values的遍历,然后在输出就行key1{name1:小明,name2:小红} key2{小明:[men,20],小红:[women,30]} for k,v in zip(key1.values(),key1.keys()):for i, …

关于 线程模型中经常使用的 __sync_fetch_and_add 原子操作的性能
最近从 kvell 这篇论文中看到一些单机存储引擎的优秀设计,底层存储硬件性能在不远的未来可能不再是主要的性能瓶颈,反而高并发下的CPU可能是软件性能的主要限制。像BPS/AEP/Optane-SSD 等Intel 推出的硬件存储栈已经能够在延时上接近DRAM的量级ÿ…

R 语言爬虫 之 cnblog博文爬取
Cnbolg Crawl a). 加载用到的R包 ##library packages needed in this case library(proto) library(gsubfn) ## Warning in doTryCatch(return(expr), name, parentenv, handler): 无法载入共享目标对象‘/Library/Frameworks/R.framework/Resources/modules//R_X11.so’&#…

Java项目:宿舍管理系统(java+jsp+SSM+Spring+mysql)
源码获取:博客首页 "资源" 里下载! 一、项目简述 功能:包括学生管理,班级管理,宿舍管理,人员信息维 护。维修登记,卫生管理,访客管理等等。 二、项目运行 环境配置&am…

项目管理5大过程组,42个过程一句话讲解
2019独角兽企业重金招聘Python工程师标准>>> 启动过程组:(1)制定项目章程:诞生项目,并为项目经理“正名”;(2)识别干系人:搞清楚谁与项目相关;规划…
Android Q 变更和新特性
安全和隐私变更 隐私保护是Android Q重要的主题之一,Android Q带来了一系列增强用户隐私保护的变更。 1 应用文件存储空间限制 应用访问限制是Android Q影响最大变更之一。在Android Q系统中,应用只可以通过路径读取自己应用沙箱内的文件,如果…

KVell 单机k/v引擎:用最少的CPU 来调度Nvme的极致性能
文章目录前言KVell背景业界引擎使用Nvme的问题CPU 会是 LSM-kv 存储的瓶颈CPU 也会是 Btree-kv 存储的瓶颈KVell 设计亮点 及 总体架构实现KVell 设计亮点1. Share nothing2. Do not sorted on disk, but keep indexes in memory3. Aim for fewer syscalls , not for sequentia…

android录像增加时间记录(源码里修改)
需要做一个功能,录像和播放时都显示录时的时间,参考文章链接找不到了,不好意思,这里记录一下,防止下次找不到了。另一篇关于源码录像的流程请参考 http://www.verydemo.com/demo_c131_i79000.html 在源码CameraSource.…

Java项目:在线旅游系统(java+jsp+SSM+Spring+mysql+maven)
源码获取:博客首页 "资源" 里下载! 一、项目简述 功能:用户的登录注册,旅游景点的展示,旅游预订,收藏,购买,以及酒店住宿留言等等,后台管理员,订单…

混合式APP开发中中间件方案Rexsee
发现Rexsee时,他已经一年多没有更新过了,最后版本是2012年的。 他的实现思路是通过Android自带的Java - Javascript 桥机制,在WebView中的JavaScript同Java进行通信,而这样的话即Javascript可以直接创建原生UI界面,以获…

vue 前端框架 (三)
VUE 生命周期 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script type"text/javascript" src"js/vue.js"></script><link rel"stylesheet" type"te…

Rocksdb 的 MergeOperator 简单使用记录
本篇仅仅是一个记录 MergeOperator 的使用方式。 Rocksdb 使用MergeOperator 来代替Update 场景中的读改写操作,即用户的一个Update 操作需要调用rocksdb的 Get Put 接口才能完成。 而这种情况下会引入一些额外的读写放大,对于支持SQL这种update 频繁的…

Java项目:考试系统Java基础Gui(java+Gui)
源码获取:博客首页 "资源" 里下载! 功能简介: 所属课程、题目内容、题目选项、题目答案、题目等级、学生管理、试卷管理、题目管理、时间控制 服务页面: public class ServerClient extends javax.swing.JFrame {/** …

软件工程需求设计说明书
Java即时通聊天程序 设计需求说明书 专业班级: 计本班1202班 项目组成员: 杨宗坤 刘瑞 满亚洲 指导教师: 张利峰 开始日期: 完成日期: 编写目的: 本说明书是在充分理解系统需求分析…

Nagios 安装文档
安装前的装备工作(1)解决安装Nagios的依赖关系:Nagios基本组件的运行依赖于httpd、gcc和gd。可以通过以下命令来检查nagios所依赖的rpm包是否已经安装完成:#yum -y install httpd gcc glibc glibc-common *gd* php php-mysql mysql mysql-server --skip-…

Comprehensive Guide to build a Recommendation Engine from scratch (in Python) / 从0开始搭建推荐系统...
https://www.analyticsvidhya.com/blog/2018/06/comprehensive-guide-recommendation-engine-python/, 一篇详细的入门级的推荐系统的文章,这篇文章内容详实,格式漂亮,推荐给大家. 下面是翻译,翻译关注的是意思&#x…

关于std::string 在 并发场景下 __grow_by_and_replace free was not allocated 的异常问题
使用string时发现了一些坑。 我们知道stl 容器并不是线程安全的,所以在使用它们的过程中往往需要一些同步机制来保证并发场景下的同步更新。 应该踩的坑还是一个不拉的踩了进去,所以还是记录一下吧。 string作为一个容器,随着我们的append 或…

Java项目:银行管理系统+文档Java基础Gui(java+Gui)
源码获取:博客首页 "资源" 里下载! 功能介绍: 登录、打印、取款、改密、转账、查询、挂失、存款、退卡 服务模块: public class atmFrame extends JFrame {private JPanel contentPane;private user user; // private…

ie旋转滤镜Matrix
旋转一个元素算是一个比较常见的需求了吧,在支持CSS3的浏览器中可以使用transform很容易地实现,这里有介绍:http://www.css88.com/archives/2168,这里有演示http://www.css88.com/tool/css3Preview/Transform.html,就不…

音频(3):iPod Library Access Programming Guide:Introduction
NextIntroduction介绍iPod库访问(iPod Library Access)让应用程序可以播放用户的歌曲、有声书、和播客。这个API设计使得基本播放变得非常简单,同时也支持高级的搜索和播放控制功能。iPod library access 通过打开iOS允许的音乐相关的广阔范围…

【2019/4/30】周进度报告
冲刺可以推迟了,但这不妨碍知识储备(另外这周看了看梦断代码,感觉还是很有意思的一本书)。 第七周所花时间约9个小时代码量700多行,主要是阅读代码为主(框架内代码)博客量1篇了解到的知识点 1.y…

关于 智能指针 的线程安全问题
先说结论,智能指针都是非线程安全的。 多线程调度智能指针 这里案例使用的是shared_ptr,其他的unique_ptr或者weak_ptr的结果都是类似的,如下多线程调度代码: #include <memory> #include <thread> #include <v…

Java项目:无库版商品管理系统(java+Gui+文档)
源码获取:博客首页 "资源" 里下载! 功能介绍: 添加商品、修改商品、删除商品、进货出货、查看流水、注册 登录业务处理: public class LoginView extends JFrame implements ComponentListener{private JPanel center…

LTE QCI分类 QoS
http://blog.163.com/gzf_lte/blog/static/20840310620130140057204/ http://blog.163.com/gzf_lte/blog/static/208403106201301403652527/ http://blog.sina.com.cn/u/1731932381 lte2010 QCI (QoS Class Identifier)同时应用于GBR和Non-GBR承载。一个QCI是一个值࿰…

CSS 单行溢出文本只显示部分内容
.cc-item div { width:175px; text-overflow:clip; //该属性适用于IE6,IE7 max-width:175px; //该属性适用于IE8,FF,谷歌}

Audio声音
转载于:https://www.cnblogs.com/kubll/p/10799187.html

Rocksdb Ribbon Filter : 结合 XOR-filter 以及 高斯消元算法 实现的 高效filter
文章目录前言XOR-filter 实现原理xor filter 的构造原理xor filter 构造总结XOR-filter 和 ADD-filter对比XOR-filter 在计算上的优化Ribbon filter高斯消元法总结参考前言 还是起源于前几天的Rocksdb meetup,其中Peter C. Dillinger 这位大佬分享了自己为rocksdb实…