自動調(diào)優(yōu) RocksDB

最近看到一篇 Paper裤唠,Auto-tuning RocksDB莹痢,頓時兩眼放光竞膳。RocksDB 以配置多,難優(yōu)化而著稱坦辟,據(jù)傳 RocksDB 配置多到連 RocksDB 自己的開發(fā)者都沒法提供出一個好的配置,所以很多時候滔吠,我們都只能大概給一個比較優(yōu)的配置挠日,在根據(jù)用戶實際的 workload 調(diào)整。所以這時候真的希望能有一個自動 tuning 的方案冬骚。

對于數(shù)據(jù)庫來說懂算,auto tuning 是當(dāng)前一個非常熱門的研究領(lǐng)域,譬如 CMU 知名的 Peloton 項目计技,但這些項目通常都會關(guān)注特別多的配置,使用 TensorFlow 等技術(shù)進(jìn)行機(jī)器學(xué)習(xí)舍悯,靠人工智能來調(diào)優(yōu)。這個當(dāng)然也能用到 RocksDB 上面饮醇,不過對作者來說秕豫,這些都太復(fù)雜了(其實對我們也一樣,雖然人工智能誘惑很大混移,但坑很多)歌径。所以,作者主要關(guān)注的是如何更好的提升寫入性能沮脖。而基本原理也很簡單芯急,在寫入負(fù)載高的時候關(guān)掉 compaction,而在寫入負(fù)載低的時候打開 compaction免姿。那么自然要考慮的就是榕酒,如何去實現(xiàn)一個 compaction auto-tuner 了。

RocksDB 介紹

因為 RocksDB 在之前的文章中已經(jīng)介紹了太多了紊婉,這里就稍微簡單介紹一下辑舷。RocksDB 是基于 LSM-Tree 的,大概如下

雖然大部分讀者對于 LSM 已經(jīng)非常熟悉了肢础, 但這里還是簡單的介紹一下碌廓。首先,任何的寫入都會先寫到 WAL慨蛙,然后在寫入 Memory Table(Memtable)。當(dāng)然為了性能股淡,也可以不寫入 WAL唯灵,但這樣就可能面臨崩潰丟失數(shù)據(jù)的風(fēng)險。Memory Table 通常是一個能支持并發(fā)寫入的 skiplist埠帕,但 RocksDB 同樣也支持多種不同的 skiplist敛瓷,用戶可以根據(jù)實際的業(yè)務(wù)場景進(jìn)行選擇。

當(dāng)一個 Memtable 寫滿了之后呐籽,就會變成 immutable 的 Memtable狡蝶,RocksDB 在后臺會通過一個 flush 線程將這個 Memtable flush 到磁盤,生成一個 Sorted String Table(SST) 文件苏章,放在 Level 0 層奏瞬。當(dāng) Level 0 層的 SST 文件個數(shù)超過閾值之后,就會通過 Compaction 策略將其放到 Level 1 層并淋,以此類推珍昨。

這里關(guān)鍵就是 Compaction,如果沒有 Compaction酬诀,那么寫入是非陈嫫玻快的,但會造成讀性能降低肴裙,同樣也會造成很嚴(yán)重的空間放大問題。為了平衡寫入甜癞,讀取宛乃,空間這些問題,RocksDB 會在后臺執(zhí)行 Compaction征炼,將不同 Level 的 SST 進(jìn)行合并谆奥。但 Compaction 并不是沒有開銷的,它也會占用 I/O宰译,所以勢必會影響外面的寫入和讀取操作魄懂。

對于 RocksDB 來說,他有三種 Compaction 策略,一種就是默認(rèn)的 Leveled Compaction乡括,另一種就是 Universal Compaction,也就是常說的 Size-Tired Compaction诲泌,還有一種就是 FIFO Compaction。在之前介紹 Dostoevsky 的文章里面哀蘑,已經(jīng)詳細(xì)的介紹了 Leveled 和 Tired葵第,這里就不在重新說明了卒密。對于 FIFO 來說,它的策略非常的簡單哮奇,所有的 SST 都在 Level 0,如果超過了閾值哲身,就從最老的 SST 開始刪除,其實可以看到怔揩,這套機(jī)制非常適合于存儲時序數(shù)據(jù)误辑。

實際對于 RocksDB 來說巾钉,它其實用的是一種 Hybrid 的策略,在 Level 0 層砰苍,它其實是一個 Size-Tired 的赚导,而在其他層就是 Leveled 的。

