rocksdb性能调优
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了rocksdb性能调优相关的知识,希望对你有一定的参考价值。
参考技术A一、关键参数
create_if_missing:创建缺失表
num_levels:层次数量,默认是7。如果L0大小有512MB,6层能容纳512M+512M+5G+50G+500G+5T,如果配置是7,在数据量少于前面计算的5T+的数据之前,最后一层是不会被使用的。如果num_levels配置为6,那么最下面一层数据量会大于5T
max_background_flushes:memtable dump成sstable的并发线程数。默认是1,线程数小,当写入量大时,会导致无法写入。
max_background_compactions:底层sst向高层sst compact的并发线程数。并发compaction会加快compaction的速度,如果compaction过慢,达到soft_pending_compaction_bytes_limit会发生阻塞,达到hard_pending_compaction_bytes会停写。
max_write_buffer_number:指定memtable和immutable memtable总数。当写入速度过快,或者flush线程速度较慢,出现memtable数量超过了指定大小,请求会无法写入
write_buffer_size:单个memtable的大小,当memtable达到指定大小,会自动转换成immutable memtable并且新创建一个memtable
max_bytes_for_level_base:L1的总大小,L1的大小建议设置成和L0大小一致,提升L0->L1的compaction效率
min_write_buffer_number_to_merge:immutable memtable在flush之前先进行合并,比如参数设置为2,当一个memtable转换成immutable memtable后,RocksDB不会进行flush操作,等到至少有2个后才进行flush操作。这个参数调大能够减少磁盘写的次数,因为多个memtable中可能有重复的key,在flush之前先merge后就避免了旧数据刷盘;但是带来的问题是每次数据查找,当memtable中没有对应数据,RocksDB可能需要遍历所有的immutable memtable,会影响读取性能。
level0_file_num_compaction_trigger:L0达到指定个数的sstable后,触发compaction L0->L1。所以L0稳定状态下大小为write_buffer_size min_write_buffer_number_to_merge level0_file_num_compaction_trigger
statistics:统计系统性能和吞吐信息,开启statistics会增加5%到10%的额外开销
stats_dump_period_sec:统计信息导出日志时间间隔
compression_type: 压缩类型
bloom_filter_bits:使用bloom过滤器来避免不必要的磁盘访问
lru_cache_size:cache大小
max_open_files:最大打开文件句柄
skip_stats_update_on_db_open: 打开db时,是否跳过stats。建议设为false
二、wirte sall 常见情况及解决方法
(1)RocksDB在flush或compaction速度来不及处理新的写入,会启动自我保护机制,延迟写或者禁写。主要有几种情况:
写限速:如果max_write_buffer_number大于3,将要flush的memtables大于等于max_write_buffer_number-1,write会被限速。
禁写:memtable个数大于等于max_write_buffer_number,触发禁写,等到flush完成后允许写入。
写限速:L0文件数量达到level0_slowdown_writes_trigger,触发写限速。
禁写:L0文件数量达到level0_stop_writes_trigger,禁写。
写限速:等待compaction的数据量达到soft_pending_compaction_bytes,触发写限速。
禁写:等待compaction的数据量达到hard_pending_compaction_bytes,触发禁写。
(2)当出现write stall时,可以按具体的系统的状态调整如下参数:
调大max_background_flushes
调大max_write_buffer_number
调大max_background_compactions
调大write_buffer_size
调大min_write_buffer_number_to_merge
三、推荐配置示例
存储介质flash
options.options.compaction_style = kCompactionStyleLevel;
options.write_buffer_size = 67108864; // 64MB
options.max_write_buffer_number = 3;
options.target_file_size_base = 67108864; // 64MB
options.max_background_compactions = 4;
options.level0_file_num_compaction_trigger = 8;
options.level0_slowdown_writes_trigger = 17;
options.level0_stop_writes_trigger = 24;
options.num_levels = 4;
options.max_bytes_for_level_base = 536870912; // 512MB
options.max_bytes_for_level_multiplier = 8;
全内存
options.allow_mmap_reads = true;
BlockBasedTableOptions table_options;
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
table_options.no_block_cache = true;
table_options.block_restart_interval = 4;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.level0_file_num_compaction_trigger = 1;
options.max_background_flushes = 8;
options.max_background_compactions = 8;
options.max_subcompactions = 4;
options.max_open_files = -1;
ReadOptions.verify_checksums = false
TX-Rocks Sum性能调优之旅
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的下推操作处理逻辑
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层的记录格式。主要流程如下:
...... 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 拆分的粒度
可以基于文件级别,也可以基于记录级别。由于我们的数据直方图中只有文件级别的统计信息,因此只能基于文件级别进行拆分。
基于以上考虑,我们的并发拆分策略如下:
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, 腾讯数据库技术团队专注于增强数据库内核功能,提升数据库性能,保证系统稳定性并解决用户在生产过程中遇到的问题,并对生产环境中遇到的问题及知识进行分享。
以上是关于rocksdb性能调优的主要内容,如果未能解决你的问题,请参考以下文章