Skip to content

Commit 7e198b1

Browse files
committed
new doc
1 parent 25e000d commit 7e198b1

File tree

4 files changed

+396
-0
lines changed

4 files changed

+396
-0
lines changed

201802/20180205_03.md

Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
## PostgreSQL 异步调用与多机并行 - 相似搜索集群线性扩容架构设计与实践
2+
3+
### 作者
4+
digoal
5+
6+
### 日期
7+
2018-02-05
8+
9+
### 标签
10+
PostgreSQL , 多表并行 , 多机并行 , dblink , 异步调用 , 相似搜索
11+
12+
----
13+
14+
## 背景
15+
背景请参考如下:
16+
17+
[《PostgreSQL 相似搜索设计与性能 - 地址、QA、POI等文本 毫秒级相似搜索实践》](../201802/20180202_01.md)
18+
19+
当需要进行相似搜索的数据量大于单机处理能力时,我们需要水平拆分来提高搜索能力。
20+
21+
或者可以使用阿里云的PolarDB for PG的产品(类似ORACLE RAC,支持增加计算节点)。比水平分库的好处是数据是共享存储的,不需要拆分。
22+
23+
24+
回到水平分库的场景,如果我们把数据库拆成了多个,那么,如何让查询并行起来呢?
25+
26+
用DBLINK异步调用,可以让查询并行起来。架构设计如下:
27+
28+
![pic](20180205_03_pic_001.jpg)
29+
30+
实际上采用DBLINK异步调用实现并行的例子很多:
31+
32+
[《PostgreSQL dblink异步调用实现 并行hash分片JOIN - 含数据交、并、差 提速案例》](../201802/20180201_02.md)
33+
34+
进入正题,下面是一个DEMO,按部就班的演示如何使用异步调用实现多库并行相似搜索。
35+
36+
## DEMO
37+
1、我们这里使用本地的4个DB来代表远程数据库,这4个DB完全可以安装到远程。这里只是为了测试方便。
38+
39+
本地库名:
40+
41+
```
42+
postgres
43+
```
44+
45+
远程库名:
46+
47+
```
48+
db0
49+
db1
50+
db2
51+
db3
52+
```
53+
54+
2、首先需要创建用户和测试DB
55+
56+
```
57+
create role test login encrypted password 'secret';
58+
create database db0 with owner test;
59+
create database db1 with owner test;
60+
create database db2 with owner test;
61+
create database db3 with owner test;
62+
```
63+
64+
3、在本地某个库中创建dblink插件
65+
66+
```
67+
create extension dblink;
68+
```
69+
70+
4、创建连接远程库的SERVER
71+
72+
```
73+
CREATE SERVER db0 FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname 'db0');
74+
CREATE SERVER db1 FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname 'db1');
75+
CREATE SERVER db2 FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname 'db2');
76+
CREATE SERVER db3 FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname 'db3');
77+
```
78+
79+
5、配置连接远程库的用户密码(用户密码都是远程库的,可不是本地的哦,你想用本地用户连远程库,没门)
80+
81+
```
82+
CREATE USER MAPPING FOR postgres SERVER db0 OPTIONS (user 'test', password 'secret');
83+
CREATE USER MAPPING FOR postgres SERVER db1 OPTIONS (user 'test', password 'secret');
84+
CREATE USER MAPPING FOR postgres SERVER db2 OPTIONS (user 'test', password 'secret');
85+
CREATE USER MAPPING FOR postgres SERVER db3 OPTIONS (user 'test', password 'secret');
86+
```
87+
88+
### 远程库操作
89+
90+
在所有远程库上创建测试表,灌入测试数据,创建相似搜索函数。(注意下面的脚本需要调整好对应调度dbname)
91+
92+
1、必要的插件(注意下面的脚本需要调整好对应调度dbname)
93+
94+
```
95+
\c db3 postgres
96+
create extension pg_trgm;
97+
create extension dblink;
98+
```
99+
100+
2、主表和相似搜索依赖的索引(注意下面的脚本需要调整好对应调度dbname)
101+
102+
```
103+
\c db3 test
104+
create unlogged table tbl(id int primary key, info text);
105+
create index idx_tbl_info on tbl using gin (info gin_trgm_ops);
106+
107+
-- alter table tbl set (parallel_workers =64);
108+
```
109+
110+
3、创建分区(本文仅做测试,真正的分区表用法请参考: [《PostgreSQL 11 分区表用法及增强 - 增加HASH分区支持 (hash, range, list)》](../201802/20180205_02.md)
111+
112+
```
113+
do language plpgsql $$
114+
declare
115+
begin
116+
for i in 0..63
117+
loop
118+
execute format('drop table if exists tbl%s ', i);
119+
execute format('create unlogged table tbl%s (like tbl including all) inherits(tbl)', i);
120+
-- 提前设置好表级并行度,方便后面做并行测试
121+
-- execute format('alter table tbl%s set (parallel_workers =64)', i);
122+
end loop;
123+
end;
124+
$$;
125+
```
126+
127+
4、创建连接函数
128+
129+
```
130+
create or replace function conn(
131+
name, -- dblink名字
132+
text -- 连接串,URL
133+
) returns void as $$
134+
declare
135+
begin
136+
perform dblink_connect($1, $2);
137+
return;
138+
exception when others then
139+
return;
140+
end;
141+
$$ language plpgsql strict;
142+
```
143+
144+
5、创建生成随机函数的函数
145+
146+
```
147+
-- 生成随机汉字符串
148+
create or replace function gen_hanzi(int) returns text as $$
149+
declare
150+
res text;
151+
begin
152+
if $1 >=1 then
153+
select string_agg(chr(19968+(random()*20901)::int), '') into res from generate_series(1,$1);
154+
return res;
155+
end if;
156+
return null;
157+
end;
158+
$$ language plpgsql strict;
159+
```
160+
161+
6、写入测试数据,随机文本(注意下面的脚本需要调整好对应调度dbname)
162+
163+
```
164+
do language plpgsql $$
165+
declare
166+
dbname name := 'db3';
167+
begin
168+
for i in 0..63
169+
loop
170+
perform conn('link'||i, 'hostaddr=127.0.0.1 user=test password=secret dbname='||dbname);
171+
perform dblink_send_query('link'||i, format('insert into tbl%s select generate_series(1, 15625), gen_hanzi(64); analyze tbl%s;', i, i));
172+
end loop;
173+
end;
174+
$$;
175+
```
176+
177+
7、创建相似搜索用到的UDF
178+
179+
```
180+
create or replace function get_res(
181+
text, -- 要按相似搜的文本
182+
int8, -- 限制返回多少条
183+
float4 default 0.3, -- 相似度阈值,低于这个值不再搜搜
184+
float4 default 0.1 -- 相似度递减步长,直至阈值
185+
) returns setof record as $$
186+
declare
187+
lim float4 := 1;
188+
begin
189+
-- 判定
190+
if not ($3 <= 1 and $3 > 0) then
191+
raise notice '$3 must >0 and <=1';
192+
return;
193+
end if;
194+
195+
if not ($4 > 0 and $4 < 1) then
196+
raise notice '$4 must >0 and <=1';
197+
return;
198+
end if;
199+
loop
200+
-- 设置相似度阈值
201+
perform set_limit(lim);
202+
203+
-- 查看当前阈值下,有没有相似记录
204+
perform similarity(info, $1) as sml, * from tbl where info % $1 limit 1;
205+
206+
-- 如果有,则返回N条
207+
if found then
208+
return query select similarity(info, $1) as sml, * from tbl where info % $1 order by sml desc limit $2;
209+
return;
210+
end if;
211+
212+
-- 否则继续,降低阈值
213+
-- 当阈值小于0.3时,不再降阈值搜索,认为没有相似。
214+
if lim < $3 then
215+
return;
216+
else
217+
lim := lim - $4;
218+
end if;
219+
end loop;
220+
end;
221+
$$ language plpgsql strict;
222+
```
223+
224+
### 本地库操作
225+
226+
创建建立远程连接的函数
227+
228+
```
229+
create or replace function conn(
230+
name, -- dblink名字
231+
text -- 连接串,URL
232+
) returns void as $$
233+
declare
234+
begin
235+
perform dblink_connect($1, $2);
236+
return;
237+
exception when others then
238+
return;
239+
end;
240+
$$ language plpgsql strict;
241+
```
242+
243+
#### 返回游标
244+
1、定义UDF1 - 返回游标(如果返回记录数很多,建议使用游标,因为PLPGSQL是需要等所有记录都拿到才会开始返回,返回记录的话RT会较高)
245+
246+
例子
247+
248+
```
249+
create or replace function get_res_cursor(
250+
text, -- 要按相似搜的文本
251+
int8, -- 限制返回多少条
252+
float4 default 0.3, -- 相似度阈值,低于这个值不再搜搜
253+
float4 default 0.1 -- 相似度递减步长,直至阈值
254+
) returns setof refcursor as $$
255+
declare
256+
i int := 1;
257+
ref refcursor[];
258+
res refcursor;
259+
dbname name[] := array['db0', 'db1', 'db2', 'db3']; -- 定义集群
260+
db name;
261+
begin
262+
foreach db in array dbname
263+
loop
264+
ref[i] := 'link'||i;
265+
res := ref[i];
266+
perform conn('link'||i, db);
267+
perform dblink_open('link'||i, 'link'||i, format('select * from get_res(%L, %s, %s, %s) as t(sml real, id int, info text)', $1, $2, $3, $4));
268+
return next res;
269+
i := i+1;
270+
end loop;
271+
end;
272+
$$ language plpgsql strict;
273+
```
274+
275+
使用例子
276+
277+
```
278+
postgres=# begin;
279+
BEGIN
280+
Time: 0.045 ms
281+
postgres=# select * from get_res_cursor('怮媕苸淏倍椡帪暀虻爴荡巒讉輶魂馜虑范噞蠭鲧烳渃麠钸趥剘偣瑴鑪颭蚢佚簀哌內霡擷槧緸褫齈跊甏軙襧漆疅泅睤帍槇驗縐棂', 10, 0.1, 0.05);
282+
get_res_cursor
283+
----------------
284+
link1
285+
link2
286+
link3
287+
link4
288+
(4 rows)
289+
290+
Time: 18.624 ms
291+
postgres=# select * from dblink_fetch('link1','link1',10) as t(sml real, id int, info text);
292+
sml | id | info
293+
-----+----+------
294+
(0 rows)
295+
296+
Time: 219.972 ms
297+
postgres=# select * from dblink_fetch('link1','link1',10) as t(sml real, id int, info text);
298+
sml | id | info
299+
-----+----+------
300+
(0 rows)
301+
302+
Time: 0.252 ms
303+
postgres=# select * from dblink_fetch('link2','link2',10) as t(sml real, id int, info text);
304+
sml | id | info
305+
-----+----+------
306+
(0 rows)
307+
308+
Time: 215.891 ms
309+
postgres=# select * from dblink_fetch('link3','link3',10) as t(sml real, id int, info text);
310+
sml | id | info
311+
-----+----+------
312+
(0 rows)
313+
314+
Time: 215.188 ms
315+
postgres=# select * from dblink_fetch('link4','link4',10) as t(sml real, id int, info text);
316+
sml | id | info
317+
----------+----+----------------------------------------------------------------------------------------------------------------------------------
318+
0.779412 | 1 | 递陊怮媕苸淏倍椡帪暀虻爴荡巒讉輶魂馜虑范噞蠭鲧烳渃麠钸趥剘偣瑴鑪颭蚢佚簀哌內霡擷槧緸褫齈跊甏軙襧漆疅泅睤帍槇驗縐棂轪氐洚重銄懟諔
319+
(1 row)
320+
321+
Time: 106.692 ms
322+
```
323+
324+
#### 返回记录
325+
1、定义UDF2 - 返回记录(注意,建议限制返回的条数,因为PLPGSQL是需要等所有记录都拿到才会开始返回)
326+
327+
例子
328+
329+
```
330+
create or replace function get_res_record(
331+
text, -- 要按相似搜的文本
332+
int8, -- 限制返回多少条
333+
float4 default 0.3, -- 相似度阈值,低于这个值不再搜搜
334+
float4 default 0.1 -- 相似度递减步长,直至阈值
335+
) returns setof record as $$
336+
declare
337+
i int;
338+
ref refcursor[];
339+
res refcursor;
340+
dbname name[] := array['db0', 'db1', 'db2', 'db3']; -- 定义集群
341+
db name;
342+
begin
343+
i := 1;
344+
foreach db in array dbname
345+
loop
346+
perform conn('link'||i, db);
347+
perform 1 from dblink_get_result('link'||i) as t(sml real, id int, info text);
348+
perform dblink_send_query('link'||i, format('select * from get_res(%L, %s, %s, %s) as t(sml real, id int, info text)', $1, $2, $3, $4));
349+
i := i+1;
350+
end loop;
351+
352+
i := 1;
353+
foreach db in array dbname
354+
loop
355+
return query SELECT * FROM dblink_get_result('link'||i) as t(sml real, id int, info text);
356+
i := i+1;
357+
end loop;
358+
end;
359+
$$ language plpgsql strict;
360+
```
361+
362+
使用例子
363+
364+
365+
```
366+
postgres=# select * from get_res_record('怮媕苸淏倍椡帪暀虻爴荡巒讉輶魂馜虑范噞蠭鲧烳渃麠钸趥剘偣瑴鑪颭蚢佚簀哌內霡擷槧緸褫齈跊甏軙襧漆疅泅睤帍槇驗縐棂', 10, 0.77, 0.4) as (sml real, id int, info text);
367+
sml | id | info
368+
----------+----+----------------------------------------------------------------------------------------------------------------------------------
369+
0.779412 | 1 | 递陊怮媕苸淏倍椡帪暀虻爴荡巒讉輶魂馜虑范噞蠭鲧烳渃麠钸趥剘偣瑴鑪颭蚢佚簀哌內霡擷槧緸褫齈跊甏軙襧漆疅泅睤帍槇驗縐棂轪氐洚重銄懟諔
370+
(1 row)
371+
372+
Time: 32.329 ms
373+
```
374+
375+
## 小结
376+
使用本文提到的方法,你就可以将多个PostgreSQL当成一个PostgreSQL来使用,实现并行相似搜索的线性扩容。
377+
378+
379+
性能指标,详见:
380+
381+
[《PostgreSQL 相似搜索设计与性能 - 地址、QA、POI等文本 毫秒级相似搜索实践》](../201802/20180202_01.md)
382+
383+
使用dblink异步调用,实现相似文本搜索的横向线性扩展,性能不衰减。
384+
385+
## 参考
386+
387+
388+
[《PostgreSQL 相似搜索设计与性能 - 地址、QA、POI等文本 毫秒级相似搜索实践》](../201802/20180202_01.md)
389+
390+
[《PostgreSQL dblink异步调用实现 并行hash分片JOIN - 含数据交、并、差 提速案例》](../201802/20180201_02.md)
391+
392+
https://www.postgresql.org/docs/10/static/dblink.html
393+
394+

201802/20180205_03_pic_001.jpg

96.9 KB
Loading

0 commit comments

Comments
 (0)