23-标签体系.md 14 KB

标签体系

本文档记录 poyee-data-warehouse TDM 层(主题域模型层)的标签架构设计。 TDM 层采用长表(明细)+ 宽表(服务)+ 人群包三级结构,按实体类型分表。

标签体系与指标体系是独立的两套体系:指标回答"多少"(度量 + 聚合,住在 DWS/ADS),标签回答"是什么/哪一类"(分类 + 圈选,住在 TDM)。两者消费者不同、更新模式不同、计算逻辑不同,分开设计、各自演进。

1. 整体架构

DWD / DWS(事实 + 维度)
        |
        v
  +---------------------------------------------+
  |  TDM 标签明细长表(EAV)                      |
  |  tdm_usr_tag_ful_d                           |
  |  tdm_prd_tag_ful_d                           |
  |  tdm_shp_tag_ful_d                           |
  |  所有标签纵向存放,灵活扩展,不改表结构         |
  +--------------+------------------------------+
                 | pivot 高频核心标签
                 v
  +---------------------------------------------+
  |  TDM 核心标签宽表                              |
  |  tdm_usr_profile_ful_d                       |
  |  tdm_prd_profile_ful_d                       |
  |  tdm_shp_profile_ful_d                       |
  |  高频标签横向展开,下游 ADS / BI 直接 JOIN      |
  +--------------+------------------------------+
                 |
        +--------+--------+
        v                 v
  +-----------+    +---------------------+
  |  ADS 层    |    |  TDM 人群包          |
  |  主题标签   |    |  tdm_usr_crowd_ful_d |
  |  宽表/报表  |    |  圈选 + 交叉计算     |
  +-----------+    +---------------------+

设计原则

  • 实体类型分表(用户/商品/商家),不按标签主题拆表
  • 长表负责全量明细存储,宽表负责高频服务,人群包负责圈选计算
  • 前期只用 Hive 做离线标签计算,不引入额外组件

2. 标签明细长表(EAV 结构)

每个实体类型一张长表,所有标签值纵向存放。新增标签只需插入新行,不改表结构。

表清单

表名 实体 更新策略
tdm_usr_tag_ful_d 用户 全量重刷,日
tdm_prd_tag_ful_d 商品 全量重刷,日
tdm_shp_tag_ful_d 商家 全量重刷,日

典型字段结构

