]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
crimson/os/seastore: introduce RotationalDevice
authorZhang Song <zhangsong02@qianxin.com>
Thu, 31 Jul 2025 08:16:56 +0000 (16:16 +0800)
committerXuehan Xu <xuxuehan@qianxin.com>
Sat, 23 May 2026 09:11:36 +0000 (17:11 +0800)
Signed-off-by: Zhang Song <zhangsong02@qianxin.com>
Signed-off-by: Xuehan Xu <xuxuehan@qianxin.com>
14 files changed:
src/crimson/os/seastore/CMakeLists.txt
src/crimson/os/seastore/async_cleaner.h
src/crimson/os/seastore/device.cc
src/crimson/os/seastore/extent_placement_manager.cc
src/crimson/os/seastore/random_block_manager.cc
src/crimson/os/seastore/random_block_manager.h
src/crimson/os/seastore/random_block_manager/hdd_device.cc [new file with mode: 0644]
src/crimson/os/seastore/random_block_manager/hdd_device.h [new file with mode: 0644]
src/crimson/os/seastore/random_block_manager/nvme_block_device.h
src/crimson/os/seastore/random_block_manager/rbm_device.cc
src/crimson/os/seastore/random_block_manager/rbm_device.h
src/crimson/os/seastore/seastore_types.cc
src/crimson/os/seastore/seastore_types.h
src/crimson/os/seastore/transaction_manager.cc

index dbac365a3b2373afd23c7afafc423edfe830740c..1a707cfea5b063db59a895188afb46a9906d4eb9 100644 (file)
@@ -46,6 +46,7 @@ set(crimson_seastore_srcs
   random_block_manager/block_rb_manager.cc
   random_block_manager/rbm_device.cc
   random_block_manager/nvme_block_device.cc
+  random_block_manager/hdd_device.cc
   random_block_manager/avlallocator.cc
   journal/segmented_journal.cc
   journal/segment_allocator.cc
index 3399a5d3c90b27545edeaa617d4d34482b995283..c04a369d024ae583cf67d3c899c51ac836b9d73e 100644 (file)
@@ -1279,6 +1279,8 @@ public:
 
   virtual const std::set<device_id_t>& get_device_ids() const = 0;
 
+  virtual backend_type_t get_backend_type() const = 0;
+
   virtual std::size_t get_reclaim_size_per_cycle() const = 0;
 
 #ifdef UNIT_TESTS_BUILT
@@ -1483,6 +1485,10 @@ public:
     return sm_group->get_device_ids();
   }
 
+  backend_type_t get_backend_type() const final {
+    return backend_type_t::SEGMENTED;
+  }
+
   std::size_t get_reclaim_size_per_cycle() const final {
     return config.reclaim_bytes_per_cycle;
   }
@@ -1821,6 +1827,10 @@ public:
     return rb_group->get_device_ids();
   }
 
+  backend_type_t get_backend_type() const final {
+    return backend_type_t::RANDOM_BLOCK;
+  }
+
   std::size_t get_reclaim_size_per_cycle() const final {
     return 0;
   }
index 2e653a48a23f7297a8565ada1d64a35f5879697f..1c0d6f1bc3cff1409049ecfc4ce84f75cc4ee127 100644 (file)
@@ -108,7 +108,8 @@ void device_superblock_t::validate() const
       ceph_assert(shard_infos[i].size > block_size &&
                   shard_infos[i].size % block_size == 0);
       ceph_assert_always(shard_infos[i].size <= DEVICE_OFF_MAX);
-      ceph_assert(journal_size > 0 && journal_size % block_size == 0);
+      ceph_assert((journal_size > 0 && journal_size % block_size == 0) ||
+                   config.spec.dtype == device_type_t::RANDOM_BLOCK_HDD);
       ceph_assert(shard_infos[i].start_offset < total_size &&
                   shard_infos[i].start_offset % block_size == 0);
     }
