优化器
与许多其他数据库一样,分批处理变更比逐个执行变更要高效得多。Qdrant 也不例外。由于 Qdrant 运行的数据结构并不总是易于修改,因此有时必须完全重建这些结构。
Qdrant 中的存储优化发生在分段(segment)级别(请参阅存储)。在这种情况下,待优化的分段在重建期间仍保持可读状态。

通过将分段封装在透明处理数据变更的代理中,可以实现可用性。变更后的数据被放置在“写时复制”(copy-on-write)分段中,该分段在检索和后续更新中具有优先级。
清理优化器 (Vacuum Optimizer)
需要重建分段存储库的最简单示例是删除向量点(points)。像许多其他数据库一样,Qdrant 不会在查询后立即删除条目。相反,它将记录标记为已删除,并在后续查询中忽略它们。
这种策略使我们能够最大限度地减少磁盘访问——这是最慢的操作之一。然而,这种策略的一个副作用是,随着时间的推移,已删除的记录会堆积起来,占用内存并拖慢系统速度。
为了避免这些不利影响,使用了“清理优化器”。如果分段中积累了太多已删除记录,则会使用该优化器。
启动优化器的标准在配置文件中定义。
以下是参数值的示例
storage:
optimizers:
# The minimal fraction of deleted vectors in a segment, required to perform segment optimization
deleted_threshold: 0.2
# The minimal number of vectors in a segment, required to perform segment optimization
vacuum_min_vector_number: 1000
合并优化器 (Merge Optimizer)
服务可能需要创建临时分段。例如,此类分段是在优化过程中作为“写时复制”分段创建的。
拥有至少一个小型分段也非常重要,Qdrant 将使用它来存储频繁更新的数据。另一方面,太多的小分段会导致搜索性能不佳。
如果当前分段过多,合并优化器会不断尝试减少分段数量。期望的分段数量通过 default_segment_number 指定,默认值为 CPU 数量。优化器可能会获取至少三个最小的分段并将它们合并为一个。
如果合并后的分段超过了 max_segment_size_kb 配置的最大分段大小,则不会进行合并。这可以防止创建过大而无法有效索引的分段。如果您有大量数据,增加此数值有助于减少分段数量,并可能提高搜索性能。
启动优化器的标准在配置文件中定义。
以下是参数值的示例
storage:
optimizers:
# Target amount of segments optimizer will try to keep.
# Real amount of segments may vary depending on multiple parameters:
# - Amount of stored points
# - Current write RPS
#
# It is recommended to select default number of segments as a factor of the number of search threads,
# so that each segment would be handled evenly by one of the threads.
# If `default_segment_number = 0`, will be automatically selected by the number of available CPUs
default_segment_number: 0
# Do not create segments larger this size (in KiloBytes).
# Large segments might require disproportionately long indexation times,
# therefore it makes sense to limit the size of segments.
#
# If indexation speed have more priority for your - make this parameter lower.
# If search speed is more important - make this parameter higher.
# Note: 1Kb = 1 vector of size 256
# If not set, will be automatically selected considering the number of available CPUs.
max_segment_size_kb: null
索引优化器 (Indexing Optimizer)
Qdrant 允许您根据记录数量选择索引类型和数据存储方法。例如,如果向量点数量少于 10,000 个,使用任何索引的效果都不如暴力扫描(brute force scan)。
索引优化器用于在达到最小记录数时执行索引启用和内存映射(memmap)存储。
启动优化器的标准在配置文件中定义。
以下是参数值的示例
storage:
optimizers:
# Maximum size (in kilobytes) of vectors to store in-memory per segment.
# Segments larger than this threshold will be stored as read-only memmaped file.
# Memmap storage is disabled by default, to enable it, set this threshold to a reasonable value.
# To disable memmap storage, set this to `0`.
# Note: 1Kb = 1 vector of size 256
memmap_threshold: 200000
# Maximum size (in KiloBytes) of vectors allowed for plain index.
# Default value based on experiments and observations.
# Note: 1Kb = 1 vector of size 256
# To explicitly disable vector indexing, set to `0`.
# If not set, the default value will be used.
indexing_threshold_kb: 10000
除了配置文件外,您还可以为每个集合单独设置优化器参数。
动态参数更新可能非常有用,例如,为了更高效地进行初始数据加载。您可以在上传过程中通过这些设置禁用索引,并在上传完成后立即启用它。这样,您就不会浪费额外的计算资源来重建索引。
阻止从非索引分段读取
自 v1.17.1 版本起可用
当集合接收到大量更新时(例如在夜间批量更新期间,或在停机后处理大量更新积压时),优化器可能无法足够快地索引新点以保持进度。发生这种情况时,由于 Qdrant 必须在每次查询时扫描大量未索引的数据,搜索速度可能会变慢。
为了解决这个问题,Qdrant 支持仅查询已索引数据,只需将 indexed_only 设置为 true。由于 Qdrant 中的更新是通过先删除后插入实现的,因此仅搜索已索引数据的副作用是,最近更新的数据可能会暂时从搜索结果中消失,直到它们被重新索引为止。这是因为删除操作会立即从索引中移除旧点,而插入操作则将新点添加到对搜索不可见的未索引分段中。
为了缓解这种情况,优化器支持 prevent_unoptimized 模式。启用后,写入大于 indexing_threshold 的未索引分段的点会被接受并持久存储,但在优化器索引该分段之前,它们在搜索结果中不可见。这些被称为“延迟点”(deferred points)。只有当优化器完成对包含延迟点的分段的索引后,这些点才会变得可见。
在创建或更新集合时设置 prevent_unoptimized 为 true
PATCH /collections/{collection_name}
{
"optimizers_config": {
"prevent_unoptimized": true
}
}
curl -X PATCH https://:6333/collections/{collection_name} \
-H 'Content-Type: application/json' \
--data-raw '{
"optimizers_config": {
"prevent_unoptimized": true
}
}'
client.update_collection(
collection_name="{collection_name}",
optimizers_config=models.OptimizersConfigDiff(prevent_unoptimized=True),
)
client.updateCollection("{collection_name}", {
optimizers_config: {
prevent_unoptimized: true,
},
});
use qdrant_client::qdrant::{OptimizersConfigDiffBuilder, UpdateCollectionBuilder};
client
.update_collection(
UpdateCollectionBuilder::new("{collection_name}").optimizers_config(
OptimizersConfigDiffBuilder::default().prevent_unoptimized(true),
),
)
.await?;
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();
using Qdrant.Client;
using Qdrant.Client.Grpc;
await client.UpdateCollectionAsync(
collectionName: "{collection_name}",
optimizersConfig: new OptimizersConfigDiff { PreventUnoptimized = true }
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client.UpdateCollection(context.Background(), &qdrant.UpdateCollection{
CollectionName: "{collection_name}",
OptimizersConfig: &qdrant.OptimizersConfigDiff{
PreventUnoptimized: qdrant.PtrOf(true),
},
})
启用 prevent_unoptimized 后,无需将 indexed_only 设置为 true 即可避免搜索变慢,因为未索引的分段不会返回延迟点。
prevent_unoptimized | indexed_only | 影响 |
|---|---|---|
false(默认) | false | 所有点均可搜索,但如果存在大量未索引的点,搜索可能会很慢。 |
false(默认) | true | 仅已索引的点可搜索,但最近更新的点可能会在被索引前暂时从搜索结果中消失。 |
true | false(默认) | 读取操作返回已索引的点和未延迟的未索引点。延迟点在被索引前对读取不可见。 |
true | true | 读取操作仅对已索引的点可见。 |
对 wait=true 的影响
Qdrant 按严格顺序处理更新:每个更新都写入预写日志(write-ahead log),然后由更新工作线程按顺序应用,从而保持此顺序。
在正常条件下,在写请求上设置 wait=true 会在更新应用到分段后返回。启用 prevent_unoptimized 后,响应会保持挂起,直到包括当前更新在内的每个延迟点都被索引并对搜索可见为止。根据更新量和优化器的速度,这可能需要很长时间,并可能导致客户端超时。如果客户端超时,更新预计会被持久存储并最终被索引,但客户端将不会收到该特定请求的确认。
由于更新工作线程必须完成索引才能继续处理队列,因此被阻塞的 wait=true 请求也会延迟所有后续使用 wait=true 的更新。使用 wait=false 的更新会立即写入预写日志,但在被阻塞的请求解除阻塞之前,它们不会被应用。这种队头阻塞(head-of-line blocking)意味着 wait=true 可能会在索引期间阻塞整个更新管道。当启用了 prevent_unoptimized 且集群处于重负载写入时,请谨慎使用它。
监控延迟点
您可以通过集合信息 API 响应中的 update_queue 部分查看集合中的延迟点数量。相同的信息也可在遥测和指标中获得,从而实现仪表板监控和警报。
延迟点计数不为零意味着优化器正在处理积压任务。这在重负载写入下是正常的;请监控该计数以确认它随时间推移正在减少。
优化监控
从 v1.17.0 开始提供
/collections/{collection_name}/optimizations API 端点返回有关特定集合优化的信息,包括:
- 优化活动摘要,包含排队优化的数量、排队的分段、排队的向量点以及空闲分段(无需优化的分段)。
- 有关任何当前正在进行的优化的详细信息,包括:
- 具体的优化器
- 其状态
- 涉及的分段
- 其进度
您可以选择使用 with 查询参数,并配合以下一个或多个以逗号分隔的值来检索附加信息:
queued,返回排队中的优化列表completed,返回已完成的优化列表idle_segments,返回空闲分段列表
例如
GET /collections/{collection_name}/optimizations?with=queued,completed
Web UI
相同的信息也可通过Web UI 中“集合”界面内的“优化”选项卡访问。对于特定集合,此选项卡提供了当前优化状态的概览以及当前和过去优化周期的详细时间轴。

从时间轴中选择特定的优化周期,可以提供有关该周期内所执行任务的详细信息,包括其持续时间。
