]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/crimson/os/seastore/onode_manager/staged-fltree/tree_utils.h
import quincy beta 17.1.0
[ceph.git] / ceph / src / crimson / os / seastore / onode_manager / staged-fltree / tree_utils.h
index 536052003df4d16b265e20ddb41478b3e873c8ab..e061cf8c53daee2f15e015b6dd63f859cfcd1258 100644 (file)
@@ -11,6 +11,8 @@
 #include <utility>
 #include <vector>
 
+#include <seastar/core/thread.hh>
+
 #include "crimson/common/log.h"
 #include "stages/key_layout.h"
 #include "tree.h"
 
 namespace crimson::os::seastore::onode {
 
-class Onodes {
+/**
+ * templates to work with tree utility classes:
+ *
+ * struct ValueItem {
+ *   <public members>
+ *
+ *   value_size_t get_payload_size() const;
+ *   static ValueItem create(std::size_t expected_size, std::size_t id);
+ * };
+ * std::ostream& operator<<(std::ostream& os, const ValueItem& item);
+ *
+ * class ValueImpl final : public Value {
+ *   ...
+ *
+ *   using item_t = ValueItem;
+ *   void initialize(Transaction& t, const item_t& item);
+ *   void validate(const item_t& item);
+ * };
+ *
+ */
+
+template <typename CursorType>
+void initialize_cursor_from_item(
+    Transaction& t,
+    const ghobject_t& key,
+    const typename decltype(std::declval<CursorType>().value())::item_t& item,
+    CursorType& cursor,
+    bool insert_success) {
+  ceph_assert(insert_success);
+  ceph_assert(!cursor.is_end());
+  ceph_assert(cursor.get_ghobj() == key);
+  auto tree_value = cursor.value();
+  tree_value.initialize(t, item);
+}
+
+
+template <typename CursorType>
+void validate_cursor_from_item(
+    const ghobject_t& key,
+    const typename decltype(std::declval<CursorType>().value())::item_t& item,
+    CursorType& cursor) {
+  ceph_assert(!cursor.is_end());
+  ceph_assert(cursor.get_ghobj() == key);
+  auto tree_value = cursor.value();
+  tree_value.validate(item);
+}
+
+template <typename ValueItem>
+class Values {
  public:
-  Onodes(size_t n) {
+  Values(size_t n) {
     for (size_t i = 1; i <= n; ++i) {
-      auto p_onode = &create(i * 8);
-      onodes.push_back(p_onode);
+      auto item = create(i * 8);
+      values.push_back(item);
     }
   }
 
-  Onodes(std::vector<size_t> sizes) {
+  Values(std::vector<size_t> sizes) {
     for (auto& size : sizes) {
-      auto p_onode = &create(size);
-      onodes.push_back(p_onode);
+      auto item = create(size);
+      values.push_back(item);
     }
   }
 
-  ~Onodes() = default;
+  ~Values() = default;
 
-  const onode_t& create(size_t size) {
-    ceph_assert(size <= std::numeric_limits<uint16_t>::max());
-    onode_t config{static_cast<uint16_t>(size), id++};
-    auto onode = onode_t::allocate(config);
-    auto p_onode = onode.get();
-    tracked_onodes.push_back(std::move(onode));
-    return *reinterpret_cast<onode_t*>(p_onode);
+  ValueItem create(size_t size) {
+    return ValueItem::create(size, id++);
   }
 
-  const onode_t& pick() const {
-    auto index = rd() % onodes.size();
-    return *onodes[index];
-  }
-
-  const onode_t& pick_largest() const {
-    return *onodes[onodes.size() - 1];
-  }
-
-  static void validate_cursor(
-      const Btree::Cursor& cursor, const ghobject_t& key, const onode_t& onode) {
-    ceph_assert(!cursor.is_end());
-    ceph_assert(cursor.get_ghobj() == key);
-    ceph_assert(cursor.value());
-    ceph_assert(cursor.value() != &onode);
-    ceph_assert(*cursor.value() == onode);
-    onode_t::validate_tail_magic(*cursor.value());
+  ValueItem pick() const {
+    auto index = rd() % values.size();
+    return values[index];
   }
 
  private:
-  uint16_t id = 0;
+  std::size_t id = 0;
   mutable std::random_device rd;
-  std::vector<const onode_t*> onodes;
-  std::vector<std::unique_ptr<char[]>> tracked_onodes;
+  std::vector<ValueItem> values;
 };
 
+template <typename ValueItem>
 class KVPool {
-  struct kv_conf_t {
-    unsigned index2;
-    unsigned index1;
-    unsigned index0;
-    size_t ns_size;
-    size_t oid_size;
-    const onode_t* p_value;
-
-    ghobject_t get_ghobj() const {
-      assert(index1 < 10);
-      std::ostringstream os_ns;
-      os_ns << "ns" << index1;
-      unsigned current_size = (unsigned)os_ns.tellp();
-      assert(ns_size >= current_size);
-      os_ns << std::string(ns_size - current_size, '_');
+ public:
+  struct kv_t {
+    ghobject_t key;
+    ValueItem value;
+  };
+  using kv_vector_t = std::vector<kv_t>;
+  using kvptr_vector_t = std::vector<kv_t*>;
+  using iterator_t = typename kvptr_vector_t::iterator;
 
-      std::ostringstream os_oid;
-      os_oid << "oid" << index1;
-      current_size = (unsigned)os_oid.tellp();
-      assert(oid_size >= current_size);
-      os_oid << std::string(oid_size - current_size, '_');
+  size_t size() const {
+    return kvs.size();
+  }
+
+  iterator_t begin() {
+    return serial_p_kvs.begin();
+  }
+  iterator_t end() {
+    return serial_p_kvs.end();
+  }
+  iterator_t random_begin() {
+    return random_p_kvs.begin();
+  }
+  iterator_t random_end() {
+    return random_p_kvs.end();
+  }
 
-      return ghobject_t(shard_id_t(index2), index2, index2,
-                        os_ns.str(), os_oid.str(), index0, index0);
+  void shuffle() {
+    std::random_shuffle(random_p_kvs.begin(), random_p_kvs.end());
+  }
+
+  void erase_from_random(iterator_t begin, iterator_t end) {
+    random_p_kvs.erase(begin, end);
+    kv_vector_t new_kvs;
+    for (auto p_kv : random_p_kvs) {
+      new_kvs.emplace_back(*p_kv);
     }
-  };
-  using kv_vector_t = std::vector<kv_conf_t>;
+    std::sort(new_kvs.begin(), new_kvs.end(), [](auto& l, auto& r) {
+      return l.key < r.key;
+    });
 
- public:
-  using kv_t = std::pair<ghobject_t, const onode_t*>;
-
-  KVPool(const std::vector<size_t>& str_sizes,
-         const std::vector<size_t>& onode_sizes,
-         const std::pair<unsigned, unsigned>& range2,
-         const std::pair<unsigned, unsigned>& range1,
-         const std::pair<unsigned, unsigned>& range0)
-      : str_sizes{str_sizes}, onodes{onode_sizes} {
+    kvs.swap(new_kvs);
+    serial_p_kvs.resize(kvs.size());
+    random_p_kvs.resize(kvs.size());
+    init();
+  }
+
+  static KVPool create_raw_range(
+      const std::vector<size_t>& ns_sizes,
+      const std::vector<size_t>& oid_sizes,
+      const std::vector<size_t>& value_sizes,
+      const std::pair<index_t, index_t>& range2,
+      const std::pair<index_t, index_t>& range1,
+      const std::pair<index_t, index_t>& range0) {
     ceph_assert(range2.first < range2.second);
-    ceph_assert(range2.second - 1 <= (unsigned)std::numeric_limits<shard_t>::max());
-    ceph_assert(range2.second - 1 <= std::numeric_limits<crush_hash_t>::max());
+    ceph_assert(range2.second - 1 <= MAX_SHARD);
+    ceph_assert(range2.second - 1 <= MAX_CRUSH);
     ceph_assert(range1.first < range1.second);
     ceph_assert(range1.second - 1 <= 9);
     ceph_assert(range0.first < range0.second);
+
+    kv_vector_t kvs;
     std::random_device rd;
-    for (unsigned i = range2.first; i < range2.second; ++i) {
-      for (unsigned j = range1.first; j < range1.second; ++j) {
-        auto ns_size = (unsigned)str_sizes[rd() % str_sizes.size()];
-        auto oid_size = (unsigned)str_sizes[rd() % str_sizes.size()];
-        for (unsigned k = range0.first; k < range0.second; ++k) {
-          kvs.emplace_back(kv_conf_t{i, j, k, ns_size, oid_size, &onodes.pick()});
+    Values<ValueItem> values{value_sizes};
+    for (index_t i = range2.first; i < range2.second; ++i) {
+      for (index_t j = range1.first; j < range1.second; ++j) {
+        size_t ns_size;
+        size_t oid_size;
+        if (j == 0) {
+          // store ns0, oid0 as empty strings for test purposes
+          ns_size = 0;
+          oid_size = 0;
+        } else {
+          ns_size = ns_sizes[rd() % ns_sizes.size()];
+          oid_size = oid_sizes[rd() % oid_sizes.size()];
+          assert(ns_size && oid_size);
+        }
+        for (index_t k = range0.first; k < range0.second; ++k) {
+          kvs.emplace_back(
+              kv_t{make_raw_oid(i, j, k, ns_size, oid_size), values.pick()}
+          );
         }
       }
     }
-    random_kvs = kvs;
-    std::random_shuffle(random_kvs.begin(), random_kvs.end());
+    return KVPool(std::move(kvs));
   }
 
-  class iterator_t {
-   public:
-    iterator_t() = default;
-    iterator_t(const iterator_t&) = default;
-    iterator_t(iterator_t&&) = default;
-    iterator_t& operator=(const iterator_t&) = default;
-    iterator_t& operator=(iterator_t&&) = default;
-
-    kv_t get_kv() const {
-      assert(!is_end());
-      auto& conf = (*p_kvs)[i];
-      return std::make_pair(conf.get_ghobj(), conf.p_value);
-    }
-    bool is_end() const { return !p_kvs || i >= p_kvs->size(); }
-    size_t index() const { return i; }
-
-    iterator_t& operator++() {
-      assert(!is_end());
-      ++i;
-      return *this;
+  static KVPool create_range(
+      const std::pair<index_t, index_t>& range_i,
+      const std::vector<size_t>& value_sizes) {
+    kv_vector_t kvs;
+    std::random_device rd;
+    for (index_t i = range_i.first; i < range_i.second; ++i) {
+      auto value_size = value_sizes[rd() % value_sizes.size()];
+      kvs.emplace_back(
+          kv_t{make_oid(i), ValueItem::create(value_size, i)}
+      );
     }
+    return KVPool(std::move(kvs));
+  }
 
-    iterator_t operator++(int) {
-      iterator_t tmp = *this;
-      ++*this;
-      return tmp;
-    }
+ private:
+  KVPool(kv_vector_t&& _kvs)
+      : kvs(std::move(_kvs)), serial_p_kvs(kvs.size()), random_p_kvs(kvs.size()) {
+    init();
+  }
 
-   private:
-    iterator_t(const kv_vector_t& kvs) : p_kvs{&kvs} {}
+  void init() {
+    std::transform(kvs.begin(), kvs.end(), serial_p_kvs.begin(),
+                   [] (kv_t& item) { return &item; });
+    std::transform(kvs.begin(), kvs.end(), random_p_kvs.begin(),
+                   [] (kv_t& item) { return &item; });
+    shuffle();
+  }
 
-    const kv_vector_t* p_kvs = nullptr;
-    size_t i = 0;
-    friend class KVPool;
-  };
+  static ghobject_t make_raw_oid(
+      index_t index2, index_t index1, index_t index0,
+      size_t ns_size, size_t oid_size) {
+    assert(index1 < 10);
+    std::ostringstream os_ns;
+    std::ostringstream os_oid;
+    if (index1 == 0) {
+      assert(!ns_size);
+      assert(!oid_size);
+    } else {
+      os_ns << "ns" << index1;
+      auto current_size = (size_t)os_ns.tellp();
+      assert(ns_size >= current_size);
+      os_ns << std::string(ns_size - current_size, '_');
 
-  iterator_t begin() const {
-    return iterator_t(kvs);
-  }
+      os_oid << "oid" << index1;
+      current_size = (size_t)os_oid.tellp();
+      assert(oid_size >= current_size);
+      os_oid << std::string(oid_size - current_size, '_');
+    }
 
-  iterator_t random_begin() const {
-    return iterator_t(random_kvs);
+    return ghobject_t(shard_id_t(index2), index2, index2,
+                      os_ns.str(), os_oid.str(), index0, index0);
   }
 
-  size_t size() const {
-    return kvs.size();
+  static ghobject_t make_oid(index_t i) {
+    std::stringstream ss;
+    ss << "object_" << i;
+    auto ret = ghobject_t(
+      hobject_t(
+        sobject_t(ss.str(), CEPH_NOSNAP)));
+    ret.set_shard(shard_id_t(0));
+    ret.hobj.nspace = "asdf";
+    return ret;
   }
 
- private:
-  std::vector<size_t> str_sizes;
-  Onodes onodes;
   kv_vector_t kvs;
-  kv_vector_t random_kvs;
+  kvptr_vector_t serial_p_kvs;
+  kvptr_vector_t random_p_kvs;
 };
 
-template <bool TRACK>
+template <bool TRACK, typename ValueImpl>
 class TreeBuilder {
  public:
-  using ertr = Btree::btree_ertr;
-  template <class ValueT=void>
-  using future = ertr::future<ValueT>;
+  using BtreeImpl = Btree<ValueImpl>;
+  using BtreeCursor = typename BtreeImpl::Cursor;
+  using ValueItem = typename ValueImpl::item_t;
+  using iterator_t = typename KVPool<ValueItem>::iterator_t;
 
-  TreeBuilder(KVPool& kvs, NodeExtentManagerURef&& nm)
+  TreeBuilder(KVPool<ValueItem>& kvs, NodeExtentManagerURef&& nm)
       : kvs{kvs} {
     tree.emplace(std::move(nm));
   }
 
-  future<> bootstrap(Transaction& t) {
+  eagain_ifuture<> bootstrap(Transaction& t) {
     std::ostringstream oss;
 #ifndef NDEBUG
     oss << "debug=on, ";
@@ -223,98 +298,251 @@ class TreeBuilder {
     return tree->mkfs(t);
   }
 
-  future<> insert(Transaction& t) {
-    kv_iter = kvs.random_begin();
-    auto cursors = seastar::make_lw_shared<std::vector<Btree::Cursor>>();
-    logger().warn("start inserting {} kvs ...", kvs.size());
-    auto start_time = mono_clock::now();
-    return crimson::do_until([&t, this, cursors]() -> future<bool> {
-      if (kv_iter.is_end()) {
-        return ertr::make_ready_future<bool>(true);
-      }
-      auto [key, p_value] = kv_iter.get_kv();
-      logger().debug("[{}] {} -> {}", kv_iter.index(), key_hobj_t{key}, *p_value);
-      return tree->insert(t, key, *p_value
-      ).safe_then([&t, this, cursors](auto ret) {
-        auto& [cursor, success] = ret;
-        assert(success == true);
-        if constexpr (TRACK) {
-          cursors->emplace_back(cursor);
-        }
+  eagain_ifuture<BtreeCursor> insert_one(
+      Transaction& t, const iterator_t& iter_rd) {
+    auto p_kv = *iter_rd;
+    logger().debug("[{}] insert {} -> {}",
+                   iter_rd - kvs.random_begin(),
+                   key_hobj_t{p_kv->key},
+                   p_kv->value);
+    return tree->insert(
+        t, p_kv->key, {p_kv->value.get_payload_size()}
+    ).si_then([&t, this, p_kv](auto ret) {
+      auto success = ret.second;
+      auto cursor = std::move(ret.first);
+      initialize_cursor_from_item(t, p_kv->key, p_kv->value, cursor, success);
 #ifndef NDEBUG
-        auto [key, p_value] = kv_iter.get_kv();
-        Onodes::validate_cursor(cursor, key, *p_value);
-        return tree->lower_bound(t, key).safe_then([this, cursor](auto cursor_) {
-          auto [key, p_value] = kv_iter.get_kv();
-          ceph_assert(cursor_.get_ghobj() == key);
-          ceph_assert(cursor_.value() == cursor.value());
-          ++kv_iter;
-          return ertr::make_ready_future<bool>(false);
-        });
+      validate_cursor_from_item(p_kv->key, p_kv->value, cursor);
+      return tree->find(t, p_kv->key
+      ).si_then([cursor, p_kv](auto cursor_) mutable {
+        assert(!cursor_.is_end());
+        ceph_assert(cursor_.get_ghobj() == p_kv->key);
+        ceph_assert(cursor_.value() == cursor.value());
+        validate_cursor_from_item(p_kv->key, p_kv->value, cursor_);
+        return cursor;
+      });
 #else
-        ++kv_iter;
-        return ertr::make_ready_future<bool>(false);
+      return eagain_iertr::make_ready_future<BtreeCursor>(cursor);
 #endif
-      });
-    }).safe_then([&t, this, start_time, cursors] {
-      std::chrono::duration<double> duration = mono_clock::now() - start_time;
-      logger().warn("Insert done! {}s", duration.count());
+    }).handle_error_interruptible(
+      [] (const crimson::ct_error::value_too_large& e) {
+        ceph_abort("impossible path");
+      },
+      crimson::ct_error::pass_further_all{}
+    );
+  }
+
+  eagain_ifuture<> insert(Transaction& t) {
+    auto ref_kv_iter = seastar::make_lw_shared<iterator_t>();
+    *ref_kv_iter = kvs.random_begin();
+    auto cursors = seastar::make_lw_shared<std::vector<BtreeCursor>>();
+    logger().warn("start inserting {} kvs ...", kvs.size());
+    auto start_time = mono_clock::now();
+    return trans_intr::repeat([&t, this, cursors, ref_kv_iter,
+                            start_time]()
+      -> eagain_ifuture<seastar::stop_iteration> {
+      if (*ref_kv_iter == kvs.random_end()) {
+        std::chrono::duration<double> duration = mono_clock::now() - start_time;
+        logger().warn("Insert done! {}s", duration.count());
+        return seastar::make_ready_future<seastar::stop_iteration>(
+          seastar::stop_iteration::yes);
+      } else {
+        return insert_one(t, *ref_kv_iter
+        ).si_then([cursors, ref_kv_iter] (auto cursor) {
+          if constexpr (TRACK) {
+            cursors->emplace_back(cursor);
+          }
+          ++(*ref_kv_iter);
+          return seastar::stop_iteration::no;
+        });
+      }
+    }).si_then([&t, this, cursors, ref_kv_iter] {
       if (!cursors->empty()) {
         logger().info("Verifing tracked cursors ...");
-        kv_iter = kvs.random_begin();
+        *ref_kv_iter = kvs.random_begin();
         return seastar::do_with(
-            cursors->begin(), [&t, this, cursors](auto& c_iter) {
-          return crimson::do_until([&t, this, &c_iter, cursors]() -> future<bool> {
-            if (kv_iter.is_end()) {
+            cursors->begin(),
+            [&t, this, cursors, ref_kv_iter] (auto& c_iter) {
+          return trans_intr::repeat(
+            [&t, this, &c_iter, cursors, ref_kv_iter] ()
+            -> eagain_ifuture<seastar::stop_iteration> {
+            if (*ref_kv_iter == kvs.random_end()) {
               logger().info("Verify done!");
-              return ertr::make_ready_future<bool>(true);
+              return seastar::make_ready_future<seastar::stop_iteration>(
+                seastar::stop_iteration::yes);
             }
             assert(c_iter != cursors->end());
-            auto [k, v] = kv_iter.get_kv();
+            auto p_kv = **ref_kv_iter;
             // validate values in tree keep intact
-            return tree->lower_bound(t, k).safe_then([this, &c_iter](auto cursor) {
-              auto [k, v] = kv_iter.get_kv();
-              Onodes::validate_cursor(cursor, k, *v);
+            return tree->find(t, p_kv->key).si_then([&c_iter, ref_kv_iter](auto cursor) {
+              auto p_kv = **ref_kv_iter;
+              validate_cursor_from_item(p_kv->key, p_kv->value, cursor);
               // validate values in cursors keep intact
-              Onodes::validate_cursor(*c_iter, k, *v);
-              ++kv_iter;
+              validate_cursor_from_item(p_kv->key, p_kv->value, *c_iter);
+              ++(*ref_kv_iter);
               ++c_iter;
-              return ertr::make_ready_future<bool>(false);
+              return seastar::stop_iteration::no;
             });
           });
         });
       } else {
-        return ertr::now();
+        return eagain_iertr::now();
       }
     });
   }
 
-  future<> get_stats(Transaction& t) {
+  eagain_ifuture<> erase_one(
+      Transaction& t, const iterator_t& iter_rd) {
+    auto p_kv = *iter_rd;
+    logger().debug("[{}] erase {} -> {}",
+                   iter_rd - kvs.random_begin(),
+                   key_hobj_t{p_kv->key},
+                   p_kv->value);
+    return tree->erase(t, p_kv->key
+    ).si_then([&t, this, p_kv] (auto size) {
+      ceph_assert(size == 1);
+#ifndef NDEBUG
+      return tree->contains(t, p_kv->key
+      ).si_then([] (bool ret) {
+        ceph_assert(ret == false);
+      });
+#else
+      return eagain_iertr::now();
+#endif
+    });
+  }
+
+  eagain_ifuture<> erase(Transaction& t, std::size_t erase_size) {
+    assert(erase_size <= kvs.size());
+    kvs.shuffle();
+    auto erase_end = kvs.random_begin() + erase_size;
+    auto ref_kv_iter = seastar::make_lw_shared<iterator_t>();
+    auto cursors = seastar::make_lw_shared<std::map<ghobject_t, BtreeCursor>>();
+    return eagain_iertr::now().si_then([&t, this, cursors, ref_kv_iter] {
+      (void)this; // silence clang warning for !TRACK
+      (void)t; // silence clang warning for !TRACK
+      if constexpr (TRACK) {
+        logger().info("Tracking cursors before erase ...");
+        *ref_kv_iter = kvs.begin();
+        auto start_time = mono_clock::now();
+        return trans_intr::repeat(
+          [&t, this, cursors, ref_kv_iter, start_time] ()
+          -> eagain_ifuture<seastar::stop_iteration> {
+          if (*ref_kv_iter == kvs.end()) {
+            std::chrono::duration<double> duration = mono_clock::now() - start_time;
+            logger().info("Track done! {}s", duration.count());
+            return seastar::make_ready_future<seastar::stop_iteration>(
+              seastar::stop_iteration::yes);
+          }
+          auto p_kv = **ref_kv_iter;
+          return tree->find(t, p_kv->key).si_then([cursors, ref_kv_iter](auto cursor) {
+            auto p_kv = **ref_kv_iter;
+            validate_cursor_from_item(p_kv->key, p_kv->value, cursor);
+            cursors->emplace(p_kv->key, cursor);
+            ++(*ref_kv_iter);
+            return seastar::stop_iteration::no;
+          });
+        });
+      } else {
+        return eagain_iertr::now();
+      }
+    }).si_then([&t, this, ref_kv_iter, erase_end] {
+      *ref_kv_iter = kvs.random_begin();
+      logger().warn("start erasing {}/{} kvs ...",
+                    erase_end - kvs.random_begin(), kvs.size());
+      auto start_time = mono_clock::now();
+      return trans_intr::repeat([&t, this, ref_kv_iter,
+                              start_time, erase_end] ()
+        -> eagain_ifuture<seastar::stop_iteration> {
+        if (*ref_kv_iter == erase_end) {
+          std::chrono::duration<double> duration = mono_clock::now() - start_time;
+          logger().warn("Erase done! {}s", duration.count());
+          return seastar::make_ready_future<seastar::stop_iteration>(
+            seastar::stop_iteration::yes);
+        } else {
+          return erase_one(t, *ref_kv_iter
+          ).si_then([ref_kv_iter] {
+            ++(*ref_kv_iter);
+            return seastar::stop_iteration::no;
+          });
+        }
+      });
+    }).si_then([this, cursors, ref_kv_iter, erase_end] {
+      if constexpr (TRACK) {
+        logger().info("Verifing tracked cursors ...");
+        *ref_kv_iter = kvs.random_begin();
+        while (*ref_kv_iter != erase_end) {
+          auto p_kv = **ref_kv_iter;
+          auto c_it = cursors->find(p_kv->key);
+          ceph_assert(c_it != cursors->end());
+          ceph_assert(c_it->second.is_end());
+          cursors->erase(c_it);
+          ++(*ref_kv_iter);
+        }
+      }
+      kvs.erase_from_random(kvs.random_begin(), erase_end);
+      if constexpr (TRACK) {
+        *ref_kv_iter = kvs.begin();
+        for (auto& [k, c] : *cursors) {
+          assert(*ref_kv_iter != kvs.end());
+          auto p_kv = **ref_kv_iter;
+          validate_cursor_from_item(p_kv->key, p_kv->value, c);
+          ++(*ref_kv_iter);
+        }
+        logger().info("Verify done!");
+      }
+    });
+  }
+
+  eagain_ifuture<> get_stats(Transaction& t) {
     return tree->get_stats_slow(t
-    ).safe_then([this](auto stats) {
+    ).si_then([](auto stats) {
       logger().warn("{}", stats);
     });
   }
 
+  eagain_ifuture<std::size_t> height(Transaction& t) {
+    return tree->height(t);
+  }
+
   void reload(NodeExtentManagerURef&& nm) {
     tree.emplace(std::move(nm));
   }
 
-  future<> validate(Transaction& t) {
-    logger().info("Verifing insertion ...");
+  eagain_ifuture<> validate_one(
+      Transaction& t, const iterator_t& iter_seq) {
+    assert(iter_seq != kvs.end());
+    auto next_iter = iter_seq + 1;
+    auto p_kv = *iter_seq;
+    return tree->find(t, p_kv->key
+    ).si_then([p_kv, &t] (auto cursor) {
+      validate_cursor_from_item(p_kv->key, p_kv->value, cursor);
+      return cursor.get_next(t);
+    }).si_then([next_iter, this] (auto cursor) {
+      if (next_iter == kvs.end()) {
+        ceph_assert(cursor.is_end());
+      } else {
+        auto p_kv = *next_iter;
+        validate_cursor_from_item(p_kv->key, p_kv->value, cursor);
+      }
+    });
+  }
+
+  eagain_ifuture<> validate(Transaction& t) {
+    logger().info("Verifing inserted ...");
     return seastar::do_with(
-        kvs.begin(), [&t, this] (auto& kvs_iter) {
-      return crimson::do_until([&t, this, &kvs_iter]() -> future<bool> {
-        if (kvs_iter.is_end()) {
-          logger().info("Verify done!");
-          return ertr::make_ready_future<bool>(true);
+      kvs.begin(),
+      [this, &t] (auto &iter) {
+      return trans_intr::repeat(
+        [this, &t, &iter]() ->eagain_iertr::future<seastar::stop_iteration> {
+        if (iter == kvs.end()) {
+          return seastar::make_ready_future<seastar::stop_iteration>(
+            seastar::stop_iteration::yes);
         }
-        auto [k, v] = kvs_iter.get_kv();
-        return tree->lower_bound(t, k
-        ).safe_then([&kvs_iter, k=k, v=v] (auto cursor) {
-          Onodes::validate_cursor(cursor, k, *v);
-          ++kvs_iter;
-          return ertr::make_ready_future<bool>(false);
+        return validate_one(t, iter).si_then([&iter] {
+          ++iter;
+          return seastar::make_ready_future<seastar::stop_iteration>(
+            seastar::stop_iteration::no);
         });
       });
     });
@@ -322,12 +550,11 @@ class TreeBuilder {
 
  private:
   static seastar::logger& logger() {
-    return crimson::get_logger(ceph_subsys_filestore);
+    return crimson::get_logger(ceph_subsys_test);
   }
 
-  KVPool& kvs;
-  std::optional<Btree> tree;
-  KVPool::iterator_t kv_iter;
+  KVPool<ValueItem>& kvs;
+  std::optional<BtreeImpl> tree;
 };
 
 }