Skip to content

Conversation

coszio
Copy link
Contributor

@coszio coszio commented Jul 22, 2025

Similar to #6685, swaps the container of ids per value in mutable map index from BTreeSet<u32> to RoaringBitmap.

This is not too noticeable on "normal" workloads, but when there is many values per point, it does provide a bit of advantage on insertion.

For example, with the following command (100k points):

bfb -n 300000 --max-keywords 1000 --keywords 10000  --search --match-any 900

This is the performance improvement (edit: reran with release build of bfb and perf builds of qdrant)

build final upsert RPS total upsert time median search RPS
dev (perf build) 1685 00:02:57 526
PR (perf build) 3159 00:01:34 525

And, with just 1 keyword per point

bfb -n 300000 --keywords 10000 --search --match-any 900
build final upsert RPS total upsert time median search RPS
dev (perf build) 147385 00:00:02 618
PR (perf build) 161625 00:00:01 628

@coszio coszio requested review from agourlay and generall July 22, 2025 18:47
Copy link
Contributor

coderabbitai bot commented Jul 22, 2025

📝 Walkthrough

Walkthrough

This set of changes standardizes the iterator types across various map index implementations by shifting from iterators over references to iterators over owned PointOffsetType values. In ImmutableMapIndex, MmapMapIndex, and MapIndex, the get_iterator method's return type is updated to yield owned values, and corresponding .copied() calls are removed from iterator usage. In MutableMapIndex, the internal storage is changed from BTreeSet to RoaringBitmap, and all related logic is updated to use bitmap operations and iterators. Public method signatures and internal data structures are updated to reflect these changes.

Estimated code review effort

3 (~45 minutes)

Possibly related PRs

  • [text index] Faster mutable posting list #6685: This PR introduces RoaringBitmap for mutable posting lists, which directly relates to the current changes that switch MutableMapIndex from using BTreeSet to RoaringBitmap for internal storage and iteration.
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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 (1)
lib/segment/src/index/field_index/map_index/mutable_map_index.rs (1)

443-444: Potential overflow when casting from u64 to usize.

RoaringBitmap's len() returns u64, but casting to usize could overflow on 32-bit systems where usize is 32 bits. While Qdrant likely targets 64-bit systems, consider using try_into() for safer conversion or documenting the 64-bit requirement.

-        self.map.get(value).map(|p| p.len() as usize)
+        self.map.get(value).and_then(|p| p.len().try_into().ok())
-        self.map.iter().map(|(k, v)| (k.borrow(), v.len() as usize))
+        self.map.iter().filter_map(|(k, v)| {
+            v.len().try_into().ok().map(|len| (k.borrow(), len))
+        })

Also applies to: 447-448

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fdf445 and a2b26f8.

📒 Files selected for processing (4)
  • lib/segment/src/index/field_index/map_index/immutable_map_index.rs (3 hunks)
  • lib/segment/src/index/field_index/map_index/mmap_map_index.rs (2 hunks)
  • lib/segment/src/index/field_index/map_index/mod.rs (8 hunks)
  • lib/segment/src/index/field_index/map_index/mutable_map_index.rs (5 hunks)
🧠 Learnings (5)
📓 Common learnings
Learnt from: generall
PR: qdrant/qdrant#6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.
lib/segment/src/index/field_index/map_index/mmap_map_index.rs (7)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/iterator.rs:0-0
Timestamp: 2025-05-26T14:50:13.183Z
Learning: The advance_until_greater_or_equal method in PostingIterator in lib/posting_list/src/iterator.rs is intentionally designed with different semantics than next() and can output the same value multiple times. This is intended behavior for a search method that finds the first element with ID >= target_id.

Learnt from: coszio
PR: #6528
File: lib/posting_list/src/view.rs:118-118
Timestamp: 2025-05-19T14:40:20.068Z
Learning: In the bitpacking crate, the BitPacker::decompress_strictly_sorted function takes an Option as its first parameter, which means using checked_sub(1) without unwrapping is intentional and correct.

Learnt from: generall
PR: #6323
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:12-16
Timestamp: 2025-04-07T23:31:22.515Z
Learning: The populate() method in QuantizedMmapStorage correctly returns void instead of a Result because it directly implements the Madviseable trait which defines populate(&self) without a return type. Adding an unnecessary Ok(()) return would trigger Clippy warnings about unnecessary Result wrapping.

