]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
os/bluestore/AvlAllocator: introduce bluestore_avl_alloc_ff_max_search_count
authorKefu Chai <kchai@redhat.com>
Wed, 2 Jun 2021 07:57:04 +0000 (15:57 +0800)
committerKefu Chai <kchai@redhat.com>
Mon, 21 Jun 2021 14:10:26 +0000 (22:10 +0800)
so AvlAllocator can switch from the first-first mode to best-fit mode
without walking through the whole space map tree. in the
highly-fragmented system, iterating the whole tree could hurt the
performance of fast storage system a lot.

the idea comes from openzfs's metaslab allocator.

Signed-off-by: Kefu Chai <kchai@redhat.com>
src/common/options/global.yaml.in
src/os/bluestore/AvlAllocator.cc
src/os/bluestore/AvlAllocator.h

index 7a69fee070fe5c68b73ef970dfb3afa8c4aac3b6..05d7d38234a3303ddf06d6aa8328d89edeea6aee 100644 (file)
@@ -5029,6 +5029,12 @@ options:
   - hdd
   - ssd
   with_legacy: true
+- name: bluestore_avl_alloc_ff_max_search_count
+  type: uint
+  level: dev
+  desc: Search for this many ranges in first-fit mode before switching over to
+    to best-fit mode. 0 to iterate through all ranges for required chunk.
+  default: 100
 - name: bluestore_avl_alloc_bf_threshold
   type: uint
   level: dev
index f3b721e87658ee117d5d33258dc2d98842962332..1d463b31a5546b02f238bb75781431e8a55d5b6b 100644 (file)
@@ -34,6 +34,7 @@ uint64_t AvlAllocator::_pick_block_after(uint64_t *cursor,
                                         uint64_t align)
 {
   const auto compare = range_tree.key_comp();
+  uint32_t search_count = 0;
   auto rs_start = range_tree.lower_bound(range_t{*cursor, size}, compare);
   for (auto rs = rs_start; rs != range_tree.end(); ++rs) {
     uint64_t offset = p2roundup(rs->start, align);
@@ -41,6 +42,9 @@ uint64_t AvlAllocator::_pick_block_after(uint64_t *cursor,
       *cursor = offset + size;
       return offset;
     }
+    if (max_search_count > 0 && ++search_count > max_search_count) {
+      return -1ULL;
+    }
   }
   if (*cursor == 0) {
     // If we already started from beginning, don't bother with searching from beginning
@@ -53,6 +57,9 @@ uint64_t AvlAllocator::_pick_block_after(uint64_t *cursor,
       *cursor = offset + size;
       return offset;
     }
+    if (max_search_count > 0 && ++search_count > max_search_count) {
+      return -1ULL;
+    }
   }
   return -1ULL;
 }
@@ -240,13 +247,27 @@ int AvlAllocator::_allocate(
 
   const int free_pct = num_free * 100 / device_size;
   uint64_t start = 0;
-  /*
-   * If we're running low on space switch to using the size
-   * sorted AVL tree (best-fit).
-   */
+  // If we're running low on space, find a range by size by looking up in the size
+  // sorted tree (best-fit), instead of searching in the area pointed by cursor
   if (force_range_size_alloc ||
       max_size < range_size_alloc_threshold ||
       free_pct < range_size_alloc_free_pct) {
+    start = -1ULL;
+  } else {
+    /*
+     * Find the largest power of 2 block size that evenly divides the
+     * requested size. This is used to try to allocate blocks with similar
+     * alignment from the same area (i.e. same cursor bucket) but it does
+     * not guarantee that other allocations sizes may exist in the same
+     * region.
+     */
+    uint64_t align = size & -size;
+    ceph_assert(align != 0);
+    uint64_t* cursor = &lbas[cbits(align) - 1];
+    start = _pick_block_after(cursor, size, unit);
+    dout(20) << __func__ << " first fit=" << start << " size=" << size << dendl;
+  }
+  if (start == -1ULL) {
     do {
       start = _pick_block_fits(size, unit);
       dout(20) << __func__ << " best fit=" << start << " size=" << size << dendl;
@@ -257,25 +278,6 @@ int AvlAllocator::_allocate(
       // that large block due to misaligned extents
       size = p2align(size >> 1, unit);
     } while (size >= unit);
-  } else {
-    do {
-      /*
-       * Find the largest power of 2 block size that evenly divides the
-       * requested size. This is used to try to allocate blocks with similar
-       * alignment from the same area (i.e. same cursor bucket) but it does
-       * not guarantee that other allocations sizes may exist in the same
-       * region.
-       */
-      uint64_t* cursor = &lbas[cbits(size) - 1];
-      start = _pick_block_after(cursor, size, unit);
-      dout(20) << __func__ << " first fit=" << start << " size=" << size << dendl;
-      if (start != uint64_t(-1ULL)) {
-        break;
-      }
-      // try to collect smaller extents as we could fail to retrieve
-      // that large block due to misaligned extents
-      size = p2align(size >> 1, unit);
-    } while (size >= unit);
   }
   if (start == -1ULL) {
     return -ENOSPC;
@@ -328,6 +330,8 @@ AvlAllocator::AvlAllocator(CephContext* cct,
     cct->_conf.get_val<uint64_t>("bluestore_avl_alloc_bf_threshold")),
   range_size_alloc_free_pct(
     cct->_conf.get_val<uint64_t>("bluestore_avl_alloc_bf_free_pct")),
+  max_search_count(
+    cct->_conf.get_val<uint64_t>("bluestore_avl_alloc_ff_max_search_count")),
   range_count_cap(max_mem / sizeof(range_seg_t)),
   cct(cct)
 {}
index bb4c44ea0a5421f13f77f849a5f0b44de307a132..f813fa41c825ac822f8fb88a76daf14d8d7e74eb 100644 (file)
@@ -158,7 +158,12 @@ private:
    * switch to using best-fit allocations.
    */
   int range_size_alloc_free_pct = 0;
-
+  /*
+   * Maximum number of segments to check in the first-fit mode, without this
+   * limit, fragmented device can see lots of iterations and _block_picker()
+   * becomes the performance limiting factor on high-performance storage.
+   */
+  const uint32_t max_search_count;
   /*
   * Max amount of range entries allowed. 0 - unlimited
   */