L版本閑置Ceph集群 BlueFS Log容量異常增長的問題分析

【問題描述】
L版本 12.2.13
空置一年的集群,檢查發(fā)現(xiàn)大量OSD的meta容量占用特別高湾宙;
重啟這些OSD樟氢,會長時(shí)間卡在BlueFS::_replay()函數(shù)里;
這些OSD最終有的可以啟動侠鳄,有的會因?yàn)長og的校驗(yàn)報(bào)錯而停止啟動埠啃;
replay時(shí)間以小時(shí)計(jì),盤越大的時(shí)間越長伟恶。
獨(dú)立DB分區(qū)的OSD從未出現(xiàn)過Log容量無限增長的問題碴开,而未單獨(dú)配置block.db的OSD則很大概率出此問題

最后無奈只能刪除OSD重建,但是集群空置一段時(shí)間后知押,這個現(xiàn)象又回來了叹螟。

從BlueStore.cc中的kv提交線程開始分析:

void BlueStore::_kv_sync_thread()
{
  dout(10) << __func__ << " start" << dendl;
  std::unique_lock<std::mutex> l(kv_lock);
  assert(!kv_sync_started);
  bool bluefs_do_check_balance = false;
  kv_sync_started = true; 
  kv_cond.notify_all();
  while (true) {
    assert(kv_committing.empty());
    if (kv_queue.empty() &&
        ((deferred_done_queue.empty() && deferred_stable_queue.empty()) ||
         !deferred_aggressive) &&
        (bluefs_do_check_balance == false)) {

閑置的時(shí)候,上面所有queue都空台盯,bluefs_do_check_balance初始為false罢绽,進(jìn)入到
if流程,在等待默認(rèn)1秒沒有新的kv寫時(shí)静盅,bluefs_do_check_balance被置為true

      if (kv_stop)
        break;
      dout(20) << __func__ << " sleep" << dendl;
      std::cv_status status = kv_cond.wait_for(l,
        std::chrono::milliseconds(int64_t(cct->_conf->bluestore_bluefs_balance_interval * 1000)));
      dout(20) << __func__ << " wake" << dendl;
      if (status == std::cv_status::timeout) {
        bluefs_do_check_balance = true; 
      }     
    } else {

下一次循環(huán)時(shí)良价,由于bluefs_do_check_balance被置為true,進(jìn)入到else流程蒿叠,省略掉很多無關(guān)代碼明垢,以...代替

    } else {
      deque<TransContext*> kv_submitting;
      deque<DeferredBatch*> deferred_done, deferred_stable;
      uint64_t aios = 0, costs = 0;
      ... ...
      // we will use one final transaction to force a sync
      KeyValueDB::Transaction synct = db->get_transaction();

上面這行代碼挺關(guān)鍵,用意是獲取db記錄的最后一個transaction市咽,后面用它來觸發(fā)一次sync操作痊银,確保本次循環(huán)所commit的事務(wù)落盤。
大致的流程如下:

... ...
      for (auto txc : kv_committing) {
        if (txc->state == TransContext::STATE_KV_QUEUED) {
          txc->log_state_latency(logger, l_bluestore_state_kv_queued_lat);
          int r = cct->_conf->bluestore_debug_omit_kv_commit ? 0 : db->submit_transaction(txc->t);
          assert(r == 0);
          txc->state = TransContext::STATE_KV_SUBMITTED;
          ... ...

        } else {
          assert(txc->state == TransContext::STATE_KV_SUBMITTED);
          txc->log_state_latency(logger, l_bluestore_state_kv_queued_lat);
        }
        ... ...
      }
      ... ...
      PExtentVector bluefs_gift_extents;
      if (bluefs &&
          after_flush - bluefs_last_balance >
          cct->_conf->bluestore_bluefs_balance_interval) {
        bluefs_last_balance = after_flush;
        int r = _balance_bluefs_freespace(&bluefs_gift_extents);
        assert(r >= 0);
        if (r > 0) {
          for (auto& p : bluefs_gift_extents) {
            bluefs_extents.insert(p.offset, p.length);
          }
          bufferlist bl;
          ::encode(bluefs_extents, bl);
          dout(10) << __func__ << " bluefs_extents now 0x" << std::hex
                   << bluefs_extents << std::dec << dendl;
          synct->set(PREFIX_SUPER, "bluefs_extents", bl);
        }
      }
      ... ...施绎,

上面的流程大致為:對kv_committing隊(duì)列里的事務(wù)溯革,調(diào)用db->submit_transaction提交贞绳,而后進(jìn)行bluefs的空閑空間判斷,空閑空間過大時(shí)執(zhí)行回收空間并更新bluefs_extents記錄致稀;更新后的bluefs_extents記錄序列化后冈闭,附加到此前獲得的synct事務(wù)中,期望伴隨著synct事務(wù)一起提交并sync抖单,接下來

// submit synct synchronously (block and wait for it to commit)
int r = cct->_conf->bluestore_debug_omit_kv_commit ? 0 : db->submit_transaction_sync(synct);
assert(r == 0);

這里調(diào)用了db->submit_transaction_sync 萎攒,注意前面提交業(yè)務(wù)Io時(shí),用的db->submit_transaction提交矛绘,兩個函數(shù)有什么不同,等會兒分析羹应。

回顧上面的流程次屠,在集群空閑沒有業(yè)務(wù)IO時(shí),周期性的過程:

  1. KeyValueDB::Transaction synct = db->get_transaction(); // 一定被執(zhí)行
  2. db->submit_transaction(txc->t); // 一定不執(zhí)行裸违,因?yàn)闆]有新的事務(wù)進(jìn)來
  3. _balance_bluefs_freespace(&bluefs_gift_extents); // 周期性執(zhí)行
  4. synct->set(PREFIX_SUPER, "bluefs_extents", bl); // 低頻率執(zhí)行本昏,因?yàn)楹竺鎠ynct的提交可能逐漸撐大bluefs空間,而Log compaction又釋放掉空間導(dǎo)致bluefs free比例過大怔昨,引發(fā)第三步的空間回收過程宿稀。
  5. db->submit_transaction_sync(synct); // 一定會執(zhí)行

從上面的過程可以初步劃定范圍,導(dǎo)致BlueFS容量無限增長的可能觸發(fā)點(diǎn)在這兩個位置:

  1. _balance_bluefs_freespace(&bluefs_gift_extents);
  2. db->submit_transaction_sync(synct);

當(dāng)出現(xiàn)問題時(shí)矮烹,OSD重啟時(shí)會在BlueFS::_replay(bool noop)函數(shù)中長時(shí)間循環(huán)奉狈,可以推斷,出問題的OSD涩惑,有巨量的BlueFS Log在做回放

先對_balance_bluefs_freespace做分析,函數(shù)中有可能操作硬盤的調(diào)用為:
bluefs->reclaim_blocks(bluefs_shared_bdev, reclaim, &extents);
這里的bluefs_shared_bdev碰纬,在有單獨(dú)DB設(shè)備時(shí)问芬,它是BDEV_DB此衅,否則是BDEV_SLOW

int BlueFS::reclaim_blocks(unsigned id, uint64_t want,
                           PExtentVector *extents)
{
  std::unique_lock<std::mutex> l(lock);
  dout(1) << __func__ << " bdev " << id
          << " want 0x" << std::hex << want << std::dec << dendl;
  assert(id < alloc.size());
  assert(alloc[id]);

  int64_t got = alloc[id]->allocate(want, alloc_size[id], 0, extents);
  ceph_assert(got != 0);
  if (got < 0) {
    derr << __func__ << " failed to allocate space to return to bluestore"
      << dendl;
    alloc[id]->dump();
    return got;
  }

  for (auto& p : *extents) {
    block_all[id].erase(p.offset, p.length);
    block_total[id] -= p.length;
    log_t.op_alloc_rm(id, p.offset, p.length);
  }

  flush_bdev();
  int r = _flush_and_sync_log(l);
  assert(r == 0);

  if (logger)
    logger->inc(l_bluefs_reclaim_bytes, got);
  dout(1) << __func__ << " bdev " << id << " want 0x" << std::hex << want
          << " got " << *extents << dendl;
  return 0;
}

上面的函數(shù)挡鞍,傳入的extents大概率是空的预烙。所以log_t并未被更新
緊接著調(diào)用了 _flush_and_sync_log函數(shù),看看里面做了什么

int BlueFS::_flush_and_sync_log(std::unique_lock<std::mutex>& l,
                                uint64_t want_seq,
                                uint64_t jump_to)
{
  while (log_flushing) {
    dout(10) << __func__ << " want_seq " << want_seq
             << " log is currently flushing, waiting" << dendl;
    assert(!jump_to);
    log_cond.wait(l);
  }
  if (want_seq && want_seq <= log_seq_stable) {
    dout(10) << __func__ << " want_seq " << want_seq << " <= log_seq_stable "
             << log_seq_stable << ", done" << dendl;
    assert(!jump_to);
    return 0;
  }
  if (log_t.empty() && dirty_files.empty()) {
    dout(10) << __func__ << " want_seq " << want_seq
             << " " << log_t << " not dirty, dirty_files empty, no-op" << dendl;
    assert(!jump_to);
    return 0;
  }

 ... ...

dirty_files是空的翘县,到這里log_t.empty()應(yīng)該成立谴分,所以函數(shù)返回了,什么也沒做忘伞。

至此
3. _balance_bluefs_freespace(&bluefs_gift_extents);導(dǎo)致Log無限增長的原因排除了沙兰。
只剩下最后一個嫌疑點(diǎn):
5. db->submit_transaction_sync(synct);

調(diào)用鏈

問題出在BlueFS::sync_metadata()函數(shù)里面

1413fed24c791654a5d6a7e31ab03de.png

經(jīng)過前面的分析鼎天,log_t大概率就是empty的舀奶,所以伪节,上面的if條件導(dǎo)致下面的if沒有執(zhí)行機(jī)會绩鸣,也就沒能觸發(fā)log compaction
社區(qū)有修復(fù)的PR,包含在在12.2.14版本
https://github.com/ceph/ceph/pull/34876

不免會有新的疑問:總有非empty的log來觸發(fā)這個compaction吧化借?
追蹤調(diào)用鏈捡多,在rocksdb的DBImpl實(shí)現(xiàn)里面铐炫,只有兩種情況會觸發(fā)BlueRocksDirectory::Fsync()

  • 首次收到woptions.sync == true的寫請求的時(shí)候
  • 切換到新的memtable時(shí)

假如這兩種情況觸發(fā)時(shí)倒信,寫下去的事務(wù)都是空的呢泳梆?
就很可能出現(xiàn)BlueRocksDirectory::Fsync()被調(diào)用,但log_t里面啥也沒有的情況乘综,
下面這個PR雖沒有被社區(qū)接受,原因是end of life卡辰,但我相信它確實(shí)堵住了源頭
https://github.com/ceph/ceph/pull/36108

綜上邪意,問題已經(jīng)比較明晰

  1. 在集群無業(yè)務(wù)負(fù)載時(shí),周期性db->submit_transaction_sync調(diào)用允蚣,提交空的事務(wù)
  2. 在首次提交以及切換memtable時(shí)呆贿,觸發(fā)BlueRocksDirectory::Fsync(),但由于log_t為空冒晰,未能執(zhí)行l(wèi)og compact
  3. 第1竟块、2經(jīng)年累月的反復(fù)執(zhí)行,導(dǎo)致WAL File的log不斷增長蒋情,又得不到compaction處理耸携,日志空間不斷增大。
  4. 當(dāng)日志增大到極限大小夺衍,約500GB時(shí),會出現(xiàn)日志損壞的情況(具體原因還不明)
  5. 此時(shí)河劝,OSD重啟時(shí),就會經(jīng)歷漫長的replay過程牌里,而且最終因日志損壞而失敗煎娇,OSD無法啟動。

解決方案
我認(rèn)為,僅僅是我認(rèn)為杭隙,將如下兩個PR一起使用,能夠解決問題:

  1. 讓compact log總是有機(jī)會執(zhí)行: https://github.com/ceph/ceph/pull/34876
  2. 盡可能的避免提交空事務(wù): https://github.com/ceph/ceph/pull/36108
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末票髓,一起剝皮案震驚了整個濱河市铣耘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌裆操,老刑警劉巖炉媒,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缎岗,居然都是意外死亡白粉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門眷细,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薪鹦,“玉大人,你說我怎么就攤上這事池磁。” “怎么了华临?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵端考,是天一觀的道長。 經(jīng)常有香客問我扶供,道長裂明,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任扳碍,我火速辦了婚禮仙蛉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荠瘪。我一直安慰自己,他們只是感情好鞭莽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布澎怒。 她就那樣靜靜地躺著,像睡著了一般喷面。 火紅的嫁衣襯著肌膚如雪走孽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天盒齿,我揣著相機(jī)與錄音,去河邊找鬼边翁。 笑死,一個胖子當(dāng)著我的面吹牛符匾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甸各,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼焰坪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了誊酌?” 一聲冷哼從身側(cè)響起露乏,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘟仿,失蹤者是張志新(化名)和其女友劉穎比勉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浩聋,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衣洁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砖第。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片环凿。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖羽杰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情考赛,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布集灌,位于F島的核電站,受9級特大地震影響欣喧,放射性物質(zhì)發(fā)生泄漏梯找。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一驯鳖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浅辙,春花似錦阎姥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诊赊。三九已至府瞄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摘能,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工严望, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逻恐,地道東北人峻黍。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓姆涩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骨饿。 傳聞我的和親對象是個殘疾皇子台腥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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