您可以通过RDS PostgreSQL 12实现实时精准营销,快速圈选目标用户。

背景信息

几乎所有行业(如互联网、游戏、教育等)都有实时精准营销的需求。通过系统生成用户画像,在营销时通过条件组合筛选用户,快速提取目标群体,例如:

  • 电商行业中,商家在运营活动前,需要根据活动的目标群体的特征,圈选出一批目标用户进行广告推送或进行活动条件的判断。
  • 游戏行业中,商家需要根据玩家的某些特征进行圈选,针对性地发放大礼包,提高玩家活跃度。
  • 教育行业中,需要根据学生不同的特征,推送有针对性的习题,帮助学生查缺补漏。
  • 搜索、门户、视频网站等业务中,根据用户的关注热点,推送不同的内容。

这些行业都有一些共同的业务痛点:

  • 数据量庞大,运算量也极大。
  • 用户标签多、字段多,占用存储空间也多。
  • 字段多,可能会超过数据库的字段数限制,一般数据库最多支持1000多个字段。
  • 使用数组替代多字段存储标签,需要数据库支持倒排索引,但并不是所有数据库都支持倒排索引。
  • 使用数组替代多字段存储标签,加上倒排索引,存储空间会暴增。
  • 圈选条件组合多样化,没有固定索引可以优化,如果每个字段一个索引,存储空间又会暴增。
  • 性能要求高,因为实时营销要求秒级响应。
  • 数据更新时效要求高,用户画像要求近实时的更新,否则可能圈选到不精准用户(例如用户昨天在浏览手机,昨晚已下单,但是数据未实时更新,那么手机卖家圈选时这个用户就会被选中,但是该用户实际上已经不是目标群体)。

常见的产品(如MySQL)无法在有限的资源下满足实时圈选目标群体的需求。

本文介绍如何基于RDS PostgreSQL实现实时精准营销,您可以根据业务需要使用三种方案之一。

准备工作

方案一

说明 PostgreSQL和MySQL都支持方案一。

表结构如下:

  1. KEY: 用户ID
  2. 标签1:
  3. 标签2:
  4. ...
  5. 标签N:

索引如下:

  1. 每个标签字段一个索引

搜索方法如下:

  1. and , or , not 组合
  2. where 标签a and 标签b and ...

方案缺陷

  • 用户标签多、字段多,占用存储空间也多。
  • 字段多,可能会超过数据库的字段数限制,一般数据库最多支持1000多个字段。也可以设置为多对多的结构,每个标签一条记录,解决字段数限制的问题。
  • 圈选条件组合多样化,没有固定索引可以优化,如果每个字段一个索引,存储空间又会暴增。
  • 新增一个用户群体标签时,需要更新大量数据。
  • 查询性能差。

操作示例

  • 创建人群表,每条记录代表一个人群。示例如下:
  1. create table t_tag_dict (
  2. tag int primary key, -- 标签(人群)id
  3. info text, -- 人群描述
  4. crt_time timestamp -- 时间
  5. );
  • 生成10万个人群标签。示例如下:
  1. insert into t_tag_dict values (1, '男', now());
  2. insert into t_tag_dict values (2, '女', now());
  3. insert into t_tag_dict values (3, '大于24岁', now());
  4. -- ...
  5. insert into t_tag_dict
  6. select generate_series(4,100000), md5(random()::text), clock_timestamp();
  • 创建用户画像表,每个用户N条记录,每条记录代表这个用户贴的某个标签。示例如下:
  1. create table t_user_tag (
  2. uid int8, -- 用户id
  3. tag int, -- 用户对应标签(人群)
  4. mod_time timestamp, -- 时间
  5. primary key (tag,uid)
  6. );
  • 给2000万个用户打标,每个用户64个随机标签,其中男、女各一半,共12.8亿条记录。示例如下:
  1. create or replace function gen_rand_tag(int,int) returns setof int as
  2. $$
  3. select case when random() > 0.5 then 1::int else 2::int end as tag
  4. union all
  5. select ceil(random()*$1)::int as tag from generate_series(1,$2);
  6. $$ language sql strict volatile;
  7. insert into t_user_tag
  8. select uid, gen_rand_tag(100000,63) as tag, clock_timestamp()
  9. from generate_series(1,20000000) as uid on conflict (uid,tag) do nothing;
  10. -- 或使用如下方法加速导入
  11. create sequence seq;
  12. vi test.sql
  13. insert into t_user_tag
  14. select uid, gen_rand_tag(100000,63) as tag, clock_timestamp()
  15. from nextval('seq'::regclass) as uid
  16. on conflict(tag,uid) do nothing;
  17. pgbench -M prepared -n -r -P 1 -f ./test.sql -c 50 -j 50 -t 400000
  • 查询包含标签1和3的人群。示例如下:
  1. 1、人群数量
  2. select count(*) from
  3. (
  4. select uid from t_user_tag where tag=1
  5. intersect
  6. select uid from t_user_tag where tag=3
  7. ) t;
  8. -- Time: 1494.789 ms (00:01.495)
  9. 2、提取人群ID
  10. select uid from t_user_tag where tag=1
  11. intersect
  12. select uid from t_user_tag where tag=3;
  13. -- Time: 3246.184 ms (00:03.246)
  • 查询包含标签1、3、10或200的人群。示例如下:
  1. 1、人群数量
  2. select count(*) from
  3. (
  4. select uid from t_user_tag where tag=1
  5. union
  6. select uid from t_user_tag where tag=3
  7. union
  8. select uid from t_user_tag where tag=10
  9. union
  10. select uid from t_user_tag where tag=200
  11. ) t;
  12. -- Time: 3577.714 ms (00:03.578)
  13. 2、提取人群ID
  14. select uid from t_user_tag where tag=1
  15. union
  16. select uid from t_user_tag where tag=3
  17. union
  18. select uid from t_user_tag where tag=10
  19. union
  20. select uid from t_user_tag where tag=200;
  21. -- Time: 5682.458 ms (00:05.682)

方案二

说明 仅PostgreSQL支持方案二。MySQL不支持数组类型、倒排索引。

表结构如下:

  1. KEY:用户ID
  2. VALUES:标签数组

索引如下:

  1. 标签数组字段: GIN倒排索引

搜索方法如下:

  1. 与、或、非
  2. where VALUES @> array[标签s] --
  3. where VALUES && array[标签s] --
  4. where not VALUES @> array[标签s] --

方案缺陷

  • 使用数组替代多字段存储标签,需要数据库支持倒排索引,但并不是所有数据库都支持倒排索引。
  • 使用数组替代多字段存储标签,加上倒排索引,存储空间会暴增。
  • 新增一个用户群体标签时,需要更新大量数据。

操作示例

  • 创建人群表,每条记录代表一个人群。示例如下:
  1. create table t_tag_dict (
  2. tag int primary key, -- 标签(人群)id
  3. info text, -- 人群描述
  4. crt_time timestamp -- 时间
  5. );
  • 生成10万个人群标签。示例如下:
  1. insert into t_tag_dict values (1, '男', now());
  2. insert into t_tag_dict values (2, '女', now());
  3. insert into t_tag_dict values (3, '大于24岁', now());
  4. -- ...
  5. insert into t_tag_dict
  6. select generate_series(4,100000), md5(random()::text), clock_timestamp();
  • 创建用户画像表,每个用户一条记录,用数组表示这个用户归属哪些标签。示例如下:
  1. create table t_user_tags (
  2. uid int8 primary key, -- 用户id
  3. tags int[], -- 用户标签(人群)数组
  4. mod_time timestamp -- 时间
  5. );
  • 创建随机生成打标数组的函数。示例如下:
  1. create or replace function gen_rand_tags(int,int) returns int[] as $$
  2. select array_agg(ceil(random()*$1)::int) from generate_series(1,$2);
  3. $$ language sql strict;
  • 在10万个标签内随机提取8个标签。示例如下:
  1. select gen_rand_tags(100000, 8);
  2. gen_rand_tags
  3. ---------------------------------------------------
  4. {43494,46038,74102,25308,99129,40893,33653,29690}
  5. (1 row)
  • 给2000万个用户打标,每个用户64个随机标签,其中男、女各一半。示例如下:
  1. insert into t_user_tags
  2. select generate_series(1,10000000),
  3. array_append(gen_rand_tags(100000, 63),1), now();
  4. insert into t_user_tags
  5. select generate_series(10000001,20000000),
  6. array_append(gen_rand_tags(100000, 63),2), now();
  • 创建人群标签字段倒排索引。示例如下:
  1. create index idx_t_user_tags_1 on t_user_tags using gin (tags);
  • 查询包含标签1和3的人群。示例如下:
  1. 1、人群数量
  2. select count(uid) from t_user_tags where tags @> array[1,3];
  3. 2、提取人群ID
  4. select uid from t_user_tags where tags @> array[1,3];
  • 查询包含标签1、3、10或200的人群。示例如下:
  1. 1、人群数量
  2. select count(uid) from t_user_tags where tags && array[1,3,10,200];
  3. 2、提取人群ID
  4. select uid from t_user_tags where tags && array[1,3,10,200];

