Skip to content

SDSTOR-22799 clean up: remove GC shard header/footer I/O, blob commit validation#446

Open
Hooper9973 wants to merge 1 commit into
eBay:dev/refactor_create_seal_shardfrom
Hooper9973:SDSTOR-22799-opt
Open

SDSTOR-22799 clean up: remove GC shard header/footer I/O, blob commit validation#446
Hooper9973 wants to merge 1 commit into
eBay:dev/refactor_create_seal_shardfrom
Hooper9973:SDSTOR-22799-opt

Conversation

@Hooper9973

@Hooper9973 Hooper9973 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor
  • GC no longer writes physical shard header/footer blocks

  • Blob commit validates chunk state matches shard metadata

@Hooper9973 Hooper9973 force-pushed the SDSTOR-22799-opt branch 2 times, most recently from f8e31f5 to 1237535 Compare June 30, 2026 09:25
@codecov-commenter

codecov-commenter commented Jun 30, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 61.01695% with 23 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (dev/refactor_create_seal_shard@340788e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/lib/homestore_backend/gc_manager.cpp 62.96% 15 Missing and 5 partials ⚠️
src/lib/homestore_backend/hs_blob_manager.cpp 50.00% 2 Missing ⚠️
src/lib/homestore_backend/heap_chunk_selector.cpp 0.00% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@                        Coverage Diff                        @@
##             dev/refactor_create_seal_shard     #446   +/-   ##
=================================================================
  Coverage                                  ?   54.83%           
=================================================================
  Files                                     ?       36           
  Lines                                     ?     5281           
  Branches                                  ?      664           
=================================================================
  Hits                                      ?     2896           
  Misses                                    ?     2087           
  Partials                                  ?      298           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Hooper9973 Hooper9973 force-pushed the SDSTOR-22799-opt branch 2 times, most recently from ea3c34b to 664db00 Compare July 1, 2026 00:36
@@ -292,9 +292,6 @@ class GCManager {
pdev_gc_metrics& metrics() { return metrics_; }

private:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this private

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment thread src/lib/homestore_backend/heap_chunk_selector.cpp Outdated
}
bool res = chunk_selector_->recover_pg_chunks_states(pair.first, excluding_chunks);
// After restart, each chunk retains at most one shard log, because every
// seal-shard commit forces a cp flush.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, we only flush dc_lsn, not trigger a cp flush as of now

Comment on lines +248 to +277
// check the blob's pchunk id and shard's pchunk id for this blob
if (blob_info.pbas.chunk_num() != sb_pchunk_id) {
BLOGE(tid, blob_info.shard_id, blob_info.blob_id,
"commit-time pchunk id mismatch: blob pchunk={} shard pchunk={}", blob_info.pbas.chunk_num(),
sb_pchunk_id);
RELEASE_ASSERT(false, "traceID={}, commit-time pchunk id mismatch: blob pchunk={} shard pchunk={}", tid,
blob_info.pbas.chunk_num(), sb_pchunk_id);
}
// check shard meta consistency
const auto ext_chunk = chunk_selector()->get_pg_vchunk(pg_id, sb_vchunk_id);
if (ext_chunk == nullptr) {
BLOGE(tid, blob_info.shard_id, blob_info.blob_id,
"chunk not found at commit time: pg={} v_chunk={} shard=0x{:x} blob={}", pg_id, sb_vchunk_id,
blob_info.shard_id, blob_info.blob_id);
RELEASE_ASSERT(false, "traceID={}, chunk not found at commit time: pg={} v_chunk={} shard=0x{:x} blob={}",
tid, pg_id, sb_vchunk_id, blob_info.shard_id, blob_info.blob_id);
} else if (!ext_chunk->valid() || ext_chunk->m_state != ChunkState::INUSE ||
ext_chunk->m_owner_shard_id != blob_info.shard_id || ext_chunk->get_chunk_id() != sb_pchunk_id) {
BLOGE(tid, blob_info.shard_id, blob_info.blob_id,
"commit-time chunk state invalid: pg={} v_chunk={} blob={} state={} owner=0x{:x} pchunk={} "
"sb_pchunk={}",
pg_id, sb_vchunk_id, blob_info.blob_id, ext_chunk->m_state, ext_chunk->m_owner_shard_id.value_or(0),
ext_chunk->get_chunk_id(), sb_pchunk_id);
RELEASE_ASSERT(
false,
"traceID={}, commit-time chunk state invalid: pg={} v_chunk={} blob={} state={} owner=0x{:x} "
"pchunk={} sb_pchunk={}",
tid, pg_id, sb_vchunk_id, blob_info.blob_id, ext_chunk->m_state,
ext_chunk->m_owner_shard_id.value_or(0), ext_chunk->get_chunk_id(), sb_pchunk_id);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better consolidate BLOGE and RELEASE_ASSERT into only one RELEASE_ASSERT for all the logs here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

--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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any issue without this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

I met one time on my env, two case runs in parallel.

LOGDEBUGMOD(gcmgr, "EGC recovery: set move_from_chunk={} owner to shard=0x{:x} for replay",
move_from_chunk, open_shard.value());
} else {
LOGWARNMOD(gcmgr, "EGC recovery: no open shard found in move_from_chunk={} or move_to_chunk={}",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we assert here? this is an unexpected case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread src/lib/homestore_backend/heap_chunk_selector.cpp Outdated
Comment thread src/lib/homestore_backend/heap_chunk_selector.h Outdated
Comment thread src/lib/homestore_backend/heap_chunk_selector.h Outdated
for (size_t v_chunk_id = 0; v_chunk_id < pg_chunks.size(); ++v_chunk_id) {
auto chunk = pg_chunks[v_chunk_id];
pg_chunk_collection->m_total_blks += chunk->get_total_blks();
if (excluding_v_chunk_ids.find(v_chunk_id) == excluding_v_chunk_ids.end()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the "DISK_DOWN" state will be affected

}
if (!exist_already) {
// Validate shard vchunk/pchunk mapping for this blob and verify chunk state at commit time.
// During log replay, GC triggers a checkpoint flush, so logs after cp_lsn should not reference GC-moved blobs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What corner case does GC refer to here? Seems a bit complex. Could we move the check to the allocation stage instead? That way, we might avoid the more complicated validation logic later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here checks three things:

  1. shard pchunk, vchunk consistency
  2. blob pba should match pchunk
  3. chunk state check.

If moves to blob_put_get_blk_alloc_hints, here are two concerns :

  1. we cannot check the 2 item, which has shown up in gc issue.
  2. It's hard to define what to do if the blob is trying to alloc on a sealed shard( seal shard+put blob racing). Because a sealed shard may in GC state, which is a intermediate state. Although we can reject the request, set error to the context, it will trigger a emergency on the specific chunk. And the concern 1 cannot be resolved.

The comment means :
Not this pr involved. When I implement the PR, I found the line223:add_to_index_table , this function's behavior is:
if put a exist key, the value(pba) should be same, otherwise it will not return btree_status_t::success.
So I'm confused, what will happen, if we replay a sealed shard 's put blob log (which chunk has finished gc), the blob's currenty pbs value is different with the pbs in logs.
But GC will triggered CP flush, so this scenario won't show up.

@xiaoxichen xiaoxichen Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced of putting such complicated check on each blob commit...
it just as wired as you have a blood check at the end of each breath

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only check the consistency between blob pba and shard's pchunkid

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot get why cannot check the 2 item, could you explain more? --- BTW checking at alloc also works for 'replay a sealed shard 's put blob log '

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are two time spot:

  1. get_blob_alloc_hint
  2. on_commit

get_blob_alloc_hint will be racing with two parallel operation : 1. seal shard on commit 2.GC
Whenever you check, you can only get the temporary state.
For an example, the GC issue2: put blob to a sealed shard.

T1: alloc pba, if check at this time, all things looks good.
T2: seal shard
T3: GC, since the blob is not committed, the data will be lost.

Although this case will be fixed, we can know only the on commit is the final state, we check the consistency after inserting the blob index, if the shard chunk id match the blob's pba, the blob is good.

RELEASE_ASSERT(false, "traceID={}, chunk not found at commit time: pg={} v_chunk={} shard=0x{:x} blob={}",
tid, pg_id, sb_vchunk_id, blob_info.shard_id, blob_info.blob_id);
} else if (!ext_chunk->valid() ||
(ext_chunk->m_state == ChunkState::INUSE && ext_chunk->m_owner_shard_id != blob_info.shard_id) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, after this PR the shard state is either loaded when the shard SB is found, or updated by that shard’s log replay. Also, only the chunk for the open shard should be inuse (on_log_replay_done selects the specific chunk). But could blob commit hit a case where the blob’s shard has already been sealed, and the same chunk has since been taken over by a new shard, causing the shard-id check here to fail?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The major concern is to adapt log replay.

Currently all chunks are initalized after recovering meta(pg, shard)
And will select all open shard on log replay done
Here are all the logs between cp_lsn & dc_lsn:

LSN 11: create shard 1 on chunk x
LSN 12: put blob b1_1
LSN 13: seal shard1 on chunk y
LSN 14: create shard2 on chunk x
LSN 15: put blob b2_1

Current on commit logic is:

  • create shard, won't select a existing shard.
  • seal shard, seal all shard.

The recovery will like, the chunk's always be available with no owner_shard_id, before on_log_replay_done.
So I only check the owner shard when the chunk is in use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I understand this case, but will this case happen and failed check: the blob’s shard has already been sealed, and the same chunk has since been taken over by a new shard, then ext_chunk->m_owner_shard_id != blob_info.shard_id

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That case will be intercepted before being inserted into the index table by comparing with sealed_lsn :
https://github.com/Hooper9973/HomeObject/blob/ee5081f4965743898d6cddf88a14f8ada87d17d3/src/lib/homestore_backend/hs_blob_manager.cpp#L306

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh,,, then another question is : will this happen in replay senario:
T1: put blob on shard A with LSN 100
T2: seal shard A with LSN 101, shard sb: sealed_lsn=101
T3: create shard B with LSN 102, chunk owner = B
T4: crash
After crash, assume that lsn100 should replay and the blob idx not persited, then 2 case:

  1. replay lsn100 but reject at https://github.com/Hooper9973/HomeObject/blob/ee5081f4965743898d6cddf88a14f8ada87d17d3/src/lib/homestore_backend/hs_blob_manager.cpp#L306, so blob index not udpate
  2. if 1 pass, assert at chunk owner_shard_id check

@xiaoxichen xiaoxichen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we target the ownership tracking as best-effort?
for example if the ownership tracking is established in-memory upon on_log_replay_done, will it simplify some of the corner cases?

The ownership tracking is something that helps us to reduce complexity and detecting issues. if we need a lot of fancy stuff to make the tracking right, even potentially more bugs and fixes in the tracking code, it doesnt make sense

Comment thread src/lib/homestore_backend/heap_chunk_selector.h Outdated
}
if (!exist_already) {
// Validate shard vchunk/pchunk mapping for this blob and verify chunk state at commit time.
// During log replay, GC triggers a checkpoint flush, so logs after cp_lsn should not reference GC-moved blobs.

@xiaoxichen xiaoxichen Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced of putting such complicated check on each blob commit...
it just as wired as you have a blood check at the end of each breath

RELEASE_ASSERT(open_shard.has_value(),
"EGC recovery: no open shard found in move_from_chunk={} or move_to_chunk={}", move_from_chunk,
move_to_chunk);
move_from_vchunk->m_owner_shard_id = open_shard;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move_from_vchunk->m_owner_shard_id = open_shard.value()?

Comment on lines 164 to 179
add_test(NAME HomestoreTestGC COMMAND homestore_test_gc --executor immediate --config_path ./
--override_config hs_backend_config.enable_gc=true
--override_config hs_backend_config.gc_enable_read_verify=true
--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)

# Shard-race repro tests: require chunks_per_pg=1 (forces vchunk reuse after seal) and gc_garbage_rate_threshold=0.
# Run together since they share the same config requirements.
add_test(NAME HomestoreTestGC_ShardRaceTests
COMMAND homestore_test_gc --executor immediate --config_path ./
--chunks_per_pg 1
--override_config hs_backend_config.enable_gc=true
--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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest combine these two test into only one HomestoreTestGC

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShardRaceTests need additional parameter --chunks_per_pg 1, that's the reason I split them

LOGWARNMOD(homeobject,
"v_chunk_id={} for pg={} is INUSE by shard=0x{:x}, cannot select for shard=0x{:x}",
v_chunk_id, pg_id, chunk->m_owner_shard_id.value_or(0), owner_shard_id);
return nullptr;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure it is safe to return a nullptr, should we continue waiting in the loop until the chunk to be available

}
if (state == ShardInfo::State::SEALED) {
home_obj_.chunk_selector()->release_chunk(ctx_->pg_id, v_chunk_id.value());
home_obj_.chunk_selector()->release_chunk(ctx_->pg_id, v_chunk_id.value(), ctx_->shard_cursor);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add an assert to check if the return value of release_chunk is true

@xiaoxichen

Copy link
Copy Markdown
Collaborator

@Hooper9973 based on the review discussion, it seems like we should first merge those cleanup codes into 0.5.6
and leave the chunk ownership tracking into 0.5.7 , what do you think?

cc @JacksonYao287 @Besroy

}
if (!exist_already) {
// Validate the blob was written to the pchunk the shard records.
// Catches GC-related inconsistencies (EGC moved chunk while blob was in-flight).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

egc will start only when all the appended log are committed, so I think if egc is working, there is no in-flight put_blob

}
std::scoped_lock lock(pg_chunk_collection->mtx);
auto chunk = pg_chunks[v_chunk_id];
if (chunk->m_state == ChunkState::AVAILABLE) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest add release_assert(chunk->m_state != ChunkState::GC)

@JacksonYao287

Copy link
Copy Markdown
Collaborator

leave the chunk ownership tracking into 0.5.7

if the committed two prs are sufficient to solve the create shard/ GC issues, I am +1 with this proposal

@Besroy

Besroy commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@Hooper9973 based on the review discussion, it seems like we should first merge those cleanup codes into 0.5.6 and leave the chunk ownership tracking into 0.5.7 , what do you think?

cc @JacksonYao287 @Besroy

lgtm, we can merge thoese cleanup codes into 0.5.6, build a new image and test on SH as soon as possible

@Hooper9973

Copy link
Copy Markdown
Contributor Author

@Hooper9973 based on the review discussion, it seems like we should first merge those cleanup codes into 0.5.6
and leave the chunk ownership tracking into 0.5.7 , what do you think?

Sounds good, I agree with that split. I’ll prepare a new version

@Hooper9973 Hooper9973 changed the title SDSTOR-22799 feat: chunk ownership tracking, remove GC shard header/footer I/O, blob commit validation SDSTOR-22799 clean up: remove GC shard header/footer I/O, blob commit validation Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants