Repository: qdrant/qdrant
Pull request #6357: [query api] don't propagate offset into prefetches
Author: login: coszio
Created: 2025-04-09T23:10:39Z | Merged: 2025-04-11T12:11:21Z

One overlooked quirk when developing prefetches is that we should not propagate the main query offset into them. 

In general terms, offsets are not compatible with prefetches. Let's look at one example:

Imagine we have a vector search prefetch (limit=5), and an order_by main query (limit=3). For simplicity, let's assume letters are point IDs, and numbers are the order_by values
```
with offset 0
prefetch candidates: [a: 5, b: 8, c: 3, d: 9, e: 2]

main query ordering: [e: 2, c: 3, a: 5, b: 8, d: 9]
main query results:  [e: 2, c: 3, a: 5]

----------------------------------------
with offset 3 (propagated)
prefetch candidates: [a: 5, b: 8, c: 3, d: 9, e: 2, f: 6, g: 1, h: 4]

main query ordering: [g: 1, e: 2, c: 3, h: 4, a: 5, f: 6, b: 8, d: 9]
main query results:  [h: 4, a: 5, f: 6] 
```

As you can see, the second query, with an offset is not consistent with the first one. Result `g` will never appear.


With this PR, offset is not propagated anymore, so the limit in the prefetch is the total amount of offsetting that can happen for the request. If the user keeps paginating with offset and a fixed limit, results will be stable, but will eventually lead to an empty result.



Diff:
diff --git a/lib/collection/src/operations/universal_query/planned_query.rs b/lib/collection/src/operations/universal_query/planned_query.rs
index 6fc05f8a476..d92b614f93b 100644
--- a/lib/collection/src/operations/universal_query/planned_query.rs
+++ b/lib/collection/src/operations/universal_query/planned_query.rs
@@ -93,9 +93,6 @@ impl PlannedQuery {
             with_payload,
             params,
         } = request;
