Tair LDB基于Prefixkey的范围查找性能优化项目之后续问题解决记录

项目是按照“Tair LDB基于Prefixkey的范围查找性能优化项目提议方案”的步骤一步步完成的,目前方案中提出的三个重点问题已经全部解决,如下所示:



虽然已经使用prefix bloomfilter对prefix key进行过滤来提升范围查找的性能,经过实验测试也达到了一定的效果。但是这个问题的解决方案又导致了新问题的产生,即产生了下面两个问题:

问题1. ldb_instance.cpp(客户端和ldb底层交互的接口)中还有类似begin_scan(扫描某一个bucket)的接口。这些scan的key是没有prefixkey的,且编码信息也不完整(并非是前几次文章所说的前面9字节元信息和area信息编码+后面8字节sequence和valueType编码)。如果这时候也按之前的方法提取prefix key必然会产生错误,而且这种情况也不需要prefix key和bloomfilter,因此需要这块需要考虑。

问题2. delete的时候也需要带入prefix的相关信息。 如果不带,那么在get_range的时候,可能会出现已经delete了,但是依然被扫描出来了。 这个怎么理解呢?一开始我也没搞明白,因为觉得delete操作是根据delete标记来判断是否查找到的数据是否是delete过的,如果是就不取就是了,跟prefix key没什么关系。后来在导师的指导下才彻底搞懂,原来自己忽略了一个非常重要的事实:delete和put相同的某个key可能存储在sst不同的block上,甚至在不同的sst文件里。下面是导师的指导:
1. bloomfilter是在sst的block中的。
2. prefix_put时,会在某个sst(比如B)中写入该prefixkey bloomfilter
3. prefix_remove的时候(即delete时),该delete key在其他sst的可能性非常大。 而其它sst中是没有该prefix key的bloomfilter。 而其它sst(比如A)一般会在低层level
4. get_range中用merge iterator遍历时,遍历到A时,先判断prefixkey bloomfilter,结果为无(因为A的prefix bloomfilter中没有存储delete key的prefix,因此这次判断实际上是错误的),则遍历B,判断prefixkey bloomfilter,结果为有。最终结果暴露了本该删除的数据



问题来了就要解决,下面是在导师指导及自己思考下提供的解决方案,方案不唯一,这里提供一个参考。

问题1解决方案

对于类似begin_scan里的key没有prefix的情况,需要在prefix bloomfilter过滤之前进行判断,如果是这种情况的key则不需要经过bloomfilter。对于这个问题,有两种解决方案:

方法1:通过每次操作的option传入参数,这里的option指的是ReadOptions,在里面添加参数bool use_prefix_bloom;默认值是true,然后在begin_scan等方法中在扫描之前设置该参数为false,下面是添加了use_prefix_bloom参数的ReadOptions:

// Options that control read operations
struct ReadOptions {
  // If true, all data read from underlying storage will be
  // verified against corresponding checksums.
  // Default: false
  bool verify_checksums;

  // Should the data read for this iteration be cached in memory?
  // Callers may wish to set this field to false for bulk scans.
  // Default: true
  bool fill_cache;

  // If "snapshot" is non-NULL, read as of the supplied snapshot
  // (which must belong to the DB that is being read and which must
  // not have been released).  If "snapshot" is NULL, use an impliicit
  // snapshot of the state at the beginning of this read operation.
  // Default: NULL
  const Snapshot* snapshot;

  bool use_prefix_bloom;

  ReadOptions()
      : verify_checksums(false),
        fill_cache(true),
        snapshot(NULL),
        use_prefix_bloom(true){
  }
};

然后在begin_scan中设置use_prefix_bloom参数:

leveldb::ReadOptions scan_read_options = read_options_;
scan_read_options.fill_cache = false; // not fill cache
scan_read_options.use_prefix_bloom = false; // not use prefix bloomfilter

最后在使用prefix bloomfilter进行过滤之前进行判断即可,如下:

if (options_.use_prefix_bloom) {
    // 使用prefix bloomfilter进行过滤
}

具体怎么使用prefix bloomfilter进行过滤可以查看这篇文章:
Tair LDB基于Prefixkey的范围查找性能优化项目之如何使用prefix bloomfilter进行过滤

方法2:第二种方法是一种取巧的方法,前面说过key的编码格式,含有prefix_size的key或一般的key在经过编码之后至少不小于7(元信息) + 2(area信息) + 8(sequence/valueType信息)个字节的编码信息,而类似begin_scan里的key编码之后没有这么多信息,可以说是特殊的key,因此我可以通过判断key的大小是否超过了7 + 2 + 8个字节来决定是否使用prefix bloomfilter,如下:

if (target.size() > 7 + 2 + 8) {
    // 使用prefix bloomfilter进行过滤
}

两种方案经过测试都是正确的,导师建议“用确定的方式去实现(即第一种方法)而不是约定俗成的方式(即第二种方案)”,因此源码中采用第一种方案实现。

