Rocksdbautotune--compaction的⾃动调优策略
⽂章⽬录
优化背景
auto tune 是lazy compaction ⽅法论中的⼀种优化⽅式,主要⽤来限制compaction/flush的I/O。
随着写⼊并发的增⼤,Internal ops :flush, L0->L1 compaction 以及 Higher Level compactions 之间的资源竞争,会造成上层的WriteStall 从⽽降低写吞吐,增加读延时。
优化演进思路
Rocksdb 的 RateLimiter
Ratelimiter 在 Flush/Compaction 写⼊之前做⼀个⽤户层的缓存,缓存⼤⼩可以⾃⼰配置。
这个缓存主要⽤来控制 Flush/Compaction 写⼊的速度,每当填充满这个缓存的时候才会触发write系统调⽤,降低频繁的internal I/O对上层⽤户请求的影响。
控制参数如下:
rate_limit_bytes_per_c:控制 compaction 和 flush 每秒总的写⼊量
refill_period_us:控制 tokens 多久再次填满,譬如 rate_limit_bytes_per_c 是 10MB/s,⽽ refill_period_us 是 100ms,那么每100ms 的流量就是 1MB/s。
fairness:⽤来控制 high 和 low priority 的请求,防⽌ low priority 的请求饿死。
可以通过如下配置设置。
options.(NewGenericRateLimiter(
static_cast<int64_t>(kNumL0Files * kNumKeysPerFile *
kBytesPerKey)/* rate_bytes_per_c */,
10*1000/* refill_period_us */,10/* fairness */,
RateLimiter::Mode::kReadsOnly));
缺点:设置好之后这个限制的写⼊速率是固定的,适合写⼊速率较稳稳定的workload场景。如果上层压⼒不断变化,可能这个rate限制的写⼊速率就偏⼩或者偏⼤,并不能达到提升吞吐,降低延时的⽬
的。
Rocksdb的 auto tune
为了让底层rate limiter 限制的write rate能够根据上层请求压⼒情况动态变化,⽽不⽤⼈主动得去调整设置的写⼊速度,社区开发了⼀个版本的auto tune。⽀持根据Flush/Compaction的I/O 压⼒来动态调整rate_limit_bytes_per_c 配置,从⽽达到动态调整write rate的⽬的。
通过如下代码进⾏配置,每隔10s 根据 Flush+Compaction的I/O情况 调整⼀次 rate_limiter的写⼊速度。
if(auto_tuned_){
static const int kRefillsPerTune =100;
std::chrono::microconds now(NowMicrosMonotonic(env_));
if(now - tuned_time_ >=
心神不宁的意思
kRefillsPerTune * std::chrono::microconds(refill_period_us_))//refill_period_us_为100ms
{
Tune();
}
}
详细代码在Tune 函数中。
主要是通过两个⽔位 在 当前配置的rate_limit_bytes_per_c 的⼀段范围内进⾏微调整[max_bytes_per_c_ / kAllowedRangeFactor, max_bytes_per_c_] , kAllowedRangeFactor。
详细的⼏个控制参数如下:
1. kLowWatermarkPct = 50
· 控制⽔位,认为flush/compaction IO 速度超过了50%.
2. kHighWatermarkPct = 90
· 控制⽔位, 认为flush/compaction IO速度超过了 90%.
生活态度3. kAdjustFactorPct = 5
· 微调当前写⼊速率的变量 ,5%调整增量.
4. kAllowedRangeFactor = 20
· ⼀个控制因⼦,最低的写⼊速率不能低于max_bytes_per_c_/kAllowedRangeFactor 。
(num_drains−prev_num_drains)⋅100
drained_pct=elapd_intervals
以上公式⽤来表⽰后台I/O情况,其中num_drains 和 prev_num_drains 是在统计Flush/Compaction的过程进⾏变更的。
总的来说,Rocksdb 原⽣实现的auto tune 虽然能够进⾏写⼊速率的动态调整,仍然有两个问题:
1. 它所监控的I/O 是 Flush和Compaction 总的I/O情况,并不是两者分开的。
不能分开调整Flush 也就是调整的值 ⽆法准确体现在上层吞吐。Flush的优先级 ⾼于 compaction,因为其直接导致write-stall。⽽如果每次预估增加的吞吐都是由compaction带来的,那这样的调整对Flus
h并没有优势。
电饭锅焖饭2. 调整⽅式是在⼀个范围内微调,治标不治本。(如果短时间内压⼒剧增,不论怎么限制rate,都⽆法降低后台I/O对上层请求的影响)auto tuned compaction(rocskdb默认auto tune基础上的优化)
这个⽅法来源于:
针对rocksdb实现的auto tune的两个问题,该设计单⼑直⼊:
1. 在auto tune的时候区分Flush/Compaction,优先针对 Flush 的I/O达到⽔位的情况。
2. 调整的过程是通过动态调整diable_auto_compactions 以及 level0_file_num_compaction_trigger 参数
Flush 速度慢了,直接会造成write-stall,从⽽对吞吐和延时有严重影响。diable_auto_compactions间接降低后台Compaction对 Flush的影响,同时增⼤level0_file_num_compaction_trigger,直接减少写放⼤。
当Flush的I/O⽔位⾼于50% 且 总的I/O⽔位⾼于90%,认为I/O压⼒较重,禁⽌compaction,并增⼤
level0_file_num_compaction_trigger为最⼤。
当Flush的I/O⽐例低于50% 且 总的I/O⽔位低于90%,认为I/O压⼒较轻,开启compaction,并恢复
level0_file_num_compaction_trigger 配置。
关于TuneCompaction的代码实现如下:
Status GenericRateLimiter::TuneCompaction(Statistics *stats){
const int kLowWatermarkPct =50;// 低⽔位
const int kHighWatermarkPct =90;// ⾼⽔位
std::chrono::microconds prev_tuned_time = tuned_time_;
tuned_time_ = std::chrono::microconds(NowMicrosMonotonic(env_));
int64_t elapd_intervals =(tuned_time_ - prev_tuned_time +
std::chrono::microconds(refill_period_us_)-
std::chrono::microconds(1))/
std::chrono::microconds(refill_period_us_);
// We tune every kRefillsPerTune intervals, so the overflow and division by
// zero conditions should never happen.
乌拉草床垫asrt(num_drains_ - prev_num_drains_ <= port::kMaxInt64 /100);
asrt(elapd_intervals >0);
int64_t drained_high_pct =// Flush请求的增长速度
(num_high_drains_ - prev_num_high_drains_)*100/
elapd_intervals;
int64_t drained_low_pct =// compaction请求的增长速度
(num_low_drains_ - prev_num_low_drains_)*100/
elapd_intervals;
红薯英文
int64_t drained_pct = drained_high_pct + drained_low_pct;// 总的增速
if(drained_pct ==0){
// Nothing
}el if(drained_pct <= kHighWatermarkPct && drained_high_pct <
kLowWatermarkPct){
env_->EnableCompactions();// 增速减缓,开启compaction,并开启level0⽂件限制
}el if(drained_pct >= kHighWatermarkPct && drained_high_pct >=
kLowWatermarkPct){
env_->DisableCompactions();// 增速增加,关闭compaction,关闭level0⽂件限制
RecordTick(stats, COMPACTION_DISABLED_COUNT,1);
}
num_low_drains_ = prev_num_low_drains_;
num_high_drains_ = prev_num_high_drains_;
num_drains_ = prev_num_drains_;
return Status::OK();
}
其中EnableCompactions 和 DisableCompactions函数实现在了全局共享变量Env中,可以通过共享变量来进⾏相关option的动态变更(这⾥的意义在于提供了⼀种思路,可以通过Env 来实现rocksdb参数的动态调优)
bool auto_tuned_compaction;
bool disable_auto_compactions;
int level0_file_num_compaction_trigger;
int prev_level0_file_num_compaction_trigger;
// Unlimit level0 file num, and this function will be ud with Env
void DisableCompactions(){
if(!disable_auto_compactions){
新年手抄报prev_level0_file_num_compaction_trigger =
level0_file_num_compaction_trigger;
disable_auto_compactions = true;
level0_file_num_compaction_trigger =(1<<30);
}
}
// Limit Level0 file nums, and ret back the option
// and will be ud in Env.
void EnableCompactions(){
if(disable_auto_compactions){
disable_auto_compactions = fal;
level0_file_num_compaction_trigger =
非计划再次手术
prev_level0_file_num_compaction_trigger;
猴头菇煮多久能熟
}
}
当设置了Enable和Disable之后,会在下次触发write stall之前重置compaction相关的信息
WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(){
auto write_stall_condition = WriteStallCondition::kNormal;
// Wether to enable the auto compaction by check auto_tuned_compaction
/
/ to delay the write stall condition
if(current_ != nullptr){
if(mutable_cf_options_.auto_tuned_compaction){
mutable_cf_options_.disable_auto_compactions =// 设置enble/disable compaction
env_->disable_auto_compactions;
mutable_cf_options_.level0_file_num_compaction_trigger =// 设置level0⽂件数量
env_->level0_file_num_compaction_trigger;
}
}
......
}
关于这种Auto tuned compaction 存在的问题是:
如果有段时间⽤户并发压⼒处于⼀个⾼峰的稳态,可能导致L0⽂件数量过多,对读性能有⼀定的影响(具体数据还需要详细测试),同时持续⾼峰过久,也会因为pending-byes 过多再次出现write-stall的问题。
如下图,相关指标可以看看下⽂benchmark的说明,总之 蓝⾊的先代表的⽤户I/O吞吐,灰⾊的线代表的是rocksdb compaction的速度,可以看到compaction吞吐被限制在180M以下,但是⽤户I/O吞吐还是有抖动(预期应该是⼀个平滑的sin 曲线),rocksdb⽇志中出现的write-stall问题就是 超过64G的pending-bytes,持续写⼊压⼒过⼤,累积的compaction量过多。
也就是,当⽤户I/O压⼒持续稳定在⼀个⾼峰,如果不变更底层rocksdb的compaction逻辑或者LSM的
数据结构的情况下,write-stall问题不可避免。
优点:
1. 曲线波动的情况下,能够有效降低compaction/flush I/O对客户端性能的影响,保证了吞吐的稳定性
2. 调优⽅式值得借鉴,能够对后续 rocksdb 参数的 ⾃动调优 提供参考
Bench Mark
硬件配置
cpu : Intel® Xeon® Gold 5218 CPU @ 2.30GHz 64 core
mem: 500G
Disk: nvme-ssd * 3.2T
fs: xfs (rw,noatime,attr2,inode64,noquota)
软件配置
Value-size: 100B
Threads: 1
Compression: none
Bloomfilter: whole_key_filtering = true, NewBloomFilterPolicy(16,fal)
Max_background_compactions: 2
db_bench 6.6.0
Value-size: 512B 4096B, 64K, 256K,1M (value较⼤时write-stall更明显)
测试模型
sin曲线变化的请求压⼒下: 开启comapction, 禁⽌compaction,开启rocksdb原⽣的auto tune(和ratelimiter的效果差不多),论⽂优化后的 auto tune 四种对⽐测试的statistics 数据。
这⾥主要展⽰的是纯写场景中New auto tune的优化⽅式对写性能的影响。
测试指标