crimson/os/seastore: add write through policy

Write extents larger than some threshold to the cold tier directly

Signed-off-by: Zhang Song <zhangsong02@qianxin.com>
Signed-off-by: Xuehan Xu <xuxuehan@qianxin.com>
This commit is contained in:
Zhang Song 2025-09-03 16:05:38 +08:00 committed by Xuehan Xu
parent 43d0b3316a
commit afde4c6dcb
11 changed files with 117 additions and 31 deletions

View File

@ -388,3 +388,8 @@ options:
level: advanced
desc: Size in bytes of extents to be demoted from logical bucket
default: 2_M
- name: seastore_write_through_size
type: size
level: dev
desc: Select write through policy when data length is greater than this value.
default: 512_K

View File

@ -1264,28 +1264,26 @@ std::vector<CachedExtentRef> Cache::alloc_new_data_extents_by_type(
Transaction &t, ///< [in, out] current transaction
extent_types_t type, ///< [in] type tag
extent_len_t length, ///< [in] length
placement_hint_t hint, ///< [in] user hint
rewrite_gen_t gen, ///< [in] rewrite generation
bool is_tracked
alloc_option_t opt ///< [in] allocation options
)
{
LOG_PREFIX(Cache::alloc_new_data_extents_by_type);
SUBDEBUGT(seastore_cache, "allocate {} 0x{:x}B, hint={}, gen={}",
t, type, length, hint, rewrite_gen_printer_t{gen});
t, type, length, opt.hint, rewrite_gen_printer_t{opt.gen});
ceph_assert(get_extent_category(type) == data_category_t::DATA);
std::vector<CachedExtentRef> res;
switch (type) {
case extent_types_t::OBJECT_DATA_BLOCK:
{
auto extents = alloc_new_data_extents<
ObjectDataBlock>(t, length, {hint, gen, is_tracked});
ObjectDataBlock>(t, length, std::move(opt));
res.insert(res.begin(), extents.begin(), extents.end());
}
return res;
case extent_types_t::TEST_BLOCK:
{
auto extents = alloc_new_data_extents<
TestBlock>(t, length, {hint, gen, is_tracked});
TestBlock>(t, length, std::move(opt));
res.insert(res.begin(), extents.begin(), extents.end());
}
return res;
@ -2200,7 +2198,8 @@ void Cache::init()
P_ADDR_ROOT,
PLACEMENT_HINT_NULL,
NULL_GENERATION,
TRANS_ID_NULL);
TRANS_ID_NULL,
write_policy_t::WRITE_BACK);
root->set_modify_time(seastar::lowres_system_clock::now());
INFO("init root -- {}", *root);
add_extent(root);
@ -2624,7 +2623,8 @@ Cache::_get_absent_extent_by_type(
offset,
PLACEMENT_HINT_NULL,
NULL_GENERATION,
TRANS_ID_NULL);
TRANS_ID_NULL,
write_policy_t::WRITE_BACK);
DEBUGT("{} length=0x{:x} is absent, add extent ... -- {}",
t, type, length, *ret);
add_extent(ret);

View File