Learnt from: coszio
PR: #6609
File: lib/gridstore/src/blob.rs:46-59
Timestamp: 2025-06-02T18:10:47.203Z
Learning: In the Qdrant codebase, zerocopy crate is extensively used for safe byte-level operations across GPU operations, HNSW indices, memory-mapped structures, and serialization. When implementing Blob trait for generic Vec, using zerocopy's FromBytes and IntoBytes traits is preferred over size_of::() because it guarantees memory layout equals byte representation, making serialization safe and correct.

Learnt from: JojiiOfficial
PR: #5951
File: lib/segment/src/index/field_index/map_index/mmap_map_index.rs:225-242
Timestamp: 2025-03-12T09:33:16.728Z
Learning: In Rust, std::mem::size_of_val(slice) correctly calculates the total size of a slice (equivalent to slice.len() * std::mem::size_of::<T>()). Using manual calculation with slice.len() * std::mem::size_of::<T>() should be avoided as it triggers the Clippy lint "manual_slice_size_calculation".

lib/segment/src/index/field_index/map_index/mod.rs (5)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/iterator.rs:0-0
Timestamp: 2025-05-26T14:50:13.183Z
Learning: The advance_until_greater_or_equal method in PostingIterator in lib/posting_list/src/iterator.rs is intentionally designed with different semantics than next() and can output the same value multiple times. This is intended behavior for a search method that finds the first element with ID >= target_id.

Learnt from: JojiiOfficial
PR: #5951
File: lib/api/src/grpc/qdrant.rs:6493-6496
Timestamp: 2025-03-12T09:25:16.340Z
Learning: The payload_index_io_read field in HardwareUsage was introduced in PR #5951 and is safe to modify as it was not used in production before this PR.

Learnt from: IvanPleshkov
PR: #6663
File: lib/quantization/src/encoded_vectors_binary.rs:270-276
Timestamp: 2025-06-18T09:23:14.977Z
Learning: Pattern matching on &Option<T> with Some(x) is valid and idiomatic in Rust. The bound variable x will have type &T and can be used to access fields through automatic dereferencing. No need to use .as_ref() in such cases.

lib/segment/src/index/field_index/map_index/immutable_map_index.rs (6)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/iterator.rs:0-0
Timestamp: 2025-05-26T14:50:13.183Z
Learning: The advance_until_greater_or_equal method in PostingIterator in lib/posting_list/src/iterator.rs is intentionally designed with different semantics than next() and can output the same value multiple times. This is intended behavior for a search method that finds the first element with ID >= target_id.

Learnt from: timvisee
PR: #6444
File: lib/segment/src/index/field_index/map_index/immutable_map_index.rs:373-375
Timestamp: 2025-05-08T10:34:42.223Z
Learning: In the Qdrant codebase, storage backends may have different error handling approaches. Specifically, the mmap variant of remove_point method does not return errors, while the RocksDB variant does propagate errors using the ? operator. These differences are intentional by design.

Learnt from: timvisee
PR: #6150
File: lib/segment/src/id_tracker/mutable_id_tracker.rs:62-71
Timestamp: 2025-03-12T16:23:44.715Z
Learning: In the MutableIdTracker implementation in Qdrant, the error case where versions file exists but mappings file is missing is considered a system-level error (potential corruption) rather than a user error, and is handled via logging and debug assertions without surfacing user-facing errors.

Learnt from: coszio
PR: #6528
File: lib/posting_list/src/view.rs:118-118
Timestamp: 2025-05-19T14:40:20.068Z
Learning: In the bitpacking crate, the BitPacker::decompress_strictly_sorted function takes an Option as its first parameter, which means using checked_sub(1) without unwrapping is intentional and correct.

lib/segment/src/index/field_index/map_index/mutable_map_index.rs (8)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/builder.rs:63-67
Timestamp: 2025-05-26T14:47:23.505Z
Learning: In the posting_list crate's PostingChunk struct, avoid adding extra fields like storing computed chunk_bits to prevent struct bloat and maintain mmap compatibility. The bit calculations from offsets are inexpensive compared to the memory and compatibility benefits of keeping the struct minimal.

Learnt from: generall
PR: #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.