-        // Final offset is handled at collection level
-        let limit = limit + offset;
-
         let merge_plan = if !prefetches.is_empty() {
             if depth > MAX_PREFETCH_DEPTH {
                 return Err(CollectionError::bad_request(format!(
@@ -108,13 +105,8 @@ impl PlannedQuery {
             })?;
 
             if rescore.needs_intermediate_results() {
-                let sources = recurse_prefetches(
-                    &mut self.searches,
-                    &mut self.scrolls,
-                    prefetches,
-                    offset,
-                    &filter,
-                )?;
+                let sources =
+                    recurse_prefetches(&mut self.searches, &mut self.scrolls, prefetches, &filter)?;
 
                 let merge_plan = MergePlan {
                     sources,
@@ -129,13 +121,8 @@ impl PlannedQuery {
                     with_payload,
                 }
             } else {
-                let sources = recurse_prefetches(
-                    &mut self.searches,
-                    &mut self.scrolls,
-                    prefetches,
-                    offset,
-                    &filter,
-                )?;
+                let sources =
+                    recurse_prefetches(&mut self.searches, &mut self.scrolls, prefetches, &filter)?;
 
                 let merge_plan = MergePlan {
                     sources,
@@ -154,6 +141,14 @@ impl PlannedQuery {
                 }
             }
         } else {
+            // Only increase the limit with the offset when there are no prefetches
+            //
+            // Reason for this is because there is no guarantee of stability of results when prefetches and main query are not
+            // strongly correlated.
+            // For example: if prefetch is a knn search, and main query is an `order_by`, propagating the offset for trying to
+            // get a second page will just increase the pool of candidates for `order_by`. The second page in this case will be
+            // invalid because it will cut the offset on a different total ordering.
+            let limit = limit + offset;
             let sources = match query {
                 Some(ScoringQuery::Vector(query)) => {
                     // Everything should come from 1 core search
@@ -254,7 +249,6 @@ fn recurse_prefetches(
     core_searches: &mut Vec<CoreSearchRequest>,
     scrolls: &mut Vec<QueryScrollRequestInternal>,
     prefetches: Vec<ShardPrefetch>,
-    root_offset: usize, // Offset is added to all prefetches, so we make sure we have enough
     propagate_filter: &Option<Filter>, // Global filter to apply to all prefetches
 ) -> CollectionResult<Vec<Source>> {
     let mut sources = Vec::with_capacity(prefetches.len());
@@ -266,22 +260,18 @@ fn recurse_prefetches(
         let ShardPrefetch {
             prefetches,
             query,
-            limit: prefetch_limit,
+            limit,
             params,
             filter,
             score_threshold,
         } = prefetch;
 
-        // Offset is replicated at each step from the root to the leaves
-        let limit = prefetch_limit + root_offset;
-
         // Filters are propagated into the leaves
         let filter = Filter::merge_opts(propagate_filter.clone(), filter);
 
         let source = if !prefetches.is_empty() {
             // This has nested prefetches. Recurse into them
-            let inner_sources =
-                recurse_prefetches(core_searches, scrolls, prefetches, root_offset, &filter)?;
+            let inner_sources = recurse_prefetches(core_searches, scrolls, prefetches, &filter)?;
 
             let rescore = query.ok_or_else(|| {
                 CollectionError::bad_request("cannot have prefetches without a query".to_string())
@@ -772,7 +762,7 @@ mod tests {
                 ),),
                 filter: dummy_filter,
                 params: dummy_params,
-                limit: 37 + 49, // limit + offset
+                limit: 37,
                 offset: 0,
                 with_payload: Some(WithPayloadInterface::Bool(false)),
                 with_vector: Some(WithVector::Bool(false)),
diff --git a/lib/common/memory/src/madvise.rs b/lib/common/memory/src/madvise.rs
index 722f132b43a..8655f4b5ed2 100644
--- a/lib/common/memory/src/madvise.rs
+++ b/lib/common/memory/src/madvise.rs
@@ -1,7 +1,6 @@
 //! Platform-independent abstractions over [`memmap2::Mmap::advise`]/[`memmap2::MmapMut::advise`]
 //! and [`memmap2::Advice`].
 
-use std::fs::File;
 use std::hint::black_box;
 use std::io;
 use std::num::Wrapping;
@@ -187,6 +186,7 @@ pub fn clear_disk_cache(file_path: &Path) -> io::Result<()> {
         target_env = "uclibc",
     ))]
     {
+        use std::fs::File;
         use std::os::fd::AsRawFd as _;
 
         use nix::fcntl;
diff --git a/tests/openapi/test_query_full.py b/tests/openapi/test_query_full.py
index 16fa22945aa..96b8c76a968 100644
--- a/tests/openapi/test_query_full.py
+++ b/tests/openapi/test_query_full.py
@@ -1441,8 +1441,8 @@ def test_random_rescore_with_offset(collection_name):
     random_result = response.json()["result"]["points"]
     assert len(random_result) == 1
     assert random_result[0]["id"] == 1
-    
-    # assert offset is propagated to prefetch
+
+    # assert offset is NOT propagated to prefetch
     seen = set()
     for _ in range(100):
         response = request_with_validation(
@@ -1450,7 +1450,7 @@ def test_random_rescore_with_offset(collection_name):
             method="POST",
             path_params={"collection_name": collection_name},
             body={
-                "prefetch": { "limit": 1 },
+                "prefetch": { "limit": 2 },
                 "query": {"sample": "random"},
                 "offset": 1,
             },
@@ -1458,14 +1458,14 @@ def test_random_rescore_with_offset(collection_name):
         assert response.ok, response.json()
         random_result = response.json()["result"]["points"]
         assert len(random_result) == 1
-    
+
         seen.add(random_result[0]["id"])
         if seen == {1, 2}:
             return
-    
-    # Although prefetch limit is 1, offset should be propagated, so randomness is applied to points 1 and 2.
+
+    # Offset is not propagated to prefetch, since prefetch without a query is a scroll, random sampling is applied to points 1 and 2.
     # By this point we should've seen both points.
-    assert False, f"after 100 tries, `seen` is expected to be {{1, 2}}, but it was {seen}"
+    raise AssertionError(f"after 100 tries, `seen` is expected to be {{1, 2}}, but it was {seen}")
 
 
 @pytest.mark.parametrize("query_filter", [None, *get_uuid_index_filters()])


Reviews:
user: login: agourlay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T08:09:56Z
user: login: timvisee
body: Could you add a test that asserts this behavior?

Prefetch with and without an offset, and assert that we eventually run out of results. That'll ensure the behavior we document actually happens in practice.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T09:03:05Z
user: login: timvisee
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T09:05:41Z
user: login: agourlay
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-11T11:03:30Z
user: login: timvisee
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-11T11:42:33Z

Review comments:
diff_hunk: @@ -1,7 +1,6 @@
 //! Platform-independent abstractions over [`memmap2::Mmap::advise`]/[`memmap2::MmapMut::advise`]
 //! and [`memmap2::Advice`].
 
-use std::fs::File;
path: lib/common/memory/src/madvise.rs
user: login: agourlay
body: maybe unnecessary on Mac but breaks the build for everyone else :)
created_at: 2025-04-10T08:09:56Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,6 @@
 //! Platform-independent abstractions over [`memmap2::Mmap::advise`]/[`memmap2::MmapMut::advise`]
 //! and [`memmap2::Advice`].
 
-use std::fs::File;
path: lib/common/memory/src/madvise.rs
user: login: timvisee
body: It was indeed 'unused' on macOS because it's only usage is gated in

```
    #[cfg(any(
        target_os = "linux",
        target_os = "freebsd",
        target_os = "android",
        target_os = "fuchsia",
        target_os = "emscripten",
        target_os = "wasi",
        target_env = "uclibc",
    ))]
```

I've moved the import into that scope to resolve the problem.

https://github.com/qdrant/qdrant/pull/6357/commits/72cbf76f39a5eea3cc120b5c6d6094e9c827b0a8
created_at: 2025-04-10T09:05:40Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-09T23:12:43Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

The changes update the query handling and memory management modules. In the query processing logic, the adjustment of the limit by adding the offset has been removed from the main query code when prefetches are involved. A comment now clarifies that the limit should only be increased by the offset in specific cases without prefetches. Additionally, the recursive prefetch function has been modified by removing the redundant parameter used to pass offset adjustments, simplifying its signature and internal logic. In the memory management module, an unused import for handling file-related operations has been removed and re-scoped to be used only within the `clear_disk_cache` function. In the testing module, assertions and comments regarding the offset parameter and prefetch functionality have been updated for clarity. These changes streamline the code by reducing unnecessary computations and dependencies while maintaining its overall structure and functionality.

</details>

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c5fbbad6830d5309956cdc27be53ae0b98cafe51 and 58c26fa5c1f1b89530e5131326eb05846b1cfdaf.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `lib/collection/src/operations/universal_query/planned_query.rs` (5 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* lib/collection/src/operations/universal_query/planned_query.rs

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (13)</summary>

* GitHub Check: Basic TLS/HTTPS tests
* GitHub Check: test-snapshot-operations-s3-minio
* GitHub Check: test-shard-snapshot-api-s3-minio
* GitHub Check: test-low-resources
* GitHub Check: test-consistency
* GitHub Check: test-consensus-compose
* GitHub Check: test (macos-latest)
* GitHub Check: test-consensus
* GitHub Check: test (windows-latest)
* GitHub Check: lint
* GitHub Check: test
* GitHub Check: test
* GitHub Check: test (ubuntu-latest)

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=qdrant/qdrant&utm_content=6357):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `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 generate unit testing code for this file.`
  -	`@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 generate unit testing code.`
  - `@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.`

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](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: coszio
created_at: 2025-04-10T16:27:58Z
body: @timvisee There was actually a failing test (https://github.com/qdrant/qdrant/pull/6357/commits/c5fbbad6830d5309956cdc27be53ae0b98cafe51) that I have now edited to assert the new behavior.
author_association: CONTRIBUTOR
user: login: agourlay
created_at: 2025-04-11T12:27:45Z
body: This may break the local mode congruence tests :crystal_ball: 
author_association: MEMBER