@@ -128,7 +129,7 @@ Device::make_device(
     });
   } else {
     ceph_assert(btype != backend_type_t::NONE);
-    return get_rb_device(device
+    return get_rb_device(device, dtype
     ).then([](DeviceRef ret) {
       return ret;
     });
index fc9fc163e80d829932056bf303158ec75f7ca4a2..564ad3f0ad9b5a11e6cc5f78867f76d18b47c883 100644 (file)
@@ -197,9 +197,8 @@ void ExtentPlacementManager::init(
 {
   LOG_PREFIX(ExtentPlacementManager::init);
   writer_refs.clear();
-  auto cold_segment_cleaner = dynamic_cast<SegmentCleaner*>(cold_cleaner.get());
   dynamic_max_rewrite_generation = hot_tier_generations - 1;
-  if (cold_segment_cleaner) {
+  if (cold_cleaner) {
     dynamic_max_rewrite_generation = hot_tier_generations + cold_tier_generations - 1;
   }
   DEBUG("dynamic_max_rewrite_generation: {}, "
@@ -254,36 +253,58 @@ void ExtentPlacementManager::init(
     for (auto *rb : rb_cleaner->get_rb_group()->get_rb_managers()) {
       add_device(rb->get_device());
     }
-  }
-
-  if (cold_segment_cleaner) {
-    // Cold DATA Segments
-    for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
-      writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
-            data_category_t::DATA, gen, *cold_segment_cleaner,
-            *ool_segment_seq_allocator));
+    for (rewrite_gen_t gen = OOL_GENERATION; gen < hot_tier_generations; ++gen) {
       data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
     }
-    for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
-      // Cold METADATA Segments
-      writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
-            data_category_t::METADATA, gen, *cold_segment_cleaner,
-            *ool_segment_seq_allocator));
+    for (rewrite_gen_t gen = OOL_GENERATION; gen < hot_tier_generations; ++gen) {
       md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
     }
-    for (auto *device : cold_segment_cleaner->get_segment_manager_group()
-                                            ->get_segment_managers()) {
-      add_device(device);
-    }
   }
 
+  if (cold_cleaner) {
+    if (cold_cleaner->get_backend_type() == backend_type_t::SEGMENTED) {
+      auto cold_segment_cleaner = static_cast<SegmentCleaner*>(cold_cleaner.get());
+      for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
+        writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
+              data_category_t::DATA, gen, *cold_segment_cleaner,
+              *ool_segment_seq_allocator));
+        data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
+      }
+      for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
+        writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
+              data_category_t::METADATA, gen, *cold_segment_cleaner,
+              *ool_segment_seq_allocator));
+        md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
+      }
+      for (auto *device : cold_segment_cleaner->get_segment_manager_group()
+                                              ->get_segment_managers()) {
+        add_device(device);
+      }
+    } else {
+      ceph_assert(cold_cleaner->get_backend_type() == backend_type_t::RANDOM_BLOCK);
+      auto rb_cleaner = static_cast<RBMCleaner*>(cold_cleaner.get());
+      ceph_assert(rb_cleaner);
+      writer_refs.emplace_back(std::make_unique<RandomBlockOolWriter>(rb_cleaner));
+      for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
+        data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
+      }
+      for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
+        md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
+      }
+      for (auto *rb : rb_cleaner->get_rb_group()->get_rb_managers()) {
+        add_device(rb->get_device());
+      }
+     }
+   }
+
+  auto cold_cleaner_ = cold_cleaner.get();
   background_process.init(std::move(trimmer),
                           std::move(cleaner),
                           std::move(cold_cleaner),
                           hot_tier_generations,
                           pinboard);
   ceph_assert(get_main_backend_type() != backend_type_t::NONE);
