分布式部署
自 v0.8.0 版本起,Qdrant 支持分布式部署模式。在此模式下,多个 Qdrant 服务相互通信,将数据分布在各个对等节点(peer)上,从而扩展存储能力并提高稳定性。
我应该运行多少个 Qdrant 节点?
Qdrant 节点的理想数量取决于您在成本节约、弹性和性能/可扩展性之间如何进行权衡。
优先考虑成本节约:如果您最看重成本,请运行单个 Qdrant 节点。不建议在生产环境中使用此配置。缺点:
- 弹性:节点重启期间用户会遇到停机,除非有备份或快照,否则无法恢复。
- 性能:仅限于单台服务器的资源。
优先考虑弹性:如果您最看重弹性,请运行一个包含三个或更多节点以及两个或更多分片副本的 Qdrant 集群。拥有三个或更多节点且启用了复制功能的集群,即使在一个节点宕机的情况下也能执行所有操作。此外,它们可以从负载均衡中获得性能提升,并且可以在不依赖备份或快照的情况下从单个节点的永久性丢失中恢复(尽管仍强烈建议进行备份)。这是生产环境最推荐的配置。缺点:
- 成本:大型集群比小型集群成本更高,这是该配置唯一的缺点。
平衡成本、弹性和性能:运行一个包含两个节点且分片已复制的 Qdrant 集群,允许集群在单个节点宕机(例如维护期间)时仍能响应大多数读/写请求。拥有两个节点也意味着比单节点集群具有更高的性能,同时成本仍低于三节点集群。缺点:
- 弹性(正常运行时间):当一个节点宕机时,集群无法对集合(collection)执行操作。这些操作需要 >50% 的节点在线,因此只有在 3+ 节点的集群中才可能实现。由于创建、编辑和删除集合的操作通常很少见,许多用户认为这个缺点可以忽略不计。
- 弹性(数据完整性):如果两个节点之一上的数据永久丢失或损坏,除了通过快照或备份外,无法恢复。只有 3+ 节点的集群才能从单个节点的永久丢失中恢复,因为恢复操作需要 >50% 的集群节点处于健康状态。
- 成本:复制分片意味着需要存储两份数据副本。
- 性能:Qdrant 集群的最大性能会随着节点数量的增加而提升。
总之,单节点集群最适合非生产工作负载,复制的 3+ 节点集群是行业黄金标准,而复制的 2 节点集群则提供了一个良好的平衡点。
在自托管 Qdrant 中启用分布式模式
要启用分布式部署,请在配置文件中启用集群模式,或使用环境变量:QDRANT__CLUSTER__ENABLED=true。
cluster:
# Use `enabled: true` to run Qdrant in distributed deployment mode
enabled: true
# Configuration of the inter-cluster communication
p2p:
# Port for internal communication between peers
port: 6335
# Configuration related to distributed consensus algorithm
consensus:
# How frequently peers should ping each other.
# Setting this parameter to lower value will allow consensus
# to detect disconnected node earlier, but too frequent
# tick period may create significant network and CPU overhead.
# We encourage you NOT to change this parameter unless you know what you are doing.
tick_period_ms: 100
默认情况下,Qdrant 将使用 6335 端口进行内部通信。集群内的所有对等节点都应能通过此端口访问,但请确保将此端口与外部访问隔离开,因为它可能被用于执行写操作。
此外,您必须为第一个对等节点提供 --uri 参数,以便它能告知其他节点如何连接到它。
./qdrant --uri 'http://qdrant_node_1:6335'
集群中的后续对等节点必须至少知道现有集群中的一个节点,才能通过它与集群的其余部分进行同步。
为此,需要为它们提供一个引导(bootstrap)URL。
./qdrant --bootstrap 'http://qdrant_node_1:6335'
新对等节点自身的 URL 将根据其请求的 IP 地址自动计算。也可以使用 --uri 参数手动指定它们。
USAGE:
qdrant [OPTIONS]
OPTIONS:
--bootstrap <URI>
Uri of the peer to bootstrap from in case of multi-peer deployment. If not specified -
this peer will be considered as a first in a new deployment
--uri <URI>
Uri of this peer. Other peers should be able to reach it by this uri.
This value has to be supplied if this is the first peer in a new deployment.
In case this is not the first peer and it bootstraps the value is optional. If not
supplied then qdrant will take internal grpc port from config and derive the IP address
of this peer on bootstrap peer (receiving side)
成功同步后,您可以通过 REST API 查看集群状态。
GET /cluster
结果示例
{
"result": {
"status": "enabled",
"peer_id": 11532566549086892000,
"peers": {
"9834046559507417430": {
"uri": "http://172.18.0.3:6335/"
},
"11532566549086892528": {
"uri": "http://qdrant_node_1:6335/"
}
},
"raft_info": {
"term": 1,
"commit": 4,
"pending_operations": 1,
"leader": 11532566549086892000,
"role": "Leader"
}
},
"status": "ok",
"time": 5.731e-06
}
请注意,启用分布式模式不会自动复制您的数据。请参阅关于利用新的分布式 Qdrant 集群的章节以获取后续步骤。
在 Qdrant Cloud 中启用分布式模式
为获得最佳效果,请首先确保您的集群运行的是 Qdrant v1.7.4 或更高版本。旧版本也支持分布式模式,但 v1.7.4 中的改进使分布式集群在中断期间更具弹性。
在 Qdrant Cloud 控制台中,点击“Scale Up”(扩展)将集群大小增加到 >1。Qdrant Cloud 会自动配置分布式模式设置。
此外,Qdrant Cloud 还提供了自动重新平衡和重新分片(reshard)集合的功能,这是自托管 Qdrant 所不具备的。有关详细信息,请参阅重新分片和分片重新平衡章节。
扩展过程完成后,您将在现有节点旁边拥有一个新的空节点。要将数据复制到此新节点,请参阅下一章节。
利用新的分布式 Qdrant 集群
当您启用分布式模式并扩展到两个或更多节点时,数据不会自动移动到新节点;它初始是空的。要使用新的空节点,请执行以下操作之一:
- 通过将 replication_factor(复制因子)设置为 2 或更多,并将 分片数量设置为节点数量的倍数,来创建一个新的已复制集合。
- 如果您现有的集合没有足够的分片供每个节点使用,则必须按照前一点所述创建一个新集合。
- 如果您已经有足够的分片供每个节点使用,并且只需要复制数据,请按照创建新分片副本的说明进行操作。
- 如果您已经有足够的分片供每个节点使用,且数据已复制,则可以通过移动分片将数据(无需复制)移至新节点。
Raft
Qdrant 使用 Raft 共识协议来维护集群拓扑和集合结构的一致性。
另一方面,点(point)操作不经过共识基础设施。Qdrant 不旨在提供强事务保证,这使其能够以极低的开销执行点操作。实际上,这意味着 Qdrant 不保证原子化的分布式更新,但允许您等待直到操作完成以查看写入结果。
相反,集合操作是共识的一部分,它保证所有操作都是持久的,并最终由所有节点执行。实际上,这意味着在服务执行操作之前,大多数节点必须就应执行的操作达成一致。
实践中,如果集群处于过渡状态(例如在故障后选举新领导者或启动中),集合更新操作将被拒绝。
您可以使用集群 REST API 来检查共识状态。
分片
Qdrant 中的集合由一个或多个分片组成。分片是一个独立的点存储单元,能够执行集合提供的所有操作。有两种将点分布在分片上的方法:
自动分片:点通过一致性哈希算法分布在分片中,从而使分片管理不相交的点子集。这是默认行为。
用户定义分片:v1.7.0 起可用 - 每个点被上传到特定的分片,因此操作可以仅命中所需的单个或多个分片。即使采用这种分配方式,分片仍确保拥有不相交的点子集。了解更多...
每个节点通过共识协议知道集合的所有部分存储在哪里,因此当您向一个 Qdrant 节点发送搜索请求时,它会自动查询所有其他节点以获取完整的搜索结果。
选择正确的分片数量
创建集合时,Qdrant 将集合拆分为 shard_number 个分片。如果不设置,shard_number 将设置为创建集合时集群中的节点数。如果不重新创建集合,则无法更改 shard_number。
PUT /collections/{collection_name}
{
"vectors": {
"size": 300,
"distance": "Cosine"
},
"shard_number": 6
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=300, distance=models.Distance.COSINE),
shard_number=6,
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: {
size: 300,
distance: "Cosine",
},
shard_number: 6,
});
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(300, Distance::Cosine))
.shard_number(6),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createCollectionAsync(
CreateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setVectorsConfig(
VectorsConfig.newBuilder()
.setParams(
VectorParams.newBuilder()
.setSize(300)
.setDistance(Distance.Cosine)
.build())
.build())
.setShardNumber(6)
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
collectionName: "{collection_name}",
vectorsConfig: new VectorParams { Size = 300, Distance = Distance.Cosine },
shardNumber: 6
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateCollection(context.Background(), &qdrant.CreateCollection{
CollectionName: "{collection_name}",
VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
Size: 300,
Distance: qdrant.Distance_Cosine,
}),
ShardNumber: qdrant.PtrOf(uint32(6)),
})
为确保集群中的所有节点负载均衡,分片数量必须是当前集群运行节点数量的倍数。
附注:多租户等高级用例可能需要不均匀的分片分布。请参阅多租户。
我们建议每个节点至少创建 2 个分片,以便在未来扩展时无需重新分片。重新分片在使用我们的云服务时是可能的,但如果部署在其他地方,应避免重新分片,因为那需要创建新集合。
如果您预计会有大量增长,我们建议设置 12 个分片,这样您可以从 1 个节点扩展到 2、3、6 和 12 个节点,而无需重新分片。在小型集群中拥有超过 12 个分片可能不值得带来的性能开销。
集合初次创建时,分片会均匀分布在所有现有节点上。
当您向集群添加或删除节点时,现有分片在节点间的再平衡取决于您的部署方式。
重新分片
云端 v1.13.0 起可用
如果您使用我们的 Cloud 服务,重新分片允许您更改现有集合中的分片数量。
重新分片可以向上或向下更改分片数量,无需从零开始重新创建集合。
请参阅我们云文档中的重新分片章节以获取更多详细信息。
移动分片
v0.9.0 起可用
Qdrant 允许在集群中的节点之间移动分片,并从集群中移除节点。此功能实现了无需停机即可动态扩展集群大小的能力。它还允许您在不停机的情况下升级或迁移节点。
如果您的集群在 Qdrant Cloud 上运行,分片会自动在集群节点间平衡。更多信息请参阅配置云集群和云集群扩展文档。
Qdrant 使用 集合集群信息 API 提供有关集群中当前分片分布的信息。
使用 更新集合集群设置 API 来发起分片传输。
POST /collections/{collection_name}/cluster
{
"move_shard": {
"shard_id": 0,
"from_peer_id": 381894127,
"to_peer_id": 467122995
}
}
传输发起后,服务将根据所使用的传输方法进行处理,并保持两个分片同步。传输完成后,源节点上的旧分片将被删除。
如果您想缩小集群规模,可以将所有分片从一个对等节点移走,然后使用 移除对等节点 API 删除该对等节点。
DELETE /cluster/peer/{peer_id}
此后,Qdrant 将把该节点从共识中排除,实例即可准备关闭。
用户定义分片
自 v1.7.0 起可用
Qdrant 允许您分别为每个点指定分片。如果您希望控制数据的分片位置,以便操作仅命中它们实际需要的那个分片子集,此功能非常有用。在大型集群中,这可以显著提高那些不需要扫描整个集合的操作的性能。
此功能的一个明确用例是管理多租户集合,其中假设每个租户(用户或组织)都是隔离的,因此可以将他们的数据存储在单独的分片中。
要启用用户定义分片,请在创建集合时将 sharding_method 设置为 custom。
PUT /collections/{collection_name}
{
"shard_number": 1,
"sharding_method": "custom"
// ... other collection parameters
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.create_collection(
collection_name="{collection_name}",
shard_number=1,
sharding_method=models.ShardingMethod.CUSTOM,
# ... other collection parameters
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
shard_number: 1,
sharding_method: "custom",
// ... other collection parameters
});
use qdrant_client::qdrant::{
CreateCollectionBuilder, Distance, ShardingMethod, VectorParamsBuilder,
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(300, Distance::Cosine))
.shard_number(1)
.sharding_method(ShardingMethod::Custom.into()),
)
.await?;
import static io.qdrant.client.ShardKeyFactory.shardKey;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.ShardingMethod;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createCollectionAsync(
CreateCollection.newBuilder()
.setCollectionName("{collection_name}")
// ... other collection parameters
.setShardNumber(1)
.setShardingMethod(ShardingMethod.Custom)
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
collectionName: "{collection_name}",
// ... other collection parameters
shardNumber: 1,
shardingMethod: ShardingMethod.Custom
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateCollection(context.Background(), &qdrant.CreateCollection{
CollectionName: "{collection_name}",
// ... other collection parameters
ShardNumber: qdrant.PtrOf(uint32(1)),
ShardingMethod: qdrant.ShardingMethod_Custom.Enum(),
})
在此模式下,shard_number 表示每个分片键(shard key)的分片数量,点将均匀分布。例如,如果您有 10 个分片键,并且集合配置如下:
{
"shard_number": 1,
"sharding_method": "custom",
"replication_factor": 2
}
那么该集合总共将拥有 1 * 10 * 2 = 20 个物理分片。
物理分片需要大量资源,因此请确保您的自定义分片键具有较低的基数(cardinality)。
对于具有高基数的键,建议改用 基于载荷的分区(partition by payload)。
现在,您需要创建自定义分片(API 参考)。
PUT /collections/{collection_name}/shards
{
"shard_key": "{shard_key}"
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.create_shard_key("{collection_name}", "{shard_key}")
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createShardKey("{collection_name}", {
shard_key: "{shard_key}"
});
use qdrant_client::qdrant::{
CreateShardKeyBuilder, CreateShardKeyRequestBuilder
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client
.create_shard_key(
CreateShardKeyRequestBuilder::new("{collection_name}")
.request(CreateShardKeyBuilder::default().shard_key("{shard_key}".to_string())),
)
.await?;
import static io.qdrant.client.ShardKeyFactory.shardKey;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateShardKey;
import io.qdrant.client.grpc.Collections.CreateShardKeyRequest;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client.createShardKeyAsync(CreateShardKeyRequest.newBuilder()
.setCollectionName("{collection_name}")
.setRequest(CreateShardKey.newBuilder()
.setShardKey(shardKey("{shard_key}"))
.build())
.build()).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateShardKeyAsync(
"{collection_name}",
new CreateShardKey { ShardKey = new ShardKey { Keyword = "{shard_key}", } }
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateShardKey(context.Background(), "{collection_name}", &qdrant.CreateShardKey{
ShardKey: qdrant.NewShardKey("{shard_key}"),
})
您可以列出集合中的所有自定义分片键。
GET /collections/{collection_name}/shards
from qdrant_client import QdrantClient
client = QdrantClient(url="https://:6333")
client.list_shard_keys(
collection_name="{collection_name}",
)
client.listShardKeys("{collection_name}");
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client.list_shard_keys("{collection_name}").await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client.listShardKeysAsync("{collection_name}").get();
using Qdrant.Client;
var client = new QdrantClient("localhost", 6334);
await client.ListShardKeysAsync("{collection_name}");
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.ListShardKeys(context.Background(), "{collection_name}")
要为每个点指定分片,您需要在 upsert 请求中提供 shard_key 字段。
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1111,
"vector": [0.1, 0.2, 0.3]
},
],
"shard_key": "user_1"
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1111,
vector=[0.1, 0.2, 0.3],
),
],
shard_key_selector="user_1",
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.upsert("{collection_name}", {
points: [
{
id: 1111,
vector: [0.1, 0.2, 0.3],
},
],
shard_key: "user_1",
});
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
use qdrant_client::Payload;
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
vec![PointStruct::new(
111,
vec![0.1, 0.2, 0.3],
Payload::default(),
)],
)
.shard_key_selector("user_1".to_string()),
)
.await?;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ShardKeySelectorFactory.shardKeySelector;
import static io.qdrant.client.VectorsFactory.vectors;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.UpsertPoints;
import java.util.List;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.upsertAsync(
UpsertPoints.newBuilder()
.setCollectionName("{collection_name}")
.addAllPoints(
List.of(
PointStruct.newBuilder()
.setId(id(111))
.setVectors(vectors(0.1f, 0.2f, 0.3f))
.build()))
.setShardKeySelector(shardKeySelector("user_1"))
.build()
)
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new() { Id = 111, Vectors = new[] { 0.1f, 0.2f, 0.3f } }
},
shardKeySelector: new ShardKeySelector { ShardKeys = { new List<ShardKey> { "user_1" } } }
);
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(111),
Vectors: qdrant.NewVectors(0.1, 0.2, 0.3),
},
},
ShardKeySelector: &qdrant.ShardKeySelector{
ShardKeys: []*qdrant.ShardKey{
qdrant.NewShardKey("user_1"),
},
},
})
现在,您可以通过在任何操作中指定 shard_key 来将操作定位到特定的分片。未指定分片键的操作将在所有分片上执行。
另一个用例是拥有按时间顺序跟踪数据的分片,这样您可以进行更复杂的调度,比如在一个分片中上传实时数据,并在达到一定年限后将其归档。