字段 类型 说明
entity_id BIGINT 实体 ID(user_id / product_id / shop_id)
tag_code STRING 标签编码(全局唯一,如 usr_genderusr_pref_sport
tag_value STRING 标签值(统一 STRING,下游按需转型)
tag_type STRING 标签类型(categorical / numeric / boolean / json
confidence DECIMAL(5,4) 置信度(0~1,规则标签填 1.0,模型标签填模型输出)
etl_time TIMESTAMP ETL 写入时间
dt STRING 分区日期

优势

  • 新增标签零 DDL 变更,只需在标签计算 SQL 中增加 INSERT 行
  • 所有标签统一口径管理,便于建设标签字典元数据
  • 支持按 tag_code 灵活查询任意标签组合

3. 核心标签宽表

从长表 pivot 高频、核心标签为列,供下游 ADS 和 BI 直接 JOIN,避免每次都对长表做条件聚合。

表清单

表名 实体 更新策略
tdm_usr_profile_ful_d 用户 全量重刷,日
tdm_prd_profile_ful_d 商品 全量重刷,日
tdm_shp_profile_ful_d 商家 全量重刷,日

用户宽表候选字段(示例)

字段 类型 来源标签
user_id BIGINT 主键
gender STRING usr_gender
age INT usr_age
city STRING usr_city
reg_days INT usr_reg_days
pref_sport STRING usr_pref_sport(Top 1 偏好运动)
pref_product STRING usr_pref_product(Top 1 偏好商品)
user_lvl STRING usr_value_level(高/中/低价值)
is_churn_risk BOOLEAN usr_churn_risk
etl_time TIMESTAMP ETL 写入时间
dt STRING 分区日期

长表 -> 宽表的 ETL 模式

-- 从长表 pivot 到宽表(Spark SQL CASE WHEN 模式)
INSERT OVERWRITE TABLE tdm.tdm_usr_profile_ful_d PARTITION (dt='${dt}')
SELECT
    entity_id AS user_id
    ,MAX(CASE WHEN tag_code = 'usr_gender' THEN tag_value END) AS gender
    ,MAX(CASE WHEN tag_code = 'usr_age' THEN CAST(tag_value AS INT) END) AS age
    ,MAX(CASE WHEN tag_code = 'usr_city' THEN tag_value END) AS city
    -- ... 其他核心标签
    ,CURRENT_TIMESTAMP() AS etl_time
FROM tdm.tdm_usr_tag_ful_d
WHERE dt = '${dt}'
GROUP BY entity_id
;

宽表字段准入标准

  • 下游 >= 2 个 ADS 表或 BI 看板引用
  • 标签计算口径已稳定(不处于频繁调整期)
  • 非核心 / 低频标签保留在长表,按需从长表取

4. 人群包

以圈选规则为主键,存储实体 ID 集合,支持人群圈选和二次计算(交集、并集、差集)。

阶段 1 为单表设计:所有人群包存入一张表 tdm_usr_crowd_ful_d,每行一个人群(crowd_code 区分)。阶段 2+ 存储格式从 ARRAY<BIGINT> 升级为 BINARY(bitmap),但表结构不变。详见 §6 演进路线。

5. 标签字典

所有 tag_code 必须在标签字典中注册后才能写入长表,确保编码唯一、口径明确。

字段 说明
tag_code 全局唯一编码,格式 {实体}_{标签名}(如 usr_genderprd_price_band
tag_name 中文名称
entity_type usr / prd / shp
tag_type categorical / numeric / boolean / json
compute_logic 计算口径(SQL 或自然语言)
source_table 上游依赖表
update_cycle 更新周期
owner 负责人
is_core 是否进入宽表

标签字典前期用 Docmost / 文档维护,后期可建元数据管理模块。

6. 标签服务演进路线

前期不引入新组件,Hive 承担离线标签计算的全部职责。后续按实际瓶颈逐阶段引入新能力,不超前建设。

graph TB
    subgraph "阶段 1 - Hive 7 表"
        L1[3 长表 + 3 宽表]
        C1["1 人群包<br/>ARRAY BIGINT 存 ID 列表"]
    end

    subgraph "阶段 2 - Bitmap UDF"
        C2["人群包改用 BINARY<br/>RoaringBitmap 序列化<br/>自研 UDF 做交并差"]
    end

    subgraph "阶段 3 - HBase 在线服务"
        HB["宽表同步至 HBase<br/>毫秒级单实体标签查询"]
    end

    subgraph "阶段 4 - ClickHouse 人群计算"
        CK["人群包迁移至 ClickHouse<br/>原生 bitmap 函数<br/>高性能交叉计算"]
    end

    subgraph "阶段 5 - ES 检索型画像"
        ES["宽表同步至 ES<br/>多条件组合筛选<br/>模糊匹配画像检索"]
    end

    L1 --> C1
    C1 -->|"人群包 >50 个<br/>或交叉计算 >3 层"| C2
    C2 -->|"业务需要实时<br/>标签查询"| HB
    C2 -->|"Hive bitmap UDF<br/>性能不足"| CK
    HB -->|"需要反向检索<br/>按标签条件找人"| ES

阶段 1:Hive 7 表(当前)

纯 Hive / Spark,零额外组件。

数量 存储 说明
标签明细长表 3 Hive ORC `tdm_{usr
核心标签宽表 3 Hive ORC `tdm_{usr
人群包 1 Hive ORC tdm_usr_crowd_ful_d,用 ARRAY<BIGINT> 存实体 ID 列表

人群包表结构(阶段 1)

字段 类型 说明
crowd_code STRING 人群编码(如 high_valuechurn_risk
crowd_name STRING 人群中文名
crowd_rule STRING 圈选规则描述(便于审计)
member_ids ARRAY<BIGINT> 实体 ID 列表
member_cnt BIGINT 人群人数(冗余,避免每次 SIZE(member_ids)
etl_time TIMESTAMP ETL 写入时间
dt STRING 分区日期

圈选方式:宽表 WHERE 条件过滤 -> COLLECT_LIST(user_id) 写入人群包。交叉计算用 array_intersect / array_union(Spark 2.4 内置)。

阶段 1 的局限

  • ARRAY<BIGINT> 存百万级 ID 列表时,单行数据量大,序列化/反序列化慢
  • array_intersect 是 O(n*m) 复杂度,两个百万级数组交叉计算耗时严重
  • 不支持跨行的集合运算(两个人群包做交集需要先 LATERAL VIEW EXPLODE 再 JOIN,性能差)

阶段 2:Bitmap UDF

触发条件:人群包数量 > 50 个,或出现 >= 3 层交叉计算需求(如"高价值 ∩ 活跃 ∩ 非流失")。

改造内容

  • 人群包表 member_ids 字段从 ARRAY<BIGINT> 改为 BINARY(RoaringBitmap 序列化)
  • 自研 Spark UDF(Java/Scala,注册到 dw_base/udf/):
UDF 功能 对应 SQL
bitmap_create(array<bigint>) ID 列表 -> bitmap 写入时调用
bitmap_and(binary, binary) 交集 人群 A ∩ B
bitmap_or(binary, binary) 并集 人群 A ∪ B
bitmap_andnot(binary, binary) 差集 人群 A - B
bitmap_count(binary) 计数 人群人数
bitmap_contains(binary, bigint) 判断是否包含某 ID 单用户判定
bitmap_to_array(binary) bitmap -> ID 列表 导出明细

优势:RoaringBitmap 对整型集合压缩率高(百万 ID 通常 < 1MB),交并差都是 O(n) 位运算,比数组快 2-3 个数量级。

阶段 3:HBase 在线标签服务

触发条件:业务侧需要实时标签查询(推荐系统、风控引擎、客服工作台等需要毫秒级响应单用户画像)。

改造内容

  • Hive TDM 仍然是离线标签计算的唯一来源,计算逻辑不变
  • 新增同步链路:宽表 tdm_{usr|prd|shp}_profile_ful_d 每日计算完成后,通过 BulkLoad 同步到 HBase
  • HBase 表设计:RowKey = {entity_type}_{entity_id},Column Family 按标签分类,Qualifier = 标签字段名
  • 业务侧通过 HBase Client / Thrift API 查询,Hive 侧不受影响

    Hive TDM 宽表 --BulkLoad--> HBase(在线读,毫秒级)
     |
     +--> ADS(离线报表,不变)
    

要点:HBase 只做读服务,不做标签计算。标签口径和计算逻辑的唯一来源始终是 Hive TDM 长表。

阶段 4:ClickHouse 人群包计算服务

触发条件:人群包数量多(> 200)且交叉计算复杂(> 5 层嵌套),Hive bitmap UDF 的批处理模式无法满足运营侧"秒级出圈选结果"的交互需求。

改造内容

  • 人群包存储和交叉计算从 Hive 迁移至 ClickHouse
  • ClickHouse 提供原生 bitmap 函数(bitmapAndbitmapOrbitmapXorbitmapCardinalitybitmapContains),无需自研 UDF
  • Hive TDM 仍然负责标签计算,计算完成后将人群包通过 DataX / JDBC 写入 ClickHouse
  • 运营侧圈选请求直接查 ClickHouse,秒级返回

    Hive TDM(离线标签计算,唯一来源)
    +--> ADS(报表/分析)
    +--> HBase(在线标签服务,单实体查询)
    +--> ClickHouse(人群包存储 + bitmap 交叉计算,秒级圈选)
    

要点:ClickHouse 只承接人群包的存储和集合运算,标签本身的计算口径仍由 Hive TDM 长表统一管理,避免出现两套计算逻辑。

阶段 5:Elasticsearch 检索型画像服务

触发条件:业务侧需要按标签条件反向检索实体(如"找出所有城市=上海、年龄>25、偏好运动=篮球的用户"),HBase 只支持 RowKey 点查,无法满足多条件组合筛选和模糊匹配。

改造内容

  • 宽表 tdm_{usr|prd|shp}_profile_ful_d 每日计算完成后,同步写入 ES(通过 DataX hdfs-elasticsearch 或 Spark ES connector)
  • ES 索引设计:每个实体类型一个索引(tdm_usr_profile / tdm_prd_profile / tdm_shp_profile),文档 ID = entity_id,字段映射与宽表一致
  • 运营/产品侧通过 ES 查询接口做多条件组合筛选、范围查询、模糊匹配

HBase vs ES 职责分工

场景 组件 示例
正向点查:已知实体 ID,取全部标签 HBase 推荐系统传入 user_id,取用户画像
反向检索:按标签条件组合筛选实体 ES 运营圈选"上海+25岁以上+篮球爱好者"

要点:ES 和 HBase 都只做读服务,数据来源是同一份 Hive TDM 宽表,不存在口径分歧。

完整数据流(阶段 5)

Hive TDM(离线标签计算,唯一来源)
    +--> ADS(报表/分析)
    +--> HBase(在线点查:已知 ID 取标签)
    +--> ES(检索型画像:按标签条件找实体)
    +--> ClickHouse(人群包 bitmap 交叉计算,秒级圈选)

演进总览

阶段 组件 TDM 表 人群包存储 圈选能力 在线查询
1 Hive 3 长 + 3 宽 + 1 人群 ARRAY<BIGINT> 宽表 WHERE + COLLECT_LIST
2 Hive + bitmap UDF 同上 BINARY(RoaringBitmap) bitmap 交并差,2-3 数量级提速
3 +HBase 同上 同阶段 2 同阶段 2 HBase 点查(ID -> 标签)
4 +ClickHouse 同上 ClickHouse 原生 bitmap 秒级交互式圈选 HBase(点查)+ ClickHouse(圈选)
5 +ES 同上 同阶段 4 同阶段 4 HBase(点查)+ ES(反向检索)+ ClickHouse(圈选)

核心不变量:无论演进到哪个阶段,标签的计算口径和计算逻辑始终由 Hive TDM 长表统一管理。HBase / ES / ClickHouse 只做下游的读服务和集合运算,不做标签计算。

7. 相关文档