Qdrant 低延迟搜索技巧
通过副本进行水平扩展
Qdrant 可以部署在分布式配置中。在分布式模式下,多个 Qdrant 实例(称为节点/peers)作为一个整体(称为集群)运行。数据存储在集合中,这些集合被划分为分布在各个节点上的分片 (shards)。每个分片可以拥有多个副本 (replicas),以实现冗余和负载均衡。由于同一分片的每个副本都包含相同的数据,因此读取请求可以分摊到各个副本上,从而降低延迟并提高吞吐量。
例如,一个包含三个分片且副本因子为二的集合总共会有六个副本(三个分片,每个分片两个副本)。在拥有三个节点的集群上,这些副本可以均匀分布在各个节点上,每个节点托管两个副本。

查询集合时,Qdrant 会从每个指定分片的一个副本中读取数据。每个副本都可以独立处理读取请求,因此增加节点数量和副本因子可以让你将读取负载分配到更多节点上,从而降低延迟并提高吞吐量。
然而,请记住副本并非免费的。运行更多的节点需要更多的硬件。由于写入操作需要同步到所有副本,增加副本因子可能会增加写入延迟。因此,在配置副本因子时,找到读取性能和资源使用之间的平衡点非常重要。
使用延迟扇出 (Delayed Fan-Outs)
从 v1.17.0 开始提供
默认情况下,搜索操作会查询集合中每个分片的单个副本。如果某个副本因负载或网络问题响应缓慢,整体搜索延迟可能会增加。这种现象(即单个缓慢副本导致系统整体 95% 或 99% 的百分位延迟增加)被称为“长尾延迟”。高长尾延迟会明显降低用户体验。
为了降低读取操作的长尾延迟,Qdrant 支持延迟扇出。通过延迟扇出,如果对某个副本的初始请求超过了指定的延迟阈值,系统会向另一个副本发送额外的读取请求。然后,Qdrant 将使用第一个返回的响应。
你可以通过设置 read_fan_out_delay_ms 参数(即等待尝试从另一个副本读取的毫秒数)来为每个集合启用延迟扇出。若要禁用已启用的延迟扇出,请将此参数设置为 0(默认值)。
另一种扇出读取的方法是始终从多个副本读取,而不考虑延迟。要启用此功能,请将 read_fan_out_factor 参数设置为需要额外读取的副本数量。请注意,这会增加集群负载,通常不建议这样做,因为 read_fan_out_delay_ms 可以在系统额外负载极小的情况下实现类似的延迟改善效果。
仅查询已索引的数据
分片将其数据存储在段 (segments) 中。写入操作在变更被完全索引并可供搜索之前,会经历几个阶段。
- 首先,每个传入的写入请求都会被写入分片的预写日志 (WAL)。在此阶段,数据尚不可搜索,但写入请求已被持久化,最终将被应用。
- 更新队列会跟踪 WAL 中尚未应用的数据条目。每个分片的更新队列最多可跟踪一百万个待处理更新。当队列满时,客户端会遇到背压:新的写入请求会暂停,直到队列有可用容量。
- 接下来,更新会从队列中取出并应用到一个或多个(未优化)段中。在此阶段,数据是可搜索的,但尚未建立索引,因此搜索这些数据的速度可能会较慢。
- 最后,索引优化器会为未优化的段创建向量索引 (HNSW 图)。一旦索引过程完成,数据即被完全索引,搜索性能达到最优。索引是一个相对较慢的操作,因此在写入数据和完全索引数据之间可能会存在延迟,特别是在高写入负载下。
搜索延迟可能会根据数据在上述流程中所处的位置而有所不同。查询大量未索引的数据会导致延迟增加。这种情况可能发生在高写入负载下,例如夜间批量更新期间,或者在停机后处理大量积压更新时。
如果你的应用程序需要持续的低搜索延迟,Qdrant 提供了两种机制来避免搜索未索引的数据。你可以使用 indexed_only 查询参数,或者启用 prevent_unoptimized 优化器设置。选择其中一种方法即可,无需同时使用。
indexed_only 搜索参数
自 v1.7.0 起可用
要将搜索限制在已索引的数据以及低于索引阈值的小段内,请将 indexed_only 搜索参数设置为 true。这可以确保更一致且更低的响应时间。然而,权衡之处在于,最新的数据在被索引之前可能不会出现在搜索结果中。
使用 indexed_only 的一个副作用是,它可能导致搜索结果中出现“闪烁”点。当一个未优化的段低于索引阈值时,其所有点在 indexed_only 搜索中均可见。但一旦插入操作使该段超过阈值,其所有点就会暂时从搜索结果中消失,直到该段被索引。更新操作也可能导致点闪烁,因为 Qdrant 是通过先删除再插入来实现更新的。为减轻“闪烁”点问题,请改用下一节中描述的 prevent_unoptimized。
prevent_unoptimized 优化器设置
自 v1.17.1 版本起提供
作为 indexed_only 的替代方案,为了减轻“闪烁”点问题,可以将 prevent_unoptimized 优化器设置设为 true。这可以防止创建包含未索引数据的大型段。相反,一旦一个段达到 indexing_threshold,所有额外的点都会以“延迟 (deferred)”状态添加。延迟点在读取时不可见,但在写入操作中会被处理。一旦段完成优化,延迟点就会被提升为可见点。
有关其工作原理的更多详细信息,请参考防止读取大型未索引段。