跳转到主要内容

迁移指南

VectorChord 的绝大多数向量数据类型均由 pgvector 原生支持,本文介绍如何从 PGVecto.rs 平滑迁移至 VectorChord。

outline: [2,3]

VectorChord 迁移概览

从 PGVecto.rs 迁移

VectorChord 的绝大多数向量类型均由 pgvector 提供支持。 因此,从 PGVecto.rs 迁移到 VectorChord 的过程,基本上就是从 pgvector 迁移到 PGVecto.rs 的逆向操作。

提示

我们提供了专用的迁移镜像 modelzai/pgvectors-vchord,用于支持在 pgvecto.rs 与 VectorChord 之间进行双向迁移。

前置条件

假设您已经拥有包含向量数据的表,并且安装了 PGVecto.rs 扩展。一个简单的示例如下:

CREATE TABLE table_vector (id bigserial PRIMARY KEY, vec_f32 vectors.vector(3));
CREATE TABLE table_float16 (id bigserial PRIMARY KEY, vec_f16 vectors.vecf16(3));
CREATE TABLE table_binary (id bigserial PRIMARY KEY, vec_bits vectors.bvector(3));

此外,表中还插入了一些数据行,如下所示:

INSERT INTO table_vector (vec_f32) VALUES ('[0.1, 0.1, 0.1]'), ('[0.2, 0.2, 0.2]');
INSERT INTO table_float16 (vec_f16) VALUES ('[0.1, 0.1, 0.1]'), ('[-0.2, 0.2, 0.2]');
INSERT INTO table_binary (vec_bits) VALUES ('[0, 0, 0]'), ('[0, 0, 1]');

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

CREATE INDEX index_vector ON table_vector USING vectors (vec_f32 vectors.vector_l2_ops) WITH (options = "[indexing.hnsw]");
-- CREATE INDEX index_float16 ON table_float16 USING vectors (vec_f16 vectors.vecf16_cos_ops) WITH (options = "[indexing.hnsw]");
-- CREATE INDEX index_binary ON table_binary USING vectors (vec_bits vectors.bvector_hamming_ops) WITH (options = "[indexing.hnsw]");

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

  • 请注意,您的 pgvector 和 VectorChord 扩展不能安装在 vectors 模式(schema)下。这意味着您可以在同一个数据库中同时安装 pgvector 和 PGVecto.rs。

迁移步骤

1. 安装 VectorChord

请按照安装说明安装 VectorChord,并确保 PostgreSQL 已加载该扩展。

CREATE EXTENSION vchord CASCADE;
说明

上述命令会将 vectorchord 和 pgvector 安装在 public 模式下。如果您选择了其他模式,请在后续迁移命令中将 public 替换为您实际使用的模式名。

详情

此时,VectorChord 和 PGVecto.rs 这两个扩展已同时安装在 PostgreSQL 中,但位于不同的模式下。 若要验证这一点,请在 psql 中运行 \dx。您将看到已安装以下 3 个扩展:

  • 位于 vectors 模式下的 vectors(即 PGVecto.rs)
  • 位于 public 模式下的 vector(即 pgvector)
  • 位于 public 模式下的 vchord(即 VectorChord)
postgres=# \dx
                                                 List of installed extensions
  Name   | Version |   Schema   |                                         Description                                          
---------+---------+------------+----------------------------------------------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vchord  | 0.1.0   | public     | vchord: Vector database plugin for Postgres, written in Rust, specifically designed for LLM
 vector  | 0.8.0   | public     | vector data type and ivfflat and hnsw access methods
 vectors | 0.4.0   | vectors    | vectors: Vector database plugin for Postgres, written in Rust, specifically designed for LLM

2. 检查现有索引

对于在向量列上创建的索引,请先查询其定义并将其记录在安全的地方。 在后续恢复重建索引的阶段,您将需要用到这些信息。

postgres=# \di
                       List of relations
 Schema |        Name        | Type  |  Owner   |     Table     
--------+--------------------+-------+----------+---------------
 public | index_float16      | index | postgres | table_float16
 public | index_vector       | index | postgres | table_vector
 public | table_binary_pkey  | index | postgres | table_binary
 public | table_float16_pkey | index | postgres | table_float16
 public | table_vector_pkey  | index | postgres | table_vector

