热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

惊天性能!单RDSPostgreSQL实例支撑2000亿实时标签透视案例(含dblink异步并行调用)

digoal德哥专栏惊天性能!单RDSPostgreSQL实例支撑2000亿-实时标签透视案例(含dbl

作者

digoal


日期

2017-12-23


标签

PostgreSQL , varbitx , 标签 , 任意圈选 , 阿里云 , RDS , bitmap , 分段 , 并行计算 , 异步调用 , dblink , 异步dblink , 游标




背景

20亿用户,每个用户1000个标签,基于任意标签组合圈选、透视(业务上的需求是一次最多计算100个标签的组合)。

相当于要处理2000亿记录。

1、实时求标签组合的记录数。(即满足标签组合的用户有多少)

2、用户ID。(级满足标签组合的用户ID。)

要求实时响应。

通常你肯定会想,这个至少需要上百台机器来支撑。

但是我要给你一个惊喜,这个数据量,一台RDS PG实例即可。怎么做呢?听我道来,用最少的资源解决业务问题,用到RDS PG黑科技。


RDS PG 解决方案

方案如下:

《阿里云RDS PostgreSQL varbitx实践 - 流式标签 (阅后即焚流式批量计算) - 万亿级,任意标签圈人,毫秒响应》


优化方案,提高响应速度

1、bitmap切段

2、计算满足条件的USER COUNT值时,并行计算(使用dblink异步调用)

3、求用户ID时,使用游标,流式返回。


DEMO

1、需要用到的插件

create extension dblink;
create extension varbitx;

2、创建标签表,切段,例如20亿个用户,切成400段,每一段5000万个用户BIT。

postgres=# create table t_bitmap (
tagid int, -- 标签ID
ofid int, -- 偏移值, 乘以5000万
v varbit -- userid 比特
);
CREATE TABLE

3、创建索引(约束)

create unique index idx_t_bitmap_1 on t_bitmap (tagid, ofid);

4、创建1000个标签的BITMAP数据,每一个标签400条,每条的BIT长度为5000万位。

```

postgres=# do language plpgsql $$

declare v varbit := repeat('1',5000000)::varbit;

begin

for i in 1..100 loop

for x in 0..399 loop

insert into t_bitmap values (i, x, v);

end loop;

end loop;

end;

$$;

DO

Time: 150468.359 ms (02:30.468)

```

5、创建生成dblink连接的函数,重复创建不报错。

create or replace function conn(
name, -- dblink名字
text -- 连接串,URL
) returns void as $$
declare
begin
perform dblink_connect($1, $2);
return;
exception when others then
return;
end;
$$ language plpgsql strict;

6、AND标签组合的并行计算函数(dblink 异步并行),返回USERID透视数。

```

create or replace function get_bitcount_and(

and_tagids int[], -- 输入标签ID数组

v_bit int, -- 求1或0的比特个数

conn text, -- 连接串
OUT cnt int8 -- 返回值, 多少个1或0

) returns setof int8 as $$

declare

begin

for i in 0..399 loop -- 生成400个链接,因为每行5000万,20亿个BIT,刚好400条。并LOOP

perform conn('link'||i, conn); -- 连接

perform dblink_get_result('link'||i); -- 消耗掉上一次异步连接的结果,否则会报错。

-- 发送异步DBLINK调用

-- 每次操作一个bit分段,返回BIT为0或1的位数

perform dblink_send_query('link'||i, format('select bit_count(bit_and(v), %s) from t_bitmap where tagid = any (%L) and ofid=%s', v_bit, and_tagids, i));

end loop;

for i in 0..399 loop

-- 返回异步调用结果,包括所有分段

return query SELECT * FROM dblink_get_result('link'||i) as t(cnt int8);

end loop;

end;

$$ language plpgsql strict;

```

7、OR标签组合的并行计算函数(dblink 异步并行),返回USERID透视数。

