Skip to content

Integrate hnsw_with_vectors#7232

Merged
xzfc merged 3 commits intodevfrom
integtate-hnsw-with-vectors
Sep 18, 2025
Merged

Integrate hnsw_with_vectors#7232
xzfc merged 3 commits intodevfrom
integtate-hnsw-with-vectors

Conversation

@xzfc
Copy link
Member

@xzfc xzfc commented Sep 8, 2025

This PR integrates the hnsw_with_vectors feature. Two new feature options are added:

struct FeatureFlags {/// Allow writing the HNSW graph in the `CompressedWithVectors` format.
    /// Also see `GraphLinksFormatConfig`.
    pub hnsw_with_vectors: bool,
}

// inside storage.hnsw_global_config.links_with_vectors
pub enum GraphLinksFormatConfig {
    /// Decide the format based on the HNSW parameters and vector size in bytes.
    /// Do not convert during segment loading.
    #[default]
    Auto,
    /// Always use `Compressed`.
    /// Convert during segment loading.
    ForceWithoutVectors,
    /// Use `CompressedWithVectors` when possible.
    /// Convert during segment loading.
    ForceWithVectors,
}

The plan is to enable hnsw_with_vectors in a future release.

The new method GraphLinksFormatParam::recommended decides which format to use for a newly written segment. (in short: only when graph node is no more than 8 KiB)

@xzfc xzfc added this to the Graph with vectors milestone Sep 8, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

📝 Walkthrough

Walkthrough

Introduces 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

next patch

Suggested reviewers

  • timvisee
  • generall
  • IvanPleshkov

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Integrate hnsw_with_vectors" is a concise, single-sentence summary that directly reflects the primary change in the diff—adding support and configuration for writing HNSW graphs with vectors (feature flag, GraphLinksFormatConfig, and related runtime selection). It is specific enough for a reviewer scanning PR history to understand the main intent.
Description Check ✅ Passed The PR description explains the new feature flag, the GraphLinksFormatConfig variants, and the purpose of GraphLinksFormatParam::recommended, which matches the code, tests, and documentation updates in the changeset; it is directly related to the changes and provides useful context for reviewers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch integtate-hnsw-with-vectors

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.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 None and conversion for Some(...) are implemented cleanly with appropriate Advice usage (Random for steady-state, Sequential during conversion).
  • Suggest using info for 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

📥 Commits

Reviewing files that changed from the base of the PR and between c05df46 and 8095c51.

📒 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.rs
  • lib/segment/benches/fixture.rs
  • lib/common/common/src/flags.rs
  • lib/segment/src/fixtures/index_fixtures.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/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.rs
  • lib/common/common/src/flags.rs
  • lib/segment/src/fixtures/index_fixtures.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/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.rs
  • lib/segment/src/fixtures/index_fixtures.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • 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/graph_layers_builder.rs
  • lib/segment/src/fixtures/index_fixtures.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/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.rs
  • lib/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.rs
  • lib/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_vectors and hnsw_format_force when all is 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 None for force_format matches 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_new centralizes 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_links is good, and HnswM is derived before link loading. LGTM.


519-596: Edge case: forced CompressedWithVectors without available vectors.

Ensure upstream only constructs GraphLinksFormatParam::CompressedWithVectors when 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.

@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from 8095c51 to a4c8c83 Compare September 8, 2025 22:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 “CompressedWithVectors format” 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 load

If 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 context

Including 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-safe

Tiny 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 tests

Micro DX improvement when tests misconfigure vectors.

-                None => panic!(),
+                None => panic!("CompressedWithVectors requires vectors in tests"),

9-11: Drop unused import

Madviseable isn’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

📥 Commits

Reviewing files that changed from the base of the PR and between 8095c51 and a4c8c83.

📒 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.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/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
  • lib/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: FeatureFlags declares both hnsw_with_vectors and hnsw_format_force; init_feature_flags(all=true) sets each to true; and AppBuildTelemetry.runtime_features exposes the flags via common::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 advise

The “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 well

Great 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 correct

The 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 correctly

Sequential advise during load/verify mirrors the conversion path. Looks good.


249-261: All GraphLinks::load_from_file call sites include the new advice argument
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 unavailable

When hnsw_format_force=true and hnsw_with_vectors=true but graph_links_vectors is None, the code no-ops (None). Confirm this is intentional vs. forcing Compressed and/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(...) correctly

Good to gate CompressedWithVectors by feature flags, m0, and vectors availability during build.

@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from a4c8c83 to 85b456e Compare September 8, 2025 23:29
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 conversions

Current 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 logging

Useful 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 diagnose

Returning 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

📥 Commits

Reviewing files that changed from the base of the PR and between a4c8c83 and 85b456e.

📒 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.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/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.rs
  • lib/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 retained

Switching 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 formats

Good 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 infallible

Trait change to return GraphLinksVectorsLayout directly simplifies callers and matches serializer changes.


137-139: Cached layout accessor is fine

Returning the precomputed layout avoids repeated fallible queries; matches the new trait.


251-259: load_from_file now accepts Advice; good

Passing Advice through to open_read_mmap enables Random/Sequential hints from callers. Implementation is correct.


512-514: Test: explicit Advice during load

Using Advice::Sequential here matches the write/read pattern and validates the new API.

@xzfc xzfc changed the title StorageGraphLinksVectors::try_new Integrate hnsw_with_vectors Sep 9, 2025
@xzfc xzfc requested a review from timvisee September 9, 2025 10:06
@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from 85b456e to 40a056d Compare September 9, 2025 13:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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::Compressed

Would 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

📥 Commits

Reviewing files that changed from the base of the PR and between 85b456e and 40a056d.

📒 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.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/common/common/src/flags.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/common/common/src/flags.rs
  • lib/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.rs
  • lib/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.rs
  • lib/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.rs
  • 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/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: All GraphLinksVectors::vectors_layout implementations updated to infallible signature. No outdated signatures detected.


252-263: No action needed: all GraphLinks::load_from_file calls now supply an Advice argument.

Copy link
Member

@timvisee timvisee left a comment

Choose a reason for hiding this comment

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

Can we add a test that builds/loads both GraphLinksFormat::CompressedWithVectors and GraphLinksFormat::Compressed?

///
/// When set, the graph will be converted on startup to the corresponding
/// format (`Compressed` or `CompressedWithVectors`).
pub hnsw_format_force: bool,
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you think that's desired?

No, but I did it for consistency with other options.

What if I add it to (SettingsStorageConfigHnswGlobalConfig; 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.

@xzfc
Copy link
Member Author

xzfc commented Sep 12, 2025

Can we add a test that builds/loads both GraphLinksFormat::CompressedWithVectors and GraphLinksFormat::Compressed?

A lot of existing tests are parametrized by the format type (run rg -C10 '#\[case::.*GraphLinksFormat::') and already cover both of these.
Or you mean you want a single test that builds both at the same time?
Also, test_save_and_load also covers conversion (12 variants), tho it's not related to the production case (we don't want to convert in the production configuration).

@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from 40a056d to 396ec3c Compare September 16, 2025 20:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 via all sets 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: Bind HnswGlobalConfig::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: Bind HnswGlobalConfig::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 local let hgc = HnswGlobalConfig::default(); and reuse it below for HnswIndexOpenArgs to avoid duplication and make future tweaks explicit.


130-131: Pass the same config instance to build and open.

Use the same hgc variable 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 &hgc here 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)] on HnswGlobalConfig plus Default impl 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 &hgc here 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 formats

Add 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 for graph_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 oneOf with 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_vectors and 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 then temp_dir is 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 GraphLinksVectorsLayout struct could benefit from deriving Debug for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 40a056d and 396ec3c.

