跳转到主要内容

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

返回本页常规视图.

pgvecto.rs 文档

vchord 的前身 pgvecto.rs(vectors 扩展)完整文档归档。
警告

pgvecto.rs 已由 vchord 接替。 对于新部署环境,请使用 vchord;对于现有的 pgvecto.rs 数据库,请参考迁移指南。

1 - 快速入门

pgvecto.rs 的简介与安装指南。

1.1 - 概览

pgvecto.rs 简介。
警告

我们强烈建议您探索全新的 VectorChord 实现,它提供了更高的稳定性和性能。建议用户迁移至 VectorChord,详情请参考迁移指南。

pgvecto.rs 简介。

什么是 pgvecto.rs

pgvecto.rs 是一个提供向量相似度搜索功能的 Postgres 扩展。它使用 Rust 编写,基于 pgrx 构建。目前处于测试(Beta)阶段,诚邀您在生产环境中试用并向我们提供反馈。更多信息请参阅📝我们的发布博客。

与 pgvector 的对比

查看 pgvecto.rs vs pgvector 了解更多详情。

特性 pgvecto.rs pgvector
过滤(Filtering) 引入 VBASE 方法,将向量搜索与关系查询统一(例如:单向量 TopK + 过滤 + 连接)。 应用过滤条件时,返回结果可能不完整。例如,原本希望限制返回 10 条结果,加上过滤后可能最终只得到 5 条结果。
向量维度 最高支持 65535 维。 最高支持 2000 维。
SIMD 在运行时动态分派 SIMD 指令,根据具体机器的硬件能力最大化性能。 在 0.7.0 版本中为 Linux x86-64 增加了距离函数的 CPU 动态分派。
数据类型 引入额外的数据类型:二值向量(binary vector)、FP16(16 位浮点数)和 INT8(8 位整数)。 -
索引机制 独立于 PostgreSQL 管理索引的存储和内存 依赖 PostgreSQL 的原生存储引擎
WAL 支持 为数据提供预写式日志(WAL)支持,索引的 WAL 支持正在开发中。 为索引和数据均提供预写式日志(WAL)支持。

快速开始

对于新用户,我们建议使用 Docker 镜像快速上手。

docker run \
  --name pgvecto-rs-demo \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d tensorchord/pgvecto-rs:pg16-v0.2.0

随后您可以使用 psql 命令行工具连接到数据库。默认用户名为 postgres,默认密码为 mysecretpassword。

psql postgresql://postgres:mysecretpassword@localhost:5432/postgres

运行以下 SQL 确保扩展已启用:

DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

pgvecto.rs 引入了新的数据类型 vector(n) 来表示 n 维向量,括号内的 n 代表向量的维度。

您可以使用以下 SQL 创建一张表:

-- 创建包含向量列的表

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding vector(3) NOT NULL -- 3 维
);
详情

仅当 1≤n≤655351 \leq n \leq 65535 时,vector(n) 才是有效的数据类型。受限于 PostgreSQL 的机制,您有可能在 vector(3) 类型的列中存入 55 维的值,而且不带维度的 vector 也是合法的数据类型。但您仍然不能向向量中放入 00 个标量或超过 6553565535 个标量。如果您在列定义中使用不带维度的 vector,或者列中存在与声明维度不匹配的值,您将无法在该列上创建索引。

接下来,您可以按如下方式向表中插入向量数据:

-- 插入值

INSERT INTO items (embedding)
VALUES ('[1,2,3]'), ('[4,5,6]');

-- 或者将数组类型转换为向量后插入

INSERT INTO items (embedding)
VALUES (ARRAY[1, 2, 3]::real[]), (ARRAY[4, 5, 6]::real[]);

我们支持三种用于计算两个向量之间距离的操作符:

  • <->:平方欧氏距离(Squared Euclidean distance),定义为 Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2。
  • <#>:负点积(Negative dot product),定义为 −Σxiyi- \Sigma x_iy_i。
  • <=>:余弦距离(Cosine distance),定义为 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}。
-- 通过操作符调用距离函数

-- 平方欧氏距离
SELECT '[1, 2, 3]'::vector <-> '[3, 2, 1]'::vector;
-- 负点积
SELECT '[1, 2, 3]'::vector <#> '[3, 2, 1]'::vector;
-- 余弦距离
SELECT '[1, 2, 3]'::vector <=> '[3, 2, 1]'::vector;

您可以像这样简单地搜索相似向量:

-- 查询相似的嵌入向量
SELECT * FROM items ORDER BY embedding <-> '[3,2,1]' LIMIT 5;

简单的问答(Question-Answering)应用

请查看问答应用教程。

半精度浮点数

vecf16 类型除了标量元素类型外,其余与 vector 完全一致。它存储 16 位浮点数。如果您希望减少内存占用以获得更好的性能,可以尝试将 vector 类型替换为 vecf16 类型。

关于 vecf16 的更多用法,请参考向量类型。

稀疏向量

svector 类型是稀疏向量类型。它以稀疏格式存储向量,非常适合包含大量零元素的向量。

关于 svector 的更多用法,请参考向量类型。

二值向量

bvector 类型是二值向量类型,本质上是定长位串(bit string)。除了上述 3 种距离外,我们还支持 jaccard 距离 <~>,其定义为 1−∣X∩Y∣∣X∪Y∣1 - \frac{|X\cap Y|}{|X\cup Y|}。而汉明距离(hamming distance)与平方欧氏距离等价,您可以使用 <-> 操作符来计算。此外,我们还提供了 binarize 函数,用于从 vector 构造 bvector(将正数元素设为 1,其余设为 0)。

关于 bvector 的更多用法,请参考向量类型。

路线图 🗂️

请查看路线图(ROADMAP)。

参与贡献 😊

我们欢迎来自开源社区、个人及合作伙伴的各种形式的贡献。

与我们交流

💬 有兴趣与我们聊聊您在构建或管理 AI/ML 应用方面的经验吗?

预约交流时间!

1.2 - 安装

安装 pgvecto.rs 的四种方式。
警告

我们强烈建议您探索全新的 VectorChord 实现,它提供了更高的稳定性和性能。建议用户迁移至 VectorChord,详情请参考迁移指南。

安装 pgvecto.rs 有四种方式。

Docker

试用 pgvecto.rs 最简单的方式是直接运行开箱即用的 Docker 镜像。

docker run \
  --name pgvecto-rs-demo \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d tensorchord/pgvecto-rs:pg16-v0.2.0

随后您可以使用 psql 命令行工具连接到数据库。默认用户名为 postgres,默认密码为 mysecretpassword。

psql postgresql://postgres:mysecretpassword@localhost:5432/postgres

运行以下 SQL 确保扩展已启用:

DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

为了获得完整性能,请添加类似 -v $PWD/pgdata:/var/lib/postgresql/data 的选项,将数据卷挂载到 PostgreSQL 数据目录。

您可以参考父镜像文档 https://hub.docker.com/_/postgres/ 来配置 PostgreSQL。

使用 Debian 软件包安装

提示

从 Debian 软件包安装需要依赖 GLIBC >= 2.31,例如:

  • Ubuntu 20.04 或更高版本
  • Debian Bullseye 或更高版本

Debian 软件包(.deb)适用于基于 Debian 的发行版(如 Ubuntu 等),可以通过 dpkg 或 apt-get 轻松安装。

  1. 在 Release 页面下载 .deb 软件包,并执行 sudo apt install vectors-pg15-*.deb 进行安装。

  2. 修改 shared_preload_libraries 和 search_path 以包含该扩展,从而完成 PostgreSQL 配置。

psql -U postgres -c 'ALTER SYSTEM SET shared_preload_libraries = "vectors.so"'
psql -U postgres -c 'ALTER SYSTEM SET search_path TO "$user", public, vectors'
# 需要重启 PostgreSQL 集群以使配置生效。
sudo systemctl restart postgresql.service   # 适用于通过 systemd 运行的 pgvecto.rs
  1. 连接到数据库并启用扩展。
DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

使用 ZIP 压缩包安装

since v0.2.1
提示

从 ZIP 压缩包安装需要依赖 GLIBC >= 2.31,例如:

  • RHEL 9 或更高版本

对于非 Debian 系且无法运行 Docker 容器的系统,请按以下步骤安装:

  1. 安装前,请确保已安装必要的软件包,包括 PostgreSQL、pg_config、unzip 和 wget。
# RHEL 9 dnf 示例
# 请根据您的包管理器进行检查
sudo dnf install -y unzip wget libpq-devel
sudo dnf module install -y postgresql:15/server
sudo postgresql-setup --initdb
sudo systemctl start postgresql.service
sudo systemctl enable postgresql.service
  1. 验证 PostgreSQL 是否已设置 $pkglibdir 和 $shardir。
pg_config --pkglibdir
# 输出类似于:
# /usr/lib/postgresql/15/lib 或
# /usr/lib64/pgsql

pg_config --sharedir
# 输出类似于:
# /usr/share/postgresql/15 或
# /usr/share/pgsql
  1. 在 Release 页面下载 zip 压缩包并解压到临时目录。
wget https://github.com/tensorchord/pgvecto.rs/releases/download/v0.2.1/vectors-pg15_x86_64-unknown-linux-gnu_0.2.1.zip -O vectors.zip
unzip vectors.zip -d vectors
  1. 将扩展文件复制到 PostgreSQL 目录。
# 将动态库复制到 `$pkglibdir`
sudo cp vectors/vectors.so $(pg_config --pkglibdir)/
# 将 schema 文件复制到 `$shardir`
sudo cp vectors/vectors--* $(pg_config --sharedir)/extension/
sudo cp vectors/vectors.control $(pg_config --sharedir)/extension/
  1. 修改 shared_preload_libraries 和 search_path 以包含该扩展,从而完成 PostgreSQL 配置。
psql -U postgres -c 'ALTER SYSTEM SET shared_preload_libraries = "vectors.so"'
psql -U postgres -c 'ALTER SYSTEM SET search_path TO "$user", public, vectors'
# 需要重启 PostgreSQL 集群以使配置生效。
sudo systemctl restart postgresql.service   # 适用于通过 systemd 运行的 pgvecto.rs
  1. 连接到数据库并启用扩展。
DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

从源码构建安装

在从源码构建之前,您可以参考开发指南配置开发环境。

  1. 构建并安装扩展:
sed -e "s/@CARGO_VERSION@/0.0.0/g" ./vectors.control  # 这里的 0.0.0 是您所需的版本号
sudo cp -a ./sql/upgrade/. $(pg_config --sharedir)/extension
cargo pgrx install --sudo --release
  1. 修改 shared_preload_libraries 和 search_path 以包含该扩展,从而完成 PostgreSQL 配置。
psql -U postgres -c 'ALTER SYSTEM SET shared_preload_libraries = "vectors.so"'
psql -U postgres -c 'ALTER SYSTEM SET search_path TO "$user", public, vectors'
# 需要重启 PostgreSQL 集群以使配置生效。
sudo systemctl restart postgresql.service   # 适用于通过 systemd 运行的 pgvecto.rs
service postgresql restart  # 适用于在 envd 中运行的 pgvecto.rs
  1. 连接到数据库并启用扩展。
DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

2 - 使用指南

在 pgvecto.rs 中创建索引、执行相似度搜索、监控、量化以及兼容性配置。

2.1 - 索引

索引是构建高效搜索数据结构的过程。pgvecto.rs 支持三种索引算法:暴力搜索(flat)、IVF 和 HNSW。

索引是构建支持高效搜索的数据结构的过程。pgvecto.rs 支持三种索引算法:暴力搜索(flat)、IVF 和 HNSW。默认算法为 HNSW。

要为向量构建索引,首先创建一张名为 items 的表,其中包含类型为 vector(n) 的 embedding 列,然后向表中填充生成的数据。

CREATE TABLE items (embedding vector(3));
INSERT INTO items (embedding) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);
提示

vector 是适用于绝大多数场景的数据类型。pgvecto.rs 还支持其他特定的向量类型,包括:

您可以使用以下 SQL 为平方欧氏距离创建向量索引:

CREATE INDEX ON items USING vectors (embedding vector_l2_ops);

vector_l2_ops 操作符类用于计算平方欧氏距离。有关操作符类的完整列表,请参阅参考文档。

若要将索引算法从默认的 HNSW 切换为 IVF,请执行以下 SQL 命令。更多使用示例请参阅下文的示例。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.ivf]
$$);

现在您可以使用以下 SQL 执行 KNN 搜索,利用向量索引加速查询:

SELECT * FROM items ORDER BY embedding <-> '[3,2,1]' LIMIT 5;
详情

pgvecto.rs 采用异步方式构建索引。当您向表中插入新行时,数据会首先写入一个仅追加(append-only)文件中。后台线程会定期将新插入的行合并到现有索引中。当用户在合并完成前执行任何搜索时,系统会同时扫描该仅追加文件,以确保查询结果的准确性和一致性。

示例

以下是使用不同算法和参数创建索引的示例。有关所有可配置项的完整列表,请参阅索引选项参考指南。

-- HNSW 算法,默认配置。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops);

-- 使用 FP16 向量类型优化数据存储。

CREATE TABLE items (embedding vecf16(3));
CREATE INDEX ON items USING vectors (embedding vecf16_l2_ops)

--- 或者使用结合乘积量化(PQ)的暴力搜索(flat)。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.flat]
quantization.product.ratio = "x16"
$$);

-- 或者使用结合标量量化(SQ)的索引。
CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = "[indexing.hnsw.quantization.scalar]");

--- 或者使用 IVFPQ 算法。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.ivf]
quantization.product.ratio = "x16"
$$);

-- 使用更多线程在后台构建索引。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
optimizing.optimizing_threads = 16
$$);

-- 倾向于使用更小的 HNSW 图分段。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
segment.max_growing_segment_size = 200000
$$);

选择建议

选择合适的索引算法与参数对向量搜索性能至关重要。以下是针对不同使用场景的建议。

暴力搜索(flat)

如果您搜索的数据量较小(1,000 到 10,000 条之间)或需要完全精确的结果,暴力搜索(flat)算法是一个可行的选择。

该算法是衡量其他索引方法的基准。它以原始形式保留向量而不做压缩,避免了额外的索引开销。虽然简单直观、易于使用,但它不适合处理大规模数据集。

此外,您还可以使用量化来减少内存占用。有关所有选项的完整列表,请参阅量化选项文档。关于量化的详细介绍也可以在量化指南中找到。

倒排文件索引(IVF)

IVF 主要利用倒排索引的思想,将向量 (id, vector) 存储在各自的聚类中心下。查询向量时,它会先找到最近的若干个聚类中心,然后分别搜索这些中心下的向量。

在开始搜索之前,需要先通过 K-Means 聚类训练 nlist 个聚类中心,并可选择设置采样点数 nsample。较高的值能生成更精确的聚类结果,但需要更长的训练时间。有关更多设置,请参阅参考指南。

分层可导航小世界图(HNSW)

HNSW 将跳表(Skip List)与可导航小世界(NSW)图的思想相结合,采用多层图结构:在较高层使用较长的边以实现快速跳转搜索,在较低层使用较短的边以提高搜索精度。

可导航小世界(Navigable small world)

可导航小世界(NSW)基于这样一个前提:图中的任意节点都可以通过极少数的“跳数(hops)”从其他任何节点到达。选定入口点(无论是随机选择、启发式选择还是算法确定)后,算法会检查周围的邻居节点是否比当前节点更接近查询目标。搜索不断向最近的邻居推进,直到在当前节点的邻接列表中找不到更近的节点为止。

构建图时,参数 m 决定了新节点与其最近邻节点建立连接的数量。较高的 m 值会产生更稠密、互联程度更高的图,但也会增加内存占用并降低插入速度。在插入节点时,算法会找出最近的 m 个节点并与它们建立双向连接。

搜索过程从预定义的入口点开始遍历 NSW 图。算法以贪心方式移动到离查询向量更近的相邻顶点,一旦在当前顶点的邻接列表中找不到更近的邻居,搜索便停止。

NSW 搜索的一个局限在于,它总是选择通往最近节点的表面最短路径,而忽略了图的全局结构。这种“贪心搜索”方法容易陷入局部最优,这一问题通常被称为“早停(early stopping)”。

跳表(Skip list)

跳表使用概率方法构建:随着层级的升高,节点出现的概率逐渐降低,因此高层中的节点数量更少。搜索节点时,算法从顶层的“头节点”开始,向后移动到大于或等于目标的下一个节点;如果未找到目标节点,则下降到更低(更细粒度)的一层并重复此模式,直到定位到目标节点或其最近邻。

HNSW

构建这种多层图从包含整个数据集的基础层开始。沿着层级向上,每一层都提供其下一层的精简摘要,包含更少的节点,充当在图内进行大跨度跳跃的“快速通道”。

较小的 m 值更适合低维数据或对召回率要求不高的场景;较大的 m 值则适用于高维数据或对高召回率有严格要求的场景。

ef_construction 参数决定了添加新节点时的动态候选列表大小;增大该值可以提高召回率,但会延长索引构建时间。

HNSW 索引属于资源密集型索引,需要额外的内存,并且在处理大型数据集时需要调大 maintenance_work_mem 设置。如果您追求显著快于 IVF 的查询性能、高召回率以及随数据集规模良好扩展的能力,HNSW 是极佳的选择。

2.2 - 搜索

获取与目标向量最近的 5 个邻居

获取与目标向量最近的 5 个邻居:

SET vectors.hnsw_ef_search = 64;
SELECT * FROM items ORDER BY embedding <-> '[3,2,1]' LIMIT 5;

操作符

以下操作符用于表示不同的距离度量:

名称 说明
<-> 平方欧氏距离(Squared Euclidean distance)
<#> 负点积(Negative dot product)
<=> 余弦距离(Cosine distance)

有关它们的数学定义,请参阅概览。

过滤查询

在指定类别下,获取与目标向量最近的 10 个邻居:

SELECT 1 FROM items WHERE category_id = 1 ORDER BY embedding <#> '[0.5,0.5,0.5]' limit 10

查询选项

在当前会话中将 ivf 扫描列表数设置为 1:

SET vectors.ivf_nprobe=1;

在当前事务中将 hnsw 搜索范围设置为 40:

SET LOCAL vectors.hnsw_ef_search=40;

有关所有可配置项,请参阅搜索选项。

搜索模式

支持两种搜索模式:vbase 和 basic。

vbase

作为默认搜索模式,vbase 适用于绝大多数场景。

关于其工作原理,请参阅论文 VBASE: Unifying Online Vector Similarity Search and Relational Queries via Relaxed Monotonicity。

basic

basic 模式在行为上与 faiss 等向量算法库保持一致。如果您希望将结果与这些库对齐,该模式会很有帮助。

启用 basic 模式时,必须遵守以下限制:

  • 在执行更新操作后、进行搜索前,必须先执行 VACUUM。
  • 搜索时不能带有过滤条件(Filter)。

2.3 - 监控

我们提供了 pg_vector_index_stat 视图来监控索引构建进度。

我们提供了 pg_vector_index_stat 视图来监控索引的构建进度。

列名 类型 说明
tablerelid oid 表的 OID。
indexrelid oid 索引的 OID。
tablename name 表名。
indexname name 索引名。
idx_status text 取值为 NORMAL 或 UPGRADE,表示该索引处于正常状态还是需要升级。
idx_indexing bool 当 idx_status 为 NORMAL 时非空。表示后台线程是否正在构建索引。
idx_tuples int8 当 idx_status 为 NORMAL 时非空。索引中的元组总数。
idx_sealed int8[] 当 idx_status 为 NORMAL 时非空。每个已封存分段(sealed segment)中的元组数量。
idx_growing int8[] 当 idx_status 为 NORMAL 时非空。每个增长中分段(growing segment)中的元组数量。
idx_write int8 当 idx_status 为 NORMAL 时非空。写缓冲区(write buffer)中的元组数量。
idx_size int8 当 idx_status 为 NORMAL 时非空。所有分段占用的总字节大小。
idx_options text 当 idx_status 为 NORMAL 时非空。索引的配置信息。
idx_options 详情

idx_options 是一个包含向量(vector)、分段(segment)、优化(optimizing)和索引(indexing)选项的 JSON 字符串。示例如下:

{
    "vector": {
        "dimensions": 256,
        "distance": "L2",
        "kind": "F32"
    },
    "segment": {
        "max_growing_segment_size": 20000,
        "max_sealed_segment_size": 1000000
    },
    "optimizing": {
        "sealing_secs": 60,
        "sealing_size": 1,
        "delete_threshold": 0.2,
        "optimizing_threads": 2
    },
    "indexing": {
        "hnsw": {
            "m": 12,
            "ef_construction": 300,
            "quantization": {
                "trivial": {}
            }
        }
    }
}

如果不再有新的插入操作,并且您希望等待所有索引构建完成,可以每隔 60 秒检查一次 idx_indexing 字段,直到其变为 false(注:原文写作 true,实际 idx_indexing = false 表示后台索引构建已完成)。

2.4 - 量化

量化是一种在可接受的精度损失下降低内存消耗与计算复杂度的技术。在向量搜索场景中,主要有乘积量化和标量量化两种类型。

量化(Quantization)是一种在可接受的精度损失范围内,降低内存消耗和计算复杂度的技术。在向量搜索中,主要有两种量化方式:乘积量化(Product Quantization)和标量量化(Scalar Quantization)。

乘积量化(Product Quantization)

乘积量化通过在数据集上训练多个子空间聚类模型,将每个高维向量映射为一组聚类中心索引列表。它常用于解决高维向量数据内存占用过高的问题。

在 pgvecto.rs 中,您可以通过以下方式启用乘积量化:

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.ivf]
quantization.product.ratio = "x16"
$$);

更多配置可在索引文档中找到。

标量量化(Scalar Quantization)

标量量化将每个标量值映射为低比特格式(例如从 32 位浮点数映射为 8 位整数)。通常情况下,这不会对精度造成明显影响,但能有效降低内存占用并提升查询性能。pgvecto.rs 支持通过以下方式启用 uint8 标量量化:

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = "[indexing.hnsw.quantization.scalar]");

选择建议

当需要使用量化时,建议始终优先尝试标量量化。它在精度与内存占用之间取得了良好的平衡。

如果内存占用仍然过高,可以尝试乘积量化。该方法能大幅降低内存占用,但可能会影响精度。由于乘积量化的效果与训练数据集高度相关,您需要尝试不同的 ratio 和 sample 参数值,以找到最适合您数据集的配置。

此外,您还可以使用主成分分析(PCA)等降维方法来降低向量维度。

2.5 - `pgvector` 兼容性

pgvecto.rs 对 pgvector 的原生兼容性与兼容模式配置。

pgvecto.rs 在以下方面原生兼容 pgvector:

  • CREATE TABLE 命令,例如 CREATE TABLE t (val vector(3))
  • INSERT INTO 命令,例如 INSERT INTO t (val) VALUES ('[0.6,0.6,0.6]')

pgvecto.rs 还可以通过配置兼容 pgvector 的以下语法:

  • 索引选项,允许通过 USING hnsw (val vector_ip_ops) 创建索引
  • 查询选项,支持通过 SET ivfflat.probes = 10 设置参数

该特性称为 pgvector 兼容模式,可通过 SET vectors.pgvector_compatibility = on 启用。

索引选项与查询选项

对于 ivfflat 和 hnsw 索引,仅支持以下选项:

  • 索引选项的默认值与 pgvecto.rs 原生默认值不同,而是与 pgvector 保持一致。
  • 查询选项的默认值则与 pgvecto.rs 保持一致。

ivfflat 的索引选项:

键名 类型 取值范围 默认值 说明
nlist integer [1, 1_000_000] 100 聚类单元(列表)的数量。

ivfflat 的查询选项:

选项 类型 取值范围 默认值 说明
ivfflat.probes integer [1, 1_000_000] 10 扫描的列表数量。
警告

ivfflat.probes 的默认值为 10,而非 pgvector 中的 1。

hnsw 的索引选项:

键名 类型 取值范围 默认值 说明
m integer [4, 128] 16 节点的最大出度。
ef_construction integer [10, 2000] 64 构建索引时的搜索范围。

hnsw 的查询选项:

选项 类型 取值范围 默认值 说明
hnsw.ef_search integer [1, 65535] 100 HNSW 的搜索范围。
警告

hnsw.ef_search 的默认值为 100,而非 pgvector 中的 40。

示例

启用 pgvector 兼容模式并开始向量查询非常简单:

DROP TABLE IF EXISTS t;
SET vectors.pgvector_compatibility=on;
SET hnsw.ef_search=40;
CREATE TABLE t (val vector(3));
INSERT INTO t (val) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);
CREATE INDEX hnsw_cos_index ON t USING hnsw (val vector_cosine_ops);
SELECT COUNT(1) FROM (SELECT 1 FROM t ORDER BY val <-> '[0.5,0.5,0.5]' limit 100) t2;
DROP INDEX hnsw_cos_index;

支持创建多种类型的索引:

SET vectors.pgvector_compatibility=on;
SET hnsw.ef_search=40;
-- 使用默认选项的 [hnsw + vector_l2_ops] 索引
CREATE INDEX hnsw_l2_index ON t USING hnsw (val vector_l2_ops);
-- 指定单个 ef_construction 选项的 [hnsw + vector_cosine_ops] 索引
CREATE INDEX hnsw_cosine_index ON t USING hnsw (val vector_cosine_ops) WITH (ef_construction = 80);
-- 指定全部选项的匿名 [hnsw + vector_ip_ops] 索引
CREATE INDEX ON t USING hnsw (val vector_ip_ops) WITH (ef_construction = 80, m = 12);
SET ivfflat.probes=1;
-- 使用默认选项的 [ivfflat + vector_l2_ops] 索引
CREATE INDEX ivfflat_l2_index ON t USING ivfflat (val vector_l2_ops);
-- 指定全部选项的 [ivfflat + vector_ip_ops] 索引
CREATE INDEX ivfflat_ip_index ON t USING ivfflat (val vector_cosine_ops) WITH (nlist = 80);
-- 指定全部选项的匿名 [ivf + vector_ip_ops] 索引
CREATE INDEX ON t USING ivfflat (val vector_ip_ops) WITH (lists = 80)

3 - 应用案例

关于混合搜索、自适应检索、以图搜图、多租户架构、稀疏向量以及 RAG 问答系统的实战教程。

3.1 - 混合搜索

本节将向您展示如何使用 pgvecto.rs 实现混合搜索。

本节将向您展示如何使用 pgvecto.rs 实现混合搜索(Hybrid Search)。

Postgres 基于匹配操作符 @@ 提供了全文检索(Full Text Search)功能。当 tsvector(文档)与 tsquery(查询)匹配时,它会返回 true。这两种数据类型的书写先后顺序不影响结果:

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
 ?column?
----------
 t

SELECT 'cat & rat'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
 ?column?
----------
 t
提示

如上例所示,tsquery 与 tsvector 一样,并不仅仅是原始文本。tsquery 包含必须已经规范化为词位(lexeme)的搜索词项,并且可以使用 AND、OR、NOT 以及 FOLLOWED BY 操作符组合多个词项。在本例中,tsquery 'cat & rat' 与 tsvector 'a fat cat sat on a mat and ate a fat rat' 匹配,因为文档中同时包含 'cat' 和 'rat' 两个词项。

借助 pgvecto.rs,您可以在单条查询中同时结合使用全文检索和向量搜索。在本例中,我们创建一张名为 items 的表,包含两个字段:content 和 embedding。其中 content 列的类型为 text,embedding 列的类型为 vector(3)。我们向表中插入四条记录:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  content text NOT NULL,
  embedding vector(3) NOT NULL -- 3 维
);

INSERT INTO items (content, embedding) VALUES
  ('a fat cat sat on a mat and ate a fat rat', '[1, 2, 3]'),
  ('a fat dog sat on a mat and ate a fat rat', '[4, 5, 6]'),
  ('a thin cat sat on a mat and ate a thin rat', '[7, 8, 9]'),
  ('a thin dog sat on a mat and ate a thin rat', '[10, 11, 12]');

查询该表将得到如下内容:

postgres=# select * from items;
 id |                  content                   |  embedding   
----+--------------------------------------------+--------------
  2 | a fat cat sat on a mat and ate a fat rat   | [1, 2, 3]
  3 | a fat dog sat on a mat and ate a fat rat   | [4, 5, 6]
  4 | a thin cat sat on a mat and ate a thin rat | [7, 8, 9]
  5 | a thin dog sat on a mat and ate a thin rat | [10, 11, 12]
(4 rows)

首先,我们使用全文检索来查找 content 中同时包含单词 cat 和 rat 的记录。to_tsvector 函数会将文本转换为 tsvector,而操作符 @@ 则检查 tsvector 是否与 tsquery 匹配。

SELECT content, embedding
FROM items
WHERE to_tsvector('english', content) @@ 'cat & rat'::tsquery;
提示

to_tsvector 会将文本文档解析为词元(token),将词元归约为词位(lexeme),并返回一个列出所有词位及其在文档中出现位置的 tsvector。

SELECT to_tsvector('english', 'a fat  cat sat on a mat - it ate a fat rats');
                  to_tsvector
-----------------------------------------------------
 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

在上面的例子中可以看到,由于文本搜索配置设为了 english,生成的 tsvector 不包含停用词 a、on 或 it,单词 rats 被归约为 rat,标点符号 - 也被忽略了。

我们将得到以下结果:

                  content                   | embedding 
--------------------------------------------+-----------
 a fat cat sat on a mat and ate a fat rat   | [1, 2, 3]
 a thin cat sat on a mat and ate a thin rat | [7, 8, 9]
(2 rows)

接下来,我们使用向量搜索,按照每条记录的嵌入向量与查询向量之间的距离对结果进行排序:

SELECT content, embedding
FROM items
WHERE to_tsvector('english', content) @@ 'cat & rat'::tsquery
ORDER BY embedding <-> '[7, 8, 9]'::vector;

此时,嵌入向量为 [7, 8, 9] 的记录将排在第一位,而嵌入向量为 [1, 2, 3] 的记录排在第二位:

                  content                   | embedding 
--------------------------------------------+-----------
 a thin cat sat on a mat and ate a thin rat | [7, 8, 9]
 a fat cat sat on a mat and ate a fat rat   | [1, 2, 3]
(2 rows)

您可以对全文检索结果和向量搜索结果的排名进行归一化,然后将两者融合在一起:

-- 查询从名为 "semantic_search" 的公用表表达式(CTE)开始。
WITH semantic_search AS (
    SELECT id, content, embedding, 
    RANK () OVER (ORDER BY embedding <-> '[7, 8, 9]') AS rank
    FROM items
    ORDER BY embedding <-> '[7, 8, 9]'::vector
    ), 
-- 定义另一个名为 "full_text_search" 的 CTE。
    full_text_search AS (
    SELECT id, content, embedding, 
    RANK () OVER (ORDER BY 
        ts_rank(to_tsvector('english', content), 
            'cat & rat'::tsquery) DESC) AS rank
    FROM items
    WHERE to_tsvector('english', content) @@ 'cat & rat'::tsquery
    ORDER BY ts_rank(
        to_tsvector('english', content), 'cat & rat'::tsquery) DESC
    )
-- 主查询从两个 CTE 中选择列并融合排名。
    SELECT 
        COALESCE(semantic_search.id, full_text_search.id) AS id,
        COALESCE(semantic_search.content, full_text_search.content) AS content,
        COALESCE(semantic_search.embedding, full_text_search.embedding) AS embedding,
        COALESCE(1.0 / (1 + semantic_search.rank), 0.0) + 
        COALESCE(1.0 / (1 + full_text_search.rank), 0.0) AS rank
    FROM semantic_search FULL OUTER JOIN full_text_search USING (id)
    ORDER BY rank DESC;
提示

上述 SQL 查询对 items 表同时执行了语义搜索和全文检索,并根据相关性得分检索结果。以下是该查询的详细解析:

  • 查询以一个名为 semantic_search 的公用表表达式(CTE)开始。它选择 id、content、embedding 列,并使用测量 embedding 列与向量 '[7, 8, 9]' 之间距离的 <-> 操作符计算排名(rank),结果按该距离排序。
  • 定义另一个名为 full_text_search 的 CTE。它选择与前一个 CTE 相同的列,但结合全文检索查询使用 ts_rank 函数计算排名。to_tsvector 函数将 content 列转换为 tsvector,而 tsquery 'cat & rat' 则匹配 content 列中同时包含 "cat" 和 "rat" 的行,结果按全文检索相关性排名排序。
  • 主查询使用 COALESCE 函数从两个 CTE 中选取列,以处理其中一个 CTE 可能为空值的情况。同时,它将两个 CTE 排名的倒数相加,计算出综合得分(rank)。
  • 主查询在两个 CTE 之间基于 id 列执行全外连接(FULL OUTER JOIN)。
  • 最终结果按照综合得分降序排列。

在这条查询中,我们先分别计算全文检索结果和向量搜索结果的排名,然后将两部分结果融合,最终得到如下输出:

 id |                  content                   |  embedding   |          rank          
----+--------------------------------------------+--------------+------------------------
  4 | a thin cat sat on a mat and ate a thin rat | [7, 8, 9]    | 1.00000000000000000000
  2 | a fat cat sat on a mat and ate a fat rat   | [1, 2, 3]    | 0.70000000000000000000
  3 | a fat dog sat on a mat and ate a fat rat   | [4, 5, 6]    | 0.33333333333333333333
  5 | a thin dog sat on a mat and ate a thin rat | [10, 11, 12] | 0.33333333333333333333
(4 rows)

3.2 - 自适应检索与缩短嵌入向量

OpenAI 最新的嵌入模型支持缩短嵌入向量,开发者只需移除序列末尾的部分数值,即可在保持有效文本表示的同时降低存储与计算开销。

OpenAI 最新的嵌入模型支持缩短嵌入向量(Shortening embeddings,即套娃嵌入 Matryoshka Embeddings),开发者只需简单地“移除序列末尾的部分数值”,依然能够保持有效的文本语义表示。

这带来了极其灵活的使用方式。例如,当使用仅支持最高 1024 维嵌入的向量数据存储时,开发者现在仍然可以使用我们最好的嵌入模型 text-embedding-3-large,只需将 API 参数 dimensions 指定为 1024,即可将嵌入从 3072 维缩短为 1024 维,通过牺牲少许精度来换取更小的向量体积。

缩短嵌入向量

OpenAI 的新嵌入模型 text-embedding-3-large 默认生成 3072 维的嵌入向量。

但您可以安全地移除序列末尾的部分数值,同时仍保留有效的文本语义表示。例如,您可以将嵌入向量缩短为 1024 维。

详情

您可能需要对截断后的向量进行归一化(Normalization),以确保缩短后的嵌入向量与点积等距离计算方式兼容。如果您直接通过 OpenAI 的 API 调用 text-embedding-3-large 生成低维嵌入(而非自行截断原始嵌入),API 会自动帮您完成归一化处理。

embedding_3072_dim_from_openai = [-0.00611, -0.00269, ...]
embedding_1024_dim_from_openai = [-0.01520, -0.03839, ...]

embedding_1024_dim_truncated_from_3072_dim = [-0.00611, -0.00269, ...]
embedding_1024_dim_normalized = [-0.01520, -0.03839, ...]

缩短嵌入向量有诸多优势:

  • 降低内存占用:缩短嵌入向量可以显著减少存储向量所需的内存。
  • 更低的维度:缩短嵌入向量可以降低向量维度,适配某些仅支持特定维度上限(例如 2000 维)的向量数据存储。
  • 更快的索引与搜索速度:较小的嵌入向量比大型嵌入向量的索引构建和搜索速度更快。
  • 性能与成本的权衡:缩短嵌入向量可以通过牺牲少许精度来换取更小的向量规模和更低的资源成本。

不过,缩短后的嵌入向量在精度(召回率)上不如完整维度的嵌入向量。

性能与成本的权衡

我们进行了一项实验,评估缩短后的嵌入向量与完整嵌入向量相比的实际效果。基准测试使用了 dbpedia-entities-openai3-text-embedding-3-large-3072-1M 数据集,并在规格为 n2-standard-8(8 vCPU、32 GB 内存)的 Google Cloud 虚拟机上运行。

我们创建了一张表,并向其中填充了 100 万条 3072 维的嵌入向量:

CREATE TABLE openai3072 (
  id bigserial PRIMARY KEY,
  text_embedding_3_large_3072_embedding vector(3072),
  text_embedding_3_large_3072_embedding bvector(3072)
);
详情

向表中插入 100 万条嵌入向量的代码如下:

from pgvecto_rs.sdk import PGVectoRs, Record
from pgvecto_rs.psycopg import register_vector
import psycopg

conn = psycopg.connect(conninfo='postgresql://postgres:mysecretpassword@localhost:5433/postgres', autocommit=True)

import json
import os

import numpy as np
import random
from datasets import load_dataset
from datasets.exceptions import DatasetNotFoundError
from tqdm import tqdm

MODEL_NAME, DIMENSIONS = "text-embedding-3-large", 3072
DATASET_NAME = f"Qdrant/dbpedia-entities-openai3-{MODEL_NAME}-{DIMENSIONS}-1M"
collection_name = f"dbpedia-{MODEL_NAME}-{DIMENSIONS}"
embedding_column_name = f"{MODEL_NAME}-{DIMENSIONS}-embedding"

dataset = load_dataset(
        DATASET_NAME,
        streaming=False,
        split="train",
    )

bs = 1000
for i, record in tqdm(enumerate(dataset)):
    if i % bs == 0:
        points = []
    points.append({
        "embedding": record[embedding_column_name]
    })
    if i % bs == bs - 1:
        batch_points = f", ".join([f"('{p['embedding']}')" for p in points])
        conn.execute(f"INSERT INTO openai3072 (text_embedding_3_large_3072_embedding) VALUES %s" % (batch_points))
    print(f"Inserted {i} records")

此外,我们分别为 256 维、1024 维和 3072 维的嵌入向量创建了索引,并针对每个维度执行了近似最近邻(ANN)查询。注意,我们无需单独创建 256 维或 1024 维的列,因为可以直接通过表达式索引截取 3072 维嵌入向量的前 256 个或前 1024 个维度:

-- 创建 256 维表达式索引
CREATE INDEX openai_vector_index_256 ON public.openai3072 USING vectors (((text_embedding_3_large_3072_embedding[0:256])::vector(256)) vector_l2_ops);

-- 创建 1024 维表达式索引
CREATE INDEX openai_vector_index_1024 on openai3072 using vectors((text_embedding_3_large_3072_embedding[0:1024]::vector(1024)) vector_l2_ops);

-- 创建 3072 维完整索引
CREATE INDEX openai_vector_index on openai3072 using vectors(text_embedding_3_large_3072_embedding vector_l2_ops);

同时,我们还为 3072 维嵌入构建了二值向量索引(Binary Vector Index),并使用该索引执行了 ANN 查询:

CREATE INDEX openai_vector_index_bvector ON public.openai3072 USING vectors (text_embedding_3_large_3072_bvector bvector_l2_ops);

我们针对要检索的最近邻数量设置了不同的 LIMIT 值(5、10、50、100)来运行 KNN 查询。

详情

我们借助 pgbench 测量了每种查询的每秒查询数(QPS)和平均查询耗时:

export PGHOST="localhost"
export PGUSER="postgres"
export PGPASSWORD="mysecretpassword"
export PGDATABASE="postgres"
export PGPORT=5433
export VECTOR_DIM=3072
TEST_TIME=300
for clients in 2 4 8 16 32; do
    pgbench -n -T "${TEST_TIME}" \
      --file=query.sql \
      -c "${clients}" -j "${clients}"
done

query.sql 文件包含以下 SQL 查询:

\set random_id random(1, 1000000)

select id from openai3072 order by text_embedding_3_large_3072_embedding <-> (select text_embedding_3_large_3072_embedding from openai3072 where id = :random_id) limit 100;

实验结果如下所示:

不出所料,3072 维嵌入向量展现出最高的准确率;1024 维嵌入向量的准确率略低,约为 85%;256 维嵌入向量的准确率最低,约为 65%。另一方面,二值向量索引达到了约 80% 的准确率。

在每秒请求数(RPS)指标方面,二值向量索引表现出最高的性能效率,其次是 256 维嵌入、1024 维嵌入,最后是 3072 维嵌入。

详情
table

索引的内存占用是另一个需要重点考量的方面。为 100 万条 3072 维的二值向量建立索引仅需约 600MB 内存;相比之下,3072 维浮点向量索引则消耗了约 18GB 内存。两者在内存占用上存在巨大差异——3072 维浮点向量索引消耗的内存约为二值向量索引的 30 倍。

通过自适应检索提升准确率

与完整维度索引相比,低维索引和二值向量索引通过牺牲一定的准确率,换取了更高的 RPS 和更低的内存占用。而自适应检索(Adaptive Retrieval)技术可以将两者的优势结合起来,实现兼顾准确率、内存占用和查询速度的优化方案。

自适应检索背后的逻辑非常简单。以“获取前 100 个最佳候选结果”为例,我们可以分两步执行:

  1. 首先查询低维索引或二值向量索引,从 100 万条嵌入向量中快速召回前 200 个候选结果。这一步操作非常快。
  2. 使用全维度精确 KNN 查询对候选集进行重排序(Rerank),从中选出最终的前 100 个结果。由于精确 KNN 非常适合在小规模候选集上进行高精度相似度计算,因此在这里用于重排序再合适不过。

虽然重排序步骤会比单次粗召回略慢一点,但它仍然远快于直接查询完整维度的索引。在 pgvecto.rs 中可以通过如下 SQL 函数轻松实现:

CREATE OR REPLACE FUNCTION match_documents_adaptive(
  query_embedding vector(3072),
  match_count int
)
RETURNS SETOF openai3072
LANGUAGE SQL
AS $$
WITH shortlist AS (
  SELECT *
  FROM openai3072
  ORDER BY (text_embedding_3_large_3072_embedding[0:256])::vector(256) <-> (query_embedding[0:256])::vector(256)
  LIMIT match_count * 2
)
SELECT *
FROM shortlist
ORDER BY text_embedding_3_large_3072_embedding <-> query_embedding
LIMIT match_count;
$$;