```

create or replace function get_bitcount_or(

or_tagids int[],

v_bit int,

conn text, -- 连接串
OUT cnt int8

) returns setof int8 as $$

declare

begin

for i in 0..399 loop

perform conn('link'||i, conn);

perform dblink_get_result('link'||i);

perform dblink_send_query('link'||i, format('select bit_count(bit_or(v), %s) from t_bitmap where tagid = any (%L) and ofid=%s', v_bit, or_tagids, i));

end loop;

for i in 0..399 loop

return query SELECT * FROM dblink_get_result('link'||i) as t(cnt int8);

end loop;

end;

$$ language plpgsql strict;

```

8、AND,OR 标签组合的并行计算函数(dblink 异步并行),返回USERID透视数。

```

create or replace function get_bitcount_and_or(

and_tagids int[],

or_tagids int[],

v_bit int,

conn text, -- 连接串
OUT cnt int8

) returns setof int8 as $$

declare

begin

for i in 0..399 loop

perform conn('link'||i, conn);

perform dblink_get_result('link'||i);

perform dblink_send_query('link'||i, format('

with t1 as (select bit_and(v) b from t_bitmap where tagid = any (%L) and ofid=%s),

t2 as (select bit_or(v) b from t_bitmap where tagid = any (%L) and ofid=%s)

select bit_count(bitor(t1.b, t2.b), %s) from t1,t2',

and_tagids, i, or_tagids, i, v_bit));

end loop;

for i in 0..399 loop

return query SELECT * FROM dblink_get_result('link'||i) as t(cnt int8);

end loop;

end;

$$ language plpgsql strict;

```

-- 更复杂的QUERY,可以自行修改函数。实际业务中这种需求较少。
-- (a and b andc or d) or (a and c) or (d and not b)..........

9、计数透视的性能如下,50个标签组合,仅1.5秒,100个标签组合,仅2.6秒:

我们统计2000亿个user_tags组合(每个用户一条记录,每条记录1000个标签时的换算),仅仅需要2.6秒。

```

一个标签:

postgres=# select sum(cnt) from (select get_bitcount_and(array_agg(id),1,'dbname=postgres user=postgres') cnt from generate_series(1,1) t(id)) t;

sum



2000000000

(1 row)

Time: 791.392 ms

10个标签组合:

postgres=# select sum(cnt) from (select get_bitcount_and(array_agg(id),1,'dbname=postgres user=postgres') cnt from generate_series(1,10) t(id)) t;

sum



2000000000

(1 row)

Time: 847.427 ms

50个标签组合:

postgres=# select sum(cnt) from (select get_bitcount_and(array_agg(id),1,'dbname=postgres user=postgres') cnt from generate_series(1,50) t(id)) t;

sum



2000000000

(1 row)

Time: 1478.847 ms (00:01.479)

100个标签组合:

postgres=# select sum(cnt) from (select get_bitcount_and(array_agg(id),1,'dbname=postgres user=postgres') cnt from generate_series(1,100) t(id)) t;

sum



2000000000

(1 row)

Time: 2574.761 ms (00:02.575)

```

10、AND 、 OR组合性能如下,性能一样:

```

postgres=# select sum(cnt) from (select get_bitcount_and_or(array_agg(case mod(id,2) when 0 then id end), array_agg(case mod(id,2) when 1 then id end), 1,'dbname=postgres user=postgres') cnt from generate_series(1,1) t(id)) t;

sum



(1 row)

Time: 854.934 ms

postgres=# select sum(cnt) from (select get_bitcount_and_or(array_agg(case mod(id,2) when 0 then id end), array_agg(case mod(id,2) when 1 then id end), 1,'dbname=postgres user=postgres') cnt from generate_series(1,10) t(id)) t;

sum



2000000000

(1 row)

Time: 889.472 ms

postgres=# select sum(cnt) from (select get_bitcount_and_or(array_agg(case mod(id,2) when 0 then id end), array_agg(case mod(id,2) when 1 then id end), 1,'dbname=postgres user=postgres') cnt from generate_series(1,50) t(id)) t;

sum



2000000000

(1 row)

Time: 1519.031 ms (00:01.519)

postgres=# select sum(cnt) from (select get_bitcount_and_or(array_agg(case mod(id,2) when 0 then id end), array_agg(case mod(id,2) when 1 then id end), 1,'dbname=postgres user=postgres') cnt from generate_series(1,100) t(id)) t;

sum



2000000000

(1 row)

Time: 2597.701 ms (00:02.598)

```

