海明距离mysql查询_海量数据,海明距离高效检索(smlar) - 阿里云RDS PosgreSQL最佳实践-阿里云开发者社区...

标签

PostgreSQL , 海明距离 , smlar , GiST索引

背景

SimHash的应用

通过上面的步骤,我们可以利用SimHash算法为每一个网页生成一个向量指纹,那么问题来了,如何判断2篇文本的相似性?

这里面主要应用到是海明距离。

(1)什么是海明距离

两个码字的对应比特取值不同的比特数称为这两个码字的海明距离。在一个有效编码集中,任意两个码字的海明距离的最小值称为该编码集的海明距离。举例如下:10101和00110从第一位开始依次有第一位、第四、第五位不同,则海明距离为3。

(2)海明距离的几何意义

n位的码字可以用n维空间的超立方体的一个顶点来表示。两个码字之间的海明距离就是超立方体两个顶点之间的一条边,而且是这两个顶点之间的最短距离。

(3)海明距离的应用场景

用于编码的检错和纠错

经过SimHash算法提取来的指纹(Simhash对长文本500字+比较适用,短文本可能偏差较大,具体需要根据实际场景测试),最后使用海明距离,求相似,在google的论文给出的数据中,64位的签名,在海明距离为3的情况下,可认为两篇文档是相似的或者是重复的,当然这个值只是参考值,针对自己的应用可能又不同的测试取值

到这里相似度问题基本解决,但是按这个思路,在海量数据几百亿的数量下,效率问题还是没有解决的,因为数据是不断添加进来的,不可能每来一条数据,都要和全库的数据做一次比较,按照这种思路,处理速度会越来越慢,线性增长。

针对海量数据的去重效率,我们可以将64位指纹,切分为4份16位的数据块,根据抽屉原理在海明距离为3的情况,如果两个文档相似,那么它必有一个块的数据是相等的。

那么数据库是否支持这种高效率的检索呢?

反正PostgreSQL是支持的,通过黑科技smlar插件。

一、需求

二、架构设计

在PostgreSQL中,从海量数据中,搜索海明距离小于N的数据,有多种设计手段。每种方法的能耗比都不一样,读者可以按需选择。

1 暴力计算

1、单机多核并行计算,暴力扫描。采用阿里云RDS PostgreSQL 10提供的多核并行能力,暴力扫描。

2、多机多核并行计算,暴力扫描。采用阿里云HybridDB for PostgreSQL提供的多级并行计算能力,暴力扫描。

3、利用GPU、FPGA加速暴力运算。

PostgreSQL提供了扩展接口,可以利用GPU,FPGA的能力对数据进行计算。

4、利用CPU向量计算指令,暴力计算。

PostgreSQL提供了扩展接口,可以利用CPU向量计算指令的能力加速计算。

2 索引

索引是高效的做法,例如PostgreSQL smlar插件,在阿里的导购平台就有使用,用于实时导购文的海量相似度查询。

如果要让smlar加速海明距离的搜索,需要采用更科学的方法,比如切片。

直接使用位置,会有问题,因为smlar的第一道工序是块级收敛,而海明码是bit64的编码,在一个数据块中,有若干条记录,任何位置都可能同时出现0和1,任何数据块都包含0和1,因此无法完成第一道过滤。

我们可以采用切片,减少这种可能性。例如每2个BIT一片,或者每4个BIT一片,或者更多。

通常海明距离大于3的,就没有什么相关性了。

三、DEMO与性能

1 暴力计算

1、全扫,并行扫描

创建测试表

create table hm (

id int, -- id

hmval bit(64) -- 海明HASH

);

写入1000万测试数据

postgres=# insert into hm select id, val::int8::bit(64) from (select id, sqrt(random())::numeric*9223372036854775807*2-9223372036854775807::numeric as val from generate_series(1,10000000) t(id)) t;

INSERT 0 10000000

postgres=# select * from hm limit 10;

id | hmval

----+------------------------------------------------------------------

1 | 0000101001110110110101010111101011100110101010000111100011110111

2 | 0110011100110101101000001010101111010001011101100111111011001110

3 | 1010110111001011011110110000111111101101101111010111111100101110

4 | 0110011110110000001011000010010000101011100101010100111000101001

5 | 0101110100101111010110010110000000101110000010001011010110110000

6 | 0011010000100000101011011100000101111110010110111101100001100001

7 | 1011110011101101101000011101011101010111011001011010110111101000

8 | 1110010011000101001101110010001111110100001101010101111101110010

9 | 0110111111110011101001001000101101011011111100010010111010001111

10 | 0011100011000010101011010001111000000110100011100100111011011001

(10 rows)

设置暴力并行度

postgres=# set force_parallel_mode = on;

postgres=# set min_parallel_table_scan_size = 0;

postgres=# set parallel_setup_cost = 0;

postgres=# set parallel_tuple_cost = 0;

postgres=# alter table hm set (parallel_workers = 128);

postgres=# set max_parallel_workers_per_gather = 64;

并行查询海明距离小于4的记录,耗时463毫秒。

postgres=# select * from hm where length(replace(bitxor(bit'0011110001011010110010001011010101001000111110000111110010010110', hmval)::text,'0','')) < 4;

id | hmval

----+------------------------------------------------------------------

16 | 0011110001011010110010001011010101001000111110000111110010010110

(1 row)

Time: 463.314 ms

非并行查询海明距离小于4的记录,耗时16秒。

postgres=# select * from hm where length(replace(bitxor(bit'0011110001011010110010001011010101001000111110000111110010010110', hmval)::text,'0','')) < 4;

id | hmval

----+------------------------------------------------------------------

16 | 0011110001011010110010001011010101001000111110000111110010010110

(1 row)

Time: 16791.215 ms (00:16.791)

求两个BIT的不同位数,还有更高效率的方法。理论上可以达到100毫秒以内。

https://www.postgresql.org/message-id/flat/ab1ea6540903121110l2a3021d4h6632b206e2419898%40mail.gmail.com#ab1ea6540903121110l2a3021d4h6632b206e2419898@mail.gmail.com

2 索引

阿里云RDS PostgreSQL提供了一个smlar插件,用于高效率的求数组的相似度。

我们需要将海明HASH,转换为数组,根据前面的设计,我们采用8个BIT一片的切法,支持索引查询海明距离为8以内的值。

切之前,验证一下切片后的过滤性:

postgres=# select relpages from pg_class where relname='hm';

relpages

----------

63695

(1 row)

1、单个片为1时,不用说,每个块都包含。

postgres=# select count(*) from (select substring(ctid::text,'(\d+),') from hm where substring(hmval,1,1)='0' group by 1)t;

count

-------

63695

(1 row)

2、单个片为8时,有接近一半的块包含。

postgres=# select count(*) from (select substring(ctid::text,'(\d+),') from hm where substring(hmval,1,8)='00000000' group by 1)t;

count

-------

29100

(1 row)

3、单个片为16时,只有100多个块包含了。

postgres=# sel

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值