📒 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.rs
  • lib/segment/tests/integration/exact_search_test.rs
  • lib/segment/src/types.rs
  • lib/segment/tests/integration/hnsw_quantized_search_test.rs
  • lib/segment/src/segment/tests.rs
  • lib/segment/tests/integration/payload_index_test.rs
  • lib/shard/src/proxy_segment/tests.rs
  • lib/common/common/src/flags.rs
  • lib/segment/tests/integration/segment_on_disk_snapshot.rs
  • lib/segment/tests/integration/hnsw_incremental_build.rs
  • lib/segment/tests/integration/sparse_discover_test.rs
  • lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs
  • lib/segment/tests/integration/filtrable_hnsw_test.rs
  • lib/segment/tests/integration/batch_search_test.rs
  • lib/segment/tests/integration/byte_storage_hnsw_test.rs
  • lib/segment/src/index/struct_payload_index.rs
  • lib/segment/benches/hnsw_incremental_build.rs
  • lib/segment/tests/integration/segment_tests.rs
  • lib/segment/tests/integration/sparse_vector_index_search_tests.rs
  • lib/segment/tests/integration/hnsw_discover_test.rs
  • lib/segment/tests/integration/multivector_hnsw_test.rs
  • lib/segment/tests/integration/fixtures/segment.rs
  • lib/segment/benches/multi_vector_search.rs
  • lib/segment/src/segment_constructor/segment_builder.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/shard/src/segment_holder/mod.rs
  • lib/segment/src/segment_constructor/simple_segment_constructor.rs
  • lib/collection/src/shards/local_shard/mod.rs
  • lib/segment/tests/integration/byte_storage_quantization_test.rs
  • lib/segment/tests/integration/gpu_hnsw_test.rs
  • lib/segment/tests/integration/multivector_quantization_test.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/segment/src/segment_constructor/segment_constructor_base.rs
  • src/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.rs
  • lib/segment/src/types.rs
  • lib/segment/src/segment/tests.rs
  • lib/shard/src/proxy_segment/tests.rs
  • lib/common/common/src/flags.rs
  • lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs
  • lib/segment/src/index/struct_payload_index.rs
  • lib/segment/src/segment_constructor/segment_builder.rs
  • lib/shard/src/segment_holder/mod.rs
  • lib/segment/src/segment_constructor/simple_segment_constructor.rs
  • lib/collection/src/shards/local_shard/mod.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/segment/src/index/hnsw_index/graph_links.rs
  • lib/segment/src/segment_constructor/segment_constructor_base.rs
  • src/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.rs
  • lib/segment/tests/integration/hnsw_quantized_search_test.rs
  • lib/segment/tests/integration/payload_index_test.rs
  • lib/segment/tests/integration/segment_on_disk_snapshot.rs
  • lib/segment/tests/integration/hnsw_incremental_build.rs
  • lib/segment/tests/integration/sparse_discover_test.rs
  • lib/segment/src/index/hnsw_index/tests/test_graph_connectivity.rs
  • lib/segment/tests/integration/filtrable_hnsw_test.rs
  • lib/segment/tests/integration/batch_search_test.rs
  • lib/segment/tests/integration/byte_storage_hnsw_test.rs
  • lib/segment/benches/hnsw_incremental_build.rs
  • lib/segment/tests/integration/segment_tests.rs
  • lib/segment/tests/integration/sparse_vector_index_search_tests.rs
  • lib/segment/tests/integration/hnsw_discover_test.rs
  • lib/segment/tests/integration/multivector_hnsw_test.rs
  • lib/segment/tests/integration/fixtures/segment.rs
  • lib/segment/benches/multi_vector_search.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/segment/tests/integration/byte_storage_quantization_test.rs
  • lib/segment/tests/integration/gpu_hnsw_test.rs
  • lib/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.rs
  • 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/byte_storage_quantization_test.rs
  • 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/byte_storage_quantization_test.rs
  • lib/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.rs
  • lib/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 HnswGlobalConfig

The 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 of HnswGlobalConfig into build_segment.