11、求USERID,AND 函数如下,我们为了达到高速响应,使用游标返回。

```

create or replace function get_pos_and(

and_tagids int[], -- 标签组合

v_bit int -- 求1或0的BIT位,返回游标,游标包含ofid与位置下标(当然了,这个翻译动作也可以交给程序,那么返回BIT和ofid即可)

) returns setof refcursor as $$

declare

ref refcursor[]; -- 返回游标数组

res refcursor; -- 返回游标

sql text; -- 游标对应的SQL,即取USERID位置的SQL

begin

for x in 1..400 loop -- 生成400个游标名

ref[x] := 'cur'||x;

end loop;

for i in 0..399 loop

-- 使用0到399的偏移值, 乘以5000万系数。

-- 赋予游标名
res := ref[i+1];
-- 生成游标对应的动态SQL(ofid, bit位置),注意bit位置可以不翻译,交给程序翻译也没问题。程序翻译的话,翻译好之后,再使用in查询字典
-- select uid from uid_mapping where pos in (pos_array);
-- 1亿,in 100万, 380毫秒
-- [《HTAP数据库 PostgreSQL 场景与性能测试之 25 - (OLTP) IN , EXISTS 查询》](201711/20171107_26.md)
sql := format('select %s, bit_posite(bit_and(v), %s, true) from t_bitmap where tagid = any (%L) and ofid=%s', i, v_bit, and_tagids, i);
-- 打开游标
open res for execute sql ;
-- 返回游标
return next res;

end loop;

end;

$$ language plpgsql strict;

```

12、求USERID,OR 函数如下,我们为了达到高速响应,使用游标返回。

```

create or replace function get_pos_or(

or_tagids int[],

v_bit int

) returns setof refcursor as $$

declare

ref refcursor[];

res refcursor;

sql text;

begin

for x in 1..400 loop

ref[x] := 'cur'||x;

end loop;

for i in 0..399 loop

res := ref[i+1];

sql := format('select %s, bit_posite(bit_or(v), %s, true) from t_bitmap where tagid = any (%L) and ofid=%s', i, v_bit, or_tagids, i);

open res for execute sql ;

return next res;

end loop;

end;

$$ language plpgsql strict;

```

13、求USERID,AND OR 函数如下,我们为了达到高速响应,使用游标返回。

```

create or replace function get_pos_and_or(

and_tagids int[],

or_tagids int[],

v_bit int

) returns setof refcursor as $$

declare

ref refcursor[];

res refcursor;

sql text;

begin

for x in 1..400 loop

ref[x] := 'cur'||x;

end loop;

for i in 0..399 loop

res := ref[i+1];

sql := format('with t1 as

(select bit_and(v) v from t_bitmap where tagid = any (%L) and ofid=%s),

t2 as

(select bit_or(v) v from t_bitmap where tagid = any (%L) and ofid=%s)

select %s, bit_posite(bitor(t1.v, t2.v), %s, true) from t1,t2',

and_tagids, i, or_tagids, i, i, v_bit);

open res for execute sql ;

return next res;

end loop;

end;

$$ language plpgsql strict;

```

14、求USERID例子,88毫秒响应,极端速度。

```

postgres=# begin;

BEGIN

Time: 0.031 ms

postgres=# select * from get_pos_and_or(array[1,2,3], array[4,5,6], 1);

get_pos_and_or



cur1

cur2

cur3

cur4

cur5

cur6

cur7

....

cur399

cur400

(400 rows)

Time: 88.069 ms

```

