前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TX-Rocks Sum性能调优之旅

TX-Rocks Sum性能调优之旅

作者头像
腾讯数据库技术
发布2019-01-08 20:21:09
8710
发布2019-01-08 20:21:09
举报

提示:公众号展示代码会自动折行,建议横屏阅读

TXRocks是TXSQL适配RocksDB的版本,基于Facebook开源的MySQL进行了深度定制和优化。相对于当前线上常用的InnoDB引擎,RocksDB的主要优势是空间占用少。主要原因有两点,第一:RocksDB的数据页是压缩后append方式存储,而InnoDB的数据默认是先凑齐16K,然后再压缩对齐,对齐会造成额外的空间占用;第二:InnoDB的B+树的页面本身也有空洞。一般情况下,RocksDB的空间占用大概是压缩InnoDB的1/2左右。而且数据的冗余越多,InnoDB的补齐开销就越大,RocksDB的优势就越明显。

最近一个内部项目打算上线TXRocks,这个业务数据冗余较多,而且大部分都是数字,因此压缩比很高。经过内部测试TXRocks的空间占用只有带压缩的InnoDB的1/10。但是,测试也发现TXRocks的sum性能较差,只有InnoDB的60%左右(InnoDB耗时38.29s, TX-Rocks耗时 62.8s)。

经过分析,性能差的原因主要有三点:(1)server层遍逐条遍历记录的代价较大; (2)引擎层对遍历的每条记录的所有列都进行了解析,由于sum操作只针对少数列,因此这里对操作不涉及的列进行解析都是没有必要的;(3)server层单线程处理聚合请求,并发不够。针对这几点原因,我们从聚合操作下推、优化单条记录的处理开销、多线程并发三个方面进行优化。经过优化,sum查询时延最低降到1.74s,只有InnoDB的5%不到。下面就详细介绍问题现象及优化过程。

1. 问题剖析

1.1 perf 火焰图

部分火焰图如下:

通过火焰图发现CPU时间大部份花在三个地方:

(1) myrocks::ha_rocksdb:: convert_record_from_storage_format

(2) rocksdb::DBIter::Next

(3) sub_select到rnd_next_with_direction的函数调用开销。 

1.2 top

Top命令查看Mysqld线程占用一个核,CPU消耗100%。

1.3 瓶颈及优化方案总结 第二个现象说明当前是单线程进行CPU消耗型操作;而第一个现象则说明了当前CPU的主要消耗点。因此,我们明确了几个主要瓶颈点及优化方案: (1) convert_record_from_storage_format:sum操作仅需要解析操作所涉及的列,而当前的流程是解析了所有的列。这里可以通过只解析需要的列来优化。 (2) Rocksdb内部迭代器Next: 操作涉及到Rocksdb底层迭代器的固有机制,暂不优化。 (3) SQL层循环迭代开销大:sum操作下推的方式来优化。 (4) 单线程操作导致并发不够:多线程并发聚合。

2.优化点一:sum操作下推

针对SQL层循环迭代开销大的问题,我们决定采用sum操作从SQL层下推到引擎层的方式解决,目前这种优化只针对整型。 2.1 下推的实现方式 从上面的堆栈看,当前的sum执行方式为在sub_select函数里不停的通过rr_sequential获取引擎层的记录并计算。rr_sequential的调用层次依次为rr_sequential->handler::ha_rnd_next->ha_rocksdb::rnd_next->……。因此我们的做法是在SQL侧对下推条件进行判断,如果判断满足条件则在handler中设置相应标志位;在引擎的rnd_next中如果发觉设置了标志位,则遍历所有的列进行聚合运算。

2.2 SQL层sum操作下推的条件

(1) 带order by,group by, having, where, 涉及多表的操作不下推; (2) 非sum/count操作不下推; (3) 如果涉及多个field的不下推。 

2.3 引擎层rnd_next的下推操作处理逻辑

代码语言:javascript
复制
if (agg_sum_push_down)
{
  /*1.遍历整个表的数据*/
  for (;;) {
    ......
    scan_it->Next();

    /*2.遍历结束*/
    if (!scan_it->Valid()) {
      rc = HA_ERR_END_OF_FILE;
      break;
    }
    ......
    /*3.将需要的field从storage format中解压出来*/
    rc = convert_needed_filed_from_value(&value, value, field_is_null);
    ......

    /*4.sum结果溢出判断,如果溢出则结束本轮遍历,由SQL层发起下一轮遍历*/
    if (agg_sum_is_overflow(local_sum, value)) {
      rc = HA_ERR_ROCKSDB_STATUS_TRY_AGAIN;
      break;
    }

    /*5.进行sum*/
    local_sum += value;
  }
}
else
{
   获取一行记录返回;
}
  ......

3.优化点二:convert_record_from_storage_format函数优化

convert_record_from_storage_format的主要作用是将record从memcomparable格式转SQL层的记录格式。主要流程如下:

代码语言:javascript
复制
  ......