這里在聊聊幾個放大因子凰锡,對于 LSM 來說圈暗,我們需要考慮寫放大,讀放大和空間放大勇哗,讀放大可以認(rèn)為是 RA = number of queries * disc reads寸齐,譬如用戶要讀取一個 page,但實際下面讀取了 3 個 pages扰法,那么讀放大就是 3毅厚。而寫放大則是 WA = data writeen to disc / data written to database,譬如用戶寫入了 10 字節(jié)殴边,但實際寫到磁盤的有 100 字節(jié),那么寫放大就是 10竖幔。而對于空間放大來說是偷,則是 SA = size of database files / size of databases used on disk蛋铆,也就是數(shù)據(jù)庫可能是 100 MB,但實際占用了 200 MB 的空間刺啦,那么就空間放大就是 2玛瘸。

這里簡單的聊了聊 RocksDB 相關(guān)的一些知識,下面就來說說作者是如何做 Auto tuning 的右核。

Statistics

因為關(guān)注的目標(biāo)是寫入壓力情況下面的 compaction 優(yōu)化渺绒,所以自然我們需要關(guān)注的是 RocksDB 的 compaction 統(tǒng)計。RocksDB 會定期將很多統(tǒng)計信息給寫入到日志里面躏鱼,所以我們只需要分析日志就行了了针炉。

我們需要關(guān)注的 RocksDB 日志如下:

Cumulative compaction: 2.09 GB write, 106.48 MB/s write, 1.19 GB read,
    60.66 MB/s read, 14.4 seconds
Interval compaction: 1.85 GB write, 130.27 MB/s write, 1.19 GB read, 83.86
     MB/s read, 13.2 seconds
Cumulative writes: 10K writes, 10K keys, 10K commit groups, 1.0 writes per
     commit group, ingest: 0.93 GB, 47.57 MB/s
Cumulative WAL: 10K writes, 0 syncs, 10000.00 writes per sync, written:
    0.93 GB, 47.57 MB/s
Cumulative stall: 00:00:0.000 H:M:S, 0.0 percent
Interval writes: 7201 writes, 7201 keys, 7201 commit groups, 1.0 writes
    per commit group, ingest: 686.97 MB, 47.36 MB/s
Interval WAL: 7201 writes, 0 syncs, 7201.00 writes per sync, written: 0.67
     MB, 47.36 MB/s
Interval stall: 00:00:0.000 H:M:S, 0.0 percent

具體的分析腳本在 這里篡帕,這個腳本會提取相應(yīng)的字段贸呢,然后繪制成圖表,這樣我們就能直觀的看實際的 I/O 量了怔鳖。

Compaction Tuner

要控制 auto compaction固蛾,RocksDB 有一個 disable_auto_compactions 參數(shù),當(dāng)設(shè)置為 false 的時候献幔,就會停止 compaction蜡感,但這時候需要將 Level 0 的 slowdown 參數(shù)也設(shè)置大,不然就會出現(xiàn) write stall 問題郑兴。

RocksDB 自身提供了一個 SetOptions 的函數(shù)情连,方便外面動態(tài)的去調(diào)整參數(shù),但這樣其實就需要自己在外面顯示的維護(hù) RocksDB 實例球榆。另一種方式就是給 RocksDB 傳一個共享的 environment禁筏,通過這個來控制幾個參數(shù)的修改。權(quán)衡之后每强,作者決定使用共享 env 的方式州刽,因為容易實現(xiàn),同時也能更方便的去訪問到 database 的內(nèi)部穗椅。

所以作者定制了一個 env,提供了 Enable 和 Disable 兩個函數(shù)门坷,在 Disable 里面袍镀,將 level0_file_num_compaction_trigger 設(shè)置成了 (1<<30)苇羡,這個也是 RocksDB PrepareForBulkLoad 函數(shù)里面的值。

bool disable_auto_compactions;
int prev_level0_file_num_compaction_trigger;
int level0_file_num_compaction_trigger;

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);
    }
};

void EnableCompactions() {
    if (disable_auto_compactions) {
      disable_auto_compactions = false;
      level0_file_num_compaction_trigger =
          prev_level0_file_num_compaction_trigger;
    } 
}

RocksDB 的 compaction 控制在 ColumnFamilyData 類里面锦茁,通過函數(shù) RecalculateWriteStallConditions 來計算的,但 ColumnFamilyData 并沒有 env撑刺,所以作者擴(kuò)展了一下握玛,給 ColumnFamilyData 的構(gòu)造函數(shù)加了個 env 變量:

