Docs for prevent_unoptimized (#2237)

This commit is contained in:
Abdon Pijpelink
2026-03-27 17:36:43 +01:00
committed by GitHub
parent 59d90d9978
commit 0c49e9d0ef
17 changed files with 222 additions and 0 deletions
@@ -0,0 +1 @@
Enable the `prevent_unoptimized` optimizer setting on an existing collection to prevent deferred points from being visible in search results until they are indexed.
@@ -0,0 +1,7 @@
curl -X PATCH http://localhost:6333/collections/{collection_name} \
-H 'Content-Type: application/json' \
--data-raw '{
"optimizers_config": {
"prevent_unoptimized": true
}
}'
@@ -0,0 +1,15 @@
using Qdrant.Client;
using Qdrant.Client.Grpc;
public class Snippet
{
public static async Task Run()
{
var client = new QdrantClient("localhost", 6334); // @hide
await client.UpdateCollectionAsync(
collectionName: "{collection_name}",
optimizersConfig: new OptimizersConfigDiff { PreventUnoptimized = true }
);
}
}
@@ -0,0 +1,9 @@
```bash
curl -X PATCH http://localhost:6333/collections/{collection_name} \
-H 'Content-Type: application/json' \
--data-raw '{
"optimizers_config": {
"prevent_unoptimized": true
}
}'
```
@@ -0,0 +1,9 @@
```csharp
using Qdrant.Client;
using Qdrant.Client.Grpc;
await client.UpdateCollectionAsync(
collectionName: "{collection_name}",
optimizersConfig: new OptimizersConfigDiff { PreventUnoptimized = true }
);
```
@@ -0,0 +1,14 @@
```go
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client.UpdateCollection(context.Background(), &qdrant.UpdateCollection{
CollectionName: "{collection_name}",
OptimizersConfig: &qdrant.OptimizersConfigDiff{
PreventUnoptimized: qdrant.PtrOf(true),
},
})
```
@@ -0,0 +1,13 @@
```java
import io.qdrant.client.grpc.Collections.OptimizersConfigDiff;
import io.qdrant.client.grpc.Collections.UpdateCollection;
client
.updateCollectionAsync(
UpdateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setOptimizersConfig(
OptimizersConfigDiff.newBuilder().setPreventUnoptimized(true).build())
.build())
.get();
```
@@ -0,0 +1,6 @@
```python
client.update_collection(
collection_name="{collection_name}",
optimizers_config=models.OptimizersConfigDiff(prevent_unoptimized=True),
)
```
@@ -0,0 +1,11 @@
```rust
use qdrant_client::qdrant::{OptimizersConfigDiffBuilder, UpdateCollectionBuilder};
client
.update_collection(
UpdateCollectionBuilder::new("{collection_name}").optimizers_config(
OptimizersConfigDiffBuilder::default().prevent_unoptimized(true),
),
)
.await?;
```
@@ -0,0 +1,7 @@
```typescript
client.updateCollection("{collection_name}", {
optimizers_config: {
prevent_unoptimized: true,
},
});
```
@@ -0,0 +1,25 @@
package snippet
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
func Main() {
// @hide-start
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
// @hide-end
if err != nil { panic(err) } // @hide
client.UpdateCollection(context.Background(), &qdrant.UpdateCollection{
CollectionName: "{collection_name}",
OptimizersConfig: &qdrant.OptimizersConfigDiff{
PreventUnoptimized: qdrant.PtrOf(true),
},
})
}
@@ -0,0 +1,8 @@
```http
PATCH /collections/{collection_name}
{
"optimizers_config": {
"prevent_unoptimized": true
}
}
```
@@ -0,0 +1,22 @@
package com.example.snippets_amalgamation;
import io.qdrant.client.grpc.Collections.OptimizersConfigDiff;
import io.qdrant.client.grpc.Collections.UpdateCollection;
public class Snippet {
public static void run() throws Exception {
// @hide-start
io.qdrant.client.QdrantClient client =
new io.qdrant.client.QdrantClient(io.qdrant.client.QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
// @hide-end
client
.updateCollectionAsync(
UpdateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setOptimizersConfig(
OptimizersConfigDiff.newBuilder().setPreventUnoptimized(true).build())
.build())
.get();
}
}
@@ -0,0 +1,8 @@
from qdrant_client import QdrantClient, models # @hide
client = QdrantClient(url="http://localhost:6333") # @hide
client.update_collection(
collection_name="{collection_name}",
optimizers_config=models.OptimizersConfigDiff(prevent_unoptimized=True),
)
@@ -0,0 +1,15 @@
use qdrant_client::qdrant::{OptimizersConfigDiffBuilder, UpdateCollectionBuilder};
pub async fn main() -> anyhow::Result<()> {
let client = qdrant_client::Qdrant::from_url("http://localhost:6334").build()?; // @hide
client
.update_collection(
UpdateCollectionBuilder::new("{collection_name}").optimizers_config(
OptimizersConfigDiffBuilder::default().prevent_unoptimized(true),
),
)
.await?;
Ok(())
}
@@ -0,0 +1,9 @@
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
client.updateCollection("{collection_name}", {
optimizers_config: {
prevent_unoptimized: true,
},
});
@@ -122,6 +122,49 @@ In addition to the configuration file, you can also set optimizer parameters sep
Dynamic parameter updates may be useful, for example, for more efficient initial loading of points. You can disable indexing during the upload process with these settings and enable it immediately after it is finished. As a result, you will not waste extra computation resources on rebuilding the index.
## Prevent Reads from Unindexed Segments
*Available as of v1.17.1*
<aside role="alert"><code>prevent_unoptimized</code> is an experimental feature; its behavior may change slightly in future releases and it must be used with care.</aside>
When a collection receives a high volume of updates, for example, during nightly batch updates or when processing a large backlog of updates after a period of downtime, the optimizer might not be able to index new points fast enough to keep up. When this happens, searches may slow down as Qdrant has to scan through large amounts of unindexed data for every query.
To address this, Qdrant supports [querying indexed data only](/documentation/search/low-latency-search/#query-indexed-data-only), by setting `indexed_only` to `true`. Because updates in Qdrant are implemented as a delete followed by an insert, a side effect of searching indexed data only is that it can cause recently updated data to temporarily disappear from search results until it is indexed again. This is because the delete operation immediately removes the old point from the index, while the insert operation adds the new point to an unindexed segment that is not yet visible to searches.
To mitigate this, the optimizer supports a `prevent_unoptimized` mode. When enabled, points written to an unindexed segment that is larger than `indexing_threshold` are accepted and durably stored but are not visible in search results until the optimizer has indexed the segment. These are called deferred points. Not until the optimizer finishes indexing a segment containing deferred points, do those points become visible.
Set `prevent_unoptimized` to `true` when creating or updating a collection:
{{< code-snippet path="/documentation/headless/snippets/update-collection/prevent-unoptimized/" >}}
<aside role="status">
Enabling <code>prevent_unoptimized</code> only affects newly created segments. Existing segments are not changed retroactively. Similarly, changing <code>indexing_threshold</code> does not affect existing segments. Only new segments will use the updated threshold.
</aside>
With `prevent_unoptimized` enabled, setting `indexed_only` to `true` is not necessary to avoid slow searches, as unindexed segments do not return deferred points.
| `prevent_unoptimized` | `indexed_only` | Effect |
|----------------------|----------------|--------|
| `false` (default) | `false` | All points are searchable, but searches may be slow if there are many unindexed points. |
| `false` (default) | `true` | Only indexed points are searchable, but recently updated points may temporarily disappear from search results until they are indexed. |
| `true` | `false` (default) | Reads return indexed points and unindexed points that are not deferred. Deferred points are not visible to reads until indexed. |
| `true` | `true` | Only indexed points are visible to reads. |
### Effect on `wait=true`
Qdrant processes updates in strict order: each update is written to the write-ahead log and then applied sequentially by the update worker, preserving this order.
Under normal conditions, setting `wait=true` on a write request returns after the update has been applied to a segment. After enabling `prevent_unoptimized`, the response is held until every deferred point, including the current update, has been indexed and is visible for search. Depending on the volume of updates and the speed of the optimizer, this can take a significant amount of time and may lead to timeouts on the client side. If the client times out, the update can be expected to be durably stored and eventually indexed, but the client will not receive a confirmation for that specific request.
Because the update worker must finish indexing before continuing to consume the queue, a blocked `wait=true` request also delays all subsequent updates that use `wait=true`. Updates with `wait=false` are written to the write-ahead log immediately, but they are not applied until the blocked request unblocks. This head-of-line blocking means that `wait=true` can stall the entire update pipeline for as long as indexing takes. Use it with caution when `prevent_unoptimized` is enabled and the cluster is under heavy write load.
### Monitoring Deferred Points
You can check the number of deferred points in a collection via the `update_queue` section in the response of the [collection info API](/documentation/manage-data/collections/#collection-info). The same information is also available in [telemetry and metrics](/documentation/operations/monitoring/), enabling dashboards and alerting.
A non-zero deferred point count means the optimizer is processing a backlog. This is expected under heavy write load; monitor the count to confirm that it is decreasing over time.
## Optimization Monitoring
*Available as of v1.17.0*