了解为什么 BM25 + Block-WeakAnd 在相关性与性能上显著优于 PostgreSQL 原生 ts_rank 与 GIN 索引。
这是本节的多页打印视图。 .
快速上手
1 - 架构概览
PostgreSQL 通过 tsvector、tsquery、ts_rank 和 GIN 索引提供了内置的全文检索能力。虽然它在布尔关键词匹配(@@)方面表现良好,但在现代搜索与检索增强生成(RAG)工作负载下,PostgreSQL 内置的相关性排序存在两个结构性局限:
- 排序公式不理想(
ts_rank):PostgreSQL 的ts_rank和ts_rank_cd不会在索引中维护全语料库级别的逆文档频率(IDF)或平均文档长度()。常见词与罕见的高信息量词被赋予相同的权重,且词频(TF)缺乏合理的饱和度控制。 ORDER BY ... LIMIT K必须全量扫描:PostgreSQL 的GIN索引只能加速布尔匹配(WHERE tsv @@ tsquery)。为了返回得分最高的 Top-10 文档,PostgreSQL 必须从堆表(Heap)中取出所有匹配行,逐一计算ts_rank,再对整个结果集进行排序。
VectorChord-BM25 如何解决这两个问题
VectorChord-BM25 (vchord_bm25) 引入了专为 Top-K 词法检索设计的 PostgreSQL 索引访问方法(USING bm25)和稀疏 BM25 评分操作符(<&>):
| 能力维度 | PostgreSQL 内置 (GIN + ts_rank) |
VectorChord-BM25 (vchord_bm25) |
|---|---|---|
| 排序模型 | 局部词频 / 词距 (ts_rank) |
全语料库 Okapi BM25(、、、) |
| Top-K 执行方式 | 取出并计算所有匹配元组后再排序 | 索引内 Block-WeakAnd (Block-Max WAND) 提前剪枝 |
| 倒排链压缩 | 未压缩或基础变长整数 TID 列表 | 128 文档分块、对 SIMD 友好的差分位打包(Delta-Bitpacking) |
| 文档长度归一化 | 可选的粗粒度位掩码标志 | 单字节 Fieldnorm 量化(256 项查找表) |
| 查询语义 | 要求布尔 AND/OR 精确匹配 |
跨所有查询词的非合取(Non-conjunctive)Top-K 相关性排序 |
| 分词能力 | 内置 ts_debug / Snowball |
pg_tokenizer(LLM 分词器、jieba、lindera、自定义词表)或原生 tsvector |
架构概览
flowchart LR
subgraph Tokenization["1. 分词层 (Tokenization Layer)"]
T1["原始文本 (Raw Text)"] --> T2["pg_tokenizer (v0.1–v0.3)\n或 to_tsvector (main)"]
T2 --> T3["bm25vector / tsvector\n(词元 ID + 词频)"]
end
subgraph Index["2. BM25 索引 (USING bm25)"]
I1["增长段 (Growing Segment)\n(仅追加页)"] --> I2["封闭段 (Sealed Segment)\n(Delta-Bitpacked 128 文档块)"]
I2 --> I3["词项统计信息 (DF)\n+ 单字节 Fieldnorm 数组"]
end
subgraph Query["3. Top-K 查询 (ORDER BY vec <&> bm25query LIMIT k)"]
Q1["块级最大得分上界 (Block-Max)"] --> Q2["Block-WeakAnd 枢轴剪枝"]
Q2 --> Q3["Top-K 最小堆 (-BM25 得分)"]
end
T3 --> Index
Index --> Query两代 API 演进
在使用 vchord_bm25 时,请注意不同版本间的两代 API 设计:
-
稳定发布版(
v0.1.0–v0.3.0,模式bm25_catalog):- 使用自定义数据类型
bm25vector(稀疏的token_id:frequency映射,例如{1:1, 2:2})和bm25query。 - 配合
pg_tokenizer扩展(tokenizer_catalog)使用,将文本转换为INT[]词元 ID 数组,并可隐式转换为bm25vector。 - 创建索引:
CREATE INDEX idx ON documents USING bm25 (embedding bm25_ops); - 构造查询:
to_bm25query('idx', tokenize('query text', 'tokenizer_name'))
- 使用自定义数据类型
-
新一代原生
tsvector架构(main分支):- 直接作用于 PostgreSQL 内置的
tsvector数据类型(CREATE OPERATOR CLASS bm25_ops FOR TYPE tsvector USING bm25),无需额外维护单独的bm25vector列。 - 创建索引:
CREATE INDEX idx ON documents USING bm25 (vector bm25_ops) WITH (options = $$ k1 = 1.2, b = 0.75 $$); - 构造查询:
to_bm25query(to_tsvector('english', 'query text'), 'idx')
- 直接作用于 PostgreSQL 内置的
本指南在各章节中均并列提供了这两种工作流的完整示例,你可以根据实际部署的版本直接选用对应的写法。
2 - 安装指南
当前源码面向 0.4.0(尚未发布),使用原生 tsvector 和新的索引格式。标注 0.3.x 的 bm25vector 示例仅适用于旧版。两者不能原地升级;请先阅读0.4.0 迁移指南。
vchord_bm25 支持 Linux(x86_64 与 aarch64)及 macOS 平台上的 PostgreSQL 14、15、16、17 和 18。
方式一:Pigsty / 原生操作系统软件包(推荐)
Pigsty 为 EL 8/9/10、Debian 12/13 以及 Ubuntu 22.04/24.04 上的 PostgreSQL 14–18 提供了预构建的 vchord、vchord_bm25 和 pg_tokenizer 原生 RPM 与 DEB 软件包。
配置好 Pigsty 软件源后,也可以直接通过 apt 或 dnf 安装:
方式二:Docker(tensorchord/vchord-suite)
官方 tensorchord/vchord-suite Docker 镜像开箱即用地集成了 PostgreSQL、VectorChord (vchord)、VectorChord-BM25 (vchord_bm25) 以及 pg_tokenizer:
你也可以从 GitHub Container Registry 拉取镜像:ghcr.io/tensorchord/vchord-suite:pg18-latest(或 pg17-latest、pg16-latest、pg15-latest、pg14-latest)。
使用 psql 连接数据库:
方式三:GitHub Release 软件包(.deb 与 RHEL .zip)
从 pgsty/vchord_bm25(上游 supervc-stack/VectorChord-bm25)与 pgsty/pg_tokenizer(上游 supervc-stack/pg_tokenizer.rs)的 GitHub Releases 页面下载对应 PostgreSQL 主版本(14–18)和 CPU 架构(amd64 / x86_64 或 arm64 / aarch64)的 .deb 或 RHEL 9+ .zip 软件包:
Debian / Ubuntu(.deb)
RHEL 9+ / Enterprise Linux(.zip 归档包)
在 RHEL 9+ 系统(要求 glibc >= 2.34)中手动通过 Release .zip 归档包安装时:
方式四:从源码编译
如需从源码编译 pgsty/vchord_bm25:
编译期启用可选的 Lindera 词典特性:如果希望将 Lindera 中日韩形态学词典(ipadic、ipadic-neologd、unidic、ko-dic、cc-cedict)直接内嵌编译到 pg_tokenizer.so 中,请在编译 pg_tokenizer 时传入 --features 参数:
本地开发与调试
使用 dev 构建配置,在开启 BM25 编译优化的同时保留调试符号:
在配置预加载 vchord_bm25 后重启一个可丢弃的 PostgreSQL 测试实例,再通过 psql 连接。需要附加调试器时,可查询当前会话的后端进程 PID:
配置 PostgreSQL 并启用扩展
1. 配置 shared_preload_libraries(0.4.0 必需)
当前源码必须通过 shared_preload_libraries 预加载 vchord_bm25。请保留其他所需扩展;pg_tokenizer 对原生 to_tsvector 工作流是可选的:
修改 shared_preload_libraries 后请重启 PostgreSQL。
2. 创建扩展并设置 search_path
连接到目标数据库并启用扩展:
在 v0.1.0–v0.3.0 版本中,vchord_bm25 会将其对象安装到 bm25_catalog 模式,而 pg_tokenizer 会安装到 tokenizer_catalog 模式。请将这两个模式加入你的 search_path:
验证已安装的扩展版本:
3 - 快速开始
当前源码面向 0.4.0(尚未发布),使用原生 tsvector 和新的索引格式。标注 0.3.x 的 bm25vector 示例仅适用于旧版。两者不能原地升级;请先阅读0.4.0 迁移指南。
本指南将带你一步步创建数据表、对文档进行分词、构建 bm25 索引,并执行 Top-K BM25 检索查询。
工作流 A:bm25vector + pg_tokenizer(v0.1 – v0.3)
在已发布的 v0.1.0 至 v0.3.0 版本中,vchord_bm25 与 pg_tokenizer 配合使用,将文本分词并存储到 bm25vector 列中。
1. 启用扩展并配置 search_path
2. 创建分词器
使用 create_tokenizer 搭配内置的 llmlingua2(或 bert_base_uncased)模型创建分词器:
需要领域专属词表、中文分词(jieba)或日文分词(lindera)?请参阅 分词器 (pg_tokenizer) 章节。
3. 创建表并写入文档
4. 将文档分词为 bm25vector
tokenize(text, tokenizer_name) 返回一个由词元 ID 组成的 INT[] 数组,它可以隐式转换为 bm25vector(自动将重复的词元 ID 聚合为词频):
5. 构建 bm25 索引
6. 执行 Top-K BM25 查询
在升序 ORDER BY 子句中结合 <&> 操作符与 to_bm25query(index_name, query_vector) 执行检索:
为什么 <&> 返回负数?
PostgreSQL 仅支持按升序(ASC)执行索引驱动的 ORDER BY 扫描。因此,<&> 操作符返回的是负的 BM25 得分(例如 -2.8415)。数值越负代表相关性越高,从而使 ORDER BY embedding <&> to_bm25query(...) ASC LIMIT K 能够直接利用 Block-WeakAnd 索引扫描。如果希望展示正数得分,只需在 SELECT 列表中对该表达式取反(-(embedding <&> ...))即可。
工作流 B:原生 tsvector API(main 分支)
在新一代 main 分支重构中,vchord_bm25 可直接为 PostgreSQL 原生的 tsvector 类型建立索引——对于标准语言无需安装额外的分词器扩展。