作者
digoal
日期
2023-08-29
标签
PostgreSQL , PolarDB , 数据库 , 教学
背景
欢迎数据库应用开发者参与贡献场景, 在此issue回复即可, 共同建设《沉浸式数据库学习教学素材库》, 帮助开发者用好数据库, 提升开发者职业竞争力, 同时为企业降本提效.
- 系列课程的核心目标是教大家怎么用好数据库, 而不是怎么运维管理数据库、怎么开发数据库内核. 所以面向的对象是数据库的用户、应用开发者、应用架构师、数据库厂商的产品经理、售前售后专家等角色.
本文的实验可以使用永久免费的阿里云云起实验室来完成.
如果你本地有docker环境也可以把镜像拉到本地来做实验:
x86_64机器使用以下docker image:
ARM机器使用以下docker image:
业务场景1 介绍: 电商|短视频|新闻|内容推荐业务(根据用户行为推荐相似内容)、监控预测报警系统(基于相似指标预判告警)、音视图文多媒体相似搜索、人脸|指纹识别|比对 - 向量搜索应用
1、在电商业务中, 用户浏览商品的行为会构成一组用户在某个时间段的特征, 这个特征可以用向量来表达(多维浮点数组), 同时商品、店铺也可以用向量来表达它的特征. 那么为了提升用户的浏览体验(快速找到用户想要购买的商品), 可以根据用户向量在商品和店铺向量中进行相似度匹配搜索. 按相似度来推荐商品和店铺给用户.
2、在短视频业务中, 用户浏览视频的行为, 构成了这个用户在某个时间段的兴趣特征, 这个特征可以用向量来表达(多维浮点数组), 同时短视频也可以用向量来表达它的特征. 那么为了提升用户的观感体验(推荐他想看的视频), 可以在短视频向量中进行与用户特征向量的相似度搜索. 按相似度来推荐短视频给用户.
3、内容网站、新闻网站等, 和上面有类似的场景. 根据用户浏览行为、搜索词等, 生成特征向量, 根据特征向量的相似度匹配, 快速找到用户想要的内容.
4、监控系统, 例如应用软件的健康度监控, 根据多个维度的监控指标值+故障事件, 训练出一套向量值和故障事件对应的数据作为预判依据. 当新的监控数据到达后, 转换为特征向量, 然后到前面训练好的向量库中搜索相似度达到阈值的向量对应的故障事件, 进行故障预判.
5、人脸识别, 指纹识别. 存储录入的人脸、指纹向量, 未来可以通过向量数据库来进行指纹比对和人脸比对.
6、在音频、视频、图片网站中, 存储了音、视、图的特征向量, 同时根据用户的浏览行为生成用户的特征向量, 使用用户特征向量搜索推荐相似的音频、视频、图片, 提升用户的观感体验.
7、在股市预测中, 也可以有类似的应用. 类似上面说的监控系统.
实现和对照
传统方法 设计和实验
传统数据库没有数组类型, 只能使用text表达, 或者使用多个数值字段的组合来表达向量.
传统数据库没有向量距离搜索的操作符, 无法实现向量特征相似搜索.
传统数据库没有办法在text或者多个数值字段组合上建立向量索引, 因此即使实现了计算2个向量距离的函数, 也无法实现高效率的向量相似检索.
PolarDB|PG新方法1 设计和实验
PolarDB|PG 支持向量类型、向量距离计算操作符和函数、向量索引. 可以存储向量、进行向量距离计算、快速检索相似向量(向量距离相近).
创建向量索引插件
create extension vector;
设计一张向量特征表, 存储已知特征向量, 例如商品、视频、图文、人脸、指纹、监控事件等的特征.
create unlogged table tbl_vector ( id serial primary key, -- 内容ID vec vector(1024) -- 内容ID对应的向量, 例子使用了1024维度 );
创建一个生成随机N维向量的函数
create or replace function gen_rand_vector(int) returns vector as $$ select array_to_vector(array_agg((random()*1000)::int), $1, true) from generate_series(1,$1); $$ language sql strict; postgres=# select gen_rand_vector(10); gen_rand_vector ----------------------------------------- [841,286,91,478,961,965,99,132,315,125] (1 row)
写入测试特征向量数据100万条.
insert into tbl_vector(vec) select gen_rand_vector(1024) from generate_series(1,1000000);
创建向量索引 (支持3种距离算法, 本例使用cosine. PGVECTOR 0.5.0开始支持ivfflat和hnsw两种索引算法, 本例使用hnsw.)
-- 尽量和表一样大, 创建索引可以快一点 set maintenance_work_mem='4096MB'; CREATE INDEX ON tbl_vector USING hnsw (vec vector_cosine_ops) WITH (m = 12, ef_construction=40); -- 可以设置不同的参数, 对比一下性能.
在另一个会话中可以观测索引创建过程:
SELECT phase, tuples_done, tuples_total FROM pg_stat_progress_create_index; phase | tuples_done | tuples_total --------------------------------+-------------+-------------- building index: loading tuples | 311336 | 0 (1 row)
测试数据占用空间如下:
postgres=# \dt+ List of relations Schema | Name | Type | Owner | Persistence | Access method | Size | Description --------+------------+-------+----------+-------------+---------------+---------+------------- public | tbl_vector | table | postgres | unlogged | heap | 4979 MB | (1 row) postgres=# \di+ List of relations Schema | Name | Type | Owner | Table | Persistence | Access method | Size | Description --------+--------------------+-------+----------+------------+-------------+---------------+---------+------------- public | tbl_vector_pkey | index | postgres | tbl_vector | unlogged | btree | 21 MB | public | tbl_vector_vec_idx | index | postgres | tbl_vector | unlogged | hnsw | 7813 MB | (2 rows)
根据特征向量进行搜索
vacuum analyze tbl_vector; -- alter role postgres SET enable_seqscan = off; -- alter role postgres SET hnsw.ef_search = 10; -- 可以设置不同的参数, 对比一下性能. alter function gen_rand_vector(int) immutable; -- 为了测试索引的性能, immutable让这个函数产生常数, 强制使用vector索引. explain select id,vec <=> gen_rand_vector(1024) from tbl_vector order by vec <=> gen_rand_vector(1024) limit 1; Limit (cost=94.13..94.67 rows=1 width=12) -> Index Scan using tbl_vector_vec_idx on tbl_vector (cost=94.13..540741.53 rows=1000000 width=12) Order By: (vec <=> '[694,866,463,132,749,862,809,779,486,650,525,550,821,944,99,194,640,720,147,582,58,186,525,345,237,673,907,694,721,46,315,781,250,840,533,994,80,956,574,128,616,399,815,847,746,764,336,171,344,458,66,155,514,276,604,446,86,953,59,922,737,888,675,333,605,694,754,291,105,809,935,451,651,747,865,500,175,844,86,107,771,604,280,105,926,48,208,666,718,286,631,370,133,802,396,940,265,583,846,954,363,52,735,158,350,149,349,889,944,479,242,616,734,53,686,363,383,393,25,858,533,680,742,25,266,975,315,118,266,609,393,353,888,300,209,809,349,186,387,138,491,281,955,17,287,39,479,271,224,232,328,359,161,711,372,849,685,300,200,31,395,916,814,80,163,384,723,566,213,667,256,562,511,512,624,653,622,664,876,334,933,846,987,327,253,886,385,491,806,629,583,990,893,290,953,273,90,415,201,25,77,884,838,262,777,873,695,202,685,201,713,758,327,298,438,681,106,401,541,418,993,722,663,196,121,783,809,3,509,182,743,904,387,502,335,421,614,248,787,465,30,668,418,298,525,431,78,157,643,803,28,976,557,411,766,211,706,862,884,203,750,377,937,90,245,608,730,756,535,908,625,154,379,169,868,897,203,954,616,984,242,33,986,874,156,467,883,673,654,570,668,651,711,162,360,556,836,386,507,658,539,669,295,161,413,505,624,635,635,102,853,661,904,377,537,597,112,680,768,753,942,209,752,504,988,559,776,641,475,951,120,502,568,509,311,816,286,554,43,478,13,387,412,361,276,796,827,244,134,635,98,841,77,45,62,879,520,173,514,188,728,822,718,634,625,143,6,638,357,923,772,317,566,922,906,436,21,168,562,287,954,707,214,978,128,529,735,994,287,753,551,50,150,539,627,538,50,53,729,69,651,368,114,825,948,836,45,543,33,911,549,763,372,755,911,524,737,541,113,347,532,77,775,459,107,926,81,669,458,476,368,699,537,479,785,600,424,263,192,881,2,506,750,388,209,27,806,562,592,147,368,458,23,678,948,754,207,501,711,472,836,846,684,476,131,145,537,113,331,417,849,40,824,415,330,96,676,769,770,544,885,388,262,653,849,891,854,979,817,520,284,74,303,199,958,412,920,280,165,964,334,253,596,967,362,892,211,506,645,45,922,673,232,572,530,401,250,745,142,837,190,184,198,145,26,32,469,267,116,317,399,930,958,199,966,540,52,187,635,812,340,261,695,523,571,602,391,709,984,174,361,516,676,469,233,443,798,116,269,730,220,984,166,313,912,481,758,261,857,651,225,602,927,895,466,397,532,809,693,52,730,70,681,867,546,120,51,535,778,771,787,581,319,831,733,99,694,526,299,554,74,227,51,104,523,941,254,930,781,816,185,374,21,663,814,456,897,274,955,184,889,95,750,349,600,47,232,407,983,944,141,190,474,840,205,348,113,547,979,416,831,916,414,620,218,734,617,974,171,240,398,925,374,653,911,952,594,26,64,746,316,598,702,310,312,183,373,769,873,376,795,389,833,468,756,334,16,162,897,509,760,913,746,307,19,116,102,827,629,219,675,564,551,249,352,570,661,394,912,720,409,159,983,931,834,817,74,799,989,299,894,187,212,340,802,313,530,368,100,52,23,750,221,613,612,291,431,900,612,131,939,426,402,969,917,227,15,231,506,230,662,680,970,668,538,929,571,460,570,560,810,948,483,293,605,596,20,61,717,370,813,536,912,850,166,688,587,846,387,358,877,522,627,568,977,15,112,798,791,69,962,626,265,679,241,736,392,630,718,236,834,676,856,603,224,994,615,194,202,277,478,702,249,39,190,820,827,417,668,776,712,216,545,757,940,333,401,98,111,3,663,806,98,734,13,427,293,123,137,745,342,188,483,619,90,966,53,600,741,281,905,540,628,851,198,325,884,996,18,224,425,730,722,140,307,893,820,864,667,413,884,75,192,931,381,333,579,701,530,412,705,187,173,405,669,606,513,777,173,308,391,749,692,416,340,619,969,253,9,69,622,527,970,699,337,785,539,471,537,808,806,972,601,204,771,253,338,764,642,970,167,18,86,830,391,916,502,365,479,607,286,442,767,617,75,917,617,437,276,618,486,6,717,406,543,69,336,107,309,912,792,477,100,15,178,145,940,292,472,544,731,485,456,459,160,249,966,37,559,197,906,389,341,729,580,289,896,733,829,854,979,769,889,341,470,357,217,671,383,580,824,753,669,820,402,906,414,759,424,291,869,457,753,527,224,716,548,6,490,766,623,605,266,693,893,791,759,779,415,715,465,850,255,987,289,724,400,995,324,528,78,970,491,887]'::vector) (3 rows) postgres=# select id,vec <=> gen_rand_vector(1024) from tbl_vector order by vec <=> gen_rand_vector(1024) limit 1; id | ?column? ----------+-------------------- 10836124 | 0.2136536819090169 (1 row) Time: 50.547 ms
性能压测
vi t1.sql select id,vec <=> gen_rand_vector(1024) from tbl_vector order by vec <=> gen_rand_vector(1024) limit 1; pgbench -M prepared -n -r -P 1 -f ./t1.sql -c 10 -j 10 -T 120
压测结果
hnsw.ef_search = 10; transaction type: ./t1.sql scaling factor: 1 query mode: prepared number of clients: 10 number of threads: 10 duration: 120 s number of transactions actually processed: 1659482 latency average = 0.723 ms latency stddev = 0.329 ms initial connection time = 20.194 ms tps = 13831.040378 (without initial connection time) statement latencies in milliseconds: 0.723 select id,vec <=> gen_rand_vector(1024) from tbl_vector order by vec <=> gen_rand_vector(1024) limit 1; hnsw.ef_search = 1; transaction type: ./t1.sql scaling factor: 1 query mode: prepared number of clients: 10 number of threads: 10 duration: 120 s number of transactions actually processed: 5841138 latency average = 0.205 ms latency stddev = 0.112 ms initial connection time = 22.311 ms tps = 48685.030555 (without initial connection time) statement latencies in milliseconds: 0.205 select id,vec <=> gen_rand_vector(1024) from tbl_vector order by vec <=> gen_rand_vector(1024) limit 1;
hnsw.ef_search = 10;
时 TPS: 13831.040378
hnsw.ef_search = 1;
时 TPS: 48685.030555
对照
传统数据库不支持向量类型、向量距离计算、向量类型索引.
PolarDB|PG 支持向量类型、向量距离计算操作符和函数、向量索引. 可以存储向量、进行向量距离计算、快速检索相似向量(向量距离相近).
- macbook m2 机器上, 4核的docker资源, TPS可以达到数万.
知识点
1 什么是向量类型
2 什么是向量索引
3 向量距离搜索操作符有哪些?
4 向量搜索算法ivfflat、hnsw
- 《PostgreSQL 阿里云rds pg发布高维向量索引,支持图像识别、人脸识别 - pase 插件, 以及ivfflat,hnsw搜索算法说明》
- https://zhuanlan.zhihu.com/p/644056626
- https://neon.tech/blog/pg-embedding-extension-for-vector-search
5 向量搜索算法ivfflat、hnsw 支持哪些向量调参?
- https://github.com/pgvector/pgvector/blob/master/src/hnsw.h
- https://github.com/pgvector/pgvector/blob/master/src/ivfflat.h
- https://github.com/pgvector/pgvector
/* IVFFlat parameters */ #define IVFFLAT_DEFAULT_LISTS 100 // lists个数越大, 性能越好. 相当于每个list桶内的向量记录更少. 每个桶的向量记录数 = 总条数/lists #define IVFFLAT_MIN_LISTS 1 #define IVFFLAT_MAX_LISTS 32768 #define IVFFLAT_DEFAULT_PROBES 1 // probe越大, 精准度越高, 性能越差. 相当于搜索时从所有lists对应的若干个中心点中选出N个最近的中心点, 进入对应的这N个桶中进行继续过滤.
/* HNSW parameters */ #define HNSW_DEFAULT_M 16 // the max number of connections per layer (16 by default) #define HNSW_MIN_M 2 #define HNSW_MAX_M 100 #define HNSW_DEFAULT_EF_CONSTRUCTION 64 // the size of the dynamic candidate list for constructing the graph (64 by default) #define HNSW_MIN_EF_CONSTRUCTION 4 #define HNSW_MAX_EF_CONSTRUCTION 1000 #define HNSW_DEFAULT_EF_SEARCH 40 // Specify the size of the dynamic candidate list for search (40 by default). SET hnsw.ef_search = 100; #define HNSW_MIN_EF_SEARCH 1 #define HNSW_MAX_EF_SEARCH 1000
postgres=# load 'vector' ; LOAD postgres=# show ivfflat.probes ; ivfflat.probes ---------------- 1 (1 row) postgres=# show hnsw.ef_search ; hnsw.ef_search ---------------- 40 (1 row)
6 ivfflat和hnsw两种向量索引应该如何选择? 和向量数据量有关吗?
7 为什么说使用 ivfflat 向量搜索是近似结果, 那么结果的精准度和什么有关? 如何配置?
8 k-means
9 向量检索有没有标准或流行的benchmark工具?
10 还有哪些支持向量搜索的数据库? 各自的产品特性如何? 我们的数据库选型标准是什么? 综合哪些因素来进行选择?
11 还有哪些支持向量搜索的插件?
- hnsw, embedding, cube, imgsmlr, ...
思考
1 如何将非结构化的业务数据转换成向量?
2 如果训练向量和事件的相关性?
- madlib
- https://madlib.apache.org/
3 如何高效过滤已读内容?
4 除了应用报警, 还有什么业务能使用类似的方法进行预测?
- 气象、地震灾害、地质灾害? 市场预测?
5 向量特征相似搜索的性能取决于什么?
6 当数据库的向量搜索请求并发非常高, 已经达到了数据库性能瓶颈时, 如何解决? 使用PostgreSQL只读实例 还是 PolarDB共享存储|存算分离 更好? 为什么?
7 pgvector目前支持几种向量距离计算方法? 那种性能最好?
- Euclidean distance
- negative inner product
- cosine distance
- taxicab distance
- If vectors are normalized to length 1 (like OpenAI embeddings), use inner product for best performance.
- https://mathworld.wolfram.com/NormalizedVector.html
8 如何将向量转换为归一化向量? 下面是chatgpt给的方法, 大家思考一下是否正确?
- 首先,计算向量的长度(模/范数)。对于二维向量
(x, y)
,长度可以通过以下公式计算:len = sqrt(x^2 + y^2)
。对于具有更多维度的向量,需要对每个分量进行平方并求和,然后取平方根。 - 接下来,将每个向量分量除以其长度,以获得归一化向量的分量。对于二维向量
(x, y)
,归一化向量可以通过以下公式计算:normalized_vector = (x/len, y/len)
。对于具有更多维度的向量,将每个分量除以长度即可。
下面是一个示例代码,演示如何将向量(x, y)
归一化为长度为1的向量:
在这个示例中,我们使用CTE(公共表达式)定义了一个包含分量x和y的向量。然后,我们通过计算平方和的平方根来获取向量的长度。最后,我们将每个分量除以长度,得到归一化向量。
WITH my_table AS ( SELECT 2 AS x, 3 AS y ) SELECT x / sqrt(x^2 + y^2) AS normalized_x, y / sqrt(x^2 + y^2) AS normalized_y FROM my_table;
9 如果数据量特别大, 如何提升创建索引的速度?
- 使用分区表, 并行创建? 有什么负作用?
- 提升maintenance_work_mem?
- 内核是否支持并行创建向量索引?
参考
《标准知识库 + PostgreSQL或PolarDB + 向量插件 + openai(或其他大模型) 提升通用ai机器人在专业领域的精准度》
《ChatGPT背后的数据库技术体验 - 向量近似搜索之 PostgreSQL+pase(hnsw,ivfflat,ivfpq)》
《ChatGPT背后的数据库技术体验 - 向量近似搜索之 pgvector : 如何用 PolarDB 在不确定世界寻找确定答案 (例如图像相似) - pgvector|pase》
《PostgreSQL 开源 高维向量相似搜索插件 vector - 关联阿里云rds pg pase, cube, 人脸识别》
《PostgreSQL 应用开发解决方案最佳实践系列课程 - 3. 人脸识别和向量相似搜索》
《PostgreSQL+MySQL 联合解决方案 - 第11课视频 - 多维向量相似搜索 - 图像识别、相似人群圈选等》
《PostgreSQL 阿里云rds pg发布高维向量索引,支持图像识别、人脸识别 - pase 插件, 以及ivfflat,hnsw搜索算法说明》