feat(pyramid): support paths in GetDataByIdsWithFlag - #2763
Conversation
|
/label status/waiting-for-review |
Merge Protections🟢 All 2 merge protections satisfied — ready to merge. Show 2 satisfied protections🟢 Require kind label
🟢 Require version label
|
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR adds an opt-in Pyramid build parameter (store_paths) that retains original per-ID paths (default and named hierarchies) and returns them from GetDataByIds, including persistence through regular and streaming serialization.
Changes:
- Introduces
store_pathsparameter plumbing (parsing, JSON, compatibility checks, external param mapping). - Implements
PyramidPathStorewith per-hierarchy path retention and incorporates it intoBuild/Add/CloneandGetDataByIds. - Adds serialization support (BinarySet + streaming) and expands unit/functional tests and docs (EN/ZH).
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_pyramid_paths.cpp | Adds functional tests for default/named hierarchy path return behavior. |
| tests/test_pyramid_path_serialization.cpp | Adds tests for BinarySet + streaming serialization of stored paths. |
| tests/test_pyramid.cpp | Extends cache import/export smoke test to assert paths survive warmed build. |
| src/storage/serialization_tags.h | Adds PYRAMID_PATHS streaming tag and mapping helpers. |
| src/inner_string_params.h | Adds internal key string PYRAMID_STORE_PATHS_KEY. |
| src/constants.cpp | Adds exported constant PYRAMID_STORE_PATHS. |
| src/algorithm/pyramid/pyramid_zparameters_test.cpp | Adds unit tests for store_paths param + compatibility, updates mapping test. |
| src/algorithm/pyramid/pyramid_zparameters.h | Adds store_paths field to PyramidParameters. |
| src/algorithm/pyramid/pyramid_zparameters.cpp | Implements parse/emit/compatibility for store_paths. |
| src/algorithm/pyramid/pyramid_path_store_test.cpp | Adds unit tests for PyramidPathStore behavior + serialization robustness. |
| src/algorithm/pyramid/pyramid_path_store.h | Introduces PyramidPathStore API. |
| src/algorithm/pyramid/pyramid_path_store.cpp | Implements PyramidPathStore and adds Pyramid path (de)serialization + GetDataByIds path attachment. |
| src/algorithm/pyramid/pyramid.h | Wires store_paths_ + per-hierarchy path_store, declares path helpers and overrides GetDataByIds. |
| src/algorithm/pyramid/pyramid.cpp | Records paths on build/add, persists paths in regular + streaming serialization, adds external param mapping. |
| src/algorithm/pyramid/CMakeLists.txt | Builds new pyramid_path_store.cpp compilation unit. |
| include/vsag/constants.h | Exposes PYRAMID_STORE_PATHS constant. |
| docs/docs/zh/src/resources/index_parameters.md | Documents store_paths in zh index-parameter resources. |
| docs/docs/zh/src/indexes/pyramid.md | Documents path retrieval and serialization semantics in zh Pyramid docs. |
| docs/docs/en/src/resources/index_parameters.md | Documents store_paths in en index-parameter resources. |
| docs/docs/en/src/indexes/pyramid.md | Documents path retrieval and serialization semantics in en Pyramid docs. |
Suppressed comments (1)
src/storage/serialization_tags.h:119
- For streaming serialization,
PYRAMID_PATHSis emitted only whenstore_paths_is enabled, and the loader enforces the block’s presence in that configuration—so the block is effectively required when present. Marking it non-critical means older readers that don’t understandPYRAMID_PATHSmay silently skip it and still “successfully” load the index (but lose the promised path semantics), which is risky operationally. Consider making this block critical whenstore_pathsis enabled (e.g., by treatingPYRAMID_PATHSas critical inStreamSerializationTagCritical, or by overriding thecriticalflag passed toAppendStreamingManifestBlock/WriteStreamingBlockfor this tag whenstore_paths_is true).
StreamSerializationTagCritical(uint32_t tag) {
switch (static_cast<StreamSerializationTag>(tag)) {
case StreamSerializationTag::SECTION_END:
return false;
case StreamSerializationTag::LABEL_TABLE:
case StreamSerializationTag::BASE_CODES:
case StreamSerializationTag::BOTTOM_GRAPH:
case StreamSerializationTag::ROUTE_GRAPHS:
case StreamSerializationTag::IVF_BUCKET:
case StreamSerializationTag::IVF_PARTITION_STRATEGY:
case StreamSerializationTag::HIGH_PRECISION_CODES:
case StreamSerializationTag::SINDI_WINDOWS:
case StreamSerializationTag::SINDI_RERANK_INDEX:
case StreamSerializationTag::SINDI_TERM_ID_MAPPER:
case StreamSerializationTag::PYRAMID_HIERARCHIES:
case StreamSerializationTag::CODE_SLOT_MAP:
case StreamSerializationTag::IVF_PRECISE_BUCKET:
return true;
case StreamSerializationTag::ATTRIBUTE_FILTER:
case StreamSerializationTag::EXTRA_INFO:
case StreamSerializationTag::RAW_VECTOR:
case StreamSerializationTag::IVF_BUCKET_GRAPH:
case StreamSerializationTag::PYRAMID_PATHS:
return false;
}
return false;
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
3b25900 to
d04eea9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.
Suppressed comments (1)
src/storage/serialization_tags.h:119
PYRAMID_PATHSis treated as non-critical here, but whenstore_paths_is enabled the streaming loader hard-requires the paths block and fails if it's missing. Marking it non-critical can allow older readers (that don't understand the tag) to silently ignore the block and potentially load an index without its required payload. Consider makingPYRAMID_PATHScritical (or at least setting the manifest'scriticalflag to true whenstore_paths_is enabled) to better reflect the 'required when enabled' contract and improve forward/backward behavior.
StreamSerializationTagCritical(uint32_t tag) {
switch (static_cast<StreamSerializationTag>(tag)) {
case StreamSerializationTag::SECTION_END:
return false;
case StreamSerializationTag::LABEL_TABLE:
case StreamSerializationTag::BASE_CODES:
case StreamSerializationTag::BOTTOM_GRAPH:
case StreamSerializationTag::ROUTE_GRAPHS:
case StreamSerializationTag::IVF_BUCKET:
case StreamSerializationTag::IVF_PARTITION_STRATEGY:
case StreamSerializationTag::HIGH_PRECISION_CODES:
case StreamSerializationTag::SINDI_WINDOWS:
case StreamSerializationTag::SINDI_RERANK_INDEX:
case StreamSerializationTag::SINDI_TERM_ID_MAPPER:
case StreamSerializationTag::PYRAMID_HIERARCHIES:
case StreamSerializationTag::CODE_SLOT_MAP:
case StreamSerializationTag::IVF_PRECISE_BUCKET:
return true;
case StreamSerializationTag::ATTRIBUTE_FILTER:
case StreamSerializationTag::EXTRA_INFO:
case StreamSerializationTag::RAW_VECTOR:
case StreamSerializationTag::IVF_BUCKET_GRAPH:
case StreamSerializationTag::PYRAMID_PATHS:
return false;
}
return false;
}
d04eea9 to
7f5a71c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/algorithm/inner_index_interface.cpp:842
get_data_by_ids_with_flagdivides bybuild_thread_count_without guarding against it being 0.InnerIndexParameter::FromJsonaccepts"build_thread_count": 0, which would makeitem_per_thread = (count + thread_count - 1) / thread_counta division-by-zero UB whenGetDataByIdsWithFlagis called. Clamp the computed thread count to at least 1 before calculatingitem_per_thread.
auto thread_count = static_cast<int64_t>(this->build_thread_count_);
auto item_per_thread = (count + thread_count - 1) / thread_count;
auto dataset = Dataset::Make();
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
0ad9b83 to
6a5d95e
Compare
|
Final minimal-scope update is pushed in 087ff82. Implementation:
Scope cleanup in this update:
Validation:
Release A/B, 100K vectors, dim=4, NSW, store_paths=true, 7 alternating runs:
|
LHT129
left a comment
There was a problem hiding this comment.
Thank you for this well-structured PR. The separation of PyramidPathStore from pyramid_paths.cpp is clean, and the test coverage is thorough. I have a few observations:
[suggestion] pyramid_paths.cpp:91 — Eager path array allocation before validation
In GetDataByIdsWithFlag, for each hierarchy, std::make_unique<std::string[]>(count) is allocated before GetPaths validates the inner IDs. If GetPaths returns false (e.g. a hole or missing ID), the allocation is wasted. For large count and many hierarchies, this adds unnecessary peak memory. Consider calling GetPaths with a temporary vector first, or restructuring to allocate only after validation succeeds.
[suggestion] pyramid_path_store.cpp:141 — Unchecked data_biases[offset] index
In Writer::Insert, data_biases[offset] (int64_t) is used as an index into paths[...] without a non-negative check. While the callers (build_by_odescent, Add, build_with_cache) all populate data_biases from valid offsets, a defensive CHECK_ARGUMENT would guard against future misuse.
[note] pyramid.cpp:985 — Deserialize empty-index behavior
The Footer::Parse + EmptyIndex() check now throws INDEX_EMPTY for empty indexes. This is consistent with the previous read_index_footer behavior (which also threw on empty metadata), so no regression is introduced. The empty-index roundtrip tests in test_pyramid_path_serialization.cpp test Serialize/Deserialize on an index that was never built (0 elements), which succeeds because the index was never serialized with footer metadata — the Clone() path and the Serialize() of an empty index produce different codepaths than Deserialize of a previously-built-and-emptied index. This is worth documenting.
[note] serialization_tags.h:120 — Non-critical PYRAMID_PATHS tag
The PYRAMID_PATHS block is marked non-critical in the tag table, but read_streaming_body enforces its presence when store_paths_ is true. This is intentional: older readers that don't know about paths can skip the block, while readers configured with store_paths=true validate separately. The design is sound, though a brief comment in the tag table explaining this dual-enforcement pattern would help future maintainers.
Signed-off-by: jc543239 <jc543239@antgroup.com> Assisted-by: Codex:gpt-5
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/algorithm/pyramid/pyramid_path_store.cpp:173
Writer::Insert(...)always callsPrepare(...), even when the caller already reserved/expanded slots for a batch (e.g., Pyramid build/add paths). This adds avoidable per-insert checks and capacity computations in the hot path. Consider skippingPreparewhen both the slot arrays andpaths_capacity are already sufficient, while still keeping the same argument validation.
try {
Prepare(slot + 1, path_count);
for (uint64_t offset = 0; offset < path_count; ++offset) {
|
Tick the box to add this pull request to the merge queue (same as
|
Change Type
Linked Issue
What Changed
Test Evidence
Compatibility Impact
Performance and Concurrency Impact
Release A/B with 100K vectors, dim=4, NSW, store_paths=true, 7 alternating runs against the prior single-path implementation:
Each enabled hierarchy has one shared mutex; one Writer holds the exclusive lock for a batch. Default-off allocates no per-vector path state.
Documentation Impact
English and Chinese Pyramid, index-parameter, and API docs are updated.
Risk and Rollback
Checklist