Doris 画像标签存储实践

根据画像标签的需求场景,我们常常将画像存储分为两部份,分别是:

  1. 画像基本信息的存储
  2. 用户画像人群的筛选需求的存储

常见画像标签存储方式:

  1. 根据类目创建宽表,或者根据更新的频率创建宽表
  2. 创建竖表-每个用户+每个标签=一条记录
  3. 竖表+横表=》分开计算,定时聚合
  4. ES 标签对象存储,rowKey为user_id,HBASE存储用户明细,通过user_id关联
  5. 倒排表,标签-》多个用户Id,bitmap方案

一、根据类目创建宽表,或者根据更新的频率创建宽表

1. 这是最容易想到的方式,同时也是最直观的方式,比如有基本信息表,兴趣爱好表
user_id 姓名 性别
1 老五
2 老四
user_id 购房意向 意向户型
1 50-100m
2 200-300m
优点:
  1. 够直观,简单,结构清晰,数据量可控
缺点:
  1. 标签的增加需要修改表结构 — 只要能确定标签的范围,那可以省略这个问题
  2. 存储一定冗余,会存在有些字段无值的场景
  3. 计算不友好,在同一表中的各标签值计算时间和规则是不相同的,这些标签值的来源常常来源于各实时计算,或离线计算程序,或者是有的每天计算一次,比如日活跃,有的是一周计算一次,比如周活跃
  4. 画像信息存储只是过程,最终的需求是要能够任意维度标签的快速查询,而在二维性数据库中,你无法对所有字段建立索引,人群筛选场景下查询效率将会极低

二、创建竖表-每个用户+每个标签=一条记录

  1. 这种方式应用于标签值不固定的场景,可以理解为宽表的空间换时间方案
user_id 标签类型 标签值
1 性别
1 年龄 15
2 性别
优点:
  1. 易于扩展,适用于标签不固定场景
  2. 存储无冗余,只存有值的标签项
  3. 计算友好,每一行记录都是一个单独的计算项
  4. 可以对标签类型+标签值建立联合索引,任意维度查询的场景性能会比较高
缺点:
  1. 数据量巨大,100万用户,每个用户100个标签,将会产生1亿条数据
  2. 查询不友好,比如我要统计性别=男,年龄=15的人,需要这样写:
    select user_id from user_tags where (tag='sex' and tag_value='男') and (tag='age' and tag_value='15')

三、竖表+横表=》分开计算,定时聚合

  1. 通过竖表完成各统计项的计算,以及任意标签维度查询
  2. 定时将这些离散的数据汇聚到宽表中,用于用户画像基本信息的展示
  3. 结合,第一种和第二种,能够满足,用户画像自定义维度的查询,也能够满足用户标签信息的展示
  4. 除了数据量问题,没有其他问题
user_id 标签类型 标签值
1 性别
1 年龄 15
2 性别

定义汇聚为:

user_id 姓名 性别
1 老五
2 老四

四、ES 标签对象存储,rowKey为user_id,HBASE存储用户明细,通过user_id关联

  1. 早期的大数据画像存储方案
  2. es的每一条记录为一个文档,同时也为一个用户的画像信息,rowKey为user_id
  3. 人群筛选时,通过es查询出匹配的user_id集合,然后去Hbase中查询这批集合的明细,能够实现秒级的响应

五、倒排表,标签-》多个用户Id,bitmap方案

  1. ES搜索之所以快,功劳在于他的倒排索引上,所以又有了以下方案:
    常见的描述是用户身上有哪些属性,而倒排索引是这个属性有哪些用户拥有
标签类型 标签值 user_ids
性别 1,2
性别 3
  1. 对标签类型+标签值建立索引,user_ids字段中存储所有拥有这个标签的用户Id
  2. user_ids字段为bitmap类型,上面为了直观,显示bitmap转换前的用户Id更多bitmap描述见:https://www.sohu.com/a/300039010_114877
  3. 在对人群筛选时,只需要对所有匹配的结果取user_ids的交集即可

优点

  1. 在人群筛选上,是五种方案中的最优解
  2. 因为用bitmap存储,所以占用空间少,100个标签,每个标签5个标签值,只有500条记录
  3. 匹配条件为二进制的与或非计算,效率很高,