282-285: LGTM — consistent with the new build_segment signature.

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: Missing hnsw_format_force flag in schema — add it to FeatureFlags

Repository search for hnsw_format_force returned 0 matches across 1,243 files; docs/redoc/master/openapi.json (around lines 11757–11762) only exposes hnsw_with_vectors. If the PR intends two flags, add hnsw_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 SegmentBuilder fields is done correctly before deriving the temporary directory and destination path.


594-594: LGTM!

The hnsw_global_config field is correctly propagated into VectorIndexOpenArgs for use in vector index opening.


684-690: LGTM!

The updated load_segment call correctly passes the hnsw_global_config parameter, maintaining consistency with the new signature.

lib/segment/src/segment_constructor/segment_constructor_base.rs (10)

277-277: LGTM!

The hnsw_global_config field is correctly added to VectorIndexOpenArgs and used consistently throughout the codebase.


301-301: LGTM!

The hnsw_global_config field is correctly extracted in the destructuring pattern for use in subsequent operations.


317-317: LGTM!

The hnsw_global_config is correctly passed through to HnswIndexOpenArgs for HNSW index configuration.


333-333: LGTM!

The hnsw_global_config field is correctly extracted and passed to the HNSW index build process.


350-350: LGTM!

The hnsw_global_config is correctly propagated through the HNSW index build path.


445-445: LGTM!

The hnsw_global_config parameter is correctly added to the create_segment function signature.


556-556: LGTM!

The hnsw_global_config is correctly passed to the vector index open arguments during segment creation.


693-697: LGTM!

The load_segment function signature is correctly updated to include the hnsw_global_config parameter.


757-757: LGTM!

The hnsw_global_config is correctly passed through to the create_segment function during segment loading.


796-796: LGTM!

The build_segment function correctly accepts and propagates the hnsw_global_config parameter to create_segment.

Also applies to: 808-808

lib/segment/src/index/hnsw_index/hnsw.rs (7)

49-49: LGTM!

The import of GraphLinksFormatParam and StorageGraphLinksVectors is appropriate for the new runtime graph format selection logic.


64-65: LGTM!

The imports for GraphLinksFormatConfig and HnswGlobalConfig are correctly added to support the new configuration-driven approach.


126-127: LGTM!

The hnsw_global_config field is correctly added to HnswIndexOpenArgs and properly extracted during destructuring.

Also applies to: 138-139


236-237: LGTM!

The hnsw_global_config is 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::recommended is well-structured. It correctly considers:

  1. Feature flags (hnsw_with_vectors)
  2. Global configuration (hnsw_global_config.graph_links_format)
  3. Graph parameters (config.m0)
  4. Storage characteristics (on-disk status of vector storage, quantized vectors, and HNSW index)

1489-1489: LGTM!

The hnsw_global_config is 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, and PAGE_SIZE_BYTES are 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 returning OperationResult<GraphLinksVectorsLayout> to returning GraphLinksVectorsLayout directly is a good simplification. The cached layout in StorageGraphLinksVectors avoids redundant computations.

Also applies to: 138-140


97-101: LGTM!

Making the fields of StorageGraphLinksVectors private with a cached vectors_layout is a good design choice for encapsulation and performance.


103-117: LGTM!

The try_new constructor properly validates inputs and pre-computes the layout. The use of Option return type appropriately handles the case where required vectors are unavailable.


191-221: Well-designed format selection logic.

The recommended method implements a sophisticated decision tree for selecting the graph format:

  1. Uses a reasonable heuristic (node size ≤ 2 × page size) to determine when vectors should be included
  2. Correctly respects explicit configuration when ForceWithoutVectors or ForceWithVectors is set
  3. 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 advice parameter to load_from_file provides better control over memory access patterns, which is important for performance tuning.

Also applies to: 267-267


381-381: LGTM!

Test updates correctly pass Advice parameter and update vector layout representations to use GraphLinksVectorsLayout.

Also applies to: 407-410, 424-426, 521-522