Learnt from: timvisee
PR: #6644
File: lib/gridstore/src/blob.rs:41-57
Timestamp: 2025-06-06T07:52:38.478Z
Learning: In the Blob trait implementations for gridstore (lib/gridstore/src/blob.rs), the maintainer prefers using assert! for input validation over Result-based error handling, as they don't expect invalid inputs in real scenarios and changing to Result would require updating the entire trait interface.

Learnt from: timvisee
PR: #6444
File: lib/segment/src/index/field_index/map_index/immutable_map_index.rs:373-375
Timestamp: 2025-05-08T10:34:42.223Z
Learning: In the Qdrant codebase, storage backends may have different error handling approaches. Specifically, the mmap variant of remove_point method does not return errors, while the RocksDB variant does propagate errors using the ? operator. These differences are intentional by design.

Learnt from: coszio
PR: #6609
File: lib/gridstore/src/blob.rs:46-59
Timestamp: 2025-06-02T18:10:47.203Z
Learning: In the Qdrant codebase, zerocopy crate is extensively used for safe byte-level operations across GPU operations, HNSW indices, memory-mapped structures, and serialization. When implementing Blob trait for generic Vec, using zerocopy's FromBytes and IntoBytes traits is preferred over size_of::() because it guarantees memory layout equals byte representation, making serialization safe and correct.

Learnt from: coszio
PR: #6528
File: lib/posting_list/src/view.rs:118-118
Timestamp: 2025-05-19T14:40:20.068Z
Learning: In the bitpacking crate, the BitPacker::decompress_strictly_sorted function takes an Option as its first parameter, which means using checked_sub(1) without unwrapping is intentional and correct.

🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: generall
PR: qdrant/qdrant#6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.
lib/segment/src/index/field_index/map_index/mmap_map_index.rs (7)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/iterator.rs:0-0
Timestamp: 2025-05-26T14:50:13.183Z
Learning: The advance_until_greater_or_equal method in PostingIterator in lib/posting_list/src/iterator.rs is intentionally designed with different semantics than next() and can output the same value multiple times. This is intended behavior for a search method that finds the first element with ID >= target_id.

Learnt from: coszio
PR: #6528
File: lib/posting_list/src/view.rs:118-118
Timestamp: 2025-05-19T14:40:20.068Z
Learning: In the bitpacking crate, the BitPacker::decompress_strictly_sorted function takes an Option as its first parameter, which means using checked_sub(1) without unwrapping is intentional and correct.

Learnt from: generall
PR: #6323
File: lib/segment/src/vector_storage/quantized/quantized_mmap_storage.rs:12-16
Timestamp: 2025-04-07T23:31:22.515Z
Learning: The populate() method in QuantizedMmapStorage correctly returns void instead of a Result because it directly implements the Madviseable trait which defines populate(&self) without a return type. Adding an unnecessary Ok(()) return would trigger Clippy warnings about unnecessary Result wrapping.

Learnt from: coszio
PR: #6609
File: lib/gridstore/src/blob.rs:46-59
Timestamp: 2025-06-02T18:10:47.203Z
Learning: In the Qdrant codebase, zerocopy crate is extensively used for safe byte-level operations across GPU operations, HNSW indices, memory-mapped structures, and serialization. When implementing Blob trait for generic Vec, using zerocopy's FromBytes and IntoBytes traits is preferred over size_of::() because it guarantees memory layout equals byte representation, making serialization safe and correct.

Learnt from: JojiiOfficial
PR: #5951
File: lib/segment/src/index/field_index/map_index/mmap_map_index.rs:225-242
Timestamp: 2025-03-12T09:33:16.728Z
Learning: In Rust, std::mem::size_of_val(slice) correctly calculates the total size of a slice (equivalent to slice.len() * std::mem::size_of::<T>()). Using manual calculation with slice.len() * std::mem::size_of::<T>() should be avoided as it triggers the Clippy lint "manual_slice_size_calculation".

lib/segment/src/index/field_index/map_index/mod.rs (5)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/iterator.rs:0-0
Timestamp: 2025-05-26T14:50:13.183Z
Learning: The advance_until_greater_or_equal method in PostingIterator in lib/posting_list/src/iterator.rs is intentionally designed with different semantics than next() and can output the same value multiple times. This is intended behavior for a search method that finds the first element with ID >= target_id.

Learnt from: JojiiOfficial
PR: #5951
File: lib/api/src/grpc/qdrant.rs:6493-6496
Timestamp: 2025-03-12T09:25:16.340Z
Learning: The payload_index_io_read field in HardwareUsage was introduced in PR #5951 and is safe to modify as it was not used in production before this PR.

