feat(hgraph): support UpdateVector with deduplicate_storage - #2588
feat(hgraph): support UpdateVector with deduplicate_storage#2588jac0626 wants to merge 1 commit into
Conversation
|
/label status/waiting-for-review |
|
Automated pull request review completed. Review effort: No actionable inline findings were found. |
Merge Protections🟢 All 3 merge protections satisfied — ready to merge. Show 3 satisfied protections🟢 Require kind label
🟢 Require version label
🟢 Require linked issue for feature/bug PRs
|
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: medium (379 changed lines across 8 files).
Submitted 1 inline comment.
Reviewed commit 097f34f.
| const auto next_slot = this->code_slot_map_->PhysicalCount(); | ||
| this->ensure_physical_code_capacity_unlocked(next_slot + 1); | ||
| const auto new_slot = this->code_slot_map_->AllocateSlot(); | ||
| this->insert_persistent_codes_to_slot(new_base_vec, new_slot); |
There was a problem hiding this comment.
[P2] Make copy-on-write slot creation transactional
If any InsertVectorToCodeSlot operation throws (for example from allocation, encoding, or an IO write), AllocateSlot() has already permanently incremented physical_count_, while only some physical flatten layers may have advanced their own counts. The logical binding remains unchanged, but a retry consumes another slot; with precise/raw layers this can immediately make their serialized physical counts disagree, and even a base-only index can reach physical_count > serialized logical slots, causing deserialization validation to reject the index. Preserve the pre-update state on insertion failure or otherwise roll back/resynchronize the allocated slot and every code layer before returning the error.
| update_status = | ||
| update_status && high_precise_codes_->UpdateVector(new_base_vec, inner_id); | ||
| } | ||
| // A member previously split from a shared slot must keep its private raw code in sync on |
There was a problem hiding this comment.
[note] The comment says "Preserve the existing unique-ID behavior outside duplicate groups", but the enclosing condition not duplicate_ids.empty() means this branch is entered when the ID is part of a duplicate group (just not sharing a physical slot). The earlier comment on the same block already explains the intent well. Consider rewording to avoid confusion:
// A member previously split from a shared slot must keep its private raw
// code in sync on later updates, matching the per-layer update done for
// unique IDs in the non-dedup path.
| const auto current_slot = this->code_slot_map_->Resolve(inner_id); | ||
| const auto duplicate_ids = duplicate_tracker->GetDuplicateIds(inner_id); | ||
| bool slot_is_shared = false; | ||
| for (const auto duplicate_id : duplicate_ids) { |
There was a problem hiding this comment.
[suggestion] The slot_is_shared loop calls Resolve for every duplicate_id, including inner_id itself (if GetDuplicateIds returns the full group). When duplicate_id == inner_id, Resolve always returns current_slot, making the comparison trivially true. Consider skipping self to avoid the redundant atomic load:
for (const auto duplicate_id : duplicate_ids) {
if (duplicate_id == inner_id) {
continue;
}
if (this->code_slot_map_->Resolve(duplicate_id) == current_slot) {
slot_is_shared = true;
break;
}
}This is a minor efficiency concern since duplicate groups are typically small, but it makes the intent clearer.
| @@ -69,6 +69,35 @@ CodeSlotMap::PublishSlot(InnerIdType inner_id, CodeSlotIdType code_slot_id) { | |||
| published_logical_count_.fetch_add(1, std::memory_order_acq_rel); | |||
There was a problem hiding this comment.
[note] RebindSlot correctly does not increment published_logical_count_ — it replaces an existing binding rather than publishing a new logical ID. This is consistent with PublishSlot which does increment the counter. Worth keeping in mind for future serialization: the serialized format stores per-inner_id slot bindings, so rebinding is naturally captured without any format change.
LHT129
left a comment
There was a problem hiding this comment.
Overall this is a well-designed change. The copy-on-write approach for detaching a shared duplicate-group member is sound, and the concurrency handling with std::lock + std::defer_lock correctly avoids deadlock between the global and codes mutexes.
Summary of what was reviewed:
hgraph.cppUpdateVector: The three-way split (non-dedup early-return / dedup-not-shared in-place / dedup-shared copy-on-write) is clean and each path is correct.code_slot_map.cppRebindSlot: The CAS-based rebind with expected-slot validation provides strong atomicity guarantees. Correctly does not touchpublished_logical_count_.code_slot_map.h: Interface changes are minimal and well-documented.- Tests: Comprehensive coverage including shared-group update, precise-reorder codes, separate raw vectors, concurrent updates, serialization round-trip, and error-path validation in
CodeSlotMaptests.
Inline comments posted:
[note]onhgraph.cpp:804— comment wording clarification[suggestion]onhgraph.cpp:791— minor loop optimization (skip self inslot_is_sharedcheck)[note]oncode_slot_map.cpp:69—published_logical_count_consistency note
No blocking issues found. The implementation correctly handles the key invariant: every code layer is initialized before the new slot binding is published, so concurrent readers observe either the complete old vector or the complete new vector.
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
097f34f to
9e03ed1
Compare
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: medium (379 changed lines across 8 files).
No actionable inline findings were found.
Reviewed commit 9e03ed1.
Change Type
Linked Issue
What Changed
HGraph::UpdateVectorwhen a logical ID shares storage underdeduplicate_storage.CodeSlotMapbindings rather than stale duplicate-group size.CodeSlotMap::RebindSlotwith expected-slot validation and release/acquire publication.UpdateVectorpath unchanged.Test Evidence
make fmtmake lintmake testmake cov, run tests, and collect coverageTest details:
Compatibility Impact
CodeSlotMap::RebindSlotis internal.deduplicate_storage=truenow supports updating IDs that still share a physical code slot. Only the target ID is redirected to private storage.Performance and Concurrency Impact
UpdateVector. The first update of a shared ID allocates one private physical slot; later updates of that ID are in place. Search, Build, Add, and the non-deduplicated update path are unchanged.Documentation Impact
README.mdDEVELOPMENT.mdCONTRIBUTING.mddocs/docs/en/src/indexes/hgraph.mddocs/docs/zh/src/indexes/hgraph.mdRisk and Rollback
Checklist
kind/bugandkind/feature; see "Linked Issue" above)[skip ci]prefix)