match_documents_adaptive 函数接收查询向量 query_embedding 和匹配数量 match_count 作为输入参数。它首先使用 256 维索引从 100 万条记录中检索出 match_count * 2 条候选记录,随后使用 3072 维精确距离对这些候选记录进行重排序,返回最终的前 match_count 条记录。

基于二值向量索引和 1024 维索引的自适应检索函数也可以用完全相同的方式实现。

我们使用自适应检索技术再次运行了基准测试,并在下图中将其标记为 Adaptive Retrieval:

adaptive retrieval benchmark

可以看到,增加重排序步骤后,低维索引和二值向量索引的准确率得到了显著提升。

结合自适应检索的二值向量索引达到了约 95% 的准确率,而与不使用自适应检索相比,其 RPS 仅略有下降。更令人印象深刻的是,结合自适应检索的二值向量索引的内存占用依然只有约 600MB,与未启用自适应检索时完全相同(因为完整向量存储在磁盘表中,仅在重排序少量候选行时按需读取)。

详情
table

总结

缩短嵌入向量可以减少存储向量所需的内存占用,并加快索引构建和搜索的速度。然而,单纯使用缩短后的嵌入向量在准确率上不及完整嵌入向量。因此,我们需要结合自适应检索(两阶段检索与重排序)技术来提升低维索引和二值索引的准确率。

综合来看,对于 text-embedding-3-large 模型,结合自适应检索的二值向量索引是性价比最高的选择;如果您需要极高的准确率(例如 99%),也可以考虑使用结合自适应检索的 1024 维索引。

附注:本实验中使用的所有代码和数据均已在 pgvecto.rs-matryoshka-embeddings-benchmark 仓库中开源。

3.3 - 以图搜图与智能图像搜索

智能图像搜索(又称基于内容的图像检索)超越了传统的关键词搜索,让用户能够根据图像的视觉内容进行搜索。

智能图像搜索,又称基于内容的图像检索(Content-Based Image Retrieval),超越了传统的基于关键词的搜索方式,使用户能够根据图像的视觉内容搜索图像。例如,您可以直接搜索包含红蘑菇的图片(图片来自 immich):

image-search

这项功能看似神奇,因为它允许您在没有任何标签或关键词的情况下搜索图片。搜索引擎能够理解图像和视频的内容,并找出与查询语义相似的结果。

工作原理

智能图像搜索利用先进的计算机视觉技术和机器学习算法来理解图像中的视觉特征,从而使用户无需依赖特定的关键词或元数据即可执行自由形式的搜索。

第一步是从图像中提取有意义的特征,并生成每张图像的向量表示。这通常使用诸如 CLIP 之类的深度学习模型来完成。生成的向量表示被称为嵌入(Embedding),它是一个代表该图像视觉特征的数值列表。

随后,您可以使用像 pgvecto.rs 这样的向量数据库来存储这些嵌入向量,并执行高效的向量相似度搜索。

示例

我们将利用连接文本与图像的神经网络模型 CLIP 为图像生成嵌入向量。这些嵌入向量捕捉了图像中呈现的视觉概念。接着,我们将使用基于 Postgres 构建的可扩展向量搜索工具 pgvecto.rs 来存储这些嵌入并执行高效的向量搜索。通过结合 CLIP 和 pgvecto.rs 的强大能力,我们可以根据视觉内容检索相似图像。

生成嵌入向量

您可以使用 CLIP 为图像生成嵌入向量:

from transformers import CLIPProcessor, CLIPModel

processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")

def compute_image_embeddings(list_of_images):
    return model.get_image_features(**processor(images=list_of_images, return_tensors="pt", padding=True))

存储嵌入向量

假设您有一张名为 images 的表,包含三个列:id、author 和 embedding,其中 embedding 列的类型为 vector(512)。您可以按如下方式将图像数据插入表中:

-- 创建表
CREATE TABLE images (
    id SERIAL PRIMARY KEY,
    author TEXT NOT NULL,
    vector vector(512) NOT NULL -- 512 维
);

INSERT INTO images (author, embedding) VALUES
  ('Allen', '[...]');

搜索相似图像

您可以像这样搜索相似的图像:

SELECT * FROM images ORDER BY embedding <-> '[...]' LIMIT 5;

假设您希望搜索距离最近的 64 条记录,且要求结果中不包含作者 Allen 的图片。

您可能会期望以下 SQL 能够正常工作:

SET vectors.hnsw_ef_search = 64;
SELECT * FROM images ORDER BY embedding <-> '[...]' WHERE author != 'Allen' LIMIT 64;

在传统的 basic 搜索模式下,您可能最终只得到了 32 条记录,因为 HNSW 算法在图搜索过程中会绕过过滤条件,仅依赖 PostgreSQL 在搜索完成后对固定数量的候选集进行后置过滤(Post-filtering)。

您可以使用 vbase 搜索模式来解决这一问题。在 vbase 模式下,搜索结果变成了一个迭代器(Iterator),可以持续不断地返回近似最近邻结果。数据库可以从中不断拉取结果,直到满足用户的过滤条件和返回数量要求为止。这与 faiss 等其他向量搜索库截然不同——后者只能返回固定大小的结果集,且该大小必须在搜索前预先确定。对于带有过滤条件的查询,事先无法预知需要检索多少候选集才能凑够目标数量,因此传统做法要么导致返回结果数量不足,要么因过度搜索而增加延迟。该设计思想源于论文 VBASE: Unifying Online Vector Similarity Search and Relational Queries via Relaxed Monotonicity。

您可以通过 SQL 语句 SET vectors.search_mode = vbase; 启用 vbase 模式,从而获得符合预期的行为。immich 的图像搜索功能便采用了这一模式。从 0.2 版本起,vbase 已成为默认搜索模式。

真实生产案例

开源相册应用 immich 使用了智能图像搜索功能,使用户能够根据视觉内容搜索照片。此处提供的代码片段源自 immich 中图像搜索功能的真实实现:

SELECT a.*, s.*, e.*
FROM asset_entity AS a
INNER JOIN smart_search AS s ON s.assetId = a.id
LEFT JOIN exif_info AS e ON e.assetId = a.id
WHERE a.ownerId IN (:...userIds)
  AND a.isArchived = false
  AND a.isVisible = true
  AND a.fileCreatedAt < NOW()
ORDER BY s.embedding <=> :embedding
LIMIT :numResults;

该查询根据指定的过滤条件和向量距离排序,从 asset_entity、smart_search 和 exif_info 三张表中检索数据。下面我们来逐步拆解这条查询:

SELECT a.*, s.*, e.*

查询的这一部分指定了要从 asset_entity、smart_search 和 exif_info 表中选择的列,并使用别名 a、s 和 e 分别引用这三张表。

FROM asset_entity AS a
INNER JOIN smart_search AS s ON s.assetId = a.id
LEFT JOIN exif_info AS e ON e.assetId = a.id

在这里,asset_entity 表别名为 a,smart_search 表别名为 s,exif_info 表别名为 e。查询使用 INNER JOIN 基于条件 s.assetId = a.id 关联 asset_entity 与 smart_search,并使用 LEFT JOIN 基于条件 e.assetId = a.id 引入 exif_info 表中的匹配记录。

详情

asset_entity 表的结构定义如下:

CREATE TABLE asset_entity (
  id UUID PRIMARY KEY,
  deviceAssetId VARCHAR NOT NULL,
  ownerId VARCHAR NOT NULL,
  libraryId VARCHAR NOT NULL,
  deviceId VARCHAR NOT NULL,
  type VARCHAR NOT NULL,
  originalPath VARCHAR NOT NULL,
  resizePath VARCHAR,
  webpPath VARCHAR DEFAULT '',
  thumbhash BYTEA,
  encodedVideoPath VARCHAR,
  createdAt TIMESTAMPTZ NOT NULL,
  updatedAt TIMESTAMPTZ NOT NULL,
  deletedAt TIMESTAMPTZ,
  fileCreatedAt TIMESTAMPTZ NOT NULL,
  localDateTime TIMESTAMPTZ NOT NULL,
  fileModifiedAt TIMESTAMPTZ NOT NULL,
  isFavorite BOOLEAN DEFAULT false,
  isArchived BOOLEAN DEFAULT false,
  isExternal BOOLEAN DEFAULT false,
  isReadOnly BOOLEAN DEFAULT false,
  isOffline BOOLEAN DEFAULT false,
  checksum BYTEA NOT NULL,
  duration VARCHAR,
  isVisible BOOLEAN DEFAULT true,
  livePhotoVideoId UUID,
  originalFileName VARCHAR NOT NULL,
  sidecarPath VARCHAR,
  stackId UUID,
  CONSTRAINT fk_asset_owner FOREIGN KEY (ownerId) REFERENCES user_entity (id) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT fk_asset_library FOREIGN KEY (libraryId) REFERENCES library_entity (id) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT fk_asset_live_photo_video FOREIGN KEY (livePhotoVideoId) REFERENCES asset_entity (id) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT fk_asset_stack FOREIGN KEY (stackId) REFERENCES asset_stack_entity (id) ON DELETE SET NULL ON UPDATE CASCADE
);
WHERE a.ownerId IN (:...userIds)
  AND a.isArchived = false
  AND a.isVisible = true
  AND a.fileCreatedAt < NOW()

在这一部分中,查询对 asset_entity 表应用了过滤条件:筛选出 ownerId 在指定的 userIds 列表中、isArchived 为 false、isVisible 为 true,且文件创建时间 fileCreatedAt 早于当前时间(NOW())的行。

ORDER BY s.embedding <=> :embedding

查询使用余弦距离操作符 <=> 将 smart_search 表的 embedding 列与传入的查询向量 :embedding 进行比较,并按距离从小到大排序。

LIMIT :numResults;

最后,将查询返回的行数限制为 :numResults 参数指定的值。

这是一个典型的真实生产环境案例,展示了如何在实际业务系统中结合关系查询、权限过滤与向量搜索来实现智能图像检索。

3.4 - 多租户架构

多租户对于 SaaS 应用至关重要,它允许单个实例服务多个用户或群组,同时确保数据隐私与安全。本文概述如何使用 pgvecto.rs 设计并实现一个简单的多租户向量搜索系统。

多租户(Multi-tenancy)对于 SaaS 应用至关重要,它允许单个数据库实例同时服务多个用户或团队,同时确保数据隐私与隔离安全。本文将介绍如何使用 pgvecto.rs 设计并实现一个简洁的多租户向量搜索系统。

设计数据库表结构

在多租户系统中,基于用户的隔离能否成功,取决于结构良好的数据库 Schema 设计:

用户与文档(Users and Documents)

每个用户由唯一的 user_id 标识,该标识直接关联到其拥有的文档以实现私有访问控制。documents 表存储每个用户的文档,每篇文档具有唯一的 id、title(标题)和 content(内容)。还可以在 documents 表中加入其他元数据字段以增强搜索功能。为了更真实地还原生产场景,我们将 user_id 关联在 documents 表上,而不是直接冗余在切片嵌入(chunk embeddings)表中。

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL,
    title TEXT,
    content TEXT,
    -- 其他元数据字段
);

文本切片(Chunks)

chunks 表用于存储文档切片的向量化表示(embedding),并通过外键 document_id 引用其所属的文档。

CREATE TABLE chunks (
    id SERIAL PRIMARY KEY,
    document_id INTEGER REFERENCES documents(id),
    embedding vector(512) NOT NULL
);

实体关系图(ER 图)

实现多租户查询

创建索引

在 chunks.embedding 列上创建向量索引以加速查询过程。这里我们使用点积(dot product)作为相似度度量方式:

CREATE INDEX idx_chunks_embedding ON chunks USING vectors (embedding vector_dot_ops);

按 user_id 执行带过滤的向量查询

SELECT chunks.id AS chunk_id, documents.id AS document_id, documents.title
FROM chunks
INNER JOIN documents ON chunks.document_id = documents.id
WHERE documents.user_id = 'alice' /* 替换为实际的 user_id */
ORDER BY chunks.embedding <-> '[3,2,1]' /* 替换为查询向量 */ LIMIT 5;

在这条查询中:

  • SELECT 子句包含了 chunks.id AS chunk_id、documents.id AS document_id 和 documents.title,分别返回切片 ID、文档 ID 和文档标题。
  • INNER JOIN 与 WHERE 条件确保只检索属于指定用户(如 'alice')的文档所关联的文本切片。
  • ORDER BY 子句计算切片嵌入向量与给定查询向量 '[3,2,1]' 之间的距离,并按相似度排序,最接近(最相似)的切片会排在最前面返回。
  • 在实际使用时,请将 '[3,2,1]' 替换为真实的查询嵌入向量,并将 'alice' 替换为目标用户的实际 ID。

性能优化建议

在带过滤条件的向量搜索过程中,索引会扫描额外的候选点并逐一评估过滤条件。因此,如果系统中的用户 ID 数量非常庞大,或者某个用户的过滤条件命中率极低(数据极其稀疏),查询速度可能会受到影响。为了进一步提升大规模多租户场景下的性能,建议结合使用 PostgreSQL 的表分区(Partitioned Table)功能按租户进行分区。

3.5 - 稀疏向量搜索

过去,混合搜索结合了传统的关键词搜索与基于向量的相似度搜索。如今,稀疏向量可以替代关键词搜索,通过纯向量搜索释放数据流水线的全部潜力。

过去,混合搜索(Hybrid Search)通常将传统的基于关键词的搜索与基于向量的相似度搜索结合在一起。然而,稀疏向量(Sparse Vector)可以作为关键词搜索的替代方案,通过纯向量搜索充分释放数据流水线的全部潜力。

提示

有关基于关键词的混合搜索的更多信息,请参阅混合搜索。

本文将探讨如何结合使用 BGE-M3 与 pgvecto.rs,同时利用稀疏向量和稠密向量发掘混合搜索的潜力。

下面让我们开始吧。

稀疏向量与稠密向量嵌入概览

稀疏向量是仅包含极少数非零值的高维向量,非常适合传统的信息检索场景。例如,一个拥有 32,000 个维度但只有 2 个非零元素的向量就是典型的稀疏向量。

S[1×32000]=[000.0150000⋯0.5430] S[1\times 32000]=\left[ \begin{matrix} 0 & 0 & 0.015 & 0 & 0 & 0 & 0 & \cdots & 0.543 & 0 \end{matrix} \right]

稠密向量(Dense Vector)则是来自神经网络的嵌入表示。它们由文本嵌入模型生成,绝大部分或所有元素均为非零值。它们的维度通常较低,例如 256 维或 1536 维,远低于稀疏向量。

D[1×256]=[0.3421.7740.0870.3210.6640.8700.001⋯0.5430.999] D[1\times 256]=\left[ \begin{matrix} 0.342 & 1.774 & 0.087 & 0.321 & 0.664 & 0.870 & 0.001 & \cdots & 0.543 & 0.999 \end{matrix} \right]

稠密向量和稀疏向量都用于表示文本片段的特征,但两者存在显著差异。稠密向量嵌入中的单个维度本身并没有明确的字面含义,因为它过于抽象;只有将所有维度组合在一起时,它们才能表达输入文本的整体语义。

相比之下,稀疏向量的维度等于词表(Vocabulary)的大小,其中每个元素代表对应词项在该文本中的权重。

通常情况下,权重越高的词在文本中越重要。稀疏向量简化了对文本与词表之间关系的分析和可视化;而若要对稠密向量进行可视化,则需要借助额外的降维算法,例如:

稠密向量(Dense Vector) 稀疏向量(Sparse Vector)
维度规模 数百至数千维 数万至数十万维
单个元素含义 无单独含义 词项权重(Word Weight)
可解释性 困难 容易
常见用途 计算向量距离 计算向量距离 / 可视化分析
语义敏感度 高 低

与稠密向量相比,稀疏向量在捕捉词与词之间的深层语义关系(尤其是同义词和相关概念)方面通常不如稠密向量有效。

例如,rocket(火箭)和 SPACE X 都与太空探索相关,稠密向量更有可能识别出它们之间的语义相似性,并为相应文本赋予更近的向量距离。

如何生成向量嵌入?

向量嵌入是输入数据在深度学习模型(即嵌入模型)中的内部表示。大多数嵌入模型(如 text-embedding-3-small)仅输出稠密嵌入。

而 SPLADE 和 BGE-M3 等模型则可以生成稀疏嵌入(有时也称为词法权重 lexical weights)。在本节中,我们将介绍如何使用 BGE-M3 模型同时生成稠密向量和稀疏向量。

BGE-M3 是一个多功能模型,能够同时执行嵌入模型的三种常见检索功能:稠密检索(Dense retrieval)、多向量检索(Multi-vector retrieval)和稀疏检索(Sparse retrieval)。下面我们来学习如何用它生成稠密嵌入和稀疏嵌入。

# pip install -U FlagEmbedding

from FlagEmbedding import BGEM3FlagModel

model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=True)
sentences = ["What is BM25?", "Definition of BM25"]

output = model.encode(sentences, return_dense=True, return_sparse=True)

# 上述两段文本的稠密嵌入:
dense_embedding = output['dense_vecs']
# [[-0.02501617 -0.04525582 -0.01972856 ... -0.0099566   0.01677509
#   0.01292699]
# [-0.0106499  -0.04731942 -0.01477693 ... -0.0149255   0.01040097
#   0.00965083]

sparse_weight_id = output['lexical_weights']

# 您可以查看每个 token 对应的权重:
# print(model.convert_id_to_token(sparse_weight_id))
# 
# [{'What': 0.10430284, 'is': 0.10090457, 'BM': 0.2635918, '25': 0.3382988, '?': 0.052101523}, 
# {'Definition': 0.14298248, 'of': 0.076763615, 'BM': 0.2577639, '25': 0.33806682}]

对于所有句子和词项,可以构建如下的“文本-词项”矩阵:

文本 What is BM 25 ? Definition of …
What is BM25? 0.08 0.08 0.13 0.25 0.04 0 0 …
Definition of BM25 0 0 0.25 0.33 0 0.14 0.06 …

当包含整个词表时,该矩阵是高度稀疏的——大多数词只出现在少数几个句子中,只有这些位置会产生非零权重。

通过将文本输入到像 BGE-M3 这样的稀疏嵌入模型中,我们可以轻松获得文本的稀疏嵌入表示:

# pip install -U numpy

import numpy as np

# 提取文本的稀疏向量下标与对应权重值
sparse_indices = [[int(token_id) for token_id in text] for text in sparse_weight_id]
# [[4865, 83, 90017, 2588, 32], [155455, 111, 90017, 2588]]
sparse_values = [[float(text[token_id]) for token_id in text] for text in sparse_weight_id]
# [[0.10430284, 0.10090457, 0.2635918, 0.3382988, 0.052101523], [0.14298248, 0.076763615, 0.2577639, 0.33806682]]

# 在 pgvecto.rs 中,每个稀疏向量的下标必须按升序排列
for i in range(len(sparse_weight_id)):
    sparse_values[i] = [x for _, x in sorted(zip(sparse_indices[i], sparse_values[i]))]
    sparse_indices[i] = sorted(sparse_indices[i])

现在我们同时拥有了稀疏嵌入和稠密嵌入,接下来可以将它们插入向量数据库了。

在 pgvecto.rs 中存储向量

首先,向量存储和检索需要一个运行中的 pgvecto.rs 实例。可以通过官方的 pgvecto-rs Docker 镜像快速启动:

docker run \
  --name pgvecto-rs-demo \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d tensorchord/pgvecto-rs:pg16-v0.3.0

准备好数据库环境后,让我们创建一张表来存储向量。

pgvecto.rs 通过不同的数据类型分别支持稠密向量和稀疏向量:

  • vector:用于存储稠密向量
  • svector:用于存储稀疏向量

BGE-M3 模型的稠密嵌入维度为 1024,而稀疏嵌入维度为 250002(与分词器词表的大小一致)。

print(len(model.tokenizer))
# 250002

在本例中,我们创建一张名为 documents 的表,包含四个列:ID 列(id)、文本列(text)以及两个向量列(sparse 用于存储稀疏向量,dense 用于存储稠密向量)。

# pip install -U psycopg

import psycopg
URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"

with psycopg.connect(URL) as conn:
    conn.execute("DROP TABLE IF EXISTS documents;")
    conn.execute(f"""
        CREATE TABLE documents (
            id SERIAL PRIMARY KEY, 
            text TEXT NOT NULL,
            dense vector(1024) NOT NULL,
            sparse svector({len(model.tokenizer)}) NOT NULL);
    """
    )

表创建完成后,我们可以将嵌入向量插入表中,并为两个向量列分别创建索引:

# pip install -U pgvecto_rs

import psycopg
from pgvecto_rs.psycopg import register_vector
URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"

with psycopg.connect(URL) as conn:
    register_vector(conn)
    for text, dense, sparse_ind, sparse_val in zip(sentences, dense_embedding, sparse_indices, sparse_values):
        conn.execute(
            "INSERT INTO documents (text, dense, sparse) VALUES (%s, %s, to_svector(%s, %s::real[]::int[], %s::real[]));",
            (text, dense, len(model.tokenizer), sparse_ind, sparse_val),
        )
    conn.execute("""
        CREATE INDEX ON documents 
        USING vectors (sparse svector_dot_ops) 
        WITH (options = \"[indexing.hnsw]\");
    """)
    conn.execute("""
        CREATE INDEX ON documents 
        USING vectors (dense vector_l2_ops) 
        WITH (options = \"[indexing.hnsw]\");
    """)