-  if (cold_segment_cleaner) {
+  if (cold_cleaner_) {
     ceph_assert(get_main_backend_type() == backend_type_t::SEGMENTED);
     ceph_assert(background_process.has_cold_tier());
   } else {
index b849c1878436a7572b14b1ea659b4fdd885ff96c..64196db5ae69db36f22c9f7def5061a85ebd0e2b 100644 (file)
@@ -5,17 +5,25 @@
 #include "crimson/os/seastore/random_block_manager.h"
 #include "crimson/os/seastore/random_block_manager/nvme_block_device.h"
 #include "crimson/os/seastore/random_block_manager/rbm_device.h"
+#include "crimson/os/seastore/random_block_manager/hdd_device.h"
 
 namespace crimson::os::seastore {
 
 seastar::future<random_block_device::RBMDeviceRef>
 get_rb_device(
-  const std::string &device)
+  const std::string &device, device_type_t dtype)
 {
-  return seastar::make_ready_future<random_block_device::RBMDeviceRef>(
-    std::make_unique<
-      random_block_device::nvme::NVMeBlockDevice
-    >(device + "/block"));
+  if (dtype == device_type_t::RANDOM_BLOCK_HDD) {
+    return seastar::make_ready_future<random_block_device::RBMDeviceRef>(
+      std::make_unique<
+        random_block_device::RotationalDevice
+      >(device + "/block"));
+  } else {
+    return seastar::make_ready_future<random_block_device::RBMDeviceRef>(
+      std::make_unique<
+        random_block_device::nvme::NVMeBlockDevice
+      >(device + "/block"));
+  }
 }
 
 }
index dadd2578f45d0540ec05f2498baffd2a754f820d..f776483edf270b1bf90ecb72466fe9ff51a5e302 100644 (file)
@@ -114,7 +114,7 @@ namespace random_block_device {
 }
 
 seastar::future<std::unique_ptr<random_block_device::RBMDevice>> 
-  get_rb_device(const std::string &device);
+  get_rb_device(const std::string &device, device_type_t dtype);
 
 std::ostream &operator<<(std::ostream &out, const rbm_extent_state_t &state);
 }