@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from 396ec3c to 213ea42 Compare September 16, 2025 21:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 signature

Just 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*\)' || true

Also applies to: 630-646

🧹 Nitpick comments (6)
docs/redoc/master/openapi.json (2)

11776-11778: Set an explicit default for graph_links_format

Many 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 in force_with_vectors description

Make it explicit that we fall back to Compressed if 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::Random vs Sequential, 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 prevent CompressedWithVectors even 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 honored

A 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

📥 Commits

Reviewing files that changed from the base of the PR and between 396ec3c and 213ea42.

📒 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.rs
  • lib/segment/benches/fixture.rs
  • lib/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.rs
  • lib/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.json
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/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.rs
  • 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_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.rs
  • 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
🧬 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: Add hnsw_with_vectors flag — LGTM; confirm scope in docs

Looks 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 of hnsw_format_force

I ran a repo-wide search for hnsw_format_force and 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 the hnsw_format_force FeatureFlags entry back.

lib/segment/benches/fixture.rs (1)

56-56: API call site updated correctly

Passing None for the new force_format parameter matches the updated GraphLayers::load signature. LGTM.

lib/segment/src/index/hnsw_index/graph_layers.rs (2)

503-517: Load API refactor looks sound

Constructing HnswM once and delegating to load_links with the optional force_format is clean and cohesive. No issues.


714-774: Great test coverage for load/convert matrix

The parametrized round-trip validates normal load and all forced conversions, including vectors-inclusive. Nicely done.

@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from 213ea42 to 039a135 Compare September 16, 2025 22:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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; bind HnswGlobalConfig::default() to a local first.

&HnswGlobalConfig::default() creates a reference to a temporary dropped at the end of the let 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 test

If you want this test suite to also cover writing CompressedWithVectors when feasible, consider tying HnswConfig.on_disk to the on_disk test parameter; the current Some(false) disables that branch in recommended().

     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 originals

The 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 intent

Docs 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

📥 Commits

Reviewing files that changed from the base of the PR and between 213ea42 and 039a135.

📒 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.rs
  • lib/segment/tests/integration/filtrable_hnsw_test.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/segment/tests/integration/multivector_quantization_test.rs
  • lib/segment/tests/integration/payload_index_test.rs
  • lib/segment/tests/integration/hnsw_incremental_build.rs
  • lib/segment/tests/integration/fixtures/segment.rs
  • lib/segment/tests/integration/segment_tests.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/segment/src/segment_constructor/segment_builder.rs
  • lib/shard/src/segment_holder/mod.rs
  • lib/segment/benches/hnsw_incremental_build.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/segment/src/segment_constructor/segment_builder.rs
  • lib/shard/src/segment_holder/mod.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/segment/tests/integration/multivector_quantization_test.rs
  • lib/segment/tests/integration/payload_index_test.rs
  • lib/segment/tests/integration/hnsw_incremental_build.rs
  • lib/segment/tests/integration/fixtures/segment.rs
  • lib/segment/tests/integration/segment_tests.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/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.rs
  • lib/segment/tests/integration/filtrable_hnsw_test.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/segment/tests/integration/multivector_quantization_test.rs
  • lib/segment/tests/integration/payload_index_test.rs
  • lib/segment/tests/integration/hnsw_incremental_build.rs
  • lib/segment/tests/integration/fixtures/segment.rs
  • lib/segment/tests/integration/segment_tests.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/segment/src/segment_constructor/segment_builder.rs
  • lib/shard/src/segment_holder/mod.rs
  • lib/segment/benches/hnsw_incremental_build.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/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.

HnswIndexOpenArgs receives hnsw_global_config as 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 HnswGlobalConfig

HNSWIndex 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 receives hnsw_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 correct

Supplying &HnswGlobalConfig::default() to build_segment matches the new API and aligns with the intended behavior for freshly created segments.


334-335: Open/build path wiring for HNSW index is consistent

Passing hnsw_global_config: &HnswGlobalConfig::default() into HnswIndexOpenArgs mirrors 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 change