对于稠密向量,vector_l2_ops 计算平方欧氏距离,这是最常用的距离度量:

DL2=Σ(xi−yi)2 D_{L2} = \Sigma (x_i - y_i) ^ 2

对于稀疏向量,svector_dot_ops 计算负点积,这种计算方式在稀疏表示下更加高效:

Ddot=−Σxiyi D_{dot} = - \Sigma x_i y_i

有关 pgvecto.rs 中索引类型的更多信息,请查看我们的索引文档。

检索与结果融合

将向量存入数据库后,我们便可以使用 pgvecto.rs 扩展高效地查询它们。为了找出与给定输入文本最接近的向量,我们需要使用相同的模型对查询文本进行编码。

该编码过程与上一节完全一致:

def embedding(sentence: str) -> tuple[np.ndarray, list[list[int]], list[list[float]]]:
    """
    从文本生成稠密嵌入和稀疏嵌入
    """
    output = model.encode(sentence, return_dense=True, return_sparse=True, return_colbert_vecs=False)
    dense_embedding = output['dense_vecs']
    sparse_weight_id = output['lexical_weights']
    sparse_indices = [int(token_id) for token_id in sparse_weight_id]
    sparse_values = [float(sparse_weight_id[token_id]) for token_id in sparse_weight_id]

    sparse_values = [x for _, x in sorted(zip(sparse_indices, sparse_values))]
    sparse_indices = sorted(sparse_indices)
    return dense_embedding, sparse_indices, sparse_values

借助 pgvecto.rs 扩展,只需通过简单的 SELECT 语句即可分别检索稀疏向量和稠密向量:

dense_embedding, sparse_indices, sparse_values = embedding("The text you want to search...")

with psycopg.connect(URL) as conn:
    register_vector(conn)
    cur = conn.execute(
        "SELECT text FROM documents ORDER BY sparse <#> to_svector(%s, %s::real[]::int[], %s::real[]) LIMIT 50;",
        (len(model.tokenizer), sparse_indices, sparse_values),
    )
    sparse_result = cur.fetchall()
      
    cur = conn.execute(
              "SELECT text FROM documents ORDER BY dense <-> %s LIMIT 50;",
              (dense_embedding,),
          )
    dense_result = cur.fetchall()
    
# 合并稀疏向量与稠密向量的搜索结果
mix_text = set([r[0] for r in dense_result]).union([r[0] for r in sparse_result])

为了对合并后的结果进行重排序(Rerank),您可以使用常见的融合算法,例如倒数排名融合(Reciprocal Rank Fusion, RRF)。不过,为了充分发挥 BGE-M3 生态的优势,我们可以引入配套的 Reranker(重排序)模型。与嵌入模型不同,Reranker 直接以查询文本和候选文本对作为输入,并输出它们之间的相关性得分。

我们可以初始化一个 Reranker 模型,并将合并后的候选文本输入其中。通过这种方式,只需按 Reranker 模型估算的得分进行排序,即可轻松完成重排序:

from FlagEmbedding import FlagReranker

reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=True)

# 获取相关性得分(得分越高表示相关性越强)
scores = reranker.compute_score([["The text you want to search...", candidate] for candidate in mix_text])
# 对结合了稠密嵌入与稀疏嵌入检索出的候选文本进行重排序
reranked_text = [t for _, t in sorted(zip(scores, mix_text))]

恭喜!至此,我们已经迈出了探索稀疏向量搜索世界的第一步。

3.6 - 生成式问答(Generative QA)

向量搜索可用于为 OpenAI GPT 系列等生成式模型提供上下文,从而提升输出质量。

向量搜索还可以用于为生成式模型(例如 OpenAI 的 GPT 系列)提供上下文,从而提升模型输出的质量。

本文将探讨如何使用 pgvecto.rs 构建向量知识库并从中检索信息,以增强生成式模型在问答(Question-Answering, QA)任务中的表现。知识库充当了生成式模型的“长期记忆”,为其提供持久化、经过整理且准确的信息作为上下文(Context),供模型在生成问题答案时参考。这样不仅能生成更准确的回答,减少模型产生“幻觉”(毫无事实依据的陈述)的可能性,还能增强用户对输出结果的信任。这项技术被称为检索增强生成(Retrieval-Augmented Generation,简称 RAG):更多信息请参阅这篇博文或相关论文。

问答任务概览

生成式模型的问答(QA)任务是一种**信息检索(Information Retrieval)**方法。模型接收一个问题,并根据给定的上下文(通常是模型可以查阅以获取回答问题所需信息的文档或其他信息源)生成答案。

在本案例中,我们将使用 Hugging Face 上的 vishnupriyavr/wiki-movie-plots-with-summaries-faiss-embeddings 数据集,为 OpenAI 的 gpt-3.5-turbo-instruct 模型提供上下文,以回答有关电影的问题。

整体工作流程如下所示:

无上下文的问答

首先,让我们尝试在不提供任何上下文的情况下让模型回答几个问题。

我们先定义一个函数,用于简化向模型发送提示词(Prompt)并获取响应的过程:

# pip install openai

import openai

openai.api_key = "YOUR_API_KEY_HERE"
def complete(prompt):
    result = openai.Completion.create(
        engine='gpt-3.5-turbo-instruct',
        prompt=prompt,
        temperature=0,
        max_tokens=400,
        top_p=1,
        frequency_penalty=0,
        presence_penalty=0,
        stop=None
    )
    return result['choices'][0]['text'].strip()
说明

获取 api_key 需要拥有 OpenAI 账号。登录后,可从右上角下拉菜单中申请 API 密钥,并将其粘贴替换代码中的 YOUR_API_KEY_HERE。

让我们先提问一部知名度较高的电影:

base_prompt = "What is the release year and overall plot of Skyfall?"
print(complete(base_prompt))
输出:

The release year of Skyfall is 2012. The overall plot follows James Bond as he investigates an attack on MI6 and uncovers a sinister plot by a former agent seeking revenge on M and the entire agency. Bond must confront his past and protect MI6 from destruction.

回答相当准确,尽管剧情简介略微笼统。但是,如果换作一部知名度低得多的电影呢?

base_prompt_2 = "What is the release year and overall plot of Cat Napping?"
print(complete(base_prompt_2))
输出:

The release year of Cat Napping is unknown. The overall plot of Cat Napping is about a group of cats who go on a mission to rescue their friend who has been kidnapped by a group of evil dogs. Along the way, they encounter various challenges and obstacles, but with their cleverness and teamwork, they are able to save their friend and defeat the dogs.

正确答案:

《Cat Napping》(猫咪午睡)实际上是 1951 年上映的一部《猫和老鼠》(Tom and Jerry)动画短片,讲述了汤姆和杰瑞争夺一张吊床的故事。

我们已经触及了模型自身“知识”的边界:它无法给出上映年份,并且仅凭片名编造了一段剧情。接下来,我们将通过构建一个供模型检索的知识库来解决这一问题。

要让模型更好地回答我们的问题,通常有两种选择:

  1. 使用目标领域的文本数据(本例中为电影数据)对模型进行微调(Fine-tune)。
  2. 采用检索增强生成(RAG),在流程中引入信息检索组件。这使我们能够检索相关信息并将其作为辅助信息源输入给模型。

在本文中,我们将采用第二种方案。

构建知识库

让我们使用 Hugging Face 上的 vishnupriyavr/wiki-movie-plots-with-summaries-faiss-embeddings 数据集构建一个电影知识库,以便从中检索相关信息。该数据集包含 33,155 部电影的片名、上映年份、演员阵容、维基百科页面、剧情简介/长度,以及这些信息的向量嵌入(Vector Embeddings)。每部电影还包含一个综合了片名、上映年份、演员阵容和剧情简介的 text 字段。在本应用中,我们将使用其中的 text 和 embeddings 数据。

加载数据

首先加载数据集:

# pip install -U datasets
from datasets import load_dataset
dataset = load_dataset("vishnupriyavr/wiki-movie-plots-with-summaries-faiss-embeddings", split='train')

texts = dataset['train']['text']
# texts[0]
# 'Alice in Wonderland \n  starring May Clark \n  released in the year1903 \n  with the following plot  \n Alice follows a large white rabbit down a "Rabbit-hole". She finds a tiny door. When she finds a bottle labeled "Drink me", she does, and shrinks, but not enough to pass through the door. She then eats something labeled "Eat me" and grows larger. She finds a fan when enables her to shrink enough to get into the "Garden" and try to get a "Dog" to play with her. She enters the "White Rabbit\'s tiny House," but suddenly resumes her normal size. In order to get out, she has to use the "magic fan."\r\nShe enters a kitchen, in which there is a cook and a woman holding a baby. She persuades the woman to give her the child and takes the infant outside after the cook starts throwing things around. The baby then turns into a pig and squirms out of her grip. "The Duchess\'s Cheshire Cat" appears and disappears a couple of times to Alice and directs her to the Mad Hatter\'s "Mad Tea-Party." After a while, she leaves.\r\nThe Queen invites Alice to join the "ROYAL PROCESSION": a parade of marching playing cards and others headed by the White Rabbit. When Alice "unintentionally offends the Queen", the latter summons the "Executioner". Alice "boxes the ears", then flees when all the playing cards come for her. Then she wakes up and realizes it was all a dream.'

生成向量嵌入

由于该数据集自带了向量嵌入,您可以跳过生成步骤,直接使用现成的嵌入数据:

embeddings = dataset['train']['embeddings']

如果您希望从文本数据生成向量嵌入,可以使用 OpenAI、Cohere 或其他嵌入 API。下面我们提供一个使用 Cohere API 的示例(您可以在此处获取 Cohere API 密钥):

# pip install cohere
import getpass
import os
import cohere

if not os.getenv("COHERE_API_KEY"):
    os.environ["COHERE_API_KEY"] = getpass.getpass("Enter your Cohere API key: ")

co = cohere.Client()
# 嵌入模型可参考此文档:https://docs.cohere.com/docs/cohere-embed
embed = co.embed(
    texts=texts,
    input_type="search_query",
    model="embed-english-v3.0"
)
embeddings = embed.embeddings
说明

本案例中的嵌入为稠密向量(Dense Vectors)。如需创建稀疏向量(Sparse Vectors),请参阅稀疏向量章节。

将数据插入数据库

向量存储与检索需要部署一个 pgvecto.rs 实例。我们可以使用官方的 pgvecto-rs Docker 镜像:

docker run \
  --name pgvecto-rs-demo \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d tensorchord/pgvecto-rs:pg16-v0.3.0

安装用于与 Postgres 交互的 psycopg 库,并确保扩展已启用:

#pip install "psycopg[binary,pool]"
import psycopg

URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"
# 启用扩展
with psycopg.connect(URL) as conn:
    conn.execute("DROP EXTENSION IF EXISTS vectors;")
    conn.execute("CREATE EXTENSION vectors;")

现在,我们创建一张名为 movies 的表,包含三个列:ID 列(id)、文本列(text)和向量列(embedding)。

import psycopg
URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"

# 创建表
with psycopg.connect(URL) as conn:
    conn.execute("DROP TABLE IF EXISTS movies;")
    conn.execute(f"""
        CREATE TABLE movies (
            id SERIAL PRIMARY KEY, 
            text TEXT NOT NULL,
            embedding vector(768) NOT NULL);
    """
    )
说明

由于我们处理的是稠密向量嵌入,因此使用 vector(n) 类型,其中 n 是每个嵌入向量的维度(本例中为 768)。

表创建完成后,我们可以将数据集中的嵌入插入表中:

# 可选安装,但推荐使用以显示进度条:
# pip install tqdm
import psycopg
from tqdm import tqdm
URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"

# 插入数据到表中
with psycopg.connect(URL) as conn:
    with conn.cursor() as cursor:    
        for text, embedding in tqdm(zip(texts, embeddings), total=len(texts)):
            cursor.execute(
                """INSERT INTO movies (text, embedding) 
                    VALUES (%s, %s::real[]);""",
                (text, embedding),
            )
        conn.commit()
警告

根据待插入数据量的大小,此过程可能需要花费一些时间。

创建索引

import psycopg
URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"

# 为 embedding 列创建索引
with psycopg.connect(URL) as conn:
    cursor = conn.cursor()
    cursor.execute("""
        CREATE INDEX ON movies 
        USING vectors (embedding vector_l2_ops) 
        WITH (options = \"[indexing.hnsw]\");
    """)
    conn.commit()
说明

回顾一下,对于稠密向量,vector_l2_ops 计算的是平方欧氏距离(Squared Euclidean distance),这也是最常用的距离度量方式:

>DL2=Σ(xi−yi)2> > D_{L2} = \Sigma (x_i - y_i) ^ 2 >

结合上下文的问答

为了利用我们的知识库,首先需要对查询语句进行编码,以确定应当检索哪些文本:

# pip install sentence-transformers

from sentence_transformers import SentenceTransformer

# 可选模型:'paraphrase-mpnet-base-v2', 'all-mpnet-base-v2'
encoder = SentenceTransformer('all-mpnet-base-v2')

def encode_query(query): # query 为字符串
    # 预处理查询语句
    query = query.lower()
    query = query.split()
    query = [word for word in query]
    query = ' '.join(query)
    
    # 对查询进行编码
    vector = encoder.encode(query)
    return vector

接下来,我们定义一个函数,自动将检索到的上下文拼接到初始提示词中:

import psycopg
URL = "postgresql://postgres:mysecretpassword@localhost:5432/postgres"

def add_context(prompt):
    search_vector = encode_query(prompt).tolist()
    # 构造包含检索上下文的提示词
    prompt_start = (
        "Answer the question based on the following context:\n\n"
    )
    prompt_end = (
        f"\n\nQuestion: {prompt}\nAnswer:"
    )
    with psycopg.connect(URL) as conn:
        cursor = conn.cursor()
        # 根据希望提供给模型的电影文本数量调整 LIMIT 值
        cursor.execute("SELECT text FROM movies ORDER BY embedding <-> %s::real[] LIMIT 3;", 
                  (search_vector,),)
        result = cursor.fetchall()
    context = ""
    for r in result:
        context += r[0] + "\n\n"
    return prompt_start + context + prompt_end
说明

回顾一下距离操作符的选项:

  • <->:平方欧氏距离(Squared Euclidean distance)
  • <#>:负点积(Negative dot product)
  • <=>:余弦距离(Cosine distance)

有关详细定义,请参阅概览。

现在,让我们附上检索到的上下文,再次尝试之前的提问:

base_prompt_2 = "What is the release year and overall plot of Cat Napping?"

context_prompt_2 = add_context(base_prompt_2)
context_response_2 = complete(context_prompt_2)
print(context_response_2)
输出:

The release year of Cat Napping is 1951 and the overall plot involves Tom and Jerry fighting over a hammock, with Jerry ultimately getting revenge on Tom.

现在模型给出了准确的回答!我们甚至可以针对细节进一步追问:

base_prompt_3 = "In Cat Napping, what does Tom do to Jerry?"
print(complete(add_context(base_prompt_3)))
输出:

Tom tries to send Jerry sliding into a nearby pond by unhooking the hammock he is sleeping in. He also tries to whack the hammock to send Jerry flying into the air, but Jerry lands in a bird’s nest. Tom then picks Jerry up on a spatula and places him onto a walking army of ants, causing Jerry to wake up as he bumps his head on a sprinkler. Tom also tries to chase Jerry with a lawn mower and a baseball bat.

正如我们在前文中所看到的,大语言模型(LLM)本身虽然非常强大,但在面对冷门或特定领域的问题时往往力不从心,容易产生不易察觉的“幻觉”。引入向量知识库能够显著提升回答的事实准确性,进而增强用户对生成结果的信任。

更多应用场景

当然,这种方法不仅限于电影问答。其他应用场景还包括提取科学论文摘要(不妨试试这个数据集!),或者基于企业内部文档库回答有关公司规章制度的问题。

4 - 生态集成

将 pgvecto.rs 与 LangChain、LlamaIndex 以及 Citus 分布式 PostgreSQL 集成。

4.1 - LangChain

本文介绍如何将 pgvecto.rs 与 LangChain 集成。

本文介绍如何将 pgvecto.rs 与 LangChain 集成。

LangChain 是一个用于开发由语言模型驱动的应用程序的框架。它提供了一套工具,支持在生产环境中微调、部署和使用语言模型。

pgvecto.rs 提供了 LangChain 集成,支持在 LangChain 中检索最相似的向量。

安装依赖

使用 LangChain 集成需要安装以下依赖:

pip install langchain langchain-openai pgvecto-rs

你可以在 Docker 容器中启动带有 pgvecto.rs 扩展的 Postgres 实例:

docker run \
  --name pgvecto-rs-demo \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d tensorchord/pgvecto-rs:pg16-v0.2.0

然后可以使用 psql 命令行工具连接到数据库。默认用户名为 postgres,默认密码为 mysecretpassword。

psql postgresql://postgres:mysecretpassword@localhost:5432/postgres

执行以下 SQL 以确保扩展已启用:

DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

创建数据库并加载文档

下面展示如何在 LangChain 中使用 pgvecto.rs 检索最相似的向量。

首先,你需要创建文本加载器(TextLoader)和文本切分器(CharacterTextSplitter),将文本切分为多个片段(chunk)。这里以 Markdown 文件 getting-started/overview.md 为例。

## Loading Environment Variables
from dotenv import load_dotenv

load_dotenv()

from langchain.docstore.document import Document
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores.pgvecto_rs import PGVecto_rs
from langchain_openai import OpenAIEmbeddings


loader = TextLoader("./src/getting-started/overview.md")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

embeddings = OpenAIEmbeddings()

接下来,创建 PGVecto_rs 实例并将文档加载到数据库中。

## PGVecto.rs needs the connection string to the database.
## We will load it from the environment variables.
import os

PORT = os.getenv("DB_PORT", 5432)
HOST = os.getenv("DB_HOST", "localhost")
USER = os.getenv("DB_USER", "postgres")
PASS = os.getenv("DB_PASS", "mysecretpassword")
DB_NAME = os.getenv("DB_NAME", "postgres")

# Run tests with shell:
URL = "postgresql+psycopg://{username}:{password}@{host}:{port}/{db_name}".format(
    port=PORT,
    host=HOST,
    username=USER,
    password=PASS,
    db_name=DB_NAME,
)

# The pgvectors Module will try to create a table with the name of the collection.
# So, make sure that the collection name is unique and the user has the permission to create a table.

COLLECTION_NAME = "state_of_the_union_test"

db = PGVecto_rs.from_documents(
    embedding=embeddings,
    documents=docs,
    collection_name=COLLECTION_NAME,
    db_url=URL,
)

查询索引

最后,我们可以在 LangChain 中检索最相似的文档片段。

query = "What is pgvecto.rs"
docs_with_score = db.similarity_search_with_score(query)

for doc, score in docs_with_score:
    print("-" * 80)
    print("Score: ", score)
    print(doc.page_content)
    print("-" * 80)

输出结果如下:

Created a chunk of size 1181, which is longer than the specified 1000
--------------------------------------------------------------------------------
Score:  0.25059962
# Overview

An introduction to the pgvecto.rs.

## What is pgvecto.rs

pgvecto.rs is a Postgres extension that provides vector similarity search functions. It is written in Rust and based on [pgrx](https://github.com/tcdi/pgrx). It is currently in the beta status, we invite you to try it out in production and provide us with feedback. Read more at [📝our launch blog](https://modelz.ai/blog/pgvecto-rs).

## Why use pgvecto.rs
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Score:  0.29536954
- 💃 **Easy to use**: pgvecto.rs is a Postgres extension, which means that you can use it directly within your existing database. This makes it easy to integrate into your existing workflows and applications.
- 🔗 **Async indexing**: pgvecto.rs's index is asynchronously constructed by the background threads and does not block insertions and always ready for new queries.
- 🥅 **Filtering**: pgvecto.rs supports filtering. You can set conditions when searching or retrieving points. This is the missing feature of other postgres extensions.
- 🧮 **Quantization**: pgvecto.rs supports scalar quantization and product quantization up to 64x.
- 🦀 **Rewrite in Rust**: Rust's strict compile-time checks ensure memory safety, reducing the risk of bugs and security issues commonly associated with C extensions.

## Comparison with pgvector
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Score:  0.35845917
More details at [📝`pgvecto.rs` vs. pgvector](../faqs/comparison-pgvector.md).

## Quick start

For new users, we recommend using the [Docker image](https://hub.docker.com/r/tensorchord/pgvecto-rs) to get started quickly.
...

从现有数据库初始化

在上面的示例中,我们从零开始创建了一个向量存储。但在很多时候,我们希望连接并使用已有的向量存储,此时可以直接对其进行初始化:

db = PGVecto_rs(
    embedding=embeddings,
    collection_name=COLLECTION_NAME,
    # OpenAI embedding has 1536 dimensions.
    dimension=1536,
    db_url=URL,
)

随后,我们可以向存储中添加向量并进行查询:

db.add_documents([Document(page_content="foo")])
docs_with_score = db.similarity_search_with_score("foo")
print(docs_with_score[0])

输出结果如下:

(Document(page_content='foo'), 0.0)

4.2 - LlamaIndex

LlamaIndex 是一个简单、灵活的数据框架,用于将自定义数据源连接到大语言模型(LLM)。

LlamaIndex 是一个简单、灵活的数据框架,用于将自定义数据源连接到大语言模型(LLM)。

pgvecto.rs 提供了 LlamaIndex 集成,支持检索最相似的向量。

安装依赖

使用 LlamaIndex 集成需要安装以下依赖:

pip install llama-index "pgvecto_rs[sdk]"

你可以在 Docker 容器中启动带有 pgvecto.rs 扩展的 Postgres 实例:

docker run \
  --name pgvecto-rs-demo \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d tensorchord/pgvecto-rs:pg16-v0.2.0

然后可以使用 psql 命令行工具连接到数据库。默认用户名为 postgres,默认密码为 mysecretpassword。

psql postgresql://postgres:mysecretpassword@localhost:5432/postgres

执行以下 SQL 以确保扩展已启用:

DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;

创建数据库并加载文档

首先,创建数据库连接客户端。这里同样以 Markdown 文件 getting-started/overview.md 为例。

import logging
import os
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))

