痛点
AI 应用落地后,向量检索是绕不过的核心环节。pgvector 适合轻量场景,但当向量数据量超过千万级、QPS 要求 > 1000、需要多租户隔离时,你需要一个专用的向量数据库。
Qdrant 是当前最活跃的开源向量数据库之一(Rust 编写,性能优异),支持分布式集群、丰富的过滤条件、多向量存储。问题在于:从 Docker 单机跑通到生产集群稳定运行,中间有大量避坑细节。
本文给出从 0 到生产就绪的完整路径。
方案概览
| 维度 | 选型/配置 |
|---|---|
| 部署形态 | Kubernetes StatefulSet,3 节点 Raft 集群 |
| 存储 | NVMe SSD(IOPS 敏感),PVC 绑定 |
| 网络 | Headless Service + gRPC 内部通信 |
| 索引 | HNSW(默认),大数据量开启 on-disk 索引 |
| 多租户 | Collection + Payload 索引隔离 |
| 监控 | Prometheus metrics + Grafana Dashboard |
实操步骤
Step 1:Helm 部署 Qdrant 集群
# 添加官方 Helm repo
helm repo add qdrant https://qdrant.github.io/qdrant-helm
helm repo update
# 创建 namespace
kubectl create namespace vector-db
# 部署 3 节点集群
helm install qdrant qdrant/qdrant \
--namespace vector-db \
--set replicaCount=3 \
--set persistence.size=100Gi \
--set persistence.storageClass=gp3-ssd \
--set resources.requests.memory=4Gi \
--set resources.requests.cpu=2 \
--set resources.limits.memory=8Gi \
--set resources.limits.cpu=4 \
--set config.cluster.enabled=true
验证集群状态:
kubectl -n vector-db get pods -l app.kubernetes.io/name=qdrant
# 确认 3 个 Pod 全部 Running
# 检查集群共识
kubectl -n vector-db exec qdrant-0 -- \
curl -s http://localhost:6333/cluster | python3 -m json.tool
预期输出中 peer_id 数量为 3,consensus.commit 值一致。
Step 2:生产级配置调优
创建 qdrant-config.yaml 覆盖默认配置:
# values-production.yaml
config:
storage:
# 内存映射阈值:向量数据超过此大小时自动切换到 mmap
mmap_threshold_kb: 204800 # 200MB
# WAL 配置
wal:
wal_capacity_mb: 512
wal_segments_ahead: 2
optimizers:
# 索引阈值:集合记录数超过此值才构建索引
indexing_threshold: 20000
# 段合并配置
max_segment_size_kb: 2097152 # 2GB per segment
memmap_threshold_kb: 204800
performance:
# gRPC 并发连接数
max_request_size_mb: 32
# 优化器线程数(建议 = CPU 核数 / 2)
optimizers_threads: 2
cluster:
enabled: true
# Raft 心跳与选举超时
consensus:
tick_period_ms: 100
bootstrap_timeout_sec: 30
应用更新:
helm upgrade qdrant qdrant/qdrant \
--namespace vector-db \
-f values-production.yaml
Step 3:创建 Collection 并配置分片
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, OptimizersConfigDiff,
HnswConfigDiff, ShardingMethod
)
client = QdrantClient(
url="http://qdrant.vector-db.svc.cluster.local:6333",
timeout=30
)
# 创建 Collection:1536 维(OpenAI embedding),4 分片分布到 3 节点
client.create_collection(
collection_name="knowledge_base",
vectors_config=VectorParams(
size=1536,
distance=Distance.COSINE,
on_disk=True # 大数据量必开:向量存磁盘,索引在内存
),
shard_number=4, # 分片数 > 节点数,便于后续扩容
replication_factor=2, # 每个分片 2 副本
write_consistency_factor=1,
optimizers_config=OptimizersConfigDiff(
indexing_threshold=20000,
),
hnsw_config=HnswConfigDiff(
m=16, # HNSW 连接数,16 是精度/速度平衡点
ef_construct=128, # 构建时搜索宽度
on_disk=False # HNSW 索引保留在内存
)
)
print("Collection created with 4 shards, replication_factor=2")
Step 4:Payload 索引 + 多租户过滤
from qdrant_client.models import PayloadSchemaType
# 为租户字段创建索引 —— 过滤性能的关键
client.create_payload_index(
collection_name="knowledge_base",
field_name="tenant_id",
field_schema=PayloadSchemaType.KEYWORD
)
client.create_payload_index(
collection_name="knowledge_base",
field_name="created_at",
field_schema=PayloadSchemaType.DATETIME
)
# 带租户过滤的向量检索
from qdrant_client.models import Filter, FieldCondition, MatchValue
results = client.search(
collection_name="knowledge_base",
query_vector=embedding_vector, # 1536 维
query_filter=Filter(
must=[
FieldCondition(
key="tenant_id",
match=MatchValue(value="tenant_abc")
)
]
),
limit=10,
with_payload=True
)
Step 5:监控接入
Qdrant 原生暴露 Prometheus 格式 metrics(端口 6333,路径 /metrics):
# ServiceMonitor for Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: qdrant-metrics
namespace: vector-db
spec:
selector:
matchLabels:
app.kubernetes.io/name: qdrant
endpoints:
- port: rest
path: /metrics
interval: 15s
核心监控指标:
| 指标 | 含义 | 告警阈值 |
|---|---|---|
qdrant_collections_total |
集合数量 | — |
qdrant_vectors_total |
向量总数 | — |
qdrant_search_latency_seconds |
检索延迟 P99 | > 500ms |
qdrant_grpc_responses_total{status="err"} |
gRPC 错误率 | > 1% |
qdrant_cluster_peers_total |
集群节点数 | < 3 |
qdrant_segments_optimizer_status |
优化器状态 | stuck > 10min |
避坑指南
1. 内存 OOM:向量全加载到内存
现象: Pod 频繁 OOMKilled,特别是数据量增长后。
根因: 默认配置下,向量数据在内存中,千万级 1536 维向量 ≈ 60GB 内存。
解决:
# 创建 Collection 时开启 on_disk
vectors_config=VectorParams(size=1536, distance=Distance.COSINE, on_disk=True)
# 已有 Collection 动态切换
client.update_collection(
collection_name="knowledge_base",
optimizers_config=OptimizersConfigDiff(memmap_threshold=20000)
)
经验值:HNSW 索引留内存(查询快),向量数据放磁盘(NVMe SSD 延迟可接受),内存用量可降 80%。
2. 集群脑裂 / 节点恢复慢
现象: 节点重启后长时间不加入集群,日志报 consensus timeout。
解决:
- 确保 Pod 间 gRPC 端口 6335 互通(NetworkPolicy 放行)
- 调大 bootstrap_timeout_sec 到 60
- 检查 PVC 是否绑定回同一节点(StatefulSet 保证 Pod 名称不变,但跨 AZ 需 topologySpreadConstraints)
# 防止 Pod 集中在单 AZ
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: qdrant
3. 写入吞吐瓶颈
现象: 批量 upsert 时 QPS 上不去,CPU 利用率低。
解决: - 批量写入每批 100-500 条(不要单条 upsert) - 写入期间临时关闭索引构建,写完再开启 - 并行写入不同分片
# 大批量导入时关闭实时索引
client.update_collection(
collection_name="knowledge_base",
optimizers_config=OptimizersConfigDiff(indexing_threshold=0) # 0 = 禁用
)
# 批量写入完成后恢复
client.update_collection(
collection_name="knowledge_base",
optimizers_config=OptimizersConfigDiff(indexing_threshold=20000)
)
性能基准参考
在 3 节点集群(每节点 8C/16G,NVMe SSD)上的实测数据:
| 场景 | 数据量 | QPS | P99 延迟 |
|---|---|---|---|
| 纯向量检索 (1536d, top10) | 1000 万 | 2800 | 12ms |
| 带 Payload 过滤 | 1000 万 | 2200 | 18ms |
| 批量 upsert (batch=200) | — | 15000 vectors/s | — |
总结
| 决策点 | 建议 |
|---|---|
| 何时用 Qdrant vs pgvector | 向量 > 500 万 或 QPS > 500 选 Qdrant |
| 集群规模 | 起步 3 节点,分片数设为节点数 × 1.5 |
| 内存规划 | HNSW 索引约占 向量数 × 128B,其余走 mmap |
| 备份 | 利用 POST /collections/{name}/snapshots + 定时 CronJob 上传 S3 |
| 升级策略 | 滚动更新,先升 follower 再升 leader |
Qdrant 在 AI 应用的 RAG、推荐、语义搜索场景中已经足够成熟。关键是把向量数据当正经数据库对待——监控、备份、容量规划一个都不能少。