Repository: qdrant/qdrant
Pull request #6478: Don't batch queries if having more segments than search threads
Author: login: timvisee
Created: 2025-05-01T10:14:39Z | Merged: 2025-05-07T10:57:49Z

Fixes <https://github.com/qdrant/qdrant/issues/6423> regression
Extension of <https://github.com/qdrant/qdrant/pull/6326>

While the condition change is not perfect, it does help significantly with the issue as described above.

This just prevents batching for a specific case that is known to perform bad: when having a high number of segments versus search threads.

### All Submissions:

* [x] Contributions should target the `dev` branch. Did you create your branch from `dev`?
* [x] Have you followed the guidelines in our Contributing document?
* [x] Have you checked to ensure there aren't other open [Pull Requests](../../../pulls) for the same update/change?

<!-- You can erase any parts of this template not applicable to your Pull Request. -->

### New Feature Submissions:

1. [x] Does your submission pass tests?
2. [x] Have you formatted your code locally using `cargo +nightly fmt --all` command prior to submission?
3. [x] Have you checked your code using `cargo clippy --all --all-features` command?


Diff:
diff --git a/lib/collection/src/operations/shared_storage_config.rs b/lib/collection/src/operations/shared_storage_config.rs
index fe166fb2fae..3f8319c931c 100644
--- a/lib/collection/src/operations/shared_storage_config.rs
+++ b/lib/collection/src/operations/shared_storage_config.rs
@@ -31,6 +31,7 @@ pub struct SharedStorageConfig {
     pub outgoing_shard_transfers_limit: Option<usize>,
     pub snapshots_path: String,
     pub snapshots_config: SnapshotsConfig,
+    pub search_thread_count: usize,
 }
 
 impl Default for SharedStorageConfig {
@@ -48,6 +49,7 @@ impl Default for SharedStorageConfig {
             outgoing_shard_transfers_limit: DEFAULT_IO_SHARD_TRANSFER_LIMIT,
             snapshots_path: DEFAULT_SNAPSHOTS_PATH.to_string(),
             snapshots_config: default::Default::default(),
+            search_thread_count: common::defaults::search_thread_count(common::cpu::get_num_cpus()),
         }
     }
 }
