Skip to content

数据模型

white_shiro_bai edited this page Sep 17, 2024 · 15 revisions

模型介绍

ghost_sa 的目标用户群是DAU小于10万或每日数据量在1亿条以下的小微产品团队,整个数据模型和架构都是为了适应这些特点进行设计的。

这样的团队具有如下特点:

1.对成本敏感,不具备在物理上隔离开发测试生产环境的资源,也没有专职DBA或数据开发团队可以维护成套的大数据基础架构,对数据容量也较为敏感。

2.产品本身没有跑出来,总结不出稳定的用户和增长模型,存在随着业务探索,模型需要跟着业务灵活变化的需求。同时还有多个产品在一起跑,存在资源共享的情况。每一条数据都格外宝贵。

3.护城河浅,不能承受薅羊毛或竞品的技术攻击。又需要大量推广的动作,又雇不起具有技术背景的运营和市场。需要All in One的技术一体解决方案。

4.数据量小,对性能需求低。

所以ghost_sa在模型设计时,在成本,灵活性,性能之间,优先选择了成本和灵活性。性能从技术视角上是比较弱的。

功能表设计逻辑

1.project_list是整个项目的名单。shortcut系列可以脱离项目独立运行。access_control,recall_blacklist,status_code 是为了项目之间可以共享资源,scheduler_jobs 是为了可以在各项目之间统一协调资源。

2.所有功能表都具备记录项目的字段。在需要进行隔离的时候,可以从业务层面进行隔离。在不许要隔离的时候,可以一次查询一个索引解决问题。降低数据库压力。

项目表设计逻辑

1.{project},{project}_device,{project}_user,{project}_properties 四张表完成了跟踪的基本逻辑,这也是与神策官方列存储模式不同,实现低成本与灵活性的关键。

2.usergroup系列表都是用户分群的表,需要独立运行scheduler服务才能使用。

3.noti系列表都是推送相关的表,需要配置发送渠道并独立运行messenger服务。

Q&A:

Q:为什么神策是一张event表,鬼策的event表去哪了?

A:ghost_sa的{project}与神策的event表功能一致。区别在于存储方式,ghost_sa是行存储json存信息,神策是列存储大宽表。双方各自用适应自己库特性的方式节约存储空间。ghost_sa没有神策字段一旦确定,更改需要找客户技术支持的问题。当然,也失去了神策对字段类型的校验。换回来的是在不同的事件里,可以使用相同的字段名。更符合神策,事件,动作,属性的埋点模型。

Q:为什么神策就一张user表。ghost_sa增加了一个device表?

A:神策的user表实际上是ghost_sa的user表,拼接了device表。ghost_sa选择拆开,主要考虑了如下方面: 1.神策模型上提到的局限,通过user和device表拆开,可以低效率的解决。而不是不能解决。

2.处于1的理由,ghost_sa不预先洗好数据,这与ghost_sa的目标用户特性有关,这也是ghost_sa解决1提到局限的方法。 一旦用户不选择device作为分析路径,选择与event表做关联进行唯一性识别。user表的体积通常是device表的1/50。虽然这对分布式数据库来说影响不大,不过关联查询时仍然可以提高性能。

3.user表与device表或event表配合,可以灵活的区分设备视角或用户视角的分析。

Q:为什么user表的主键是区分lib的,但是device表的主键只有distinct_id?那用户更换设备之后不是找不到历史信息了?

A:回答这个问题我们需要先理解神策SDK的数据上报内容。为了方便理解,我们简化神策SDK上报的内容为distinct_id,lib,数据。其中,distinct_id在用户未登录的时候,使用的是匿名id,在用户登陆后(触发login事件),会使用用户的登录id。lib是上报数据的SDK名称。数据就是上报的具体内容,比如device表里的其他信息(其他信息的更新逻辑可以参考新增参数及设置指南,根据不同的配置,会有不同的更新逻辑以适应业务场景)。 我们假设用户A有2台手机M1,M2,2台电脑P1,P2。他们依次登录你的产品。那么device表和user表的记录如下。

device表:

distinct_id lib 数据 created_at updated_at
M1 iOS 根据device策略不同有差异 M1第一次出现的时间 M1登录前最后一个动作的时间
A js 根据device策略不同有差异 A第一次登录的时间 A最后一个动作的时间
M2 iOS 根据device策略不同有差异 M2第一次出现的时间 M2登录前最后一个动作的时间
P1 js 根据device策略不同有差异 P1第一次出现的时间 P1登录前最后一个动作的时间
p2 js 根据device策略不同有差异 P2第一次出现的时间 P2登录前最后一个动作的时间

user表:

distinct_id lib original_id all_profile_json created_at updated_at
A iOS M1 {} M1第一次登录的时间 M1最后一次登录的时间
A iOS M2 {} M2第一次登录的时间 M2最后一次登录的时间
A iOS {iOS平台上A的用户信息} iOS上首次更新用户信息的时间 iOS上最后一次更新用户信息的时间
A js P1 {} P1第一次登录的时间 P1最后一次登录的时间
A js P2 {} P2第一次登录的时间 P2最后一次登录的时间
A js {js平台上A的用户信息} js上首次更新用户信息的时间 js上最后一次更新用户信息的时间

因为神策SDK在关联用户信息的时候,并不传递original_id,所以如果产品支持多端登录,无法判断信息是哪台设备更新的。只能根据判断到用户和lib。

通过这两张表,几乎可以在不查询event表的情况下,快速获得用户关键时间点的各种信息,如:

场景 目的 方法
用户全平台首次出现时间和信息 找全平台首次出现时间点 user表根据created_at查到M1,再device表里查created_at和不带latest的信息。
用户首次登录时间和信息 找全平台首次激活的时间 device表created_at和不带latest的信息。
用户最后产品使用时间和信息 用户生命周期管理 device表updated_at和latest开头的信息。
用户最后一次登录的时间 产品登录策略设计,风控 user表updated_at。
用户登录设备数量 风控,如VIP账号出租 user表根据distinct_id数original_id去重。
同设备登录多个用户 风控,如刷量、爬虫 user表根据original_id数disticnt_id去重。
用户换机频率 用户使用习惯,产品定位,推广间隔 user表根据distinct_id或original_id算created_at的间隔。
账号异常登录 风控,如账号被盗 user表根据distinct_id找到original_id,再到device表里找ip_city。如果device.updated最大的设备或当前distinct_id,与其他设备的ip_city中城市不一致,则账号异常登录可能性

Q:device表的内容更新机制是什么?

A:参考新增参数及设置指南

Q:event表既没有主键也没有独立索引,如何避免重复

A:参考去重原理,在去重的同时,保留全部信息,实现对去重效果的review,在神策调整sdk策略后,能及时发现差异,跟进迭代。

功能表(共用)

project_list <--项目列表,使用setup.py安装新项目时会自动创建,并增加条目。如果已存在,只增加条目。记录项目的创建日期和过期日期。过期的项目上报的数据会被丢弃(还没加这个功能)。项目兜底的access_control阈值也在这里维护。项目是否开启定时器任务也在这里控制。

shortcut <--短链列表,短链创建和解析依赖此表

shortcut_history <--短链解析历史,记录短链解析的记录和是否成功。

shortcut_read <--站外阅读记录,使用二维码生成接口显示二维码和站外跟踪图片加载跟踪图片时。会在这个表产生记录。

access_control <--接入控制存储的列表。在列表里的,达到条件后可以被接口抓到。

mobile_ad_list <--第三方广告跟踪创建的地址(用户自行创建后会在这里更新)

mobile_ad_src <--第三方广告跟踪的源地址(适配后会在这里更新)

recall_blacklist <--召回列表的分级控制黑名单。提供全局或限定的黑名单。降低整体召回成本和投诉风险。

recall_blacklist_history <--召回黑名单被命中的事件(可以解释没推送的原因,也可以用来算给公司降低了多少风险,你懂的)

recall_blacklist_reason <--召回黑名单入选原因(黑名单增加,修改的记录)

scheduler_jobs <--定时任务列表,支持优先级调度

status_code <--状态码字典

项目表(每个项目独立)

{project} <--埋点数据,每上报一条,都写入这个表。功能等同于神策的events表

{project}_device <--设备表,每上报一条数据,会根据数据里的内容更新表里的信息。上报的distinct_id和lib作为主键,同一个distinct_id在一个sdk里唯一。

{project}_user <--用户关联表,神策SDK里用了Profile相关操作时,会触发更新本表。distinct_id,lib,map_id,original_id作为主键,映射记录唯一。功能等同于神策的users表,并可以解决神策不能多对多关联的问题。配合device表使用,可以实现神策的users表的筛选功能。

{project}_properties <--埋点属性表,该表可以通过configs/admin.py里的use_properties值控制是否启用(默认开启)。该表用来存储每个event都包含多少个属性。这个对使用全埋点的用户比较有用,因为全埋是由神策SDK创建的字段。而自己设计埋点的用户,因为有埋点表,这个功能意义不大,可以关闭该表降低数据库负载。

{project}_usergroup_plan <--用户分群。scheduler程序会根据project表的开关,读取这个表,来生成计划任务到scheduler_jobs

{project}_usergroup_list <--用户分群列表。scheduler执行plan后,会创建list,并写入具体data

{project}_usergroup_data <--用户分群详情。scheduler执行后写入的具体数据。应用分群时也是取这一层。

{project}_noti_temple <--消息模板。该项目支持的推送模板

{project}_noti_group <--调用套模板方法后。无论是scheduler触发,trigger触发,还是通过接口触发套模板。都会先创建一个推送组,再写入推送数据。

{project}_noti <--具体的推送消息。messenger程序会逐条检查未发送的消息。确定到达定时后,会逐条发送(支持优先级管理)。