for (auto it = m_decoders_vect.begin(); it != m_decoders_vect.end(); it++) {
    const Rdb_field_encoder *const field_dec = it->m_field_enc;
    Field *const field = table->field[field_dec->m_field_index];

    if (isNull) {
      if (decode) {
        /* This sets the NULL-bit of this record */
        field->set_null();
        /*
          Besides that, set the field value to default value. CHECKSUM TABLE
          depends on this.
        */
        memcpy(field->ptr, table->s->default_values + field_offset,
               field->pack_length());
      }
    } else { /*解析非空列*/
      if (decode) {
        field->set_notnull();
      }

      if (field_dec->m_field_type == MYSQL_TYPE_BLOB) {
        err = convert_blob_from_storage_format(
            (my_core::Field_blob *) field, &reader, decode);
      } else if (field_dec->m_field_type == MYSQL_TYPE_VARCHAR) {
        err = convert_varchar_from_storage_format(
            (my_core::Field_varstring *) field, &reader, decode);
      } else {
        err = convert_field_from_storage_format(
            field, &reader, decode, field_dec->m_pack_length_in_rec);
      }
    }
  ......
  }
  ......

从上面的流程可以看到,解压记录的过程中对记录的所有field都进行了解析。但是我们的业务场景是对某一列进行sum操作,因为仅仅只涉及其中一列,没有必要对所有的field进行解析。因此,我们专门针对我们的场景做了优化,只解析需要的那一列。

4.优化点三:多线程并发

多线程并发最主要的是要解决数据的并发拆分问题,在讨论具体的拆分策略之前,我们首先要明确几点:

4.1 拆分对象内容的获取

由于MyRocks的多个索引共享一个Column Family参考1,其数据视图对应于Rocksdb的Version ,MyRocks及Rocksdb中并没有一个可以和索引相对应的数据视图,那么需要怎么获取待拆分索引的全部内容?所幸,MyRocks对索引中每条Record进行编码时都带上了indexid做前缀参考2,因此(indexid_0000, (indexid+1)_0000)的双开区间即可以表示某个column family中属于某个索引的全部数据,通过这个范围即可对应的version中过滤出需要的数据。

4.2 拆分的依据

基于什么信息进行拆分?怎么保证拆分的尽量均匀?这里有两种备选拆分策略: (1) 静态拆分。即假如需要拆分成4个线程,那么用(indexid_0000, indexid_0000_00], (indexid_0000_00, indexid_0000_01],(indexid_0000_01, indexid_0000_10],(indexid_0000_10, (indexid+1)_0000),四个区间即可对整个索引进行拆分。但是这种策略有一个坏处就是各个区间的记录个数不容易均匀,这会降低并发效果。 (2) 基于数据分布直方图的拆分。也就是根据实际的数据分布范围情况进行分布,尽量使每个分区内的记录数目相近,这样多个并发处理的线程会几乎同时完成,并发效果最好。因此,我们选择了这种方式进行拆分。

4.3 数据分布直方图的获取:

Rocksdb中每个Version对象会有一个VersionStorageInfo类型结构体storage_info来保存当前属于Version的所有文件的记录数据、记录范围、以及所处的level等相关信息,这是天然的数据分布直方图,我们只需要选择其中的记录数目最多的层进行范围拆分即可。在代码实现上,这个结构体层次比较深,而且这个对象当前Rocksdb并不对外暴露,不过这些都不是问题。

4.4 拆分的粒度

可以基于文件级别,也可以基于记录级别。由于我们的数据直方图中只有文件级别的统计信息,因此只能基于文件级别进行拆分。

基于以上考虑,我们的并发拆分策略如下:

代码语言:javascript
复制
1.获取storage_info;
2.根据storage_info获取当前LSM数的level数目;
3.判断level数据及层次,决定拆分所依据的level层次。
    3.1如果为1并且是level0层,由于lvel0层的文件之间范围有可能相互重叠,无法拆分,因此这种情况不能进行多线程并发,对应的区间为(indexid_0000, (indexid+1)_0000),拆分算法结束,return;
    3.2如果level数目为1且不是level0层,则将该层作为待拆分的层次;
    3.3如果level数目不为1,则遍历除level0层以外的所有层,找到记录数目最多的层次,作为待拆分的层次;
4.获取当前CPU空闲的个数,根据一定算法确定当前可以进行并发的线程数。
5.遍历待拆分的层文件,获取当前层记录的数目,计算出每个线程处理的记录数目为:待拆分记录数/并发线程数。
6.遍历待拆分层的文件,根据每个线程处理的记录数目,将该层的文件分为并发线程数个区间。

5.优化效果

5.1 优化后的perf图

5.1 优化后的查询时延

6.总结

本文介绍了TXRocks中sum操作的相关优化,主要是关键函数优化、下推到引擎、多线程并发,虽然优化思路很常规,但是效果明显。由于当前业务仅仅涉及整型,因此目前只针对整型优化。按照同样的思路,我们也优化了count操作。类似的需要优化的地方很多,后续我们会不断完善。


腾讯数据库技术团队对内支持微信红包,彩票、数据银行等集团内部业务,对外为腾讯云提供各种数据库产品,如CDB、CTSDB、CKV、CMongo, 腾讯数据库技术团队专注于增强数据库内核功能,提升数据库性能,保证系统稳定性并解决用户在生产过程中遇到的问题,并对生产环境中遇到的问题及知识进行分享。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯数据库技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 3.优化点二:convert_record_from_storage_format函数优化
  • 5.优化效果
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档