--- id: ai.vector-db.selection-pgvector title: 向量库选型和 pgvector 实战:Chroma / pgvector / Milvus / Elasticsearch type: lesson domain: ai stack: [vector-db, postgresql] tags: [向量库选型, pgvector, Chroma, Milvus, Elasticsearch, 元数据过滤] level: intermediate prerequisites: [ai.vector-db.ann-metrics] est_hours: 3 related: [ai.vector-db.scenario.multi-tenant-index, backend.postgresql.index, backend.elasticsearch.index] week: [5, 6] order: 2 --- # 向量库选型和 pgvector 实战:Chroma / pgvector / Milvus / Elasticsearch ## 解释说明 ### 选型对比 | 方案 | 适合 | 优点 | 注意 | | --- | --- | --- | --- | | **Chroma** | 原型、学习、小规模 | 安装即用,Python 内嵌模式 | 大规模、高可用要评估;版本间 API 有变化 | | **pgvector**(PostgreSQL 扩展) | 已有 PostgreSQL、百万级以内、需要和业务数据联查 | 一套数据库;SQL 过滤、事务、备份都复用 | 超大规模和高 QPS 时不如专用向量库;向量索引的内存占用要规划 | | **Milvus** | 千万到亿级、需要分布式 | 多种索引、分区、标量过滤、水平扩展 | 组件多,运维成本高(也有托管版) | | **Elasticsearch / OpenSearch** | 已经用 ES 做全文检索,想要关键词 + 向量混合检索 | 一个引擎做 BM25 + kNN | 向量功能的版本差异较大,以官方文档为准 | | FAISS | 嵌入到程序里的向量检索库 | 快,索引类型全 | 是库不是数据库:没有持久化服务、过滤、权限,需要自己做 | **本计划的选择**:学习阶段用 Chroma 或 numpy 快速理解;项目使用 **pgvector**,因为第 3~4 周已经学了 PostgreSQL,业务数据、权限和向量可以放在同一个库里联查;面试时能讲清楚“为什么不用 Milvus”和“什么规模需要换”。 ### pgvector 要点(以官方文档为准:https://github.com/pgvector/pgvector) - 类型:`vector(n)`;还有 `halfvec`(半精度,省一半空间)、`sparsevec`。 - 运算符:`<->` L2 距离,`<#>` **负**内积,`<=>` 余弦距离(都是越小越近)。 - 索引:`USING hnsw (embedding vector_cosine_ops)`,运算符类必须和查询用的运算符一致(`vector_l2_ops` / `vector_ip_ops` / `vector_cosine_ops`),否则不走索引。 - 查询参数:`SET hnsw.ef_search = 100;`(默认 40);IVFFlat 用 `ivfflat.probes`。 - 过滤:`WHERE tenant_id = $1 ORDER BY embedding <=> $2 LIMIT 10`。过滤条件很严格时,0.8 版本可以开启 `hnsw.iterative_scan` 让索引继续扫描直到凑够结果;或者按租户建部分索引 / 分区。 ## 案例 一个 SaaS 客服系统,50 个租户共 200 万个知识块,平均每个租户 4 万,最大租户 60 万。业务表已经在 PostgreSQL 中。选择 pgvector:块表带 `tenant_id`,HNSW 索引 + `tenant_id` 普通索引;大租户检索结果不足时开启迭代扫描。评估结论写进文档:当总量超过数千万或 QPS 超过单机能力时,再评估 Milvus。 ## 代码示例 pgvector 的表结构和查询(完整可运行版本 [examples/vector-stores/pgvector_demo.py](examples/vector-stores/pgvector_demo.py),已在 PostgreSQL 17 + pgvector 0.8.0 上运行): ```sql CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE chunks ( id BIGSERIAL PRIMARY KEY, tenant_id TEXT NOT NULL, doc_id TEXT NOT NULL, version INT NOT NULL, content TEXT NOT NULL, embedding vector(1024) NOT NULL ); CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops); CREATE INDEX ON chunks (tenant_id); CREATE INDEX ON chunks (doc_id); -- 查询:$1 租户,$2 查询向量 SET hnsw.ef_search = 100; SELECT id, doc_id, content, 1 - (embedding <=> $2) AS similarity FROM chunks WHERE tenant_id = $1 ORDER BY embedding <=> $2 LIMIT 10; ``` Chroma 的写入、元数据过滤、更新删除见 [examples/vector-stores/chroma_demo.py](examples/vector-stores/chroma_demo.py)。 ## 易错点 - 建索引用了 `vector_l2_ops`,查询用 `<=>`:不走索引,变成全表扫描。 - `<#>` 返回的是负内积,排序时直接 `ORDER BY embedding <#> q` 就是内积从大到小;自己再加负号就反了。 - 小表的执行计划显示顺序扫描:数据少时规划器认为顺序扫描更便宜,这是正常的,不是索引失效。 - HNSW 索引默认不支持太高的维度(`vector` 类型的索引上限 2000 维,`halfvec` 4000 维,以官方文档为准),高维模型要选 `halfvec` 或降维。 - 大批量导入时边写边维护 HNSW 索引很慢:先导入再建索引,并适当调大 `maintenance_work_mem`。 - Chroma 不同版本的客户端 / 持久化格式有变化,升级前看迁移说明。 ## 面试常问 - 为什么选 pgvector 而不是 Milvus?什么时候要换? - pgvector 的三种距离运算符分别是什么?索引怎么建才能生效? - 向量和业务数据放一个库有什么好处和风险?