Ceph性能瓶颈分析与优化(混合盘篇)

背景

企业级存储中,SSD+HDD的混合盘是一种典型应用场景,可以兼顾成本、性能与容量。

但网易数帆存储团队经过测试(4k随机写)发现,加了NVMe SSD做Ceph的WAL和DB后,性能提升不足一倍且NVMe盘性能余量较大。所以我们希望通过瓶颈分析,探讨能够进一步提升性能的优化方案。

测试环境

Ceph性能分析一般先用单OSD来分析,这样可以屏蔽很多方面的干扰。 我们的测试环境如下所示,1个OSD:

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

性能优化初览

Ceph性能瓶颈分析与优化(混合盘篇)

上图主要是BlueStore 对于defer write写的一个总体流程。可以看到这里的性能优化主要是两个点:

  1. 重耗时模块影响上下文

问题:返回客户端写成功函数+落盘在同一个线程(_kv_finalize_thread)
优化:返回写成功前保证元数据写成功即可,故可把这两个阶段拆分到不同的线程:

  1. 重耗时模块在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入队列之前的时间:

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耗时很短,这可以说明耗时主要花费在工作线程入队到出队这块。

基于这个认识,所以首先考虑到的便是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随机写的性能对比图,

Ceph性能瓶颈分析与优化(混合盘篇)

从优化结果可以看出,优化后并且未达限流前,性能可以提升数十倍以上,即使达到了限流(限流的参数可以根据机器内存情况配置)。

性能优化后的异常问题以及解决

迁移逻辑池时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

说明

本文纯属记录一下自己性能分析和优化的一些过程及思路,鉴于水平有限,如果有错漏的地方,期待与大家一起交流。

作者:吴宏松,网易数帆存储技术专家

上一篇:2.MMKV的基本使用


下一篇:vivo工具开发在线计算语言模型打分