方案三

说明 仅PostgreSQL支持方案三。MySQL不支持位图功能。

方案三通过roaringbitmap插件实现快速查询,相关说明请参见使用roaringbitmap插件

表结构如下:

  1. KEY:标签ID
  2. VALUES: 用户bitmap

索引如下:

  1. 标签ID字段: Btree索引

搜索方法如下:

  1. 聚合bitmap: 与、或、非
  2. and_agg(bitmaps) where KEY in (标签s) --
  3. or_agg(bitmaps) where KEY in (标签s) --
  4. except(bitmap1,bitmap2) --
  • 方案优势
    • 表存储占用空间小。
    • 索引存储占用空间小。仅需一个Btree索引,索引记录数少(有多少标签,就有多少条记录,通常标签数在百万以内)。
    • 新增一个用户群体标签时,不需要更新大量数据,仅需新增一条人群的bitmap记录。
    • 查询性能极好。
  • 方案缺陷
    • bitmap最大长度为1GB,用户数超过长度时需要使用offset,方法如下:
  1. offset0_bitmap, offset1gb_bitmap, ...
  • 用户ID需要是数字(建议连续数值),如果没有数值型UID,需要建立映射表。

操作示例

说明

  • 安装插件。示例如下:
  1. create extension roaringbitmap;
  • 创建用户标签bitmap表。示例如下:
  1. create table t_tag_users (
  2. tagid int primary key, -- 用户标签(人群)id
  3. uid_offset int, -- 由于useridint8类型,roaringbitmap内部使用int4存储,需要转换一下。
  4. userbits roaringbitmap, -- 用户id聚合的 bitmap
  5. mod_time timestamp -- 时间
  6. );
  • 生成UID标签bitmap表。示例如下:
  1. insert into t_tag_users
  2. select tagid, uid_offset, rb_build_agg(uid::int) as userbits from
  3. (
  4. select
  5. unnest(tags) as tagid,
  6. (uid / (2^31)::int8) as uid_offset,
  7. mod(uid, (2^31)::int8) as uid
  8. from t_user_tags
  9. ) t
  10. group by tagid, uid_offset;
  • 查询包含标签1和3的人群。示例如下:
  1. 1、人群数量
  2. select sum(ub) from
  3. (
  4. select uid_offset,rb_and_cardinality_agg(userbits) as ub
  5. from t_tag_users
  6. where tagid in (1,3)
  7. group by uid_offset
  8. ) t;
  9. 2、提取人群ID
  10. select uid_offset,rb_and_agg(userbits) as ub
  11. from t_tag_users
  12. where tagid in (1,3)
  13. group by uid_offset;
  • 查询包含标签1、3、10或200的人群。示例如下:
  1. 1、人群数量
  2. select sum(ub) from
  3. (
  4. select uid_offset,rb_or_cardinality_agg(userbits) as ub
  5. from t_tag_users
  6. where tagid in (1,3,10,200)
  7. group by uid_offset
  8. ) t;
  9. 2、提取人群ID
  10. select uid_offset,rb_or_agg(userbits) as ub
  11. from t_tag_users
  12. where tagid in (1,3,10,200)
  13. group by uid_offset;

方案对比

对比项方案一
(MySQL/PostgreSQL)
方案二
(PostgreSQL)
方案三
(PostgreSQL)
方案三相比方案一的提升
与查询圈选用户速度1.5秒0.042秒0.0015秒99900%
或查询圈选用户速度3.6秒3秒0.0017秒211665%
空间占用(表)63,488 MB3,126 MB1390MB4467%
空间占用(索引)62,464 MB3139 MB2MB3123100%
创建索引速度-20分钟极快(约为0秒)-

说明 方案使用的MySQL 8.0和PostgreSQL 12实例规格均为8核CPU、32GB内存、1500GB ESSD云盘。

总结

RDS PostgreSQL 12支持位图功能(roaringbitmap插件),可以高效生成、压缩、解析位图数据,支持最常见的位图聚合操作(与、或、非、异或),满足您在亿级以上用户、千万级标签的大数据量下实时精准营销、快速圈选用户的需求。

相比MySQL的方案,RDS PostgreSQL方案优势非常明显,是一个低成本、高效率的解决方案。

视频教程

实时精准营销

优惠活动

新用户9.9元试用、老用户5折升级