存储
一个集合内的所有数据被分成段(segments)。每个段都有独立的向量和载荷存储以及索引。
存储在段中的数据通常不重叠。但是,将同一点存储在不同的段中不会引起问题,因为搜索机制包含去重功能。
段由向量和载荷存储、向量和载荷索引以及 ID 映射器组成,ID 映射器存储内部 ID 和外部 ID 之间的关系。
根据使用的存储和索引类型,一个段可以是可附加的(appendable)
或不可附加的(non-appendable)
。你可以在可附加的
段中自由添加、删除和查询数据。不可附加的
段只能读取和删除数据。
集合中段的配置可以不同且相互独立,但一个集合中必须至少存在一个“可附加的”段。
向量存储
根据应用的需求,Qdrant 可以使用其中一种数据存储选项。需要在搜索速度和使用的 RAM 大小之间进行选择。
内存存储 - 将所有向量存储在 RAM 中,速度最快,因为仅持久化时需要磁盘访问。
内存映射存储 (Memmap storage) - 创建与磁盘文件关联的虚拟地址空间。Wiki。内存映射文件不会直接加载到 RAM 中。相反,它们使用页面缓存来访问文件内容。这种方案允许灵活使用可用内存。在 RAM 充足的情况下,其速度几乎与内存存储一样快。
配置内存映射存储
有两种方法可以配置内存映射(也称为磁盘)存储的使用
- 在集合创建 API 中为向量设置
on_disk
选项
自 v1.2.0 起可用
PUT /collections/{collection_name}
{
"vectors": {
"size": 768,
"distance": "Cosine",
"on_disk": true
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(
size=768, distance=models.Distance.COSINE, on_disk=True
),
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: {
size: 768,
distance: "Cosine",
on_disk: true,
},
});
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(768, Distance::Cosine).on_disk(true)),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createCollectionAsync(
"{collection_name}",
VectorParams.newBuilder()
.setSize(768)
.setDistance(Distance.Cosine)
.setOnDisk(true)
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
"{collection_name}",
new VectorParams
{
Size = 768,
Distance = Distance.Cosine,
OnDisk = true
}
);
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: 768,
Distance: qdrant.Distance_Cosine,
OnDisk: qdrant.PtrOf(true),
}),
})
这将创建一个集合,其中所有向量立即存储在内存映射存储中。如果您的 Qdrant 实例使用快速磁盘并且您正在处理大型集合,这是推荐的方式。
- 设置
memmap_threshold
选项。此选项将设置段转换为内存映射存储的阈值。
有两种方法可以做到这一点
PUT /collections/{collection_name}
{
"vectors": {
"size": 768,
"distance": "Cosine"
},
"optimizers_config": {
"memmap_threshold": 20000
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
optimizers_config=models.OptimizersConfigDiff(memmap_threshold=20000),
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: {
size: 768,
distance: "Cosine",
},
optimizers_config: {
memmap_threshold: 20000,
},
});
use qdrant_client::qdrant::{
CreateCollectionBuilder, Distance, OptimizersConfigDiffBuilder, VectorParamsBuilder,
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(768, Distance::Cosine))
.optimizers_config(OptimizersConfigDiffBuilder::default().memmap_threshold(20000)),
)
.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.OptimizersConfigDiff;
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(768)
.setDistance(Distance.Cosine)
.build())
.build())
.setOptimizersConfig(
OptimizersConfigDiff.newBuilder().setMemmapThreshold(20000).build())
.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 = 768, Distance = Distance.Cosine },
optimizersConfig: new OptimizersConfigDiff { MemmapThreshold = 20000 }
);
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: 768,
Distance: qdrant.Distance_Cosine,
}),
OptimizersConfig: &qdrant.OptimizersConfigDiff{
MaxSegmentSize: qdrant.PtrOf(uint64(20000)),
},
})
设置内存映射阈值参数的经验法则很简单
- 如果您有均衡的使用场景 - 将内存映射阈值设置为与
indexing_threshold
相同(默认为 20000)。在这种情况下,优化器不会进行任何额外的运行,并会一次性优化所有阈值。 - 如果您有高写入负载和低 RAM - 将内存映射阈值设置得比
indexing_threshold
低,例如 10000。在这种情况下,优化器会首先将段转换为内存映射存储,然后才应用索引。
此外,您不仅可以将内存映射存储用于向量,还可以用于 HNSW 索引。要启用此功能,您需要在集合创建或更新期间将 hnsw_config.on_disk
参数设置为 true
。
PUT /collections/{collection_name}
{
"vectors": {
"size": 768,
"distance": "Cosine",
"on_disk": true
},
"hnsw_config": {
"on_disk": true
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE, on_disk=True),
hnsw_config=models.HnswConfigDiff(on_disk=True),
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: {
size: 768,
distance: "Cosine",
on_disk: true,
},
hnsw_config: {
on_disk: true,
},
});
use qdrant_client::qdrant::{
CreateCollectionBuilder, Distance, HnswConfigDiffBuilder,
VectorParamsBuilder,
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.vectors_config(VectorParamsBuilder::new(768, Distance::Cosine).on_disk(true))
.hnsw_config(HnswConfigDiffBuilder::default().on_disk(true)),
)
.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.HnswConfigDiff;
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(768)
.setDistance(Distance.Cosine)
.setOnDisk(true)
.build())
.build())
.setHnswConfig(HnswConfigDiff.newBuilder().setOnDisk(true).build())
.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 = 768, Distance = Distance.Cosine, OnDisk = true },
hnswConfig: new HnswConfigDiff { OnDisk = true }
);
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: 768,
Distance: qdrant.Distance_Cosine,
OnDisk: qdrant.PtrOf(true),
}),
HnswConfig: &qdrant.HnswConfigDiff{
OnDisk: qdrant.PtrOf(true),
},
})
载荷存储
Qdrant 支持两种类型的载荷存储:内存存储 (InMemory) 和磁盘存储 (OnDisk)。
内存载荷存储的组织方式与内存向量相同。载荷数据在服务启动时加载到 RAM 中,而磁盘和 Gridstore 仅用于持久化。这种类型的存储速度相当快,但可能需要大量空间来将所有数据保存在 RAM 中,特别是当载荷附带大值时 - 例如文本摘要甚至图像。
对于大型载荷值,使用磁盘载荷存储可能更好。这种类型的存储会将载荷直接读写到 RocksDB,因此不需要大量的 RAM 来存储。然而,缺点是访问延迟。如果您需要使用一些基于载荷的条件查询向量 - 检查存储在磁盘上的值可能需要太多时间。在这种情况下,我们建议为过滤条件中使用的每个字段创建载荷索引,以避免磁盘访问。一旦创建字段索引,Qdrant 将无论载荷存储类型如何,都在 RAM 中保留索引字段的所有值。
您可以使用配置文件或在集合创建期间使用集合参数 on_disk_payload
指定所需的载荷存储类型。
版本控制
为了确保数据完整性,Qdrant 分两个阶段执行所有数据更改。第一步,数据被写入预写日志(WAL),它对所有操作进行排序并为其分配一个序列号。
一旦更改被添加到 WAL 中,即使发生断电也不会丢失。然后更改进入段。每个段存储应用于该段的最后一次更改的版本以及每个单独点的版本。如果新的更改的序列号小于该点当前的版本,更新器将忽略该更改。这种机制允许 Qdrant 在发生异常关闭时安全高效地从 WAL 中恢复存储。