1. 业务背景:以图搜图背后的向量检索压力
我们做的是电商平台的“相似商品推荐”功能,商品图片经过ResNet50提取特征后得到768维float向量。上线初期约500万商品,日增2万,后期扩容到1000万。业务对查询性能要求是P99延迟 100 and price < 500"- Qdrant:{"must": [{"key": "brand", "match": {"value": "nike"}}, {"key": "price", "range": {"gte": 100, "lte": 500}}]}`
5. 踩坑与优化:血泪史
Milvus坑
1. etcd过期问题:standalone模式下etcd默认在5分钟后自动压缩,导致集合元数据丢失。解决方案:在milvus.yaml中设置etcd.compaction.interval: 30m。
2. 内存爆掉:默认load模式全量载入内存,1000万条768维向量占内存约30GB。我们改用load_collection(collection_name, replica_number=1, release_memory=True)启用Disk Index,内存降到12GB。
3. 过滤性能差:当过滤条件命中率低于5%时,延迟飙到150ms。原因是Milvus的倒排索引基于bitmap,过滤后需要重排。优化:将brand字段设为keyword类型并指定enable_analyzer: false,延迟降到80ms。
Qdrant坑
1. HNSW参数不敏感:我们以为调大ef_construct能提升召回,结果从128调到256,召回率只提升0.2%,但索引时间增加40%。建议保持默认。
2. segment合并阻塞:默认8个segment,当数据量增长到1000万时,segment合并任务占用大量CPU,导致查询延迟波动大。解决:设置default_segment_number: 16,并启用optimizers.cpu_budget: 4。
3. 内存控制:Qdrant的mmap机制对OS缓存依赖高,我们设置memmap_threshold: 100000后,内存占用稳定在8GB左右,但查询有冷启动问题(首次查询200ms+)。
6. 效果数据:实测对比
压测30分钟后统计结果(1000万向量,768维):
| 指标 | Milvus (IVF_SQ8) | Qdrant (HNSW) |
|---|---|---|
| 内存占用 | 12.3GB | 7.8GB |
| 磁盘占用 | 18.5GB | 27.2GB |
| 构建索引时间 | 47分钟 | 2小时15分钟 |
| 平均延迟 | 42.5ms | 38.7ms |
| P99延迟 | 78ms | 85ms |
| RPS(纯向量) | 2100 | 3200 |
| RPS(含过滤) | 1400 | 2800 |
| 召回率(Recall@10) | 96.8% | 97.2% |
关键发现:
1. 纯向量查询性能Qdrant领先35%以上,主要因为HNSW比IVF_SQ8的图遍历更高效。
2. 含复杂标量过滤时,Qdrant依旧领先,因为它将过滤条件下推到payload索引,而Milvus需要先向量检索再过滤。
3. Milvus索引构建速度碾压Qdrant,如果数据更新频繁(每日增量),Milvus体验更好。
4. 内存占用Qdrant更优,但磁盘占用多50%(HNSW的边数据)。
7. 总结与建议
如果是单机部署、查询性能优先、标量过滤复杂,Qdrant是更省心的选择——部署简单(一个二进制),性能稳定,内存占用低。
如果是分布式扩展(超过5000万向量)、需要频繁增量更新、以及利用Milvus的GPU加速能力,Milvus更适合。
我们最终选了Qdrant,因为业务查询过滤条件复杂且增长量可控(日增2万),单机Qdrant支撑了3000 QPS,双机主备足够。但如果你有PB级数据或者需要多副本高可用,Milvus的架构更完善。
最后提醒一句:向量数据库的选型一定要基于自己的数据分布和查询模式做压测,别信任何博客的“玄学推荐”,包括这篇。