ColumnFamilyData* new_cfd = new ColumnFamilyData(
  id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
  *db_options_, env_options_, this, Env::Default());

然后在改了下 RecalculateWriteStallConditions挠铲,讓其能接受 env 的參數(shù)來控制。

-WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
-   const MutableCFOptions& mutable_cf_options) {
+WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions() {
    auto write_stall_condition = WriteStallCondition::kNormal;
+    if (current_ != nullptr) {
+        if (mutable_cf_options_.atuo_tuned_compaction) {
+            mutable_cf_options_.level0_file_num_compaction_trigger = env_->level0_file_num_compaction_trigger;
+            mutable_cf_options_.disable_auto_compations = env_disable_auto_compations;
+        }
+    }
+    const MutableCFOptions& mutable_cf_options = mutable_cf_options_;

Rate Limiter

在 RocksDB 里面安聘,我們也可以通過 Rate Limiter 來 控制 I/O浴韭,通常有幾個參數(shù):

  • rate_limit_bytes_per_sec:控制 compaction 和 flush 每秒總的寫入量
  • refill_period_us:控制 tokens 多久再次填滿脯宿,譬如 rate_limit_bytes_per_sec 是 10MB/s,而 refill_period_us 是 100ms榴芳,那么每 100ms 的流量就是 1MB/s跺撼。
  • fairness:用來控制 high 和 low priority 的請求,防止 low priority 的請求餓死柿祈。

另外哩至,RocksDB 還提供了一個 Auto-tuned Rate Limiter憨募,它使用了一個 Multiplicative Increase Multiplicative Decrease(MIMD) 算法袁辈,auto-tuned 發(fā)生條件如下:

if (auto_tuned_) {
    static const int kRefillsPerTune = 100;
    std::chrono::microseconds now(NowMicrosMonotonic(env_));
    if (now - tuned_time_ >=
        kRefillsPerTune * std::chrono::microseconds(refill_period_us_))
    {
        Tune(); 
    }
}

Auto-tuned RateLimiter 里面已經(jīng)有很高效的 I/O 判斷了,但是這個 I/O 包含的是 flush 和 compaction 的請求的尾膊,作者需要區(qū)分兩種不同的請求。這個在 RocksDB 里面很容易待笑,因為 compaction 和 low priority 請求抓谴,而 flush 是 high priority 的癌压。作者把 GenericRateLimiter::Request 里面計算 num_drain_ 的方式改了下,引入了 num_high_drains_num_low_drains_ 兩個變量集侯,然后得到 num_drains帜消,如下:num_drains_ = num_high_drains_ + num_low_drains_;

有了 high 和 low 的 drains 變量辈讶,就可以直接來控制 compaction 了粘衬,作者新增了一個 TuneCompaction 函數(shù)稚新,類似原來的 Tune

Status GenericRateLimiter::TuneCompaction(Statistics* stats) {
    const int kLowWatermarkPct = 50;
    const int kHighWatermarkPct = 90;
    std::chrono::microseconds prev_tuned_time = tuned_time_;
    tuned_time_ = std::chrono::microseconds(NowMicrosMonotonic(env_));
    int64_t elapsed_intervals = (tuned_time_ - prev_tuned_time +
        std::chrono::microseconds(refill_period_us_) -
        std::chrono::microseconds(1)) /
        std::chrono::microseconds(refill_period_us_);
    // We tune every kRefillsPerTune intervals, so the overflow and division by
    // zero conditions should never happen.
    assert(num_drains_ - prev_num_drains_ <= port::kMaxInt64 / 100);
    assert(elapsed_intervals > 0);
    int64_t drained_high_pct =
        (num_high_drains_ - prev_num_high_drains_) * 100 /
        elapsed_intervals;
    int64_t drained_low_pct =
        (num_low_drains_ - prev_num_low_drains_) * 100 /
        elapsed_intervals;
    int64_t drained_pct = drained_high_pct + drained_low_pct;
    if (drained_pct == 0) {
        // Nothing
    } else if (drained_pct <= kHighWatermarkPct && drained_high_pct <
        kLowWatermarkPct) {
        env_->EnableCompactions();
    } else if (drained_pct >= kHighWatermarkPct && drained_high_pct >=
        kLowWatermarkPct) {
        env_->DisableCompactions();
        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();
}

觸發(fā)規(guī)則也比較容易褂删,如果 flush I/O 高于 50%,而總的 I/O 超過了 90%缅帘,就關(guān)掉 compaction难衰,反之則打開 compaction盖袭。

DB bench

準(zhǔn)備好了所有東西彼宠,下一步自然是測試弟塞,驗證 tuning 能否有效了。作者在 RocksDB 官方的 db_bench 上面加入了一種 Sine Wave 模式摧冀,也就是讓寫入滿足如下規(guī)則:

這個模式現(xiàn)在已經(jīng)加入了 db_bench 里面索昂,后面我們也可以嘗試一下扩借。然后就是確定下 RocksDB 的一些參數(shù),開始測試了框产。這里具體不說了错洁,反正就是改參數(shù)屯碴,做實驗,得到一個比較優(yōu)的配置的過程忱叭。然后作者對比了 RocksDB 默認(rèn)開啟 compaction今艺,不開啟 compaction 以及使用自己的 Auto-tuner 的情況,一些結(jié)果:

可以看到撵彻,數(shù)據(jù)還是很不錯的实牡。詳細(xì)的數(shù)據(jù)可以看作者的 Paper创坞。

總結(jié)

總的來說,作者實現(xiàn)的 Auto-tuner 通過控制 compaction偎谁,取得了比較好的效果,后面對我們的參數(shù)調(diào)優(yōu)也有很好的借鑒意義。另外婉支,RocksDB team 也一直在致力于 I/O 的優(yōu)化向挖,我還是很堅信 RocksDB 會越來越快的∏砹耍現(xiàn)在我們也在進(jìn)行 TiKV 的 tuning 工作晌块,會分析 TiKV 當(dāng)前的 workload 來調(diào)整 RocksDB 的參數(shù)徊件,如果你對這方面感興趣蒜危,歡迎聯(lián)系我 tl@pingcap.com辐赞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末响委,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子夹囚,更是在濱河造成了極大的恐慌邀窃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敲茄,死亡現(xiàn)場離奇詭異堰燎,居然都是意外死亡笋轨,警方通過查閱死者的電腦和手機(jī)赊淑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門陶缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洁灵,“玉大人徽千,你說我怎么就攤上這事“倏颍” “怎么了牍汹?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵慎菲,是天一觀的道長。 經(jīng)常有香客問我棠众,道長有决,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮台汇,結(jié)果婚禮上苟呐,老公的妹妹穿的比我還像新娘。我一直安慰自己严衬,他們只是感情好笆呆,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俄精,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嫌套。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天碳胳,我揣著相機(jī)與錄音沫勿,去河邊找鬼。 笑死诫惭,一個胖子當(dāng)著我的面吹牛蔓挖,可吹牛的內(nèi)容都是我干的瘟判。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼拷获,長吁一口氣:“原來是場噩夢啊……” “哼篮撑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匆瓜,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赢笨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驮吱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茧妒,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年左冬,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘶伟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片又碌。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡九昧,死狀恐怖绊袋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铸鹰,我是刑警寧澤癌别,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蹋笼,受9級特大地震影響展姐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剖毯,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一圾笨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逊谋,春花似錦擂达、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至究恤,卻和暖如春俭令,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背部宿。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工抄腔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人理张。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓妓柜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涯穷。 傳聞我的和親對象是個殘疾皇子棍掐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 最近項目中用到這個nb的玩意,所以就花時間研究了下拷况,同時整理下助自己記憶作煌。這個猛虎上山的logo就是rocksdb...
    小東_16d3閱讀 9,060評論 3 10
  • 在先前我們討論了 RocksDB 的 statistics 和 write stall,但這些只能讓我們發(fā)現(xiàn)問題赚瘦,...
    siddontang閱讀 8,043評論 2 16
  • 最近看了一篇 Paper粟誓,Dostoevsky: Better Space-Time Trade-Offs for...
    siddontang閱讀 8,205評論 1 21
  • 本文缺少實際的實踐經(jīng)驗。全部來自在網(wǎng)上的“道聽途說”和自己的“胡思亂想”起意。 寫放大鹰服、讀放大、空間放大 基于 LSM...
    linjinhe閱讀 7,476評論 2 5
  • 09年美網(wǎng)結(jié)束后,李娜正式與網(wǎng)球圈著名的經(jīng)紀(jì)公司IMG簽約悲酷,麥克斯?埃森巴德正式擔(dān)任李娜的經(jīng)紀(jì)人套菜。 麥克斯?埃森巴...
    Vicky小允閱讀 1,054評論 0 0