diff --git a/src/crimson/os/seastore/random_block_manager/hdd_device.cc b/src/crimson/os/seastore/random_block_manager/hdd_device.cc
new file mode 100644 (file)
index 0000000..d3a6cf5
--- /dev/null
@@ -0,0 +1,155 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "crimson/common/errorator-utils.h"
+#include "crimson/os/seastore/logging.h"
+#include "crimson/os/seastore/random_block_manager/hdd_device.h"
+
+SET_SUBSYS(seastore_device);
+
+namespace crimson::os::seastore::random_block_device {
+
+seastar::future<> RotationalDevice::start(uint32_t shard_nums) {
+  device_shard_nums = shard_nums;
+  auto num_shard_services = (device_shard_nums + seastar::smp::count - 1 ) /
+    seastar::smp::count;
+  LOG_PREFIX(NVMeBlockDevice::start);
+  DEBUG("device_shard_nums={} seastar::smp={}, num_shard_services={}",
+    device_shard_nums, seastar::smp::count, num_shard_services);
+  return shard_devices.start(num_shard_services, device_path);
+}
+
+RotationalDevice::mkfs_ret RotationalDevice::mkfs(device_config_t config) {
+  LOG_PREFIX(RotationalDevice::mkfs);
+  INFO("{}", config);
+  return shard_devices.local().mshard_devices[0]->do_primary_mkfs(
+    config, seastar::smp::count, 0);
+}
+
+RotationalDevice::mount_ret RotationalDevice::mount() {
+  LOG_PREFIX(RotationalDevice::mount);
+  DEBUG("mount");
+  return shard_devices.invoke_on_all([](auto &local_device) {
+    return seastar::do_for_each(
+      local_device.mshard_devices,
+      [](auto &mshard_device) {
+      return mshard_device->do_shard_mount(
+      ).handle_error(
+       crimson::ct_error::assert_all{
+         "Invalid error in RBMDevice::do_mount"
+       }
+      );
+    });
+  });
+}
+
+read_ertr::future<> RotationalDevice::read(
+  uint64_t offset,
+  bufferptr &bptr)
+{
+  auto length = bptr.length();
+  return device.dma_read(offset, bptr.c_str(), length
+  ).handle_exception([](auto e) -> read_ertr::future<size_t> {
+    return crimson::ct_error::input_output_error::make();
+  }).then([length](auto result) -> read_ertr::future<> {
+    if (result != length) {
+      return crimson::ct_error::input_output_error::make();
+    }
+    return read_ertr::now();
+  });
+}
+
+read_ertr::future<> RotationalDevice::_readv(
+  uint64_t offset,
+  std::vector<bufferptr> ptrs) {
+  LOG_PREFIX(NVMeBlockDevice::_readv);
+  DEBUG("block: read offset {}, {} buffers", offset, ptrs.size());
+  if (ptrs.size() == 0) {
+    return read_ertr::now();
+  }
+
+  std::vector<iovec> iov;
+  size_t length = 0;
+  for (auto &ptr : ptrs) {
+    length += ptr.length();
+    assert((ptr.length() % super.block_size) == 0);
+    iov.emplace_back(ptr.c_str(), ptr.length());
+  }
+  return device.dma_read(offset, std::move(iov)
+  ).handle_exception(
+    [FNAME](auto e) -> read_ertr::future<size_t> {
+    ERROR("read: dma_read got error{}", e);
+    return crimson::ct_error::input_output_error::make();
+  }).then([length, FNAME](auto result) -> read_ertr::future<> {
+    if (result != length) {
+      ERROR("read: dma_read got error with not proper length");
+      return crimson::ct_error::input_output_error::make();
+    }
+    return read_ertr::now();
+  });
+}
+
+write_ertr::future<> RotationalDevice::write(
+  uint64_t offset,
+  bufferptr bptr,
+  uint16_t stream)
+{
+  auto length = bptr.length();
+  return seastar::do_with(std::move(bptr), [this, offset, length](auto &bptr) {
+    return device.dma_write(offset, bptr.c_str(), length
+    ).handle_exception([](auto e) -> write_ertr::future<size_t> {
+      return crimson::ct_error::input_output_error::make();
+    }).then([length](auto result) -> write_ertr::future<> {
+      if (result != length) {
+        return crimson::ct_error::input_output_error::make();
+      }
+      return write_ertr::now();
+    });
+  });
+}
+
+open_ertr::future<> RotationalDevice::open(
+  const std::string &path,
+  seastar::open_flags mode)
+{
+  return seastar::open_file_dma(path, mode).then([this](auto file) {
+    device = std::move(file);
+  }).handle_exception([](auto e) -> open_ertr::future<> {
+    return crimson::ct_error::input_output_error::make();
+  });
+}
+
+write_ertr::future<> RotationalDevice::writev(
+  uint64_t offset,
+  ceph::bufferlist bl,
+  uint16_t stream) {
+  bl.rebuild_aligned(super.block_size);
+
+  return seastar::do_with(
+    bl.prepare_iovs(),
+    std::move(bl),
+    [this, offset](auto& iovs, auto& bl)
+  {
+    return write_ertr::parallel_for_each(
+      iovs,
+      [this, offset](auto& p) mutable
+    {
+      auto off = offset + p.offset;
+      auto len = p.length;
+      auto& iov = p.iov;
+      return device.dma_write(off, std::move(iov)
+      ).handle_exception(
+        [](auto e) -> write_ertr::future<size_t>
+      {
+        return crimson::ct_error::input_output_error::make();
+      }).then([len](size_t written) -> write_ertr::future<> {
+        if (written != len) {
+          return crimson::ct_error::input_output_error::make();
+        }
+        return write_ertr::now();
+      });
+    });
+  });
+}
+
+}
diff --git a/src/crimson/os/seastore/random_block_manager/hdd_device.h b/src/crimson/os/seastore/random_block_manager/hdd_device.h
new file mode 100644 (file)
index 0000000..bd3755b
--- /dev/null
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include "crimson/os/seastore/random_block_manager/rbm_device.h"
+
+namespace crimson::os::seastore::random_block_device {
+class RotationalDevice : public RBMDevice {
+public:
+  RotationalDevice(
+    std::string device_path,
+    store_index_t store_index = 0)
+    : RBMDevice(store_index),
+      device_path(device_path)
+  {}
+  ~RotationalDevice() = default;
+
+  /// Device interface
+
+  seastar::future<> start(uint32_t shard_nums) final;
+
+  seastar::future<> stop() final {
+    return shard_devices.stop();
+  }
+
+  Device& get_sharded_device(store_index_t store_index) final {
+    assert(store_index < shard_devices.local().mshard_devices.size());
+    return *shard_devices.local().mshard_devices[store_index];
+  }
+
+  mkfs_ret mkfs(device_config_t config) final;
+
+  mount_ret mount() final;
+
+  device_type_t get_device_type() const final {
+    return device_type_t::RANDOM_BLOCK_HDD;
+  }
+
+  close_ertr::future<> close() final {
+    return device.close();
+  }
+
+  /// RBMDevice interface
+
+  read_ertr::future<> read(
+    uint64_t offset,
+    bufferptr &bptr) final;
+  read_ertr::future<> _readv(
+    uint64_t offset,
+    std::vector<bufferptr> ptrs) final;
+
+  write_ertr::future<> write(
+    uint64_t offset,
+    bufferptr bptr,
+    uint16_t stream = 0) final;
+
+  open_ertr::future<> open(
+    const std::string &path,
+    seastar::open_flags mode) final;
+
+  write_ertr::future<> writev(
+    uint64_t offset,
+    ceph::bufferlist bl,
+    uint16_t stream = 0) final;
+
+  stat_device_ret stat_device() final {
+    return seastar::file_stat(device_path, seastar::follow_symlink::yes
+    ).then([this](auto stat) {
+      return seastar::open_file_dma(
+        device_path,
+       seastar::open_flags::rw | seastar::open_flags::dsync
+      ).then([stat](auto file) mutable {
+       return seastar::do_with(std::move(file), [stat](auto &file) mutable {
+         return file.size().then([stat](auto size) mutable {
+           stat.size = size;
+           return stat;
+         }).then([file](auto stat) mutable {
+           return file.close().then([stat] {
+             return stat;
+           });
+         });
+       });
+      });
+    }).handle_exception([](auto e) -> stat_device_ret {
+      return crimson::ct_error::input_output_error::make();
+    });
+  }
+
+  std::string get_device_path() const final {
+    return device_path;
+  }
+
+private:
+  std::string device_path;
+  seastar::file device;
+  seastar::sharded<MultiShardDevices<RotationalDevice>> shard_devices;
+};
+
+}
index 3031b8d01917a1c30e70004a1f6e0d63a685e663..21eae2fec3a88180c7b83c8fc33002a7bd4dbf3f 100644 (file)
@@ -374,25 +374,7 @@ private:
   int namespace_id; // TODO: multi namespaces
   std::string device_path;
 
