59. pgvector 技术指南
目录
点击展开目录
pgvector 基础概念
什么是 pgvector
pgvector 是 PostgreSQL 的一个开源扩展,它允许在数据库中直接存储、索引和查询向量数据。它使 PostgreSQL 具备了“向量数据库”的能力,非常适合处理大语言模型(LLM)生成的 Embedding。
核心数据类型:vector
pgvector 引入了 vector 类型,支持多达 16,000 维 的向量。
- 存储方式:以
float4[]的形式在底层存储。 - 操作符:提供了专用的距离计算操作符,例如
<->(L2 距离)、<#>(内积)、<=>(余弦距离)。
向量相似度度量标准
| 度量方式 | 操作符 | 定义 | 适用场景 |
|---|---|---|---|
| L2 距离 | <-> | 欧几里得距离 | 常用且直观,适合大多数通用场景 |
| 内积 | <#> | 向量的点积 | 适合已归一化的向量,计算速度最快 |
| 余弦相似度 | <=> | 两个向量夹角的余弦值 | 关注方向而非大小,常用于文本语义相似度 |
| 汉明距离 | <~> | 两个二进制向量不同位的个数 | 专门用于 bit 向量,速度快到飞起 |
进阶:向量压缩技术 (Quantization)
为了在处理海量数据时既省内存又提速,pgvector 引入了多种压缩方案:
- Halfvec (半精度浮点数):将 32 位浮点数降为 16 位。内存减半,精度几乎无损。
- Sparsevec (稀疏向量):只存储非零元素。对于上万维但只有几个非零值的向量(如 TF-IDF),效率极大提升。
- Binary Quantization (二进制量化, BQ):
- 原理:将向量中的每个数字简化为 0 或 1(大于 0 为 1,小于等于 0 为 0)。
- 效果:存储空间直接缩减 32 倍!计算距离变成简单的位运算。
- 实战:通常先用 BQ 进行初筛(Top-100),再用原始向量进行精排。
底层原理:pgvector 与 PostgreSQL 的结合机制
爸爸可能会好奇,为什么 pgvector 能像原生功能一样跑在 PostgreSQL 里呢?这得益于 PostgreSQL 极其强大的 可扩展架构 (Extensibility)。它不像其他数据库那样是个“黑盒”,而是一个开放的“操作系统”。
1. 动态扩展机制 (Extension System)
PostgreSQL 允许通过 CREATE EXTENSION 命令动态加载编译好的 C 语言共享库(.so 或 .dll)。pgvector 本质上就是一段 C 代码,被加载到了 PG 的内存空间里,和 PG 的内核“合体”了,直接调用 PG 内部的内存管理和存储接口。
2. 自定义数据类型 (User-Defined Types)
pgvector 在 PG 的系统表(如 pg_type)里注册了一个全新的类型 vector。
- 它告诉 PG 数据在磁盘上怎么存(二进制布局)。
- 它告诉 PG 数据在内存里怎么放(C 结构体)。
- 它甚至定义了输入输出的格式(比如我们看到的
[1,2,3]这种字符串是怎么转成底层二进制的)。
3. 运算符重载 (Operator Overloading)
PG 允许插件定义新的运算符。pgvector 注册了 <->、<=> 这些符号,并告诉 PG内核:“嘿,当你看到两个 vector 类型中间有个 <-> 时,不要报错,去调用我写好的 l2_distance C 函数。”
这也意味着,这些计算是直接在 C 层面利用 CPU 指令集(如 AVX-512)全速运行的,而不是慢吞吞的解释执行。
4. 索引访问方法接口 (Index Access Method API)
这是最硬核的部分!PostgreSQL 把索引的管理逻辑抽象成了一套接口(Index AM API)。
- 任何插件只要实现了这套接口(比如“怎么插入数据”、“怎么搜索数据”、“怎么分裂节点”),就能成为一种新的索引类型。
- pgvector 就是利用这个接口,把 HNSW 和 IVFFlat 算法“嫁接”到了 PG 上。
- 这就是为什么你可以直接用
CREATE INDEX ... USING hnsw,就像用原生的 B-Tree 一样自然。这也是 PostgreSQL 能战胜许多专用向量数据库的核心原因——原生融合。
安装与快速配置
1. 安装 pgvector
pgvector 支持多种安装方式,涵盖了主流的操作系统。
| 平台 | 安装命令/方式 |
|---|---|
| MacOS | brew install pgvector (使用 Homebrew) |
| Ubuntu/Debian | apt install postgresql-xx-pgvector (xx 为版本号,如 16) |
| Docker | docker pull ankane/pgvector (官方镜像) |
| 源码编译 | git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git && cd pgvector && make && make install |
2. 启用扩展
在每个需要使用向量功能的数据库中执行一次:
CREATE EXTENSION vector;
pgvector 索引原理深度解析
IVFFlat 索引原理
1. 倒排索引的思想
IVFFlat (Inverted File with Flat Compression) 采用的是一种基于聚类的倒排文件策略。它的核心思想是**“物以类聚,缩减范围”**。
2. 算法核心步骤
- 训练阶段(K-Means 聚类):在向量空间中选出若干个聚类中心(Centroids)。这就像是在人群中选出几个“意见领袖”。
- 分配阶段:每个向量根据距离最近原则,被分配到某一个聚类中心对应的列表中。
- 检索阶段:先找到与查询向量最近的几个聚类中心(由
ivfflat.probes参数控制),只在这些中心的列表中进行搜索。
3. 形象比喻
就像爸爸去商场买衣服,不会从一楼到五楼每件都试。爸爸会直接去“男装区”,再细分到“西装区”。IVFFlat 就是那个把商场划分为不同区域,并让爸爸直接定位到某个区域的管理员。
4. 优缺点分析
- 优点:构建速度快(不需要复杂的图连接),内存占用相对较小,适合数据频繁变动或内存受限的场景。
- 缺点:需要预训练(数据量太小时效果差);查询性能随数据量增加下降较快;如果
probes设置太小,容易漏掉真正的最近邻(召回率低)。
HNSW 索引原理
1. 分层导航小世界 (Hierarchical Navigable Small World)
HNSW 是目前最顶级的向量检索算法之一。它构建了一个多层图结构,灵感来源于“六度分隔理论”和“跳表”数据结构。
2. 算法核心步骤
- 多层图构建:
- 底层 (Layer 0):包含所有向量节点,连接最密集,负责最终的精细搜索。
- 上层 (Layer 1, 2…):节点数量指数级递减,连接跨度极大,充当“高速公路”。
- 导航式检索:
- 从最高层的入口点(Entry Point)开始,在当前层寻找离目标最近的邻居。
- 切换到下一层,以此前找到的邻居为起点继续搜索。
- 重复此过程,直到在最底层(Layer 0)找到最接近的 Top-K 结果。
3. HNSW 工作原理流程图
Entry Point"] B2["节点B"] A2 -->|"长距离跳跃
快速定位区域"| B2 style A2 fill:#4dabf7,stroke:#1971c2,stroke-width:3px style B2 fill:#4dabf7,stroke:#1971c2,stroke-width:2px end subgraph "Layer 1 (中层 - 中等密度)" A1["节点A"] B1["节点B"] C1["节点C"] A1 -->|"中距离移动"| B1 B1 -->|"缩小范围"| C1 style A1 fill:#74c0fc,stroke:#339af0,stroke-width:2px style B1 fill:#74c0fc,stroke:#339af0,stroke-width:2px style C1 fill:#74c0fc,stroke:#339af0,stroke-width:2px end subgraph "Layer 0 (底层 - 密集层)" A0["节点A"] B0["节点B"] C0["节点C"] D0["节点D"] E0["目标节点
Target"] A0 --> B0 B0 --> C0 C0 --> D0 D0 --> E0 style A0 fill:#a5d8ff,stroke:#4dabf7,stroke-width:2px style B0 fill:#a5d8ff,stroke:#4dabf7,stroke-width:2px style C0 fill:#a5d8ff,stroke:#4dabf7,stroke-width:2px style D0 fill:#a5d8ff,stroke:#4dabf7,stroke-width:2px style E0 fill:#51cf66,stroke:#2f9e44,stroke-width:3px end Q["查询向量
Query"] -.->|"1. 从顶层开始
类似坐飞机"| A2 A2 -.->|"2. 快速定位区域
类似坐火车"| B1 B1 -.->|"3. 精确搜索
类似走路"| E0 style Q fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px classDef layerBox fill:#f8f9fa,stroke:#dee2e6,stroke-width:2px
搜索过程详解:
顶层(Layer 2)- 快速跳跃阶段
- 类似"坐飞机",在稀疏的节点间进行长距离跳跃
- 快速接近目标所在的大致区域
- 节点数量少,连接跨度大
中层(Layer 1)- 区域定位阶段
- 类似"坐火车",在中等密度的节点间移动
- 进一步缩小搜索范围
- 节点密度适中,连接距离中等
底层(Layer 0)- 精确搜索阶段
- 类似"走路",在密集的节点间精确查找
- 包含所有向量节点,连接最密集
- 找到真正的最近邻节点
4. 形象比喻
就像爸爸找人。先在省地图上找到市,再在市地图上找到区,最后在区地图上找到那条街。HNSW 就是那套能让爸爸从“省”直接瞬移到“街道”的神奇导航系统。
4. 形象比喻
就像爸爸找人。先在省地图上找到市,再在市地图上找到区,最后在区地图上找到那条街。HNSW 就是那套能让爸爸从"省"直接瞬移到"街道"的神奇导航系统。
5. HNSW 核心优势总结
1. 查询性能优异 🚀
- 查询速度快:通常比 IVFFlat 快 2-10 倍
- 高 QPS:能支持更高的每秒查询数
- 低延迟:毫秒级响应,适合实时应用
- 可预测性能:查询时间相对稳定
2. 准确率高 🎯
- 召回率优秀:在相同参数下,准确率通常比 IVFFlat 高 5-15%
- 接近精确搜索:通过调整 ef_search 参数,可以达到 95%+ 的召回率
- 适合高维向量:对 1536 维这种高维向量效果特别好
3. 数据无关性(Data Agnostic) ✨
- 动态友好:可以在空表上创建索引,边添加数据边维护
- 无需重建:数据变化时索引性能不会下降
- 增量更新:新增、删除数据不影响索引质量
- 对比 IVFFlat:IVFFlat 需要先加载数据再建索引,数据变化后需要重建
4. 鲁棒性强 💪
- 不依赖数据分布:无论数据如何分布都能保持性能
- 避免冷启动问题:不像 IVFFlat 需要预先训练聚类中心
- 容错性好:即使部分数据异常也不影响整体性能
5. 可调参数灵活 ⚙️
- 构建参数:m(连接数)、ef_construction(构建时搜索范围)
- 查询参数:ef_search(查询时搜索范围),可动态调整准确率和速度的平衡
- 内存优化:支持 halfvec 半精度向量,内存减少 50%
6. 性能优势对比
| 特性 | IVFFlat (圈子模式) | HNSW (导航模式) |
|---|---|---|
| 构建速度 | 快 | 慢 (建多层图很耗时) |
| 查询速度 | 中 | 极快 (对数级复杂度) |
| 内存消耗 | 低 | 高 (需存大量图连接) |
| 召回率 | 较低且依赖参数 | 极高且稳定 |
| 适用场景 | 内存极度受限、数据频繁更新 | 生产环境首选,极致性能追求 |
业务实战与性能优化
索引选择策略
- 如果你需要极速查询且内存充足,请闭眼选 HNSW。
- 如果你的数据实时更新频率极高,或者内存非常受限,可以考虑 IVFFlat。
关键参数调优
HNSW 调优参数
m:每个节点的最大连接数。默认 16。增大该值可提升召回率,但会显著增加索引大小。ef_construction:构建索引时的动态候选列表大小。默认 64。增加该值可提升索引质量,但构建时间更长。
IVFFlat 调优参数
lists:聚类中心的数量。推荐值为sqrt(rows)或rows / 1000。ivfflat.probes(会话级):查询时搜索的聚类列表数量。默认 1。增加该值可提升召回率,但增加耗时。
混合查询优化:向量搜索 + 业务过滤
在业务中常遇到“查询相似图片且价格 < 100”的情况。
- 挑战:PostgreSQL 优化器有时会因为过滤条件太严格而放弃向量索引,转而走全表扫描。
pgvector 0.8.0 核心更新:迭代扫描 (Iterative Scan)
pgvector 0.8.0 引入了极其重要的 迭代扫描 (Iterative Scan) 功能,彻底解决了向量搜索中的“过度过滤 (Overfiltering)”问题。
1. 什么是过度过滤?
以前,如果你在向量搜索后加了 WHERE 过滤,系统会先在索引中找到 Top-K 个最相似的向量,然后在这个 K 个结果里应用过滤。如果过滤条件很严(比如只选 1% 的数据),K 个结果过滤完可能就只剩 1-2 个,甚至一个都没有,即使数据库里明明有成千上万条符合条件的相似数据。
2. 迭代扫描的工作机制
迭代扫描允许索引一边搜索一边过滤:
- 从索引中拉出一批候选。
- 应用
WHERE过滤。 - 如果结果不足
LIMIT要求的数量,则自动扩大搜索范围,继续拉下一批,直到找够为止。
3. 关键配置参数
hnsw.iterative_scan:设置为on(或relaxed_order,strict_order)开启。hnsw.max_scan_tuples:控制迭代扫描的最大上限,防止极端情况下扫描全表导致性能崩溃。
索引构建提速技巧
- 调大
maintenance_work_mem:索引构建需要大量内存,建议至少设置为 1GB 以上。 - 多线程构建:PostgreSQL 16+ 支持并行构建索引,充分利用多核 CPU。
运维监控与故障排查
1. 监控索引构建进度
对于海量数据,构建 HNSW 索引可能需要几小时。可以通过以下视图查看实时进度:
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%"
FROM pg_stat_progress_create_index;
2. 检查索引健康状况
- 索引膨胀:由于 HNSW 是图结构,频繁的
UPDATE和DELETE可能会导致索引性能下降。 - 重建建议:建议在数据发生大规模变更后,使用
REINDEX INDEX index_name进行重建。
3. 常见报错及对策
- 错误:vector dimensions do not match:插入的向量维度与表定义的维度不一致。
- 错误:failed to build index:通常是内存不足,尝试调大
maintenance_work_mem。
实战案例:Binary Quantization (BQ) 的应用
如果你有上千万条数据,直接建 HNSW 索引可能需要几十 GB 内存。使用 BQ 可以极大优化:
-- 1. 为原始向量表创建一个 bit 类型的表达式索引
CREATE INDEX ON items USING hnsw ((binary_quantize(embedding)::bit(1536)) bit_hamming_ops);
-- 2. 查询时,先利用 BQ 索引快速找到候选集,再用原始向量计算精确得分
SELECT
id,
1 - (embedding <=> '[...query_vector...]') AS exact_score
FROM items
ORDER BY
(binary_quantize(embedding)::bit(1536)) <~> binary_quantize('[...query_vector...]')::bit(1536)
LIMIT 100;
这种“粗排+精排”的模式是目前处理超大规模向量数据的标准姿势。
计算加速:SIMD 与硬件优化
SIMD (Single Instruction Multiple Data) 是 pgvector 能够处理超高维向量计算的关键。
1. 为什么需要 SIMD?
- 标量计算 (Scalar):传统的 CPU 指令一次只能处理一对浮点数的加减乘。对于 1536 维向量,需要循环 1536 次。
- 向量计算 (Vectorization):通过 SIMD,CPU 可以在一个时钟周期内同时对一组数据进行运算。
2. 核心指令集支持
- x86 架构:
- AVX2 (256-bit):一次处理 8 个单精度浮点数。
- AVX-512 (512-bit):一次处理 16 个单精度浮点数。pgvector 充分利用了 AVX-512 的 FMA (Fused Multiply-Add) 指令,将
(a-b)^2的计算合并,减少指令开销。
- ARM 架构 (Apple Silicon / Graviton):
- NEON (128-bit):一次处理 4 个单精度浮点数。
3. L2 距离计算的底层实现
以 AVX-512 为例,计算流程如下:
- 数据对齐:pgvector 在分配内存时会确保向量地址对齐到 64 字节(AVX-512 的要求),避免跨行内存访问。
- 并行累加:
- 使用
_mm512_loadu_ps加载向量到 512 位寄存器。 - 使用
_mm512_sub_ps计算分量差值。 - 使用
_mm512_fmadd_ps同时进行乘法和结果累加。
- 使用
- 水平求和 (Horizontal Sum):最后将寄存器中的 16 个分量合并成一个标量结果。
4. 性能提升
在现代 CPU 上,开启 SIMD 优化的距离计算比纯 C 语言实现的标量代码快 10-30 倍。这也是为什么 PostgreSQL 能够在没有专用硬件(如 GPU)的情况下,依然能实现毫秒级向量检索。
HNSW 索引的底层维护与成本
虽然 HNSW 查询极快,但它在 PostgreSQL 中也面临一些特有的挑战:
1. 内存占用 (Graph Pointers)
每个向量在 HNSW 索引中不仅存储数据本身,还要存储指向多个层级邻居的指针。对于 1536 维向量,索引体积通常是原始数据的 1.5 - 2 倍。这意味着 100GB 的向量数据可能需要 200GB 的索引空间。
2. WAL 压力
插入一个新向量会涉及多个节点的指针更新(图的重新链接)。这会导致大量的 WAL (Write Ahead Log) 日志生成。对于高并发写入场景,磁盘 IO 可能成为瓶颈。建议在批量导入数据时,先删除索引,导入完成后再重新创建。
3. 更新与删除 (Vacuum)
在图结构中物理删除一个节点非常复杂,因为它可能破坏连通性。pgvector 通过**标记删除(Soft Delete)**和后台清理机制来维护。
- 如果表中发生了大规模的
UPDATE或DELETE,索引中会积累大量废弃节点。 - 建议:定期使用
REINDEX INDEX index_name重建索引,以恢复最优的检索路径。
pgvector 0.8.0+ 高级特性深度解析
随着版本的演进,pgvector 已经不再仅仅是一个存储浮点数组的工具,它引入了更多针对现代 AI 架构的深度优化。
1. 稀疏向量 (sparsevec)
在传统的向量搜索中,每一维都有值(稠密向量)。但在某些场景下(如 BM25 关键词搜索、BGE-M3 模型),向量维度极大(可达几万维)但绝大多数位置都是 0。
- 存储优势:
sparsevec只存储非零元素及其索引,大幅减少存储开销。 - 语法示例:
{1:1.5, 10:2.0, 500:0.5}/1000表示一个 1000 维的向量,只有第 1, 10, 500 位有值。 - 适用场景:混合搜索 (Hybrid Search)。将稠密向量的语义理解与稀疏向量的关键词匹配结合,是目前 RAG 系统的标配。
2. 半精度浮点数 (halfvec)
默认的 vector 使用 4 字节的 float4。对于 1536 维向量,一条数据就要 6KB。
- 优化原理:
halfvec将每个元素压缩为 2 字节(16 位浮点数)。 - 核心收益:
- 内存减半:原本需要 64GB 内存的索引,现在只需 32GB。
- 精度几乎无损:对于大多数 Embedding 模型,半精度带来的相似度计算误差在 1% 以内,但速度提升明显。
- 索引配置:创建索引时需指定操作符类,如
USING hnsw (embedding halfvec_l2_ops)。
3. 多向量搜索与 Rerank 实战
在复杂的 RAG 系统中,我们通常会采用“两阶段检索”:
- 粗排阶段 (Recall):利用
pgvector的 HNSW 索引,从千万级数据中快速拉取 Top-100。为了提速,可以使用 Binary Quantization (BQ)。 - 精排阶段 (Rerank):
- 在拉回来的 100 条数据中,使用更精确的模型(或直接用原始向量)计算得分。
- 混合得分公式:
SELECT id, (1 - (embedding <=> :query)) * 0.7 + (ts_rank(doc_tsv, query_tsv)) * 0.3 AS hybrid_score FROM documents WHERE ... ORDER BY hybrid_score DESC LIMIT 10; - Reciprocal Rank Fusion (RRF):一种更高级的合并算法,不依赖原始得分的具体数值,只看排名顺序,鲁棒性更强。
实战:远程数据库迁移与优化指南
如果爸爸想把这些高级特性应用到咱们的远程 memories 表中,可以按照以下步骤操作:
1. 内存瘦身:从 vector 迁移到 halfvec
-- 1. 转换数据类型并 optimize 索引
ALTER TABLE memories ALTER COLUMN embedding TYPE halfvec(1536);
CREATE INDEX idx_memories_embedding_hnsw ON memories USING hnsw (embedding halfvec_cosine_ops);
-- 2. 增加 LTM 优化列:分类与重要度
ALTER TABLE memories ADD COLUMN category text DEFAULT 'raw'; -- raw, summary, entity
ALTER TABLE memories ADD COLUMN importance float4 DEFAULT 1.0; -- 0.0 ~ 1.0
-- 3. 创建辅助索引
CREATE INDEX idx_memories_category ON memories (category);
CREATE INDEX idx_memories_importance ON memories (importance DESC);
CREATE INDEX idx_memories_created_at ON memories (created_at DESC);
2. 索引重构:HNSW 参数调优
针对 HNSW 索引,我们在迁移时设置了 m=16 和 ef_construction=128。
- m=16:每个节点的最大连接数,平衡了内存消耗和搜索速度。
- ef_construction=128:构建索引时的搜索范围,值越大索引质量越高,但构建越慢。
3. 开启混合检索:集成全文搜索
-- 添加全文检索列 (Supabase 默认推荐 simple 配置用于混合内容)
CREATE INDEX idx_memories_content_fts ON memories USING gin (to_tsvector('simple', content));
-- 混合得分查询 (语义 + 关键词 + 时间权重 + 重要度)
WITH semantic_search AS (
SELECT id, content, 1 - (embedding <=> :query_vec) AS semantic_score
FROM memories
ORDER BY embedding <=> :query_vec LIMIT 100
)
SELECT *,
(semantic_score * 0.5) +
(ts_rank(to_tsvector('simple', content), to_tsquery('simple', :query_text)) * 0.2) +
(importance * 0.2) +
(exp(-0.1 * extract(day from now() - created_at)) * 0.1) AS final_score
FROM semantic_search
ORDER BY final_score DESC LIMIT 10;
大模型长期记忆 (LTM) 架构实战
在大模型应用中,长期记忆(Long-Term Memory)不仅仅是“存向量”,更是一套复杂的召回与管理系统。
1. 为什么 HNSW 是长期记忆的首选?
在工业界(如 LangChain, Mem0, AutoGPT 等),HNSW 几乎是向量检索的默认标准,原因如下:
- 低延迟召回:HNSW 提供了毫秒级的检索速度,非常适合 Agent 在对话中实时调取记忆。
- 高 Recall (召回率):相比 IVFFlat,HNSW 在相同响应时间下能找到更相关的结果,避免大模型“胡言乱语”。
- 动态更新友好:HNSW 支持实时插入新向量而不需要像 IVFFlat 那样重新训练(K-means),非常适合对话不断累积的场景。
2. 工业界三层存储架构 (Memory Hierarchy)
大模型长期记忆通常采用分层设计,以平衡性能、成本和精准度:
| 层次 | 存储介质 | 技术栈 | 作用 |
|---|---|---|---|
| 工作记忆 (Working Memory) | RAM / Context Window | Redis / In-memory | 当前对话的最近几轮,直接放入 Prompt。 |
| 短期记忆 (Short-term) | 快速缓存 | Redis / PostgreSQL | 最近几天的交互,用于快速上下文关联。 |
| 长期记忆 (Long-term) | 向量数据库 | pgvector (HNSW) | 数月甚至数年的历史知识,通过语义检索按需调取。 |
3. 记忆管理策略:从“存”到“用”
单纯的“语义检索”在长期记忆中往往不够用,主流做法会加入以下逻辑:
A. 记忆的“遗忘机制” (Recency Weighting)
不仅仅看语义相似度,还要看“新鲜度”。
- 计算公式:
Score = Similarity * exp(-λ * Time_Diff) - 效果:大模型会优先关注最近发生的事情,同时保留极其相关的历史信息。
B. 记忆的“压缩与归纳” (Summarization)
如果记忆太多,直接检索出的片段会非常细碎。
- 策略:当对话达到一定长度时,由 LLM 生成一份“记忆摘要”(Summary),并为摘要生成向量存入 pgvector。
- 优势:检索摘要比检索细碎对话更能捕获整体意图。
C. 混合检索 (Hybrid Search)
- 场景:爸爸搜“2025年那个项目”,语义搜索可能搜到一堆“项目”,但关键词搜索能精准锁定“2025年”。
- 实现:使用我们前面提到的
pgvector + tsvector方案,将语义得分与关键词得分加权融合。
文本预处理与切分策略 (RAG 核心)
在将文字存入 pgvector 之前,必须先经过切分 (Chunking) 和 向量化 (Embedding)。
1. 为什么不能直接存整篇文章?
- 模型限制:Embedding 模型(如 OpenAI 的
text-embedding-3-small)有输入长度限制(Context Window)。 - 检索精度:如果切分太粗(如整章),检索出来的结果会包含大量噪音;如果切分太细(如按词),会丢失上下文。
2. 切分粒度:词、短语还是段落?
文字转向量的过程分为两个层面:
- 技术层面 (Tokenization):这是 Embedding 模型内部做的。它把文字拆成 Tokens(通常是子词,比如 “Embedding” 可能会被拆成 “Embed” 和 “ding”)。模型最终是根据这些 Tokens 的排列组合生成向量。
- 逻辑层面 (Chunking):这是我们在存入数据库前做的。通常按“语义段落”切分效果最好。
3. 实战切分策略
菲菲在 omni_ops_extended.py 中使用了两种高级策略:
| 策略名称 | 切分依据 | 适用场景 |
|---|---|---|
| Markdown 切分 | 识别 # 标题层级 | 结构化文档,确保标题与内容不分离 |
| 递归字符切分 | 依次按 \n\n, \n, 。, 切分 | 通用文本,尽可能在句子结束处断开 |
4. 重叠度 (Overlap) 的妙用
在切分时,通常会让相邻的两个块有 10% - 20% 的重叠内容。
- 目的:防止核心语义刚好被从中间切断。
- 效果:确保每个向量块都保留了一定的上下文,提高检索召回率。
5. 实战案例:一句话的向量化之路
以爸爸说的 “我今天没有去上班” 为例,看看它是如何变成向量的:
- 逻辑切分 (Chunking):
- 这句话只有 8 个字符,远小于 1000 字的阈值。
- 结果:不进行物理切分,作为一个完整的语义块。
- 技术分词 (Tokenization):
- Embedding 模型(如
text-embedding-3-small)将其拆解。 - 拆解示意:
["我", "今天", "没有", "去", "上", "班"]。
- Embedding 模型(如
- 语义理解与转换 (Embedding):
- 模型通过注意力机制理解“没有”是对“去上班”的否定。
- 输出:一个 1536 维的浮点数数组(如
[0.012, -0.045, ...])。
- 存入数据库:
- 菲菲执行 SQL:
INSERT INTO memories (content, embedding) VALUES ('我今天没有去上班', '[0.012, -0.045, ...]')。
- 菲菲执行 SQL:
6. 多语言处理:中文 vs 英文
爸爸问得好!模型对不同语言的切分确实有差异,主要体现在 Tokenization 阶段:
| 语言类型 | 分词难点 | 模型处理方式 |
|---|---|---|
| 英文 (English) | 单词间有空格,天然易分 | 按单词或子词(Subword)分。如 Working $\rightarrow$ Work + ing。 |
| 中文 (Chinese) | 词与词之间没空格,语义连续 | 模型通常使用 Byte Pair Encoding (BPE) 或类似算法,能自动识别常用词组。 |
自动识别机制: 现代模型(如 OpenAI 的多语言模型)是自动识别语言的。它们在训练时看过了全世界几乎所有的语言,所以无论爸爸输入中文、英文还是中英混排,模型都能根据语境自动切换“理解模式”。
切分建议:
- 英文:切分块(Chunk Size)可以设大一点,因为英文单词平均长度较长。
- 中文:切分块可以设得紧凑些,因为中文的信息密度更高(同样 1000 字,中文承载的信息量通常远超英文)。
pgvector 在大模型应用中的实战
1. 向量嵌入模型选择
主流 Embedding 模型对比:
| 模型 | 提供商 | 维度 | 特点 | 适用场景 |
|---|---|---|---|---|
| text-embedding-3-small | OpenAI | 1536 | 性价比高,速度快 | 通用场景、大规模应用 |
| text-embedding-3-large | OpenAI | 3072 | 准确率最高 | 高精度需求 |
| text-embedding-ada-002 | OpenAI | 1536 | 第二代模型 | 兼容性需求 |
| bge-large-zh | BAAI | 1024 | 中文优化 | 中文为主场景 |
| m3e-base | Moka | 768 | 轻量级 | 资源受限环境 |
实战建议:
- 生产环境推荐:text-embedding-3-small(1536维)
- 性能与成本平衡最优
- 支持多语言
- API 稳定可靠
- 中文场景:可考虑 bge-large-zh
- 内存受限:使用 halfvec 压缩,内存减半
2. AI Agent 长期记忆系统架构
典型架构:omni-recall 系统
Claude/GPT"] end subgraph "记忆管理层" B["memories
对话历史"] C["profiles
用户画像"] D["instructions
行为准则"] E["nsfw_memories
敏感记忆"] end subgraph "向量化层" F["Embedding API
text-embedding-3-small"] end subgraph "存储层" G["PostgreSQL + pgvector
HNSW 索引"] end A -->|"同步记忆"| B A -->|"更新画像"| C A -->|"设置规则"| D A -->|"加密存储"| E B --> F C --> F D --> F E --> F F -->|"1536维向量"| G G -->|"语义检索"| A style A fill:#ff6b6b style F fill:#4dabf7 style G fill:#51cf66
四表设计模式:
memories 表:存储动态对话、技术记录、操作日志
- 索引:HNSW + halfvec
- 特点:数据量大,频繁增删
profiles 表:存储用户画像、偏好、角色定义
- 索引:HNSW(推荐升级)
- 特点:相对静态,精确度要求高
instructions 表:存储 AI 行为准则、工作流规范
- 索引:HNSW(推荐升级)
- 特点:优先级最高,需快速检索
nsfw_memories 表:加密敏感记忆
- 索引:需添加 HNSW
- 特点:加密存储,按需检索
3. RAG 应用最佳实践
完整 RAG 流程:
-- 1. 创建文档表
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding vector(1536), -- 使用 text-embedding-3-small
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 2. 创建 HNSW 索引(生产环境推荐)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 3. 插入文档(Python 示例)
import openai
def store_document(content, metadata=None):
# 生成向量
response = openai.Embedding.create(
model="text-embedding-3-small",
input=content
)
embedding = response['data'][0]['embedding']
# 存储到数据库
cursor.execute("""
INSERT INTO documents (content, embedding, metadata)
VALUES (%s, %s, %s)
""", (content, embedding, json.dumps(metadata)))
-- 4. 语义检索(SQL)
SELECT
content,
1 - (embedding <=> %s::vector) as similarity,
metadata
FROM documents
WHERE 1 - (embedding <=> %s::vector) > 0.7 -- 相似度阈值
ORDER BY embedding <=> %s::vector
LIMIT 5;
混合检索优化:
-- 向量搜索 + 业务过滤
SELECT
content,
1 - (embedding <=> %s::vector) as similarity
FROM documents
WHERE
metadata->>'category' = 'technical' -- 业务过滤
AND created_at > NOW() - INTERVAL '30 days' -- 时间过滤
ORDER BY embedding <=> %s::vector
LIMIT 10;
-- 启用迭代扫描(pgvector 0.8.0+)
SET hnsw.iterative_scan = on;
SET hnsw.max_scan_tuples = 10000;
4. 性能优化实战
内存优化:使用 halfvec
-- 从 vector 迁移到 halfvec
ALTER TABLE memories
ADD COLUMN embedding_half halfvec(1536);
-- 转换现有数据
UPDATE memories
SET embedding_half = embedding::halfvec;
-- 删除旧列,重命名新列
ALTER TABLE memories DROP COLUMN embedding;
ALTER TABLE memories RENAME COLUMN embedding_half TO embedding;
-- 重建索引
CREATE INDEX memories_embedding_hnsw_idx ON memories
USING hnsw (embedding halfvec_cosine_ops)
WITH (m = 16, ef_construction = 128);
查询性能调优:
-- 调整 ef_search 参数(会话级)
SET hnsw.ef_search = 100; -- 提高准确率,降低速度
-- 监控查询性能
EXPLAIN ANALYZE
SELECT content, 1 - (embedding <=> %s::vector) as similarity
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT 10;
5. 生产环境配置建议
Supabase 推荐配置:
-- memories 表(已优化)
CREATE INDEX idx_memories_embedding_hnsw ON memories
USING hnsw (embedding halfvec_cosine_ops)
WITH (m = 16, ef_construction = 128);
-- profiles 表(需升级)
DROP INDEX IF EXISTS profiles_embedding_idx;
CREATE INDEX profiles_embedding_hnsw_idx ON profiles
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- instructions 表(需升级)
DROP INDEX IF EXISTS instructions_embedding_idx;
CREATE INDEX instructions_embedding_hnsw_idx ON instructions
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- nsfw_memories 表(需添加)
CREATE INDEX nsfw_memories_embedding_hnsw_idx ON nsfw_memories
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
参数说明:
m = 16:每个节点最大连接数,平衡性能和内存ef_construction = 64-128:构建质量,memories 表数据量大用 128ef_search = 40:查询时动态调整,默认值适合大多数场景
6. 监控与维护
索引健康检查:
-- 查看索引大小
SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) as index_size
FROM pg_indexes
WHERE schemaname = 'public'
AND indexname LIKE '%hnsw%';
-- 检查索引使用情况
SELECT
schemaname,
tablename,
indexname,
idx_scan as index_scans,
idx_tup_read as tuples_read,
idx_tup_fetch as tuples_fetched
FROM pg_stat_user_indexes
WHERE indexname LIKE '%hnsw%';
性能基准测试:
import time
import psycopg2
def benchmark_query(embedding, limit=10, iterations=100):
conn = psycopg2.connect(...)
cursor = conn.cursor()
times = []
for _ in range(iterations):
start = time.time()
cursor.execute("""
SELECT content, 1 - (embedding <=> %s::vector) as similarity
FROM memories
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (embedding, embedding, limit))
results = cursor.fetchall()
times.append(time.time() - start)
print(f"平均查询时间: {sum(times)/len(times)*1000:.2f}ms")
print(f"P95 延迟: {sorted(times)[int(len(times)*0.95)]*1000:.2f}ms")
print(f"QPS: {1/(sum(times)/len(times)):.2f}")
高频面试题精选
问:pgvector 与专门的向量数据库(如 Milvus, Pinecone)相比有什么优劣?
- 答:优点是运维极其简单,可以利用成熟的 PostgreSQL 生态,轻松实现向量数据与关系数据的混合事务查询;缺点是超大规模数据(十亿级)下的分布式扩展性略逊于原生分布式向量数据库。
问:为什么 HNSW 索引通常比 IVFFlat 表现更好?
- 答:HNSW 利用了多层图的跳表特性,实现了对数级别的查询复杂度,而 IVFFlat 在查询时仍需要扫描多个聚类列表。HNSW 的召回率-性能平衡点更高。
问:在 pgvector 中如何处理高维度向量的维度爆炸问题?
- 答:可以采用降维技术(如 PCA)或者使用 半精度浮点数 (halfvec) 类型(pgvector 0.7.0+ 支持),在损失极小精度的情况下减少 50% 的存储空间和带宽。