From 8c74e07e2019dc0db7b103e0d793633918b25b8f Mon Sep 17 00:00:00 2001 From: Hooper Date: Mon, 6 Jul 2026 22:35:16 +0800 Subject: [PATCH] Remove unused shard header/footer generation during gc, and add validation on blob commit --- conanfile.py | 2 +- src/lib/homestore_backend/CMakeLists.txt | 14 + src/lib/homestore_backend/gc_manager.cpp | 345 ++++++------------ src/lib/homestore_backend/gc_manager.hpp | 4 - .../homestore_backend/heap_chunk_selector.cpp | 30 +- src/lib/homestore_backend/hs_blob_manager.cpp | 14 + .../homestore_backend/tests/hs_gc_tests.cpp | 36 +- 7 files changed, 153 insertions(+), 292 deletions(-) diff --git a/conanfile.py b/conanfile.py index 5e03ecdf..2fc86598 100644 --- a/conanfile.py +++ b/conanfile.py @@ -10,7 +10,7 @@ class HomeObjectConan(ConanFile): name = "homeobject" - version = "4.2.2" + version = "4.2.3" homepage = "https://github.com/eBay/HomeObject" description = "Blob Store built on HomeStore" diff --git a/src/lib/homestore_backend/CMakeLists.txt b/src/lib/homestore_backend/CMakeLists.txt index 4361b995..4df2b8a7 100644 --- a/src/lib/homestore_backend/CMakeLists.txt +++ b/src/lib/homestore_backend/CMakeLists.txt @@ -177,3 +177,17 @@ add_test(NAME HomestoreTestGC_ShardRaceTests --override_config hs_backend_config.gc_garbage_rate_threshold=0 --override_config hs_backend_config.gc_scan_interval_sec=5 --gtest_filter=HomeObjectFixture.StalePChunkRouteAfterGC:HomeObjectFixture.StaleBlobRouteAfterSealAndGC) + +# All tests share disk, port, and shared-memory resources, so run them serially to avoid parallel conflicts. +set_tests_properties( + HomestoreTestBlob + HomestoreTestMisc + HomestoreTestReplaceMember + HomestoreTestReplaceMemberWithBaselineResync + HomestoreTestBaselineResync + HomestoreResyncTestWithFollowerRestart + HomestoreResyncTestWithLeaderRestart + FetchDataWithOriginatorGC + HomestoreTestGC + HomestoreTestGC_ShardRaceTests + PROPERTIES RUN_SERIAL TRUE) diff --git a/src/lib/homestore_backend/gc_manager.cpp b/src/lib/homestore_backend/gc_manager.cpp index df7baf34..f0eb9577 100644 --- a/src/lib/homestore_backend/gc_manager.cpp +++ b/src/lib/homestore_backend/gc_manager.cpp @@ -650,27 +650,6 @@ bool GCManager::pdev_gc_actor::replace_blob_index( return true; } -sisl::sg_list GCManager::pdev_gc_actor::generate_shard_super_blk_sg_list(shard_id_t shard_id) { - // TODO: do the buffer check before using it. - auto raw_shard_sb = m_hs_home_object->_get_hs_shard(shard_id); - RELEASE_ASSERT(raw_shard_sb, "can not find shard super blk for shard_id=0x{:x} !!!", shard_id); - - const auto shard_sb = - const_cast< HSHomeObject::HS_Shard* >(d_cast< const HSHomeObject::HS_Shard* >(raw_shard_sb))->sb_.get(); - - auto blk_size = homestore::data_service().get_blk_size(); - auto shard_sb_size = sizeof(HSHomeObject::shard_info_superblk); - auto total_size = sisl::round_up(shard_sb_size, blk_size); - auto shard_sb_buf = iomanager.iobuf_alloc(blk_size, total_size); - - std::memcpy(shard_sb_buf, shard_sb, shard_sb_size); - - sisl::sg_list shard_sb_sgs; - shard_sb_sgs.size = total_size; - shard_sb_sgs.iovs.emplace_back(iovec{.iov_base = shard_sb_buf, .iov_len = total_size}); - return shard_sb_sgs; -} - // note that, when we copy data, there is not create shard or put blob in this chunk, only delete blob might happen. bool GCManager::pdev_gc_actor::copy_valid_data( chunk_id_t move_from_chunk, chunk_id_t move_to_chunk, @@ -723,13 +702,11 @@ bool GCManager::pdev_gc_actor::copy_valid_data( homestore::blk_alloc_hints hints; hints.chunk_id_hint = move_to_chunk; - homestore::MultiBlkId out_blkids; auto pg_index_table = m_hs_home_object->get_hs_pg(pg_id)->index_table_; const auto blk_size = data_service.get_blk_size(); for (const auto& shard_id : shards) { - bool is_last_shard = (shard_id == last_shard_id); std::vector< std::pair< BlobRouteKey, BlobRouteValue > > valid_blob_indexes; auto start_key = BlobRouteKey{BlobRoute{shard_id, std::numeric_limits< uint64_t >::min()}}; auto end_key = BlobRouteKey{BlobRoute{shard_id, std::numeric_limits< uint64_t >::max()}}; @@ -786,12 +763,8 @@ bool GCManager::pdev_gc_actor::copy_valid_data( } if (valid_blob_indexes.empty()) { - GCLOGD(task_id, pg_id, shard_id, "empty shard found in move_from_chunk={}", move_from_chunk); - // TODO::send a delete shard request to raft channel. there is a case that when we are doing gc, the - // shard becomes empty, need to handle this case - - // we should always write a shard header for the last shard if the state of it is open. - if (last_shard_state != ShardInfo::State::OPEN || !is_last_shard) continue; + GCLOGD(task_id, pg_id, shard_id, "empty shard found in move_from_chunk={}, skip", move_from_chunk); + continue; } else { GCLOGD(task_id, pg_id, shard_id, "{} valid blobs found in move_from_chunk={}", valid_blob_indexes.size(), move_from_chunk); @@ -812,220 +785,120 @@ bool GCManager::pdev_gc_actor::copy_valid_data( } } - // prepare a shard header for this shard in move_to_chunk - sisl::sg_list header_sgs = generate_shard_super_blk_sg_list(shard_id); - - // we ignore the state in shard header blk. we never read a shard header since we don`t know where it is(nor - // record the pba in indextable) -#if 0 - // we now generate shard header from metablk. the shard state in shard header blk should be open, but for sealed - // shard, the state in the generated in-memory header_sgs is sealed. - if (!is_last_shard_in_emergent_chunk) { - // for the sealed shard, the shard state in header should also be open.now, the written header is the same - // as footer except the shard state, so we lost the original header. - r_cast< HSHomeObject::shard_info_superblk* >(header_sgs.iovs[0].iov_base)->info.state = - ShardInfo::State::OPEN; - - // TODO:: get the original header from the move_from_chunk and change the following part if needed. - /* - uint64_t created_time; - uint64_t last_modified_time; - uint64_t available_capacity_bytes; - uint64_t total_capacity_bytes; - */ - } -#endif - // TODO::involve ratelimiter in the following code, where read/write are scheduled. or do we need a central - // ratelimter shared by all components except client io? - const auto succeed_copying_shard = - // 1 write the shard header to move_to_chunk - data_service.async_alloc_write(header_sgs, hints, out_blkids) - .thenValue([this, &hints, &move_to_chunk, &move_from_chunk, &is_last_shard, &shard_id, &blk_size, - &valid_blob_indexes, &data_service, task_id, &last_shard_state, &copied_blobs, pg_id, - header_sgs = std::move(header_sgs)](auto&& err) { - RELEASE_ASSERT(header_sgs.iovs.size() == 1, "header_sgs.iovs.size() should be 1, but not!"); - // shard header occupies one blk - COUNTER_INCREMENT(metrics_, gc_write_blk_count, 1); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(header_sgs.iovs[0].iov_base)); - if (err) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to write shard header for move_to_chunk={}, err={}, err_category={}, " - "err_message={}", - move_to_chunk, err.value(), err.category().name(), err.message()); - return folly::makeFuture< bool >(false); - } - - if (valid_blob_indexes.empty()) { - RELEASE_ASSERT(is_last_shard && last_shard_state == ShardInfo::State::OPEN, - "find empty shard in move_from_chunk={} " - "but is_emergent is false, shard_id=0x{:x}, pg_id={}", - move_from_chunk, shard_id, pg_id); - return folly::makeFuture< bool >(true); - } + // copy all the valid blobs in the shard from move_from_chunk to move_to_chunk + std::vector< folly::Future< bool > > futs; + for (const auto& [k, v] : valid_blob_indexes) { + // k is shard_id + blob_id, v is multiblk id + auto pba = v.pbas(); + auto total_size = pba.blk_count() * blk_size; + + // buffer for read and write data + sisl::sg_list data_sgs; + data_sgs.size = total_size; + data_sgs.iovs.emplace_back( + iovec{.iov_base = iomanager.iobuf_alloc(blk_size, total_size), .iov_len = total_size}); + + futs.emplace_back(std::move( + // read blob from move_from_chunk + data_service.async_read(pba, data_sgs, total_size) + .thenValue([this, k, &hints, &move_from_chunk, &move_to_chunk, &data_service, task_id, pg_id, + data_sgs = std::move(data_sgs), pba, &copied_blobs](auto&& err) { + COUNTER_INCREMENT(metrics_, gc_read_blk_count, pba.blk_count()); + RELEASE_ASSERT(data_sgs.iovs.size() == 1, "data_sgs.iovs.size() should be 1, but not!"); + + const auto shard_id = k.key().shard; + const auto blob_id = k.key().blob; + + if (err) { + GCLOGE(task_id, pg_id, shard_id, + "Failed to read blob from move_from_chunk={}, blob_id={}, err={}, " + "err_category={}, err_message={}", + move_from_chunk, blob_id, err.value(), err.category().name(), err.message()); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + return folly::makeFuture< bool >(false); + } + + GCLOGD(task_id, pg_id, shard_id, + "successfully read blob from move_from_chunk={}, blob_id={}, pba={}", move_from_chunk, + blob_id, pba.to_string()); + + if (m_enable_read_verify) { + // after a blob is deleted at originator, if it receives a fetch_data request of + // this blob, a fake delete_marker blob will be returned to the requester. This + // case happens in incremental resync scenario. when verifying blob, if it is a + // delete_marker, we let it pass the verification in gc scenario so that it will + // not block any gc task. + if (!m_hs_home_object->verify_blob(data_sgs.iovs[0].iov_base, shard_id, blob_id, true)) { + GCLOGE(task_id, pg_id, shard_id, + "blob verification fails for move_from_chunk={}, blob_id={}, pba={}", + move_from_chunk, blob_id, pba.to_string()); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + return folly::makeFuture< bool >(false); + } + } + + // write the blob to the move_to_chunk. we do not care about the blob order in a + // shard since we can not guarantee a certain order + homestore::MultiBlkId new_pba; + return data_service.async_alloc_write(data_sgs, hints, new_pba) + .thenValue([this, shard_id, blob_id, new_pba, &move_to_chunk, task_id, pg_id, &copied_blobs, + data_sgs = std::move(data_sgs)](auto&& err) { + COUNTER_INCREMENT(metrics_, gc_write_blk_count, new_pba.blk_count()); + RELEASE_ASSERT(data_sgs.iovs.size() == 1, "data_sgs.iovs.size() should be 1, but not!"); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + if (err) { + GCLOGE(task_id, pg_id, shard_id, + "Failed to write blob to move_to_chunk={}, blob_id={}, err={}, " + "err_category={}, err_message={}", + move_to_chunk, blob_id, err.value(), err.category().name(), err.message()); + return false; + } - std::vector< folly::Future< bool > > futs; - - // 2 copy all the valid blobs in the shard from move_from_chunk to move_to_chunk - for (const auto& [k, v] : valid_blob_indexes) { - // k is shard_id + blob_id, v is multiblk id - auto pba = v.pbas(); - auto total_size = pba.blk_count() * blk_size; - - // buffer for read and write data - sisl::sg_list data_sgs; - data_sgs.size = total_size; - data_sgs.iovs.emplace_back( - iovec{.iov_base = iomanager.iobuf_alloc(blk_size, total_size), .iov_len = total_size}); - - futs.emplace_back(std::move( - // read blob from move_from_chunk - data_service.async_read(pba, data_sgs, total_size) - .thenValue([this, k, &hints, &move_from_chunk, &move_to_chunk, &data_service, task_id, - pg_id, data_sgs = std::move(data_sgs), pba, &copied_blobs](auto&& err) { - COUNTER_INCREMENT(metrics_, gc_read_blk_count, pba.blk_count()); - RELEASE_ASSERT(data_sgs.iovs.size() == 1, - "data_sgs.iovs.size() should be 1, but not!"); - - const auto shard_id = k.key().shard; - const auto blob_id = k.key().blob; - - if (err) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to read blob from move_from_chunk={}, blob_id={}, err={}, " - "err_category={}, err_message={}", - move_from_chunk, blob_id, err.value(), err.category().name(), - err.message()); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - return folly::makeFuture< bool >(false); - } - - GCLOGD(task_id, pg_id, shard_id, - "successfully read blob from move_from_chunk={}, blob_id={}, pba={}", - move_from_chunk, blob_id, pba.to_string()); - - if (m_enable_read_verify) { - // after a blob is deleted at originator, if it receives a fetch_data request of - // this blob, a fake delete_marker blob will be returned to the requester. This - // case happens in incremental resync scenario. when verifying blob, if it is a - // delete_marker, we let it pass the verification in gc scenario so that it will - // not block any gc task. - if (!m_hs_home_object->verify_blob(data_sgs.iovs[0].iov_base, shard_id, blob_id, - true)) { - GCLOGE(task_id, pg_id, shard_id, - "blob verification fails for move_from_chunk={}, blob_id={}, pba={}", - move_from_chunk, blob_id, pba.to_string()); - iomanager.iobuf_free( - reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - return folly::makeFuture< bool >(false); - } - } - - // write the blob to the move_to_chunk. we do not care about the blob order in a - // shard since we can not guarantee a certain order - homestore::MultiBlkId new_pba; - return data_service.async_alloc_write(data_sgs, hints, new_pba) - .thenValue([this, shard_id, blob_id, new_pba, &move_to_chunk, task_id, pg_id, - &copied_blobs, data_sgs = std::move(data_sgs)](auto&& err) { - COUNTER_INCREMENT(metrics_, gc_write_blk_count, new_pba.blk_count()); - RELEASE_ASSERT(data_sgs.iovs.size() == 1, - "data_sgs.iovs.size() should be 1, but not!"); - iomanager.iobuf_free( - reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - if (err) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to write blob to move_to_chunk={}, blob_id={}, err={}, " - "err_category={}, err_message={}", - move_to_chunk, blob_id, err.value(), err.category().name(), - err.message()); - return false; - } - - // insert a new entry to gc index table for this blob. [move_to_chunk_id, - // shard_id, blob_id] -> [new pba] - BlobRouteByChunkKey key{BlobRouteByChunk{move_to_chunk, shard_id, blob_id}}; - BlobRouteValue value{new_pba}, existing_value; - - homestore::BtreeSinglePutRequest put_req{ - &key, &value, homestore::btree_put_type::INSERT, &existing_value}; - auto status = m_index_table->put(put_req); - if (status != homestore::btree_status_t::success) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to insert new key to gc index table for " - "move_to_chunk={}, blob_id={}, err={}", - move_to_chunk, blob_id, status); - return false; - } - - GCLOGD(task_id, pg_id, shard_id, - "successfully insert new key to gc index table for " - "move_to_chunk={}, blob_id={}, new_pba={}", - move_to_chunk, blob_id, new_pba.to_string()); - - BlobRouteByChunk route_key{move_to_chunk, shard_id, blob_id}; - auto ret = copied_blobs.insert(route_key, value); - RELEASE_ASSERT(ret.second, - "we should not copy the same blob twice in gc task, " - "move_to_chunk={}, shard_id=0x{:x}, pg_id={}, blob_id={}", - move_to_chunk, shard_id, pg_id, blob_id); - - return true; - }); - }))); - } + // insert a new entry to gc index table for this blob. [move_to_chunk_id, + // shard_id, blob_id] -> [new pba] + BlobRouteByChunkKey key{BlobRouteByChunk{move_to_chunk, shard_id, blob_id}}; + BlobRouteValue value{new_pba}, existing_value; - // 3 write a shard footer for this shard - sisl::sg_list footer_sgs = generate_shard_super_blk_sg_list(shard_id); - return folly::collectAllUnsafe(futs) - .thenValue([this, &is_last_shard, &shard_id, &blk_size, &hints, &move_to_chunk, pg_id, - &last_shard_state, task_id, &data_service, footer_sgs](auto&& results) { - // if any blob copy fails, we will not write footer, and drop this gc task - for (auto const& ok : results) { - RELEASE_ASSERT(ok.hasValue(), "we never throw any exception when copying data"); - if (!ok.value()) { + homestore::BtreeSinglePutRequest put_req{ + &key, &value, homestore::btree_put_type::INSERT, &existing_value}; + auto status = m_index_table->put(put_req); + if (status != homestore::btree_status_t::success) { GCLOGE(task_id, pg_id, shard_id, - "Failed to copy blob for move_to_chunk={}, will cancel this task", - move_to_chunk); - // if any op fails, we drop this gc task. - return folly::makeFuture< std::error_code >( - std::make_error_code(std::errc::operation_canceled)); + "Failed to insert new key to gc index table for " + "move_to_chunk={}, blob_id={}, err={}", + move_to_chunk, blob_id, status); + return false; } - } - // we skip writing footer only if the last shard of this chunk is in open state. - if (is_last_shard && last_shard_state == ShardInfo::State::OPEN) { GCLOGD(task_id, pg_id, shard_id, - "skip writing the footer for move_to_chunk={} for emergent gc task", - move_to_chunk); - return folly::makeFuture< std::error_code >(std::error_code{}); - } + "successfully insert new key to gc index table for " + "move_to_chunk={}, blob_id={}, new_pba={}", + move_to_chunk, blob_id, new_pba.to_string()); + + BlobRouteByChunk route_key{move_to_chunk, shard_id, blob_id}; + auto ret = copied_blobs.insert(route_key, value); + RELEASE_ASSERT(ret.second, + "we should not copy the same blob twice in gc task, " + "move_to_chunk={}, shard_id=0x{:x}, pg_id={}, blob_id={}", + move_to_chunk, shard_id, pg_id, blob_id); + + return true; + }); + }))); + } - // write shard footer, which occupies one blk - - // in fact , we have a dangling reference of out_blkids here, but it does not matter since - // we do not use it after this and the async_alloc_write will complete soon and the memory - // of out_blkids will be released after that. - homestore::MultiBlkId out_blkids; - COUNTER_INCREMENT(metrics_, gc_write_blk_count, 1); - return data_service.async_alloc_write(footer_sgs, hints, out_blkids); - }) - .thenValue([this, &move_to_chunk, &shard_id, footer_sgs, task_id, pg_id](auto&& err) { - RELEASE_ASSERT(footer_sgs.iovs.size() == 1, "footer_sgs.iovs.size() should be 1, but not!"); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(footer_sgs.iovs[0].iov_base)); - - if (err) { - // we will come here if: - // 1 any blob copy fails, then err is operation_cancelled - // 2 write footer fails, then err is the error code of write footer - GCLOGE( - task_id, pg_id, shard_id, - "Failed to copy some blobs or failed to write shard footer for move_to_chunk={}, " - "err={}, error_category={}, error_message={}, pls check the log for more " - "detailed info", - move_to_chunk, err.value(), err.category().name(), err.message()); - return false; - } - return true; - }); + const auto succeed_copying_shard = + folly::collectAllUnsafe(futs) + .thenValue([this, &shard_id, &move_to_chunk, task_id, pg_id](auto&& results) { + for (auto const& ok : results) { + RELEASE_ASSERT(ok.hasValue(), "we never throw any exception when copying data"); + if (!ok.value()) { + GCLOGE(task_id, pg_id, shard_id, + "Failed to copy blob for move_to_chunk={}, will cancel this task", move_to_chunk); + return false; + } + } + return true; }) .get(); diff --git a/src/lib/homestore_backend/gc_manager.hpp b/src/lib/homestore_backend/gc_manager.hpp index f85d9718..abd078a6 100644 --- a/src/lib/homestore_backend/gc_manager.hpp +++ b/src/lib/homestore_backend/gc_manager.hpp @@ -291,10 +291,6 @@ class GCManager { pdev_gc_metrics& metrics() { return metrics_; } - private: - // utils - sisl::sg_list generate_shard_super_blk_sg_list(shard_id_t shard_id); - private: friend class gc_task_guard; uint32_t m_pdev_id; diff --git a/src/lib/homestore_backend/heap_chunk_selector.cpp b/src/lib/homestore_backend/heap_chunk_selector.cpp index 49f02d34..bfba1052 100644 --- a/src/lib/homestore_backend/heap_chunk_selector.cpp +++ b/src/lib/homestore_backend/heap_chunk_selector.cpp @@ -44,34 +44,10 @@ void HeapChunkSelector::add_chunk_internal(const chunk_num_t p_chunk_id, bool ad } } -// select_chunk will only be called in homestore when creating a shard. +// select_chunk will never be called in homestore since create shard is log only. csharedChunk HeapChunkSelector::select_chunk(homestore::blk_count_t count, const homestore::blk_alloc_hints& hint) { - auto& chunkIdHint = hint.chunk_id_hint; - if (chunkIdHint.has_value()) { - LOGWARNMOD(homeobject, "should not allocated a chunk with exiting chunkIdHint={} in hint!", - chunkIdHint.value()); - return nullptr; - } - - if (!hint.application_hint.has_value()) { - LOGWARNMOD(homeobject, "should not allocated a chunk without exiting application_hint in hint!"); - return nullptr; - } else { - // Both chunk_num_t and pg_id_t are of type uint16_t. - static_assert(std::is_same< pg_id_t, uint16_t >::value, "pg_id_t is not uint16_t"); - static_assert(std::is_same< homestore::chunk_num_t, uint16_t >::value, "chunk_num_t is not uint16_t"); - auto application_hint = hint.application_hint.value(); - pg_id_t pg_id = (uint16_t)(application_hint >> 16 & 0xFFFF); - homestore::chunk_num_t v_chunk_id = (uint16_t)(application_hint & 0xFFFF); - auto exVChunk = select_specific_chunk(pg_id, v_chunk_id); - if (exVChunk == nullptr) { - LOGWARNMOD(homeobject, "failed to select chunk with pg_id={} and v_chunk_id={} from application_hint={}", - pg_id, v_chunk_id, application_hint); - return nullptr; - } else { - return exVChunk->get_internal_chunk(); - } - } + RELEASE_ASSERT(false, "create shard is log only, this function should never be called"); + return nullptr; } bool HeapChunkSelector::try_mark_chunk_to_gc_state(const chunk_num_t chunk_id, bool force) { diff --git a/src/lib/homestore_backend/hs_blob_manager.cpp b/src/lib/homestore_backend/hs_blob_manager.cpp index 575f7c11..0a377e3f 100644 --- a/src/lib/homestore_backend/hs_blob_manager.cpp +++ b/src/lib/homestore_backend/hs_blob_manager.cpp @@ -229,6 +229,20 @@ bool HSHomeObject::local_add_blob_info(pg_id_t const pg_id, BlobInfo const& blob return false; } if (!exist_already) { + homestore::chunk_num_t sb_pchunk_id; + { + std::shared_lock lock_guard(_shard_lock); + const auto iter = _shard_map.find(blob_info.shard_id); + RELEASE_ASSERT(iter != _shard_map.end(), "shardID=0x{:x}, pg={}, shard=0x{:x}, shard does not exist", + blob_info.shard_id, (blob_info.shard_id >> homeobject::shard_width), + (blob_info.shard_id & homeobject::shard_mask)); + sb_pchunk_id = d_cast< HS_Shard* >((*iter->second).get())->p_chunk_id(); + } + RELEASE_ASSERT(blob_info.pbas.chunk_num() == sb_pchunk_id, + "traceID={}, commit-time pchunk mismatch: blob pchunk={} shard pchunk={} " + "shard=0x{:x} blob={} pg={}", + tid, blob_info.pbas.chunk_num(), sb_pchunk_id, blob_info.shard_id, blob_info.blob_id, pg_id); + // The PG superblock (durable entities) will be persisted as part of HS_CLIENT Checkpoint, which is always // done ahead of the Index Checkpoint. Hence, if the index already has this entity, whatever durable // counters updated as part of the update would have been persisted already in PG superblock. So if we were diff --git a/src/lib/homestore_backend/tests/hs_gc_tests.cpp b/src/lib/homestore_backend/tests/hs_gc_tests.cpp index 775ce430..450ff101 100644 --- a/src/lib/homestore_backend/tests/hs_gc_tests.cpp +++ b/src/lib/homestore_backend/tests/hs_gc_tests.cpp @@ -74,15 +74,13 @@ TEST_F(HomeObjectFixture, BasicGC) { uint64_t total_blob_occupied_blk_count{0}; const auto& shard_vec = pg_shard_id_vec[pg_id]; for (const auto& shard_id : shard_vec) { - // TODO: GC will not persist shard header/footer futher, - // temporarily comment blk count check. - // total_blob_occupied_blk_count += 2; /*header and footer*/ + // shard create/seal is log-only; no header/footer blocks written for (const auto& [_, blk_count] : shard_blob_ids_map[shard_id]) { total_blob_occupied_blk_count += blk_count; } } // check pg durable entities - // ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); + ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); // check pg index table, the valid blob index count should be equal to the blob count ASSERT_EQ(get_valid_blob_count_in_pg(pg_id), pg_blob_id[pg_id]); @@ -117,7 +115,7 @@ TEST_F(HomeObjectFixture, BasicGC) { ASSERT_TRUE(chunk_opt.has_value()); auto chunk_id = chunk_opt.value(); // now, the chunk state is not determined, maybe GC(being gc) or AVAILABLE(complete gc), skip checking it. - uint32_t used_blks{2}; /* header and footer */ + uint32_t used_blks{0}; for (const auto& [_, blk_count] : blob_to_blk_count) { used_blks += blk_count; @@ -192,14 +190,13 @@ TEST_F(HomeObjectFixture, BasicGC) { uint64_t total_blob_occupied_blk_count{0}; const auto& shard_vec = pg_shard_id_vec[pg_id]; for (const auto& shard_id : shard_vec) { - // total_blob_occupied_blk_count += 2; /*header and footer*/ for (const auto& [_, blk_count] : shard_blob_ids_map[shard_id]) { total_blob_occupied_blk_count += blk_count; } } - // ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, total_blob_occupied_blk_count); - // ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); + ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, total_blob_occupied_blk_count); + ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); } restart(); @@ -241,13 +238,12 @@ TEST_F(HomeObjectFixture, BasicGC) { uint64_t total_blob_occupied_blk_count{0}; const auto& shard_vec = pg_shard_id_vec[pg_id]; for (const auto& shard_id : shard_vec) { - // total_blob_occupied_blk_count += 2; /*header and footer*/ for (const auto& [_, blk_count] : shard_blob_ids_map[shard_id]) { total_blob_occupied_blk_count += blk_count; } } - // ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, total_blob_occupied_blk_count); - // ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); + ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, total_blob_occupied_blk_count); + ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); } // delete remaining blobs @@ -627,13 +623,10 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { uint64_t total_blob_occupied_blk_count{0}; const auto& shard_vec = pg_shard_id_vec[pg_id]; for (const auto& shard_id : shard_vec) { - total_blob_occupied_blk_count += 2; /*header and footer*/ for (const auto& [_, blk_count] : shard_blob_ids_map[shard_id]) { total_blob_occupied_blk_count += blk_count; } } - // for each chunk, we have an open shard, which has only header. - total_blob_occupied_blk_count -= pg_chunk_nums[pg_id]; ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, total_blob_occupied_blk_count); ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); @@ -685,13 +678,10 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { uint64_t total_blob_occupied_blk_count{0}; const auto& shard_vec = pg_shard_id_vec[pg_id]; for (const auto& shard_id : shard_vec) { - total_blob_occupied_blk_count += 2; /*header and footer*/ for (const auto& [_, blk_count] : shard_blob_ids_map[shard_id]) { total_blob_occupied_blk_count += blk_count; } } - // for each chunk, we have an open shard, which has only header. - total_blob_occupied_blk_count -= pg_chunk_nums[pg_id]; ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, total_blob_occupied_blk_count); ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, total_blob_occupied_blk_count); @@ -728,25 +718,23 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { futs.clear(); - // for each chunk in this pg, there is only one shard header + // for each chunk in this pg, all blobs are gone (no header/footer since shard create is log-only) for (const auto& [pg_id, chunk_num] : pg_chunk_nums) { const auto pg_chunks = chunk_selector->get_pg_chunks(pg_id); for (uint64_t i{0}; i < chunk_num; i++) { auto chunk_id = pg_chunks->at(i); auto EXVchunk = chunk_selector->get_extend_vchunk(chunk_id); - // the open shard is not sealed, so there is only the shard header for each shard - ASSERT_EQ(EXVchunk->get_used_blks(), 1); + ASSERT_EQ(EXVchunk->get_used_blks(), 0); } } // check vchunk to pchunk for every pg for (const auto& [pg_id, shard_vec] : pg_shard_id_vec) { auto& hs_pg = HS_PG_map[pg_id]; - // check pg durable entities. only shard header left, and every chunk has a open shard, so - // total_occupied_blk_count is equal to the num of chunks in this pg since each chunk has a shard header. - ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, pg_chunk_nums[pg_id]); - ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, pg_chunk_nums[pg_id]); + // all blobs deleted and gc'd; no header/footer blks since shard create is log-only + ASSERT_EQ(hs_pg->pg_sb_->total_occupied_blk_count, 0); + ASSERT_EQ(hs_pg->durable_entities().total_occupied_blk_count, 0); // after all blobs have been deleted, the pg index table should be empty ASSERT_EQ(get_valid_blob_count_in_pg(pg_id), 0);