3. 迁移向量列

要从 PGVecto.rs 的向量列进行迁移,第一步是在不同扩展之间转换列的数据类型。

以下列出了 VectorChord 各向量类型的转换路径:

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

对于稀疏向量类型 svector,我们需要编写一个额外的辅助函数来完成类型转换,具体请参阅附录。

列类型迁移可通过单条命令完成,且无需消耗额外内存。 但是,您需要预先删除该向量列上的现有索引。

警告

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

  • ALTER TABLE ALTER COLUMN 会持有较长时间的 ACCESS EXCLUSIVE 排他锁
-- 索引名称可在第 2 步中查得
DROP INDEX IF EXISTS index_vector;

-- 转换常规 vector 类型
ALTER TABLE table_vector ALTER COLUMN vec_f32 TYPE public.vector(3) USING vec_f32::real[]::public.vector;

-- 转换半精度 halfvec 类型
ALTER TABLE table_float16 ALTER COLUMN vec_f16 TYPE public.halfvec(3) USING vec_f16::vectors.vector::real[]::public.halfvec;

-- 转换二值 bit 类型
ALTER TABLE table_binary ALTER COLUMN vec_bits TYPE bit(3) USING array_to_string(vec_bits::vectors.vector::real[], '')::bit(3);

4. 重建索引

下一步是在转换后的 vector 列上使用 VectorChord 重新创建索引。

CREATE INDEX ON index_vector USING vchordrq (vec_f32 vector_l2_ops) WITH (options = $$
residual_quantization = true
[build.internal]
lists = [4096]
spherical_centroids = false
$$);

关于在 VectorChord 中创建索引的更多信息,请参阅构建索引。

5. 清理环境

如果不再需要 PGVecto.rs 扩展,现在您可以安全地将其移除:

DROP extension vectors;

附录:稀疏向量的类型转换

上文已经讨论了大多数向量类型,唯独还没有涉及稀疏向量(Sparse Vector)。它的转换比其他类型稍显复杂。假设我们有一张包含稀疏向量列的表:

CREATE TABLE table_sparse (id bigserial PRIMARY KEY, vec_sparse vectors.svector(3));
INSERT INTO table_sparse (vec_sparse) VALUES ('{0:-0.1, 1:0.1, 2:0.1}/3'), ('{0:-0.4, 1:-0.4, 2:-0.4}/3');

稀疏向量在 PGVecto.rs 和 pgvector 之间具有不同的文本表示格式:在 PGVecto.rs 中,维度下标从 0 开始;而在 pgvector 中,维度下标从 1 开始。

例如,一个典型的稀疏向量 [-1, 0, 1, 0, 2] 的文本表示分别为:

  • 在 PGVecto.rs 中:{0:-1, 2:1, 4:2}/5
  • 在 pgvector 中:{1:-1, 3:1, 5:2}/5

因此,我们需要创建一个辅助函数,通过处理字符串将下标整体加 1:

CREATE OR REPLACE FUNCTION migrate_sparse(input_text text) RETURNS text AS $$
DECLARE
    output_text text := '';
    cache_index_number text := '';
    number_started boolean := false;
BEGIN
    FOR i IN 1..length(input_text) LOOP
        IF substring(input_text from i for 1) = ':' THEN
            output_text := output_text || (cache_index_number::int + 1)::text || ':';
            number_started := false;
        ELSIF substring(input_text from i for 1) ~ '[,{]' THEN
            cache_index_number := '';
            number_started := true;
            output_text := output_text || substring(input_text from i for 1);
        ELSIF substring(input_text from i for 1) ~ '[0-9]' THEN
            IF number_started THEN
                cache_index_number := cache_index_number || substring(input_text from i for 1);
            ELSE
                output_text := output_text || substring(input_text from i for 1);
            END IF;
        ELSE
            output_text := output_text || substring(input_text from i for 1);
        END IF;
    END LOOP;
    RETURN output_text;
END;
$$ LANGUAGE plpgsql;

创建该函数后,即可顺利完成稀疏向量列的迁移:

ALTER TABLE table_sparse ALTER COLUMN vec_sparse TYPE public.sparsevec USING migrate_sparse(vec_sparse::text)::public.sparsevec;