-  class MultiShardDevices {
-    public:
-      std::vector<std::unique_ptr<NVMeBlockDevice>> mshard_devices;
-
-    public:
-    MultiShardDevices(size_t count,
-                      const std::string path)
-    : mshard_devices() {
-      mshard_devices.reserve(count);
-      for (size_t store_index = 0; store_index < count; ++store_index) {
-        mshard_devices.emplace_back(std::make_unique<NVMeBlockDevice>(
-          path, store_index));
-      }
-    }
-    ~MultiShardDevices() {
-     mshard_devices.clear();
-    }
-  };
-  seastar::sharded<MultiShardDevices> shard_devices;
+  seastar::sharded<MultiShardDevices<NVMeBlockDevice>> shard_devices;
 };
 
 }
index 86e2b6bb45d1487bd3300e603921640b8ed06e04..6ec8795730cbcba819868cfdc30a17a435a1b21a 100644 (file)
@@ -29,7 +29,6 @@ RBMDevice::mkfs_ret RBMDevice::do_primary_mkfs(device_config_t config,
     maybe_create = check_create_device(get_device_path(), size);
   }
 
-
   co_await std::move(maybe_create);
   auto st = co_await stat_device(
   ).safe_then([] (auto st) mutable {
@@ -44,9 +43,11 @@ RBMDevice::mkfs_ret RBMDevice::do_primary_mkfs(device_config_t config,
     );
   }
 
+  config.spec.id |= 0x80;
   const size_t cur_block_size = (*st).block_size;
   const size_t cur_total_size = (*st).size;
-  ceph_assert_always(journal_size > 0);
+  ceph_assert_always(journal_size > 0 ||
+                     config.spec.dtype == device_type_t::RANDOM_BLOCK_HDD);
   ceph_assert_always(cur_total_size >= journal_size);
   ceph_assert_always(shard_num > 0);
 
index 3e87960849db592cecbc5c7066701097d858e911..d380b2a78ac1b0c865156d543bc07cc7f5f28d20 100644 (file)
@@ -111,7 +111,7 @@ public:
     return super.config.spec.magic;
   }
 