分片传输方法
自 v1.7.0 起可用
有多种方法可以用于将分片移动或复制到另一个节点。根据您想要的性能和保证,以及您希望如何管理集群,您可能需要选择特定的方法。每种方法都有其优缺点。哪种方法最快取决于分片的大小和状态。
可用的分片传输方法有:
stream_records: (默认) 通过将记录分批流式传输到目标节点来传输。snapshot: 通过自动利用快照,包含索引和量化数据进行传输。wal_delta: (自动恢复默认) 通过解析 WAL 差异进行传输;即处理错过的操作。
每种方法都有各自的优缺点和具体要求,部分包括:
| 方法 | Stream records(流式记录) | Snapshot(快照) | WAL delta(WAL 增量) |
|---|---|---|---|
| 版本 | v0.8.0+ | v1.7.0+ | v1.8.0+ |
| 目标 | 新/现有分片 | 新/现有分片 | 现有分片 |
| 连接性 | 内部 gRPC API (6335) | REST API (6333) 内部 gRPC API (6335) | 内部 gRPC API (6335) |
| HNSW 索引 | 不传输,将在目标上重新索引。 | 进行传输,在目标上立即就绪。 | 不传输,可能在目标上索引。 |
| 量化 | 不传输,将在目标上重新量化。 | 进行传输,在目标上立即就绪。 | 不传输,可能在目标上量化。 |
| 顺序性 | 目标上的无序更新1 | 目标上的有序更新2 | 目标上的有序更新2 |
| 磁盘空间 | 无需额外空间 | 在两个节点上都需要额外快照空间 | 无需额外空间 |
要选择分片传输方法,请指定 method,例如:
POST /collections/{collection_name}/cluster
{
"move_shard": {
"shard_id": 0,
"from_peer_id": 381894127,
"to_peer_id": 467122995,
"method": "snapshot"
}
}
stream_records 传输方法是最简单的。它只是分批将所有分片记录传输到目标节点,直到全部传输完毕,并保持两个分片同步。它还会确保传输的分片索引过程在执行最终切换之前跟上。该方法有两个常见缺点:1. 它不传输索引或量化数据,这意味着分片必须在目标节点上重新优化,这可能非常昂贵。2. 顺序性保证是 弱(weak)1 的,这对某些应用程序不适用。因为它非常简单,所以也非常稳健,如果您能接受上述缺点,它是一个可靠的选择。如果您的集群不稳定且资源匮乏,最好使用 stream_records 传输方法,因为它不太可能失败。
snapshot 传输方法利用快照来传输分片。快照会自动创建,然后传输并恢复到目标节点。完成后,快照将从两个节点中删除。在快照/传输/恢复操作进行期间,源节点会排队等待所有新操作。所有排队的更新随后按顺序发送到目标分片,以使其与源达到相同状态。有两个重要的优点:1. 它传输索引和量化数据,因此分片不需要在目标节点上重新优化,使其立即可用。通过这种方式,Qdrant 确保传输结束时不会出现性能下降。特别是在大型分片上,这可以带来巨大的性能提升。2. 顺序保证可以是 强(strong)2 的,这是某些应用程序所必需的。
wal_delta 传输方法仅传输两个分片之间的差异。更具体地说,它将所有错过的操作传输到目标分片。两个分片的 WAL 被用于解析差异。有两个优点:1. 速度非常快,因为它只传输差异而非所有数据。2. 顺序保证可以是 强(strong)2 的,这是某些应用程序所必需的。两个缺点是:1. 它只能用于传输到已经在另一节点上存在的分片。2. 适用性有限,因为 WAL 通常不会保留超过 64MB 的近期操作。但对于快速重启的节点(例如为了升级)来说,这应该足够了。如果增量无法解析,此方法会自动回退到 stream_records,这等同于传输整个分片。
目前默认使用 stream_records 方法。未来可能会有变化。截至 Qdrant 1.9.0,wal_delta 被用于自动分片复制以恢复死掉的分片。
复制
Qdrant 允许您在集群中的节点之间复制分片。
分片复制通过在集群中保留多个分布式的分片副本,提高了集群的可靠性。除非所有副本都丢失,否则这确保了节点故障时数据的可用性。
复制因子(Replication factor)
创建集合时,您可以通过更改 replication_factor 来控制您想要存储的分片副本数量。默认情况下,replication_factor 设置为“1”,意味着不会自动维护额外的副本。可以在 Qdrant 配置中更改默认值。您可以在创建集合时通过设置 replication_factor 来更改它。
replication_factor 可以针对现有集合进行更新,但其效果取决于您运行 Qdrant 的方式。如果您自行托管开源版本的 Qdrant,则在集合创建后更改复制因子不会产生任何效果。您可以手动创建或删除分片副本以达到所需的复制因子。在 Qdrant Cloud(包括混合云、私有云)中,您的分片将自动复制或删除以匹配您配置的复制因子。
PUT /collections/{collection_name}
{
"vectors": {
"size": 300,
"distance": "Cosine"
},
"shard_number": 6,
"replication_factor": 2
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=300, distance=models.Distance.COSINE),
shard_number=6,
replication_factor=2,
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: {
size: 300,
distance: "Cosine",
},
shard_number: 6,
replication_factor: 2,
});
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(300, Distance::Cosine))
.shard_number(6)
.replication_factor(2),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createCollectionAsync(
CreateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setVectorsConfig(
VectorsConfig.newBuilder()
.setParams(
VectorParams.newBuilder()
.setSize(300)
.setDistance(Distance.Cosine)
.build())
.build())
.setShardNumber(6)
.setReplicationFactor(2)
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
collectionName: "{collection_name}",
vectorsConfig: new VectorParams { Size = 300, Distance = Distance.Cosine },
shardNumber: 6,
replicationFactor: 2
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateCollection(context.Background(), &qdrant.CreateCollection{
CollectionName: "{collection_name}",
VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
Size: 300,
Distance: qdrant.Distance_Cosine,
}),
ShardNumber: qdrant.PtrOf(uint32(6)),
ReplicationFactor: qdrant.PtrOf(uint32(2)),
})
此代码示例创建了一个集合,总共 6 个逻辑分片,由总共 12 个物理分片支撑。
由于“2”的复制因子需要两倍的存储空间,建议预先确保硬件可以托管额外的分片副本。
创建新分片副本
可以使用 更新集合集群设置 API 手动创建或删除现有集合上的副本。这通常仅在您运行开源 Qdrant 时才需要。在 Qdrant Cloud 中,分片复制会自动处理和更新,以匹配配置的 replication_factor。
可以通过指定要从其进行复制的对等节点,在特定的对等节点上添加副本。
POST /collections/{collection_name}/cluster
{
"replicate_shard": {
"shard_id": 0,
"from_peer_id": 381894127,
"to_peer_id": 467122995
}
}
并且可以在特定的对等节点上移除副本。
POST /collections/{collection_name}/cluster
{
"drop_replica": {
"shard_id": 0,
"peer_id": 381894127
}
}
请记住,集合必须包含至少一个处于活动状态的分片副本。
错误处理
副本可以处于不同状态:
- Active(活动):健康并准备好提供流量服务。
- Dead(死亡):不健康且未准备好提供流量服务。
- Partial(部分):当前正在进行重新同步,处于激活前状态。
如果副本未响应内部健康检查或无法提供流量服务,则会被标记为死亡。
死亡的副本将不会从其他对等节点接收流量,如果它没有自动恢复,可能需要人工干预。
此机制确保了更新操作期间部分副本失败时的数据一致性和可用性。
节点故障恢复
有时硬件故障可能会导致 Qdrant 集群的某些节点无法恢复。没有系统能免疫这种情况。
但有几种恢复场景可以让 Qdrant 保持对请求的可用,甚至避免性能下降。让我们从最好到最坏的情况来梳理一下。
通过复制的集合进行恢复
如果故障节点的数量少于集合的复制因子,那么您的集群应该仍然能够执行读取、搜索和更新查询。
现在,如果故障节点重启,共识机制将触发复制过程,以使用它错过的最新更新来更新正在恢复的节点。
如果故障节点从未重启,如果您有 3+ 个节点的集群,则可以恢复丢失的分片。在更小的集群中,您无法恢复丢失的分片,因为恢复操作通过 Raft 进行,这需要 >50% 的节点处于健康状态。
通过复制的集合重新创建节点
如果节点发生故障且无法恢复,您应该将死亡节点从共识中排除并创建一个空节点。
要将故障节点从共识中排除,请使用 移除对等节点 API。必要时应用 force 标志。
创建新节点时,请确保通过指定带有任何正在运行的集群节点 URL 的 --bootstrap CLI 参数,将其连接到现有集群。
一旦新节点准备就绪并与集群同步,您可能希望确保集合分片已得到足够的复制。请记住,Qdrant 不会自动平衡分片,因为这是一种昂贵的操作。使用 复制分片操作 在新连接的节点上创建分片的另一个副本。
值得一提的是,Qdrant 仅提供创建自动故障恢复所需的构建块。构建一个完全自动化的集合扩展过程需要对集群机器本身的控制。请查看我们的 云解决方案,我们在那里实现了完全相同的功能。
从快照恢复
如果集群中没有数据副本,仍然可以从快照中恢复。
按照相同的步骤分离故障节点并在集群中创建一个新节点。
- 要将故障节点从共识中排除,请使用 移除对等节点 API。必要时应用
force标志。 - 创建一个新节点,确保通过指定带有任何正在运行的集群节点 URL 的
--bootstrapCLI 参数将其连接到现有集群。
单节点部署中使用的快照恢复与集群部署不同。共识管理有关所有集合的所有元数据,不需要快照来恢复它。但您可以使用快照来恢复集合中丢失的分片。
使用 集合快照恢复 API 来执行此操作。该服务将下载指定的集合快照并从中恢复分片数据。
一旦集合的所有分片都恢复,集合将再次变得可操作。
临时节点故障
如果配置正确,以分布式模式运行 Qdrant 可以使您的集群在单个节点暂时失效时抵抗中断。
以下是不同配置的 Qdrant 集群的响应方式:
- 1 节点集群:所有操作超时或失败长达几分钟。这取决于重启和从磁盘加载数据所需的时间。
- 分片未复制的 2 节点集群:所有操作超时或失败长达几分钟。这取决于重启和从磁盘加载数据所需的时间。
- 所有分片都在两个节点上复制的 2 节点集群:除集合操作外的所有请求在中断期间继续工作。
- 所有分片至少在 2 个节点上复制的 3+ 节点集群:所有请求在中断期间继续工作。
一致性保证
默认情况下,Qdrant 专注于搜索操作的可用性和最大吞吐量。对于大多数用例,这是一个更可取的权衡。
在正常运行状态下,可以从集群中的任何对等节点搜索和修改数据。
在响应客户端之前,处理请求的对等节点会根据当前拓扑分发所有操作,以保持集群中的数据同步。
- 读取使用部分扇出(fan-out)策略来优化延迟和可用性。
- 写入在所有活动分片副本上并行执行。
默认情况下,对同一个点并发更新可能导致不一致的状态。例如,如果两个客户端同时更新每个分片有三个副本的集合中的同一个点。在某些副本上,该点可能反映来自一个客户端的更新,而在其他副本上,该点可能反映来自另一个客户端的更新。