from pgvecto_rs.sdk import PGVectoRs

URL = "postgresql+psycopg://{username}:{password}@{host}:{port}/{db_name}".format(
    port=os.getenv("DB_PORT", "5432"),
    host=os.getenv("DB_HOST", "localhost"),
    username=os.getenv("DB_USER", "postgres"),
    password=os.getenv("DB_PASS", "mysecretpassword"),
    db_name=os.getenv("DB_NAME", "postgres"),
)

client = PGVectoRs(
    db_url=URL,
    collection_name="example",
    dimension=1536,  # Using OpenAI’s text-embedding-ada-002
)

接着配置 OpenAI API 密钥,并将文档加载到数据库中。

import os

os.environ["OPENAI_API_KEY"] = "sk-..."

from llama_index import SimpleDirectoryReader, VectorStoreIndex
from llama_index.vector_stores import PGVectoRsStore

# load documents
documents = SimpleDirectoryReader("./src/getting-started/overview.md").load_data()

# initialize without metadata filter
from llama_index.storage.storage_context import StorageContext

vector_store = PGVectoRsStore(client=client)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
    documents, storage_context=storage_context
)

查询索引

最后,我们可以检索最相似的文档片段。

# set Logging to DEBUG for more detailed outputs
query_engine = index.as_query_engine()
response = query_engine.query("What did the author do growing up?")

4.3 - Citus

Citus 是将 Postgres 转化为分布式数据库的 PostgreSQL 扩展。pgvecto.rs 可与 Citus 良好配合,本文介绍在分布式 PostgreSQL 实例中启用向量检索的步骤。

citus 是一个将 Postgres 转化为分布式数据库的 PostgreSQL 扩展。pgvecto.rs 能够与 citus 良好协作。下面介绍如何在分布式 PostgreSQL 实例中启用向量检索。

citus 支持在单节点(Single-Node)和多节点(Multi-Node)模式下运行,这两种模式均原生兼容 pgvecto.rs。

单节点 Citus

首先,准备一个同时安装了 pgvecto.rs 和 citus 的 Dockerfile,并将它们加载到 PostgreSQL 中。

详情

由于 citus 的自身限制,在 shared_preload_libraries 中必须将 citus 放在第一位。

FROM tensorchord/pgvecto-rs-binary:pg16-v0.3.0-amd64 as binary

FROM postgres:16
COPY --from=binary /pgvecto-rs-binary-release.deb /tmp/vectors.deb
RUN apt-get update && apt-get install -y curl
RUN apt-get install -y /tmp/vectors.deb && rm -f /tmp/vectors.deb
RUN curl https://install.citusdata.com/community/deb.sh -o deb.sh
RUN bash deb.sh 
RUN apt-get install -y postgresql-16-citus-12.1

CMD ["postgres", "-c" ,"shared_preload_libraries=citus,vectors.so", "-c", "search_path=\"$user\", public, vectors"]

接着在本地构建名为 citus-vector:16 的 Docker 镜像并启动容器。

警告

请勿在生产环境中使用 POSTGRES_HOST_AUTH_METHOD: "trust"。详细的认证配置请参阅 citus 文档中关于提高 Worker 安全性(Increasing Worker Security)的说明。

docker build -t citus-vector:16 .
docker run --name citus-single -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432:5432 -d citus-vector:16

要启用所需扩展,请使用 psql 连接到容器并执行以下 SQL 命令:

-- psql -h 127.0.0.1 -d postgres -U postgres -w
CREATE EXTENSION IF NOT EXISTS citus;
CREATE EXTENSION IF NOT EXISTS vectors;

扩展创建完成后,你可以先创建一张普通表,然后将其转换为分布式表(distributed table)。

CREATE TABLE items (id bigserial, embedding vector(3), category_id bigint, PRIMARY KEY (id, category_id));
SET citus.shard_count = 4;
SELECT create_distributed_table('items', 'category_id');

之后的索引构建和向量检索操作与单机版完全一致。表和索引会自动分发到所有分片(Shard)/ 工作节点(Worker)上。

INSERT INTO items (embedding, category_id)
SELECT '[1, 1, 1]'::vector, FLOOR(random() * 100) FROM generate_series(1, 1000)
UNION ALL
SELECT '[1, 2, 3]'::vector, FLOOR(random() * 100) FROM generate_series(1, 1000)
UNION ALL
SELECT '[3, 2, 1]'::vector, FLOOR(random() * 100) FROM generate_series(1, 1000);
CREATE INDEX ON items USING vectors (embedding vector_cos_ops) WITH (options = "[indexing.hnsw]");
SELECT id FROM items ORDER BY embedding <=> '[1, 1, 1]' LIMIT 100;

多节点 Citus

你可以复用与单节点 Citus 相同的 Dockerfile 和镜像来部署多节点 citus。 在本示例中,我们将使用 Docker Compose 启动一个包含 1 个协调器节点(Coordinator)和 2 个工作节点(Worker)的集群。

警告

请勿在生产环境中使用 POSTGRES_HOST_AUTH_METHOD: "trust"。详细的认证配置请参阅 citus 文档中关于提高 Worker 安全性(Increasing Worker Security)的说明。

services:
  main:
    container_name: main
    image: 'citus-vector:16'
    ports:
      - "5432:5432"
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"
  worker1:
    container_name: worker1
    image: 'citus-vector:16'
    ports:
      - "5431:5432"
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"
  worker2:
    container_name: worker2
    image: 'citus-vector:16'
    ports:
      - "5430:5432"
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"

使用 Docker Compose 启动所有容器。每个容器对应一个 citus 节点,包括 1 个协调器节点(main)和 2 个工作节点。

docker compose up -d

与之前一样,需要启用相关扩展,但请确保在所有节点上都执行该命令:

psql -h 127.0.0.1 -d postgres -U postgres -w -c 'CREATE EXTENSION IF NOT EXISTS citus;CREATE EXTENSION IF NOT EXISTS vectors;'
psql -h 127.0.0.1 -d postgres -U postgres -w -p 5431 -c 'CREATE EXTENSION IF NOT EXISTS citus;CREATE EXTENSION IF NOT EXISTS vectors;'
psql -h 127.0.0.1 -d postgres -U postgres -w -p 5430 -c 'CREATE EXTENSION IF NOT EXISTS citus;CREATE EXTENSION IF NOT EXISTS vectors;'

为了让协调器知晓工作节点的信息,请使用 psql 连接到协调器节点,并注册这两个工作节点:

-- psql -h 127.0.0.1 -d postgres -U postgres -w
SELECT citus_set_coordinator_host('main', 5432);
SELECT * from citus_add_node('worker1', 5432);
SELECT * from citus_add_node('worker2', 5432);

最后,你可以按照与单节点 Citus 完全相同的方式执行向量查询:

CREATE TABLE items (id bigserial, embedding vector(3), category_id bigint, PRIMARY KEY (id, category_id));
SET citus.shard_count = 4;
SELECT create_distributed_table('items', 'category_id');
INSERT INTO items (embedding, category_id) 
SELECT '[1, 1, 1]'::vector, FLOOR(random() * 100) FROM generate_series(1, 1000)
UNION ALL
SELECT '[1, 2, 3]'::vector, FLOOR(random() * 100) FROM generate_series(1, 1000)
UNION ALL
SELECT '[3, 2, 1]'::vector, FLOOR(random() * 100) FROM generate_series(1, 1000);
CREATE INDEX ON items USING vectors (embedding vector_cos_ops) WITH (options = "[indexing.hnsw]");
SELECT id FROM items ORDER BY embedding <-> '[1, 1, 1]' LIMIT 100;

监控

如果你使用的是 citus 分布式表,协调器节点上的 pg_vector_index_stat 视图将为空。因为所有索引实际上都创建在工作节点上,所以我们需要将查询分发到各个工作节点来查看向量索引状态:

SELECT * from run_command_on_workers($$ SELECT (to_json(array_agg(pg_vector_index_stat .*))) FROM pg_vector_index_stat $$);

5 - 运维管理

pgvecto.rs 的配置、升级、逻辑复制、FDW、Kubernetes 部署与迁移指南。

5.1 - 配置

默认情况下无法捕获所有 pgvecto.rs 日志。pgvecto.rs 会启动后台工作进程进行索引构建并将日志输出到标准错误,需要开启 logging_collector 来捕获日志。

日志配置

默认情况下,你无法捕获 pgvecto.rs 的全部日志。pgvecto.rs 会启动一个后台工作进程(Background Worker)来构建索引,并将日志输出到标准错误(stderr)。若要捕获这些日志,你需要将 logging_collector 设置为 on。更多详情可参阅 PostgreSQL 关于日志收集器的官方文档。

你可以通过以下命令将 logging_collector 设置为 on:

psql -U postgres -c 'ALTER SYSTEM SET logging_collector = on;'
# 需要重启 PostgreSQL 集群以使配置生效。
sudo systemctl restart postgresql.service   # 适用于通过 systemd 运行的 pgvecto.rs
docker restart pgvecto-rs-demo  # 适用于在 Docker 中运行的 pgvecto.rs

索引配置

自 v0.3.0 起

对于每个索引,都有若干可动态调整的配置项。与创建时的静态选项(options)不同,这些配置项支持在索引创建后进行实时修改。你可以通过 alter_vector_index 函数来修改这些配置项。

-- alter_vector_index(dim: OID, key: TEXT, value: TEXT)
SELECT alter_vector_index('index_name'::regclass::oid, 'configuration_name', 'value');

目前支持的所有配置项如下:

后端优化线程数

默认情况下,每个向量索引在后端仅由一个线程执行优化。如果你有多个空闲 CPU 核心并希望充分利用它们,可以增加优化线程的数量。

将 key 参数设置为 optimizing.threads,并将 value 参数设置为期望的线程数:

-- alter_vector_index(dim: OID, key = 'optimizing.threads', value: TEXT as INT[1-65535])
SELECT alter_vector_index('table_col_idx'::regclass::oid, 'optimizing.threads', '4');

5.2 - 版本升级

如何将 pgvecto.rs 扩展升级到新版本并更新扩展 Schema 与索引。

通用步骤

如果你将扩展更新到了新版本(假设版本号为 a.b.c),可以使用以下命令更新扩展的 Schema:

ALTER EXTENSION vectors UPDATE;
-- 或
ALTER EXTENSION vectors UPDATE TO 'a.b.c';

如果你是从 0.1.x 版本升级,请先阅读从 0.1.x 升级。

如果你要升级到 0.4.0 版本,请阅读升级到 0.4.0。

随后执行以下 SQL,它会协助完成必要的维护工作:

SELECT pgvectors_upgrade();

你需要重启 PostgreSQL 实例以使更改生效。

你可以通过以下命令查看所有向量索引的状态:

SELECT
        I.relname AS indexname
    FROM pg_index X JOIN
         pg_class I ON I.oid = X.indexrelid JOIN
         pg_am A ON A.oid = I.relam
    WHERE A.amname = 'vectors';

假设输出如下:

 indexname
-------------
 t_val_idx_1
 t_val_idx_2

然后你可以重建所有向量索引:

REINDEX INDEX t_val_idx_1;
REINDEX INDEX t_val_idx_2;

升级到 0.4.0

如果在将 pgvecto.rs 升级到 0.4.0 之前已经创建了一些索引,你可能会遇到以下错误:

ERROR:  index public.this_is_index depends on operator class vector_cos_ops for access method vectors
index public.this_is_index depends on operator class vector_cos_ops for access method vectors cannot drop operator class vector_cos_ops for access method vectors because other objects depend on it 

ERROR:  cannot drop operator class vector_cos_ops for access method vectors because other objects depend on it
SQL state: 2BP01
Detail: index public.that_is_also_index depends on operator class vector_cos_ops for access method vectors
index public.that_is_also_index depends on operator class vector_cos_ops for access method vectors
Hint: Use DROP ... CASCADE to drop the dependent objects too.

你只需先删除这些索引,然后再升级扩展并重建索引即可:

DROP INDEX this_is_index;
DROP INDEX that_is_also_index;

ALTER EXTENSION vectors UPDATE;

-- CREATE INDEX this_is_index ...
-- CREATE INDEX that_is_also_index ...

从 0.1.x 升级

你需要按照以下步骤操作,才能使 ALTER EXTENSION vectors UPDATE 正常工作。

假设你当前的 pgvecto.rs 版本为 0.1.x(请将 x 替换为实际的数字):

CREATE SCHEMA IF NOT EXISTS vectors;
UPDATE pg_catalog.pg_extension SET extversion = '0.1.x' where extname = 'vectors';
UPDATE pg_catalog.pg_extension SET extrelocatable = true where extname = 'vectors';
ALTER EXTENSION vectors SET SCHEMA vectors;
UPDATE pg_catalog.pg_extension SET extrelocatable = false where extname = 'vectors';
ALTER EXTENSION vectors UPDATE;
提示

从 0.2.0 版本开始,pgvecto.rs 安装在 vectors Schema 下,你可能需要为数据库配置 search_path。

5.3 - 逻辑复制

逻辑复制是 PostgreSQL 的一项特性,支持将单个数据库对象或数据子集从一个 PostgreSQL 数据库复制到另一个 PostgreSQL 数据库。

逻辑复制(Logical Replication)是 PostgreSQL 的一项特性,支持将单个数据库对象或数据子集从一个 PostgreSQL 数据库复制到另一个数据库。

借助逻辑复制,你可以根据预定义的复制规则,有选择地复制特定的表、数据库甚至特定的行。与复制整个数据库集群的物理复制相比,逻辑复制提供了更高的灵活性,允许你设计自定义的复制拓扑结构,并仅复制业务所需的数据。

下面我们将演示如何使用逻辑复制将向量数据从一个 PostgreSQL 数据库复制到另一个数据库。

部署源端和目标端 PostgreSQL 集群

$ echo 'version: "3.7"
services:
  source_db:
    image: tensorchord/pgvecto-rs:pg15-v0.2.0
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: password
    command: >
      -c wal_level=logical
      -c shared_preload_libraries=vectors.so
    networks:
      localnet:

  target_db:
    image: tensorchord/pgvecto-rs:pg15-v0.2.0
    ports:
      - "5433:5432"
    environment:
      POSTGRES_PASSWORD: password
    command: >
      -c wal_level=logical
      -c shared_preload_libraries=vectors.so
    networks:
      localnet:

networks:
  localnet:
' > docker-compose.yml

$ docker compose up -d

在源数据库和目标数据库中分别创建 vectors 扩展:

# create extension vectors
$ psql -h <source_db_ip> -U postgres -p 5432 
DROP EXTENSION IF EXISTS "vectors";
CREATE EXTENSION "vectors";

$ psql -h <target_db_ip> -U postgres -p 5432
DROP EXTENSION IF EXISTS "vectors";
CREATE EXTENSION "vectors";

配置逻辑复制

现在我们可以在源数据库和目标数据库之间配置逻辑复制。

准备测试数据

我们需要在源数据库和目标数据库中分别创建一张名为 test 的表,其中包含一个类型为 vector(10) 的 embedding 列。然后在源数据库和目标数据库的 test 表的 embedding 列上分别创建索引。最后,向源数据库中插入数据。

创建表

DROP TABLE IF EXISTS test;
CREATE TABLE test (id integer PRIMARY KEY, embedding vector(10) NOT NULL);

创建索引

我们在源数据库和目标数据库的 test 表的 embedding 列上创建索引。这里使用 flat(暴力搜索)索引类型,并选择 vector_l2_ops(欧氏距离平方)来度量向量之间的距离。其他索引类型和距离函数请参阅索引指南。

CREATE INDEX ON test USING vectors (embedding vector_l2_ops) WITH (options = "[indexing.flat]");

插入数据

我们只需要向源数据库插入数据,数据随后会同步到目标数据库。

INSERT INTO test
SELECT i, ARRAY[random(), random(),random(),random(),random(),random(),random(),random(),random()]::real[] 
FROM generate_series(1, 100) i;

现在你可以在源数据库中搜索 test 表 embedding 列的最近邻向量:

postgres=# SELECT id FROM test ORDER BY embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
 id 
----
 82
 69
 80
 22
 31
 20
 85
 97
 72
(10 rows)

此时如果查询目标数据库,结果将为空:

SELECT id FROM test ORDER BY embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
 id 
----
(0 rows)

设置逻辑复制

为了简化在两个 PostgreSQL 数据库之间配置逻辑复制的流程,我们将使用 pg-easy-replicate。

我们使用 pg-easy-replicate 在源数据库和目标数据库之间建立逻辑复制:

# get the network name
$ docker network ls | grep  pg
8631dd77129b   pg_regress_localnet   bridge    local

# get source and target container ip
$ docker inspect <container_id> | grep IP

$ docker run -e SOURCE_DB_URL="postgres://postgres:password@<source_db_ip>:5432/postgres"    -e TARGET_DB_URL="postgres://postgres:password@<target_db_ip>:5432/postgres"   -it --rm --network=<network> shayonj/pg_easy_replicate:latest   pg_easy_replicate config_check
/usr/bin/pg_dump
✅ Config is looking good.

初始化引导(Bootstrap)

在正式设置两个数据库之间的同步之前,每次同步都需要先执行初始化引导(Bootstrap)。Bootstrap 会创建一个新的超级用户来执行后续流程所需的编排操作,同时还会创建一些内部元数据表用于记录状态。

$ docker run -e SOURCE_DB_URL="postgres://postgres:password@<source_db_ip>:5432/postgres"    -e TARGET_DB_URL="postgres://postgres:password@<target_db_ip>:5432/postgres"   -it --rm --network=pg_regress_localnet shayonj/pg_easy_replicate:latest pg_easy_replicate bootstrap --group-name database-cluster-1 --copy-schema

启动同步

Bootstrap 完成后,即可启动同步。启动同步会自动配置发布(Publication)、订阅(Subscription)并完成其他清理维护工作。

$ docker run -e SOURCE_DB_URL="postgres://postgres:[email protected]:5432/postgres"    -e TARGET_DB_URL="postgres://postgres:[email protected]:5432/postgres"   -it --rm --network=pg_regress_localnet shayonj/pg_easy_replicate:latest pg_easy_replicate start_sync --group-name database-cluster-1 

测试逻辑复制

查询验证

现在我们可以查询目标数据库,获取 test 表 embedding 列中向量的最近邻。查询结果与源数据库完全一致:

postgres=# SELECT id FROM test ORDER BY embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
 id 
----
 82
 69
 80
 22
 31
 20
 85
 97
 72
(10 rows)

更新索引

如果你向源数据库继续插入新数据,数据也会实时同步到目标数据库。向源数据库插入新数据:

INSERT INTO test
SELECT i, ARRAY[random(), random(),random(),random(),random(),random(),random(),random(),random()]::real[] 
FROM generate_series(101, 200) i;

在源数据库中查询:

postgres=# SELECT id FROM test ORDER BY embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
id  
-----
  82
 130
 145
  69
  80
 200
  22
  31
 182
 (10 rows)

在目标数据库中查询,将返回相同的结果:

postgres=# SELECT id FROM test ORDER BY embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
id  
-----
  82
 130
 145
  69
  80
 200
  22
  31
 182
 (10 rows)

5.4 - 外部数据包装器 (FDW)

外部数据包装器(FDW)是 PostgreSQL 的一项扩展,允许你像查询本地表一样访问和查询存储在远程数据库中的数据。

外部数据包装器(Foreign Data Wrapper,简称 FDW)是 PostgreSQL 中的一项扩展,允许你像操作本地表一样访问和查询存储在远程数据库中的数据。FDW 提供了一种与不同数据源集成和交互的标准方式。

借助 FDW 扩展,你可以在本地 PostgreSQL 数据库中定义外部表(Foreign Table),用于映射远程数据库中的表结构。这些外部表充当远程数据的代理或包装器,使你能够像操作普通 PostgreSQL 表一样对它们执行 SELECT、INSERT、UPDATE 和 DELETE 操作。

在本教程中,我们将使用内置外部数据包装器的 postgres_fdw 模块。该包装器可用于访问存储在外部 PostgreSQL 服务器中的数据。本文将介绍如何使用 postgres_fdw 访问已安装 pgvecto.rs 扩展的远程 PostgreSQL 集群中的向量索引数据。

部署本地和远程 PostgreSQL 集群

在本教程中,我们将使用 Docker Compose 部署两个 PostgreSQL 集群。

$ echo 'version: "3.7"
services:
  local_db:
    image: tensorchord/pgvecto-rs:pg15-v0.2.0
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: password
    command: >
      -c shared_preload_libraries=vectors.so
    networks:
      localnet:

  foreign_db:
    image: tensorchord/pgvecto-rs:pg15-v0.2.0
    ports:
      - "5433:5432"
    environment:
      POSTGRES_PASSWORD: password
    command: >
      -c shared_preload_libraries=vectors.so
    networks:
      localnet:

networks:
  localnet:
' > docker-compose.yml

$ docker compose up -d
# create extension postgres_fdw in local db
$ psql -h <local_db_ip> -U postgres -p 5432 
DROP EXTENSION IF EXISTS postgres_fdw;
CREATE EXTENSION postgres_fdw;

# create extension vectors in foreign db
$ psql -h <foreign_db_ip> -U postgres -p 5432
DROP EXTENSION IF EXISTS "vectors";
CREATE EXTENSION "vectors";

远程数据库操作

首先,我们需要在远程数据库(foreign_db)中创建一张 test 表并在其上构建向量索引。test 表包含两列:id 和 embedding。其中 embedding 列是类型为 vector(10) 的向量列,id 列是 test 表的主键。

创建表

DROP TABLE IF EXISTS test;
CREATE TABLE test (id integer PRIMARY KEY, embedding vector(10) NOT NULL);
INSERT INTO test SELECT i, ARRAY[random(),random(),random(),random(),random(),random(),random(),random(),random(),random()]::real[] FROM generate_series(1, 100) i;

创建用户

在远程数据库中创建一个名为 fdw_user 的用户,并将 test 表上的 SELECT、INSERT、UPDATE、DELETE 权限授予 fdw_user:

CREATE USER fdw_user WITH ENCRYPTED PASSWORD 'secret';
GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE test TO fdw_user;

创建索引

在 test 表的 embedding 列上创建向量索引。这里使用 flat(暴力搜索)索引类型,并选择 vector_l2_ops(欧氏距离平方)来度量向量距离。其他索引类型和距离函数请参阅索引指南。

CREATE INDEX ON test USING vectors (embedding vector_l2_ops) WITH (options = "[indexing.flat]");

本地数据库操作

在本地数据库中,我们需要创建一张本地表 local 和一个外部服务器 foreign_server。local 表包含两列:id 和 name,其中 id 是主键。foreign_server 用于连接访问远程数据库。

创建本地表

DROP TABLE IF EXISTS local;
CREATE TABLE local (id integer PRIMARY KEY, name VARCHAR(50) NOT NULL);
INSERT INTO local (id, name) VALUES (1, 'terry'), (2, 'jason'), (3, 'curry');

创建本地用户

使用超级用户在本地 PostgreSQL 数据库中执行以下语句,创建一个名为 local_user 的普通用户:

CREATE USER local_user WITH ENCRYPTED PASSWORD 'secret';

创建外部服务器

使用 CREATE SERVER 语句创建外部服务器时,需要指定远程数据库的主机地址、端口和数据库名称:

CREATE SERVER foreign_server
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '<foreign_db_ip>', port '5432', dbname 'postgres');

创建用户映射

使用 CREATE USER MAPPING 语句创建本地用户与远程用户之间的映射关系,需提供远程用户的用户名和密码:

CREATE USER MAPPING FOR local_user
    SERVER foreign_server
    OPTIONS (user 'fdw_user', password 'secret');

创建外部表

使用 CREATE FOREIGN TABLE 语句创建远程表的映射表。需要注意,每一列的数据类型必须与远程表的实际类型匹配,并且建议保持列名一致;否则,你需要为每个列单独使用 column_name 参数指定远程表中的列名。

CREATE FOREIGN TABLE foreign_test (id integer, embedding vector(10) NOT NULL)
    SERVER foreign_server
    OPTIONS (schema_name 'public', table_name 'test');

权限授予

为 local_user 授予所需权限:

  • 授予 local_user 使用 postgres_fdw 的权限。
  • 授予 local_user 使用外部服务器 foreign_server 的权限。
  • 授予 local_user 访问 public Schema 下所有表的权限。
GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO local_user;
GRANT USAGE ON FOREIGN SERVER foreign_server TO local_user;
GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA public TO local_user;

查询测试

现在我们可以在本地数据库中通过连接查询(JOIN)访问外部表:

$ psql -h <local_db_ip> -U local_user -p 5432 -d postgres
SELECT l.id, l.name FROM local l LEFT JOIN foreign_test f on l.id = f.id ORDER BY f.embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
 id | name  
----+-------
  2 | jason
  3 | curry
  1 | terry


EXPLAIN SELECT l.id, l.name FROM local l LEFT JOIN foreign_test f on l.id = f.id ORDER BY f.embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]' LIMIT 10;
 Limit  (cost=209.63..209.65 rows=10 width=126)
   ->  Sort  (cost=209.63..213.04 rows=1365 width=126)
         Sort Key: ((f.embedding <-> '[0.40671515, 0.24202824, 0.37059402, 0.50316447, 0.10779921, 0.80774295, 0.8879849, 0.31292745, 0.05584943, 0.8738258]'::vector))
         ->  Hash Right Join  (cost=122.15..180.13 rows=1365 width=126)
               Hash Cond: (f.id = l.id)
               ->  Foreign Scan on foreign_test f  (cost=100.00..150.95 rows=1365 width=36)
               ->  Hash  (cost=15.40..15.40 rows=540 width=122)
                     ->  Seq Scan on local l  (cost=0.00..15.40 rows=540 width=122)

5.5 - Kubernetes

如果你想在运行于 Kubernetes 上的 PostgreSQL 中使用 pgvecto.rs,本教程将演示如何使用 CloudNative-PG 在 Kubernetes 中部署集群。

如果你想在运行于 Kubernetes 上的 PostgreSQL 中使用 pgvecto.rs,可以参考本教程。在本教程中,我们将使用 CloudNative-PG 在 Kubernetes 中部署 PostgreSQL 集群。

环境要求

组件名称 版本要求
helm >= 2.4.1
kubectl >= 1.14
Kubernetes >= 1.24.0

安装 CloudNative-PG

我们使用 CloudNative-PG (cnpg) 在 Kubernetes 中部署 PostgreSQL 集群。你可以通过 Helm 安装它:

$ sudo helm repo add cnpg https://cloudnative-pg.github.io/charts
$ sudo helm upgrade --install cnpg   --namespace cnpg-system   --create-namespace   cnpg/cloudnative-pg
$ helm list --all-namespaces
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                         APP VERSION
cnpg                    cnpg-system     1               2024-01-02 08:17:11.661297545 +0000 UTC deployed        cloudnative-pg-0.20.0         1.22.0     
$ kubectl get pod -n cnpg-system   
NAME                                   READY   STATUS    RESTARTS   AGE
cnpg-cloudnative-pg-67ff4f5f66-94m8d   1/1     Running   0          1m

构建包含 pgvecto.rs 的 PostgreSQL 镜像

我们以官方的 CloudNative-PG Docker 镜像作为基础镜像,并在其中安装 pgvecto.rs。你可以在 Releases 页面查看可用的 pgvecto.rs 版本。

# syntax=docker/dockerfile-upstream:master
ARG CNPG_TAG

FROM ghcr.io/cloudnative-pg/postgresql:$CNPG_TAG

ARG CNPG_TAG
ARG PGVECTORS_TAG
ARG TARGETARCH

# drop to root to install packages
USER root

# install pgvecto.rs
ADD https://github.com/tensorchord/pgvecto.rs/releases/download/$PGVECTORS_TAG/vectors-pg${CNPG_TAG%.*}_${PGVECTORS_TAG#"v"}_$TARGETARCH.deb ./pgvectors.deb
RUN apt install ./pgvectors.deb

USER postgres
$ docker build -t <your-postgresql-image-repo>:15 --build-arg CNPG_TAG=15 --build-arg PGVECTORS_TAG=v0.1.13   --build-arg TARGETARCH=amd64 .

注意:PostgreSQL Docker 镜像的 Tag 必须以 CNPG_TAG(PostgreSQL 主版本号)开头,以便 CloudNative-PG 识别 Postgres 版本。

创建 PostgreSQL 集群

下面提供一个用于在 Kubernetes 中创建 PostgreSQL 集群的示例 YAML 文件,你可以根据实际需求进行修改。请注意以下两点:

  • 配置 shared_preload_libraries 以加载 pgvecto.rs 共享库(也可以在 postInitApplicationSQL 中配置 ALTER SYSTEM SET shared_preload_libraries = "vectors.so"; 来加载共享库)。
  • 执行 CREATE EXTENSION IF NOT EXISTS "vectors"; 以创建 vectors 扩展。
apiVersion: v1
kind: Secret
metadata:
  name: tensorchord
type: kubernetes.io/basic-auth
data:
  password: dGVuc29yY2hvcmQ= # tensorchord 
  username: dGVuc29yY2hvcmQ= # tensorchord
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pgvectors 
spec:
  instances: 1 
  bootstrap:
    initdb:
      database: tensorchord
      postInitApplicationSQL:
        - CREATE EXTENSION IF NOT EXISTS "vectors";
      owner: tensorchord
      secret:
        name: tensorchord
      dataChecksums: true
      encoding: 'UTF8'
  storage:
    size: 1Gi
  imageName: <your-postgresql-image-repo>:<pg-tag>
  postgresql:
    shared_preload_libraries: # load pgvecto.rs shared library
    - "vectors.so"

你可以安装 cnpg 的 kubectl 插件来管理 PostgreSQL 集群。现在我们可以查看集群状态:

$ sudo kubectl get pod
pgvectors-1                                                1/1     Running   0             3m54s

$ kubectl cnpg status pgvectors
Cluster Summary
Name:                pgvectors
Namespace:           default
System ID:           7323107530735296538
PostgreSQL Image:    docker.io/xieydd/pgvectors:15
Primary instance:    pgvectors-1
Primary start time:  2024-01-12 07:17:34 +0000 UTC (uptime 17m33s)
Status:              Cluster in healthy state 
Instances:           1
Ready instances:     1
Current Write LSN:   0/4000000 (Timeline: 1 - WAL File: 000000010000000000000003)

Certificates Status
Certificate Name       Expiration Date                Days Left Until Expiration
----------------       ---------------                --------------------------
pgvectors-ca           2024-04-11 07:12:00 +0000 UTC  89.98
pgvectors-replication  2024-04-11 07:12:00 +0000 UTC  89.98
pgvectors-server       2024-04-11 07:12:00 +0000 UTC  89.98

Continuous Backup status
Not configured

Physical backups
No running physical backups found

Streaming Replication status
Not configured

Unmanaged Replication Slot Status
No unmanaged replication slots found

Managed roles status
No roles managed

Tablespaces status
No managed tablespaces

Instances status
Name         Database Size  Current LSN  Replication role  Status  QoS         Manager Version  Node
----         -------------  -----------  ----------------  ------  ---         ---------------  ----
pgvectors-1  29 MB          0/4000000    Primary           OK      BestEffort  1.22.0           kind-control-plane

注意:本文仅提供最小化配置示例,不包含逻辑复制、流式复制、备份与恢复等高级配置。更多 Cluster 配置项请参阅 CloudNative-PG 官方文档。

连接 PostgreSQL 集群

你可以使用 kubectl port-forward 转发端口并连接到 PostgreSQL 集群:

$ kubectl port-forward services/pgvectors-rw 5432:5432
$ psql -h 127.0.0.1 -d tensorchord -U tensorchord
tensorchord=> \dx
Name   | Version |   Schema   |                                         Description                                          
---------+---------+------------+----------------------------------------------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vectors | 0.1.13  | public     | vectors: Vector database plugin for Postgres, written in Rust, specifically designed for LLM

至此,vectors 扩展已安装成功。你可以跳过安装步骤,直接体验快速开始。

5.6 - 迁移指南

从 pgvector 迁移到 PGVecto.rs 的完整步骤与注意事项。

PGVecto.rs 迁移概览

从 pgvector 迁移

前置条件

从 pgvector 迁移到 PGVecto.rs 时,你通常已经有一张包含 vector 类型列的表。例如:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

并且表中已经插入了一些数据行:

INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');

在大多数情况下,表上还会有一个或多个向量索引:

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);

这是使用 pgvector 时的典型场景。如果满足以下条件,即可开始迁移:

  • 请确认你的 pgvector 扩展并未安装在 vectors Schema 中。这意味着你可以在同一个数据库中同时安装 pgvector 和 PGVecto.rs。

迁移步骤

1. 安装 PGVecto.rs

请按照安装指南安装 PGVecto.rs,并确保 PostgreSQL 已加载该扩展。

CREATE EXTENSION vectors;

此时,pgvector 和 PGVecto.rs 两个扩展已同时安装在 PostgreSQL 中。

要验证安装结果,请在 psql 中运行 \dx。你会看到代表 pgvector 的 vector 扩展,以及代表 PGVecto.rs 的 vectors 扩展。

postgres=# \dx
                                                 List of installed extensions
  Name   | Version |   Schema   |                                         Description                                          
---------+---------+------------+----------------------------------------------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vector  | 0.7.2   | public     | vector data type and ivfflat and hnsw access methods
 vectors | 0.3.0   | vectors    | vectors: Vector database plugin for Postgres, written in Rust, specifically designed for LLM

请注意,这两个扩展中存在一些同名的类型和符号,这可能会在迁移过程中引起混淆。为了区分二者,请先确认当前的 search_path 中不包含 PGVecto.rs 的 vectors Schema:

SHOW search_path;
-- 如果显示为:`"$user", public` -> 正常
-- 如果显示为:`"$user", public, vectors` -> 请通过以下命令移除 `vectors`:
SET search_path = "$user, public";

2. 检查现有索引

对于在向量列上创建的索引,请先查询其定义并记录下来,以便在后续阶段重建索引。

postgres=# SELECT indexdef FROM pg_indexes WHERE tablename = 'items';
                                       indexdef                                        
---------------------------------------------------------------------------------------
 CREATE UNIQUE INDEX items_pkey ON public.items USING btree (id)
 CREATE INDEX items_embedding_idx ON public.items USING hnsw (embedding vector_l2_ops)
(2 rows)

3. 迁移向量列

要迁移 pgvector 的向量列,第一步是创建一个对应类型的 PGVecto.rs 向量列,第二步是将原列中的向量数据复制到新列中。

以下是 pgvector 各向量类型的转换路径:

  • vector(pgvector) -> real[] -> vector(PGVecto.rs)
  • halfvec -> vector(pgvector) -> real[] -> vector(PGVecto.rs) -> vecf16
  • bit -> text[] -> real[] -> vector(PGVecto.rs) -> bvector

目前暂不支持迁移以下向量类型:

  • sparsevec

方法一:先验证再替换

使用此方法,你可以在迁移过程中检查向量数据的正确性,而且无需担心原向量列上已创建的索引(在删除原列时索引会自动删除)。 但这种方法需要额外的存储空间来同时保存迁移前后的向量数据。

警告

此步骤会导致一定的停机时间,原因包括:

  • ALTER TABLE ADD COLUMN 会获取短暂的 ACCESS EXCLUSIVE 锁
  • UPDATE 会持有较长时间的 ROW EXCLUSIVE 锁
  • ALTER TABLE DROP COLUMN 会获取短暂的 ACCESS EXCLUSIVE 锁
-- 从 vector 类型转换
ALTER TABLE items ADD COLUMN migrate_embedding vectors.vector(3);
UPDATE items SET migrate_embedding = embedding::real[]::vectors.vector;

-- 从 halfvec 类型转换
ALTER TABLE items ADD COLUMN migrate_embedding vectors.vecf16(3);
UPDATE items SET migrate_embedding = embedding::vector::real[]::vectors.vector::vectors.vecf16;

-- 从 bit 类型转换
ALTER TABLE items ADD COLUMN migrate_embedding vectors.bvector(3);
UPDATE items SET migrate_embedding = string_to_array(embedding::text, NULL)::real[]::vectors.vector::vectors.bvector;

数据复制完成后,你可以通过 SELECT 检查数据,然后用新列替换旧列:

ALTER TABLE items DROP COLUMN embedding;
ALTER TABLE items RENAME COLUMN migrate_embedding TO embedding;

方法二:原地转换(无需额外存储空间)

使用此方法,可以在一条命令内完成列类型转换,无需占用额外的列存储空间。 但你需要提前手动删除该向量列上的索引。

警告

此步骤会导致一定的停机时间,原因包括:

  • ALTER TABLE ALTER COLUMN 会持有较长时间的 ACCESS EXCLUSIVE 锁
-- 索引名称来自第 2 步的查询结果
DROP INDEX IF EXISTS items_embedding_idx;

-- 从 vector 类型转换
ALTER TABLE items ALTER COLUMN embedding TYPE vectors.vector(3) USING embedding::real[]::vectors.vector;

-- 从 halfvec 类型转换
ALTER TABLE items ALTER COLUMN embedding TYPE vectors.vecf16(3) USING embedding::vector::real[]::vectors.vector::vectors.vecf16;

-- 从 bit 类型转换
ALTER TABLE items ALTER COLUMN embedding TYPE vectors.bvector(3) USING string_to_array(embedding::text, NULL)::real[]::vectors.vector::vectors.bvector;

4. 重建索引

要为 PGVecto.rs 的 vector 列重建索引,可以开启兼容模式并直接执行之前的索引创建语句:

SET vectors.pgvector_compatibility=on;
-- 执行第 2 步中记录的命令
CREATE INDEX items_embedding_idx ON public.items USING hnsw (embedding vector_l2_ops);

对于其他向量类型(如 halfvec、sparsevec 和 bit),请参考索引指南重新创建索引。目前 pgvector_compatibility 兼容模式暂不支持这些类型。

5. 清理环境

如果不再需要 pgvector 扩展,现在可以安全地将其卸载:

DROP extension vector;

卸载 pgvector 扩展后,建议重新设置 search_path,以确保可以在全局直接访问 PGVecto.rs 中的符号:

SET search_path = "$user, public, vectors";
-- 或在全局级别设置
ALTER SYSTEM SET search_path = "$user, public, vectors";

验证结果

迁移任务执行完毕后,你可以通过 SELECT 语句验证新列的数据与类型:

SELECT embedding, pg_typeof("embedding") from items limit 3;

-- embedding |   pg_typeof    
-------------+----------------
-- [1, 2, 3] | vectors.vector
-- [4, 5, 6] | vectors.vector

6 - 常见问题

常见问题解答、与 pgvector 及专用向量数据库的对比分析,以及性能基准测试。

6.1 - 常见问答

在大多数情况下,找不到向量类型或操作符类是由于缺少 Schema 搜索路径(search_path)或权限不足导致的。
警告

我们强烈建议你了解并选用我们全新的 VectorChord 实现,它提供了更高的稳定性和性能。建议现有用户迁移至 VectorChord,具体步骤请参阅迁移指南。

找不到向量类型或操作符类

在大多数情况下,这是由于缺少 Schema 搜索路径(search_path)或权限不足导致的。

请按照以下检查清单逐一排查:

  1. 在 psql 中运行 \dx 或执行 SELECT * FROM pg_extension;,检查扩展是否已安装:
postgres=# \dx
                                                    List of installed extensions
  Name   |    Version    |   Schema   |                                         Description                                          
---------+---------------+------------+----------------------------------------------------------------------------------------------
 vectors | 0.3.0         | vectors    | vectors: Vector database plugin for Postgres, written in Rust, specifically designed for LLM

如果未安装,请执行 CREATE EXTENSION vectors; 进行安装。

  1. 运行 SHOW search_path;,检查其中是否包含 vectors:
postgres=# SHOW search_path;
       search_path        
--------------------------
 "$user", public, vectors

如果不包含,请执行 SET search_path="$user", public, vectors; 进行设置。

  1. 如果 pgvecto.rs 是由其他超级用户安装的:

请在 psql 中运行 \dn 或执行 SELECT nspname FROM pg_catalog.pg_namespace;,检查 vectors Schema 是否存在且当前用户是否有权访问:

postgres=# SELECT current_user;
 current_user 
--------------
 tensorchord

postgres=# \dn
       List of schemas
  Name   |       Owner       
---------+-------------------
 public  | pg_database_owner
 vectors | postgres

如果当前用户无权访问,请以超级用户身份登录并将 Schema 权限授予当前用户(current_user):

GRANT ALL ON SCHEMA vectors to tensorchord;

6.2 - 与 pgvector 对比

原文:2024 年 pgvector 与 pgvecto.rs 全面对比:PostgreSQL 向量检索选型指南。

原文:pgvector vs. pgvecto.rs in 2024: A Comprehensive Comparison for Vector Search in PostgreSQL

太长不看(TL; DR):请直接跳转到总结部分,查看展示两者核心差异的对比表格。

检索是数据库的核心能力,对于实现高效的数据获取、帮助用户快速查找所需信息至关重要。下面我们将展示 pgvecto.rs 与 pgvector 的性能表现,并重点对比二者提供的各项功能特性。

过滤检索

虽然 pgvector 和 pgvecto.rs 都支持在 PostgreSQL 中进行向量相似度检索,但 pgvecto.rs 更进一步,引入了来自 OSDI 2023 的 VBASE 方法。

真实业务负载通常涉及向量检索与关系型查询的结合。向量检索擅长相似度匹配与相似向量召回,而关系型查询则支持对结构化数据执行复杂的连接(JOIN)、过滤(Filter)和聚合(Aggregation)。

pgvecto.rs 专为高效处理**单向量 TopK + 过滤 + 连接(Single-Vector TopK + Filter + Join)**场景而设计。

由于目前缺乏通用的关系型向量基准测试集,我们使用 laion-768-5m-ip-probability 数据集进行基准测试。该数据集衍生自 LAION 2B 图像数据集,包含 5,000,000 条向量和 10,000 条查询。

该数据集包含一个 probability 列,用于存储从 0 到 1 均匀分布生成的随机浮点数。选择率(ratio)为 0.01 意味着每次查询过滤后覆盖数据集的 1%(即 0.01 倍),便于开展针对性的选择率分析。

我们在保持 ef_search 不变的前提下,展示了不同 probability 范围下的召回率(Recall)、延迟(毫秒)和每秒请求数(RPS)。ef_search 参数表示 k-NN(k 近邻)搜索期间使用的候选列表大小,决定了搜索精度与查询处理耗时之间的权衡。

当结合 VBASE 使用时,pgvecto.rs 能够始终保持更高的召回率,尤其是在低选择率(高过滤比)场景下优势极为显著。

稀疏向量是仅包含少量非零元素的高维向量,非常适用于传统信息检索(IR)场景。例如,一个拥有 32,000 个维度但仅有 2 个非零元素的向量就是典型的稀疏向量:

S[1×32000]=[000.0150000⋯0.5430] S[1\times 32000]=\left[ \begin{matrix} 0 & 0 & 0.015 & 0 & 0 & 0 & 0 & \cdots & 0.543 & 0 \end{matrix} \right]

稠密向量则是神经网络生成的嵌入向量(Embeddings),由文本嵌入模型生成,其中绝大多数或全部元素均为非零值。它们的维度通常较低(如 256 或 1536 维),远小于稀疏向量:

D[1×256]=[0.3421.7740.0870.8700.001⋯0.5430.999] D[1\times 256]=\left[ \begin{matrix} 0.342 & 1.774 & 0.087 & 0.870 & 0.001 & \cdots & 0.543 & 0.999 \end{matrix} \right]

pgvecto.rs 同时支持稠密向量检索和稀疏向量检索。它提供了 svector 数据类型,用于构建稀疏向量索引并执行高效检索:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding svector(10) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[0.1,0,0,0,0,0,0,0,0,0]');

CREATE INDEX your_index_name ON items USING vectors (embedding svector_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0,0,0,0,0,0,0,0,0]' LIMIT 1;

向量维度上限

pgvecto.rs 扩展支持最高 65,535 维的向量,而 pgvector 扩展的 vector 索引最多仅支持 2,000 维。这一维度上限的差异使得 pgvecto.rs 能够直接处理 OpenAI 最新高维嵌入模型生成的向量。

单指令多数据流(SIMD)

pgvecto.rs 会在运行时动态检测并利用用户机器上可用的最优 SIMD(单指令多数据流)指令集;而 pgvector 则依赖编译器在编译期生成静态 SIMD 代码,无法在运行时针对宿主机的硬件特性自动选择更快的指令集。更快的向量距离计算使得 pgvecto.rs 的检索速度更具优势。

数据类型

二值向量

bvector 是 pgvecto.rs 中的二值向量类型,表示每个分量仅取两个可能值(通常为 0 和 1)的向量。

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding bvector(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[1,0,1]'), ('[0,1,0]');

CREATE INDEX your_index_name ON items USING vectors (embedding bvector_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[1,0,1]' LIMIT 5;

在配合特定嵌入模型(如 OpenAI 嵌入模型)使用时,二值向量具有显著优势:它能在保持理想召回精度的同时,大幅降低内存占用。

以下是 bvector 类型的性能基准测试,采用 dbpedia-entities-openai3-text-embedding-3-large-3072-1M 数据集,测试环境为 Google Cloud 上的 n2-standard-8(8 vCPU,32 GB 内存)虚拟机。

我们将 100 万条二值向量写入表中并建立索引,随后对每条查询运行 KNN 搜索。为 100 万条二值向量建立索引仅需约 600 MB 内存,而使用 vector 类型为相同数量的向量建立索引则需要约 18 GB 内存。如果结合采用自适应检索(Adaptive Retrieval),bvector 的准确率可超过 95%。

bvector

FP16/INT8

除二值向量外,pgvecto.rs 还支持 FP16(16 位浮点数)数据类型以及 INT8(8 位整数)量化存储。

索引架构

与 pgvector 相比,pgvecto.rs 采用了不同的索引架构设计。它独立于 PostgreSQL 管理索引的存储与内存,而不是像 pgvector 那样依赖 PostgreSQL 的原生页面存储引擎。

这是一种工程架构上的权衡。在开发初期,我们也曾尝试像 pgvector 一样基于 PostgreSQL 的页面存储(Page Storage)来实现分层可导航小世界(HNSW)图索引,但遇到了诸多制约其性能与功能的瓶颈:

  • 并行化难题:PostgreSQL 采用单语句单进程模型,且内部 API 并非线程安全,这给多线程并行带来了巨大挑战。构建向量索引属于计算密集型任务,并行化能显著提升性能;但我们在尝试并行化该过程时,频繁遭遇 Too many shared buffer locked(锁定的共享缓冲区过多)错误。
  • 预写式日志(WAL)写放大:插入一条 2 KB 的向量往往会产生超过 20 KB 的 WAL 日志。这一问题源于 HNSW 算法本身的特性——每插入一个节点都需要修改图中的多条边,而 PostgreSQL 会在 WAL 中逐条记录每一次页面修改,从而导致严重的 WAL 写放大。
  • 锁竞争(Lock Contention):在遍历和更新 HNSW 图时,读写操作都需要对边列表加锁。由于 HNSW 采用分层结构,越高的层级包含的节点越少,导致高层节点在索引并发读写时极易成为锁竞争瓶颈,进而引发延迟升高和并发吞吐量下降。

为此,pgvecto.rs 采用了类似 FreshDiskANN 与日志结构合并树(LSM-Tree)的架构设计。该架构由三个组件构成:写入段(Writing Segment)、增长段(Growing Segment)和封存段(Sealed Segment)。新写入的向量首先进入写入段,随后由后台进程异步转换为不可变的增长段,最后增长段会与封存段进行合并(类似于 LSM-Tree 中的 Compaction 过程)。这种设计带来了诸多优势:

  • 非阻塞插入:索引的后台构建与优化不会阻塞前台的数据插入操作。
  • 批量更新图结构:将对 HNSW 图的修改进行批量聚合处理,显著提升了写入吞吐量。
  • 消除读写锁竞争:由于封存段(Sealed Segment)是不可变的(Immutable),彻底缓解了并发检索时的读写锁竞争问题。

当然,这种独立存储方式也存在不足,其中最主要的一点是索引无法直接获得 PostgreSQL 开箱即用的预写式日志(WAL)支持。这意味着依赖 WAL 的时间点恢复(PITR)和物理复制(Physical Replication)无法直接作用于索引文件(注:这一限制已在后续的继任项目 VectorChord 中通过原生 PostgreSQL 存储引擎架构彻底解决)。

总结

下表汇总了 pgvecto.rs 与 pgvector 之间的主要区别:

特性 pgvecto.rs pgvector
过滤检索(Filtering) 引入 VBASE 方法,专为向量检索结合关系型查询(如单向量 TopK + Filter + Join)深度优化。 应用过滤条件时可能出现召回结果不足的情况(例如原本指定 LIMIT 10,过滤后可能只返回 5 条结果)。
向量维度上限 最高支持 65,535 维。 索引最高支持 2,000 维(vector 类型)。
SIMD 加速 在运行时根据宿主机硬件能力动态派发最优 SIMD 指令集,最大化计算性能。 依赖编译器在编译期生成的静态 SIMD 代码。
数据类型 提供丰富的数据类型支持:稠密向量、稀疏向量、二值向量、FP16(16 位浮点数)以及 INT8 量化。 -
索引存储架构 独立于 PostgreSQL 管理索引的磁盘存储与内存映射。 依赖 PostgreSQL 原生页面存储引擎。
WAL 支持 表数据支持预写式日志(WAL),索引的 WAL 支持尚在开发中。 表数据和索引均支持预写式日志(WAL)。

6.3 - 为什么不使用专用向量数据库?

阅读我们的完整博客文章:我们真的需要专用向量数据库吗?(2023) | vchord.pgsty.com。

欢迎阅读我们的完整博客文章:我们真的需要专用向量数据库吗?(2023) | vchord.pgsty.com。

试想一下:你现有的业务数据已经存储在 Postgres 数据库中,现在你希望引入向量数据库来执行向量相似度检索。如果采用专用向量数据库,你就必须将数据从 Postgres 同步迁移到向量数据库中,并且同时运维两套数据库系统。这显然不是一个好主意。

为什么不直接使用 Postgres 来完成向量相似度检索呢?这正是我们构建 pgvecto.rs 的初衷。使用体验非常自然直观:

-- 为 documents 表更新生成 embedding 向量列
UPDATE documents SET embedding = ai_embedding_vector(content) WHERE length(embedding) = 0;

-- 在 embedding 列上创建向量索引
CREATE INDEX ON documents USING vectors (embedding vector_l2_ops);

-- 查询最相似的嵌入向量
SELECT * FROM documents ORDER BY embedding <-> ai_embedding_vector('hello world') LIMIT 5;

引用自 SingleStore DB 博客:

向量和向量检索本质上只是一种数据类型和一种查询处理方式,而不是构建全新数据处理范式的基石。使用专用向量数据库(SVDB)会导致我们在使用多套专用系统的客户身上反复看到的那些典型问题:数据冗余、过度的数据迁移、分布式组件之间的数据不一致、需要专门技能带来的额外人力成本、额外的商业授权费用、受限的查询语言表达能力与可编程/可扩展性、匮乏的工具链生态集成,以及与真正的数据库管理系统(DBMS)相比糟糕的数据完整性与高可用保障。

6.4 - 基准测试

pgvecto.rs 从诞生之初就专注于性能。我们持续监控 pgvecto.rs 的性能表现,并与其他向量检索库进行对比。

pgvecto.rs 从诞生之初就高度专注于性能。我们持续监控 pgvecto.rs 的性能表现,并与其他向量检索库进行对比测试。以下基准测试结果来自 2024 年 1 月。

测试在 Google Cloud n2-standard-8(8 vCPU,32 GB 内存)实例上进行,使用 laion-768-5m-ip 数据集。

pgvecto.rs 对比 pgvector

在使用 HNSW 索引时,pgvecto.rs 在保持约 97% 略高召回精度的同时,每秒响应数(RPS)可达到 pgvector 的 2.5 倍。当对召回精度要求更高时,这一性能优势还会进一步扩大。

pgvecto.rs_vs_pgvector

启用 vbase 模式后,在不同的过滤条件选择率(filter probability)下,pgvecto.rs 的每秒响应数相比 pgvector 均可提升 2 倍以上。

pgvecto.rs_vs_pgvector_filtervbase

不同量化方法的对比

pgvecto.rs 支持多种量化方法以及不同低精度的索引方式。这些方法有助于在不同数据规模下大幅降低内存占用。下图展示了 pgvecto.rs 在不同量化方法下的性能表现:

pgvecto.rs_quantization

缩短嵌入向量(即 Matryoshka Embedding)

OpenAI 的最新嵌入模型支持缩短嵌入向量(Shortening Embeddings),开发者只需“直接移除序列末尾的部分数值”,即可在更短的维度下依然保留有效的文本语义表征。

这一特性带来了极高的使用灵活性。例如,当使用的向量数据存储仅支持最高 1024 维的嵌入向量时,开发者依然可以使用我们最强大的嵌入模型 text-embedding-3-large,并将 API 参数 dimensions 指定为 1024,从而将嵌入向量从 3072 维缩短为 1024 维,以极小的精度折衷换取更紧凑的向量体积。

以下是针对缩短后嵌入向量的性能基准测试。测试采用 dbpedia-entities-openai3-text-embedding-3-large-3072-1M 数据集。原始嵌入向量为 3072 维,缩短后的嵌入向量分别为 256 维和 1024 维。基准测试同样在 Google Cloud 的 n2-standard-8(8 vCPU,32 GB 内存)虚拟机上进行。

基准测试中的 First Pass(初筛阶段) 表明,仅使用缩短后的嵌入向量时,其召回精度不如完整维度的嵌入向量:256 维 HNSW 索引仅能达到约 65% 的准确率,而 1024 维 HNSW 索引约为 85%。

然而,当结合使用自适应检索(Adaptive Retrieval)时,缩短后嵌入向量的最终准确率能够超过 95%。此外,采用这种方法还能在 RPS(每秒请求数)和查询延迟方面带来显著提升。

二值向量

pgvecto.rs 支持二值向量(Binary Vectors)。bvector 类型专为存储效率与计算性能而优化,采用按位紧凑打包(bit-packed)的方式存储二值向量,并针对二值向量的距离计算进行了专门优化。

以下是 bvector 类型的性能基准测试,使用 dbpedia-entities-openai3-text-embedding-3-large-3072-1M 数据集,测试环境为 Google Cloud 上的 n2-standard-8(8 vCPU,32 GB 内存)虚拟机。

我们将 100 万条二值向量写入表中并建立索引,随后对每条查询向量运行 KNN 搜索。为 100 万条二值向量建立索引仅需约 600 MB 内存,而使用普通 vector 类型为相同数量的向量建立索引则需要约 18 GB 内存。

bvector

可以看到,单纯使用 bvector 的召回精度略低于 vector 类型,但如果结合采用自适应检索(Adaptive Retrieval),其准确率同样能够超过 95%。

7 - 参考手册

pgvecto.rs 的 SQL Schema、索引选项、搜索选项与向量类型参考。

7.1 - 数据库 Schema

pgvecto.rs 提供的数据库数据类型、操作符、函数、类型转换与操作符类一览。

以下是 pgvecto.rs 提供的数据库对象定义(Schema)。

数据类型列表

名称 说明
vector 稠密向量,标量元素类型为 IEEE 754-2008 定义的 binary32(32 位浮点数)
vecf16 半精度向量,标量元素类型为 IEEE 754-2008 定义的 binary16(16 位浮点数)
veci8 整数向量,标量元素类型为 8-bit Integer(8 位有符号整数)
svector 稀疏向量,标量元素类型为 IEEE 754-2008 定义的 binary32(32 位浮点数)
bvector 二值向量,定长位串(bit string)
vector_index_stat 表示向量索引统计信息的复合类型

操作符列表

名称 左操作数类型 右操作数类型 返回类型 说明
+ vector vector vector 逐元素加法运算
+ vecf16 vecf16 vecf16 逐元素加法运算
+ veci8 veci8 veci8 逐元素加法运算
+ svector svector svector 逐元素加法运算
- vector vector vector 逐元素减法运算
- vecf16 vecf16 vecf16 逐元素减法运算
- veci8 veci8 veci8 逐元素减法运算
- svector svector svector 逐元素减法运算
& bvector bvector bvector 逐元素按位与(AND)
| bvector bvector bvector 逐元素按位或(OR)
^ bvector bvector bvector 逐元素按位异或(XOR)
= vector vector boolean 字典序比较
= vecf16 vecf16 boolean 字典序比较
= veci8 veci8 boolean 字典序比较
= svector svector boolean 字典序比较
= bvector bvector boolean 字典序比较
<> vector vector boolean 字典序比较
<> vecf16 vecf16 boolean 字典序比较
<> veci8 veci8 boolean 字典序比较
<> svector svector boolean 字典序比较
<> bvector bvector boolean 字典序比较
< vector vector boolean 字典序比较
< vecf16 vecf16 boolean 字典序比较
< veci8 veci8 boolean 字典序比较
< svector svector boolean 字典序比较
< bvector bvector boolean 字典序比较
> vector vector boolean 字典序比较
> vecf16 vecf16 boolean 字典序比较
> veci8 veci8 boolean 字典序比较
> svector svector boolean 字典序比较
> bvector bvector boolean 字典序比较
<= vector vector boolean 字典序比较
<= vecf16 vecf16 boolean 字典序比较
<= veci8 veci8 boolean 字典序比较
<= svector svector boolean 字典序比较
<= bvector bvector boolean 字典序比较
>= vector vector boolean 字典序比较
>= vecf16 vecf16 boolean 字典序比较
>= veci8 veci8 boolean 字典序比较
>= svector svector boolean 字典序比较
>= bvector bvector boolean 字典序比较
<-> vector vector real 平方欧氏距离
<-> vecf16 vecf16 real 平方欧氏距离
<-> veci8 veci8 real 平方欧氏距离
<-> svector svector real 平方欧氏距离
<-> bvector bvector real 平方欧氏距离
<#> vector vector real 负点积
<#> vecf16 vecf16 real 负点积
<#> veci8 veci8 real 负点积
<#> svector svector real 负点积
<#> bvector bvector real 负点积
<=> vector vector real 余弦距离
<=> vecf16 vecf16 real 余弦距离
<=> veci8 veci8 real 余弦距离
<=> svector svector real 余弦距离
<=> bvector bvector real 余弦距离
<~> bvector bvector real 杰卡德距离(Jaccard 距离)

函数列表

名称 参数类型 返回类型 说明
pgvectors_upgrade void 用于升级 pgvecto.rs 的管理函数。
to_svector dims integer, index integer[], value real[] svector 根据下标数组和值数组构造一个稀疏向量。
binarize vector vector bvector 将向量二值化:所有正数元素设为 1,其余元素设为 0。
text2vec_openai input text, model text vector 调用 OpenAI Embeddings API 的嵌入生成函数。
text2vec_openai_v3 input text vector 调用 OpenAI Embeddings API 的嵌入生成函数,默认模型设为 text-embedding-3-small。

类型转换列表

源类型 目标类型 是否隐式转换?
real[] vector yes
vector real[] yes
vector vecf16 no
vecf16 vector no
vector veci8 no
veci8 vector no
vector svector no
svector vector no
vector bvector no
bvector vector no

访问方法(Access Methods)列表

名称 类型 说明
vectors Index pgvecto.rs 向量索引方法

操作符族(Operator Families)列表

访问方法 操作符族 适用类型
vectors vector_cos_ops vector
vectors vector_dot_ops vector
vectors vector_l2_ops vector
vectors vecf16_cos_ops vecf16
vectors vecf16_dot_ops vecf16
vectors vecf16_l2_ops vecf16
vectors veci8_cos_ops veci8
vectors veci8_dot_ops veci8
vectors veci8_l2_ops veci8
vectors svector_cos_ops svector
vectors svector_dot_ops svector
vectors svector_l2_ops svector
vectors bvector_cos_ops bvector
vectors bvector_dot_ops bvector
vectors bvector_l2_ops bvector
vectors bvector_jaccard_ops bvector

操作符类(Operator Classes)列表

访问方法 输入类型 操作符类 是否默认? 说明
vectors vector vector_l2_ops no 平方欧氏距离
vectors vector vector_dot_ops no 负点积
vectors vector vector_cos_ops no 余弦距离
vectors vecf16 vecf16_l2_ops no 平方欧氏距离
vectors vecf16 vecf16_dot_ops no 负点积
vectors vecf16 vecf16_cos_ops no 余弦距离
vectors veci8 veci8_l2_ops no 平方欧氏距离
vectors veci8 veci8_dot_ops no 负点积
vectors veci8 veci8_cos_ops no 余弦距离
vectors svector svector_l2_ops no 平方欧氏距离
vectors svector svector_dot_ops no 负点积
vectors svector svector_cos_ops no 余弦距离
vectors bvector bvector_l2_ops no 平方欧氏距离
vectors bvector bvector_dot_ops no 负点积
vectors bvector bvector_cos_ops no 余弦距离
vectors bvector bvector_jaccard_ops no 杰卡德距离(Jaccard 距离)

视图列表

名称 说明
pg_vector_index_stat 提供向量索引统计信息的监控视图

7.2 - 索引选项

我们使用 TOML 语法来描述索引配置。以下是配置中各个键的含义说明。

我们使用 TOML 语法来表达索引的配置参数。以下是配置中各个顶层键(表)的含义:

键名 类型 说明
segment table 分段(Segment)相关选项。
optimizing table 后台优化相关选项。
indexing table 用于构建索引的算法配置。

segment 表选项

键名 类型 取值范围 默认值 说明
max_growing_segment_size integer [1, 4_000_000_000] 20_000 未建立索引的增长中分段的最大向量数量。
max_sealed_segment_size integer [1, 4_000_000_000] 1_000_000 已封存(建立索引)分段的最大向量数量。

optimizing 表选项

键名 类型 取值范围 默认值 说明
optimizing_threads integer [1, 65535] CPU 核心数的平方根 后台构建索引的最大线程数。
sealing_secs integer [1, 60] 60 见下方说明。
sealing_size integer [1, 4_000_000_000] 1 见下方说明。

如果一个写入分段(write segment)中的向量数大于 sealing_size,且在连续 sealing_secs 秒内没有接收到新数据,该写入分段将被转为已封存分段(sealed segment)以构建索引。

indexing 表选项

键名 类型 说明
flat table 若设置此表,索引将使用暴力搜索(flat)算法。
ivf table 若设置此表,索引将使用 IVF 算法。
hnsw table 若设置此表,索引将使用 HNSW 算法。

在上述三种索引算法中只能选择其一,默认值为 hnsw。

flat 表选项

键名 类型 说明
quantization table 要使用的量化算法配置。

ivf 表选项

键名 类型 取值范围 默认值 说明
nlist integer [1, 1_000_000] 1000 聚类单元(聚类中心)的数量。
nsample integer [1, 1_000_000] 65536 用于 K-Means 聚类训练的样本数量。
least_iterations integer [1, 1_000_000] 16 K-Means 聚类的最少迭代次数。
iterations integer [1, 1_000_000] 500 K-Means 聚类的最大迭代次数。
quantization table 要使用的量化算法配置。

hnsw 表选项

键名 类型 取值范围 默认值 说明
m integer [4, 128] 12 节点的最大连接度数。
ef_construction integer [10, 2000] 300 构建索引时的搜索范围。
quantization table 要使用的量化算法配置。

quantization 表选项

键名 类型 说明
trivial table 若设置此表,则不使用任何量化。
scalar table 若设置此表,则使用标量量化(Scalar Quantization)。
product table 若设置此表,则使用乘积量化(Product Quantization)。

在上述量化算法中只能选择其一,默认值为 trivial。

product 表选项

键名 类型 取值范围 默认值 说明
sample integer [1, 1_000_000] 65535 用于量化训练的样本数量。
ratio enum "x4", "x8", "x16", "x32", "x64" "x4" 量化的压缩比。

压缩比(ratio)表示存储向量时节省的内存比例。例如,对于标准 32 位浮点向量 vector,"x4" 表示内存占用降至原来的 14\frac{1}{4};如果您是在半精度向量 vecf16 上创建索引,则 "x4" 表示内存占用降至原来的 12\frac{1}{2}。

7.3 - 搜索选项

搜索选项通过 PostgreSQL GUC 参数进行配置。

搜索选项通过 PostgreSQL GUC 参数进行指定。

ivf 选项

以下选项仅对 IVF 算法生效。详情请参阅索引指南。

选项 类型 取值范围 默认值 说明
vectors.ivf_nprobe integer [1, 1_000_000] 10 扫描的列表数量。

hnsw 选项

以下选项仅对 HNSW 算法生效。详情请参阅索引指南。

选项 类型 取值范围 默认值 说明
vectors.hnsw_ef_search integer [1, 65535] 100 HNSW 的搜索范围。

其他选项

与搜索模式及规划器相关的查询选项:

选项 类型 取值范围 默认值 说明
vectors.search_mode enum "basic", "vbase" "vbase" 搜索模式,详情请参阅搜索指南。
vectors.pgvector_compatibility boolean off 启用或禁用 pgvector 兼容模式,详情请参阅 pgvector 兼容性。
vectors.enable_index boolean on 启用或禁用查询规划器对向量索引的使用。

7.4 - 向量类型

在前面的索引和搜索章节中,我们接触了 vector 类型。本节将探讨更多向量类型及其在向量搜索领域的应用。

在前面的索引和搜索章节中,我们已经接触了标准的 vector 类型。本节将探讨更多向量数据类型及其在向量搜索领域的应用:

低精度索引

since v0.3.0

为了提升在 vector 列上的查询速度并降低内存占用,可以考虑使用表达式索引,基于 vecf16、veci8 或 bvector 类型创建低精度索引,而非直接对 vector 类型创建全精度索引:

CREATE TABLE t (val vector(3));
INSERT INTO t (val) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);

-- 在 vector 列上创建 bvector 二值向量索引
-- 注意:在构建索引时,Float32 向量会被截断为 0 或 1 的整数
CREATE INDEX lp_bin ON t USING vectors ((binarize(val)::bvector(3)) bvector_l2_ops);

-- 在 vector 列上创建 vecf16 半精度向量索引
CREATE INDEX lp_f16 ON t USING vectors ((val::vecf16(3)) vecf16_l2_ops);

-- 在 vector 列上创建 veci8 整数向量索引
-- 注意:在构建索引时,Float32 向量会被截断为 -128 到 127 之间的整数
CREATE INDEX lp_i8 ON t USING vectors ((val::veci8(3)) veci8_l2_ops);

缩短嵌入向量(Shortening Embedding)

since v0.3.0

部分嵌入模型(例如 OpenAI 的 text-embedding-3-small 和 text-embedding-3-large)支持缩短嵌入向量,即直接移除向量序列末尾的部分数值,而不会丢失嵌入向量的核心概念表达能力。

在某些场景下,可能需要在嵌入生成并入库之后再动态调整检索维度。pgvecto.rs 提供了通过数组切片语法直接使用缩短后的嵌入向量进行查询和建索引的方法:

CREATE TABLE t (val vector(3));
INSERT INTO t (val) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);
CREATE INDEX ON t USING vectors (val vector_l2_ops);