缺点

  1. 更新困难,需要按照一定的时间周期来全局刷新

Doris提供了这样的支持,(其他OLAP大多也支持类似的功能,但Doris调研来看部暑扩展是比较容易的),bitmap函数,下面演示Doris中的使用

  1. 表结构创建
	CREATE TABLE `user_tags_bitmap`(
	`tag` varchar(45) NULL COMMENT "",
	`tag_value` varchar(45) NULL COMMENT "",
	`user_ids` bitmap BITMAP_UNION NULL COMMENT ""  //bitmap_union 该标签值用户集合的并集
	) ENGINE=OLAP
	AGGREGATE KEY(`tag`,`tag_value`)
	COMMENT "OLAP"
	DISTRIBUTED BY HASH(`tag`) BUCKETS 2;
  1. 添加数据
insert into user_tags_bitmap(tag, tag_value, user_ids) values("性别","男",to_bitmap("1"));
insert into user_tags_bitmap(tag, tag_value, user_ids) values("性别","男",to_bitmap("2"));
insert into user_tags_bitmap(tag, tag_value, user_ids) values("性别","女",to_bitmap("3"));
insert into user_tags_bitmap(tag, tag_value, user_ids) values("年龄","90后",to_bitmap("1"));
  1. 查询导入结果:select tag,tag_value,bitmap_to_string(user_ids) user_ids from user_tags_bitmap; user_ids已经完成了自动聚合
tag tag_value user_ids
性别 1,2
性别 3
年龄 90后 1
  1. 比如查询年龄=90后并且性别=男的用户有哪些,可以这样写:
select 
bitmap_to_string(intersect_count(user_id, tag_value, '男', '90后'))
from user_tags_bitmap
where (tag_type='性别' and tag_value='男')
or (tag_type='年龄' and tag_value='90后')

查询结果如下:

user_ids
1
  1. 这样的设计查询效率虽然高,存储也省空间,但还缺少标签的更新能力,因此设计添加明细表,通过周期性的明细表更新到这张bitmap表,明细表设计如下:
CREATE TABLE `user_tags`(
    `user_id` varchar(45) null comment "",
    `tag` varchar(45) NULL COMMENT "",
    `tag_value` varchar(45) NULL COMMENT "",
    `update_time` datetime null
) ENGINE=OLAP
DUPLICATE KEY(`user_id`,`tag`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`user_id`) BUCKETS 2;
user_id tag tag_value update_time
1 性别 2020-10-10 10:10:10
2 性别 2020-10-10 10:10:10
1 年龄 15 2020-10-10 10:10:10
  1. 明细表这里按照竖表设计,没有用宽表,因为doris的更新策略为整行替换,宽表某字段的更新会出现并发和性能问题,而在这里竖表中更新用户的某个标签属性时,是先执行delete操作,再执行insert完成更新或删除操作
//删除历史记录
delete from user_tags where user_id=1 and tag='性别';
//添加新的记录
insert into user_tags(user_id, tag, tag_value, update_time) values("1","性别","女",CURDATE());
  1. 再周期性,比如一天,将明细表的数据更新到bitmap表中,完成标签的更新操作,定期执行以下操作
//清空bitmap表
truncate table user_tags_bitmap;
//将明细表导入到bitmap表中
insert into user_tags_bitmap(tag, tag_value, user_ids) SELECT tag,tag_value,to_bitmap(user_id) from user_tags;
  1. 使用这种模式也能够完成人群与标签的交并差集筛选,像这样,把某一类人群,当作一个标签处理:
tag tag_value user_ids
性别 1,2
性别 3
年龄 15 1
人群 高意向 1,2

要求:高意向中排除性别为女的人群有哪些,那就可以 (人群=高意向)\(性别=女)两个的差集

以上完成Doris用户画像标签存储

优化:

  1. 因为使用的bitmap,如果user_id分布稀疏,将会浪费比较大的存储和计算,建议生成全局用户自增id的字典表,映射到原始用户id
  2. 标签和枚举类型的标签值,建议也创建枚举字典表
  3. Doris支持的物化视图特性,可以使得bitmap的聚合表不需要手动维护,只需要在聚合表中建立相应的物化视图即可

你可能感兴趣的:(数据中台,数据库,elasticsearch,big,data,doris)