获取游标值,5000万ID,仅692毫秒:

fetch 1 from cur1;
Time: 692.408 ms

15、如果我们把位置翻译放到客户端做,那么只需要获取结果BITMAP,那就更快了,224毫秒就可以获取5000万BIT走。 这块也能做成并发,每个客户端获取不同的ofid。

```
CREATE OR REPLACE FUNCTION public.get_pos_and(and_tagids integer[])
RETURNS SETOF refcursor
LANGUAGE plpgsql
STRICT
AS $function$
declare
ref refcursor[];

res refcursor;
sql text;
begin
for x in 1..400 loop
ref[x] := 'cur'||x;
end loop;

for i in 0..399 loop
res := ref[i+1];
-- sql := format('select %s, bit_posite(bit_and(v), %s, true) from t_bitmap where tagid = any (%L) and ofid=%s', i, v_bit, and_tagids, i);
sql := format('select %s, bit_and(v) from t_bitmap where tagid = any (%L) and ofid=%s', i, and_tagids, i);
open res for execute sql ;
return next res;
end loop;
end;
$function$;

postgres=# \timing
Timing is on.
postgres=# begin;
BEGIN
Time: 0.045 ms
postgres=# select get_pos_and(array_agg(id)) from generate_series(1,100) t(id);
get_pos_and



cur1
cur2
cur3
...
cur397
cur398
cur399
cur400
(400 rows)

fetch 1 from cur1;
Time: 224.776 ms
```

16、如果要求包含某标签,但是不包含某标签的用户,同样使用BIT操作即可。

例子:

```
包含b1,同时不包含b2的用户

postgres=# select b1 & bitxor(b1,b2) from (values (bit'11001100', bit'11000001')) as t(b1,b2);
?column?



00001100
(1 row)

使用这个方法,新增一个UDF即可。
```


小结

varbitx是阿里云RDS PG提供的一个插件,使用它,单个RDS PG就可以实现万亿级别USER_TAGS的实时圈选。

使用BITMAP分段、DBLINK异步查询、游标等技术,提高性能。

性能指标:

1、求COUNT,2000亿(20亿用户,100个标签组合)USER_IDS,响应速度2.6秒。

2、求USERID明细,返回5000万用户ID位置,仅692毫秒。

3、求USERID明细,如果只返回BITMAP,5000万个BIT仅需224毫秒。


参考

《阿里云RDS PostgreSQL varbitx实践 - 流式标签 (阅后即焚流式批量计算) - 万亿级,任意标签圈人,毫秒响应》

《阿里云RDS for PostgreSQL varbitx插件与实时画像应用场景介绍》

《基于 阿里云 RDS PostgreSQL 打造实时用户画像推荐系统(varbitx)》


PostgreSQL 许愿链接

您的愿望将传达给PG kernel hacker、数据库厂商等, 帮助提高数据库产品质量和功能, 说不定下一个PG版本就有您提出的功能点. 针对非常好的提议,奖励限量版PG文化衫、纪念品、贴纸、PG热门书籍等,奖品丰富,快来许愿。开不开森.


9.9元购买3个月阿里云RDS PostgreSQL实例


PostgreSQL 解决方案集合


德哥 / digoal's github - 公益是一辈子的事.

digoal's wechat