-- 仅使用前两个维度计算距离
SELECT * from t ORDER BY (val[:2])::vector(2) <-> '[3, 2]'::vector;

自 v0.3.0 版本起,pgvecto.rs 中的所有向量数据类型均支持缩短嵌入向量切片操作。

类型转换

下图展示了不同向量数据类型之间的转换关系,由箭头连接的两个类型可以相互转换(Cast):

其中,ARRAY 是 PostgreSQL 的原生数组类型,其余均为 pgvecto.rs 定义的向量类型。

以下是向量类型转换的几个示例:

-- 合法:字符串 ⇒ vector
SELECT '{1,2,3}'::vector;

-- 合法:vector ⇒ ARRAY
SELECT '{1,2,3}'::vector::real[];

-- 合法:ARRAY ⇒ vector ⇒ bvector
SELECT ARRAY[1.0 ,0 ,0]::real[]::vector::bvector;

-- 报错:无法直接将类型 vecf16 转换为 veci8
SELECT '[3, 2]'::vecf16::veci8;

-- 合法:vecf16 ⇒ vector ⇒ veci8
SELECT '[3, 2]'::vecf16::vector::veci8;

7.4.1 - 半精度向量:`vecf16`

vecf16 以半精度浮点数格式存储,利用 16 位浮点数,与标准 vector 相比仅占用一半的内存和带宽。

vecf16 以半精度数值格式存储,充分利用了 16 位浮点数(FP16)的优势,与标准 32 位浮点数的 vector 相比,仅使用一半的内存和内存带宽。

以下是创建包含 vecf16 列的表并插入数据的示例:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding vecf16(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[0.1, 0.2, 0]'), ('[0, 0.1, 0.2]');

同样可以在 vecf16 类型上创建索引:

CREATE INDEX your_index_name ON items USING vectors (embedding vecf16_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0.2,0.1]' LIMIT 1;

我们支持三种用于计算两个 vecf16 值之间距离的操作符:

  • <->(vecf16_l2_ops):平方欧氏距离,定义为 Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2。
  • <#>(vecf16_dot_ops):负点积,定义为 −Σxiyi- \Sigma x_iy_i。
  • <=>(vecf16_cos_ops):余弦距离,定义为 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}。

7.4.2 - 8 位整数向量:`veci8`

Int8 是用于表示有符号整数的数据类型,使用 8 位内存存储范围在 -128 到 127 之间的整数,显著降低向量存储的内存占用。
since v0.3.0

Int8 是一种用于表示有符号整数的数据类型,代表“8 位整数(integer 8-bit)”,使用 8 比特(1 字节)内存存储一个值,可表示范围为 -128 到 127 的整数。

由于向量中的每个元素仅需 8 比特(1 字节)内存,使用 veci8 可以大幅减少程序的整体内存占用。

以下是创建包含 veci8 列的表并插入数据的示例:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding veci8(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[1, 2, 0]'), ('[-1, 15, -2]');

同样可以在 veci8 类型上创建索引:

CREATE INDEX your_index_name ON items USING vectors (embedding veci8_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0.2,0.1]' LIMIT 1;

我们支持三种用于计算两个 veci8 值之间距离的操作符:

  • <->(veci8_l2_ops):平方欧氏距离,定义为 Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2。
  • <#>(veci8_dot_ops):负点积,定义为 −Σxiyi- \Sigma x_iy_i。
  • <=>(veci8_cos_ops):余弦距离,定义为 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}。

7.4.3 - 稀疏向量:`svector`

与稠密向量不同,稀疏向量具有极高的维度,但仅包含少量非零值。
since v0.3.0

与稠密向量不同,稀疏向量(Sparse Vector)通常具有非常高的维度,但其中只包含极少数的非零值。

通常情况下,稀疏向量可以由以下方式生成:

  • 词共现矩阵(Word co-occurrence matrices)
  • 词频-逆文档频率(TF-IDF)向量或 SPLADE/BGE-M3 词法权重
  • 用户-物品交互矩阵(User-item interaction matrices)
  • 网络邻接矩阵(Network adjacency matrices)

在 pgvecto.rs 中,稀疏向量类型称为 svector。

以下是创建包含 svector 列的表并插入数据的示例:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding svector(10) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[0.1,0,0,0,0,0,0,0,0,0]'), ('[0,0,0,0,0,0,0,0,0,0.5]');

同样可以在 svector 类型上创建索引:

CREATE INDEX your_index_name ON items USING vectors (embedding svector_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0,0,0,0,0,0,0,0,0]' LIMIT 1;

我们支持三种用于计算两个 svector 值之间距离的操作符:

  • <->(svector_l2_ops):平方欧氏距离,定义为 Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2。
  • <#>(svector_dot_ops):负点积,定义为 −Σxiyi- \Sigma x_iy_i。
  • <=>(svector_cos_ops):余弦距离,定义为 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}。

此外,还提供了 to_svector 函数用于构造 svector,它会在指定的下标位置设置对应的非零值:

F(index)={value[v]∃v∈N:position[v]=index0else F(index)=\left\{ \begin{array}{rcl} value[v] & & {\exists v \in \mathbb{N}: position[v] = index }\\ 0 & & {else}\\ \end{array} \right.
-- to_svector(dim: INTEGER, position: ARRAY, value: ARRAY) -> svector
SELECT to_svector(5, '{0, 4}', '{0.3, 0.5}');
-- [0.3, 0, 0, 0, 0.5]

7.4.4 - 二值向量:`bvector`

bvector 是 pgvecto.rs 中的二值向量类型,其每个分量仅取 0 或 1 两个可能值,并针对存储和距离计算进行了深度优化。
since v0.3.0

bvector 类型是 pgvecto.rs 中的二值向量(Binary Vector)类型。在二值向量中,每个分量只能取两个可能的值(通常为 0 和 1)。

以下是创建包含 bvector 列的表并插入数据的示例:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding bvector(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[1,0,1]'), ('[0,1,0]');

同样可以在 bvector 类型上创建索引:

CREATE INDEX your_index_name ON items USING vectors (embedding bvector_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[1,0,1]' LIMIT 5;

我们支持四种用于计算两个 bvector 值之间距离的操作符:

  • <->(bvector_l2_ops):平方欧氏距离,定义为 Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2。对于二值向量,汉明距离(Hamming distance)与平方欧氏距离完全等价。
  • <#>(bvector_dot_ops):负点积,定义为 −Σxiyi- \Sigma x_iy_i。
  • <=>(bvector_cos_ops):余弦距离,定义为 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}。
  • <~>(bvector_jaccard_ops):杰卡德距离(Jaccard distance),定义为 1−∣X∩Y∣∣X∪Y∣1 - \frac{|X\cap Y|}{|X\cup Y|}。

此外,还提供了 binarize 函数用于从 vector 构造 bvector,它会将向量元素二值化为 0 或 1:

F(element)={0element≤01element>0 F(element)=\left\{ \begin{array}{rcl} 0 & & {element \leq 0}\\ 1 & & {element > 0}\\ \end{array} \right.
-- binarize(source: vector) -> bvector
SELECT binarize('[-2, -1, 0, 1, 2]'::vector);
-- [0, 0, 0, 1, 1]

性能表现

bvector 类型针对存储和计算性能进行了深度优化。它采用按位紧缩(bit-packed)的表示方式存储二值向量,并且距离计算也专门针对位运算进行了加速优化。

以下是 bvector 类型的性能基准测试结果。测试使用 dbpedia-entities-openai3-text-embedding-3-large-3072-1M 数据集,运行在 Google Cloud 的 n2-standard-8(8 vCPU,32 GB 内存)虚拟机上。

我们向表中插入了 100 万条二值向量并运行 KNN 查询。为 100 万条 3072 维二值向量建立索引仅需约 600MB 内存,而使用标准 vector 类型对相同数量的向量建立索引则需要约 18GB 内存。

bvector

可以看到,单纯使用 bvector 的准确率虽不及 vector 类型,但如果结合采用自适应检索(Adaptive Retrieval),其准确率即可超过 95%。

8 - 社区

贡献指南、项目路线图与开源生态采用者。

8.1 - 参与贡献 `pgvecto.rs`

我们欢迎来自开源社区、个人开发者及合作伙伴的各种形式的贡献。本文档为您介绍参与贡献的完整流程。

我们欢迎来自开源社区、个人开发者及合作伙伴的各种形式的贡献。如果你有兴趣参与贡献,希望本文档能帮助你清晰了解贡献流程。

无论是资深贡献者还是刚接触开源的新手,以下指南都会非常有帮助:

行为准则

行为准则(Code of Conduct)是一份确立项目参与者行为预期的文档。采用并执行行为准则有助于为社区营造积极健康的交流氛围。

如果你还不了解什么是行为准则,请参阅你的行为准则 | Open Source Guides。

参与方式

我们的社区始终欢迎热心开发者以各种方式参与其中。

⚽ 开始使用 pgvecto.rs

阅读并跟随快速开始指南操作。一切是否都符合预期?如果遇到问题,我们随时欢迎改进建议。你可以在 💬 Discord 上与我们交流,或在 GitHub 上提交 Issue。

🙋 协助整理 Issue 和 Pull Request

即使不编写任何代码,协助整理和分类新提交的 Issue 与 Pull Request 也是为项目做出重要贡献的绝佳方式:

  • 如果你认为某个 Issue 未提供解决问题所需的完整细节,可以礼貌地请求补充更多信息。
  • 建议合适的标签(Label),以帮助对 Issue 进行分类。
  • 标记已经过时(stale)或应当关闭的 Issue。
  • 询问测试计划并协助审查代码(Code Review)。

👨‍💻 为 pgvecto.rs 贡献代码

如果你发现了想要修复的 Issue,欢迎提交 Pull Request。请先阅读开发教程以快速上手。

👉 完善文档

大家都喜欢清晰易读的文档。如果你发现任何令人困惑或可以改进的地方,可以点击大多数文档页面底部的“编辑此页”,直接跳转到 GitHub 界面进行修改并提交更改建议。

💬 在 Discord 上交流 pgvecto.rs

欢迎加入我们的 💬 Discord 与社区一起交流讨论。

8.2 - 路线图

pgvecto.rs 的中期与长期发展规划,包括稀疏向量支持、流式工作负载优化、单机大容量向量存储及物理复制支持。
警告

我们强烈建议你了解并选用我们全新的 VectorChord 实现,它提供了更高的稳定性和性能。建议现有用户迁移至 VectorChord,具体步骤请参阅迁移指南。

中期规划

稀疏向量支持

稀疏向量在 RAG 和文本检索等任务中表现出了优异的性能,并且能够与稠密向量互补以提升检索效果。因此,我们计划支持稀疏向量索引。

更好地支持流式工作负载

作为数据库,支持向量数据的灵活插入和删除至关重要。然而,当前主流的 HNSW 算法对删除操作的支持并不理想,通常需要重建索引。因此,我们计划参考 DiskANN 论文并支持 Vamana 算法。Vamana 算法是一种基于图的增量索引算法,无需重建索引即可良好地支持向量的动态删除与插入。

单机支持更大规模向量

在相同硬件资源下支持更多向量,对于帮助用户降低成本具有重要意义。目前我们已经支持用户以 fp16 格式存储向量,在几乎不损失精度的前提下仅需一半的内存和存储空间。我们还计划支持更多向量格式(如 fp8 和 int8),以进一步降低存储和内存消耗。同时,我们也在积极探索基于 SSD 的向量索引算法(如 DiskANN)。

物理复制与流复制支持

pgvecto.rs 目前是一个独立于 PostgreSQL 的外部存储系统,自行管理索引内存和磁盘存储,因此无法开箱即用地支持物理复制。我们计划实现自定义的 WAL 资源管理器(Resource Manager),以满足物理复制的要求,使用户在 PG 中使用复制更加便捷。

长期规划

在 PostgreSQL 内支持十亿级(1B)向量

我们的终极目标是在 PostgreSQL 中支持 10 亿(1 Billion)规模的向量检索。要实现这一目标仍需克服诸多挑战,可能需要借助表访问方法(Table Access Method)实现自定义的向量存储方案,或者结合 Citus 等分布式分片扩展。目前我们在该领域的探索尚处于早期阶段,非常欢迎感兴趣的开发者与我们探讨交流。

与我们交流

想要参与进来?欢迎随时参与讨论和代码贡献!

以上规划项并非 MVP 的必需功能,而是我们长期路线图的一部分。如果你对其中任何方向感兴趣,欢迎随时加入!

更多规划将在未来持续补充。

8.3 - 采用者案例

如果你在应用中使用了 pgvecto.rs,欢迎编辑此页面。我们非常期待收到你的反馈并协助改进使用体验。

添加你的应用

如果你在自己的应用中使用了 pgvecto.rs,欢迎编辑此页面。我们非常高兴能收到你的反馈,并协助你不断提升 pgvecto.rs 的使用体验。

9 - 开发者指南

面向 pgvecto.rs 贡献者的开发环境搭建、编译构建与调试教程。

9.1 - 开发教程

你可以使用 envd 快速搭建开发环境,它会自动创建 Docker 容器并安装所有依赖项。

搭建开发环境

你可以使用 envd 轻松搭建开发环境,它会自动创建一个 Docker 容器并为你安装所有依赖项:

pip install envd
git clone https://github.com/tensorchord/pgvecto.rs.git
cd pgvecto.rs
envd up

或者,你也可以按照以下步骤手动搭建开发环境:

  1. 安装基础依赖项:
sudo apt install -y \
    bison \
    build-essential \
    ccache \
    flex \
    gcc \
    git \
    gnupg \
    libreadline-dev \
    libssl-dev \
    libxml2-dev \
    libxml2-utils \
    libxslt-dev \
    lsb-release \
    pkg-config \
    tzdata \
    xsltproc \
    zlib1g-dev
  1. 安装 clang-16(不支持其他版本的 clang):
sudo sh -c 'echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-16 main" >> /etc/apt/sources.list'
wget --quiet -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y --no-install-recommends clang-16
  1. 安装 Rust(请勿使用系统包管理器安装 rustc):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. 克隆代码仓库:
git clone https://github.com/tensorchord/pgvecto.rs.git
cd pgvecto.rs
  1. 安装 cargo-pgrx:
cargo install cargo-pgrx --version $(grep -o 'pgrx = { version = "=[^"]*' Cargo.toml | cut -d = -f 4)
cargo pgrx init

cargo-pgrx 是开发 PostgreSQL 扩展的得力工具,你可以通过 https://docs.rs/crate/cargo-pgrx/latest 阅读其文档。

交叉编译

假设你需要在 x86_64 宿主机环境下为 aarch64 目标架构编译二进制文件和 Schema,可以按照以下步骤操作:

  1. 安装交叉编译工具链:
sudo apt install crossbuild-essential-arm64
sudo apt install qemu-user-static

设置环境变量:

LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/aarch64-linux-gnu/lib:/usr/aarch64-linux-gnu/lib64"

在 ~/.cargo/config.toml 文件末尾添加以下配置,为 Rust 配置模拟器、链接器和 sysroot:

[target.aarch64-unknown-linux-gnu]
runner = ["qemu-aarch64-static", "-L", "/usr/aarch64-linux-gnu"]
linker = "aarch64-linux-gnu-gcc"

[env]
BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_gnu = "-isystem /usr/aarch64-linux-gnu/include/ -ccc-gcc-name aarch64-linux-gnu-gcc"
  1. 生成目标架构上的 PostgreSQL 头文件。该步骤可通过 cargo-pgrx 完成,同时你也可以直接利用仓库中 vendor/pgrx_config 和 vendor/pgrx_binding 目录下的文件。详情请参阅 pgrx 的官方文档。

调试

即使在 Release 模式下,编译生成的二进制文件中也包含了调试信息(Debug Info),因此你随时可以使用 gdb 进行调试。

对于 Debug 构建,当后台工作进程(Background Worker)中的线程发生 panic 时会打印调用栈(backtrace),但会话进程(Session Process)报错时默认不打印。对于 Release 构建,默认不打印任何 backtrace。但如果你将环境变量 RUST_BACKTRACE 设置为 1,则会打印所有 backtrace。建议在调试 Release 构建时使用命令 RUST_BACKTRACE=1 cargo pgrx run --release。