跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

pg_bestmatch 文档

在 PostgreSQL 内部生成 Okapi BM25 稀疏向量,并基于 pgvector(sparsevec)或 pgvecto.rs(svector)执行高性能关键词相关性检索。

pg_bestmatch(pg_bestmatch.rs)是一个基于 Rust(pgrx)开发的 PostgreSQL 扩展,它通过将文档与查询文本转化为兼容 pgvector(sparsevec)与 pgvecto.rs(svector)的 BM25 加权稀疏向量,在 PostgreSQL 内部原生实现 Okapi BM25 全文相关性排序。

演进推荐:vchord_bm25 + pg_tokenizer

pg_bestmatch.rs 是 TensorChord 第一代基于稀疏向量内积的 BM25 检索方案。对于新建生产环境,我们推荐使用新一代 vchord_bm25 搭配 pg_tokenizer——它采用专门设计的 Block-WeakAnd(WAND) 倒排索引替代了稀疏向量 HNSW 近似图检索,支持 100% 精确的 Top-K BM25 计分与更高吞吐量。

核心能力

  • 数据库内原生生成 BM25 稀疏向量:在 bm25_catalog 模式中维护语料库词频(TF)、逆文档频率(IDF)与平均文档长度统计信息,直接为文档与查询生成 BM25 稀疏向量。
  • 无缝对接 pgvector 与 pgvecto.rs:支持直接输出标准 sparsevec(pgvector 0.7.0+)或 svector(pgvecto.rs),使用负内积操作符 <#> 即可计算 BM25 得分并利用 HNSW 索引加速。
  • 内置分词器:内置 HuggingFace bert-base-uncased WordPiece 词表(30,522 维),并提供独立的 tokenize(text) SQL 分词函数。

文档章节导航

了解 pg_bestmatch 如何将 BM25 排序转化为稀疏向量内积,以及通过预编译包或源码安装并启用扩展。

基于斯坦福 LoCo 基准数据集的端到端实战教程,演示结合 pgvector sparsevec 与 pgvecto.rs svector 的完整检索流程。

bm25_create、bm25_refresh、bm25_drop、bm25_document_to_svector、bm25_query_to_svector 与 tokenize 完整参考及方案对比。

1 - 安装与工作原理

了解 pg_bestmatch 如何将 BM25 评分转化为稀疏向量内积运算,以及如何安装与启用扩展。

pg_bestmatch 工作原理

给定文档 DD 与分词为词元序列 q1,q2,…,qnq_1, q_2, \dots, q_n 的查询 QQ,标准的 Okapi BM25 相关性得分公式为:

BM25(D,Q)=∑i=1nIDF(qi)⋅f(qi,D)⋅(k1+1)f(qi,D)+k1⋅(1−b+b⋅∣D∣avgdl) \text{BM25}(D, Q) = \sum_{i=1}^{n} \text{IDF}(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}

可以看到,上述求和本质上是词表空间 VV(内置 bert-base-uncased 词表维度为 ∣V∣=30,522|V| = 30{,}522)中两个稀疏向量的内积(Dot Product):

  1. 文档稀疏向量 v⃗D\vec{v}_D:对于文档 DD 中出现的每个词元 tt,在对应维度存储经文档长度归一化的词频(TF)权重: vD[t]=f(t,D)⋅(k1+1)f(t,D)+k1⋅(1−b+b⋅∣D∣avgdl) v_D[t] = \frac{f(t, D) \cdot (k_1 + 1)}{f(t, D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}
  2. 查询稀疏向量 v⃗Q\vec{v}_Q:对于查询 QQ 中的每个词元 qiq_i,在对应维度存储其逆文档频率权重 IDF(qi)\text{IDF}(q_i)。

当使用 pgvector 或 pgvecto.rs 的负内积操作符 <#> 计算 v⃗D\vec{v}_D 与 v⃗Q\vec{v}_Q 的距离时,PostgreSQL 会返回 −BM25(D,Q)-\text{BM25}(D, Q)。因此,执行 ORDER BY embedding <#> query_vec ASC 即可按 BM25 相关性从高到低返回最匹配的文档!


安装扩展

前置依赖

请先安装 pgvector(需 v0.7.0+ 以支持 sparsevec 类型)或 pgvecto.rs(需 v0.3.0+ 以支持 svector 类型)。

通过预编译包安装(Debian / Ubuntu)

从 GitHub Releases 下载对应 PostgreSQL 大版本的 .deb 安装包:

# 示例:x86_64 架构下的 PostgreSQL 16
wget https://github.com/tensorchord/pg_bestmatch.rs/releases/download/v0.0.1/pg-bestmatch-pg16_0.0.1_amd64.deb
sudo dpkg -i pg-bestmatch-pg16_0.0.1_amd64.deb

从源码编译安装(cargo pgrx)

确保系统已安装 Rust 工具链与 cargo-pgrx:

git clone https://github.com/tensorchord/pg_bestmatch.rs.git
cd pg_bestmatch.rs
cargo pgrx install --release

启用扩展

连接到 PostgreSQL,创建扩展并将 bm25_catalog 加入 search_path:

CREATE EXTENSION IF NOT EXISTS pg_bestmatch;
CREATE EXTENSION IF NOT EXISTS vector; -- 若使用 pgvecto.rs 则为 CREATE EXTENSION vectors;

SET search_path TO "$user", public, bm25_catalog;

执行一条 SQL 验证内置分词器:

SELECT tokenize('PostgreSQL BM25 full-text search with pg_bestmatch');
--                 tokenize
-- -----------------------------------------
--  {postgresql,bm,##25,full,-,text,search,with,pg,_,best,##mat,##ch}

接下来请阅读使用指南,了解端到端的 BM25 稀疏向量建表、建索引与检索流程。

2 - 使用指南

结合 pg_bestmatch 与 pgvector(sparsevec)或 pgvecto.rs(svector)构建 BM25 稀疏向量索引并执行全文检索的完整步骤。

本指南演示如何将 pg_bestmatch 与 pgvector(sparsevec)搭配完成端到端的 BM25 文本检索流程,并附带基于 pgvecto.rs(svector)的等效用法。

1. 启用扩展并设置 search_path

CREATE EXTENSION IF NOT EXISTS pg_bestmatch;
CREATE EXTENSION IF NOT EXISTS vector;

SET search_path TO "$user", public, bm25_catalog;

2. 创建文档表并写入测试数据

CREATE TABLE documents (
    id bigint PRIMARY KEY,
    passage text
);

INSERT INTO documents (id, passage) VALUES
  (1, 'PostgreSQL is an advanced open-source relational database system.'),
  (2, 'VectorChord brings disk-friendly RaBitQ vector search to PostgreSQL.'),
  (3, 'BM25 is a bag-of-words ranking function used in information retrieval.');

3. 创建 BM25 语料库统计集

调用 bm25_create(table_name, column_name, statistic_name, b, k) 为目标表与文本列创建 BM25 统计信息(b = 0.75、k = 1.2 为默认推荐参数):

SELECT bm25_create('documents', 'passage', 'documents_passage_bm25', 0.75, 1.2);

bm25_create 会在后台执行以下操作:

  1. 在 bm25_catalog 模式下创建统计表与物化视图,统计全局词频、文档频率及文档总数。
  2. 在 documents 表上注册触发器,自动追踪后续新增或删除的记录。
批量更新后刷新统计集

当向表中大批量写入新文档后,如果希望重新计算全局逆文档频率(IDF)与平均文档长度(avgdl),请执行:

SELECT bm25_refresh('documents_passage_bm25');

4. 生成文档稀疏向量列(配合 pgvector)

为 documents 表添加 sparsevec(30522) 列,并调用 bm25_document_to_svector 生成文档向量:

ALTER TABLE documents ADD COLUMN embedding sparsevec(30522);

UPDATE documents
SET embedding = bm25_document_to_svector(
    'documents_passage_bm25',
    passage,
    'pgvector'
)::sparsevec;

5. 创建 HNSW 稀疏向量索引

使用 pgvector 的 HNSW 索引与内积操作符类 sparsevec_ip_ops:

CREATE INDEX documents_embedding_bm25_idx
ON documents
USING hnsw (embedding sparsevec_ip_ops);

6. 执行 BM25 检索查询

使用 bm25_query_to_svector 将查询词转化为稀疏向量,并按 <#>(负内积)升序排序:

SELECT
    id,
    passage,
    (embedding <#> bm25_query_to_svector('documents_passage_bm25', 'PostgreSQL vector search', 'pgvector')::sparsevec) * -1 AS bm25_score
FROM documents
ORDER BY embedding <#> bm25_query_to_svector('documents_passage_bm25', 'PostgreSQL vector search', 'pgvector')::sparsevec
LIMIT 5;

配合 pgvecto.rs(svector)使用

如果您使用的是 pgvecto.rs(v0.3.0+),可省略 'pgvector' 参数(或显式传入 'pgvecto.rs')并将返回值转换为 svector:

CREATE EXTENSION IF NOT EXISTS vectors;

ALTER TABLE documents ADD COLUMN embedding svector(30522);

UPDATE documents
SET embedding = bm25_document_to_svector('documents_passage_bm25', passage)::svector;

CREATE INDEX documents_embedding_svector_idx
ON documents
USING vectors (embedding svector_dot_ops);

SELECT id, passage
FROM documents
ORDER BY embedding <#> bm25_query_to_svector('documents_passage_bm25', 'PostgreSQL vector search')::svector
LIMIT 5;

3 - SQL 参考与方案对比

pg_bestmatch(bm25_catalog)的完整 SQL 函数参考手册,以及与 vchord_bm25 的架构对比。

SQL 函数参考(bm25_catalog)

pg_bestmatch 的所有 SQL 函数均位于 bm25_catalog 模式下。

bm25_create

为指定的表与文本列创建 BM25 语料库统计信息。

bm25_create(
    table_name     regclass,
    column_name    text,
    statistic_name text,
    b              real DEFAULT 0.75,
    k              real DEFAULT 1.2
) RETURNS void
  • table_name:包含文档数据的目标表名。
  • column_name:待分析的 text 文本列名。
  • statistic_name:该 BM25 统计集的唯一标识名称(需为合法的 SQL 标识符)。
  • b:BM25 文档长度归一化系数(默认 0.75)。
  • k:BM25 词频饱和度参数 k1k_1(默认 1.2)。

bm25_refresh

在文档发生增删改后,刷新 statistic_name 对应的物化语料库统计数据。

bm25_refresh(
    statistic_name text
) RETURNS void

bm25_drop

删除与 statistic_name 关联的所有内部统计表、物化视图与触发器。

bm25_drop(
    statistic_name text
) RETURNS void

bm25_document_to_svector

将文档字符串转化为兼容 pgvecto.rs(svector)或 pgvector(sparsevec)的 BM25 词频权重稀疏向量字面量(text)。

bm25_document_to_svector(
    statistic_name text,
    passage        text,
    style          text DEFAULT 'pgvecto.rs'
) RETURNS text
  • statistic_name:已注册的统计集名称。
  • passage:输入文档文本。
  • style:输出格式——'pgvector'(下标从 1 开始)或 'pgvecto.rs'(下标从 0 开始)。

bm25_query_to_svector

将检索查询字符串转化为带 IDF 权重的稀疏向量字面量(text)。

bm25_query_to_svector(
    statistic_name text,
    query          text,
    style          text DEFAULT 'pgvecto.rs'
) RETURNS text

tokenize

使用内置的 bert-base-uncased WordPiece 词表,将输入字符串切分为子词词元数组(text[])。

tokenize(
    input text
) RETURNS text[]

方案对比:pg_bestmatch vs vchord_bm25

pg_bestmatch 与 vchord_bm25 均由 TensorChord 团队开发,旨在将 Okapi BM25 排序引入 PostgreSQL,但两者采用了截然不同的底层架构:

对比维度 pg_bestmatch(pg_bestmatch.rs) vchord_bm25 + pg_tokenizer
核心机制 将 BM25 的 TF/IDF 权重映射为稀疏向量(sparsevec / svector) 原生倒排索引 + Block-WeakAnd(WAND) 块级剪枝扫描
索引类型 复用 pgvector 或 pgvecto.rs 的 HNSW 稀疏向量索引 专用 USING bm25 (vec bm25_ops) 索引访问方法
检索精度 近似检索(基于稀疏向量 HNSW 图遍历) 100% 精确 Top-K BM25 计分
分词能力 固定内置 bert-base-uncased 词表(30,522 维) 可插拔 pg_tokenizer(支持 Unicode、Jieba、Lindera、HuggingFace、Snowball 及自定义语料词表)
适用场景 仅安装了 pgvector 环境下的轻量级实验与基准对比 生产级全文检索与多路混合检索首选