Ceph性能瓶頸分析與優化(混合盤篇)

背景node

企業級存儲中,SSD+HDD的混合盤是一種典型應用場景,能夠兼顧成本、性能與容量。ios

但網易數帆存儲團隊通過測試(4k隨機寫)發現,加了NVMe SSD作Ceph的WAL和DB後,性能提高不足一倍且NVMe盤性能餘量較大。因此咱們但願經過瓶頸分析,探討可以進一步提高性能的優化方案。git

測試環境

Ceph性能分析通常先用單OSD來分析,這樣能夠屏蔽不少方面的干擾。 咱們的測試環境以下所示,1個OSD:github

usrname@hostname:~/cluster$ sudo ceph osd tree
ID CLASS WEIGHT  TYPE NAME                 STATUS REWEIGHT PRI-AFF
-5       1.09099 root single-wal-db
-6       1.09099     host single-wal-db-69
 1   hdd 1.09099         osd.1                 up  1.00000 1.00000


====== osd.1 =======
  devices                   /dev/sdc

  [ wal]    /dev/sdv2

      PARTUUID                  6b3f8b48-99ad-4ede-a4ab-0c23d5b5e162

  [  db]    /dev/sdv1

      PARTUUID                  b7debf8e-0907-4b80-90b9-04443a2e5c82

性能優化初覽

preview

上圖主要是BlueStore 對於defer write寫的一個整體流程。能夠看到這裏的性能優化主要是兩個點:docker

1. 重耗時模塊影響上下文api

  • 問題:返回客戶端寫成功函數+落盤在同一個線程(_kv_finalize_thread)
  • 優化:返回寫成功前保證元數據寫成功便可,故可把這兩個階段拆分到不一樣的線程:

2. 重耗時模塊在IO核心路徑數組

  • 問題:刷盤函數fdatasync在IO關鍵路徑上(kv_sync_thread)
  • 優化:函數目的是確保數據落盤。故可把其移動到非IO核心路徑(_deferred_aio_finish)

IO瓶頸分析

[global]
ioengine=rbd
pool=single_wal_db
rbdname=volume01(100g)
invalidate=0  
rw=randwrite
bs=4k
runtime=180
[rbd_iodepth32]
iodepth=128

write: IOPS=1594, BW=6377KiB/s (6530kB/s)(374MiB/60106msec); 0 zone resets
    slat (nsec): min=1249, max=721601, avg=5098.70, stdev=6069.83
    clat (usec): min=1157, max=589139, avg=80279.11, stdev=77925.94
     lat (usec): min=1166, max=589141, avg=80284.20, stdev=77926.06
# osd
 "op_w_latency": {
            "avgcount": 95824,
            "sum": 7593.745711498,
            "avgtime": 0.079246803
        },
        "op_w_process_latency": {
            "avgcount": 95824,
            "sum": 597.747938957,
            "avgtime": 0.006237977
        },
     "op_before_queue_op_lat": {
            "avgcount": 95887,
            "sum": 3.172325348,
            "avgtime": 0.000033083
        },
        "op_before_dequeue_op_lat": {  
            "avgcount": 95895,
            "sum": 7001.039474373,
            "avgtime": 0.073007346
        },

# bluestore    

     "state_kv_queued_lat": {
            "avgcount": 95858,
            "sum": 103.287853014,
            "avgtime": 0.001077508
        },
        "state_kv_commiting_lat": {
            "avgcount": 95858,
            "sum": 49.291618042,
            "avgtime": 0.000514214
        },

    "throttle_lat": {
            "avgcount": 95858,
            "sum": 280.404541330,
            "avgtime": 0.002925207
        },

        "commit_lat": {
            "avgcount": 95858,
            "sum": 436.058305735,
            "avgtime": 0.004549002
        },

代碼深度分析與代碼優化

從上面耗時分析能夠看出,op_before_dequeue_op_lat這個階段的耗時佔了大頭,從以下代碼能夠看出,該階段是從收到op到op出隊列的時間:性能優化

void OSD::dequeue_op()
{
    utime_t now = ceph_clock_now();
    utime_t latency = now - op->get_req()->get_recv_stamp();
    logger->tinc(l_osd_op_before_dequeue_op_lat, latency);

    pg->do_request(op, handle);
}