@ -857,7 +857,8 @@ private:
offset,
PLACEMENT_HINT_NULL,
NULL_GENERATION,
TRANS_ID_NULL);
TRANS_ID_NULL,
write_policy_t::WRITE_BACK);
SUBDEBUGT(seastore_cache,
"{} {}~0x{:x} is absent, add extent and reading range 0x{:x}~0x{:x} ... -- {}",
t, T::TYPE, offset, length, partial_off, partial_len, *ret);
@ -896,7 +897,8 @@ private:
offset,
PLACEMENT_HINT_NULL,
NULL_GENERATION,
TRANS_ID_NULL);
TRANS_ID_NULL,
write_policy_t::WRITE_BACK);
SUBDEBUG(seastore_cache,
"{} {}~0x{:x} is absent, add extent and reading range 0x{:x}~0x{:x} ... -- {}",
T::TYPE, offset, length, partial_off, partial_len, *ret);
@ -1175,7 +1177,8 @@ public:
result->paddr,
opt.hint,
result->gen,
t.get_trans_id());
t.get_trans_id(),
write_policy_t::WRITE_BACK);
t.add_fresh_extent(ret);
SUBDEBUGT(seastore_cache,
"allocated {} 0x{:x}B extent at {}, hint={}, gen={} -- {}",
@ -1213,7 +1216,8 @@ public:
result.paddr,
opt.hint,
result.gen,
t.get_trans_id());
t.get_trans_id(),
opt.write_policy);
t.add_fresh_extent(ret);
SUBDEBUGT(seastore_cache,
"allocated {} 0x{:x}B extent at {}, hint={}, gen={} -- {}",
@ -1255,7 +1259,8 @@ public:
remap_paddr,
PLACEMENT_HINT_NULL,
NULL_GENERATION,
t.get_trans_id());
t.get_trans_id(),
write_policy_t::WRITE_BACK);
auto extent = ext->template cast<T>();
extent->set_laddr(remap_laddr);
@ -1297,9 +1302,7 @@ public:
Transaction &t, ///< [in, out] current transaction
extent_types_t type, ///< [in] type tag
extent_len_t length, ///< [in] length
placement_hint_t hint, ///< [in] user hint
rewrite_gen_t gen, ///< [in] rewrite generation
bool is_tracked
alloc_option_t opt ///< [in] allocation options
);
/**

View File

@ -360,12 +360,14 @@ public:
paddr_t paddr,
placement_hint_t hint,
rewrite_gen_t gen,
transaction_id_t trans_id) {
transaction_id_t trans_id,
write_policy_t policy) {
state = _state;
set_paddr(paddr);
user_hint = hint;
rewrite_generation = gen;
pending_for_transaction = trans_id;
write_policy = policy;
}
void set_modify_time(sea_time_point t) {
@ -537,6 +539,7 @@ public:
<< ", last_committed_crc=" << last_committed_crc
<< ", refcount=" << use_count()
<< ", user_hint=" << user_hint
<< ", write_policy=" << write_policy
<< ", rewrite_gen=" << rewrite_gen_printer_t{rewrite_generation}
<< ", pending_io=";
if (is_pending_io()) {
@ -916,6 +919,18 @@ public:
is_shadow = b;
}
write_policy_t get_write_policy() const {
return write_policy;
}
void set_write_policy(write_policy_t w) {
write_policy = w;
}
void reset_write_policy() {
write_policy = write_policy_t::WRITE_BACK;
}
private:
template <typename T>
friend class read_set_item_t;
@ -1027,6 +1042,8 @@ private:
placement_hint_t user_hint = PLACEMENT_HINT_NULL;
write_policy_t write_policy = write_policy_t::WRITE_BACK;
// the target rewrite generation for the followup rewrite
// or the rewrite generation for the fresh write
rewrite_gen_t rewrite_generation = NULL_GENERATION;

View File

@ -370,7 +370,9 @@ public:
ool_segment_seq_allocator(
std::make_unique<SegmentSeqAllocator>(segment_type_t::OOL)),
max_data_allocation_size(crimson::common::get_conf<Option::size_t>(
"seastore_max_data_allocation_size"))
"seastore_max_data_allocation_size")),
write_through_size(crimson::common::get_conf<Option::size_t>(
"seastore_write_through_size"))
{
LOG_PREFIX(ExtentPlacementManager::ExtentPlacementManager);
devices_by_id.resize(DEVICE_ID_MAX, nullptr);
@ -448,6 +450,7 @@ public:
placement_hint_t hint;
rewrite_gen_t gen;
bool is_tracked;
write_policy_t write_policy = write_policy_t::WRITE_BACK;
#ifdef UNIT_TESTS_BUILT
std::optional<paddr_t> external_paddr = std::nullopt;
#endif
@ -468,7 +471,8 @@ public:
assert(opt.gen == INIT_GENERATION || opt.hint == placement_hint_t::REWRITE);
data_category_t category = get_extent_category(type);
opt.gen = adjust_generation(category, type, opt.hint, opt.gen, opt.is_tracked);
opt.gen = adjust_generation(
category, type, opt.hint, opt.gen, opt.write_policy, opt.is_tracked);
paddr_t addr;
#ifdef UNIT_TESTS_BUILT
@ -508,7 +512,8 @@ public:
assert(opt.gen == INIT_GENERATION || opt.hint == placement_hint_t::REWRITE);
data_category_t category = get_extent_category(type);
opt.gen = adjust_generation(category, type, opt.hint, opt.gen, opt.is_tracked);
opt.gen = adjust_generation(
category, type, opt.hint, opt.gen, opt.write_policy, opt.is_tracked);
assert(opt.gen != INLINE_GENERATION);
// XXX: bp might be extended to point to different memory (e.g. PMem)
@ -546,6 +551,13 @@ public:
return allocs;
}
write_policy_t get_write_policy(extent_types_t type, extent_len_t length) const {
if (has_cold_tier() && length >= write_through_size && is_data_type(type)) {
return write_policy_t::WRITE_THROUGH;
}
return write_policy_t::WRITE_BACK;
}
#ifdef UNIT_TESTS_BUILT
void prefill_fragmented_devices() {
LOG_PREFIX(ExtentPlacementManager::prefill_fragmented_devices);
@ -715,6 +727,7 @@ private:
extent_types_t type,
placement_hint_t hint,
rewrite_gen_t gen,
write_policy_t policy,
bool is_tracked) {
assert(is_real_type(type));
if (is_root_type(type)) {
@ -744,10 +757,20 @@ private:
}
} else {
assert(category == data_category_t::DATA);
gen = OOL_GENERATION;
if (background_process.has_cold_tier() &&
policy == write_policy_t::WRITE_THROUGH) {
gen = hot_tier_generations;
} else {
assert(policy != write_policy_t::WRITE_THROUGH);
gen = OOL_GENERATION;
}
}
} else if (background_process.has_cold_tier()) {
gen = background_process.adjust_generation(gen);
if (gen <= hot_tier_generations &&
policy == write_policy_t::WRITE_THROUGH) {
gen = hot_tier_generations;
}
}
if (is_tracked && gen >= hot_tier_generations &&
@ -1316,6 +1339,7 @@ private:
// TODO: drop once paddr->journal_seq_t is introduced
SegmentSeqAllocatorRef ool_segment_seq_allocator;
extent_len_t max_data_allocation_size = 0;
std::size_t write_through_size = 0;
friend class ::transaction_manager_test_t;
friend class Cache;

View File

@ -1224,6 +1224,16 @@ std::ostream& operator<<(std::ostream& out, placement_hint_t h)
}
}
std::ostream& operator<<(std::ostream& out, write_policy_t w)
{
switch(w) {
case write_policy_t::WRITE_BACK:
return out << "WRITE_BACK";
case write_policy_t::WRITE_THROUGH:
return out << "WRITE_THROUGH";
}
}
bool can_delay_allocation(device_type_t type) {
// Some types of device may not support delayed allocation, for example PMEM.
// All types of device currently support delayed allocation.

View File

@ -2070,6 +2070,13 @@ constexpr bool is_real_type(extent_types_t type) {
std::ostream &operator<<(std::ostream &out, extent_types_t t);
enum class write_policy_t {
WRITE_BACK,
WRITE_THROUGH
};
std::ostream& operator<<(std::ostream& out, write_policy_t w);
/**
* rewrite_gen_t
*
@ -3654,6 +3661,7 @@ template <> struct fmt::formatter<crimson::os::seastore::transaction_type_t> : f
template <> struct fmt::formatter<crimson::os::seastore::write_result_t> : fmt::ostream_formatter {};
template <> struct fmt::formatter<crimson::os::seastore::omap_type_t> : fmt::ostream_formatter {};
template <> struct fmt::formatter<ceph::buffer::list> : fmt::ostream_formatter {};
template <> struct fmt::formatter<crimson::os::seastore::write_policy_t> : fmt::ostream_formatter {};
#endif
namespace fmt {

View File

@ -878,6 +878,7 @@ TransactionManager::rewrite_logical_extent(
// get target rewrite generation
extent->get_rewrite_generation(),
is_tracked)->cast<LogicalChildNode>();
assert(nextent->get_write_policy() != write_policy_t::WRITE_THROUGH);
nextent->rewrite(t, *extent, 0);
DEBUGT("rewriting meta -- {} to {}", t, *extent, *nextent);
@ -919,10 +920,15 @@ TransactionManager::rewrite_logical_extent(
t,
extent->get_type(),
extent->get_length(),
extent->get_user_hint(),
// get target rewrite generation
extent->get_rewrite_generation(),
is_tracked);
{
extent->get_user_hint(),
// get target rewrite generation
extent->get_rewrite_generation(),
is_tracked,
// WRITH_THROUGH is only effective for client io, so
// always set the write policy to WRITE_BACK here
write_policy_t::WRITE_BACK
});
extent_len_t off = 0;
auto left = extent->get_length();
extent_ref_count_t refcount = 0;
@ -934,6 +940,7 @@ TransactionManager::rewrite_logical_extent(
t.force_rewrite_conflict = (extents.size() > 1);
for (auto &_nextent : extents) {
auto nextent = _nextent->template cast<LogicalChildNode>();
assert(nextent->get_write_policy() != write_policy_t::WRITE_THROUGH);
bool first_extent = (off == 0);
ceph_assert(left >= nextent->get_length());
nextent->rewrite(t, *extent, off);
@ -1213,9 +1220,12 @@ TransactionManager::promote_extent(
t,
orig_ext->get_type(),
orig_ext->get_length(),
placement_hint_t::HOT,
INIT_GENERATION,
true);
{
placement_hint_t::HOT,
INIT_GENERATION,
true,
write_policy_t::WRITE_BACK
});
promoted_extents.reserve(promoted_raw_extents.size());

View File

@ -591,7 +591,11 @@ public:
SUBDEBUGT(seastore_tm, "{} hint {}~0x{:x} phint={} ...",
t, T::TYPE, laddr_hint, len, placement_hint);
auto exts = cache->alloc_new_data_extents<T>(
t, len, {placement_hint, INIT_GENERATION});
t, len,
{
placement_hint, INIT_GENERATION, false,
epm->get_write_policy(T::TYPE, len)
});
// user must initialize the logical extent themselves
assert(is_user_transaction(t.get_src()));
for (auto& ext : exts) {

View File

@ -327,7 +327,9 @@ struct lba_btree_test : btree_test_base {
check.emplace(addr, get_map_val(len, TestBlock::TYPE));
lba_btree_update([=, this](auto &btree, auto &t) {
auto extents = cache->alloc_new_data_extents<TestBlock>(
t, TestBlock::SIZE, {placement_hint_t::HOT, 0, false, get_paddr()});
t, TestBlock::SIZE,
{placement_hint_t::HOT, 0, false,
write_policy_t::WRITE_BACK, get_paddr()});
return seastar::do_with(
std::move(extents),
[this, addr, &t, len, &btree](auto &extents) {
@ -545,7 +547,9 @@ struct btree_lba_manager_test : btree_test_base {
*t.t,
[=, this](auto &t) {
auto extents = cache->alloc_new_data_extents<TestBlock>(
t, TestBlock::SIZE, {placement_hint_t::HOT, 0, false, get_paddr()});
t, TestBlock::SIZE,
{placement_hint_t::HOT, 0, false,
write_policy_t::WRITE_BACK, get_paddr()});
return seastar::do_with(
std::vector<LogicalChildNodeRef>(
extents.begin(), extents.end()),

View File

@ -1150,6 +1150,7 @@ struct transaction_manager_test_t :
t,
placement_hint_t::HOT,
gen,
write_policy_t::WRITE_BACK,
false);
if (expected_generations[t][gen] != epm_gen) {
logger().error("caller: {}, extent type: {}, input generation: {}, "