【阿里巴巴大数据实践之路学习记录】第十章-维度设计
维度设计基础
维度的基本概念
在维度建模中,将度量称为“事实”,将环境描述为“维度”,维度是用于分析事实的多样环境。例如,在分析交易时,可以通过卖家、买家、商品和时间等维度分析交易发生过程。
维度所包含表示维度的列,称为维度属性。维度属性是查询约束条件、分组和报表标签生成的基本来源,是数据易用性的关键。
如何获得维度或维度属性?一方面,可以在报表中获取;一方面,与业务人员交谈发现,因为它常出现在by语句中。
维度使用主键标识其唯一性,主键也是确保与之相连的任何事实表之间存在引用完整性的基础。主键有两种:代理键和自然键,它们都是用于标识某维度的具体值。但代理键不具有业务含义,一般用于处理缓慢变化维。
维度的基本设计方法
维度的设计过程就是确定维度属性的过程,如何生成维度属性,以及所生成的维度属性的优劣,决定了维度使用的方便性,成为数据仓库易用性的关键。
如果以淘宝的商品维度为例,该如何设计维度?
- 选择维度或新建维度。作为维度建模的核心,必须保证维度的唯一性,有且只有一个维度的定义。
- 确定主维表。此处的主维表一般是ODS表,直接与业务系统同步。
- 确定相关维表。数据仓库是业务源系统的数据整合,不同业务系统或者同一业务系统中的表存在关联性。根据对业务的梳理,确定哪些表和主维表存在关联关系,并选择其中的某些表用于生成维度属性。
- 确定维度属性。本步骤主要包括两个阶段,其中一个阶段是从主维表中选择维度属性或生成新的维度属性;第二个阶段是从相关维表中选择维度属性或生成新的维度属性。
需要注意:
- 尽可能生成丰富的维度属性,为下游的数据统计、分析、探查提供良好的基础。
- 尽可能多地给出包括一些富有意义的文字性描述。属性不应该是编码,而是真正的文字。一般id和名字同时存在。
- 区分数值型属性和事实。
如果数值型字段通常用于查询约束条件或分组统计,则作为维度属性;如果通常用于参与量的计算,则作为事实。 - 尽可能沉淀出通用的维度属性
维度的层次结构
维度中的一些描述属性以层次方式或一对多的方式相互关联,可以被理解为包含连续主从关系的属性层次。层次的最底层代表维度中描述最低级别的详细信息,最高层代表最高级别的概要信息。在属性的结构层次中进行钻取是数据钻取的方法之一。
假设已有一个淘宝交易订单,创建事实表。最高层次的统计:某日期的下单GMV,得到一行记录;沿着层次向下钻取,添加行业,得到行业实例个数的记录数;继续沿着层次往下钻取,添加一级类目,得到一级类目实例个数的记录数。
规范化和反规范化
当属性层次被实例化为一系列维度,而不是单一的维度时,被称为雪花模式。大多数OLTP的底层数据结构在设计时采用此种规范化技术,通过规范化处理将重复属性移至其自身所属的表中,删除冗余数据。
- 雪花模式:优点:节约存储(现阶段存储成本非常低);缺点:对于OLAP来说,需要大量关联操作,复杂度高。
- 将维度的属性层次合并到单个维度中的操作称为反规范化。优点:方便、易用且性能好;缺点:冗余数据。
一致性维度和交叉探查
kimball的数据仓库总线架构提供了一种分解企业级数据仓库规划任务的合理方法,通过构建企业范围内一致性维度和事实来构建总线架构。数据仓库总线架构的重要基石之一就是【一致性维度】。
在针对不同数据域进行迭代构建或并行构建时,存在很多需求是对于不同数据域的业务过程或者同一数据域的不同业务过程合并在一起观察。现在将不同数据域的商品的事实合并在一起进行数据探查,称为【交叉探查】。
如果维度格式和内容不一致,则会导致交叉探查存在问题。例如:
对于日志数据域,统计使用的是商品维度1;对于交易数据域,统计使用的是商品维度2;
- 商品维度1包含维度属性BC类型,而商品维度2无此属性,则无法在BC类型上进行交叉探查。
- 商品维度1的商品上架时间这一维度属性的时间格式是yyyy-MM-dd HH:mm:ss,商品维度2的商品上架时间这一维度属性时间格式是UNIX timestamp,进行交叉探查时如果需要对商品上架时间做限制,则复杂度较高。
- 商品维度1不包含阿里旅行的商品,商品维度2包含全部的淘系商品,交叉探查也无法进行。
维度一致性有以下三种表现形式:
- 共享维表:如商家、卖家、买家等维度有且只有一个,基于这些公共维度进行的交叉探查不会存在任何问题。
- 一致性上卷:其中一个维度属性是另一个维度属性的子集,且两个维度的公共属性结构和内容相同。
- 交叉属性:两个维度具有部分相同的维度属性。可以在相同的维度属性上进行不同业务过程的交叉探查。
维度设计高级主题
维度整合
数据仓库的定义:数据仓库是一个面向主题的、集成的、非易失的且随时间变化的数据集合,用来支持管理人员的决策。集成是数据仓库四个特性中最重要的一个。
应用出于性能和扩展性的考虑,或者随技术架构的演变,以及业务的发展,采用不同的物理实现。拆分至不同类型数据库中;拆分成同一类型数据库中的多个物理表。(分库分表)
所以数据由面向应用的操作型环境进入数据仓库后,需要进行数据集成。将【面向应用的数据】转换为【面向主题的数据仓库数据】,本身就是一种集成。具体体现在:
- 命名规范的统一。表名、字段名等统一。
- 字段类型的统一。相同和相似字段的字段类型统一。
- 公共代码及代码值的统一。
- 业务含义相同的表的统一。依据高内聚、低耦合的理念,在物理实现中,将业务关系大、源系统影响差异小的表进行整合;将业务关系小、源系统影响差异大的表进行分而置之。有如下几种集成方式:
- 采用主从表设计方式,将两个或多个表都有的字段放在主表中(主要基本信息),从属信息分别放在各自的从表中。对于主表中的主键,要么采用复合主键、源主键和系统或表标志区别;要么采用唯一主键、“源主键和系统或表区别标志”生成新的主键。通常建议采用复合主键的方式。
- 直接合并。共有信息和个性信息都放在同一个表中。如果表字段的重合度较低,则会出现大量空值,对于存储和易用性有影响。
- 不合并,因为源表的表结构及主键等差异很大,无法合并,使用数据仓库里的多个表存放各自的数据。
维度的整合和上述三种集成方式相同,对于表级别的整合有两种表现形式:
- 垂直整合:即不同的来源表包含相同的数据集,只是存储的信息不同。
- 水平整合:即不同的来源表包含不同的数据集,不同子集之间无交叉,也可以存在部分交叉。
- 如果进行整合,首先需要考虑各个会员体系是否存在交叉,如果存在交叉,则需要去重;如果不存在交叉,则需要考虑不同子集的自然键(具有业务含义的主键)是否存在冲突 ,如果不冲突,则可以考虑将各子集的自然键作为整合后的表的自然键;如果存在冲突,则可以设置超自然键,将来源表各子集的自然键加工成一个字段作为超自然键(可作为联合主键)。
维度拆分
水平拆分
维度通常可以按照类别或类型细分。不同分类的商品,其维度属性会有部分相同,如航旅商品会有普通商品有的商品价格、标题、类型等维度属性,也会有自己独特的酒店、景点、门票等维度属性。
因此,设计维度有两种方案:
- 将维度的不同分类实例化为不同的维度,同时在主维度中保存公共属性。
- 维护单一维度,包含所有可能的属性。
在选择维度方案时需要考虑:
- 扩展性:当源系统、业务逻辑变化时,能通过较小的成本快速扩展模型,保持核心模型的相对稳定性。高内聚、低耦合。
- 效能:在性能和成本方面取得平衡。
- 易用性:模型可理解性高、访问复杂度低。
依据上述,在对维度进行水平拆分时,主要考虑如下两个依据:
- 维度的不同分类的属性差异情况。当维度属性随类型变化较大时,将所有可能的属性建立在一个表中不切实际,故用方案1.
- 业务的关联程度。两个相关性较低的业务,耦合在一起弊大于利
垂直拆分
在进行维度设计时,依据维度设计的原则,尽可能丰富维度属性,同时进行反规范化处理。具体实现时,可能存在上述由于维度分类不同而存在特殊的维度,通过水平拆分解决。
对于维度属性来源表产出时间不同,维度之间被使用频率不同,维度之间变化间隔不同,可用垂直拆分的方法:
设计主从维度,主维表存放稳定、产出时间早、热度高的属性;从维表存放变化较快、产出时间晚、热度低的属性。由于商品扩展维度有冗余的库存等变化较快的数据,对于主维度进行缓慢变化的处理较为重要。通过存储的冗余和计算成本的增加,实现了商品主模型的稳定和产出时间的提前。
历史归档
归档策略有以下几种方式:
- 同前台归档策略,在数据仓库中实现前台归档算法,定期对历史数据进行归档。缺点:前台归档策略复杂、实现成本较高;前台归档策略可能会经常变化,导致数据仓库归档算法也要随之变化。
- 同前台归档策略,但采用数据库变更日志的方式。如:通过数据库binlog日志解析获取每日增量,通过增量merge全量的方式获取最新的全量数据。
- 数据仓库自定义归档策略。原则是尽量比前台应用晚归档、少归档。
维度变化
缓慢变化维
与数据增长较快速的事实表相比,维度变化相对缓慢。
处理缓慢变化维有三种方式:
- 重写维度值。采用这种方式,不保留历史数据,始终取最新数据。
- 插入新的维度行。保留历史数据,维度值变化前的事实和过去的维度值关联,维度值变化后的事实和当前的维度值关联。缺点:不能将变化前后记录的事实归一化为变化前的维度或者归一化为变化后的维度。
- 添加维度列。将变化的维度变成新的列加入表中。
快照维表
kimball的维度建模中,必须使用代理键作为每个维表的主键,用于处理缓慢变化维。
阿里巴巴的分布式计算系统是自主研发的,不存在事务的概念。如果分布式计算系统不存在事务的概念,对于每个表的记录生成稳定(每次生成的都相同)的全局唯一的代理键难度很大。使用代理键会大大增加etl的复杂性,对etl任务的开发和维护成本很高。
不使用代理键,如何处理缓慢变化维?
使用快照方式。每个周期内均保留一份全量快照数据,通过限定日期,采用自然键进行关联即可。
上述方法的优点:
- 简单有效,开发和维护成本低。
- 使用方便,理解性好。只需要通过限定日期,即可获得当天的快照数据(对于天周期的数据),通过自然键关联即可。
上述方法的缺点:极大浪费存储空间。此方法实现牺牲存储获取etl效率的优化和逻辑上的简化。使用此方法必须要有对应的数据生命周期制度,清除无用的历史数据。
有什么方法可以实现上面优点,同时又可以很好地降低存储呢?答案就是利用阿里巴巴的极限存储(没想到吧,自己设计一个东西解决自己设计东西的缺陷,👍)。
极限存储
什么是历史拉链存储?
利用维度模型中缓慢变化维的第二种处理方式,通过新增两个时间戳字段(start_dt、end_dt),将所有以天为粒度的变更数据都记录下来,对于不变的数据,不再重复存储。通常分区字段也是时间戳字段。这样下游应用可以通过限制时间戳字段(start_dt、end_dt)来获取历史数据。
缺点:
- 对于下游使用存在理解障碍。
- 用start_dt和end_dt做分区,随着时间推移,分区数量会嫉极度膨胀,而下行的数据库系统都有分区限制。
用极限存储的方式处理:
- 透明化:底层数据依然历史拉链存储,上层做一个视图操作或者hive做一个hook(hive钩子),通过分析语句的语法树,把对极限存储前的查询转换为对极限存储的查询。
select * from a where dt = '2025-08-04'
<=>
select * from b where start_dt<='2025-08-04' and end_dt = '2025-08-04'
-
分月做历史拉链存储表:如果不做限制,用start_dt和end_dt的分区数为365364/2。如果每月重新开始做历史拉链存储表,分区数为 12(1+(30+29)/2)((30+29)/2是因为 1-30,2-30,…,3-30等,等差数列)。
缺点:- 产出效率很低,大部分极限存储通常需要T-2;
- 对变化频率高的数据并不能达到节约成本的效果。
针对以上缺点,需要对极限存储表做一些额外的处理:
- 在做极限存储前有一个全量存储表。全量存储表只保留最近一段时间的全量分区数据,历史数据通过映射的方式关联到极限存储表中。
- 对于部分变化频率频繁的字段需要过滤。
微型维度
解决维度过度增长导致极限存储效果大打折扣的问题的方法:
- 将一些属性从维表中移出,放置到全新的维表中。
- 垂直拆分,保持主维度的稳定性。
- 微型维度:将一部分不稳定的属性(如VIP等级,信用等级)从主维度中移出,将它们放置到拥有自己代理键的新表中来实现的。这些属性之间没有直接关联,不存在自然键。通过为每个组合创建新行的一次性过程来加载数据。
微型维度有以下几点缺点:
- 微型维度是事先用所有可能值的组合加载的,需要考虑每个属性的基数,且必须是枚举值。很多属性非枚举型,如VIP分数、信用分数等。
- ETL逻辑复杂。对于分布式系统,生成代理键和使用代理键进行ETL加工都非常复杂。
- 破坏了维度的可浏览性。买家维度和微型维度通过事实表建立联系,无法基于VIP等级、信用等级进行浏览和统计。
特殊维度
递归层次
维度的递归层次,指的是某维度的实例值的层次关系。按照层级是否固定分为均衡层次结构和非均衡层次结构。如地区,固定为乡镇/街道、区县、城市、省份、国家,固定数量;公司之间的关系,不同公司可能存在一个母公司,可能没有固定的一级、二级等层次关系,数量级别不固定。
通过数据探查得知ID等于21的类目属于一级类目,统计其最近一天的GMV的过程,称为上钻;在递归层次中上钻和下钻是很常见的,由于很多数据仓库系统不支持递归SQL,且用户使用递归SQL成本较高,所以在维度模型中,需要对此层次结构进行处理,处理方法如下:
- 层次结构扁平化:建立维度的固定数量的级别属性实现,如每个类目保存一条记录,并将其所属的各类目层级属性化。其中,对于高层级类目,由于其无低层级类目,故低层级类目置为空值。
此时统计类目id为21的最近一天的GMV,即通过叶子类目和类目维表的类目ID关联之后,限制一级类目ID为21的最近一天的GMV,方便性大大提高。
但存在如下问题:- 针对某类目下钻或上钻之前,必须知道其所属的类目层级,然后才能决定限制哪一级类目。
- 假设分三级类目统计最近一天的GMV,由于某些叶子类目直接是一级类目或二级类目,和交易事实表关联后,其三级类目为空,导致这些类目的数据无法被统计到。因此更改规则为如果此类目对应的三级类目为空,则取二级类目;如果二级类目仍为空,则取一级类目。另一种方式是回填,即将类目向下虚拟,高层级类目,如果无低层级类目,故低层级类目置为自己。
- 层次桥接表:针对上述问题,采用桥接表方式可以解决,不需要预先知道所属层级,不需要回填,也可解决非均衡层次结构的问题;但是复杂性高,且由于事实表和桥接表的多对多关系带来了双重计算的隐患。结构如下:
父类目id | 子类目id | 类目层级间隔 |
---|---|---|
21 | 21 | 0 |
21 | 22222 | 1 |
21 | 34356 | 2 |
22222 | 22222 | 0 |
22222 | 34356 | 1 |
34356 | 34356 | 0 |
行为维度
维度与事实相关,如交易、物流等,成为“行为维度”,或“事实衍生的维度”。按照加工方式,行为维度可以分为以下几种:
- 另一个维度的过去行为。如买家最近一次访问淘宝的时间。
- 快照事实行为维度:买家信用分值。
- 分组事实行为维度:将数值型事实转换为枚举值。如买家信用分值按照分数划分得到的信用等级。
- 复杂逻辑事实行为维度:根据复杂算法加工或多个事实综合加工得到。如商品热度(根据访问、收藏、是否加入购物车等情况综合计算得到)。
行为维度有如下两种处理方式:
- 将其冗余至现有的维表中,如将卖家信用等级冗余至卖家维表中
- 加工成单独的行为维表,如卖家主营类目
采用哪种方式需要参考如下两个原则:
- 避免维度过度增长。
- 避免耦合度过高。
多值维度
一种情况是事实表的一条记录在维表中有多条记录与之对应,如交易订单一次购买多个商品,假设设计交易父订单事实表,则对于该事实表中的每一条记录,在商品表中都有一到多条记录与之对应。有如下三种处理方式:
- 降低事实表的粒度:将交易订单设计为子订单粒度。
- 采用多字段:如一个合同存在多个买受方的情况,将买受方一、买受方二等都作为字段放入表中。
- 采用较为通用的桥接表:通过在事实表和维表之间开发一个分组表,通过此分组表建立连接。
多值属性
维表中的某个属性字段同时有多个值,称之为“多值属性”。它是多值维度的另一种表现形式。如商品和SKU、属性、标签都是多对多的关系。有如下三种处理方式:
- 保持维度主键不变,将多值属性放在维度的属性字段中。如对于商品属性,可以通过k-v对的形式放在property字段中。
- 保持维度主键不变,将多值属性放在维度的多个属性字段中。如业务需求是根据算法计算的类目top3为主营类目,则可以先设定固定数量的维度属性,主营类目一、二、三展示。
- 维度主键发生变化,一个维度值存放多条记录,但这种方法数据会极度膨胀。
杂项维度
杂项维度是由操作型系统中的指示符或者标志字段组合而成的,一般不在一般性维度之列。如交易订单的交易类型字段,可能包括话费充值、支付状态等。它们在源系统中直接保存在交易表中。
如果作为事实存放在事实表中,则会导致事实表占用空间过大;如果单独建立维表,外键关联到事实表,则会出现维度过多的情况。
解决办法即:建立杂项维度,将这些字段建立到一个维表中,在事实表中只需存一个外键即可。不要直接用所有的组合生成完整的杂项维表,在抽取遇到新的组合时生成相应的记录即可。
由于非枚举类型字段的存在,以及分布式计算系统中生成代理键的复杂度,会使用实体的主键作为杂项维度的主键。此时,该杂项维度一般是逻辑模型,物理实现时不进行物理化,订单杂项维度和其他维度一起,会将维度属性退化至事实表中。