另外發現還有一個關鍵階段的耗時統計,即op_before_queue_op_lat,如以下代碼能夠看出,該階段是從收到op到op入隊列以前的時間:app

void OSD::enqueue_op(spg_t pg, OpRequestRef& op, epoch_t epoch)
{
    utime_t latency = ceph_clock_now() - op->get_req()->get_recv_stamp();
    ogger->tinc(l_osd_op_before_queue_op_lat, latency);

    op_shardedwq.queue(make_pair(pg, PGQueueable(op, epoch)));
}

從OSD的時延統計能夠看出,op_before_dequeue_op_lat耗時很長,可是op_before_queue_op_lat耗時很短,這能夠說明耗時主要花費在工做線程入隊到出隊這塊。async

基於這個認識,因此首先考慮到的即是PG鎖或者線程數太少處理不過來,第一步即是考慮調大Ceph邏輯pool的pg數,可是調大後,發現性能未有改變;因此進一步考慮調大線程數量,以下:

#osd_op_num_shards_hdd = 5(10)
#osd_op_num_threads_per_shard_hdd = 1(2)

 write: IOPS=1571, BW=6286KiB/s (6437kB/s)(369MiB/60057msec); 0 zone resets
    slat (nsec): min=1169, max=459761, avg=4347.07, stdev=5058.94
    clat (usec): min=1903, max=8069.2k, avg=81438.80, stdev=294739.08
     lat (usec): min=1919, max=8069.2k, avg=81443.15, stdev=294739.04

 # osd
        "op_w_latency": {
            "avgcount": 94385,
            "sum": 7544.120278825,
            "avgtime": 0.079929228
        },
        "op_w_process_latency": {
            "avgcount": 94385,
            "sum": 5289.258407670,
            "avgtime": 0.056039184
        },

          "op_before_queue_op_lat": {
            "avgcount": 94420,
            "sum": 2.903951913,
            "avgtime": 0.000030755
        },
        "op_before_dequeue_op_lat": {
            "avgcount": 94421,
            "sum": 2255.264719617,
            "avgtime": 0.023885202
        },
# bluestore

      "state_kv_queued_lat": {
            "avgcount": 94391,
            "sum": 899.530260004,
            "avgtime": 0.009529830
        },
        "state_kv_commiting_lat": {
            "avgcount": 94391,
            "sum": 86.274165030,
            "avgtime": 0.000914008
        },

     "throttle_lat": {
            "avgcount": 94391,
            "sum": 804.876332278,
            "avgtime": 0.008527045
        },
        "commit_lat": {
            "avgcount": 94391,
            "sum": 1795.371420011,
            "avgtime": 0.019020578
        },
# finish
 "finisher-finisher-0": {
        "queue_len": 0,
        "complete_latency": {
            "avgcount": 2927,
            "sum": 50.626880235,
            "avgtime": 0.017296508
        }
    },

發現op_before_dequeue_op_lat的時延仍是很長,且這個時候發現finish線程的耗時顯著拉長了。此時嘗試增大finish線程數量(bluestore_shard_finishers=true),發現finish線程耗時下去了,可是總體耗時並未變短。

經過以上的優化,發現iops並未獲得提高,只是耗時的時間段發生了遷移而已。可是耗時時間段無論怎麼遷移,op_before_dequeue_op_lat階段的耗時都很長且磁盤的使用率都基本接近100%。

基於此,但願觀察下OSD進程的IO吞吐狀況,經過iotop觀察IO吞吐狀況,發現一個異常現象,以下:

633 be/3 root        0.00 B/s  102.37 K/s  0.00 % 17.85 % [jbd2/sda2-8]
   4803 be/4 root        0.00 B/s   58.50 K/s  0.00 %  6.77 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   4805 be/4 root        0.00 B/s   40.22 K/s  0.00 %  4.57 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   7774 be/4 root        0.00 B/s   29.25 K/s  0.00 %  2.33 % kubelet --node-ip 10.185.0.69 --allowed-unsafe-sysctls=kern~.io/master=,beta.kubernetes.io/instance-type=bareMetal -v=4
  12193 be/4 root        0.00 B/s   10.97 K/s  0.00 %  1.97 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   4796 be/4 root        0.00 B/s    7.31 K/s  0.00 %  1.10 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   4855 be/4 root        0.00 B/s   14.62 K/s  0.00 %  1.07 % kube-apiserver --authorization-mode=Node,RBAC --advertise-a~tls-private-key-file=/etc/kubernetes/pki/apiserver.key -v=4
   1385 be/4 root        0.00 B/s    3.66 K/s  0.00 %  0.93 % rsyslogd -n -iNONE [rs:main Q:Reg]
 444638 be/4 ceph        0.00 B/s    7.31 K/s  0.00 %  0.86 % ceph-mon -f --cluster ceph --id pubt1-ceph69 --setuser ceph --setgroup ceph [safe_timer]
 444633 be/4 ceph        0.00 B/s    7.31 K/s  0.00 %  0.46 % ceph-mon -f --cluster ceph --id pubt1-ceph69 --setuser ceph --setgroup ceph [fn_monstore]
 114000 be/4 ceph      186.47 K/s   40.22 K/s  0.00 %  0.33 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [tp_osd_tp]
 113997 be/4 ceph      186.47 K/s   58.50 K/s  0.00 %  0.31 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [tp_osd_tp]
 114001 be/4 ceph      175.50 K/s    3.66 K/s  0.00 %  0.27 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [tp_osd_tp]
 113999 be/4 ceph      168.18 K/s   36.56 K/s  0.00 %  0.24 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [tp_osd_tp]
 113998 be/4 ceph      131.62 K/s   25.59 K/s  0.00 %  0.24 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [tp_osd_tp]
 113866 be/4 ceph        0.00 B/s   12.77 M/s  0.00 %  0.03 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [bstore_kv_sync]
 113864 be/4 ceph        0.00 B/s    4.65 M/s  0.00 %  0.00 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [dfin]
 113867 be/4 ceph        0.00 B/s  950.61 K/s  0.00 %  0.00 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [bstore_kv_final]

如上,能夠看到tp_osd_tp線程居然有持續的IO寫入。這一點很奇怪,由於該線程是osd層線程,在個人理解裏面,只有store層纔會有IO寫入。

因此開始分析OSD層IO代碼,最終找到了IO寫入的地方,以下:

int BlueStore::queue_transactions()
{
    utime_t tstart = ceph_clock_now();
    throttle_bytes.get(txc->cost);
    if (txc->deferred_txn) 
    {
        if (!throttle_deferred_bytes.get_or_fail(txc->cost))
        {
          ++deferred_aggressive;
          deferred_try_submit();
        }
    }
}

BlueStore::deferred_try_submit()
--> BlueStore::_deferred_submit_unlock
 --> bdev->aio_submit  // 向磁盤提交io

從代碼能夠看出,若是達到限流了,那麼便會調用deferred_try_submit函數:而deferred_try_submit函數最終會向磁盤提交io。

 

接下來增大throttle參數(bluestore_throttle_bytes 與bluestore_throttle_deferred_bytes ),開始新一輪的io測試,此時依然使用iotop觀察,發現tp_osd_tp線程已經沒有IO寫入了,達到了預期,可是此時fio的延時仍然沒有減小,以下:

write: IOPS=1637, BW=6548KiB/s (6705kB/s)(384MiB/60072msec); 0 zone resets
    slat (nsec): min=1258, max=165928, avg=2967.35, stdev=3197.09
    clat (usec): min=1459, max=912584, avg=78184.47, stdev=66475.08
     lat (usec): min=1483, max=912587, avg=78187.44, stdev=66474.83

# osd
  "op_w_latency": {
            "avgcount": 98340,
            "sum": 7232.328193895,
            "avgtime": 0.073544114
        },
        "op_w_process_latency": {
            "avgcount": 98340,
            "sum": 7225.606938417,
            "avgtime": 0.073475767
        },
        "op_before_queue_op_lat": {
            "avgcount": 98405,
            "sum": 3.198549769,
            "avgtime": 0.000032503
        },
        "op_before_dequeue_op_lat": {
            "avgcount": 98625,
            "sum": 63.275331523,
            "avgtime": 0.000641574
        },