Learnt from: IvanPleshkov
PR: #6663
File: lib/quantization/src/encoded_vectors_binary.rs:270-276
Timestamp: 2025-06-18T09:23:14.977Z
Learning: Pattern matching on &Option<T> with Some(x) is valid and idiomatic in Rust. The bound variable x will have type &T and can be used to access fields through automatic dereferencing. No need to use .as_ref() in such cases.

lib/segment/src/index/field_index/map_index/immutable_map_index.rs (6)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/iterator.rs:0-0
Timestamp: 2025-05-26T14:50:13.183Z
Learning: The advance_until_greater_or_equal method in PostingIterator in lib/posting_list/src/iterator.rs is intentionally designed with different semantics than next() and can output the same value multiple times. This is intended behavior for a search method that finds the first element with ID >= target_id.

Learnt from: timvisee
PR: #6444
File: lib/segment/src/index/field_index/map_index/immutable_map_index.rs:373-375
Timestamp: 2025-05-08T10:34:42.223Z
Learning: In the Qdrant codebase, storage backends may have different error handling approaches. Specifically, the mmap variant of remove_point method does not return errors, while the RocksDB variant does propagate errors using the ? operator. These differences are intentional by design.

Learnt from: timvisee
PR: #6150
File: lib/segment/src/id_tracker/mutable_id_tracker.rs:62-71
Timestamp: 2025-03-12T16:23:44.715Z
Learning: In the MutableIdTracker implementation in Qdrant, the error case where versions file exists but mappings file is missing is considered a system-level error (potential corruption) rather than a user error, and is handled via logging and debug assertions without surfacing user-facing errors.

Learnt from: coszio
PR: #6528
File: lib/posting_list/src/view.rs:118-118
Timestamp: 2025-05-19T14:40:20.068Z
Learning: In the bitpacking crate, the BitPacker::decompress_strictly_sorted function takes an Option as its first parameter, which means using checked_sub(1) without unwrapping is intentional and correct.

lib/segment/src/index/field_index/map_index/mutable_map_index.rs (8)

Learnt from: generall
PR: #6766
File: lib/segment/src/index/field_index/full_text_index/inverted_index/mutable_inverted_index.rs:274-282
Timestamp: 2025-06-28T19:25:53.448Z
Learning: In the full text inverted index implementations (MutableInvertedIndex, ImmutableInvertedIndex, MmapInvertedIndex), there's an important distinction between TokenId and PointOffsetType: TokenId represents a token/word identifier used to index into postings lists, while PointOffsetType represents a point/document identifier. The iter_point_ids method should take a TokenId and return an iterator over PointOffsetType values (points that contain that token), not the other way around.

Learnt from: generall
PR: #6854
File: lib/segment/src/index/query_estimator.rs:320-327
Timestamp: 2025-07-11T11:35:21.549Z
Learning: In test code for Qdrant's query estimator (lib/segment/src/index/query_estimator.rs), simplified ID resolution logic using id.to_string().parse().unwrap() is acceptable for testing purposes and doesn't need to match production code's id_tracker.internal_id() approach. Test code can use mock implementations that serve the testing goals.

Learnt from: coszio
PR: #6565
File: lib/posting_list/src/builder.rs:63-67
Timestamp: 2025-05-26T14:47:23.505Z
Learning: In the posting_list crate's PostingChunk struct, avoid adding extra fields like storing computed chunk_bits to prevent struct bloat and maintain mmap compatibility. The bit calculations from offsets are inexpensive compared to the memory and compatibility benefits of keeping the struct minimal.

Learnt from: generall
PR: #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.

Learnt from: timvisee
PR: #6644
File: lib/gridstore/src/blob.rs:41-57
Timestamp: 2025-06-06T07:52:38.478Z
Learning: In the Blob trait implementations for gridstore (lib/gridstore/src/blob.rs), the maintainer prefers using assert! for input validation over Result-based error handling, as they don't expect invalid inputs in real scenarios and changing to Result would require updating the entire trait interface.

