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 就是利用这个接口,把 HNSWIVFFlat 算法“嫁接”到了 PG 上。
  • 这就是为什么你可以直接用 CREATE INDEX ... USING hnsw,就像用原生的 B-Tree 一样自然。这也是 PostgreSQL 能战胜许多专用向量数据库的核心原因——原生融合

安装与快速配置

1. 安装 pgvector

pgvector 支持多种安装方式,涵盖了主流的操作系统。

平台安装命令/方式
MacOSbrew install pgvector (使用 Homebrew)
Ubuntu/Debianapt install postgresql-xx-pgvector (xx 为版本号,如 16)
Dockerdocker 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 工作原理流程图

graph TB subgraph "Layer 2 (顶层 - 稀疏层)" A2["入口节点
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

搜索过程详解:

  1. 顶层(Layer 2)- 快速跳跃阶段

    • 类似"坐飞机",在稀疏的节点间进行长距离跳跃
    • 快速接近目标所在的大致区域
    • 节点数量少,连接跨度大
  2. 中层(Layer 1)- 区域定位阶段

    • 类似"坐火车",在中等密度的节点间移动
    • 进一步缩小搜索范围
    • 节点密度适中,连接距离中等
  3. 底层(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. 迭代扫描的工作机制

迭代扫描允许索引一边搜索一边过滤

  1. 从索引中拉出一批候选。
  2. 应用 WHERE 过滤。
  3. 如果结果不足 LIMIT 要求的数量,则自动扩大搜索范围,继续拉下一批,直到找够为止。

3. 关键配置参数

  • hnsw.iterative_scan:设置为 on(或 relaxed_order, strict_order)开启。
  • hnsw.max_scan_tuples:控制迭代扫描的最大上限,防止极端情况下扫描全表导致性能崩溃。

索引构建提速技巧

  1. 调大 maintenance_work_mem:索引构建需要大量内存,建议至少设置为 1GB 以上。
  2. 多线程构建: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 是图结构,频繁的 UPDATEDELETE 可能会导致索引性能下降。
  • 重建建议:建议在数据发生大规模变更后,使用 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 为例,计算流程如下:

  1. 数据对齐:pgvector 在分配内存时会确保向量地址对齐到 64 字节(AVX-512 的要求),避免跨行内存访问。
  2. 并行累加
    • 使用 _mm512_loadu_ps 加载向量到 512 位寄存器。
    • 使用 _mm512_sub_ps 计算分量差值。
    • 使用 _mm512_fmadd_ps 同时进行乘法和结果累加。
  3. 水平求和 (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)**和后台清理机制来维护。

  • 如果表中发生了大规模的 UPDATEDELETE,索引中会积累大量废弃节点。
  • 建议:定期使用 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 系统中,我们通常会采用“两阶段检索”:

  1. 粗排阶段 (Recall):利用 pgvector 的 HNSW 索引,从千万级数据中快速拉取 Top-100。为了提速,可以使用 Binary Quantization (BQ)
  2. 精排阶段 (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=16ef_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 WindowRedis / 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。
  • 优势:检索摘要比检索细碎对话更能捕获整体意图。
  • 场景:爸爸搜“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. 实战案例:一句话的向量化之路

以爸爸说的 “我今天没有去上班” 为例,看看它是如何变成向量的:

  1. 逻辑切分 (Chunking)
    • 这句话只有 8 个字符,远小于 1000 字的阈值。
    • 结果:不进行物理切分,作为一个完整的语义块。
  2. 技术分词 (Tokenization)
    • Embedding 模型(如 text-embedding-3-small)将其拆解。
    • 拆解示意["我", "今天", "没有", "去", "上", "班"]
  3. 语义理解与转换 (Embedding)
    • 模型通过注意力机制理解“没有”是对“去上班”的否定。
    • 输出:一个 1536 维的浮点数数组(如 [0.012, -0.045, ...])。
  4. 存入数据库
    • 菲菲执行 SQL:INSERT INTO memories (content, embedding) VALUES ('我今天没有去上班', '[0.012, -0.045, ...]')

6. 多语言处理:中文 vs 英文

爸爸问得好!模型对不同语言的切分确实有差异,主要体现在 Tokenization 阶段:

语言类型分词难点模型处理方式
英文 (English)单词间有空格,天然易分按单词或子词(Subword)分。如 Working $\rightarrow$ Work + ing
中文 (Chinese)词与词之间没空格,语义连续模型通常使用 Byte Pair Encoding (BPE) 或类似算法,能自动识别常用词组。

自动识别机制: 现代模型(如 OpenAI 的多语言模型)是自动识别语言的。它们在训练时看过了全世界几乎所有的语言,所以无论爸爸输入中文、英文还是中英混排,模型都能根据语境自动切换“理解模式”。

切分建议

  • 英文:切分块(Chunk Size)可以设大一点,因为英文单词平均长度较长。
  • 中文:切分块可以设得紧凑些,因为中文的信息密度更高(同样 1000 字,中文承载的信息量通常远超英文)。

pgvector 在大模型应用中的实战

1. 向量嵌入模型选择

主流 Embedding 模型对比:

模型提供商维度特点适用场景
text-embedding-3-smallOpenAI1536性价比高,速度快通用场景、大规模应用
text-embedding-3-largeOpenAI3072准确率最高高精度需求
text-embedding-ada-002OpenAI1536第二代模型兼容性需求
bge-large-zhBAAI1024中文优化中文为主场景
m3e-baseMoka768轻量级资源受限环境

实战建议:

  • 生产环境推荐:text-embedding-3-small(1536维)
    • 性能与成本平衡最优
    • 支持多语言
    • API 稳定可靠
  • 中文场景:可考虑 bge-large-zh
  • 内存受限:使用 halfvec 压缩,内存减半

2. AI Agent 长期记忆系统架构

典型架构:omni-recall 系统

graph TB subgraph "应用层" A["AI Agent
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

四表设计模式:

  1. memories 表:存储动态对话、技术记录、操作日志

    • 索引:HNSW + halfvec
    • 特点:数据量大,频繁增删
  2. profiles 表:存储用户画像、偏好、角色定义

    • 索引:HNSW(推荐升级)
    • 特点:相对静态,精确度要求高
  3. instructions 表:存储 AI 行为准则、工作流规范

    • 索引:HNSW(推荐升级)
    • 特点:优先级最高,需快速检索
  4. 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 表数据量大用 128
  • ef_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}")

高频面试题精选

  1. 问:pgvector 与专门的向量数据库(如 Milvus, Pinecone)相比有什么优劣?

    • 优点是运维极其简单,可以利用成熟的 PostgreSQL 生态,轻松实现向量数据与关系数据的混合事务查询;缺点是超大规模数据(十亿级)下的分布式扩展性略逊于原生分布式向量数据库。
  2. 问:为什么 HNSW 索引通常比 IVFFlat 表现更好?

    • :HNSW 利用了多层图的跳表特性,实现了对数级别的查询复杂度,而 IVFFlat 在查询时仍需要扫描多个聚类列表。HNSW 的召回率-性能平衡点更高。
  3. 问:在 pgvector 中如何处理高维度向量的维度爆炸问题?

    • :可以采用降维技术(如 PCA)或者使用 半精度浮点数 (halfvec) 类型(pgvector 0.7.0+ 支持),在损失极小精度的情况下减少 50% 的存储空间和带宽。

解决过程记录

请参考:69-20260212-pgvector-Principle-Explanation-Summary.md