# bluestore
      "state_kv_queued_lat": {
            "avgcount": 98376,
            "sum": 149.342751192,
            "avgtime": 0.001518081
        },
        "state_kv_commiting_lat": {
            "avgcount": 98376,
            "sum": 7050.792108282,
            "avgtime": 0.071671872
        },
         "throttle_lat": {
            "avgcount": 98376,
            "sum": 0.119449686,
            "avgtime": 0.000001214
        },
        "submit_lat": {
            "avgcount": 98376,
            "sum": 4.676406535,
            "avgtime": 0.000047536
        },
        "commit_lat": {
            "avgcount": 98376,
            "sum": 7204.522819923,
            "avgtime": 0.073234557
        },

此時op_before_dequeue_op_lat的延時已經不多了,可是延時的大頭又到了state_kv_commiting_lat階段,接下來就要看這個階段的代碼邏輯了:

void BlueStore::_kv_sync_thread() 
{
     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);
       }
       txc->state = TransContext::STATE_KV_SUBMITTED;
     }

     // other
}

void BlueStore::_kv_finalize_thread()
{    
  while (true) {
   if (kv_committing_to_finalize.empty() &&deferred_stable_to_finalize.empty()) {
      kv_finalize_cond.wait(l);
   } else {
    while (!kv_committed.empty()) {
      TransContext *txc = kv_committed.front();
       // 在這個函數裏面會調用 _txc_committed_kv 函數,該函數表示寫io完成。
       // 最後會調用txc->log_state_latency(logger, l_bluestore_state_kv_committing_lat);也即從這裏到上面的txc->log_state_latency(logger, l_bluestore_state_kv_queued_lat);程序語句中間便是state_kv_commiting_lat階段
      _txc_state_proc(txc);  
      kv_committed.pop_front();
    }

    for (auto b : deferred_stable) {
      auto p = b->txcs.begin();
      while (p != b->txcs.end()) {
         TransContext *txc = &*p;
         p = b->txcs.erase(p); // unlink here because
        _txc_state_proc(txc); // this may destroy txc
      }
      delete b;
    }
    deferred_stable.clear();

    if (!deferred_aggressive) {
      if (deferred_queue_size >= deferred_batch_ops.load() ||throttle_deferred_bytes.past_midpoint()) {
        deferred_try_submit();
      }
    }
  }
}

state_kv_commiting_lat耗時表示的是txc->log_state_latency(logger, l_bluestore_state_kv_queued_lat)到txc->log_state_latency(logger, l_bluestore_state_kv_committing_lat)階段的耗時,因此分析這之間的程序語句。這個階段也包括線程切換。

仔細查看該階段代碼,始終沒想出來哪一個地方會耗時這麼多。而後意識到_kv_finalize_thread是信號驅動的線程,那麼有可能信號即便來了,因爲該線程一直在忙其餘的事情,txc->log_state_latency(logger, l_bluestore_state_kv_queued_lat)階段前面的任務也沒有處理。

因此繼續查看_kv_finalize_thread函數txc->log_state_latency(logger, l_bluestore_state_kv_queued_lat)以後的代碼,這一看,就看到了熟悉的函數deferred_try_submit,那麼顯然在線程裏面存在IO提交,使用iotop,發現確實有_kv_finalize_thread線程。以下:

78739 be/4 ceph        0.00 B/s    5.36 M/s  0.00 % 97.70 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [bstore_kv_final]
    633 be/3 root        0.00 B/s    7.31 K/s  0.00 %  5.61 % [jbd2/sda2-8]
   3937 be/4 root        0.00 B/s    7.31 K/s  0.00 %  3.31 % kubelet --node-ip 10.185.0.69 --allowed-unsafe-sysctls=kern~.io/master=,beta.kubernetes.io/instance-type=bareMetal -v=4
  13694 be/4 root        0.00 B/s   21.92 K/s  0.00 %  2.98 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   4796 be/4 root        0.00 B/s    3.65 K/s  0.00 %  1.01 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   5101 be/4 root        0.00 B/s    7.31 K/s  0.00 %  0.93 % kube-apiserver --authorization-mode=Node,RBAC --advertise-a~tls-private-key-file=/etc/kubernetes/pki/apiserver.key -v=4
 444633 be/4 ceph        0.00 B/s    7.31 K/s  0.00 %  0.89 % ceph-mon -f --cluster ceph --id pubt1-ceph69 --setuser ceph --setgroup ceph [fn_monstore]
 444638 be/4 ceph        0.00 B/s    7.31 K/s  0.00 %  0.81 % ceph-mon -f --cluster ceph --id pubt1-ceph69 --setuser ceph --setgroup ceph [safe_timer]
  24086 be/4 root        0.00 B/s    7.31 K/s  0.00 %  0.63 % etcd --advertise-client-urls=http://10.185.0.69:2379 --auto~=etcd1 --peer-client-cert-auth=false --snapshot-count=10000
   1385 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.62 % rsyslogd -n -iNONE [rs:main Q:Reg]
   2452 be/4 root        0.00 B/s    3.65 K/s  0.00 %  0.00 % dockerd -H fd:// --containerd=/run/containerd/containerd.sock
  78738 be/4 ceph        0.00 B/s    8.66 M/s  0.00 %  0.00 % ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph [bstore_kv_sync]
 706587 be/4 ceph        0.00 B/s    3.65 K/s  0.00 %  0.00 % ceph-osd -f --cluster ceph --id 0 --setuser ceph --setgroup ceph [bstore_kv_sync]

在kv_sync_thread線程中已經完成了元數據的提交,而後其經過信號量通知_kv_finalize_thread線程遍歷已經完成元數據提交的IO,處理接下來的IO回覆。

仔細思考發現這裏存在一個比較大的時間剪刀差:由於kv_sync_thread提交元數據到SSD,而_kv_finalize_thread線程是提交IO到HDD; 因此這裏就想到能夠把IO回覆這個動做遷移到kv_sync_thread線程中處理。

另外,在某些場景下發現會因爲kv_flush_lat耗時比較長,而後致使排隊時間state_kv_queued_lat耗時較長,並進一步致使寫耗時較長的問題,以下:

"op_w_latency": {
            "avgcount": 137259,
            "sum": 13948.293318081,
            "avgtime": 0.101620245
        },
        "op_w_process_latency": {
            "avgcount": 137259,
            "sum": 9339.461465035,
            "avgtime": 0.068042616
        },
        "op_w_prepare_latency": {
            "avgcount": 137259,
            "sum": 399.964157986,
            "avgtime": 0.002913937
        },

        "kv_flush_lat": {
            "avgcount": 27184,
            "sum": 172.947431577,
            "avgtime": 0.006362103
        },
        "kv_commit_lat": {
            "avgcount": 27184,
            "sum": 5.953508519,
            "avgtime": 0.000219007
        },
        "kv_lat": {
            "avgcount": 27184,
            "sum": 178.900940096,
            "avgtime": 0.006581111
        },
       "state_kv_queued_lat": {
            "avgcount": 142499,
            "sum": 8800.295888690,
            "avgtime": 0.061756895
        },
        "state_kv_commiting_lat": {
            "avgcount": 142499,
            "sum": 94.338791604,
            "avgtime": 0.000662031
        },
        "commit_lat": {
            "avgcount": 142499,
            "sum": 9284.327304726,
            "avgtime": 0.065153631
        },

該問題的解決方案就如性能優化初覽一節中所說,對於defer write,把其flush移動到_deferred_aio_finish函數裏便可。

經過上述代碼修改,發現fio已經能夠上去了。可是接下來會有一系列的新問題,好比調整pg_num會形成IO hang死,遷移邏輯pool會致使OSD掛掉,OSD異常會致使IO長時間卡死等問題,目前這些問題已經所有解決。

性能優化結果

本次優化對於大寫(默認64kb以上)沒有優化效果,如下是4k隨機寫的性能對比圖,

從優化結果能夠看出,優化後而且未達限流前,性能能夠提高數十倍以上,即便達到了限流(限流的參數能夠根據機器內存狀況配置)。

性能優化後的異常問題以及解決

遷移邏輯池時OSD掛掉

  • 現象

遷移邏輯池ceph osd pool set poolname crush_rule rulename時OSD會掛掉,出錯日誌以下:

2021-01-13 14:43:47.187236 7f8e5133f700 -1 *** Caught signal (Segmentation fault) **
8402273  in thread 7f8e5133f700 thread_name:bstore_kv_final
8402274
8402275  ceph version 12.2.12+netease+1.0+pri+buster (a372ba6cea5cfc83ebfac2204aba6a2225a7263c) luminous (stable)
8402276  1: (ceph::BackTrace::BackTrace(int)+0x45) [0x55ae756f1cad]
8402277  2: (()+0x227da4b) [0x55ae7598ba4b]
8402278  3: (()+0x12730) [0x7f8e59c54730]
8402279  4: (coll_t::to_str[abi:cxx11]() const+0x30) [0x55ae750c4160]
8402280  5: (operator<<(std::ostream&, coll_t const&)+0x33) [0x55ae750c42da]
8402281  6: (BlueStore::_reap_collections()+0x22c) [0x55ae757cdb6e]
8402282  7: (BlueStore::_kv_finalize_thread()+0x11de) [0x55ae757ec71a]
8402283  8: (BlueStore::KVFinalizeThread::entry()+0x1c) [0x55ae75822ee8]
8402284  9: (Thread::entry_wrapper()+0xc1) [0x55ae75bc43df]
8402285  10: (Thread::_entry_func(void*)+0x18) [0x55ae75bc4314]
8402286  11: (()+0x7fa3) [0x7f8e59c49fa3]
8402287  12: (clone()+0x3f) [0x7f8e596344cf]
  • 緣由
_kv_finalize_thread
--> _txc_state_proc
  --> _txc_finish
    --> _queue_reap_collection
      --> removed_collections.push_back(c);
--> _reap_collections
  --> removed_colls.swap(removed_collections);

如上,能夠看到,在Ceph的原生版本中,對於removed_collections的push以及pop操做是在_kv_finalize_thread這同一個線程中,因此操做這個隊列時沒有加鎖。

性能優化代碼裏把_txc_state_proc的操做移動到了_kv_sync_thread函數,可是_reap_collections卻沒有移動,因此出現了這個問題

  • 解決辦法

把_reap_collections函數也一併移動到_kv_sync_thread

OSD異常退出

  • 問題

若是在OSD退出時,有defer write的數據沒有下刷到數據盤,那麼OSD重啓後,會調用+deferred_replay函數進行回放。若是要回放的數據量太大,那麼即便是Ceph的原生版本也是會有問題的,這個問題也向社區提了tracker: https://tracker.ceph.com/issues/48696 48696

  • 解決辦法

submit_batch形參中的uint16_t aios_size修改成uint64_t aios_size;

另外爲了防止函數裏面piocb數組的棧溢出,能夠設置一次提交的最大aio數量,若是aio數量過多,能夠在這個函數裏面進行循環屢次提交。

數據盤被打滿

  • 問題

因爲性能優化後會儘量地完成defer write的元數據落盤並返回客戶寫成功。因此數據一直在經過_kv_finalize_thread調用deferred_try_submit進行提交。 那麼數據盤一直會被持續打滿。

數據盤被持續打滿時,若是此時有big write帶來的非defer write或者是讀,或者是數據恢復,那麼顯然這些IO都會遲遲得不到響應。

  • 解決辦法

在上面的OSD異常退出一小節咱們提到,咱們會對aio進行循環屢次提交。 因此基於此咱們能夠引入兩個可配置參數分別控制一次提交的aio數量以及每次提交中間的sleep時間。這樣就能夠很好地經過控制因defer write帶來的磁盤IO繁忙程度了。

本次修改的其餘問題

  • 問題

上述經過引入兩個參數控制了submit_batch函數提交aio的形態。可是問題在於這個函數是通用函數,除了defer write的數據盤最終會調用這個函數來落盤,big write以及WAL、DB對應的盤都要經過這個函數落盤,而且OSD啓動時若是須要調用deferred_replay來回放,也會掉歐陽那個這個函數,可是這些場景咱們是須要他正常來作的。

  • 解決辦法

引入bool值block,只有當它爲true時,在submit_batch中才會sleep。 另外經過在KernelDevice::aio_submit函數中控制block是true仍是false,具體增長代碼以下:

+  // only defer write and the block device can be blocked(sleep)
+  string::size_type wal = path.find("block.wal");
+  string::size_type db = path.find("block.db");
+  bool block = false;
+  if (enable_wal_db_perf_optimize && wal == string::npos
+   && db == string::npos && ioc->defer_write) {
+    dout(20) << __func__ << "path is: " <<  path << dendl;
+    block = true;
+  } else {
+    dout(20) << __func__ << "path is: : "  <<  path << dendl;
+  }

   void *priv = static_cast<void*>(ioc);
   int r, retries = 0;
   r = aio_queue.submit_batch(ioc->running_aios.begin(), e,

fio過程當中io hang狀況嚴重

  • 現象

fio過程當中,IO會長時間hang住,而後IO恢復,很快又hang住,循環反覆。

  • 緣由

IO過程當中BlueStore::queue_transactions函數會獲取本次IO須要的限流值,若是已經達到限流閾值,那麼須要等待限流釋放。

限流的釋放在_deferred_aio_finish函數中,可是隻有當一個osr裏面的全部defer write(deferred_pending隊列)提交給磁盤而且完成後,纔會被調用。咱們優化後的代碼因爲IO一直在儘量提交,因此deferred_pending隊列可能比較長,那麼當_deferred_submit_unlock函數把他們提交給磁盤後,可能要較長時間全部的IO才能得以完成.那麼限流可能長時間得不到釋放

  • 解決辦法

對於defer write,每一個IO完成後都調用下_deferred_aio_finish,若是IO沒有所有完成,那麼僅僅只作釋放部分限流的工做。

另外,因爲defer write以及非defer write以及OSD啓動replay使用的都是同一套磁盤IO模型,因此要解決好通用問題,也即只有是defer write時,才能夠在IO沒有所有完成,也調用下回調函數。

調整pg num時OSD掛掉

  • 問題

用指令ceph osd pool set poolname pg_num時,fio卡死,增長的pg狀態一直 成爲不了active+clean ,最終OSD掛掉。

pg分裂,peering線程中最終會調用_split_collection函數, 進而會調用_osr_drain_preceding(txc)函數, 該函數會把全部的deferred op下刷到數據盤。

若是deferred_queue很長,致使下刷時間超過了osd_op_thread_suicide_timeout,那麼peering線程便會超時致使OSD掛掉。

  • 解決辦法

經過使得默認不能分裂。 而且在ceph osd perf指令中添加OSD的defer長度這以顯示項,當OSD中的defer長度不是太長時 ,才進行pg的分裂操做。

fio過程當中OSD掛掉

  • 問題

fio過程當中OSD會掛掉,緣由是_txc_finish中會調用deferred_try_submit, 修改後的性能優化版本因爲儘量完成IO,因此可能致使該函數耗時很長。

  • 解決辦法

txc_finish中去掉deferred_try_submit的調用。

其餘說明

中止一個OSD以前,down掉後還會調用deferred_try_submit刷全部的defer wrirte,若是defer比較多,超過了90s,那麼OSD便會被殺掉 (由於若是默認90s進程尚未退出,就會發送kill -9 https://unix.stackexchange.com/questions/310146/how-do-i-skip-the-90s-timeout-in-systemd )

可是這個無所謂,反正osd start的時候還會從新作repaly。

代碼提交

混合盤性能優化github地址

使用說明

上述代碼修改相關功能默認是沒有開啓的。若是要開啓,能夠修改以下配置並重啓OSD:

bluestore_wal_db_perf_optimize = true
bluestore_throttle_bytes = 67108864000 # 根據機器內存配置
bluestore_throttle_deferred_bytes = 134217728000

說明

本文純屬記錄一下本身性能分析和優化的一些過程及思路,鑑於水平有限,若是有錯漏的地方,期待與你們一塊兒交流。

 

做者:吳宏鬆,網易數帆存儲技術專家

相關文章
相關標籤/搜索