Learnt from: timvisee
PR: #6444
File: lib/segment/src/index/field_index/map_index/immutable_map_index.rs:373-375
Timestamp: 2025-05-08T10:34:42.223Z
Learning: In the Qdrant codebase, storage backends may have different error handling approaches. Specifically, the mmap variant of remove_point method does not return errors, while the RocksDB variant does propagate errors using the ? operator. These differences are intentional by design.

Learnt from: coszio
PR: #6609
File: lib/gridstore/src/blob.rs:46-59
Timestamp: 2025-06-02T18:10:47.203Z
Learning: In the Qdrant codebase, zerocopy crate is extensively used for safe byte-level operations across GPU operations, HNSW indices, memory-mapped structures, and serialization. When implementing Blob trait for generic Vec, using zerocopy's FromBytes and IntoBytes traits is preferred over size_of::() because it guarantees memory layout equals byte representation, making serialization safe and correct.

Learnt from: coszio
PR: #6528
File: lib/posting_list/src/view.rs:118-118
Timestamp: 2025-05-19T14:40:20.068Z
Learning: In the bitpacking crate, the BitPacker::decompress_strictly_sorted function takes an Option as its first parameter, which means using checked_sub(1) without unwrapping is intentional and correct.

⏰ 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: test-consistency
  • GitHub Check: test-consensus-compose
  • GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
  • GitHub Check: integration-tests-consensus
  • GitHub Check: integration-tests
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: rust-tests (macos-latest)
  • GitHub Check: storage-compat-test
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: lint
🔇 Additional comments (8)
lib/segment/src/index/field_index/map_index/mmap_map_index.rs (1)

325-344: LGTM! Iterator standardization implemented correctly.

The change to return IdIter<'_> yielding owned PointOffsetType values is consistent with the PR's goal of standardizing iterator types across map index implementations. The addition of .copied() after filtering is necessary and correct for the type conversion.

lib/segment/src/index/field_index/map_index/mod.rs (2)

237-243: LGTM! Consistent iterator type change across implementations.

The get_iterator method signature change to return IdIter<'_> is correctly implemented across all map index variants (Mutable, Immutable, Mmap).


493-494: Correctly removed .copied() calls after iterator standardization.

All .copied() calls have been appropriately removed since get_iterator now returns an iterator yielding owned values directly. This simplifies the code and reduces unnecessary operations.

Also applies to: 750-751, 759-760, 903-904, 920-921, 943-944, 1096-1097, 1111-1112

lib/segment/src/index/field_index/map_index/immutable_map_index.rs (2)

21-21: LGTM! Iterator type standardization in immutable index.

The import change and method signature updates correctly implement the standardized iterator type that yields owned values.

Also applies to: 513-521


522-542: Correct implementation of owned value iterator.

The get_iterator method properly handles the conversion from references to owned values with the dereference operation *idx on line 537. The empty iterator fallback also correctly returns the expected type.

lib/segment/src/index/field_index/map_index/mutable_map_index.rs (3)

2-2: Successfully migrated from BTreeSet to RoaringBitmap.

The import changes and type migration align with the PR objectives to improve performance for scenarios with many values per point. RoaringBitmap provides better memory efficiency and performance for dense sets of integers.

Also applies to: 12-12, 18-18, 42-42


298-299: Correct API usage for RoaringBitmap remove operation.

The change from vals.remove(&idx) to vals.remove(idx) is correct - RoaringBitmap's remove method takes the value directly, not a reference.


453-454: Clean iterator implementation with RoaringBitmap.

The iterator construction is correctly implemented, leveraging RoaringBitmap's built-in iterator which yields owned u32 values (PointOffsetType).

Also applies to: 456-461

@generall
Copy link
Member

Could you please also include benchmark on "normal" workloads, even if they are not improving

@coszio coszio marked this pull request as draft July 23, 2025 18:20
@coszio
Copy link
Contributor Author

coszio commented Jul 23, 2025

@generall I made a mistake when benchmarking. When compared to perf build and all feature flags on, the difference is not as dramatic. I have updated the description accordingly.

Still increases upsert performance.

@coszio coszio marked this pull request as ready for review July 23, 2025 20:06
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.

👏

@timvisee timvisee merged commit ba059b3 into dev Jul 29, 2025
16 checks passed
@timvisee timvisee deleted the roaring-mutable-map-index branch July 29, 2025 10:51
timvisee pushed a commit that referenced this pull request Aug 11, 2025
@timvisee timvisee mentioned this pull request Aug 11, 2025
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.

4 participants