背景

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 refcursorLANGUAGE plpgsqlSTRICT
AS $function$
declareref refcursor[];  res refcursor; sql text;
beginfor x in 1..400 loopref[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
-------------cur1cur2cur3
...cur397cur398cur399cur400
(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毫秒。

PgSQL · 应用案例 · 惊天性能!单RDS PostgreSQL实例支撑 2000亿相关推荐

  1. (新零售)商户网格化运营 - 阿里云RDS PostgreSQL最佳实践

    原文地址 背景 伟大的马老师说: "纯电商时代很快会结束,未来的十年.二十年,没有电子商务这一说,只有新零售这一说,也就是说线上线下和物流必须结合在一起,才能诞生真正的新零售" 线 ...

  2. 时间、空间、对象 海量极速多维检索 - 阿里云RDS PostgreSQL最佳实践

    标签 PostgreSQL , 时间 , 空间 , 对象属性 , 多维度检索 , 海量 , 空间索引 , 数据分区 , 块级索引BRIN , 多级索引 , GIN倒排索引 , JSON索引 , 多列索 ...

  3. 50倍时空算力提升,阿里云RDS PostgreSQL GPU版本上线

    2019独角兽企业重金招聘Python工程师标准>>> 2019年3月19日,阿里云RDS PostgreSQL数据库GPU规格版本正式上线,开启了RDS异构计算并行加速之路.该版本 ...

  4. Database之SQLSever:SQL命令实现的高级案例集合之单表/多表(筛选、统计个数)之详细攻略

    Database之SQLSever:SQL命令实现的高级案例集合之单表/多表(筛选.统计个数)之详细攻略 目录 一.sql实现筛选 1.单个表筛选 1.1.简单筛选 1.2.嵌套筛选

  5. NLP强者!小i机器人再入“AI最佳掘金案例年度榜单”

    12月18日,雷锋网正式揭晓 "2018 AI最佳掘金案例年度榜单",小i机器人作为AI+金融领域的杰出代表获得"最佳NLP综合应用解决方案奖",这也是小i机器 ...

  6. Spark商业案例与性能调优实战100课》第16课:商业案例之NBA篮球运动员大数据分析系统架构和实现思路

    Spark商业案例与性能调优实战100课>第16课:商业案例之NBA篮球运动员大数据分析系统架构和实现思路 http://www.basketball-reference.com/leagues ...

  7. Spark商业案例与性能调优实战100课》第2课:商业案例之通过RDD实现分析大数据电影点评系统中电影流行度分析

    Spark商业案例与性能调优实战100课>第2课:商业案例之通过RDD实现分析大数据电影点评系统中电影流行度分析 package com.dt.spark.coresimport org.apa ...

  8. 《Spark商业案例与性能调优实战100课》第17课:商业案例之NBA篮球运动员大数据分析系统代码实战

    <<<Spark商业案例与性能调优实战100课>第17课:商业案例之NBA篮球运动员大数据分析系统代码实战

  9. 《Spark商业案例与性能调优实战100课》第18课:商业案例之NBA篮球运动员大数据分析代码实战之核心基础数据项编写

    <Spark商业案例与性能调优实战100课>第18课:商业案例之NBA篮球运动员大数据分析代码实战之核心基础数据项编写

最新文章

  1. 服务模拟-ServiceMock
  2. Python基础教程:绑定方法和非绑定详细用法
  3. C++之链表中头指针、头节点、首元结点的
  4. python视频网站分类_用Python爬取b站视频
  5. 系统没有安装vc9.注意是x86 32位_Windows 软件默认安装位置之谜
  6. Jquery—JQuery对radio的操作(01)
  7. ESET NOD32 v11.0.154
  8. IE8升级到IE11 F12报错的解决方案
  9. 手写vue日历控件过程
  10. Markdown箭头总汇
  11. ★用辩证数学解答“缸中之脑”
  12. 机械动力学瑞利法matlab程序,机器人学回炉重造(4):动力学仿真(附牛顿-欧拉递归逆动力学算法matlab代码)...
  13. deepin 更新企业微信最新版本方法
  14. ATA工厂测试AT_MODE下震动不振问题分析
  15. 经典SQL语句大全(绝对的经典)----特别好
  16. matlab如何计算矩阵的幂,MATLAB矩阵幂算法
  17. b500k电位器引脚接法_收录机旋转电位器b500k,可调式电位器25k
  18. 毕设 JAVA JSP工资管理系统的设计与实现论文
  19. DevOps《凤凰项目》实战沙盘演练将亮相光环中国·2017敏捷千人峰会
  20. 【墨水屏】1.54寸墨水屏调试记录

热门文章

  1. java二维码生成-谷歌(Google.zxing)开源二维码生成学习及实例
  2. 通过CSS来垂直居中文本
  3. MonetDB heap bug?
  4. Tabs Outliner(标签系统)
  5. 在shell中获取当前机器的ip地址
  6. String 是值类型还是引用类型
  7. 电脑重启 硬件问题引发的人品问题
  8. 用GitHub Copilot 生成的项目中,40%会引入漏洞
  9. 聚焦BCS|新华财经:奇安信董事长齐向东:网络安全市场规模10年将增长10倍
  10. 坚守本心,你公司的OA解决了以下问题吗?