Moving hnsw_global_config out via destructuring to reuse for the final load_segment is sound.


593-594: Open‑args now include global HNSW config — correct propagation

Forwarding hnsw_global_config through VectorIndexOpenArgs ensures consistent behavior between open/build paths.


683-689: load_segment signature change handled correctly

New (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 — LGTM

The signature/update aligns with the forced/auto load behavior introduced in this PR.


751-773: Round‑trip/load tests across forced formats — nice coverage

Exercising None and all Some(...) 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 — good

Deriving force_format from hnsw_global_config.graph_links_format and available vectors, then passing it to GraphLayers::load, matches the new design.


630-643: Build path: recommended() inputs look right

Using feature flags, global config, m0, vectors presence, and on‑disk status to compute format_param aligns with the documented policy.

lib/segment/src/index/hnsw_index/graph_links.rs (4)

85-94: New GraphLinksVectorsLayout simplifies layout access — good

Switching 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 — OK

Early-return on missing quantized vectors and precomputing layouts is clean and cheap.


259-271: GraphLinks::load_from_file takes Advice — correct and useful

Plumbing Advice through enables smarter cache behavior for load vs convert paths.


521-523: Tests updated to pass Advice::Sequential — correct

Matches the conversion/read pattern and validates the new API.

@xzfc xzfc force-pushed the integtate-hnsw-with-vectors branch from 039a135 to 394f234 Compare September 18, 2025 15:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 the advice parameter instead of hardcoded Advice::Random.

The function signature has been updated to accept an advice parameter, but line 231 still uses a hardcoded Advice::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 missing advice parameter for load_from_file.

The test calls GraphLinks::load_from_file but the function signature requires an advice parameter 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 missing GraphLinksFormatParam::recommended method
The runtime‐based format selection relies on a pub fn recommended(…) on GraphLinksFormatParam, but no such method is defined in lib/segment/src/index/hnsw_index/graph_links.rs under the impl<'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 the clone().

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 when copy_vectors = true without quantization enabled or when multivectors are used.

lib/segment/src/index/hnsw_index/graph_links.rs (1)

94-98: Consider deriving Debug for improved diagnostics.

The struct would benefit from deriving Debug to 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_vectors off/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: bool to the test signature.


55-55: Fix misleading comment for full_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: Align max_indexing_threads with the single‑thread permit.

You set permit_cpu_count = 1 for determinism but max_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 Compressed and CompressedWithVectors and then loads them, avoiding conversion during load. This test can live separately and use GraphLinksFormatConfig::{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 when StorageGraphLinksVectors::try_new fails

When try_new fails, 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 field

HnswConfigDiff 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

📥 Commits

Reviewing files that changed from the base of the PR and between 039a135 and 394f234.

📒 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.rs
  • lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs
  • lib/collection/src/operations/config_diff.rs
  • lib/api/src/grpc/conversions.rs
  • lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs
  • lib/segment/tests/integration/hnsw_incremental_build.rs
  • lib/segment/tests/integration/exact_search_test.rs
  • lib/collection/src/operations/conversions.rs
  • lib/segment/src/types.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs
  • lib/segment/src/compat.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/segment/tests/integration/multivector_hnsw_test.rs
  • lib/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.rs
  • lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs
  • lib/collection/src/operations/config_diff.rs
  • lib/api/src/grpc/conversions.rs
  • lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs
  • lib/collection/src/operations/conversions.rs
  • lib/segment/src/types.rs
  • lib/segment/src/index/hnsw_index/graph_layers.rs
  • lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs
  • lib/segment/src/compat.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • lib/segment/tests/integration/exact_search_test.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/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.rs
  • lib/collection/src/operations/config_diff.rs
  • lib/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.rs
  • lib/segment/src/types.rs
  • lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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.rs
  • 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/graph_layers.rs
  • lib/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.rs
  • lib/segment/src/index/hnsw_index/hnsw.rs
  • lib/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: None in Default keeps backward compatibility.

lib/segment/tests/integration/exact_search_test.rs (1)

81-82: Test update is fine.

Explicit copy_vectors: None keeps 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: None to HnswConfig literals 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 — confirm copy_vectors is 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' || true

lib/segment/src/index/hnsw_index/graph_links.rs (4)

83-83: LGTM!

The signature change from OperationResult<GraphLinksVectorsLayout> to simply GraphLinksVectorsLayout is appropriate since the layout is now precomputed in StorageGraphLinksVectors::try_new(). This eliminates potential errors during runtime calls and improves performance.


87-91: LGTM!

Making GraphLinksVectorsLayout public and Copy is 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: None aligns with the new optional field in HnswConfig.

lib/collection/src/collection_manager/optimizers/vacuum_optimizer.rs (1)

471-471: LGTM!

The addition of copy_vectors: None correctly initializes the new field in HnswConfig.

lib/collection/src/collection_manager/optimizers/indexing_optimizer.rs (1)

880-880: LGTM!

The addition of copy_vectors: None properly initializes the new optional field in HnswConfig.

lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (2)

351-351: LGTM!

The addition of copy_vectors: None correctly initializes the new field in HnswConfig for the test.


512-512: LGTM!

The addition of copy_vectors: None correctly initializes the new field in HnswConfig for the multi-vector test.

lib/segment/tests/integration/multivector_filtrable_hnsw_test.rs (1)

120-128: Set copy_vectors: None is 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 attempt CompressedWithVectors unless explicitly enabled).

lib/segment/tests/integration/multivector_hnsw_test.rs (2)

112-113: LGTM!

The addition of copy_vectors: None maintains backward compatibility and follows the established pattern in the codebase.


134-136: LGTM!

Correctly passing HnswGlobalConfig::default() to VectorIndexBuildArgs provides the required global configuration for HNSW index building.

lib/segment/src/index/hnsw_index/graph_layers.rs (1)

708-718: Test logic change needs clarification

The test now always forces true for 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 in test_save_and_load, especially since the parameter is named initial_format which implies it should vary based on the format type.

Could you verify if forcing true for all format types (Plain, Compressed, CompressedWithVectors) is the intended behavior? The previous logic initial_format.is_with_vectors() seemed to respect the format type.

lib/api/src/grpc/conversions.rs (1)

2128-2130: LGTM!

The addition of copy_vectors field properly propagates the configuration from gRPC to the internal HnswConfig.

lib/collection/src/operations/conversions.rs (2)

243-244: LGTM!

The copy_vectors field 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_vectors field is correctly added to the CollectionInfo to 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 implemented

The logic correctly:

  1. Checks if copy_vectors is enabled (defaulting to false)
  2. Attempts to create StorageGraphLinksVectors when enabled
  3. Falls back to GraphLinksFormatParam::Compressed when vectors are unavailable
  4. Passes the computed format to GraphLayersBuilder::into_graph_layers

This enables runtime decision-making for HNSW graph link formatting based on configuration and vector availability.

Comment on lines +410 to +415
/// 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>,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 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/tests

Length 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().

@generall
Copy link
Member

Made a small benchmark on 1M 512d vectors, all on disk + BQ (also on disk)

Latency of the cold request (with dropped caches)

// copy_vectors = false
// "time": 0.464265383
// "time": 0.494518743

// copy_vectors = true
// "time": 0.046570457
// "time": 0.047879428

RPS results with mem limit = 300Mb

With feature ~15RPS
W/o feature ~0.5PRS

@xzfc xzfc merged commit 22df6bc into dev Sep 18, 2025
16 checks passed
@xzfc xzfc deleted the integtate-hnsw-with-vectors branch September 18, 2025 17:36
timvisee pushed a commit that referenced this pull request Sep 29, 2025
* StorageGraphLinksVectors::try_new, make GraphLinksVectorsLayout non-fallible

* Integrate hnsw_with_vectors

* remove unused function

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants