Conversation
📝 WalkthroughWalkthroughIntroduces vector-aware graph links handling and an optional HNSW setting to copy vectors. Adds StorageGraphLinksVectors with a try_new constructor and a cached GraphLinksVectorsLayout, changes GraphLinksVectors::vectors_layout to return the layout directly, and adds GraphLinksFormatParam::recommended for runtime format selection. GraphLinks::load_from_file now accepts an Advice parameter. Adds QuantizedVectors::is_on_disk. Extends HnswConfig with copy_vectors: Option, propagates it through types, gRPC/proto, conversions, OpenAPI/GRPC docs, benches, and tests. Updates GraphLayers and HNSW build paths to use the new format param and prebuilt graph-links vectors. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
lib/segment/benches/fixture.rs (1)
76-76: Typo in progress bar template.“Buildng” → “Building”.
- ProgressStyle::with_template("{percent:>3}% Buildng HNSW {wide_bar}").unwrap(), + ProgressStyle::with_template("{percent:>3}% Building HNSW {wide_bar}").unwrap(),lib/segment/src/index/hnsw_index/graph_layers.rs (1)
519-596: Force-load/convert flow looks correct; consider elevating conversion log level.
- Order-of-preference load for
Noneand conversion forSome(...)are implemented cleanly with appropriateAdviceusage (Randomfor steady-state,Sequentialduring conversion).- Suggest using
infofor the conversion summary so operators notice one-off startup conversions.- log::debug!( + log::info!( "Converted HNSW graph links in {:.1?}: {:.1}MB -> {:.1}MB ({:.1}%)",lib/segment/src/index/hnsw_index/graph_links.rs (2)
100-112: Validate base vector layout in try_new (cheap early check).You already guard on quantized layout; also verify base layout to fail fast when storage can’t provide it.
impl<'a> StorageGraphLinksVectors<'a> { pub fn try_new( vector_storage: &'a VectorStorageEnum, quantized_vectors: Option<&'a QuantizedVectors>, ) -> Option<Self> { let quantized_vectors = quantized_vectors?; - quantized_vectors.get_quantized_vector_layout().ok()?; + // Ensure both link and base layouts are obtainable + quantized_vectors.get_quantized_vector_layout().ok()?; + vector_storage.get_vector_layout().ok()?; Some(Self { vector_storage, quantized_vectors, }) } }
187-212: Use saturating math and name “magic numbers” in format selection.Prevents debug-overflow panics/wrap-around and documents the 3-byte id estimate and 2-page cap.
impl<'a> GraphLinksFormatParam<'a> { /// Decide which format to use. pub fn recommended<Q: GraphLinksVectors>( feature_flags: &FeatureFlags, hnsw_m0: usize, vectors: Option<&'a Q>, ) -> Self { - let is_small_enough = |layout: &GraphLinksVectorsLayout| { - let mut node_size_bytes = 0; - node_size_bytes += layout.base.size(); - node_size_bytes += hnsw_m0 * 3; // Rough estimate: 3 bytes per compressed id - node_size_bytes += hnsw_m0 * layout.link.size(); - node_size_bytes <= 2 * PAGE_SIZE_BYTES - }; + const COMPRESSED_ID_EST_BYTES: usize = 3; + const NODE_SIZE_THRESHOLD_BYTES: usize = 2 * PAGE_SIZE_BYTES; + let is_small_enough = |layout: &GraphLinksVectorsLayout| { + let node_size_bytes = layout + .base + .size() + .saturating_add(hnsw_m0.saturating_mul(COMPRESSED_ID_EST_BYTES)) + .saturating_add(hnsw_m0.saturating_mul(layout.link.size())); + node_size_bytes <= NODE_SIZE_THRESHOLD_BYTES + }; if feature_flags.hnsw_with_vectors && let Some(vectors) = vectors && let Ok(layout) = vectors.vectors_layout() && (is_small_enough(&layout) || feature_flags.hnsw_format_force) { GraphLinksFormatParam::CompressedWithVectors(vectors) } else { GraphLinksFormatParam::Compressed } } }lib/segment/src/index/hnsw_index/hnsw.rs (1)
167-194: Warn when force-enable is requested but vectors/layout are unavailable.Current logic silently drops forcing in this case; emit a warning to surface misconfiguration. Also split the match arm for clarity.
- let force_format = match ( - flags.hnsw_format_force, - flags.hnsw_with_vectors, - &graph_links_vectors, - ) { - // Normal flow - no feature flags enabled - (false, _, _) => None, - - // No quantized vectors (or layout is not available) - can't force the format - (true, _, None) => None, - - // Force format without vectors - (true, false, _) => Some(GraphLinksFormatParam::Compressed), - - // Force format with vectors - (true, true, Some(graph_links_vectors)) => Some( - GraphLinksFormatParam::CompressedWithVectors(graph_links_vectors), - ), - }; + let force_format = match ( + flags.hnsw_format_force, + flags.hnsw_with_vectors, + &graph_links_vectors, + ) { + // Normal flow - no feature flags enabled + (false, _, _) => None, + + // Forcing requested but we cannot enable vector format + (true, true, None) => { + log::warn!( + "hnsw_format_force=true and hnsw_with_vectors=true, \ + but quantized vectors/layout are unavailable; skipping conversion." + ); + None + } + + // Force format without vectors + (true, false, _) => Some(GraphLinksFormatParam::Compressed), + + // Force format with vectors + (true, true, Some(graph_links_vectors)) => Some( + GraphLinksFormatParam::CompressedWithVectors(graph_links_vectors), + ), + };Follow-up: Confirm this aligns with the PR’s “convert on startup” promise when forcing is set. If conversion is mandatory, consider erroring instead of warning.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/src/fixtures/index_fixtures.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(6 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/segment/src/index/hnsw_index/graph_layers_builder.rslib/segment/benches/fixture.rslib/common/common/src/flags.rslib/segment/src/fixtures/index_fixtures.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/segment/src/index/hnsw_index/graph_layers_builder.rslib/common/common/src/flags.rslib/segment/src/fixtures/index_fixtures.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
**/{tests,benches}/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Files:
lib/segment/benches/fixture.rs
🧠 Learnings (9)
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers_builder.rslib/segment/src/fixtures/index_fixtures.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers_builder.rslib/segment/src/fixtures/index_fixtures.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-05-30T15:26:14.488Z
Learnt from: generall
PR: qdrant/qdrant#6479
File: lib/segment/src/index/hnsw_index/graph_layers_builder.rs:137-182
Timestamp: 2025-05-30T15:26:14.488Z
Learning: In the subgraph_connectivity method in graph_layers_builder.rs, the previous_visited_points vector and visited BitVec entry point marking are automatically re-initialized at the start of each retry loop iteration, so manual clearing is not needed.
Applied to files:
lib/segment/benches/fixture.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-09-01T11:19:26.371Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7193
File: lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs:17-30
Timestamp: 2025-09-01T11:19:26.371Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs, the ChunkedMmapVectors underlying implementation does not validate against zero-sized vectors, so adding such validation in QuantizedChunkedMmapStorage::new is unnecessary and would be redundant.
Applied to files:
lib/segment/src/fixtures/index_fixtures.rs
📚 Learning: 2025-08-11T00:37:34.100Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#6986
File: lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs:46-55
Timestamp: 2025-08-11T00:37:34.100Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs`, the `ChunkedVectors` used in `QuantizedRamStorage` is intentionally designed to be non-persistent during updates. The `push_vector` method only updates the in-memory vectors without flushing to disk, and this is expected behavior rather than a bug.
Applied to files:
lib/segment/src/fixtures/index_fixtures.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rs
📚 Learning: 2025-08-15T11:42:00.297Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:86-90
Timestamp: 2025-08-15T11:42:00.297Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs, overflow protection for encoded_storage_size computation (quantized_vector_size * vectors_count) is implemented in PR #7048, using checked_mul with u64 casting to prevent silent overflow.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-29T08:16:28.521Z
Learnt from: CR
PR: qdrant/qdrant#0
File: .github/review-rules.md:0-0
Timestamp: 2025-08-29T08:16:28.521Z
Learning: Applies to **/{tests,benches}/**/*.rs : Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-06-14T20:35:10.603Z
Learnt from: generall
PR: qdrant/qdrant#6635
File: lib/segment/src/index/field_index/full_text_index/text_index.rs:784-832
Timestamp: 2025-06-14T20:35:10.603Z
Learning: Functions gated with `#[cfg(feature = "testing")]` within `#[cfg(test)]` modules are safe from compilation issues since both the function and its call sites are restricted to test builds. The additional feature gate is often used for optional heavy test utilities.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
🧬 Code graph analysis (6)
lib/segment/src/index/hnsw_index/graph_layers_builder.rs (1)
lib/segment/src/index/hnsw_index/graph_links.rs (2)
links(309-311)load_from_file(250-261)
lib/segment/benches/fixture.rs (1)
lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
lib/segment/src/fixtures/index_fixtures.rs (1)
lib/segment/src/index/hnsw_index/graph_links.rs (1)
try_new(101-111)
lib/segment/src/index/hnsw_index/graph_links.rs (6)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/segment/src/fixtures/index_fixtures.rs (1)
quantized_vectors(79-81)lib/common/common/src/flags.rs (1)
feature_flags(140-148)lib/quantization/src/encoded_vectors_binary.rs (1)
layout(789-795)lib/quantization/src/encoded_vectors_pq.rs (1)
layout(494-496)lib/quantization/src/encoded_vectors_u8.rs (1)
layout(334-336)
lib/segment/src/index/hnsw_index/graph_layers.rs (2)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/segment/src/index/hnsw_index/graph_links.rs (4)
new(387-401)format(285-293)load_from_file(250-261)links(309-311)
lib/segment/src/index/hnsw_index/hnsw.rs (3)
lib/common/common/src/flags.rs (1)
feature_flags(140-148)lib/segment/src/index/hnsw_index/graph_links.rs (3)
vector_storage(118-119)try_new(101-111)recommended(189-211)lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: e2e-tests
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: integration-tests-consensus
- GitHub Check: test-consistency
- GitHub Check: test-consensus-compose
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: integration-tests
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: storage-compat-test
- GitHub Check: lint
🔇 Additional comments (16)
lib/common/common/src/flags.rs (3)
63-71: New flags look correct and well-documented.Fields are public, serde-defaulted, and the docs clearly explain behavior. No issues.
87-89: Defaults are sensible and backward-compatible.Deserializing older configs will keep these disabled by default. LGTM.
114-116: Confirm intentional “all” behavior triggers startup conversion.Setting both
hnsw_with_vectorsandhnsw_format_forcewhenallis true will force conversion on startup. If this is only meant for testing, consider noting it in the docstring; otherwise, LGTM.Also applies to: 129-131
lib/segment/benches/fixture.rs (1)
56-56: Signature update LGTM.Passing
Noneforforce_formatmatches the new API and preserves default behavior.lib/segment/src/fixtures/index_fixtures.rs (1)
84-85: Good switch to constructor with validation.
StorageGraphLinksVectors::try_newcentralizes availability/layout checks and keeps the Option API. Looks good.lib/segment/src/index/hnsw_index/graph_layers_builder.rs (2)
11-11: Import of Advice is appropriate.Prepares for explicit madvise control on loads.
201-206: Using Advice::Random for mmap reload is appropriate.Post-serialization access pattern is random; this hint makes sense. No further changes needed.
lib/segment/src/index/hnsw_index/graph_layers.rs (4)
10-10: Explicit Advice import is fine.Keeps load paths consistent with memory hints.
503-517: API change to accept optional force format is clean.Separation of concerns via
load_linksis good, andHnswMis derived before link loading. LGTM.
519-596: Edge case: forced CompressedWithVectors without available vectors.Ensure upstream only constructs
GraphLinksFormatParam::CompressedWithVectorswhen vector storage/quantized layout is available; otherwise, conversion will fail here. If not guaranteed, guard earlier in the caller.
712-772: Test coverage for load/convert matrix is solid.The loop over forced formats plus result equivalence check is great. Minor: always enabling quantization in this test is intentional to permit
CompressedWithVectors. LGTM.lib/segment/src/index/hnsw_index/graph_links.rs (2)
254-258: LGTM: Advice now plumbed into mmap.Passing Advice through to open_read_mmap is correct; populate toggled by on_disk is consistent.
371-371: Tests updated to pass Advice — good coverage touchpoint.The change to supply Advice::Sequential in load_from_file aligns with the new signature.
Also applies to: 513-515
lib/segment/src/index/hnsw_index/hnsw.rs (3)
16-16: Import tweak is fine.
49-49: New imports wired correctly.
638-645: LGTM: Build-time format selection uses recommended(...) with available vectors.Using m0 and feature flags to drive Compressed vs CompressedWithVectors is consistent with the PR intent.
8095c51 to
a4c8c83
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (7)
docs/redoc/master/openapi.json (2)
11757-11761: Tighten the public description of hnsw_with_vectors (avoid internal jargon).The phrase “
CompressedWithVectorsformat” is fine, but consider adding a 1‑liner on what it means for users (e.g., stores vectors with graph to reduce random I/O at some space cost). Also, note in docs that enabling it only affects newly written segments unless forcing is enabled. Add a “Since” note for versioned clarity.Because this file is generated, please update lib/api/src/rest/schema.rs doc comments or #[schemars(description = "...")] accordingly.
Example in Rust (schema source), adjust wording as needed:
/// Allow writing the HNSW graph in the "CompressedWithVectors" format. /// Stores node vectors alongside links to reduce random I/O (higher disk usage). /// Affects only newly written segments unless `hnsw_format_force` is enabled. /// Since: 1.xx.x #[schemars(description = "Allow writing the HNSW graph in the `CompressedWithVectors` format. Stores node vectors alongside links to reduce random I/O (higher disk usage). Affects only newly written segments unless `hnsw_format_force` is enabled.")] pub hnsw_with_vectors: bool,
11762-11766: Avoid Rust-specific link in OpenAPI and clarify conversion semantics for hnsw_format_force.The text “based on the value of [
Self::hnsw_with_vectors]” won’t render well in Redoc. Replace with plain reference “based on hnsw_with_vectors”. Also explicitly state behavior when false (no forced conversion; existing segments keep their current format) and warn about potential startup-time cost due to conversion.Please change in schema.rs so this JSON is regenerated with cleaner wording.
Proposed source doc text:
/// Force conversion of existing HNSW graphs on startup to match `hnsw_with_vectors`. /// - If true: convert to `CompressedWithVectors` when `hnsw_with_vectors` is true, otherwise to `Compressed`. /// - If false: do not force-convert existing segments; only new writes follow the current format selection. /// Conversion may increase startup time; performed atomically. /// Since: 1.xx.x #[schemars(description = "Force conversion of existing HNSW graphs on startup to match `hnsw_with_vectors`. If true, convert to `CompressedWithVectors` when `hnsw_with_vectors` is true, otherwise to `Compressed`. If false, existing segments keep their format; only new writes follow the selected format. Conversion may increase startup time.")] pub hnsw_format_force: bool,lib/segment/src/index/hnsw_index/graph_layers.rs (2)
531-541: Be resilient if a higher-preference links file exists but fails to loadIf the first existing file (e.g., CompressedWithVectors) is present but corrupted, we currently bail out instead of trying the next available format. Consider continuing to the next format on load error.
Apply this diff:
- for format in formats { - let path = GraphLayers::get_links_path(dir, format); - if path.exists() { - return GraphLinks::load_from_file(&path, on_disk, format, Advice::Random); - } - } + for format in formats { + let path = GraphLayers::get_links_path(dir, format); + if path.exists() { + match GraphLinks::load_from_file(&path, on_disk, format, Advice::Random) { + Ok(links) => return Ok(links), + Err(err) => { + log::warn!( + "Failed to load HNSW links at {:?} as {:?}: {err}. Trying next format.", + path, + format + ); + } + } + } + }
594-595: Improve error text with directory contextIncluding the directory path simplifies troubleshooting when no links file is found.
- Err(OperationError::service_error("No links file found")) + Err(OperationError::service_error(format!( + "No HNSW links file found in {:?}", + dir + )))lib/segment/src/index/hnsw_index/graph_links.rs (3)
187-211: Make threshold math explicit and overflow-safeTiny readability/robustness tweak: factor out the “3 bytes per id” estimate and use saturating math.
pub fn recommended<Q: GraphLinksVectors>( feature_flags: &FeatureFlags, hnsw_m0: usize, vectors: Option<&'a Q>, ) -> Self { - let is_small_enough = |layout: &GraphLinksVectorsLayout| { - let mut node_size_bytes = 0; - node_size_bytes += layout.base.size(); - node_size_bytes += hnsw_m0 * 3; // Rough estimate: 3 bytes per compressed id - node_size_bytes += hnsw_m0 * layout.link.size(); - node_size_bytes <= 2 * PAGE_SIZE_BYTES - }; + const COMPRESSED_ID_BYTES_ESTIMATE: usize = 3; // rough estimate per compressed id + let is_small_enough = |layout: &GraphLinksVectorsLayout| { + let mut node_size_bytes = layout.base.size(); + node_size_bytes = node_size_bytes + .saturating_add(hnsw_m0.saturating_mul(COMPRESSED_ID_BYTES_ESTIMATE)); + node_size_bytes = node_size_bytes + .saturating_add(hnsw_m0.saturating_mul(layout.link.size())); + node_size_bytes <= 2 * PAGE_SIZE_BYTES + };
146-159: Add a panic message for clarity in testsMicro DX improvement when tests misconfigure vectors.
- None => panic!(), + None => panic!("CompressedWithVectors requires vectors in tests"),
9-11: Drop unused import
Madviseableisn’t referenced in this module.-use memory::madvise::{Advice, AdviceSetting, Madviseable}; +use memory::madvise::{Advice, AdviceSetting};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
docs/redoc/master/openapi.json(1 hunks)lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(6 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- lib/common/common/src/flags.rs
- lib/segment/benches/fixture.rs
- lib/segment/src/index/hnsw_index/graph_layers_builder.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
🧠 Learnings (8)
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-05-30T15:26:14.488Z
Learnt from: generall
PR: qdrant/qdrant#6479
File: lib/segment/src/index/hnsw_index/graph_layers_builder.rs:137-182
Timestamp: 2025-05-30T15:26:14.488Z
Learning: In the subgraph_connectivity method in graph_layers_builder.rs, the previous_visited_points vector and visited BitVec entry point marking are automatically re-initialized at the start of each retry loop iteration, so manual clearing is not needed.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-15T11:42:00.297Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:86-90
Timestamp: 2025-08-15T11:42:00.297Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs, overflow protection for encoded_storage_size computation (quantized_vector_size * vectors_count) is implemented in PR #7048, using checked_mul with u64 casting to prevent silent overflow.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-29T08:16:28.521Z
Learnt from: CR
PR: qdrant/qdrant#0
File: .github/review-rules.md:0-0
Timestamp: 2025-08-29T08:16:28.521Z
Learning: Applies to **/{tests,benches}/**/*.rs : Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-06-14T20:35:10.603Z
Learnt from: generall
PR: qdrant/qdrant#6635
File: lib/segment/src/index/field_index/full_text_index/text_index.rs:784-832
Timestamp: 2025-06-14T20:35:10.603Z
Learning: Functions gated with `#[cfg(feature = "testing")]` within `#[cfg(test)]` modules are safe from compilation issues since both the function and its call sites are restricted to test builds. The additional feature gate is often used for optional heavy test utilities.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-10T18:26:12.443Z
Learnt from: generall
PR: qdrant/qdrant#7006
File: docs/redoc/master/openapi.json:13645-13652
Timestamp: 2025-08-10T18:26:12.443Z
Learning: In qdrant, docs/redoc/master/openapi.json is auto-generated from the REST schemas. To change field docs, edit lib/api/src/rest/schema.rs (e.g., add doc comments or #[schemars(description = ...)]). Specifically, UpdateVectors.update_filter lacked a description and should state: "If specified, only update vectors for points that match this filter; points not matching the filter are left unchanged."
Applied to files:
docs/redoc/master/openapi.json
🧬 Code graph analysis (3)
lib/segment/src/index/hnsw_index/graph_layers.rs (4)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/common/io/src/file_operations.rs (2)
read_bin(36-40)atomic_save(10-26)lib/segment/src/index/hnsw_index/graph_links.rs (4)
new(387-401)format(285-293)load_from_file(250-261)links(309-311)lib/segment/src/index/hnsw_index/graph_links/serializer.rs (1)
serialize_graph_links(23-237)
lib/segment/src/index/hnsw_index/hnsw.rs (3)
lib/common/common/src/flags.rs (1)
feature_flags(140-148)lib/segment/src/index/hnsw_index/graph_links.rs (3)
vector_storage(118-119)try_new(101-111)recommended(189-211)lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
lib/segment/src/index/hnsw_index/graph_links.rs (6)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/common/memory/src/mmap_ops.rs (1)
open_read_mmap(44-62)lib/common/common/src/flags.rs (1)
feature_flags(140-148)lib/quantization/src/encoded_vectors_binary.rs (1)
layout(789-795)lib/quantization/src/encoded_vectors_pq.rs (1)
layout(494-496)lib/quantization/src/encoded_vectors_u8.rs (1)
layout(334-336)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: storage-compat-test
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: test-consistency
- GitHub Check: e2e-tests
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: lint
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: test-consensus-compose
- GitHub Check: integration-tests
- GitHub Check: integration-tests-consensus
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: rust-tests (windows-latest)
🔇 Additional comments (9)
docs/redoc/master/openapi.json (1)
11757-11766: All feature flags correctly wired:FeatureFlagsdeclares bothhnsw_with_vectorsandhnsw_format_force;init_feature_flags(all=true)sets each totrue; andAppBuildTelemetry.runtime_featuresexposes the flags viacommon::flags::feature_flags.lib/segment/src/index/hnsw_index/graph_layers.rs (3)
503-516: API update to accept an optional forced format looks solid
GraphLayers::load(..., force_format: Option<&GraphLinksFormatParam>)is a clean evolution and keeps lifetimes tight by borrowing the format param only during load. No concerns.
561-591: Conversion path: good use of atomic_save + sequential adviseThe “convert if missing forced target” flow is careful and efficient (sequential read for source, atomic write for target, then random advise on reload). Nice.
712-771: Tests cover forced and non-forced paths wellGreat end-to-end verification of load, convert-on-open, and search invariants across all formats.
lib/segment/src/index/hnsw_index/graph_links.rs (3)
100-112: Guarded creation of StorageGraphLinksVectors is correctThe early exits ensure we only enable vector-inclusive formatting when quantized layout is available. Fits the “try_new -> Option” semantics.
513-516: Test uses sequential advise correctlySequential advise during load/verify mirrors the conversion path. Looks good.
249-261: AllGraphLinks::load_from_filecall sites include the newadviceargument
No instances of the old 3-argument signature were found.lib/segment/src/index/hnsw_index/hnsw.rs (2)
167-195: Force-format semantics when vectors are unavailableWhen
hnsw_format_force=trueandhnsw_with_vectors=truebutgraph_links_vectorsisNone, the code no-ops (None). Confirm this is intentional vs. forcingCompressedand/or logging.Option A (log-only):
- (true, _, None) => None, + (true, _, None) => { + log::debug!("hnsw_format_force set, but quantized vectors/layout unavailable; skip forcing."); + None + }Option B (fallback to Compressed):
- (true, _, None) => None, + (true, true, None) => { + log::debug!("hnsw_format_force set, but vectors unavailable; forcing Compressed format instead."); + Some(GraphLinksFormatParam::Compressed) + }
638-645: Build path chooses format via recommended(...) correctlyGood to gate CompressedWithVectors by feature flags, m0, and vectors availability during build.
a4c8c83 to
85b456e
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
lib/segment/src/index/hnsw_index/graph_layers.rs (1)
519-597: Forced conversion path is sound; consider streaming to cut peak RAM during conversionsCurrent approach loads edges into memory (to_edges) before re-serializing. For very large graphs, a streaming converter (read-and-emit per node/level) would reduce memory footprint and speed up conversions. Optional, given this is primarily for force/maintenance paths.
lib/segment/src/index/hnsw_index/graph_links.rs (3)
89-93: Derive Debug for GraphLinksVectorsLayout for easier loggingUseful when tracing layout decisions and header packing.
-#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct GraphLinksVectorsLayout {
97-116: StorageGraphLinksVectors::try_new: silent None can be hard to diagnoseReturning Option keeps it simple, but a debug log when layouts aren't available (or quantized vectors are absent) would aid troubleshooting without affecting release performance.
- ) -> Option<Self> { - let quantized_vectors = quantized_vectors?; - Some(Self { + ) -> Option<Self> { + let quantized_vectors = quantized_vectors.or_else(|| { + log::debug!("Quantized vectors not available; CompressedWithVectors will be disabled"); + None + })?; + Some(Self { vector_storage, quantized_vectors, vectors_layout: GraphLinksVectorsLayout { base: vector_storage.get_vector_layout().ok()?, link: quantized_vectors.get_quantized_vector_layout().ok()?, }, }) }
190-213: Heuristic overflow guard in recommended()Using usize arithmetic could theoretically wrap on extreme inputs. Switch to u64 with saturating math to be robust while preserving the 8 KiB threshold (2 * PAGE_SIZE_BYTES).
- let is_small_enough = |layout: GraphLinksVectorsLayout| { - let mut node_size_bytes = 0; - node_size_bytes += layout.base.size(); - node_size_bytes += hnsw_m0 * 3; // Rough estimate: 3 bytes per compressed id - node_size_bytes += hnsw_m0 * layout.link.size(); - node_size_bytes <= 2 * PAGE_SIZE_BYTES - }; + let is_small_enough = |layout: GraphLinksVectorsLayout| { + let base = layout.base.size() as u64; + let ids = (hnsw_m0 as u64).saturating_mul(3); // ~3 bytes per id + let links= (hnsw_m0 as u64).saturating_mul(layout.link.size() as u64); + let node_size_bytes = base.saturating_add(ids).saturating_add(links); + node_size_bytes <= (2 * PAGE_SIZE_BYTES) as u64 + };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
docs/redoc/master/openapi.json(1 hunks)lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/src/fixtures/index_fixtures.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(10 hunks)lib/segment/src/index/hnsw_index/graph_links/serializer.rs(1 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- lib/segment/benches/fixture.rs
- lib/segment/src/index/hnsw_index/graph_layers_builder.rs
- lib/segment/src/index/hnsw_index/hnsw.rs
- lib/common/common/src/flags.rs
- docs/redoc/master/openapi.json
- lib/segment/src/fixtures/index_fixtures.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/segment/src/index/hnsw_index/graph_links/serializer.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/segment/src/index/hnsw_index/graph_links/serializer.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rs
🧠 Learnings (6)
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links/serializer.rs
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links/serializer.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/index/hnsw_index/graph_layers.rs
📚 Learning: 2025-08-15T11:42:00.297Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:86-90
Timestamp: 2025-08-15T11:42:00.297Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs, overflow protection for encoded_storage_size computation (quantized_vector_size * vectors_count) is implemented in PR #7048, using checked_mul with u64 casting to prevent silent overflow.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-29T08:16:28.521Z
Learnt from: CR
PR: qdrant/qdrant#0
File: .github/review-rules.md:0-0
Timestamp: 2025-08-29T08:16:28.521Z
Learning: Applies to **/{tests,benches}/**/*.rs : Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-06-14T20:35:10.603Z
Learnt from: generall
PR: qdrant/qdrant#6635
File: lib/segment/src/index/field_index/full_text_index/text_index.rs:784-832
Timestamp: 2025-06-14T20:35:10.603Z
Learning: Functions gated with `#[cfg(feature = "testing")]` within `#[cfg(test)]` modules are safe from compilation issues since both the function and its call sites are restricted to test builds. The additional feature gate is often used for optional heavy test utilities.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
🧬 Code graph analysis (3)
lib/segment/src/index/hnsw_index/graph_links/serializer.rs (1)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
vectors_layout(85-85)vectors_layout(137-139)vectors_layout(415-417)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/common/memory/src/mmap_ops.rs (1)
open_read_mmap(44-62)lib/common/common/src/flags.rs (1)
feature_flags(140-148)
lib/segment/src/index/hnsw_index/graph_layers.rs (2)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/segment/src/index/hnsw_index/graph_links.rs (4)
new(387-403)format(286-294)load_from_file(251-262)links(310-312)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: e2e-tests
- GitHub Check: integration-tests-consensus
- GitHub Check: test-consistency
- GitHub Check: test-consensus-compose
- GitHub Check: integration-tests
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: storage-compat-test
- GitHub Check: lint
🔇 Additional comments (6)
lib/segment/src/index/hnsw_index/graph_links/serializer.rs (1)
36-49: Infallible vectors_layout aligns with trait change; checks retainedSwitching to v.vectors_layout() (no Result) is consistent with the new GraphLinksVectors API, and the subsequent alignment validations still safeguard layout correctness. Looks good.
lib/segment/src/index/hnsw_index/graph_layers.rs (1)
714-773: Tests cover both auto-detect and forced formatsGood end-to-end coverage of load, conversion, format selection, and search equivalence across formats.
lib/segment/src/index/hnsw_index/graph_links.rs (4)
85-86: Make vectors_layout infallibleTrait change to return GraphLinksVectorsLayout directly simplifies callers and matches serializer changes.
137-139: Cached layout accessor is fineReturning the precomputed layout avoids repeated fallible queries; matches the new trait.
251-259: load_from_file now accepts Advice; goodPassing Advice through to open_read_mmap enables Random/Sequential hints from callers. Implementation is correct.
512-514: Test: explicit Advice during loadUsing Advice::Sequential here matches the write/read pattern and validates the new API.
85b456e to
40a056d
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
lib/segment/src/index/hnsw_index/graph_layers.rs (1)
519-597: Load/convert logic is sound; good Advice usage.
- Priority order (CompWithVectors → Compressed → Plain) makes sense.
- Random advice for direct loads, Sequential for conversion is appropriate.
- Atomic write and reload path looks correct.
Optional: consider logging which source format was converted from for easier ops tracing.
lib/segment/src/index/hnsw_index/graph_links.rs (2)
103-116: StorageGraphLinksVectors::try_new — OK, minor ergonomics nit.Returning Option is fine; if you want better error visibility later, consider Result<_, OperationError>. Not blocking.
189-214: Recommended format logic: clear, but avoid “let-chains” MSRV risk.If your MSRV might predate stable let-chains, prefer a nested form. Functionally identical:
- if feature_flags.hnsw_with_vectors - && let Some(vectors) = vectors - && (feature_flags.hnsw_format_force || is_on_disk && is_small_enough(vectors)) - { - GraphLinksFormatParam::CompressedWithVectors(vectors) - } else { - GraphLinksFormatParam::Compressed - } + if feature_flags.hnsw_with_vectors { + if let Some(vectors) = vectors { + if feature_flags.hnsw_format_force || (is_on_disk && is_small_enough(vectors)) { + return GraphLinksFormatParam::CompressedWithVectors(vectors); + } + } + } + GraphLinksFormatParam::CompressedWould you like me to scan the repo for other let-chain usages relative to your MSRV?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
docs/redoc/master/openapi.json(1 hunks)lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(10 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(4 hunks)lib/segment/src/vector_storage/quantized/quantized_vectors.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- lib/segment/benches/fixture.rs
- docs/redoc/master/openapi.json
- lib/segment/src/index/hnsw_index/hnsw.rs
- lib/segment/src/index/hnsw_index/graph_layers_builder.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rslib/segment/src/index/hnsw_index/graph_layers.rslib/common/common/src/flags.rslib/segment/src/index/hnsw_index/graph_links.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rslib/segment/src/index/hnsw_index/graph_layers.rslib/common/common/src/flags.rslib/segment/src/index/hnsw_index/graph_links.rs
🧠 Learnings (10)
📚 Learning: 2025-08-11T00:37:34.100Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#6986
File: lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs:46-55
Timestamp: 2025-08-11T00:37:34.100Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs`, the `ChunkedVectors` used in `QuantizedRamStorage` is intentionally designed to be non-persistent during updates. The `push_vector` method only updates the in-memory vectors without flushing to disk, and this is expected behavior rather than a bug.
Applied to files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rs
📚 Learning: 2025-08-21T13:43:52.191Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7113
File: lib/quantization/src/encoded_vectors_u8.rs:202-211
Timestamp: 2025-08-21T13:43:52.191Z
Learning: The `quantized_vector_size()` method is available on the `EncodedVectors` trait, not on the `EncodedStorage` trait. The `EncodedStorage` trait provides methods like `get_vector_data`, `vectors_count`, `is_on_disk`, `upsert_vector`, `flusher`, `files`, and `immutable_files`.
Applied to files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rs
📚 Learning: 2025-09-01T11:19:26.371Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7193
File: lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs:17-30
Timestamp: 2025-09-01T11:19:26.371Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs, the ChunkedMmapVectors underlying implementation does not validate against zero-sized vectors, so adding such validation in QuantizedChunkedMmapStorage::new is unnecessary and would be redundant.
Applied to files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rs
📚 Learning: 2025-08-11T07:57:01.399Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#6986
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:81-84
Timestamp: 2025-08-11T07:57:01.399Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the BitsStoreType parameter difference between single-vector and multi-vector Binary quantization is intentional: single-vector storage uses `EncodedVectorsBin<u128, ...>` to enable 128-bit SIMD/popcount optimizations for longer vectors, while multi-vector storage uses `EncodedVectorsBin<u8, ...>` because multivectors are typically shorter and benefit from byte-granular storage.
Applied to files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rs
📚 Learning: 2025-08-15T11:42:00.297Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:86-90
Timestamp: 2025-08-15T11:42:00.297Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs, overflow protection for encoded_storage_size computation (quantized_vector_size * vectors_count) is implemented in PR #7048, using checked_mul with u64 casting to prevent silent overflow.
Applied to files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-21T13:45:05.899Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7113
File: lib/quantization/src/encoded_vectors_binary.rs:515-525
Timestamp: 2025-08-21T13:45:05.899Z
Learning: The `EncodedStorage` trait in `lib/quantization/src/encoded_storage.rs` does not have a `quantized_vector_size()` method. To validate vector sizes from storage, use `get_vector_data(index).len()` to get the actual size of stored vectors.
Applied to files:
lib/segment/src/vector_storage/quantized/quantized_vectors.rs
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-29T08:16:28.521Z
Learnt from: CR
PR: qdrant/qdrant#0
File: .github/review-rules.md:0-0
Timestamp: 2025-08-29T08:16:28.521Z
Learning: Applies to **/{tests,benches}/**/*.rs : Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-06-14T20:35:10.603Z
Learnt from: generall
PR: qdrant/qdrant#6635
File: lib/segment/src/index/field_index/full_text_index/text_index.rs:784-832
Timestamp: 2025-06-14T20:35:10.603Z
Learning: Functions gated with `#[cfg(feature = "testing")]` within `#[cfg(test)]` modules are safe from compilation issues since both the function and its call sites are restricted to test builds. The additional feature gate is often used for optional heavy test utilities.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
🧬 Code graph analysis (3)
lib/segment/src/vector_storage/quantized/quantized_vectors.rs (8)
lib/quantization/src/encoded_vectors_binary.rs (1)
is_on_disk(817-819)lib/segment/src/vector_storage/vector_storage_base.rs (2)
is_on_disk(68-68)is_on_disk(849-893)lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs (1)
is_on_disk(54-56)lib/segment/src/vector_storage/quantized/quantized_multivector_storage.rs (1)
is_on_disk(390-392)lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs (1)
is_on_disk(61-63)lib/quantization/src/encoded_vectors_pq.rs (1)
is_on_disk(506-508)lib/quantization/src/encoded_vectors_u8.rs (1)
is_on_disk(371-373)lib/quantization/src/encoded_vectors.rs (1)
is_on_disk(34-34)
lib/segment/src/index/hnsw_index/graph_layers.rs (2)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/segment/src/index/hnsw_index/graph_links.rs (4)
new(389-405)format(288-296)load_from_file(253-264)links(312-314)
lib/segment/src/index/hnsw_index/graph_links.rs (2)
lib/common/memory/src/mmap_ops.rs (1)
open_read_mmap(44-62)lib/common/common/src/flags.rs (1)
feature_flags(140-148)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: storage-compat-test
- GitHub Check: e2e-tests
- GitHub Check: integration-tests
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: test-consensus-compose
- GitHub Check: test-consistency
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: lint
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: integration-tests-consensus
🔇 Additional comments (14)
lib/common/common/src/flags.rs (4)
63-71: New HNSW flags look good.Docs are clear and field visibility matches the public API.
87-89: Default values OK.Both flags default to false as intended.
114-116: Destructuring includes new flags.Plumbed correctly for init.
129-131: Confirm “all” implies forced conversion.Setting hnsw_format_force=true when all=true may trigger mass on-startup conversions; confirm this is desired for prod clusters.
Would you like an additional test asserting that all=true sets both hnsw_with_vectors and hnsw_format_force?
lib/segment/src/vector_storage/quantized/quantized_vectors.rs (1)
223-226: Expose on-disk status at wrapper level — LGTM.Simple delegate, consistent with enum-level impl and used by format selection.
lib/segment/src/index/hnsw_index/graph_layers.rs (3)
10-10: Advice import — OK.Required by new load path.
503-517: load() signature change is clean.hnsw_m creation and forwarding force_format are correct.
714-774: Tests updated to pass force_format — nice coverage.Covers None and all forced formats; verifies functional equivalence.
lib/segment/src/index/hnsw_index/graph_links.rs (6)
89-93: GraphLinksVectorsLayout type — LGTM.Copy/Clone makes sense for cached layouts.
137-139: Cached layout accessor — LGTM.Avoids recomputation in hot paths.
374-419: Test vectors layout changes — OK.Local TestGraphLinksVectors updated to the new API; assertions remain precise.
514-516: Sequential advice in test load — LGTM.Matches conversion/readback semantics you’re validating.
84-86: AllGraphLinksVectors::vectors_layoutimplementations updated to infallible signature. No outdated signatures detected.
252-263: No action needed: all GraphLinks::load_from_file calls now supply an Advice argument.
timvisee
left a comment
There was a problem hiding this comment.
Can we add a test that builds/loads both GraphLinksFormat::CompressedWithVectors and GraphLinksFormat::Compressed?
lib/common/common/src/flags.rs
Outdated
| /// | ||
| /// When set, the graph will be converted on startup to the corresponding | ||
| /// format (`Compressed` or `CompressedWithVectors`). | ||
| pub hnsw_format_force: bool, |
There was a problem hiding this comment.
This also gets enabled with the all flag. Do you think that's desired?
Maybe we can set all = true && hnsw_format_force = false in the default development configuration to prevent that.
There was a problem hiding this comment.
Do you think that's desired?
No, but I did it for consistency with other options.
What if I add it to (Settings → StorageConfig → HnswGlobalConfig; e.g. storage.hnsw_global_config.links_with_vectors)? What's our policy for configuration settings versioning / public API? This setting is not intended to be exposed to end users.
A lot of existing tests are parametrized by the format type (run |
40a056d to
396ec3c
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/collection/src/shards/local_shard/mod.rs (1)
418-424: Thread HnswGlobalConfig through SegmentHolder and pass shared_storage_config.hnsw_global_config when creating appendable segments.
- lib/shard/src/segment_holder/mod.rs:1088 calls build_segment without an hnsw config in scope — appendable segments will fall back to HnswGlobalConfig::default().
- Change SegmentHolder::create_appendable_segment / build_tmp_segment to accept &HnswGlobalConfig and propagate it to build_segment; update the create_appendable_segment call site in lib/collection/src/shards/local_shard/mod.rs (≈ lines 418–424) to pass shared_storage_config.hnsw_global_config.
- Audit other non-test build_segment/load_segment call sites (notably lib/collection/src/collection_manager/optimizers/segment_optimizer.rs:95 and src/segment_inspector.rs:45) to ensure they receive the configured HNSW global config.
♻️ Duplicate comments (1)
lib/common/common/src/flags.rs (1)
121-122: Re: enabling viaallsets hnsw_with_vectors = true — confirm intent.
This may unexpectedly change on‑disk format in dev/test configs that set all=true. If that’s not desired, exclude it from the “all” bundle or gate it behind a dedicated dev flag.
🧹 Nitpick comments (30)
lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1)
86-101: Avoid config drift during a single optimize run.
If HnswGlobalConfig can change at runtime (reload), temp_segment() and optimized_segment_builder() could see different configs. Consider snapshotting once per optimize() and threading it through to both calls; otherwise please confirm the config is immutable while optimization runs.Also applies to: 326-332
lib/common/common/src/flags.rs (1)
63-66: Clarify/public surface of hnsw_with_vectors.
If this is not meant for end users, hide it from OpenAPI or docs (e.g., add a schemars skip) and state that it only applies when format selection is Auto.Apply this diff if you want to hide it from schema:
/// Allow writing the HNSW graph in the `CompressedWithVectors` format. /// Also see `GraphLinksFormatConfig`. - pub hnsw_with_vectors: bool, + #[schemars(skip)] + pub hnsw_with_vectors: bool,lib/segment/tests/integration/gpu_hnsw_test.rs (1)
139-140: Bind HnswGlobalConfig locally instead of referencing a temporary.Avoid taking a reference to a temporary in a struct literal; bind it once for clarity and lifetime safety, mirroring other tests.
Apply within this hunk:
- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config,And add just above the HNSWIndex::build call:
let hnsw_global_config = HnswGlobalConfig::default();lib/segment/benches/hnsw_incremental_build.rs (1)
379-380: Avoid &HnswGlobalConfig::default() in struct field; bind once.This prevents any lifetime ambiguity and improves consistency with other sites.
Change in this hunk:
- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config,Add before constructing
open_args:let hnsw_global_config = HnswGlobalConfig::default();lib/segment/tests/integration/filtrable_hnsw_test.rs (1)
159-160: Bind default HnswGlobalConfig before use.Keeps lifetimes explicit and aligns with the snapshot test style.
Within this hunk:
- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config,Add just above the HNSWIndex::build call:
let hnsw_global_config = HnswGlobalConfig::default();lib/segment/tests/integration/hnsw_discover_test.rs (1)
112-113: Use a named HnswGlobalConfig instead of a temporary in both tests.Minor style/lifetime polish; keeps both tests consistent with others.
In both places:
- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config,And add before each corresponding HNSWIndex::build call:
let hnsw_global_config = HnswGlobalConfig::default();Also applies to: 238-239
lib/segment/src/segment/tests.rs (1)
250-254: Optional: also exercise forced graph-links formats in this snapshot restore.To cover both Compressed and CompressedWithVectors in restore paths, consider parameterizing over GraphLinksFormatConfig::{ForceWithoutVectors, ForceWithVectors} in addition to the default Auto.
Example (sketch):
+ use crate::types::GraphLinksFormatConfig; @@ - let hnsw_global_config = HnswGlobalConfig::default(); - let restored_segment = - load_segment(&entry.path(), &hnsw_global_config, &AtomicBool::new(false)) - .unwrap() - .unwrap(); + for format in [ + GraphLinksFormatConfig::Auto, + GraphLinksFormatConfig::ForceWithoutVectors, + GraphLinksFormatConfig::ForceWithVectors, + ] { + let mut hnsw_global_config = HnswGlobalConfig::default(); + hnsw_global_config.graph_links_format = format; + let restored_segment = + load_segment(&entry.path(), &hnsw_global_config, &AtomicBool::new(false)) + .unwrap() + .unwrap(); + // existing validations... + }lib/segment/tests/integration/exact_search_test.rs (1)
139-139: BindHnswGlobalConfig::default()to a local; avoid referencing a temporary.- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config,Add near the build call:
let hnsw_global_config = HnswGlobalConfig::default();lib/segment/src/index/struct_payload_index.rs (1)
1241-1247: BindHnswGlobalConfig::default()to a local; avoid borrowing a temporary.- load_segment( - &full_segment_path, - &HnswGlobalConfig::default(), - &AtomicBool::new(false), - ) + let hnsw_global_config = HnswGlobalConfig::default(); + load_segment(&full_segment_path, &hnsw_global_config, &AtomicBool::new(false))lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs (1)
77-77: Bind HnswGlobalConfig::default() to a local (don’t borrow a temporary)Create a local HnswGlobalConfig and pass &hnsw_global_config; update all occurrences of &HnswGlobalConfig::default().
- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config,Add before constructing the args:
let hnsw_global_config = HnswGlobalConfig::default();lib/segment/tests/integration/fixtures/segment.rs (2)
175-176: Passing HnswGlobalConfig::default() into build_segment — OK.This aligns the fixture with the new API. Consider allowing callers to inject a non-default config to exercise different graph-link formats in targeted tests.
If useful, I can add parallel helpers (keeping existing fns for BC):
+pub fn build_segment_3_with_hnsw(path: &Path, hnsw: &HnswGlobalConfig) -> Segment { + let mut segment3 = build_segment( + path, + /* existing SegmentConfig */, + hnsw, + true, + ).unwrap(); + /* rest unchanged */ + segment3 +}
271-272: Same change for sparse fixture 1 — OK.Mirror of the above; enabling injection would make it easy to parametrize tests by format.
src/segment_inspector.rs (1)
45-47: Load with default HNSW config — OK; consider exposing a CLI toggle.Defaulting to Auto is fine, but a flag to force WithVectors/WithoutVectors would help inspect/convert segments explicitly.
Example (sketch):
#[derive(Parser, Debug)] struct Args { ... + /// Graph links format: auto|with-vectors|without-vectors + #[clap(long, value_parser = ["auto","with-vectors","without-vectors"])] + graph_links_format: Option<String>, } fn main() { ... - let segment = load_segment(path, &HnswGlobalConfig::default(), &AtomicBool::new(false))? + let cfg = match args.graph_links_format.as_deref() { + Some("with-vectors") => HnswGlobalConfig { graph_links_format: GraphLinksFormatConfig::ForceWithVectors, ..Default::default() }, + Some("without-vectors") => HnswGlobalConfig { graph_links_format: GraphLinksFormatConfig::ForceWithoutVectors, ..Default::default() }, + _ => HnswGlobalConfig::default(), + }; + let segment = load_segment(path, &cfg, &AtomicBool::new(false))?lib/segment/tests/integration/sparse_vector_index_search_tests.rs (1)
773-775: Default HNSW config for large-index test — OK.If you later want to validate behavior under forced formats, consider parameterizing this test over GraphLinksFormatConfig.
lib/segment/src/segment_constructor/simple_segment_constructor.rs (1)
118-119: Default HNSW config in build_multivec_segment — OK; optional helper to accept custom config.To enable targeted tests/benchmarks with forced formats, consider adding “_with_hnsw” variants instead of changing existing signatures.
Proposed additive API:
+pub fn build_simple_segment_with_hnsw( + path: &Path, + dim: usize, + distance: Distance, + hnsw: &HnswGlobalConfig, +) -> OperationResult<Segment> { + build_segment(path, /* existing SegmentConfig */, hnsw, true) +} + +pub fn build_multivec_segment_with_hnsw( + path: &Path, + dim1: usize, + dim2: usize, + distance: Distance, + hnsw: &HnswGlobalConfig, +) -> OperationResult<Segment> { + build_segment(path, /* existing SegmentConfig */, hnsw, true) +}lib/segment/tests/integration/segment_tests.rs (1)
206-208: Nit: reuse a single HnswGlobalConfig instance.Create one variable and pass references to reduce duplication.
Also applies to: 244-246
lib/segment/tests/integration/byte_storage_hnsw_test.rs (1)
100-106: LGTM: test updated to pass HnswGlobalConfig to build and open paths.Minor nit: define let hnsw_global_config = HnswGlobalConfig::default(); once and pass &hnsw_global_config.
Also applies to: 203-204
lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (1)
84-86: LGTM: build/open paths supply HnswGlobalConfig.Optional: reuse a single local HnswGlobalConfig instead of calling default() multiple times.
Also applies to: 143-144
lib/segment/benches/multi_vector_search.rs (2)
93-99: Wire-through of HnswGlobalConfig looks correct.Consider hoisting
HnswGlobalConfig::default()into a locallet hgc = HnswGlobalConfig::default();and reuse it below forHnswIndexOpenArgsto avoid duplication and make future tweaks explicit.
130-131: Pass the same config instance to build and open.Use the same
hgcvariable here to ensure benches stay reproducible if defaults change in future.- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hgc,lib/segment/tests/integration/byte_storage_quantization_test.rs (2)
238-244: Test wiring to new build_segment signature is fine.Minor: create a single
let hgc = HnswGlobalConfig::default();and reuse it to reduce duplication and make the chosen graph-links behavior explicit in the test.- let mut segment_byte = build_segment( + let hgc = HnswGlobalConfig::default(); + let mut segment_byte = build_segment( dir_byte.path(), &config_byte, - &HnswGlobalConfig::default(), + &hgc, true, )
356-357: Consistency: reuse the same HNSW global config instance.Reuse
&hgchere as well. Optional, but keeps tests stable if defaults evolve.- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hgc,lib/segment/src/types.rs (1)
625-626: Defaulting behavior is sensible and backwards-compatible.
#[serde(default)]onHnswGlobalConfigplusDefaultimpl ensures older configs deserialize fine. Consider adding a brief field doc to clarify that non‑Auto may convert-on-load.pub struct HnswGlobalConfig { /// Enable HNSW healing if the ratio of missing points is no more than this value. /// To disable healing completely, set this value to `0.0`. #[validate(range(min = 0.0, max = 1.0))] pub healing_threshold: f64, - - pub graph_links_format: GraphLinksFormatConfig, + /// Controls graph-links format selection; `force_*` may convert and persist on load. + pub graph_links_format: GraphLinksFormatConfig, }Also applies to: 642-647
lib/segment/tests/integration/multivector_quantization_test.rs (3)
231-233: Good update to the new build_segment API.Same nit as elsewhere: hoist and reuse a single
HnswGlobalConfig::default()for this test.- let mut segment = - build_segment(dir.path(), &config, &HnswGlobalConfig::default(), true).unwrap(); + let hgc = HnswGlobalConfig::default(); + let mut segment = build_segment(dir.path(), &config, &hgc, true).unwrap();
334-335: Reuse the same global config for clarity.Pass
&hgchere to keep the test’s config single-sourced.- hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hgc,
231-233: Optional: parameterize this test to exercise both graph-link formatsAdd an rstest #[case] over GraphLinksFormatConfig::{ForceWithoutVectors, ForceWithVectors} (guarded by the existing feature flags / vector-size preconditions) so the integration test runs with both formats and verifies identical behavior; existing HNSW tests use the same pattern under lib/segment/src/index/hnsw_index/. Location: lib/segment/tests/integration/multivector_quantization_test.rs (around the build_segment(...) call at lines 231–233).
docs/redoc/master/openapi.json (2)
11775-11779: Set default and clarify precedence forgraph_links_format.Add a default of
"Auto"so client SDKs have a stable fallback, and describe interaction with feature flags to avoid ambiguity.Apply this diff:
- "graph_links_format": { - "$ref": "#/components/schemas/GraphLinksFormatConfig" - } + "graph_links_format": { + "allOf": [{ "$ref": "#/components/schemas/GraphLinksFormatConfig" }], + "default": "Auto", + "description": "Format policy for HNSW graph links. Defaults to Auto. When set to Force* it may trigger conversion on load; see also FeatureFlags.hnsw_with_vectors." + }
11781-11805: Simplify enum shape and document “when possible”.
- Current
oneOfwith three single‑value enums is verbose for generators. Prefer a single string enum.- Spell out what “when possible” means (e.g., enabled by
hnsw_with_vectorsand node ≤ 8 KiB), or link to docs.Apply this diff:
- "GraphLinksFormatConfig": { - "oneOf": [ - { - "description": "Decide the format based on the HNSW parameters and vector size in bytes. Do not convert during segment loading.", - "type": "string", - "enum": [ - "Auto" - ] - }, - { - "description": "Always use `Compressed`. Convert during segment loading.", - "type": "string", - "enum": [ - "ForceWithoutVectors" - ] - }, - { - "description": "Use `CompressedWithVectors` when possible. Convert during segment loading.", - "type": "string", - "enum": [ - "ForceWithVectors" - ] - } - ] - }, + "GraphLinksFormatConfig": { + "type": "string", + "enum": ["Auto", "ForceWithoutVectors", "ForceWithVectors"], + "description": "Auto: decide by HNSW params and vector size; no on-load conversion. ForceWithoutVectors: always use Compressed; convert on load. ForceWithVectors: prefer CompressedWithVectors when eligible (feature enabled; small node size), convert on load." + },lib/segment/src/segment_constructor/segment_builder.rs (1)
481-481: Unused tuple fields may need extraction.The tuple
(temp_dir, destination_path)is created at line 481 but thentemp_diris referenced from line 485 onwards, suggesting the original fields are still in use. This indicates that line 481 may be redundant or the fields should be extracted from the tuple consistently.lib/segment/src/index/hnsw_index/graph_links.rs (1)
90-94: Consider marking the struct as#[derive(Debug)].The
GraphLinksVectorsLayoutstruct could benefit from derivingDebugfor easier debugging and logging./// Layout of base and link vectors, returned by [`GraphLinksVectors::vectors_layout`]. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct GraphLinksVectorsLayout { pub base: Layout, pub link: Layout, }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (42)
docs/redoc/master/openapi.json(2 hunks)lib/collection/src/collection_manager/optimizers/segment_optimizer.rs(1 hunks)lib/collection/src/shards/local_shard/mod.rs(2 hunks)lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/benches/hnsw_incremental_build.rs(1 hunks)lib/segment/benches/multi_vector_search.rs(2 hunks)lib/segment/src/fixtures/index_fixtures.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(10 hunks)lib/segment/src/index/hnsw_index/graph_links/serializer.rs(1 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(7 hunks)lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs(1 hunks)lib/segment/src/index/struct_payload_index.rs(2 hunks)lib/segment/src/segment/tests.rs(2 hunks)lib/segment/src/segment_constructor/segment_builder.rs(3 hunks)lib/segment/src/segment_constructor/segment_constructor_base.rs(11 hunks)lib/segment/src/segment_constructor/simple_segment_constructor.rs(4 hunks)lib/segment/src/types.rs(1 hunks)lib/segment/src/vector_storage/quantized/quantized_vectors.rs(1 hunks)lib/segment/tests/integration/batch_search_test.rs(1 hunks)lib/segment/tests/integration/byte_storage_hnsw_test.rs(2 hunks)lib/segment/tests/integration/byte_storage_quantization_test.rs(2 hunks)lib/segment/tests/integration/exact_search_test.rs(1 hunks)lib/segment/tests/integration/filtrable_hnsw_test.rs(1 hunks)lib/segment/tests/integration/fixtures/segment.rs(4 hunks)lib/segment/tests/integration/gpu_hnsw_test.rs(1 hunks)lib/segment/tests/integration/hnsw_discover_test.rs(2 hunks)lib/segment/tests/integration/hnsw_incremental_build.rs(1 hunks)lib/segment/tests/integration/hnsw_quantized_search_test.rs(1 hunks)lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs(2 hunks)lib/segment/tests/integration/multivector_hnsw_test.rs(2 hunks)lib/segment/tests/integration/multivector_quantization_test.rs(2 hunks)lib/segment/tests/integration/payload_index_test.rs(1 hunks)lib/segment/tests/integration/segment_on_disk_snapshot.rs(1 hunks)lib/segment/tests/integration/segment_tests.rs(3 hunks)lib/segment/tests/integration/sparse_discover_test.rs(3 hunks)lib/segment/tests/integration/sparse_vector_index_search_tests.rs(4 hunks)lib/shard/src/proxy_segment/tests.rs(1 hunks)lib/shard/src/segment_holder/mod.rs(2 hunks)src/segment_inspector.rs(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- lib/segment/src/index/hnsw_index/graph_links/serializer.rs
- lib/segment/src/vector_storage/quantized/quantized_vectors.rs
- lib/segment/src/index/hnsw_index/graph_layers_builder.rs
- lib/segment/src/fixtures/index_fixtures.rs
- lib/segment/benches/fixture.rs
- lib/segment/src/index/hnsw_index/graph_layers.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/collection/src/collection_manager/optimizers/segment_optimizer.rslib/segment/tests/integration/exact_search_test.rslib/segment/src/types.rslib/segment/tests/integration/hnsw_quantized_search_test.rslib/segment/src/segment/tests.rslib/segment/tests/integration/payload_index_test.rslib/shard/src/proxy_segment/tests.rslib/common/common/src/flags.rslib/segment/tests/integration/segment_on_disk_snapshot.rslib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/sparse_discover_test.rslib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rslib/segment/tests/integration/filtrable_hnsw_test.rslib/segment/tests/integration/batch_search_test.rslib/segment/tests/integration/byte_storage_hnsw_test.rslib/segment/src/index/struct_payload_index.rslib/segment/benches/hnsw_incremental_build.rslib/segment/tests/integration/segment_tests.rslib/segment/tests/integration/sparse_vector_index_search_tests.rslib/segment/tests/integration/hnsw_discover_test.rslib/segment/tests/integration/multivector_hnsw_test.rslib/segment/tests/integration/fixtures/segment.rslib/segment/benches/multi_vector_search.rslib/segment/src/segment_constructor/segment_builder.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/shard/src/segment_holder/mod.rslib/segment/src/segment_constructor/simple_segment_constructor.rslib/collection/src/shards/local_shard/mod.rslib/segment/tests/integration/byte_storage_quantization_test.rslib/segment/tests/integration/gpu_hnsw_test.rslib/segment/tests/integration/multivector_quantization_test.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/segment_constructor/segment_constructor_base.rssrc/segment_inspector.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/collection/src/collection_manager/optimizers/segment_optimizer.rslib/segment/src/types.rslib/segment/src/segment/tests.rslib/shard/src/proxy_segment/tests.rslib/common/common/src/flags.rslib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rslib/segment/src/index/struct_payload_index.rslib/segment/src/segment_constructor/segment_builder.rslib/shard/src/segment_holder/mod.rslib/segment/src/segment_constructor/simple_segment_constructor.rslib/collection/src/shards/local_shard/mod.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rslib/segment/src/segment_constructor/segment_constructor_base.rssrc/segment_inspector.rs
**/{tests,benches}/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Files:
lib/segment/tests/integration/exact_search_test.rslib/segment/tests/integration/hnsw_quantized_search_test.rslib/segment/tests/integration/payload_index_test.rslib/segment/tests/integration/segment_on_disk_snapshot.rslib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/sparse_discover_test.rslib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rslib/segment/tests/integration/filtrable_hnsw_test.rslib/segment/tests/integration/batch_search_test.rslib/segment/tests/integration/byte_storage_hnsw_test.rslib/segment/benches/hnsw_incremental_build.rslib/segment/tests/integration/segment_tests.rslib/segment/tests/integration/sparse_vector_index_search_tests.rslib/segment/tests/integration/hnsw_discover_test.rslib/segment/tests/integration/multivector_hnsw_test.rslib/segment/tests/integration/fixtures/segment.rslib/segment/benches/multi_vector_search.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/tests/integration/byte_storage_quantization_test.rslib/segment/tests/integration/gpu_hnsw_test.rslib/segment/tests/integration/multivector_quantization_test.rs
🧠 Learnings (10)
📚 Learning: 2025-09-01T11:19:26.371Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7193
File: lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs:17-30
Timestamp: 2025-09-01T11:19:26.371Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs, the ChunkedMmapVectors underlying implementation does not validate against zero-sized vectors, so adding such validation in QuantizedChunkedMmapStorage::new is unnecessary and would be redundant.
Applied to files:
lib/segment/tests/integration/byte_storage_quantization_test.rslib/segment/tests/integration/multivector_quantization_test.rs
📚 Learning: 2025-08-11T07:57:01.399Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#6986
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:81-84
Timestamp: 2025-08-11T07:57:01.399Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the BitsStoreType parameter difference between single-vector and multi-vector Binary quantization is intentional: single-vector storage uses `EncodedVectorsBin<u128, ...>` to enable 128-bit SIMD/popcount optimizations for longer vectors, while multi-vector storage uses `EncodedVectorsBin<u8, ...>` because multivectors are typically shorter and benefit from byte-granular storage.
Applied to files:
lib/segment/tests/integration/byte_storage_quantization_test.rslib/segment/tests/integration/multivector_quantization_test.rs
📚 Learning: 2025-08-15T11:42:00.297Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:86-90
Timestamp: 2025-08-15T11:42:00.297Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs, overflow protection for encoded_storage_size computation (quantized_vector_size * vectors_count) is implemented in PR #7048, using checked_mul with u64 casting to prevent silent overflow.
Applied to files:
lib/segment/tests/integration/byte_storage_quantization_test.rslib/segment/tests/integration/multivector_quantization_test.rs
📚 Learning: 2025-08-11T00:37:34.100Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#6986
File: lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs:46-55
Timestamp: 2025-08-11T00:37:34.100Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_ram_storage.rs`, the `ChunkedVectors` used in `QuantizedRamStorage` is intentionally designed to be non-persistent during updates. The `push_vector` method only updates the in-memory vectors without flushing to disk, and this is expected behavior rather than a bug.
Applied to files:
lib/segment/tests/integration/byte_storage_quantization_test.rs
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-05-30T15:26:14.488Z
Learnt from: generall
PR: qdrant/qdrant#6479
File: lib/segment/src/index/hnsw_index/graph_layers_builder.rs:137-182
Timestamp: 2025-05-30T15:26:14.488Z
Learning: In the subgraph_connectivity method in graph_layers_builder.rs, the previous_visited_points vector and visited BitVec entry point marking are automatically re-initialized at the start of each retry loop iteration, so manual clearing is not needed.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-29T08:16:28.521Z
Learnt from: CR
PR: qdrant/qdrant#0
File: .github/review-rules.md:0-0
Timestamp: 2025-08-29T08:16:28.521Z
Learning: Applies to **/{tests,benches}/**/*.rs : Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-06-14T20:35:10.603Z
Learnt from: generall
PR: qdrant/qdrant#6635
File: lib/segment/src/index/field_index/full_text_index/text_index.rs:784-832
Timestamp: 2025-06-14T20:35:10.603Z
Learning: Functions gated with `#[cfg(feature = "testing")]` within `#[cfg(test)]` modules are safe from compilation issues since both the function and its call sites are restricted to test builds. The additional feature gate is often used for optional heavy test utilities.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
🧬 Code graph analysis (27)
lib/segment/src/types.rs (1)
lib/common/common/src/flags.rs (1)
default(69-83)
lib/segment/src/segment/tests.rs (2)
lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)
lib/segment/tests/integration/payload_index_test.rs (2)
lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1)
hnsw_global_config(69-69)lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/shard/src/proxy_segment/tests.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/tests/integration/segment_on_disk_snapshot.rs (2)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)lib/segment/src/segment_constructor/segment_builder.rs (1)
new(82-167)
lib/segment/tests/integration/hnsw_incremental_build.rs (2)
lib/common/common/src/flags.rs (1)
default(69-83)lib/collection/src/operations/shared_storage_config.rs (1)
default(41-58)
lib/segment/tests/integration/sparse_discover_test.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs (2)
lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)lib/collection/src/operations/shared_storage_config.rs (1)
default(41-58)
lib/segment/tests/integration/byte_storage_hnsw_test.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/src/index/struct_payload_index.rs (2)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)lib/segment/src/segment_constructor/segment_builder.rs (1)
new(82-167)
lib/segment/benches/hnsw_incremental_build.rs (1)
lib/collection/src/operations/shared_storage_config.rs (1)
default(41-58)
lib/segment/tests/integration/segment_tests.rs (4)
lib/segment/src/types.rs (13)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)new(416-429)new(1609-1612)new(2922-2924)new(3184-3186)new(3197-3199)lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)lib/segment/src/segment_constructor/segment_builder.rs (1)
new(82-167)lib/segment/tests/integration/payload_index_test.rs (1)
new(78-263)
lib/segment/tests/integration/sparse_vector_index_search_tests.rs (2)
lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)lib/segment/src/segment_constructor/segment_constructor_base.rs (2)
build_segment(793-821)load_segment(693-775)
lib/segment/tests/integration/fixtures/segment.rs (1)
lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)
lib/segment/benches/multi_vector_search.rs (2)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)
lib/segment/src/segment_constructor/segment_builder.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)
lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (2)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)
lib/shard/src/segment_holder/mod.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/src/segment_constructor/simple_segment_constructor.rs (1)
lib/segment/src/types.rs (8)
default(643-648)default(1234-1243)default(1247-1249)default(1270-1279)default(1392-1401)default(1742-1744)default(3074-3076)default(3134-3136)
lib/collection/src/shards/local_shard/mod.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (2)
load_segment(693-775)build_segment(793-821)
lib/segment/tests/integration/byte_storage_quantization_test.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/tests/integration/gpu_hnsw_test.rs (2)
lib/common/common/src/flags.rs (1)
default(69-83)lib/collection/src/operations/shared_storage_config.rs (1)
default(41-58)
lib/segment/tests/integration/multivector_quantization_test.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/src/index/hnsw_index/hnsw.rs (2)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
vector_storage(124-125)try_new(104-117)recommended(192-221)lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/common/memory/src/mmap_ops.rs (1)
open_read_mmap(44-62)lib/common/common/src/flags.rs (1)
feature_flags(131-139)
lib/segment/src/segment_constructor/segment_constructor_base.rs (5)
lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1)
hnsw_global_config(69-69)lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (1)
hnsw_global_config(252-254)lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs (1)
hnsw_global_config(260-262)lib/collection/src/collection_manager/optimizers/merge_optimizer.rs (1)
hnsw_global_config(84-86)lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs (1)
hnsw_global_config(189-191)
src/segment_inspector.rs (3)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)lib/segment/src/segment_constructor/segment_builder.rs (1)
new(82-167)lib/segment/tests/integration/payload_index_test.rs (1)
new(78-263)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: test-consistency
- GitHub Check: integration-tests
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: test-consensus-compose
- GitHub Check: e2e-tests
- GitHub Check: integration-tests-consensus
- GitHub Check: lint
- GitHub Check: storage-compat-test
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
🔇 Additional comments (57)
lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (2)
95-101: Plumbing HNSW global config into build_segment looks correct.
Passing self.hnsw_global_config() matches the updated API and keeps temp segment creation aligned with collection settings.
326-332: SegmentBuilder receives HNSW global config — OK.
Good propagation through the optimized builder path.lib/segment/tests/integration/hnsw_incremental_build.rs (1)
139-151: Moved hnsw_global_config into HnswIndexOpenArgs — looks good.
Keeps the test consistent with the new constructor.lib/segment/tests/integration/hnsw_quantized_search_test.rs (1)
124-137: Open‑args now carry hnsw_global_config — OK.
Change is minimal and correct.lib/segment/tests/integration/batch_search_test.rs (1)
152-162: HNSWIndex::build call updated to pass hnsw_global_config via OpenArgs — OK.
Matches the refactor; no other behavior changes.lib/common/common/src/flags.rs (2)
81-82: Default remains false — good.
Safe default that won’t surprise users.
107-108: Included in init_feature_flags destructuring — OK.
Keeps initialization consistent.lib/segment/tests/integration/payload_index_test.rs (2)
87-101: build_segment signature update handled correctly.
Passing &HnswGlobalConfig keeps the fixture aligned with runtime config.
294-300: SegmentBuilder::new updated with HnswGlobalConfig — OK.
Inline default is fine for tests.lib/segment/tests/integration/segment_on_disk_snapshot.rs (1)
203-207: LGTM: updated load path with HnswGlobalConfigThe new parameter wiring looks correct and keeps defaults explicit.
lib/segment/src/segment/tests.rs (1)
163-164: LGTM — correct import for new API.lib/segment/tests/integration/sparse_discover_test.rs (2)
155-160: LGTM — correct propagation ofHnswGlobalConfigintobuild_segment.
282-285: LGTM — consistent with the newbuild_segmentsignature.lib/segment/src/index/struct_payload_index.rs (1)
1178-1179: LGTM — test imports updated for new API.lib/segment/tests/integration/fixtures/segment.rs (2)
14-16: Imports updated for HnswGlobalConfig — looks correct.Brings the new global config into scope without widening visibility.
364-365: Same change for sparse fixture 2 — OK.Consistent with other fixtures.
src/segment_inspector.rs (1)
8-8: Importing HnswGlobalConfig — OK.Matches the new load API signature.
lib/shard/src/proxy_segment/tests.rs (1)
585-590: Updated build_segment call with HnswGlobalConfig — LGTM.Both original and write segments now use default global config, keeping behavior consistent.
lib/segment/tests/integration/sparse_vector_index_search_tests.rs (3)
29-32: Imports updated to include HnswGlobalConfig — OK.Scoped and specific.
599-601: Reusing the same HnswGlobalConfig for build and subsequent load — good.Ensures consistent graph-format decisions across persistence boundaries in this test.
637-639: load_segment now receives the same config — OK.Matches API and preserves determinism.
lib/segment/src/segment_constructor/simple_segment_constructor.rs (3)
9-11: Importing HnswGlobalConfig — OK.Required for forwarding to build_segment.
45-46: Default HNSW config in build_simple_segment — OK.Straightforward API adaptation.
74-75: Default HNSW config in build_simple_segment_with_payload_storage — OK.Consistent with other helpers.
lib/shard/src/segment_holder/mod.rs (1)
28-30: Importing HnswGlobalConfig here is correct.This aligns the holder with the new build/load signatures.
lib/segment/tests/integration/segment_tests.rs (1)
16-16: Good: tests import and use HnswGlobalConfig with the new APIs.lib/segment/tests/integration/multivector_hnsw_test.rs (1)
127-128: LGTM: HnswIndexOpenArgs now carries hnsw_global_config; tests updated accordingly.Also applies to: 149-150
lib/collection/src/shards/local_shard/mod.rs (2)
330-334: LGTM: load path now forwards the configured HNSW global config.
542-545: LGTM: build path now forwards the configured HNSW global config.docs/redoc/master/openapi.json (1)
11757-11762: Missinghnsw_format_forceflag in schema — add it to FeatureFlagsRepository search for
hnsw_format_forcereturned 0 matches across 1,243 files; docs/redoc/master/openapi.json (around lines 11757–11762) only exposeshnsw_with_vectors. If the PR intends two flags, addhnsw_format_force(boolean, default false) to components.schemas.FeatureFlags.properties. Apply this diff:"appendable_quantization": { "description": "Use appendable quantization in appendable plain segments.", "default": false, "type": "boolean" }, + "hnsw_format_force": { + "description": "If true, force the graph links format according to `hnsw_with_vectors` (convert segments on startup). If false, do not force conversion.", + "default": false, + "type": "boolean" + }, "hnsw_with_vectors": { "description": "Allow writing the HNSW graph in the `CompressedWithVectors` format. Also see `GraphLinksFormatConfig`.", "default": false, "type": "boolean" }lib/segment/src/segment_constructor/segment_builder.rs (3)
468-479: LGTM!The destructuring of
SegmentBuilderfields is done correctly before deriving the temporary directory and destination path.
594-594: LGTM!The
hnsw_global_configfield is correctly propagated intoVectorIndexOpenArgsfor use in vector index opening.
684-690: LGTM!The updated
load_segmentcall correctly passes thehnsw_global_configparameter, maintaining consistency with the new signature.lib/segment/src/segment_constructor/segment_constructor_base.rs (10)
277-277: LGTM!The
hnsw_global_configfield is correctly added toVectorIndexOpenArgsand used consistently throughout the codebase.
301-301: LGTM!The
hnsw_global_configfield is correctly extracted in the destructuring pattern for use in subsequent operations.
317-317: LGTM!The
hnsw_global_configis correctly passed through toHnswIndexOpenArgsfor HNSW index configuration.
333-333: LGTM!The
hnsw_global_configfield is correctly extracted and passed to the HNSW index build process.
350-350: LGTM!The
hnsw_global_configis correctly propagated through the HNSW index build path.
445-445: LGTM!The
hnsw_global_configparameter is correctly added to thecreate_segmentfunction signature.
556-556: LGTM!The
hnsw_global_configis correctly passed to the vector index open arguments during segment creation.
693-697: LGTM!The
load_segmentfunction signature is correctly updated to include thehnsw_global_configparameter.
757-757: LGTM!The
hnsw_global_configis correctly passed through to thecreate_segmentfunction during segment loading.
796-796: LGTM!The
build_segmentfunction correctly accepts and propagates thehnsw_global_configparameter tocreate_segment.Also applies to: 808-808
lib/segment/src/index/hnsw_index/hnsw.rs (7)
49-49: LGTM!The import of
GraphLinksFormatParamandStorageGraphLinksVectorsis appropriate for the new runtime graph format selection logic.
64-65: LGTM!The imports for
GraphLinksFormatConfigandHnswGlobalConfigare correctly added to support the new configuration-driven approach.
126-127: LGTM!The
hnsw_global_configfield is correctly added toHnswIndexOpenArgsand properly extracted during destructuring.Also applies to: 138-139
236-237: LGTM!The
hnsw_global_configis correctly extracted from build args and passed to the old index evaluation.Also applies to: 283-283
630-643: LGTM!The graph format selection logic using
GraphLinksFormatParam::recommendedis well-structured. It correctly considers:
- Feature flags (
hnsw_with_vectors)- Global configuration (
hnsw_global_config.graph_links_format)- Graph parameters (
config.m0)- Storage characteristics (on-disk status of vector storage, quantized vectors, and HNSW index)
1489-1489: LGTM!The
hnsw_global_configis correctly passed to the old index evaluation for consistency checks.
170-186: ForceWithVectors fallback is correct — no change required.Qdrant returns stored vectors when present and omits/nulls missing vectors instead of erroring; the code’s fallback to Compressed when vectors aren’t available matches expected behavior.
lib/segment/src/index/hnsw_index/graph_links.rs (7)
6-6: LGTM!The imports for
FeatureFlags,GraphLinksFormatConfig, andPAGE_SIZE_BYTESare correctly added to support the new runtime format selection logic.Also applies to: 14-15
86-86: LGTM!The signature change of
vectors_layout()from returningOperationResult<GraphLinksVectorsLayout>to returningGraphLinksVectorsLayoutdirectly is a good simplification. The cached layout inStorageGraphLinksVectorsavoids redundant computations.Also applies to: 138-140
97-101: LGTM!Making the fields of
StorageGraphLinksVectorsprivate with a cachedvectors_layoutis a good design choice for encapsulation and performance.
103-117: LGTM!The
try_newconstructor properly validates inputs and pre-computes the layout. The use ofOptionreturn type appropriately handles the case where required vectors are unavailable.
191-221: Well-designed format selection logic.The
recommendedmethod implements a sophisticated decision tree for selecting the graph format:
- Uses a reasonable heuristic (node size ≤ 2 × page size) to determine when vectors should be included
- Correctly respects explicit configuration when
ForceWithoutVectorsorForceWithVectorsis set- Gracefully handles missing vectors by falling back to compressed format
The 3-byte estimate per compressed ID aligns with typical varint encoding sizes.
264-264: LGTM!The addition of the
adviceparameter toload_from_fileprovides better control over memory access patterns, which is important for performance tuning.Also applies to: 267-267
381-381: LGTM!Test updates correctly pass
Adviceparameter and update vector layout representations to useGraphLinksVectorsLayout.Also applies to: 407-410, 424-426, 521-522
396ec3c to
213ea42
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
lib/segment/src/index/hnsw_index/hnsw.rs (1)
1-200: Re-verify all call sites of GraphLayers::load use the new 3-arg signatureJust to be safe post-rebase.
#!/bin/bash # List call sites and flag any 2-arg usages (old API). set -euo pipefail echo "All call sites (context):" rg -nC2 -P '\bGraphLayers::load\s*\(' || true echo echo "Potential old-call arity (2 args):" rg -nP '\bGraphLayers::load\s*\(\s*[^,]+,\s*[^,]+\s*\)' || trueAlso applies to: 630-646
🧹 Nitpick comments (6)
docs/redoc/master/openapi.json (2)
11776-11778: Set an explicit default forgraph_links_formatMany clients benefit from explicit defaults. Recommend defaulting to
"auto"at the property site."graph_links_format": { + "default": "auto", "$ref": "#/components/schemas/GraphLinksFormatConfig" }
11781-11805: Clarify fallback behavior inforce_with_vectorsdescriptionMake it explicit that we fall back to
Compressedif vectors-inclusive format isn’t applicable (e.g., node too large). Keeps behavior unambiguous.- "description": "Use `CompressedWithVectors` when possible. Convert during segment loading.", + "description": "Use `CompressedWithVectors` when possible, falling back to `Compressed` if the format is not supported for the segment (e.g., node too large). Convert during segment loading.",lib/segment/benches/fixture.rs (1)
76-76: Typo in progress template“Buildng” → “Building”.
- ProgressStyle::with_template("{percent:>3}% Buildng HNSW {wide_bar}").unwrap(), + ProgressStyle::with_template("{percent:>3}% Building HNSW {wide_bar}").unwrap(),lib/segment/src/index/hnsw_index/graph_layers.rs (1)
519-597: Forced format conversion flow is correct; minor polish possible
- Good: preferred-order probe,
Advice::RandomvsSequential, atomic save, and size logging.- Optional: enrich the final error with the probed filenames to aid debugging.
- Err(OperationError::service_error(format!( - "No HNSW graph links file found in {dir:?}" - ))) + Err(OperationError::service_error(format!( + "No HNSW graph links file found in {dir:?}. Searched: {:?}", + [ + GraphLayers::get_links_path(dir, GraphLinksFormat::CompressedWithVectors), + GraphLayers::get_links_path(dir, GraphLinksFormat::Compressed), + GraphLayers::get_links_path(dir, GraphLinksFormat::Plain), + ] + )))lib/segment/src/index/hnsw_index/hnsw.rs (2)
633-643: Overly strict gate for “on-disk” in format recommendation
recommended(..., is_on_disk)is already conservative. Requiring all storages (base, quantized) to be on disk may unnecessarily preventCompressedWithVectorseven when the index itself is on-disk and vectors are available. Consider passing just the index “on-disk” intent.- let format_param = GraphLinksFormatParam::recommended( + let format_param = GraphLinksFormatParam::recommended( &feature_flags, hnsw_global_config.graph_links_format, config.m0, graph_links_vectors.as_ref(), - vector_storage_ref.is_on_disk() - && quantized_vectors_ref - .as_ref() - .is_some_and(|q| q.is_on_disk()) - && hnsw_config.on_disk.unwrap_or(false), + hnsw_config.on_disk.unwrap_or(false), );
170-186: Optional: log when ForceWithVectors cannot be honoredA debug/info log helps operators understand why the forced mode fell back.
- GraphLinksFormatConfig::ForceWithVectors => { - graph_links_vectors - .as_ref() - .map(|v| GraphLinksFormatParam::CompressedWithVectors(v)) - .or(Some(GraphLinksFormatParam::Compressed)) - } + GraphLinksFormatConfig::ForceWithVectors => { + match graph_links_vectors.as_ref() { + Some(v) => Some(GraphLinksFormatParam::CompressedWithVectors(v)), + None => { + log::info!( + "ForceWithVectors requested, but link vectors are unavailable; \ + falling back to Compressed." + ); + Some(GraphLinksFormatParam::Compressed) + } + } + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
docs/redoc/master/openapi.json(2 hunks)lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(10 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(7 hunks)lib/segment/src/types.rs(1 hunks)lib/segment/src/vector_storage/quantized/quantized_vectors.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- lib/segment/src/vector_storage/quantized/quantized_vectors.rs
- lib/segment/src/index/hnsw_index/graph_layers_builder.rs
- lib/common/common/src/flags.rs
- lib/segment/src/types.rs
- lib/segment/src/index/hnsw_index/graph_links.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/benches/fixture.rslib/segment/src/index/hnsw_index/hnsw.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
**/{tests,benches}/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Files:
lib/segment/benches/fixture.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: xzfc
PR: qdrant/qdrant#7232
File: lib/shard/src/segment_holder/mod.rs:1088-1094
Timestamp: 2025-09-16T21:39:09.293Z
Learning: The hnsw_format_force and hnsw_with_vectors flags in HnswGlobalConfig primarily affect existing segment conversion during load/startup, not the initial format selection for newly created empty segments. For new segments, format decisions are made by GraphLinksFormatParam::recommended() based on runtime conditions. Therefore, using HnswGlobalConfig::default() for temporary/proxy segments in SegmentHolder::build_tmp_segment is acceptable.
📚 Learning: 2025-09-16T21:39:09.293Z
Learnt from: xzfc
PR: qdrant/qdrant#7232
File: lib/shard/src/segment_holder/mod.rs:1088-1094
Timestamp: 2025-09-16T21:39:09.293Z
Learning: The hnsw_format_force and hnsw_with_vectors flags in HnswGlobalConfig primarily affect existing segment conversion during load/startup, not the initial format selection for newly created empty segments. For new segments, format decisions are made by GraphLinksFormatParam::recommended() based on runtime conditions. Therefore, using HnswGlobalConfig::default() for temporary/proxy segments in SegmentHolder::build_tmp_segment is acceptable.
Applied to files:
docs/redoc/master/openapi.jsonlib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rs
📚 Learning: 2025-05-30T15:26:14.488Z
Learnt from: generall
PR: qdrant/qdrant#6479
File: lib/segment/src/index/hnsw_index/graph_layers_builder.rs:137-182
Timestamp: 2025-05-30T15:26:14.488Z
Learning: In the subgraph_connectivity method in graph_layers_builder.rs, the previous_visited_points vector and visited BitVec entry point marking are automatically re-initialized at the start of each retry loop iteration, so manual clearing is not needed.
Applied to files:
lib/segment/benches/fixture.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rs
🧬 Code graph analysis (3)
lib/segment/src/index/hnsw_index/graph_layers.rs (2)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/segment/src/index/hnsw_index/graph_links.rs (4)
new(396-412)format(295-303)load_from_file(260-271)links(319-321)
lib/segment/benches/fixture.rs (1)
lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
lib/segment/src/index/hnsw_index/hnsw.rs (3)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
vector_storage(124-125)try_new(104-117)recommended(192-221)lib/segment/src/fixtures/index_fixtures.rs (2)
quantized_vectors(79-81)graph_links_vectors(83-85)lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: storage-compat-test
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: integration-tests-consensus
- GitHub Check: e2e-tests
- GitHub Check: test-consensus-compose
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: integration-tests
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: test-consistency
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: lint
🔇 Additional comments (5)
docs/redoc/master/openapi.json (2)
11758-11762: Addhnsw_with_vectorsflag — LGTM; confirm scope in docsLooks good. Please confirm in the docs that load/startup conversion is governed by
HnswGlobalConfig.graph_links_format, not by this flag, to avoid user confusion.
11705-11764: PR text vs OpenAPI: confirm fate ofhnsw_format_forceI ran a repo-wide search for
hnsw_format_forceand found no matches; docs/redoc/master/openapi.json (lines 11705–11764) does not include the flag. If the flag was intentionally moved (e.g. to HnswGlobalConfig.graph_links_format), update the PR description and user-facing docs; otherwise add thehnsw_format_forceFeatureFlags entry back.lib/segment/benches/fixture.rs (1)
56-56: API call site updated correctlyPassing
Nonefor the newforce_formatparameter matches the updatedGraphLayers::loadsignature. LGTM.lib/segment/src/index/hnsw_index/graph_layers.rs (2)
503-517: Load API refactor looks soundConstructing
HnswMonce and delegating toload_linkswith the optionalforce_formatis clean and cohesive. No issues.
714-774: Great test coverage for load/convert matrixThe parametrized round-trip validates normal load and all forced conversions, including vectors-inclusive. Nicely done.
213ea42 to
039a135
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/segment/benches/hnsw_incremental_build.rs (1)
368-380: Don't take a reference to a temporary; bindHnswGlobalConfig::default()to a local first.
&HnswGlobalConfig::default()creates a reference to a temporary dropped at the end of thelet open_args = ...;statement, leading to “temporary value dropped while borrowed.” Bind it to a local and reference that.Apply this diff:
@@ - let open_args = HnswIndexOpenArgs { + let hnsw_global_config = HnswGlobalConfig::default(); + let open_args = HnswIndexOpenArgs { path, id_tracker: segment.id_tracker.clone(), vector_storage: segment.vector_data[DEFAULT_VECTOR_NAME] .vector_storage .clone(), @@ payload_index: Arc::clone(&segment.payload_index), hnsw_config, - hnsw_global_config: &HnswGlobalConfig::default(), + hnsw_global_config: &hnsw_global_config, };
♻️ Duplicate comments (1)
lib/shard/src/segment_holder/mod.rs (1)
1088-1094: Use default HNSW global config for temp/proxy segments: acknowledged.Per prior discussion, defaults are fine for brand‑new temp segments; format is decided by
GraphLinksFormatParam::recommended(). Consider adding a brief comment to prevent future re‑flags.Apply to add context:
let mut segment = build_segment( segments_path, &config, - &HnswGlobalConfig::default(), + // Safe: temp/proxy segments start empty; format for new segments is chosen by + // GraphLinksFormatParam::recommended(), not by hnsw_format_force/hnsw_with_vectors. + // See PR #7232 discussion. + &HnswGlobalConfig::default(), save_version, )?;
🧹 Nitpick comments (3)
lib/segment/tests/integration/multivector_quantization_test.rs (1)
311-318: Optional: exercise on-disk graph-with-vectors path in this testIf you want this test suite to also cover writing CompressedWithVectors when feasible, consider tying
HnswConfig.on_diskto theon_disktest parameter; the currentSome(false)disables that branch inrecommended().let hnsw_config = HnswConfig { m, ef_construct, full_scan_threshold, max_indexing_threads: 2, - on_disk: Some(false), + on_disk: Some(on_disk), payload_m: None, };lib/segment/src/index/hnsw_index/graph_layers.rs (1)
519-597: Forced-format conversion flow is correct; consider cleanup policy for originalsThe conversion path (Sequential read → serialize target → Random read) is solid. You explicitly leave the original file in place, which can double disk usage after multiple conversions. Consider an opt-in cleanup (config flag or env) or logging the presence of multiple link files during startup.
lib/segment/src/index/hnsw_index/graph_links.rs (1)
190-221: Threshold basis: 8 KiB vs 2*PAGE_SIZE_BYTES — clarify intentDocs mention “no more than 8 KiB per node,” but code uses
<= 2 * PAGE_SIZE_BYTES. On platforms with non‑4KiB pages this diverges. Either fix docs or pin the threshold to 8192 bytes for consistency.- let is_small_enough = node_size_bytes <= 2 * PAGE_SIZE_BYTES; + // Keep threshold stable across platforms (8 KiB), or document page-size coupling. + let is_small_enough = node_size_bytes <= 8 * 1024;Alternatively, add a brief comment explaining the page-size-based heuristic.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (42)
docs/redoc/master/openapi.json(2 hunks)lib/collection/src/collection_manager/optimizers/segment_optimizer.rs(1 hunks)lib/collection/src/shards/local_shard/mod.rs(2 hunks)lib/common/common/src/flags.rs(4 hunks)lib/segment/benches/fixture.rs(1 hunks)lib/segment/benches/hnsw_incremental_build.rs(1 hunks)lib/segment/benches/multi_vector_search.rs(2 hunks)lib/segment/src/fixtures/index_fixtures.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(4 hunks)lib/segment/src/index/hnsw_index/graph_layers_builder.rs(2 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(10 hunks)lib/segment/src/index/hnsw_index/graph_links/serializer.rs(1 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(7 hunks)lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs(1 hunks)lib/segment/src/index/struct_payload_index.rs(2 hunks)lib/segment/src/segment/tests.rs(2 hunks)lib/segment/src/segment_constructor/segment_builder.rs(3 hunks)lib/segment/src/segment_constructor/segment_constructor_base.rs(11 hunks)lib/segment/src/segment_constructor/simple_segment_constructor.rs(4 hunks)lib/segment/src/types.rs(1 hunks)lib/segment/src/vector_storage/quantized/quantized_vectors.rs(1 hunks)lib/segment/tests/integration/batch_search_test.rs(1 hunks)lib/segment/tests/integration/byte_storage_hnsw_test.rs(2 hunks)lib/segment/tests/integration/byte_storage_quantization_test.rs(2 hunks)lib/segment/tests/integration/exact_search_test.rs(1 hunks)lib/segment/tests/integration/filtrable_hnsw_test.rs(1 hunks)lib/segment/tests/integration/fixtures/segment.rs(4 hunks)lib/segment/tests/integration/gpu_hnsw_test.rs(1 hunks)lib/segment/tests/integration/hnsw_discover_test.rs(2 hunks)lib/segment/tests/integration/hnsw_incremental_build.rs(1 hunks)lib/segment/tests/integration/hnsw_quantized_search_test.rs(1 hunks)lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs(2 hunks)lib/segment/tests/integration/multivector_hnsw_test.rs(2 hunks)lib/segment/tests/integration/multivector_quantization_test.rs(2 hunks)lib/segment/tests/integration/payload_index_test.rs(1 hunks)lib/segment/tests/integration/segment_on_disk_snapshot.rs(1 hunks)lib/segment/tests/integration/segment_tests.rs(3 hunks)lib/segment/tests/integration/sparse_discover_test.rs(3 hunks)lib/segment/tests/integration/sparse_vector_index_search_tests.rs(4 hunks)lib/shard/src/proxy_segment/tests.rs(1 hunks)lib/shard/src/segment_holder/mod.rs(2 hunks)src/segment_inspector.rs(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (28)
- lib/common/common/src/flags.rs
- lib/segment/tests/integration/segment_on_disk_snapshot.rs
- lib/segment/src/fixtures/index_fixtures.rs
- lib/segment/tests/integration/exact_search_test.rs
- lib/segment/src/index/struct_payload_index.rs
- lib/segment/src/index/hnsw_index/graph_links/serializer.rs
- lib/segment/tests/integration/byte_storage_hnsw_test.rs
- src/segment_inspector.rs
- lib/segment/src/vector_storage/quantized/quantized_vectors.rs
- lib/segment/tests/integration/sparse_vector_index_search_tests.rs
- lib/segment/tests/integration/hnsw_quantized_search_test.rs
- lib/segment/src/types.rs
- lib/segment/src/index/hnsw_index/graph_layers_builder.rs
- lib/segment/tests/integration/batch_search_test.rs
- lib/segment/tests/integration/hnsw_discover_test.rs
- lib/segment/tests/integration/gpu_hnsw_test.rs
- lib/segment/tests/integration/byte_storage_quantization_test.rs
- lib/segment/benches/multi_vector_search.rs
- lib/collection/src/shards/local_shard/mod.rs
- lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs
- lib/segment/benches/fixture.rs
- lib/collection/src/collection_manager/optimizers/segment_optimizer.rs
- lib/segment/src/segment/tests.rs
- docs/redoc/master/openapi.json
- lib/segment/src/segment_constructor/simple_segment_constructor.rs
- lib/segment/tests/integration/sparse_discover_test.rs
- lib/shard/src/proxy_segment/tests.rs
- lib/segment/tests/integration/multivector_hnsw_test.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/segment/src/segment_constructor/segment_constructor_base.rslib/segment/tests/integration/filtrable_hnsw_test.rslib/segment/src/index/hnsw_index/graph_layers.rslib/segment/tests/integration/multivector_quantization_test.rslib/segment/tests/integration/payload_index_test.rslib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/fixtures/segment.rslib/segment/tests/integration/segment_tests.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/src/segment_constructor/segment_builder.rslib/shard/src/segment_holder/mod.rslib/segment/benches/hnsw_incremental_build.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/segment/src/segment_constructor/segment_constructor_base.rslib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/segment_constructor/segment_builder.rslib/shard/src/segment_holder/mod.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
**/{tests,benches}/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Files:
lib/segment/tests/integration/filtrable_hnsw_test.rslib/segment/tests/integration/multivector_quantization_test.rslib/segment/tests/integration/payload_index_test.rslib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/fixtures/segment.rslib/segment/tests/integration/segment_tests.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/benches/hnsw_incremental_build.rs
🧠 Learnings (11)
📓 Common learnings
Learnt from: xzfc
PR: qdrant/qdrant#7232
File: lib/shard/src/segment_holder/mod.rs:1088-1094
Timestamp: 2025-09-16T21:39:09.293Z
Learning: The hnsw_format_force and hnsw_with_vectors flags in HnswGlobalConfig primarily affect existing segment conversion during load/startup, not the initial format selection for newly created empty segments. For new segments, format decisions are made by GraphLinksFormatParam::recommended() based on runtime conditions. Therefore, using HnswGlobalConfig::default() for temporary/proxy segments in SegmentHolder::build_tmp_segment is acceptable.
📚 Learning: 2025-09-16T21:39:09.293Z
Learnt from: xzfc
PR: qdrant/qdrant#7232
File: lib/shard/src/segment_holder/mod.rs:1088-1094
Timestamp: 2025-09-16T21:39:09.293Z
Learning: The hnsw_format_force and hnsw_with_vectors flags in HnswGlobalConfig primarily affect existing segment conversion during load/startup, not the initial format selection for newly created empty segments. For new segments, format decisions are made by GraphLinksFormatParam::recommended() based on runtime conditions. Therefore, using HnswGlobalConfig::default() for temporary/proxy segments in SegmentHolder::build_tmp_segment is acceptable.
Applied to files:
lib/segment/src/segment_constructor/segment_constructor_base.rslib/segment/tests/integration/filtrable_hnsw_test.rslib/segment/src/index/hnsw_index/graph_layers.rslib/segment/tests/integration/multivector_quantization_test.rslib/segment/tests/integration/payload_index_test.rslib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/fixtures/segment.rslib/segment/tests/integration/segment_tests.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/src/segment_constructor/segment_builder.rslib/shard/src/segment_holder/mod.rslib/segment/benches/hnsw_incremental_build.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-08-15T11:41:10.629Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:857-857
Timestamp: 2025-08-15T11:41:10.629Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the multivector offset storage has two different patterns: for RAM storage, offsets are collected into Vec<MultivectorOffset> and used directly; for MMAP storage, offsets are consumed to create a file via create_offsets_file_from_iter, then the file is loaded back as MultivectorOffsetsStorageMmap. The direct consumption of offsets iterator in the MMAP case is intentional.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-09-01T11:19:26.371Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7193
File: lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs:17-30
Timestamp: 2025-09-01T11:19:26.371Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_chunked_mmap_storage.rs, the ChunkedMmapVectors underlying implementation does not validate against zero-sized vectors, so adding such validation in QuantizedChunkedMmapStorage::new is unnecessary and would be redundant.
Applied to files:
lib/segment/tests/integration/multivector_quantization_test.rs
📚 Learning: 2025-08-11T07:57:01.399Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#6986
File: lib/segment/src/vector_storage/quantized/quantized_vectors.rs:81-84
Timestamp: 2025-08-11T07:57:01.399Z
Learning: In `lib/segment/src/vector_storage/quantized/quantized_vectors.rs`, the BitsStoreType parameter difference between single-vector and multi-vector Binary quantization is intentional: single-vector storage uses `EncodedVectorsBin<u128, ...>` to enable 128-bit SIMD/popcount optimizations for longer vectors, while multi-vector storage uses `EncodedVectorsBin<u8, ...>` because multivectors are typically shorter and benefit from byte-granular storage.
Applied to files:
lib/segment/tests/integration/multivector_quantization_test.rs
📚 Learning: 2025-08-15T11:42:00.297Z
Learnt from: IvanPleshkov
PR: qdrant/qdrant#7043
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:86-90
Timestamp: 2025-08-15T11:42:00.297Z
Learning: In lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs, overflow protection for encoded_storage_size computation (quantized_vector_size * vectors_count) is implemented in PR #7048, using checked_mul with u64 casting to prevent silent overflow.
Applied to files:
lib/segment/tests/integration/multivector_quantization_test.rs
📚 Learning: 2025-05-30T15:26:14.488Z
Learnt from: generall
PR: qdrant/qdrant#6479
File: lib/segment/src/index/hnsw_index/graph_layers_builder.rs:137-182
Timestamp: 2025-05-30T15:26:14.488Z
Learning: In the subgraph_connectivity method in graph_layers_builder.rs, the previous_visited_points vector and visited BitVec entry point marking are automatically re-initialized at the start of each retry loop iteration, so manual clearing is not needed.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-29T08:16:28.521Z
Learnt from: CR
PR: qdrant/qdrant#0
File: .github/review-rules.md:0-0
Timestamp: 2025-08-29T08:16:28.521Z
Learning: Applies to **/{tests,benches}/**/*.rs : Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
📚 Learning: 2025-06-14T20:35:10.603Z
Learnt from: generall
PR: qdrant/qdrant#6635
File: lib/segment/src/index/field_index/full_text_index/text_index.rs:784-832
Timestamp: 2025-06-14T20:35:10.603Z
Learning: Functions gated with `#[cfg(feature = "testing")]` within `#[cfg(test)]` modules are safe from compilation issues since both the function and its call sites are restricted to test builds. The additional feature gate is often used for optional heavy test utilities.
Applied to files:
lib/segment/src/index/hnsw_index/graph_links.rs
🧬 Code graph analysis (14)
lib/segment/src/segment_constructor/segment_constructor_base.rs (5)
lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1)
hnsw_global_config(69-69)lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (1)
hnsw_global_config(252-254)lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs (1)
hnsw_global_config(260-262)lib/collection/src/collection_manager/optimizers/merge_optimizer.rs (1)
hnsw_global_config(84-86)lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs (1)
hnsw_global_config(189-191)
lib/segment/tests/integration/filtrable_hnsw_test.rs (1)
lib/segment/src/types.rs (8)
default(644-649)default(1235-1244)default(1248-1250)default(1271-1280)default(1393-1402)default(1743-1745)default(3075-3077)default(3135-3137)
lib/segment/src/index/hnsw_index/graph_layers.rs (3)
lib/common/io/src/file_operations.rs (2)
read_bin(36-40)atomic_save(10-26)lib/segment/src/index/hnsw_index/graph_links.rs (4)
new(396-412)format(295-303)load_from_file(260-271)links(319-321)lib/segment/src/index/hnsw_index/graph_links/serializer.rs (1)
serialize_graph_links(23-237)
lib/segment/tests/integration/multivector_quantization_test.rs (2)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)lib/segment/src/types.rs (8)
default(644-649)default(1235-1244)default(1248-1250)default(1271-1280)default(1393-1402)default(1743-1745)default(3075-3077)default(3135-3137)
lib/segment/tests/integration/payload_index_test.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/tests/integration/hnsw_incremental_build.rs (2)
lib/common/common/src/flags.rs (1)
default(69-83)lib/collection/src/operations/shared_storage_config.rs (1)
default(41-58)
lib/segment/tests/integration/fixtures/segment.rs (1)
lib/segment/src/types.rs (8)
default(644-649)default(1235-1244)default(1248-1250)default(1271-1280)default(1393-1402)default(1743-1745)default(3075-3077)default(3135-3137)
lib/segment/tests/integration/segment_tests.rs (3)
lib/segment/src/types.rs (13)
default(644-649)default(1235-1244)default(1248-1250)default(1271-1280)default(1393-1402)default(1743-1745)default(3075-3077)default(3135-3137)new(416-429)new(1610-1613)new(2923-2925)new(3185-3187)new(3198-3200)lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)lib/segment/src/segment_constructor/segment_builder.rs (1)
new(82-167)
lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/src/segment_constructor/segment_builder.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
load_segment(693-775)
lib/shard/src/segment_holder/mod.rs (1)
lib/segment/src/segment_constructor/segment_constructor_base.rs (1)
build_segment(793-821)
lib/segment/benches/hnsw_incremental_build.rs (1)
lib/collection/src/operations/shared_storage_config.rs (1)
default(41-58)
lib/segment/src/index/hnsw_index/hnsw.rs (3)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
vector_storage(124-125)try_new(104-117)recommended(192-221)lib/segment/src/fixtures/index_fixtures.rs (2)
quantized_vectors(79-81)graph_links_vectors(83-85)lib/segment/src/index/hnsw_index/graph_layers.rs (1)
load(504-517)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
lib/common/memory/src/madvise.rs (4)
madvise(93-95)madvise(101-101)madvise(107-113)madvise(131-137)lib/common/memory/src/mmap_ops.rs (1)
open_read_mmap(44-62)lib/common/common/src/flags.rs (1)
feature_flags(131-139)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: e2e-tests
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: test-consensus-compose
- GitHub Check: storage-compat-test
- GitHub Check: test-consistency
- GitHub Check: integration-tests
- GitHub Check: integration-tests-consensus
- GitHub Check: lint
🔇 Additional comments (28)
lib/segment/src/segment_constructor/segment_constructor_base.rs (7)
310-319: Open path wiring to HNSW: LGTM.
HnswIndexOpenArgsreceiveshnsw_global_configas intended.
342-351: Build path wiring to HNSW: LGTM.Consistent with the open path change.
440-448: create_segment now accepts HNSW global config: LGTM.Well-scoped and only forwarded where needed.
548-558: Correct propagation into open_vector_index.All index kinds handled; HNSW receives the config, Plain ignores it. Good separation.
794-821: build_segment signature change — call-site sweep completed: no remaining 3‑arg calls found.
All build_segment usages were verified and use the 4-argument signature (checked with ast-grep/rg across the repo).
271-278: Incorrect: HNSWIndex retains a reference to HnswGlobalConfigHNSWIndex declares
pub hnsw_global_config: &'a HnswGlobalConfig(lib/segment/src/index/hnsw_index/hnsw.rs) — it stores a borrow. Calls that pass&HnswGlobalConfig::default()are only safe if the config outlives the index; either make HNSWIndex own/clone the config or ensure callers keep the HnswGlobalConfig alive for the index's lifetime.Likely an incorrect or invalid review comment.
693-775: load_segment signature change — call-site sweep required
Change looks correct. Best-effort scan found no 2-arg load_segment(...) call-sites; environment tooling was limited (ast-grep missing; ripgrep reported "No files were searched"). Run a full workspace sweep (rg/IDE) to confirm no stale invocations remain.lib/segment/tests/integration/hnsw_incremental_build.rs (1)
149-151: Test updated to pass HNSW global config into HNSWIndex: LGTM.Matches the new API surface.
lib/segment/tests/integration/payload_index_test.rs (2)
87-102: Tests pass HNSW global config through build_segment: LGTM.Using
HnswGlobalConfig::default()here is appropriate for this fixture.
294-300: SegmentBuilder::new receives default HNSW global config: LGTM.Safe since builder clones/owns it.
lib/segment/tests/integration/filtrable_hnsw_test.rs (1)
152-160: HNSWIndex now receiveshnsw_global_config: LGTM.Consistent with refactor; tests remain deterministic.
lib/shard/src/segment_holder/mod.rs (1)
28-30: Importing HnswGlobalConfig for holder: LGTM.lib/segment/tests/integration/segment_tests.rs (1)
206-208: load_segment call sites updated with HNSW global config: LGTM.Both tests use a local default config; clean and explicit.
Also applies to: 244-246
lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (1)
84-86: build_segment and HNSWIndex OpenArgs wired with global config: LGTM.API usage aligns with the refactor.
Also applies to: 143-144
lib/segment/tests/integration/fixtures/segment.rs (1)
14-16: Fixtures updated to pass HNSW global config: LGTM.Consistent across dense and sparse helpers.
Also applies to: 175-176, 271-272, 364-366
lib/segment/tests/integration/multivector_quantization_test.rs (2)
231-233: Build path updated to pass HnswGlobalConfig — looks correctSupplying
&HnswGlobalConfig::default()tobuild_segmentmatches the new API and aligns with the intended behavior for freshly created segments.
334-335: Open/build path wiring for HNSW index is consistentPassing
hnsw_global_config: &HnswGlobalConfig::default()intoHnswIndexOpenArgsmirrors the new plumbing. No issues spotted.lib/segment/src/segment_constructor/segment_builder.rs (3)
468-477: Refactor to expose hnsw_global_config for post-move load — good changeMoving
hnsw_global_configout via destructuring to reuse for the finalload_segmentis sound.
593-594: Open‑args now include global HNSW config — correct propagationForwarding
hnsw_global_configthroughVectorIndexOpenArgsensures consistent behavior between open/build paths.
683-689: load_segment signature change handled correctlyNew
(path, hnsw_global_config, stopped)call looks right; error wrapping remains clear.lib/segment/src/index/hnsw_index/graph_layers.rs (2)
503-517: API: GraphLayers::load now accepts an optional forced format — LGTMThe signature/update aligns with the forced/auto load behavior introduced in this PR.
751-773: Round‑trip/load tests across forced formats — nice coverageExercising
Noneand allSome(...)forced formats and asserting equivalence is great.lib/segment/src/index/hnsw_index/hnsw.rs (2)
170-186: Open path: runtime force selection forwarded into GraphLayers::load — goodDeriving
force_formatfromhnsw_global_config.graph_links_formatand available vectors, then passing it toGraphLayers::load, matches the new design.
630-643: Build path: recommended() inputs look rightUsing feature flags, global config,
m0, vectors presence, and on‑disk status to computeformat_paramaligns with the documented policy.lib/segment/src/index/hnsw_index/graph_links.rs (4)
85-94: New GraphLinksVectorsLayout simplifies layout access — goodSwitching
vectors_layout()to return a value and caching it in storage-backed impl reduces error paths and repeated syscalls.
103-117: StorageGraphLinksVectors::try_new: minimal, fast validation — OKEarly-return on missing quantized vectors and precomputing layouts is clean and cheap.
259-271: GraphLinks::load_from_file takes Advice — correct and usefulPlumbing
Advicethrough enables smarter cache behavior for load vs convert paths.
521-523: Tests updated to pass Advice::Sequential — correctMatches the conversion/read pattern and validates the new API.
039a135 to
394f234
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
lib/segment/src/index/hnsw_index/graph_links.rs (3)
225-235: Consider using theadviceparameter instead of hardcodedAdvice::Random.The function signature has been updated to accept an
adviceparameter, but line 231 still uses a hardcodedAdvice::Random. This seems to be a mistake that defeats the purpose of the parameter.impl GraphLinks { pub fn load_from_file( path: &Path, on_disk: bool, format: GraphLinksFormat, + advice: Advice, ) -> OperationResult<Self> { let populate = !on_disk; - let mmap = open_read_mmap(path, AdviceSetting::Advice(Advice::Random), populate)?; + let mmap = open_read_mmap(path, AdviceSetting::Advice(advice), populate)?; Self::try_new(GraphLinksEnum::Mmap(Arc::new(mmap)), |x| { GraphLinksView::load(x.as_bytes(), format) }) }
484-484: Test missingadviceparameter forload_from_file.The test calls
GraphLinks::load_from_filebut the function signature requires anadviceparameter which is not provided here.- let cmp_links = GraphLinks::load_from_file(&links_file, true, format).unwrap(); + let cmp_links = GraphLinks::load_from_file(&links_file, true, format, Advice::Random).unwrap();
187-197: Add missingGraphLinksFormatParam::recommendedmethod
The runtime‐based format selection relies on apub fn recommended(…)onGraphLinksFormatParam, but no such method is defined inlib/segment/src/index/hnsw_index/graph_links.rsunder theimpl<'a> GraphLinksFormatParam<'a>block. Implement it to complete the PR’s objective of selecting the appropriate graph‐link format at runtime.
🧹 Nitpick comments (10)
lib/segment/src/types.rs (2)
593-599: Doc wording nit: clarify Option semantics.Docs say “Default: false” but the field is
Option<bool>. Suggest “If unset, defaults to false” to avoid ambiguity in generated docs.Apply:
-/// Store copies of original and quantized vectors within the HNSW index file. Default: false. +/// Store copies of original and quantized vectors within the HNSW index file. If unset, defaults to `false`.
611-630: Avoid unnecessary clone in mismatch check.All fields here are
Copy; destructure by value and drop theclone().Apply:
- let HnswConfig { - m, - ef_construct, - full_scan_threshold, - max_indexing_threads: _, - payload_m, - on_disk, - copy_vectors, - } = self.clone(); + let HnswConfig { + m, + ef_construct, + full_scan_threshold, + max_indexing_threads: _, + payload_m, + on_disk, + copy_vectors, + } = *self;lib/collection/src/operations/config_diff.rs (1)
93-99: REST diff field present and exposed in OpenAPI — add optional cross-field validation.Verified:
pub copy_vectors: Option<bool>exists in lib/collection/src/operations/config_diff.rs and is present in the generated OpenAPI (docs/redoc/master/openapi.json). Add a higher-level validation to error whencopy_vectors = truewithout quantization enabled or when multivectors are used.lib/segment/src/index/hnsw_index/graph_links.rs (1)
94-98: Consider derivingDebugfor improved diagnostics.The struct would benefit from deriving
Debugto aid in troubleshooting and logging.+#[derive(Debug)] pub struct StorageGraphLinksVectors<'a> { vector_storage: &'a VectorStorageEnum, // base vectors quantized_vectors: &'a QuantizedVectors, // link vectors vectors_layout: GraphLinksVectorsLayout, }lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (4)
145-152: Optional: parameterize FeatureFlags to exercise both code paths.Consider rstest parameters to run with
hnsw_with_vectorsoff/on, ensuring coverage of both gated behaviors without changing test intent.Example (sketch):
- feature_flags: FeatureFlags::default(), + feature_flags: { + let mut flags = FeatureFlags::default(); + // add a new rstest bool case to toggle this + flags.hnsw_with_vectors = enable_hnsw_with_vectors; + flags + },and add an
#[case(false)]/#[case(true)] enable_hnsw_with_vectors: boolto the test signature.
55-55: Fix misleading comment forfull_scan_threshold.The “KB” unit in the comment appears incorrect for this field. Clarify or remove the unit to avoid confusion.
- let full_scan_threshold = 8; // KB + let full_scan_threshold = 8; // threshold value (not a size unit)
124-132: Alignmax_indexing_threadswith the single‑thread permit.You set
permit_cpu_count = 1for determinism butmax_indexing_threads: 2. Make them consistent to avoid surprises.- max_indexing_threads: 2, + max_indexing_threads: 1,
145-152: Follow‑up test to build+load both link formats.Per the PR discussion, add a small smoke test that builds segments in both
CompressedandCompressedWithVectorsand then loads them, avoiding conversion during load. This test can live separately and useGraphLinksFormatConfig::{ForceWithoutVectors,ForceWithVectors}plus the feature flag.If helpful, I can draft that test skeleton.
lib/segment/src/index/hnsw_index/hnsw.rs (1)
615-625: Consider logging whenStorageGraphLinksVectors::try_newfailsWhen
try_newfails, the configuration is silently ignored (as noted in the comment). Consider adding a debug or trace log to help diagnose configuration issues.let graph_links_vectors = hnsw_config .copy_vectors .unwrap_or_default() .then(|| { // NOTE: the configuration is silently ignored if try_new fails. - StorageGraphLinksVectors::try_new( + let result = StorageGraphLinksVectors::try_new( &vector_storage_ref, quantized_vectors_ref.as_ref(), - ) + ); + if result.is_none() { + log::debug!("Failed to create StorageGraphLinksVectors - using fallback format"); + } + result }) .flatten();lib/api/src/grpc/qdrant.rs (1)
410-415: Clarify “Default: false” semantics for a Diff fieldHnswConfigDiff uses Option; “Default: false” can mislead API users during updates where None means “leave unchanged.” Please reword the docs to distinguish create vs. update behavior and tighten phrasing.
Suggested wording (update in the proto so it regenerates here):
- /// Store copies of original and quantized vectors within the HNSW index file. Default: false. - /// Enabling this option will trade the search speed for disk usage by reducing amount of - /// random seeks during the search. - /// Requires quantized vectors to be enabled. Multi-vectors are not supported. + /// Store copies of original and quantized vectors within the HNSW index file. + /// Create-time default: false. When used in UpdateCollection/VectorParamsDiff, omit this field to leave it unchanged. + /// Enabling this option trades disk space for search speed by reducing random seeks. + /// Requires quantized vectors to be enabled. Not supported for multi-vectors.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (34)
docs/grpc/docs.md(1 hunks)docs/redoc/master/openapi.json(2 hunks)lib/api/src/grpc/conversions.rs(2 hunks)lib/api/src/grpc/proto/collections.proto(1 hunks)lib/api/src/grpc/qdrant.rs(1 hunks)lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs(2 hunks)lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs(1 hunks)lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs(1 hunks)lib/collection/src/operations/config_diff.rs(1 hunks)lib/collection/src/operations/conversions.rs(6 hunks)lib/segment/benches/hnsw_incremental_build.rs(1 hunks)lib/segment/benches/multi_vector_search.rs(1 hunks)lib/segment/src/compat.rs(3 hunks)lib/segment/src/fixtures/index_fixtures.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_layers.rs(1 hunks)lib/segment/src/index/hnsw_index/graph_links.rs(6 hunks)lib/segment/src/index/hnsw_index/graph_links/serializer.rs(1 hunks)lib/segment/src/index/hnsw_index/hnsw.rs(2 hunks)lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs(1 hunks)lib/segment/src/types.rs(3 hunks)lib/segment/src/vector_storage/quantized/quantized_vectors.rs(1 hunks)lib/segment/tests/integration/batch_search_test.rs(1 hunks)lib/segment/tests/integration/byte_storage_hnsw_test.rs(1 hunks)lib/segment/tests/integration/byte_storage_quantization_test.rs(1 hunks)lib/segment/tests/integration/exact_search_test.rs(1 hunks)lib/segment/tests/integration/filtrable_hnsw_test.rs(1 hunks)lib/segment/tests/integration/gpu_hnsw_test.rs(1 hunks)lib/segment/tests/integration/hnsw_discover_test.rs(2 hunks)lib/segment/tests/integration/hnsw_incremental_build.rs(1 hunks)lib/segment/tests/integration/hnsw_quantized_search_test.rs(2 hunks)lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs(1 hunks)lib/segment/tests/integration/multivector_hnsw_test.rs(1 hunks)lib/segment/tests/integration/multivector_quantization_test.rs(1 hunks)lib/segment/tests/integration/segment_on_disk_snapshot.rs(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs/grpc/docs.md
🚧 Files skipped from review as they are similar to previous changes (16)
- lib/segment/src/index/hnsw_index/graph_links/serializer.rs
- lib/segment/benches/hnsw_incremental_build.rs
- lib/segment/tests/integration/batch_search_test.rs
- lib/segment/tests/integration/segment_on_disk_snapshot.rs
- lib/segment/src/vector_storage/quantized/quantized_vectors.rs
- lib/segment/tests/integration/gpu_hnsw_test.rs
- lib/segment/src/fixtures/index_fixtures.rs
- lib/segment/benches/multi_vector_search.rs
- lib/segment/tests/integration/byte_storage_hnsw_test.rs
- lib/segment/tests/integration/byte_storage_quantization_test.rs
- lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs
- lib/segment/tests/integration/multivector_quantization_test.rs
- lib/segment/tests/integration/hnsw_discover_test.rs
- lib/segment/tests/integration/filtrable_hnsw_test.rs
- lib/segment/tests/integration/hnsw_quantized_search_test.rs
- docs/redoc/master/openapi.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead
Files:
lib/api/src/grpc/qdrant.rslib/collection/src/collection_manager/optimizers/indexing_optimizer.rslib/collection/src/operations/config_diff.rslib/api/src/grpc/conversions.rslib/collection/src/collection_manager/optimizers/vacuum_optimizer.rslib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/exact_search_test.rslib/collection/src/operations/conversions.rslib/segment/src/types.rslib/segment/src/index/hnsw_index/graph_layers.rslib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rslib/segment/src/compat.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/tests/integration/multivector_hnsw_test.rslib/segment/src/index/hnsw_index/graph_links.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)
Files:
lib/api/src/grpc/qdrant.rslib/collection/src/collection_manager/optimizers/indexing_optimizer.rslib/collection/src/operations/config_diff.rslib/api/src/grpc/conversions.rslib/collection/src/collection_manager/optimizers/vacuum_optimizer.rslib/collection/src/operations/conversions.rslib/segment/src/types.rslib/segment/src/index/hnsw_index/graph_layers.rslib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rslib/segment/src/compat.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
**/{tests,benches}/**/*.rs
📄 CodeRabbit inference engine (.github/review-rules.md)
Using .unwrap() and panic!() in tests and benchmarks is acceptable and should not be flagged
Files:
lib/segment/tests/integration/hnsw_incremental_build.rslib/segment/tests/integration/exact_search_test.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/tests/integration/multivector_hnsw_test.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: xzfc
PR: qdrant/qdrant#7232
File: lib/shard/src/segment_holder/mod.rs:1088-1094
Timestamp: 2025-09-16T21:39:09.353Z
Learning: The hnsw_format_force and hnsw_with_vectors flags in HnswGlobalConfig primarily affect existing segment conversion during load/startup, not the initial format selection for newly created empty segments. For new segments, format decisions are made by GraphLinksFormatParam::recommended() based on runtime conditions. Therefore, using HnswGlobalConfig::default() for temporary/proxy segments in SegmentHolder::build_tmp_segment is acceptable.
📚 Learning: 2025-08-10T18:26:12.443Z
Learnt from: generall
PR: qdrant/qdrant#7006
File: docs/redoc/master/openapi.json:13645-13652
Timestamp: 2025-08-10T18:26:12.443Z
Learning: In qdrant, docs/redoc/master/openapi.json is auto-generated from the REST schemas. To change field docs, edit lib/api/src/rest/schema.rs (e.g., add doc comments or #[schemars(description = ...)]). Specifically, UpdateVectors.update_filter lacked a description and should state: "If specified, only update vectors for points that match this filter; points not matching the filter are left unchanged."
Applied to files:
lib/api/src/grpc/qdrant.rslib/collection/src/operations/config_diff.rslib/segment/src/types.rs
📚 Learning: 2025-09-16T21:39:09.353Z
Learnt from: xzfc
PR: qdrant/qdrant#7232
File: lib/shard/src/segment_holder/mod.rs:1088-1094
Timestamp: 2025-09-16T21:39:09.353Z
Learning: The hnsw_format_force and hnsw_with_vectors flags in HnswGlobalConfig primarily affect existing segment conversion during load/startup, not the initial format selection for newly created empty segments. For new segments, format decisions are made by GraphLinksFormatParam::recommended() based on runtime conditions. Therefore, using HnswGlobalConfig::default() for temporary/proxy segments in SegmentHolder::build_tmp_segment is acceptable.
Applied to files:
lib/segment/tests/integration/hnsw_incremental_build.rslib/segment/src/types.rslib/segment/tests/integration/multivector_filtrable_hnsw_test.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/tests/integration/multivector_hnsw_test.rs
📚 Learning: 2025-05-30T15:26:14.488Z
Learnt from: generall
PR: qdrant/qdrant#6479
File: lib/segment/src/index/hnsw_index/graph_layers_builder.rs:137-182
Timestamp: 2025-05-30T15:26:14.488Z
Learning: In the subgraph_connectivity method in graph_layers_builder.rs, the previous_visited_points vector and visited BitVec entry point marking are automatically re-initialized at the start of each retry loop iteration, so manual clearing is not needed.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-22T13:49:39.261Z
Learnt from: xzfc
PR: qdrant/qdrant#7118
File: lib/segment/src/index/hnsw_index/graph_links/serializer.rs:50-54
Timestamp: 2025-08-22T13:49:39.261Z
Learning: In Qdrant's graph serialization code (lib/segment/src/index/hnsw_index/graph_links/serializer.rs), the team prefers to keep the code simple rather than adding defensive input validation for edge cases that don't occur in practice, relying on unit tests to catch any unexpected issues early.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rs
📚 Learning: 2025-08-06T09:56:59.311Z
Learnt from: xzfc
PR: qdrant/qdrant#6982
File: lib/segment/src/index/hnsw_index/graph_links/view.rs:217-220
Timestamp: 2025-08-06T09:56:59.311Z
Learning: In Qdrant's GraphLinksView implementation (lib/segment/src/index/hnsw_index/graph_links/view.rs), methods like links() and links_with_vectors() intentionally use unwrap() and can panic for performance reasons, maintaining consistency across similar methods. Error handling improvements are considered secondary to feature completion and require benchmarking before implementation.
Applied to files:
lib/segment/src/index/hnsw_index/graph_layers.rslib/segment/src/index/hnsw_index/hnsw.rslib/segment/src/index/hnsw_index/graph_links.rs
🧬 Code graph analysis (2)
lib/segment/src/index/hnsw_index/graph_layers.rs (1)
lib/segment/src/fixtures/index_fixtures.rs (1)
graph_links_vectors(83-85)
lib/segment/src/index/hnsw_index/hnsw.rs (2)
lib/segment/src/fixtures/index_fixtures.rs (1)
graph_links_vectors(83-85)lib/segment/src/index/hnsw_index/graph_links.rs (1)
try_new(101-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: rust-tests (windows-latest)
- GitHub Check: rust-tests (macos-latest)
- GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
- GitHub Check: rust-tests (ubuntu-latest)
- GitHub Check: storage-compat-test
- GitHub Check: integration-tests-consensus
- GitHub Check: e2e-tests
- GitHub Check: test-shard-snapshot-api-s3-minio
- GitHub Check: test-consensus-compose
- GitHub Check: integration-tests
- GitHub Check: test-consistency
- GitHub Check: lint
🔇 Additional comments (22)
lib/segment/src/types.rs (1)
1242-1243: Default updated correctly.Including
copy_vectors: NoneinDefaultkeeps backward compatibility.lib/segment/tests/integration/exact_search_test.rs (1)
81-82: Test update is fine.Explicit
copy_vectors: Nonekeeps the struct literal compiling and behavior unchanged for this test.lib/segment/src/compat.rs (1)
159-160: Compat tests updated appropriately.Adding
copy_vectors: NonetoHnswConfigliterals maintains deserialization/upgrade tests without altering semantics.Also applies to: 191-192, 267-268
lib/api/src/grpc/proto/collections.proto (1)
198-205: Verification incomplete — confirmcopy_vectorsis propagated to generated gRPC types, Rust conversions, and OpenAPI
Automated search returned no matches; I could not verify end-to-end wiring. Verify these surfaces:
- Proto: lib/api/src/grpc/proto/collections.proto — ensure message HnswConfigDiff contains
optional bool copy_vectors.- Generated gRPC types / proto-generated crates: confirm generated code contains
copy_vectors.- Rust conversions: check lib/api/src/grpc/conversions.rs and lib/collection/src/operations/conversions.rs for mapping of
copy_vectors.- OpenAPI/REST: ensure docs/openapi.json (or the generated OpenAPI artifact) includes
"copy_vectors".Re-run to verify across the repo:
rg -n --hidden -S 'copy_vectors' || truelib/segment/src/index/hnsw_index/graph_links.rs (4)
83-83: LGTM!The signature change from
OperationResult<GraphLinksVectorsLayout>to simplyGraphLinksVectorsLayoutis appropriate since the layout is now precomputed inStorageGraphLinksVectors::try_new(). This eliminates potential errors during runtime calls and improves performance.
87-91: LGTM!Making
GraphLinksVectorsLayoutpublic andCopyis appropriate as it's a simple struct containing layout metadata that needs to be accessed across module boundaries. The fields being public is reasonable for this data-only struct.
100-115: LGTM! Precomputing the layout is a good optimization.The constructor validates inputs early and precomputes the layout, eliminating error handling overhead in
vectors_layout()calls.
135-137: LGTM!Returning the precomputed layout directly is more efficient than computing it on each call.
lib/segment/tests/integration/hnsw_incremental_build.rs (1)
134-134: LGTM!The addition of
copy_vectors: Nonealigns with the new optional field inHnswConfig.lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs (1)
471-471: LGTM!The addition of
copy_vectors: Nonecorrectly initializes the new field inHnswConfig.lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs (1)
880-880: LGTM!The addition of
copy_vectors: Noneproperly initializes the new optional field inHnswConfig.lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (2)
351-351: LGTM!The addition of
copy_vectors: Nonecorrectly initializes the new field inHnswConfigfor the test.
512-512: LGTM!The addition of
copy_vectors: Nonecorrectly initializes the new field inHnswConfigfor the multi-vector test.lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (1)
120-128: Setcopy_vectors: Noneis appropriate here (defers to runtime recommendation).Good choice to leave it unset in this test so format selection follows
GraphLinksFormatParam::recommended()rather than forcing conversions.Please confirm that in this test the feature flag default does not inadvertently override the recommendation (i.e., with
FeatureFlags::default()the writer won’t attemptCompressedWithVectorsunless explicitly enabled).lib/segment/tests/integration/multivector_hnsw_test.rs (2)
112-113: LGTM!The addition of
copy_vectors: Nonemaintains backward compatibility and follows the established pattern in the codebase.
134-136: LGTM!Correctly passing
HnswGlobalConfig::default()toVectorIndexBuildArgsprovides the required global configuration for HNSW index building.lib/segment/src/index/hnsw_index/graph_layers.rs (1)
708-718: Test logic change needs clarificationThe test now always forces
truefor vector usage on line 709, but the comment above (line 707) suggests this is intentional. Consider whether this hardcoding is appropriate for all test cases intest_save_and_load, especially since the parameter is namedinitial_formatwhich implies it should vary based on the format type.Could you verify if forcing
truefor all format types (Plain, Compressed, CompressedWithVectors) is the intended behavior? The previous logicinitial_format.is_with_vectors()seemed to respect the format type.lib/api/src/grpc/conversions.rs (1)
2128-2130: LGTM!The addition of
copy_vectorsfield properly propagates the configuration from gRPC to the internalHnswConfig.lib/collection/src/operations/conversions.rs (2)
243-244: LGTM!The
copy_vectorsfield is correctly propagated through all conversion paths between gRPC and internal representations.Also applies to: 252-253, 266-267, 275-276
419-420: LGTM!The
copy_vectorsfield is correctly added to theCollectionInfoto gRPC conversion path, maintaining consistency with the rest of the codebase.Also applies to: 501-502
lib/segment/src/index/hnsw_index/hnsw.rs (2)
49-49: LGTM!The import changes align with the move from static to runtime-driven graph link formatting.
615-632: Runtime graph link format selection properly implementedThe logic correctly:
- Checks if
copy_vectorsis enabled (defaulting to false)- Attempts to create
StorageGraphLinksVectorswhen enabled- Falls back to
GraphLinksFormatParam::Compressedwhen vectors are unavailable- Passes the computed format to
GraphLayersBuilder::into_graph_layersThis enables runtime decision-making for HNSW graph link formatting based on configuration and vector availability.
| /// Store copies of original and quantized vectors within the HNSW index file. Default: false. | ||
| /// Enabling this option will trade the search speed for disk usage by reducing amount of | ||
| /// random seeks during the search. | ||
| /// Requires quantized vectors to be enabled. Multi-vectors are not supported. | ||
| #[prost(bool, optional, tag = "7")] | ||
| pub copy_vectors: ::core::option::Option<bool>, |
There was a problem hiding this comment.
🧩 Analysis chain
Validate constraints server‑side (quantization required; no multivectors) and wire it through conversions
The docs note hard constraints; make sure requests with copy_vectors=true are rejected when quantization is off or multivectors are configured, and that conversions to internal types propagate this flag.
Run to verify propagation and guards exist:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Where is it used?
rg -n --hidden -C3 '\bcopy_vectors\b'
# Conversions layer coverage
fd -t f conversions.rs | xargs -I{} rg -n -C3 '\bcopy_vectors\b' {}
# Internal config/types usage
rg -n --hidden -C3 'struct\s+HnswConfig\b|impl\b.*HnswConfig' lib/segment
rg -n --hidden -C3 'quantiz|quantization' lib/segment | rg -n 'require|error|copy_vectors' -n
# Tests asserting invalid combos are rejected
rg -n --hidden -C3 'copy_vectors.*(multivector|multi-vector|quantiz|error|fail|panic)' lib/segment/testsLength of output: 28088
Validate constraints server‑side: reject copy_vectors=true when quantization is disabled or multivectors are configured.
Conversions already propagate copy_vectors (lib/api/src/grpc/conversions.rs; lib/collection/src/operations/conversions.rs), but there’s no explicit guard — HNSW only reads the flag and may silently ignore failures (lib/segment/src/index/hnsw_index/hnsw.rs). Reject at the API/collection-creation or config-merge layer with a clear error when quantization_config is None or multivector_config.is_some().
|
Made a small benchmark on 1M 512d vectors, all on disk + BQ (also on disk) Latency of the cold request (with dropped caches) RPS results with mem limit = 300Mb With feature ~15RPS |
* StorageGraphLinksVectors::try_new, make GraphLinksVectorsLayout non-fallible * Integrate hnsw_with_vectors * remove unused function --------- Co-authored-by: generall <andrey@vasnetsov.com>
This PR integrates the
hnsw_with_vectorsfeature. Two new feature options are added:The plan is to enable
hnsw_with_vectorsin a future release.The new method
GraphLinksFormatParam::recommendeddecides which format to use for a newly written segment. (in short: only when graph node is no more than 8 KiB)