推荐阅读
  • 本文深入探讨了数据库性能优化与管理策略,通过实例分析和理论研究,详细阐述了如何有效提升数据库系统的响应速度和处理能力。文章首先介绍了数据库性能优化的基本原则和常用技术,包括索引优化、查询优化和存储管理等。接着,结合实际应用场景,讨论了如何利用容器化技术(如Docker)来部署和管理数据库,以提高系统的可扩展性和稳定性。最后,文章还提供了具体的配置示例和最佳实践,帮助读者在实际工作中更好地应用这些策略。 ... [详细]
  • 如何使用 net.sf.extjwnl.data.Word 类及其代码示例详解 ... [详细]
  • 计算 n 叉树中各节点子树的叶节点数量分析 ... [详细]
  • BZOJ4240 Gym 102082G:贪心算法与树状数组的综合应用
    BZOJ4240 Gym 102082G 题目 "有趣的家庭菜园" 结合了贪心算法和树状数组的应用,旨在解决在有限时间和内存限制下高效处理复杂数据结构的问题。通过巧妙地运用贪心策略和树状数组,该题目能够在 10 秒的时间限制和 256MB 的内存限制内,有效处理大量输入数据,实现高性能的解决方案。提交次数为 756 次,成功解决次数为 349 次,体现了该题目的挑战性和实际应用价值。 ... [详细]
  • 在Ubuntu系统中,由于预装了MySQL,因此无需额外安装。通过命令行登录MySQL时,可使用 `mysql -u root -p` 命令,并按提示输入密码。常见问题包括:1. 错误 1045 (28000):访问被拒绝,这通常是由于用户名或密码错误导致。为确保顺利连接,建议检查MySQL服务是否已启动,并确认用户名和密码的正确性。此外,还可以通过配置文件调整权限设置,以增强安全性。 ... [详细]
  • 在Hive中合理配置Map和Reduce任务的数量对于优化不同场景下的性能至关重要。本文探讨了如何控制Hive任务中的Map数量,分析了当输入数据超过128MB时是否会自动拆分,以及Map数量是否越多越好的问题。通过实际案例和实验数据,本文提供了具体的配置建议,帮助用户在不同场景下实现最佳性能。 ... [详细]
  • 本文详细解析了 MySQL 5.7.20 版本中二进制日志(binlog)崩溃恢复机制的工作流程。假设使用 InnoDB 存储引擎,并且启用了 `sync_binlog=1` 配置,文章深入探讨了在系统崩溃后如何通过 binlog 进行数据恢复,确保数据的一致性和完整性。 ... [详细]
  • Java 零基础入门:SQL Server 学习笔记(第21篇)
    Java 零基础入门:SQL Server 学习笔记(第21篇) ... [详细]
  • MySQL性能优化与调参指南【数据库管理】
    本文详细探讨了MySQL数据库的性能优化与参数调整技巧,旨在帮助数据库管理员和开发人员提升系统的运行效率。内容涵盖索引优化、查询优化、配置参数调整等方面,结合实际案例进行深入分析,提供实用的操作建议。此外,还介绍了常见的性能监控工具和方法,助力读者全面掌握MySQL性能优化的核心技能。 ... [详细]
  • PHP中元素的计量单位是什么? ... [详细]
  • 利用C#技术实现Word文档的动态生成与编辑
    本文通过一个简单的示例,介绍了如何使用C#语言实现Word文档的动态生成与编辑功能。文章详细阐述了在项目中引用Word动态库的方法,并通过具体代码示例展示了如何创建和操作Word表格。此内容旨在为初学者提供参考和学习资料,欢迎读者提出宝贵意见和建议。 ... [详细]
  • 期末Web开发综合实践项目:运用前端技术打造趣味小游戏体验
    期末Web开发综合实践项目中,学生通过运用HTML、CSS和JavaScript等前端技术,设计并实现了一款趣味性十足的小游戏。该项目不仅检验了学生对前端基础知识的掌握情况,还提升了他们的实际操作能力和创意设计水平。视频链接展示了项目的最终成果,直观呈现了游戏的互动性和视觉效果。 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • MySQL 错误:检测到死锁,在尝试获取锁时;建议重启事务(Node.js 环境)
    在 Node.js 环境中,MySQL 数据库操作时遇到了“检测到死锁,在尝试获取锁时;建议重启事务”的错误。本文将探讨该错误的原因,并提供有效的解决策略,包括事务管理优化和锁机制的理解。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
author-avatar
望空心vn
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有