问题2解决方案

问题2需要在delete的时候也需要带入prefix的信息,这个问题解决的时间比较久,期间也理解错了一些东西,可能之前对Delete接口的研究不够深入。目前我的方案是这样的:

(1)添加了一个ValueType:kTypeDeletionWithPrefix,表示delete的时候携带Prefix key信息。之前的valueType有三个:kTypeValue ,kTypeDeletion,kTypeDeletionWithTailer,新的Type需要支持delete时候带入value,value中含有Prefix key信息。

(2)修改或添加DB存储层关于delete的一些函数
由于delete的过程和put的过程一样,只是valueType不同。首先都要将数据写入memtable,写入过程中调用的一系列delete基础实现需要修改。但为了不改动原有delete相关函数的实现,我都是通过新增另外的delete函数来实现带value的delete操作,也就是说所有实现Delete的地方,我都要增加相应的支持带value的Delete实现。

(3)修改客户端
客户端Delete操作的底层实现是LdbInstance::remove(),该函数首先会判断是否需要先从DB中查询key所对应的value,看DB中是否存在,如果存在就将查询到的value封装成LdbItem,并调用LdbInstance::do_remove()完成,如下:

 if ((db_version_care_ && version_care) || mtime_care)
 {
   rc = do_get(ldb_key, db_value, true/* get from cache */,
              false/* not fill cache */, false/* not update cache stat */);
   ...

因为我们delete时候需要value信息,而这里又给我们查询到了,那么我们就可以使用这个value了。这样真的可以吗?
嗯,仔细的人会说不可以,因为这里不一定会查询DB得到value,因为在查询之前有个if判断,既然是判断就有else的情况,如果是else不就不会从DB中查了吗?那还怎么得到value?有人可能会说直接把do_get()这句查询DB放到if条件外面不就行了,这样就保证每次都能查找到value了。嗯,这样的确可以,但仔细想一下,这样好吗?DB的底层查询操作是比较昂贵的操作,这涉及到效率和性能问题。再仔细一想,有必要获取完整的value吗?delete操作所需的value只要包含prefix size信息即可,并不需要完整的value。由于这里prefix size信息是已知的,而通过前面的分析知道在从value中提取prefix size信息时只需要用到value前面的ldb_meta_item部分,因此我们完全可以根据ldb_meta_item和prefix_size信息构造一个无真实value信息的只含有meta数据(内含prefix_size)的ldb_item,从而不需要读取DB,减少了开销。

  // construct a ldb item only contains meta data
  LdbItem ldb_meta_item;
  ldb_meta_item.meta().base_.meta_version_ = META_VER_PREFIX;
  ldb_meta_item.meta().base_.flag_ = TAIR_ITEM_FLAG_NEWMETA;
  ldb_meta_item.set_prefix_size(key.get_prefix_size());
  ldb_meta_item.set("", 1);

然后再调用具体的do_remove()方法完成delete操作,源码中do_remove()方法没有携带value信息,这样在接下来调用db的Delete操作也不能带value信息。因此我这里修改了do_remove()函数,使其带有参数value,方便后面操作。

LdbInstance::do_remove(LdbKey& ldb_key, LdbItem& ldb_item, bool synced,
    tair::common::entry_tailer* tailer)

最后在调用db底层的Delete操作之前判断ldb_item里是否设置了key的prefix_size信息,如果设置了则调用我们添加的带value支持的Delete操作,如果没有设置(即prefix size为0)则继续调用原来的Delete操作。

if(ldb_item.prefix_size() > 0)
{
  status = db_->DeleteWithPrefix(write_options_, leveldb::Slice(ldb_key.data(), ldb_key.size()),
     leveldb::Slice(ldb_item.data(), ldb_item.size()), synced);
}
else
{
  // 继续调用原来的Delete操作
}

到此Delete问题就基本解决了。

后记

整个项目下来,问题多多,但有问题不是难事,有问题没解决方案才是真正的难题。好在这个项目的几个提案步骤及后续产生的问题都基本解决,也许解决方案不是最好的,但至少是解决了,而且解决问题的过程收获了很多东西,不仅是技术上的,在项目整体架构上也有了一个更清晰的认识。如果后续能想到更好的解决方案或经测试发现新的问题,我都会去及时加以解决。

时间: 2024-08-25 11:16:00

Tair LDB基于Prefixkey的范围查找性能优化项目之后续问题解决记录的相关文章

Tair LDB基于Prefixkey的范围查找性能优化项目测试及完成总结报告

项目这周就截止了,这算是我第一个有导师指导的真正意义上的C++项目,项目基本完成,想要实现的功能也已经实现,并做了大量的性能测试.不过这对于业界来说,可能完成的还不够成熟,还有许多待改进的地方,还不能马上投入使用,还需要进行严格的考验,毕竟tair的应用场景太重要了,不容一丝疏忽.但于我个人而言,帮助还是挺大的,不仅是多了一次有价值的项目经验,更是学到了一些项目之外的东西,比如计划的重要性,惰性的控制,时间的分配管理(找工作与项目进度产生冲突)等.好了,不多说了,在这最后一篇总结报告里首先给出性

Tair LDB基于Prefixkey的范围查找性能优化项目之如何提取key的prefix_size信息

New Document/* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */ /* Author: Nicolas Hery - http://nicolashery.com */ /* Version: b13fe65ca28d2e568c6ed5d7f06581183df8f2ff */ /* Source: https://github.com/nicolahery/markdownpad-github */ /*

Tair LDB基于Prefixkey的范围查找性能优化项目之如何建立prefix bloomfilter

项目是按照"Tair LDB基于Prefixkey的范围查找性能优化项目提议方案"的步骤一步步完成的,上次解决了如何获取key的prefix_size问题"Tair LDB基于Prefixkey的范围查找性能优化项目之如何提取key的prefix_size".今天来继续解决第二个问题.在提案中有以下描述: 提取到prefix_size信息后,我们对所有的keys实现prefix bloomfilter,为了实现简单,我们可以在原有的filter block里加入新的

Tair LDB基于Prefixkey的范围查找性能优化项目之如何使用prefix bloomfilter进行过滤

项目是按照"Tair LDB基于Prefixkey的范围查找性能优化项目提议方案"的步骤一步步完成的,目前已经解决了前面两个问题: 如何获取key的prefix_size问题"Tair LDB基于Prefixkey的范围查找性能优化项目之如何提取key的prefix_size". 如何建立prefix bloomfilter"Tair LDB基于Prefixkey的范围查找性能优化项目之如何建立prefix bloomfilter" 今天来继续解

Tair LDB基于Prefixkey的范围查找性能优化项目中期总结

"Tair LDB基于Prefixkey的范围查找性能优化"这个项目刚好进行了一个月,这一个月主要是熟悉项目.掌握项目和提出设计方案的过程,下面从几个方面总结下个人在该项目上所做的工作及自己的个人所得所感. 项目工作简单总结 下面是对阶段性的成果进行总结,并附有每个阶段的总结报告. 1. 项目实施计划的确定 不管什么类型的项目(大.小,难.易),在项目开展之前都应该有个可实施的计划,一方面能够确保项目的进度,另一方面也能防止有些人三天打鱼两天晒网的心态.在导师的细心指导下,我们确定了下

Tair LDB基于Prefixkey的范围查找性能优化项目提议方案

基于prefix bloomfilter的过滤思想和get_range接口数据的特点,在导师的指导下,提出如下的简单方案,对get_range接口的范围查找过程进行优化,使得能够根据prefix进行过滤,减少无效的磁盘IO. 待优化接口 int get_range(int area, const data_entry &pkey, const data_entry &start_key, const data_entry &end_key, int offset, int limi

手机淘宝 521 性能优化项目揭秘

又是一年双十一,亿万用户都会在这一天打开手机淘宝,高兴地在会场页面不断浏览,面对琳琅满目的商品图片,抢着添加购物车,下单付款.为了让用户更顺畅更方便地实现这一切,做到“如丝般顺滑”,双十一前夕手机淘宝成立了“521”(我爱你)性能优化项目,在日常优化基础之上进行三个方面的专项优化攻关,分别是1)H5页面的一秒法则:2)启动时间和页面帧率提升20%:3)Android内存占用降低50%.优化过程中遇到的困难,思考后找寻的方案,实施后提取的经验都会在下面详细地介绍给读者. 第一章 一秒法则的实现 “

性能优化项目的总结

在性能优化项目中,我只是一个协助参与的角色,但也正好给了我从外部参看项目运作的机会,需要优化的系统已经是运行了6年的老系统,数据从来没有做过分离,涉及全库查询等致命的优化问题.另外本次项目的业主也希望对优化工作进行指导,造成走了不少弯路,同时由于垂直数据库技术不足,从外部找了合作伙伴进行深入性能优化研究.总之这个项目虽小,但具备了复杂项目的各方面的内容,我也将会对这个项目进行初步的分析. 基础方向 SQL优化 首先要做的就是找出执行慢的SQL或业务模块,调查SQL的业务逻辑是否存在可优化的空间.

magento性能优化:禁用访问日志记录

系统记录所有访问数据到数据库里面,包括访客及机器的访问(例如搜索引擎爬虫),机器访问通常会占大部分,记录大量数据,这样会生产不小的额外服务器性能开销,包括web服务.mysql数据库服务:看需要,这部分数据可以不要. 负责记录访问日志的数据库表是:log_customer.log_quote.log_summary.log_summary_type.log_url.log_url_info.log_visitor.log_visitor_info.log_visitor_online.repor