[v1.16.0] conditional update docs (#1991)

* conditional update description

* upd rust snippet

* parger pic

* Use consistent version gate

* Minor text style improvement

* docs: Added Java snippet

Signed-off-by: Anush008 <anushshetty90@gmail.com>

* chore: Removed TODO! from C#

* fix types on image

* docs: Updated io.qdrant.client.grpc.Common.Filter import

---------

Signed-off-by: Anush008 <anushshetty90@gmail.com>
Co-authored-by: Tim Visée <tim@visee.me>
Co-authored-by: Anush008 <anushshetty90@gmail.com>
This commit is contained in:
Andrey Vasnetsov
2025-11-17 14:46:28 +01:00
committed by GitHub
co-authored by Tim Visée Anush008
parent 8c9b0288c7
commit 2ac1396c3a
10 changed files with 225 additions and 0 deletions
@@ -265,6 +265,36 @@ Alternative way to specify which points to remove is to use filter.
This example removes all points with `{ "color": "red" }` from the collection.
## Conditional updates
_Available as of v1.16.0_
All update operations (including point insertion, vector updates, payload updates, and deletions) support configurable pre-conditions based on filters.
{{< code-snippet path="/documentation/headless/snippets/insert-points/with-condition/" >}}
While conditional payload modification and deletion covers the use-case of mass data modification, conditional point insertion and vector updates are particularly useful for implementing optimistic concurrency control in distributed systems.
A common scenario for such mechanism is when multiple clients try to update the same point independently.
Consider the following sequence of events:
- Client A reads point P.
- Client B reads point P.
- Client A modifies point P and writes it back to Qdrant.
- Client B modifies point P (based on the stale data) and writes it back to Qdrant, unintentionally overwriting changes made by Client A.
To prevent such situations, Client B can use conditional updates.
For this, we would need to introduce an additional field in the payload, e.g. `version`, which would be incremented on each update.
When Client A writes back the modified point P, it would set the condition that the `version` field must be equal to the value it read initially.
If Client B tries to write back its changes later, the condition would fail (as the `version` has been incremented by Client A), and Qdrant would reject the update, preventing accidental overwrites.
Instead of `version`, applications can use timestamps (assuming synchronized clocks) or any other monotonically increasing value that fits their data model.
This mechanism is especially useful in the scenarios of embedding model migration, where we need to resolve conflicts between regular application updates and background re-embedding tasks.
{{< figure src="/docs/embedding-model-migration.png" caption="Embedding model migration in blue-green deployment" width="80%" >}}
## Retrieve points
There is a method for retrieving points by their ids.
@@ -0,0 +1 @@
This code snippet demonstrates how to insert a point with conditionally. Update condition is represented by a filter, if the filter matches existing points, the point will be updated. If condition doesn't match the point, the upsert operation will be ignored. If the point doesn't exist, it will be inserted as a new point.
@@ -0,0 +1,25 @@
```csharp
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new PointStruct
{
Id = 1,
Vectors = new[] { 0.05f, 0.61f, 0.76f, 0.74f },
Payload = {
["city"] = "Berlin",
["price"] = 1.99,
["version"] = 3
}
}
},
updateFilter: Match("version", 2)
);
```
@@ -0,0 +1,29 @@
```go
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewIDNum(1),
Vectors: qdrant.NewVectors(0.05, 0.61, 0.76, 0.74),
Payload: qdrant.NewValueMap(map[string]any{
"city": "Berlin", "price": 1.99, "version": 3}),
},
},
UpdateFilter: &qdrant.Filter{
Must: []*qdrant.Condition{
qdrant.NewMatchInt("version", 2),
},
},
})
```
@@ -0,0 +1,26 @@
```http
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1,
"vector": [0.05, 0.61, 0.76, 0.74],
"payload": {
"city": "Berlin",
"price": 1.99,
"version": 3
}
}
],
"update_filter": {
"must": [
{
"key": "version",
"match": {
"value": 2
}
}
]
}
}
```
@@ -0,0 +1,31 @@
```java
import java.util.Map;
import static io.qdrant.client.ConditionFactory.match;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.UpsertPoints;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.upsertAsync(
UpsertPoints.newBuilder()
.setCollectionName("{collectionName}")
.addPoint(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(vectors(0.05f, 0.61f, 0.76f, 0.74f))
.putAllPayload(Map.of("city", value("Berlin"), "price", value(1.99)))
.build())
.setUpdateFilter(Filter.newBuilder().addMust(match("version", 2)).build())
.build())
.get();
```
@@ -0,0 +1,28 @@
```python
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
vector=[0.05, 0.61, 0.76, 0.74],
payload={
"city": "Berlin",
"price": 1.99,
"version": 3,
},
),
],
update_filter=models.Filter(
must=[
models.FieldCondition(
key="version",
match=models.MatchValue(value=2),
),
],
),
)
```
@@ -0,0 +1,26 @@
```rust
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder, Filter, Condition};
use qdrant_client::{Payload, Qdrant};
use serde_json::json;
let client = Qdrant::from_url("http://localhost:6334").build()?;
let points = vec![
PointStruct::new(
1,
vec![0.05, 0.61, 0.76, 0.74],
Payload::try_from(json!({
"city": "Berlin",
"price": 1.99,
"version": 3
})).unwrap(),
)
];
client
.upsert_points(
UpsertPointsBuilder::new("{collection_name}", points)
.wait(true)
.update_filter(Filter::must([Condition::matches("version", 2)]))
).await?;
```
@@ -0,0 +1,29 @@
```typescript
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.upsert("{collection_name}", {
points: [
{
id: 1,
vector: [0.05, 0.61, 0.76, 0.74],
payload: {
city: "Berlin",
price: 1.99,
version: 3
},
}
],
updateFilter: {
must: [
{
key: "version",
match: {
value: 2
}
}
]
}
});
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB