pg_vector 是一款对高维度向量提供高效相似度搜索能力的插件,该插件具备以下功能:
create extension vector;
select * from pg_available_extensions where name='vector';
如您的实例版本为 PostgreSQL 11 且使用的插件版本低于 0.5.0,可通过以下命令升级插件版本。
alter extension vector update to '0.5.0';
如您的实例版本为 PostgreSQL 12 或更高版本,可通过以下命令升级插件版本到 0.6.2。
alter extension vector update to '0.6.2';
drop extension vector;
pg_vector 提供了一种向量数据类型——vector,使 PostgreSQL 具备了存储向量数据的能力。
create table tbl_vector (tc1 vector(1), tc2 vector(10));
insert into tbl_vector values ('[1]', '[1,2,3,4,5,6,7,8,9,10]'); select * from tbl_vector;
pg_vector 插件为向量类型实现了 12 种操作符。
注意
操作符 | 说明 | 使用示例 |
---|---|---|
<-> | L2 欧氏距离运算 |
|
<#> | 内积运算 |
|
<=> | 余弦相似度运算 |
|
+ | 加 |
|
- | 减 |
|
< | 小于 |
|
<= | 小于等于 |
|
= | 等于 |
|
<> | 不等于 |
|
>= | 大于等于 |
|
> | 大于 |
|
* | 按位乘 |
|
通常,单表中存储的向量条目(行数)会有上亿之多,为了加速 vector 类型数据的访问和相似度计算,pg_vector 为 vector 类型提供了三种索引类型:btree 索引、ivfflat 索引和 hnsw 索引。
创建 btree 索引
drop table tbl_vector; create table tbl_vector(id serial, tc1 vector(100)); insert into tbl_vector (tc1) select array_agg(random())::vector(100) from generate_series(1.0,100.0) ; create index on tbl_vector (tc1);
说明
创建 ivfflat 索引
drop table tbl_vector ; create table tbl_vector(id serial, tc1 vector(5)); create index tbl_vector_tc1_idx on tbl_vector using ivfflat (tc1) with (lists = 4);
说明
创建 ivfflat 索引时如不指定 opclass ,默认使用 vector_l2_ops
。
ivfflat 索引要求被索引的 vector 列维度必须小于等于 2000。
ivfflat 不支持多列索引。
ivfflat 索引仅仅适用于 order by,不适用于 where 过滤。因为 where 条件只能用于 bool 类型或者 bool 表达式,而 ivfflat 的操作符 (<->、<=>、<#>)的返回值不是 bool 类型。
索引扫描时,召回率取决于 ivfflat.probes 和创建索引时指定的 lists 值。ivfflat.probes 值越高,召回率越高,索引扫描性能越低,ivfflat.probes 越低,召回率越低,索引扫描性能越高。召回率最高的是顺序扫描。
创建 hnsw 索引
drop table tbl_vector; create table tbl_vector(id serial, tc1 vector(5)); create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_l2_ops) with (m = 5, ef_construction = 10);
说明
查询索引当前的构建进度
说明
该功能仅在 pg_vector 0.6.2 及更高版本实例中提供。
vtest=# SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index; phase | % --------------------------------+----- building index: loading tuples | 8.6 (1 row)
ivfflat 索引参数
以下两个参数,分别用于 ivfflat 索引创建和索引扫描:
参数 | 何时使用 | 含义 |
---|---|---|
lists | 创建索引时指定,insert 时使用。 |
|
ivfflat.probes | 查询时指定,索引扫描时使用。 | 最小值为 1,最大值为 32768,默认值为 1,在本次索引扫描过程中,搜索的列表数目。该值越大,搜索扫描搜索的列表数越多,召回率(recall)就会越高,但是索引扫描效率会有所降低;反之,该值越小,索引扫描搜索列表数越少,召回率就会较低,但是索引扫描的效率会有所提升。 如果该值大于创建索引时指定的 lists 值时,查询优化将会忽略索引,选择全表扫描。此时,可能会降低查询性能。 |
hnsw 索引参数
以下三个参数,分别用于 hnsw 索引的创建和扫描:
参数 | 何时使用 | 含义 |
---|---|---|
m | 创建索引时指定,insert 时使用。 | 表示每个点需要与图中其他的点建立的连接数。m 的最小值为 2,最大值为 100,默认值为 16。通常 m 越大召回率越高,同时内存消耗越大;m 值越小,构建时间就越短。 |
ef_construction | 创建索引时指定,insert 时使用。 | 表示构建索引时动态候选集合的大小。ef_construction 的最小值为 4,最大值为 1000,默认值为 64。ef_construction 必须大于 m ,通常设置为 m 值的 2 倍。对于聚集性数据而言,通常 ef_construction 取较大值时效果更佳。ef_construction 的值越大,索引构建速度越慢。 |
hnsw.ef_search | 查询时指定,索引扫描时使用。 | 该参数为查询时参数,用于限制返回的最大记录数和准确性。 |
以下三个参数,可以用于加快 hnsw 索引构建速度:
参数 | 含义 |
---|---|
max_parallel_workers | 设置系统的最大并行进程数量,默认值为 8。 |
max_parallel_maintenance_workers | 设置单一工具性命令(例如 CREATE INDEX)的最大并行进程数。 |
maintenance_work_mem | 指定在维护性操作(例如 VACUUM、CREATE INDEX)中使用的最大的内存量。 |
创建并使用 btree 索引。
drop table tbl_vector ; create table tbl_vector (id serial, tc1 vector(5)); insert into tbl_vector (tc1) select array_agg(random())::vector(5) from generate_series(1.0,5.0) ; create index on tbl_vector (tc1); select * from tbl_vector order by tc1;
创建 ivfflat 索引,并使用 vector_l2_ops
,进行欧式距离相似性搜索。
drop table tbl_vector ; create table tbl_vector(id serial, tc1 vector(5)); insert into tbl_vector (tc1) select array_agg(random())::vector(5) from generate_series(1.0,5.0) ; create index tbl_vector_tc1_idx on tbl_vector using ivfflat (tc1) with (lists = 4); -- 高召回率 set ivfflat.probes = 4; -- 将 ivfflat.probes 调整成和索引的 lists 值一样,表示扫描所有列表, select * from tbl_vector order by tc1 <-> '[0.559782,0.194308,0.454407,0.0176121,0.442676]'; -- 低召回率 set ivfflat.probes = 1; -- 将 ivfflat.probes 调整成小于索引的 lists 值,表示扫描部分列表 select * from tbl_vector order by tc1 <-> '[0.559782,0.194308,0.454407,0.0176121,0.442676]';
创建 ivfflat 索引,并使用 vector_cosine_ops
,进行余弦相似性搜索。
drop index tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using ivfflat (tc1 vector_cosine_ops) with (lists = 4); -- 高召回率 set ivfflat.probes = 4; -- 将 ivfflat.probes 调整成和索引的 lists 值一样,表示扫描所有列表, select * from tbl_vector order by tc1 <=> '[0.559782,0.194308,0.454407,0.0176121,0.442676]'; -- 低召回率 set ivfflat.probes = 1; -- 将 ivfflat.probes 调整成小于索引的 lists 值,表示扫描部分列表 select * from tbl_vector order by tc1 <=> '[0.559782,0.194308,0.454407,0.0176121,0.442676]';
创建 ivfflat 索引,并使用 vector_ip_ops
,进行内积相似性搜索。
drop index tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using ivfflat (tc1 vector_ip_ops) with (lists = 4); -- 高召回率 set ivfflat.probes = 4; -- 将 ivfflat.probes 调整成和索引的 lists 值一样,表示扫描所有列表, select * from tbl_vector order by tc1 <#> '[0.559782,0.194308,0.454407,0.0176121,0.442676]'; -- 低召回率 set ivfflat.probes = 1; -- 将 ivfflat.probes 调整成小于索引的 lists 值,表示扫描部分列表 select * from tbl_vector order by tc1 <#> '[0.559782,0.194308,0.454407,0.0176121,0.442676]';
并行创建 hnsw 索引,并使用 max_parallel_workers
、max_parallel_maintenance_workers
和 maintenance_work_mem
参数。
set maintenance_work_mem = '1GB'; set max_parallel_workers = 4; set max_parallel_maintenance_workers = 4; drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_l2_ops) with (m = 10, ef_construction = 20);
创建 hnsw 索引,并使用vector_l2_ops
,进行欧式距离相似性搜索。
-- 高召回率 drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_l2_ops) with (m = 10, ef_construction = 20); set hnsw.ef_search = 10; select * from tbl_vector order by tc1 <-> '[0.559782,0.194308,0.454407,0.0176121,0.442676]'; -- 低召回率 drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_l2_ops) with (m = 5 , ef_construction = 10); set hnsw.ef_search = 10; select * from tbl_vector order by tc1 <-> '[0.559782,0.194308,0.454407,0.0176121,0.442676]';
创建 hnsw 索引,并使用vector_cosine_ops
,进行余弦相似性搜索。
-- 高召回率 drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_cosine_ops) with (m = 10, ef_construction = 20); set hnsw.ef_search = 10; select * from tbl_vector order by tc1 <=> '[0.559782,0.194308,0.454407,0.0176121,0.442676]'; -- 低召回率 drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_cosine_ops) with (m = 5 , ef_construction = 10); set hnsw.ef_search = 10; select * from tbl_vector order by tc1 <=> '[0.559782,0.194308,0.454407,0.0176121,0.442676]';
创建 hnsw 索引,并使用vector_ip_ops
,进行内积相似性搜索。
-- 高召回率 drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_ip_ops) with (m = 10, ef_construction = 20); set hnsw.ef_search = 10; select * from tbl_vector order by tc1 <#> '[0.559782,0.194308,0.454407,0.0176121,0.442676]'; -- 低召回率 drop index IF EXISTS tbl_vector_tc1_idx; create index tbl_vector_tc1_idx on tbl_vector using hnsw (tc1 vector_ip_ops) with (m = 5 , ef_construction = 10); set hnsw.ef_search = 10; select * from tbl_vector order by tc1 <#> '[0.559782,0.194308,0.454407,0.0176121,0.442676]';
说明
以上示例代码中的高召回率和低召回率仅代表设定场景下的结果,实际情况下需要根据业务模型进行调整。
曼哈顿距离函数(l1_distance())函数用于计算向量之间的曼哈顿距离,使用方法如以下示例所示:
select l1_distance('[1,1,1,1,1]', '[2,2,2,2,2]');
pg_vector 插件为向量类型提供了两个聚合函数 avg() 和 sum()。
avg() 函数用于计算向量每一维度的平均值,使用方法如下示例所示:
drop table tbl_vector ; create table tbl_vector(id serial, tc1 vector(5)); insert into tbl_vector (tc1) select array_agg(random())::vector(5) from generate_series(1.0,5.0) ; select avg(tc1) from tbl_vector; select id, avg(tc1) from tbl_vector group by id;
sum() 函数用于对 vector 的每一维度进行求和,使用方法如下示例所示:
insert into tbl_vector (tc1) select array_agg(random())::vector(5) from generate_series(1.0,5.0) ; select sum(tc1) from tbl_vector; select id, sum(tc1) from tbl_vector group by id;
pg_vector 插件提供了向量类型和几种数组类型的转换。
转换类型 | 使用示例 |
---|---|
将 vector 转换为 vector |
|
将 vector 转换为 real[ ] |
|
将 real[ ] 转换为 vector |
|
将 int4[ ] 转换为 vector |
|
将 double precision[ ] 转换为 vector |
|
将 numeric[ ] 转换为 vector |
|
云数据库 PostgreSQL 版的向量化场景中,典型场景是存储经过大语言模型(Large Language Model,简称 LLM)(比如:text-embedding-ada-002)处理过后的 embeddings 向量(维度固定为 1536 维),并计算他们的相似度。本文以此场景为参考,验证不同数据量、不同线程数、不同并发数、不同索引和不同参数取值,对数据库 TPS 和时延的影响。
测试参数 | |
---|---|
测试并发数 | 6/16 |
测试线程数 | 1/4 |
测试时间 | 60s |
PostgreSQL实例规格 | 单 AZ 一主一备 32C64G |
客户端规格 | 32vCPU128GiB 极速型 SSD PL0 40 GiB |
测试步骤 | 语句 |
---|---|
创建插件 |
|
创建表和索引 |
|
创建函数,生成指定维度的 embedding 数据 |
|
导入 10 万条数据 |
|
pg_bench 压测 sql 语句:vtest.sql |
|
pgbench 执行 |
|
导入 20 万条数据 |
|
导入 40 万条数据 |
|
导入 60 万条数据 |
|
导入 80 万条数据 |
|
导入 100 万条数据 |
|
测试数据量 | 表数据大小 | 索引数据大小 | TPS | 时延 | ||
---|---|---|---|---|---|---|
6 并发 1 线程 | 16 并发 4 线程 | 6 并发 1 线程 | 16 并发 4 线程 | |||
10 万 | 795 MB | 781 MB | 1838 | 2299 | 3.258 ms | 6.871 ms |
20 万 | 1590 MB | 1562.5 MB | 1683 | 2503 | 3.559 ms | 6.329 ms |
40 万 | 3180 MB | 3125 MB | 1588 | 2487 | 3.771 ms | 6.368 ms |
60 万 | 4770 MB | 4687.5 MB | 1564 | 2538 | 3.829 ms | 6.254 ms |
80 万 | 6360 MB | 6250 MB | 1508 | 2537 | 3.971 ms | 6.256 ms |
100 万 | 7950 MB | 7812.5 MB | 1434 | 2533 | 4.176 ms | 6.275 ms |
在单 AZ 一主一备 32C64G 实例、 6 并发 1 线程、不同数据量场景下,使用 hnsw 索引与 ivfflat 索引时对 TPS 和时延的影响。
在单 AZ 一主一备 32C64G 实例、 60 万数据量、 16 并发 4 线程场景下,固定 m
和 ef_construction
的值,设定不同的 hnsw.ef_search
值对于 TPS 和时延的影响。