在某些情况下,有必要确保在可能的硬件不稳定、相同文档的大规模并发更新等情况下有额外的保证。
Qdrant 提供了一些选项来控制一致性保证:
write_consistency_factor- 定义在响应客户端之前必须确认写操作的副本数量。增加此值将使写操作对集群中的网络分区具有容忍度,但需要更多的活动副本才能执行写操作。- 读取
consistency参数,可与搜索和检索操作结合使用,以确保从所有副本获得的结果相同。如果使用此选项,Qdrant 将在多个副本上执行读取操作,并根据选择的策略解析结果。此选项对于避免并发更新相同文档时的数据不一致非常有用。如果更新操作频繁且副本数量较少,则首选此选项。 - 写入
ordering参数,可与更新和删除操作结合使用,以确保操作在所有副本上按相同顺序执行。如果使用此选项,Qdrant 将把操作路由到分片的领导者副本,并在响应客户端之前等待响应。此选项对于避免并发更新相同文档时的数据不一致非常有用。如果读取操作比更新更频繁,且搜索性能至关重要,则首选此选项。
写入一致性因子
write_consistency_factor 表示在响应客户端之前必须确认写操作的副本数量。默认设置为 1。它可以在创建集合时或更新集合参数时配置。
此值范围从 1 到您为每个分片拥有的副本数量。
PUT /collections/{collection_name}
{
"vectors": {
"size": 300,
"distance": "Cosine"
},
"shard_number": 6,
"replication_factor": 2,
"write_consistency_factor": 2
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="https://:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=300, distance=models.Distance.COSINE),
shard_number=6,
replication_factor=2,
write_consistency_factor=2,
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: {
size: 300,
distance: "Cosine",
},
shard_number: 6,
replication_factor: 2,
write_consistency_factor: 2,
});
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(300, Distance::Cosine))
.shard_number(6)
.replication_factor(2)
.write_consistency_factor(2),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createCollectionAsync(
CreateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setVectorsConfig(
VectorsConfig.newBuilder()
.setParams(
VectorParams.newBuilder()
.setSize(300)
.setDistance(Distance.Cosine)
.build())
.build())
.setShardNumber(6)
.setReplicationFactor(2)
.setWriteConsistencyFactor(2)
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
collectionName: "{collection_name}",
vectorsConfig: new VectorParams { Size = 300, Distance = Distance.Cosine },
shardNumber: 6,
replicationFactor: 2,
writeConsistencyFactor: 2
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateCollection(context.Background(), &qdrant.CreateCollection{
CollectionName: "{collection_name}",
VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
Size: 300,
Distance: qdrant.Distance_Cosine,
}),
ShardNumber: qdrant.PtrOf(uint32(6)),
ReplicationFactor: qdrant.PtrOf(uint32(2)),
WriteConsistencyFactor: qdrant.PtrOf(uint32(2)),
})
如果活动副本的数量小于 write_consistency_factor,写操作将失败。在这种情况下,预期客户端将再次发送该操作以确保达到一致状态。
将 write_consistency_factor 设置为较低的值可能允许在有无响应节点时仍接受写入。无响应节点将被标记为死亡,并在可用后自动恢复,以确保数据一致性。
write_consistency_factor 的配置对于调整集群在某些节点因重启、升级或故障而脱机时的行为非常重要。
默认情况下,只要每个分片的至少一个副本在线,集群就会继续接受更新。然而,这种行为意味着一旦离线副本恢复,它将需要与集群其余部分进行额外的同步。在某些情况下,这种同步可能是资源密集型且不希望发生的。
将 write_consistency_factor 设置为匹配复制因子,会修改集群的行为,从而拒绝未复制的更新,防止需要额外的同步。
如果更新被应用到足够多的副本(根据 write_consistency_factor),更新将返回成功状态。任何未能应用更新的副本将被暂时禁用,并自动恢复以保持数据一致性。如果更新无法应用到足够多的副本,它将返回错误并可能被部分应用。用户必须再次提交该操作以确保数据一致性。
对于能够处理错误和重试的异步更新和注入流水线,此策略可能更为可取。
读取一致性
可以为大多数读取请求指定读取 consistency,这将确保返回的结果在整个集群节点中是一致的。
all将查询所有节点并返回所有节点上都存在的点。majority将查询所有节点并返回大多数节点上存在的点。quorum将查询随机选择的大多数节点并返回所有这些节点上都存在的点。1/2/3/等 - 将查询指定数量的随机选择节点,并返回在所有这些节点上存在的点。- 默认的
consistency为1。
POST /collections/{collection_name}/points/query?consistency=majority
{
"query": [0.2, 0.1, 0.9, 0.7],
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "London"
}
}
]
},
"params": {
"hnsw_ef": 128,
"exact": false
},
"limit": 3
}
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
query_filter=models.Filter(
must=[
models.FieldCondition(
key="city",
match=models.MatchValue(
value="London",
),
)
]
),
search_params=models.SearchParams(hnsw_ef=128, exact=False),
limit=3,
consistency="majority",
)
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
filter: {
must: [{ key: "city", match: { value: "London" } }],
},
params: {
hnsw_ef: 128,
exact: false,
},
limit: 3,
consistency: "majority",
});
use qdrant_client::qdrant::{
read_consistency::Value, Condition, Filter, QueryPointsBuilder, ReadConsistencyType,
SearchParamsBuilder,
};
use qdrant_client::{Qdrant, QdrantError};
let client = Qdrant::from_url("https://:6334").build()?;
client
.query(
QueryPointsBuilder::new("{collection_name}")
.query(vec![0.2, 0.1, 0.9, 0.7])
.limit(3)
.filter(Filter::must([Condition::matches(
"city",
"London".to_string(),
)]))
.params(SearchParamsBuilder::default().hnsw_ef(128).exact(false))
.read_consistency(Value::Type(ReadConsistencyType::Majority.into())),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.ReadConsistency;
import io.qdrant.client.grpc.Points.ReadConsistencyType;
import io.qdrant.client.grpc.Points.SearchParams;
import static io.qdrant.client.QueryFactory.nearest;
import static io.qdrant.client.ConditionFactory.matchKeyword;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
.setFilter(Filter.newBuilder().addMust(matchKeyword("city", "London")).build())
.setQuery(nearest(.2f, 0.1f, 0.9f, 0.7f))
.setParams(SearchParams.newBuilder().setHnswEf(128).setExact(false).build())
.setLimit(3)
.setReadConsistency(
ReadConsistency.newBuilder().setType(ReadConsistencyType.Majority).build())
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;
var client = new QdrantClient("localhost", 6334);
await client.QueryAsync(
collectionName: "{collection_name}",
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
filter: MatchKeyword("city", "London"),
searchParams: new SearchParams { HnswEf = 128, Exact = false },
limit: 3,
readConsistency: new ReadConsistency { Type = ReadConsistencyType.Majority }
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
Filter: &qdrant.Filter{
Must: []*qdrant.Condition{
qdrant.NewMatch("city", "London"),
},
},
Params: &qdrant.SearchParams{
HnswEf: qdrant.PtrOf(uint64(128)),
},
Limit: qdrant.PtrOf(uint64(3)),
ReadConsistency: qdrant.NewReadConsistencyType(qdrant.ReadConsistencyType_Majority),
})
写入顺序(Write ordering)
可以为任何写请求指定写入 ordering,以便通过单个“领导者”节点进行序列化,这确保了所有写操作(以相同的 ordering 发布)都按顺序执行和观察。
weak(默认) 顺序不提供任何额外的保证,因此写操作可以自由重新排序。medium顺序通过动态选举的领导者序列化所有写操作,这在领导者变更的情况下可能会导致轻微的不一致。strong顺序通过永久领导者序列化所有写操作,这提供了强一致性,但如果领导者宕机,写操作可能不可用。
PUT /collections/{collection_name}/points?ordering=strong
{
"batch": {
"ids": [1, 2, 3],
"payloads": [
{"color": "red"},
{"color": "green"},
{"color": "blue"}
],
"vectors": [
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9]
]
}
}
client.upsert(
collection_name="{collection_name}",
points=models.Batch(
ids=[1, 2, 3],
payloads=[
{"color": "red"},
{"color": "green"},
{"color": "blue"},
],
vectors=[
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9],
],
),
ordering=models.WriteOrdering.STRONG,
)
client.upsert("{collection_name}", {
batch: {
ids: [1, 2, 3],
payloads: [{ color: "red" }, { color: "green" }, { color: "blue" }],
vectors: [
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9],
],
},
ordering: "strong",
});
use qdrant_client::qdrant::{
PointStruct, UpsertPointsBuilder, WriteOrdering, WriteOrderingType
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("https://:6334").build()?;
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
vec![
PointStruct::new(1, vec![0.9, 0.1, 0.1], [("color", "red".into())]),
PointStruct::new(2, vec![0.1, 0.9, 0.1], [("color", "green".into())]),
PointStruct::new(3, vec![0.1, 0.1, 0.9], [("color", "blue".into())]),
],
)
.ordering(WriteOrdering {
r#type: WriteOrderingType::Strong.into(),
}),
)
.await?;
import java.util.List;
import java.util.Map;
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.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.UpsertPoints;
import io.qdrant.client.grpc.Points.WriteOrdering;
import io.qdrant.client.grpc.Points.WriteOrderingType;
client
.upsertAsync(
UpsertPoints.newBuilder()
.setCollectionName("{collection_name}")
.addAllPoints(
List.of(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(vectors(0.9f, 0.1f, 0.1f))
.putAllPayload(Map.of("color", value("red")))
.build(),
PointStruct.newBuilder()
.setId(id(2))
.setVectors(vectors(0.1f, 0.9f, 0.1f))
.putAllPayload(Map.of("color", value("green")))
.build(),
PointStruct.newBuilder()
.setId(id(3))
.setVectors(vectors(0.1f, 0.1f, 0.94f))
.putAllPayload(Map.of("color", value("blue")))
.build()))
.setOrdering(WriteOrdering.newBuilder().setType(WriteOrderingType.Strong).build())
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = 1,
Vectors = new[] { 0.9f, 0.1f, 0.1f },
Payload = { ["color"] = "red" }
},
new()
{
Id = 2,
Vectors = new[] { 0.1f, 0.9f, 0.1f },
Payload = { ["color"] = "green" }
},
new()
{
Id = 3,
Vectors = new[] { 0.1f, 0.1f, 0.9f },
Payload = { ["color"] = "blue" }
}
},
ordering: WriteOrderingType.Strong
);
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.9, 0.1, 0.1),
Payload: qdrant.NewValueMap(map[string]any{"color": "red"}),
},
{
Id: qdrant.NewIDNum(2),
Vectors: qdrant.NewVectors(0.1, 0.9, 0.1),
Payload: qdrant.NewValueMap(map[string]any{"color": "green"}),
},
{
Id: qdrant.NewIDNum(3),
Vectors: qdrant.NewVectors(0.1, 0.1, 0.9),
Payload: qdrant.NewValueMap(map[string]any{"color": "blue"}),
},
},
Ordering: &qdrant.WriteOrdering{
Type: qdrant.WriteOrderingType_Strong,
},
})
监听器模式(Listener mode)
在某些情况下,拥有一个仅积累数据而不参与搜索操作的 Qdrant 节点可能会很有用。这种情况有几种:
- 监听器选项可用于将数据存储在单独的节点中,这可用于备份目的或长期存储数据。
- 监听器节点可用于将数据同步到另一个区域,同时仍在本地区域执行搜索操作。
要启用监听器模式,请在配置文件中将 node_type 设置为 Listener。
storage:
node_type: "Listener"
监听器节点将不参与搜索操作,但仍将接受写操作并将数据存储在本地存储中。
存储在监听器节点上的所有分片都将转换为 Listener 状态。
此外,发送到监听器节点的所有写请求都将使用 wait=false 选项进行处理,这意味着一旦写入 WAL,写操作就被视为成功。此机制应允许在并行快照的情况下最小化 upsert 延迟。
共识检查点(Consensus Checkpointing)
共识检查点是 Raft 中使用的一种技术,通过定期创建系统状态的一致快照来提高性能并简化日志管理。此快照代表了集群中所有节点就状态达成一致的时间点,可用于截断日志,减少需要存储和在节点间传输的数据量。
例如,如果您将一个新节点附加到集群中,它应该重放所有日志条目以追赶当前状态。在长时间运行的集群中,这可能需要很长时间,并且日志可能会变得非常大。
为了防止这种情况,可以使用一种特殊的检查点机制,它将截断日志并创建当前状态的快照。
要使用此功能,只需在所需节点上调用 /cluster/recover API。
POST /cluster/recover
此 API 可以在任何非领导者节点上触发,它将发送请求给当前共识领导者以创建快照。领导者随后将快照发回给请求节点以进行应用。
在某些情况下,此 API 可通过强制创建快照来用于从不一致的集群状态恢复。