-  device_type_t get_device_type() const final {
+  virtual device_type_t get_device_type() const {
     return device_type_t::RANDOM_BLOCK_SSD;
   }
 
@@ -278,4 +278,24 @@ EphemeralRBMDeviceRef create_test_ephemeral(
   uint64_t journal_size = DEFAULT_TEST_CBJOURNAL_SIZE,
   uint64_t data_size = DEFAULT_TEST_CBJOURNAL_SIZE);
 
+template <typename T>
+class MultiShardDevices {
+  public:
+    std::vector<std::unique_ptr<T>> mshard_devices;
+
+  public:
+  MultiShardDevices(size_t count,
+                    const std::string path)
+  : mshard_devices() {
+    mshard_devices.reserve(count);
+    for (size_t store_index = 0; store_index < count; ++store_index) {
+      mshard_devices.emplace_back(std::make_unique<T>(
+        path, store_index));
+    }
+  }
+  ~MultiShardDevices() {
+    mshard_devices.clear();
+  }
+};
+
 }
index 6ac435cd0699b492c3417264e6c83614d33bee9b..df83d1e5825da042453f5762563500b9f46e31e5 100644 (file)
@@ -1243,6 +1243,9 @@ device_type_t string_to_device_type(std::string type) {
   if (type == "RANDOM_BLOCK_SSD") {
     return device_type_t::RANDOM_BLOCK_SSD;
   }
+  if (type == "RANDOM_BLOCK_HDD") {
+    return device_type_t::RANDOM_BLOCK_HDD;
+  }
   return device_type_t::NONE;
 }
 
@@ -1265,6 +1268,8 @@ std::ostream& operator<<(std::ostream& out, device_type_t t)
     return out << "RANDOM_BLOCK_SSD";
   case device_type_t::RANDOM_BLOCK_EPHEMERAL:
     return out << "RANDOM_BLOCK_EPHEMERAL";
+  case device_type_t::RANDOM_BLOCK_HDD:
+    return out << "RANDOM_BLOCK_HDD";
   default:
     return out << "INVALID_DEVICE_TYPE!";
   }
index dbf662a6bec5833ad15e336068b087be24423267..8472775a1dd91b785e01a2ab45d450113d34ee4d 100644 (file)
@@ -966,6 +966,7 @@ enum class device_type_t : uint8_t {
   EPHEMERAL_MAIN,
   RANDOM_BLOCK_SSD,
   RANDOM_BLOCK_EPHEMERAL,
+  RANDOM_BLOCK_HDD,
   NUM_TYPES
 };
 
index dd1efeaf8ca3e49311058c96b4a0d57d53c09021..7880e7a02a92fd5c6486a2e8672c552f7ddc0ab7 100644 (file)
@@ -1336,9 +1336,11 @@ TransactionManagerRef make_transaction_manager(
   auto rbs = std::make_unique<RBMDeviceGroup>();
   auto backref_manager = create_backref_manager(*cache);
   SegmentManagerGroupRef cold_sms = nullptr;
+  RBMDeviceGroupRef cold_rbs = nullptr;
   std::vector<SegmentProvider*> segment_providers_by_id{DEVICE_ID_MAX, nullptr};
 
   auto p_backend_type = primary_device->get_backend_type();
+  INFO("primary backend: {}", p_backend_type);
 
   if (p_backend_type == backend_type_t::SEGMENTED) {
     auto dtype = primary_device->get_device_type();
@@ -1346,6 +1348,7 @@ TransactionManagerRef make_transaction_manager(
                dtype != device_type_t::EPHEMERAL_COLD);
     sms->add_segment_manager(static_cast<SegmentManager*>(primary_device));
   } else {
+    assert(p_backend_type != backend_type_t::NONE);
     auto rbm = std::make_unique<BlockRBManager>(
       static_cast<RBMDevice*>(primary_device), "", is_test);
     rbs->add_rb_manager(std::move(rbm));
@@ -1354,17 +1357,29 @@ TransactionManagerRef make_transaction_manager(
   for (auto &p_dev : secondary_devices) {
     if (p_dev->get_backend_type() == backend_type_t::SEGMENTED) {
       if (p_dev->get_device_type() == primary_device->get_device_type()) {
+       INFO("add {} to main segment backend", device_id_printer_t{p_dev->get_device_id()});
         sms->add_segment_manager(static_cast<SegmentManager*>(p_dev));
       } else {
         if (!cold_sms) {
           cold_sms = std::make_unique<SegmentManagerGroup>();
         }
+       INFO("add {} to cold segment backend", device_id_printer_t{p_dev->get_device_id()});
         cold_sms->add_segment_manager(static_cast<SegmentManager*>(p_dev));
       }
     } else {
+      assert(p_backend_type != backend_type_t::NONE);
       auto rbm = std::make_unique<BlockRBManager>(
        static_cast<RBMDevice*>(p_dev), "", is_test);
-      rbs->add_rb_manager(std::move(rbm));
+      if (p_dev->get_device_type() == primary_device->get_device_type()) {
+       INFO("add {} to rbm backend", device_id_printer_t{p_dev->get_device_id()});
+       rbs->add_rb_manager(std::move(rbm));
+      } else {
+       if (!cold_rbs) {
+         cold_rbs = std::make_unique<RBMDeviceGroup>();
+       }
+       INFO("add {} to cold rbm backend", device_id_printer_t{p_dev->get_device_id()});
+       cold_rbs->add_rb_manager(std::move(rbm));
+      }
     }
   }
 
@@ -1413,10 +1428,11 @@ TransactionManagerRef make_transaction_manager(
   AsyncCleanerRef cleaner;
   JournalRef journal;
 
-  SegmentCleanerRef cold_segment_cleaner = nullptr;
+  AsyncCleanerRef cold_cleaner = nullptr;
 
   if (cold_sms) {
-    cold_segment_cleaner = SegmentCleaner::create(
+    assert(!cold_rbs);
+    auto segment_cleaner = SegmentCleaner::create(
       store_index,
       cleaner_config,
       std::move(cold_sms),
@@ -1426,11 +1442,19 @@ TransactionManagerRef make_transaction_manager(
       cleaner_is_detailed,
       /* is_cold = */ true);
     if (backend_type == backend_type_t::SEGMENTED) {
-      for (auto id : cold_segment_cleaner->get_device_ids()) {
+      for (auto id : segment_cleaner->get_device_ids()) {
         segment_providers_by_id[id] =
-          static_cast<SegmentProvider*>(cold_segment_cleaner.get());
+          static_cast<SegmentProvider*>(segment_cleaner.get());
       }
     }
+    cold_cleaner = std::move(segment_cleaner);
+  } else if (cold_rbs) {
+    cold_cleaner = RBMCleaner::create(
+      store_index,
+      std::move(cold_rbs),
+      *backref_manager,
+      *lba_manager,
+      cleaner_is_detailed);
   }
 
   if (backend_type == backend_type_t::SEGMENTED) {
@@ -1470,7 +1494,7 @@ TransactionManagerRef make_transaction_manager(
 
   epm->init(std::move(journal_trimmer),
            std::move(cleaner),
-           std::move(cold_segment_cleaner),
+           std::move(cold_cleaner),
            cache->get_extent_pinboard());
   epm->set_primary_device(primary_device);