@@ -67,6 +69,7 @@ impl SharedStorageConfig {
         outgoing_shard_transfers_limit: Option<usize>,
         snapshots_path: String,
         snapshots_config: SnapshotsConfig,
+        search_thread_count: usize,
     ) -> Self {
         let update_queue_size = update_queue_size.unwrap_or(match node_type {
             NodeType::Normal => DEFAULT_UPDATE_QUEUE_SIZE,
@@ -85,6 +88,7 @@ impl SharedStorageConfig {
             outgoing_shard_transfers_limit,
             snapshots_path,
             snapshots_config,
+            search_thread_count,
         }
     }
 }
diff --git a/lib/collection/src/shards/local_shard/search.rs b/lib/collection/src/shards/local_shard/search.rs
index 4a4b393e308..741ca852fa4 100644
--- a/lib/collection/src/shards/local_shard/search.rs
+++ b/lib/collection/src/shards/local_shard/search.rs
@@ -37,10 +37,25 @@ impl LocalShard {
             return Ok(vec![]);
         }
 
+        let skip_batching = if core_request.searches.len() <= CHUNK_SIZE {
+            // Don't batch if we have few searches, prevents cloning request
+            true
+        } else if self.segments.read().len() > self.shared_storage_config.search_thread_count {
+            // Don't batch if we have more segments than search threads
+            // Not a perfect condition, but it helps to prevent consuming a lot of search threads
+            // if the number of segments is large
+            // Note: search threads are shared with all other search threads on this Qdrant
+            // instance, and other shards also have segments. For simplicity this only considers
+            // the global search thread count and local segment count.
+            // See: <https://github.com/qdrant/qdrant/pull/6478>
+            true
+        } else {
+            false
+        };
+
         let is_stopped_guard = StoppingGuard::new();
 
-        // Don't batch if we have few searches, prevents cloning request
-        if core_request.searches.len() <= CHUNK_SIZE {
+        if skip_batching {
             return self
                 .do_search_impl(
                     core_request,
diff --git a/lib/common/common/src/defaults.rs b/lib/common/common/src/defaults.rs
index 14348979daf..c9249c2fe77 100644
--- a/lib/common/common/src/defaults.rs
+++ b/lib/common/common/src/defaults.rs
@@ -59,3 +59,26 @@ pub fn thread_count_for_hnsw(num_cpu: usize) -> usize {
         65.. => 16,
     }
 }
+
+/// Number of search threads to use in the search runtime.
+///
+/// Dynamic based on CPU size.
+#[inline(always)]
+pub fn search_thread_count(max_search_threads: usize) -> usize {
+    if max_search_threads != 0 {
+        return max_search_threads;
+    }
+
+    // At least one thread, but not more than number of CPUs - 1 if there are more than 2 CPU
+    // Example:
+    // Num CPU = 1 -> 1 thread
+    // Num CPU = 2 -> 2 thread - if we use one thread with 2 cpus, its too much un-utilized resources
+    // Num CPU = 3 -> 2 thread
+    // Num CPU = 4 -> 3 thread
+    // Num CPU = 5 -> 4 thread
+    match cpu::get_num_cpus() {
+        0..=1 => 1,
+        2 => 2,
+        num_cpu @ 3.. => num_cpu - 1,
+    }
+}
diff --git a/lib/storage/src/types.rs b/lib/storage/src/types.rs
index c703278adf1..d4cdfbb49cd 100644
--- a/lib/storage/src/types.rs
+++ b/lib/storage/src/types.rs
@@ -123,6 +123,7 @@ impl StorageConfig {
             self.performance.outgoing_shard_transfers_limit,
             self.snapshots_path.clone(),
             self.snapshots_config.clone(),
+            common::defaults::search_thread_count(self.performance.max_search_threads),
         )
     }
 }
diff --git a/src/common/helpers.rs b/src/common/helpers.rs
index ab7620f55d5..7ff33c33b77 100644
--- a/src/common/helpers.rs
+++ b/src/common/helpers.rs
@@ -18,28 +18,10 @@ pub struct LocksOption {
 }
 
 pub fn create_search_runtime(max_search_threads: usize) -> io::Result<Runtime> {
-    let mut search_threads = max_search_threads;
-
-    if search_threads == 0 {
-        let num_cpu = common::cpu::get_num_cpus();
-        // At least one thread, but not more than number of CPUs - 1 if there are more than 2 CPU
-        // Example:
-        // Num CPU = 1 -> 1 thread
-        // Num CPU = 2 -> 2 thread - if we use one thread with 2 cpus, its too much un-utilized resources
-        // Num CPU = 3 -> 2 thread
-        // Num CPU = 4 -> 3 thread
-        // Num CPU = 5 -> 4 thread
-        search_threads = match num_cpu {
-            0 => 1,
-            1 => 1,
-            2 => 2,
-            _ => num_cpu - 1,
-        };
-    }
-
+    let num_threads = common::defaults::search_thread_count(max_search_threads);
     runtime::Builder::new_multi_thread()
-        .worker_threads(search_threads)
-        .max_blocking_threads(search_threads)
+        .worker_threads(num_threads)
+        .max_blocking_threads(num_threads)
         .enable_all()
         .thread_name_fn(|| {
             static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR modifies the threading behavior during search operations to avoid batching queries when the number of segments exceeds the available search threads, addressing a performance regression. Changes include updating runtime thread settings in helpers, adding a new defaults function and associated logic, revising the search batching condition in local shard search, and propagating the search thread count into storage configuration.

### Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| src/common/helpers.rs | Refactored runtime creation to use a unified thread count from defaults. |
| lib/storage/src/types.rs | Updated storage config to pass search thread count from defaults. |
| lib/common/common/src/defaults.rs | Introduced a new function to determine the search thread count based on CPU info and provided max search threads override. |
| lib/collection/src/shards/local_shard/search.rs | Modified batching logic to skip batching when segments exceed the configured search thread count. |
| lib/collection/src/operations/shared_storage_config.rs | Added a new field for search thread count and updated defaults accordingly. |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T10:32:57Z
user: login: generall
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-01T10:50:16Z

Review comments:
diff_hunk: @@ -37,10 +37,25 @@ impl LocalShard {
             return Ok(vec![]);
         }
 
+        let skip_batching = if core_request.searches.len() <= CHUNK_SIZE {
+            // Don't batch if we have few searches, prevents cloning request
+            true
+        } else if self.segments.read().len() > self.shared_storage_config.search_thread_count {
+            // Don't batch if we have more segments than search threads
+            // Not a perfect condition, but it helps to prevent consuming a lot of search threads
+            // if the number of segments is large
+            // Note: search threads are shared with all other search threads on this Qdrant
+            // instance, and other shards also have segments. For simplicity this only considers
+            // the global search thread count and local segment count.
+            // See: <https://github.com/qdrant/qdrant/pull/6478>
+            true
+        } else {
+            false
+        };
path: lib/collection/src/shards/local_shard/search.rs
user: login: Copilot
body: [nitpick] Consider refactoring the nested if-else used to compute 'skip_batching' into a single consolidated expression (e.g. using a logical OR) to improve readability.
```suggestion
        // Don't batch if we have few searches, prevents cloning request
        // Don't batch if we have more segments than search threads
        // Not a perfect condition, but it helps to prevent consuming a lot of search threads
        // if the number of segments is large
        // Note: search threads are shared with all other search threads on this Qdrant
        // instance, and other shards also have segments. For simplicity this only considers
        // the global search thread count and local segment count.
        // See: <https://github.com/qdrant/qdrant/pull/6478>
        let skip_batching = core_request.searches.len() <= CHUNK_SIZE
            || self.segments.read().len() > self.shared_storage_config.search_thread_count;
```
created_at: 2025-05-01T10:32:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,6 +31,7 @@ pub struct SharedStorageConfig {
     pub outgoing_shard_transfers_limit: Option<usize>,
     pub snapshots_path: String,
     pub snapshots_config: SnapshotsConfig,
path: lib/collection/src/operations/shared_storage_config.rs
user: login: Copilot
body: [nitpick] Consider adding an inline comment explaining how 'search_thread_count' is derived and its role in controlling query batching logic.
```suggestion
    pub snapshots_config: SnapshotsConfig,
    /// Number of threads used for search operations.
    /// Derived based on the number of available CPUs to optimize query batching.
```
created_at: 2025-05-01T10:32:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-01T10:22:32Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

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

## Walkthrough

The changes introduce a new mechanism for determining and configuring the number of threads used for search operations across the codebase. A new field, `search_thread_count`, is added to the `SharedStorageConfig` struct and is initialized either by default or via its constructor, using a new helper function, `search_thread_count`, which calculates the thread count based on CPU count and an optional maximum thread limit. The logic for batching search requests in the local shard is updated to consider both the batch size and the number of segments relative to the configured thread count. The logic for creating the search runtime is refactored to delegate thread count calculation to the new helper function. The `StorageConfig` now passes the computed thread count to shared storage configuration, ensuring consistency in thread usage settings throughout the system.

## Assessment against linked issues

| Objective                                                                                  | Addressed | Explanation                                                                                   |
|--------------------------------------------------------------------------------------------|-----------|-----------------------------------------------------------------------------------------------|
| Investigate and mitigate high server load average (LA) after upgrade to v1.14.0 (#6423)    | ✅        |                                                                                               |
| Provide a mechanism to control or limit the number of search threads used (#6423)          | ✅        |                                                                                               |
| Ensure search thread count respects CPU count and user configuration (#6423)               | ✅        |                                                                                               |
| Refactor thread count logic to be consistent and centralized (#6423)                       | ✅        |                                                                                               |

## Suggested reviewers

- generall
- coszio

</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 08e809feb44c81a4d5e6d237c6a2abf74baecbdb and 902168a7f7174fee85c9ce0717b4a5d608d39822.

</details>

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

* `lib/collection/src/operations/shared_storage_config.rs` (4 hunks)
* `lib/collection/src/shards/local_shard/search.rs` (1 hunks)
* `lib/common/common/src/defaults.rs` (1 hunks)
* `lib/storage/src/types.rs` (1 hunks)
* `src/common/helpers.rs` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>lib/storage/src/types.rs (1)</summary><blockquote>

<details>
<summary>lib/common/common/src/defaults.rs (1)</summary>

* `search_thread_count` (67-84)

</details>

</blockquote></details>
<details>
<summary>src/common/helpers.rs (1)</summary><blockquote>

<details>
<summary>lib/common/common/src/defaults.rs (1)</summary>

* `search_thread_count` (67-84)

</details>

</blockquote></details>

</details>

</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: rust-tests (macos-latest)
* GitHub Check: integration-tests-consensus
* GitHub Check: rust-tests (windows-latest)
* GitHub Check: lint
* GitHub Check: rust-tests (ubuntu-latest)
* GitHub Check: integration-tests
* GitHub Check: storage-compat-test

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>lib/common/common/src/defaults.rs (1)</summary>

`63-84`: **Well-designed thread count calculation logic**

The new `search_thread_count` function implements a thoughtful approach to determine the optimal number of search threads based on available CPU resources. The function provides a clear fallback strategy when `max_search_threads` is not specified and properly reserves CPU resources for other tasks when more than 2 CPUs are available.

The implementation:
1. Honors user-provided `max_search_threads` if non-zero
2. Ensures at least one thread is always used
3. Uses both cores when exactly 2 CPUs are available
4. Reserves one CPU for system tasks when 3+ CPUs are available

This change helps address the performance regression in PR #6326 by providing a consistent way to determine thread count across the application.

</details>
<details>
<summary>lib/storage/src/types.rs (1)</summary>

`126-126`: **Properly integrates thread count calculation**

The `to_shared_storage_config` method now correctly passes the calculated thread count to the shared storage configuration using the newly introduced helper function. This ensures that the thread count is consistently determined across the application and properly considered during search operations.

</details>
<details>
<summary>src/common/helpers.rs (1)</summary>

`20-25`: **Improved thread count consistency in runtime configuration**

This change removes duplicated thread calculation logic by leveraging the new centralized `search_thread_count` function. The runtime configuration now uses the same thread count value that's used elsewhere in the system, ensuring consistent resource allocation for search operations.

The refactoring properly configures both worker threads and max blocking threads with the same calculated value.

</details>
<details>
<summary>lib/collection/src/operations/shared_storage_config.rs (3)</summary>

`34-34`: **Appropriate addition of thread count configuration**

Adding the `search_thread_count` field to `SharedStorageConfig` makes this important configuration parameter available throughout the system, particularly to the search implementation that needs to decide when to batch queries.

---

`52-53`: **Good default configuration with dynamic scaling**

The default implementation properly initializes the new field using the centralized helper function, which calculates an appropriate thread count based on available CPU resources.

---

`72-73`: **Constructor properly updated for thread count configuration**

The constructor now accepts and sets the `search_thread_count` parameter, completing the integration of this configuration throughout the system. This enables the application to make decisions about query batching based on the available thread count.



Also applies to: 91-92

</details>
<details>
<summary>lib/collection/src/shards/local_shard/search.rs (2)</summary>

`40-54`: **Good optimization to prevent thread exhaustion**

The addition of the new condition to skip batching when segment count exceeds search thread count is a well-targeted optimization that addresses the regression issue. The comprehensive comments clearly explain the reasoning and trade-offs involved.

This change intelligently prevents the system from consuming excessive search threads in scenarios where there are many segments, which aligns perfectly with the PR objective of fixing the performance regression mentioned in issue #6423.

---

`58-58`: **LGTM - Proper implementation of the new skip_batching logic**

The use of the newly introduced `skip_batching` variable here correctly implements the optimization logic defined above.

</details>

</blockquote></details>

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


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

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<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=6478):

- 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.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/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](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: agourlay
created_at: 2025-05-02T09:06:11Z
body: How did you validate this change?
author_association: MEMBER
user: login: generall
created_at: 2025-05-04T10:59:59Z
body: Discovered another reason to not do mini-batching:

assume we have very large on-disk collection, and we want to do a full-scan over it.

It would be much faster if we go segment-by-segment, computing full batch of points right away. In this case we only need to load from disk once.



 
author_association: MEMBER