前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据建模-维度建模-维度设计

数据建模-维度建模-维度设计

作者头像
大数据学习与分享
发布2023-09-06 11:27:23
3560
发布2023-09-06 11:27:23
举报

导读:

在Kimball维度建模中,通常将度量称为“事实”,将环境描述为“维度”,维度是用于分析事实所需要的多样环境。维度和维度属性是维度的两个核心概念,如何构建维度的属性是维度设计中需要关注的。维度具有层次结构,维度中的一些描述属性以层次方式或一对多方式相互关联。比如商品维度,有卖家、类目、品牌等父层次。对于层次结构,是采用雪花模式进行规范化处理还是将维度的属性层次合并到单个维度中进行反规范化处理,需要进行取舍。


一、第一部分

(一):维度的基本概念

维度是用于分析事实所需要的多样环境。例如,在分析交易过程时,可以通过买家、卖家、商品和时间等维度描述交易发生的环境。维度所包含的表示维度的列,称为维度属性。维度属性是查询约束条件、分组和报表标签生成的基本来源,是数据易用性的关键。例如在查询请求中,取某类目的商品,取正常状态的商品等,是通过约束商品类目属性和商品状态属性来实现的;例如,统计不同商品类目每日成交金额,是通过商品维度的类目属性进行分组的。所以维度的作用一般有以下几点,查询约束、分类汇总以及排序等。

如何获取维度或它们的属性?如上面提到的,一方面,可以在报表中获取;另一方面,可以在和业务人员的交谈中能够发现维度或它们的属性。因为它们经常出现在查询或报表请求中的"按照"(by)语句内。例如,用户需要"按照"月份和产品来查看销售情况。用户描述其业务的自然方法应该作为维度或维度属性包括在维度模型中。

维度使用主键标示其唯一性,主键是确保与之相连的任何事实表之间存在引用完整性的基础。主键有两种方式:代理键和自然键,它们都是用于标示某维度的具体值。但代理键是不具有业务含义的键,一般用于处理缓慢变化维度;自然键是具有业务含义的键。例如商品,在ETL过程中,对商品维表的每一行,可以生成一个唯一的代理键与之对应;商品本身的自然键可能是商品ID等。其实对于前台应用系统,商品ID是代理键;而对数据仓库系统来说,商品ID则属于自然键。


(二):维度的设计方法

维度的设计过程就是确定维度属性的过程,如何生成维度属性、生成的维度属性的优劣,决定了维度使用的方便性,成为数据仓库易用性的关键。正如Kimball所说,数据仓库的能力直接与维度属性的质量和深度成正比。下面以商品维度为例进行维度设计方法的进行详细说明。

第一步:选择维度或新建维度。作为维度建模的核心,我们在企业级的数据仓库中必须保证维度的唯一性。以淘宝商品维度为例,我们有且只允许有一个维度定义。

第二步:确定主维度表。此处的主维度表一般是ODS表,直接同步自业务系统。以商品维度为例,商品维表是从前台商品中心系统同步的商品表,此表即是主维度表。

第三步:确定相关维度表。数据仓库是业务源系统的数据整合,不同业务系统或者同一业务系统中的表之间存在关联性。根据对业务的梳理,确定哪些表和主维度表存在关联关系,并选择其中的某些表用于生成维度属性。以商品维度为例,根据业务逻辑梳理,可以得到商品和类目、SPU、卖家、店铺等维度存在关联关系。

第四步:确定维度属性。本步骤主要包括两个阶段,第一阶段是主维度表中选择维度属性或生成新的维度属性;第二阶段是从相关维度表中选择维度属性或生成新的维度属性。以商品维度为例。以商品维度为例,从主维度表(商品维表)和类目、SPU、卖家、店铺等相关维度表选择维度属性或生成新的维度属性。


(三):维度的层次结构

维度中的一些描述属性以层次方式或一对多方式相互关联,可以被理解为包含连续主从关系的属性层次。层次的最底层代表维度中描述最低级别的详细信息,最高层次代表最高级别的概要信息。维度常常有多个这样的嵌入式层次结构。比如商品维度,有卖家、类目、品牌等。商品属于类目、类目属于行业,其中类目的最低级别是叶子类目、叶子类目属于二级类目、二级类目属于一级类目。在属性的层次结构中进行钻取是数据钻取的方法之一。通过具体的例子,我们来看如何在层次结构中进行钻取。假设我们已有一个电商交易订单创建事实表。现在统计2015年双11的下单GMV,我们得到一行记录;沿着层次向下钻取,添加行业,得到行业实例个数的记录数;继续沿着层次向下钻取,添加一级类目,得到一级类目实例个数的记录数。可以看到,通过向报表中添加连续的维度细节级别实现在层次结构中进行钻取。

代码语言:javascript
复制
最高层次的统计:日期
行业
一级类目
下单GMV
20151111
ALLALL
912亿
钻取至行业层次
日期
行业
一级类目
下单GMV2015111行业1
ALL行业1_GMV2015111行业2ALL行业2_GMV2015111.....ALL.....
2015111行业NALL行业N_GMV钻取至商品类目日期
行业
一级类目
下单GMV2015111行业1
类目1行业1_GMV2015111行业2类目2行业2_GMV2015111...............
2015111行业N类目N行业N_GMV

类目、行业、品牌等属性层次被实例化为一系列维度还是作为维度属性存在于商品维度中? 如何设计,我们在下一节中详细讨论.


(三):规范化和反规范化

当属性层次被实例化为一系列维度,而不是单一的维度时,此模式被称为雪花模式。大多数联机事务处理系统(OLTP)的底层数据结构在设计时采用此种规范化技术,通过规范化处理将重复属性移至其自身所属的表中,删除冗余数据。此种方法用在OLTP系统中可以有效避免数据冗余导致的不一致性。比如在OLTP系统中,存在商品表和类目表,且商品表中冗余有类目表的属性字段,假设对某类目进行更新,则必须更新商品表和类目表,且由于商品和类目是一对多的关系,商品表可能每次需要更新几十万甚至上百万条记录,这是不合理的。而对于联机分析处理系统(OLAP),数据是稳定的,不存在OLTP系统中存在的问题。

对于商品维度,如果采用雪花模式进行规范化处理,将表现为如下形式:

将维度的属性层次合并到单个维度中的操作称为反规范化。分析系统的主要目的是用于数据分析和统计,如何更方便用户进行统计分析决定了分析系统的优劣。采用雪花模式,用户在统计分析的过程中需要大量的关联操作,使用复杂度高,同时查询性能很差;采用反规范化处理,方便易用且性能好。

对于商品维度,如果采用反规范化处理,将表现为如下形式:

如上所述,从用户的角度来看这简化了模型,并且使数据库查询优化器的连接路径比完全规范化的模型简化许多。反规范化的维度仍包含与规范化模型同样的信息和关系,从分析角度来看,没有丢失任何信息,但复杂性降低了。采用雪花模式,除了可以节约一部分存储,对于OLAP系统来说没有其它效用。而现阶段存储的成本非常低。基于易用性和性能考虑,维度表一般是很不规范化的。实际应用中,几乎总是使用维度表的空间来换取简明性和查询性能。


(三):规范化和反规范化

构建企业级的数据仓库不可能一蹴而就,一般采用迭代式的构建过程。但单独构建存在的问题是形成独立型数据集市,导致严重的不一致性。Kimball的数据仓库总线架构提供了一种分解企业级数据仓库规划任务的合理方法。通过构建企业范围内一致性的维度和事实来构建总线架构。  

数据仓库总线架构重要基石之一就是一致性维度。在针对不同数据域进行迭代构建或并行构建时,存在很多需求是对于不同数据域的业务过程或者同一数据域的不同业务过程合并在一起观察;比如对于日志数据域,我们统计了商品维度的最近一天访问PV和UV数;对于交易数据域,我们统计了商品维度的最近一天的下单GMV。现在我们将不同数据域的商品的事实合并在一起进行数据探查,如计算转化率等,我们称为交叉探查。  

如果不同数据域计算过程使用的维度不一致,就会导致交叉探查存在问题。当存在重复的维度,但维度属性或维度属性的值不一致,会导致交叉探查无法进行或交叉探查结果错误。如上所示,假设日志数据域统计商品维度的最近一天PV和UV使用的商品维度1,交易数据域统计商品维度使用的是商品维度2。商品维度1包含维度属性BC类型,而商品维度2无此属性,则无法在BC类型上进行交叉探查;商品维度1商品上架时间这一维度属性时间格式是字符串格式yyyy-MM-dd HH:mm:ss,商品维度2商品上架时间这一维度属性时间格式是Unix timestamp,进行交叉探查时如果需要根据商品上架时间做限制,则复杂性较高;还有很多种形式的不一致,不再一一列举,基本可以划分维度格式和内容的不一致这两种类型。  

我们对维度不一致进行了详细分析,维度一致性的几种表现形式如下:  

(1)共享维度表,比如在电商的数据仓库中,商品、卖家、买家、类目等维度,有且只有一个。所以基于这些公共维度进行的交叉探查,不会存在任何问题。  

(2)一致性上卷,其中一个维度的维度属性是另一个维度的维度属性的子集,且两个维度的公共维度属性结构和内容相同。比如在商品体系中,有商品维度和类目维度,其中类目维度的维度属性是商品维度的维度属性的子集,且有相同维度属性和维度属性值。这样基于类目维度进行不同业务过程的探查也不会存在任何问题。

(3)交叉属性,两个维度具有部分相同的维度属性。比如在商品维度中具有类目属性,在卖家维度中具有主营类目属性,两个维度具有相同的类目属性,则可以在相同的类目属性上进行不同业务过程的交叉探查。


(四):维度设计高级主题

集成是数据仓库的四个特性中最重要的一点,维度设计中需要考虑如何集成来自应用的大量分散的操作型环境的数据。接下来,我们将结合数据集成的概念,逐步阐述维度设计的一些高级主题,例如维度的整合拆分、维度的历史归档、缓慢变化维的应对以及部分特殊维度的处理等等。

二、第二部分

在Kimball维度建模中,通常将度量称为“事实”,将环境描述为“维度”,维度是用于分析事实所需要的多样环境。维度和维度属性是维度的两个核心概念,如何构建维度的属性是维度设计中需要关注的。维度具有层次结构,维度中的一些描述属性以层次方式或一对多方式相互关联。比如商品维度,有卖家、类目、品牌等父层次。对于层次结构,是采用雪花模式进行规范化处理还是将维度的属性层次合并到单个维度中进行反规范化处理,需要进行取舍。


维度设计高级主题

集成是数据仓库的四个特性中最重要的一点,维度设计中需要考虑如何集成来自应用的大量分散的操作型环境的数据。维度有两种拆分方式,水平拆分和垂直拆分。水平拆分通常基于维度类别或类型进行细分。垂直拆分通常基于扩展性、产出时间、易用性等方面的进行考虑。主维度表存放稳定、产出时间早、热度高的属性;从维度表存放变化较快、产出时间晚、热度低的属性。面对如此庞大的数据量,如何设计模型、如何降低存储、如何让下游方便获取数据,成为必须要解决的问题。数据归档是解决此问题的有效方法之一。


(一)维度的聚合

数据仓库的重要数据来源是大量的、分散的面向应用的操作型环境。不同的应用在设计过程中,可以自由决策,主要满足本应用的需求,很少会考虑和其它系统进行数据集成。应用的差异具体表现在如下几个方面:

(1)应用在编码、命名习惯、度量单位等方面会存在很大差异。比如不同应用对于用户的性别编码不同:有0和1,有F和M等;不同应用用户ID含义相同但字段名称不同:有user,有user_id等;不同应用对于金额的度量单位不同:有元,有分等等。

(2)应用基于性能和扩展性的考虑,或者随技术架构的演变,或者随业务的发展,采用不同的物理实现,拆分至不同类型数据库,部分数据采用关系型数据库存储(如Oracle、Mysql等),部分数据采用NoSQL数据库存储(如Hbase、Tair等)。

(3)同一类数据基于范式建模,拆分成同一类型数据库中多张的物理表,比如商品,有商品主表和商品扩展表,商品主表存商品基本信息;商品扩展表存储商品特殊信息,如不同产品线定制化的信息等;比如会员,有会员主表和会员扩展表,会员主表存用户基本信息;会员扩展表存储用户扩展信息,如用户的各种标签信息等。

综上,数据由面向应用的操作型环境进入数据仓库后,需要进行数据集成。将面向应用的数据转换为面向主题数据仓库数据,本身就是一种集成。具体体现在如下几个方面:

(1)命名规范的统一:表名、字段名等统一; 

(2)字段类型的统一:相同和相似字段的字段类型的统一;

(3)公共字段及枚举值的统一:公共字段及枚举值的类型、命名方式等统一;

(4)业务含义相同的表的统一:相同含义的业务表进行聚合统一;

主要依据高内聚、低耦合的理念,在物理实现中,将业务关系大、源系统影响差异小的进行整合;业务关系小、源系统影响差异大的进行分而置之。通常有如下集成方式:

(1)采用主从表的设计方式,两表或多表都有的字段放在主表中(基本信息),从属信息分别放在各自的从表中。对于主表中的主键,要么采用复合主键,源主键和系统或表区别标志;要么采用唯一主键,“源主键||系统或表区别标志”生成新的主键。通常建议采用复合主键的方式。  

(2)直接合并,共有信息和个性信息都放在同一个表中。如果表字段的重合度较低,会出现大量空值,对于存储和易用性会有影响,需谨慎选择。  

(3)不合并,源表的表结构及主键等差异很大,无法合并,使用数据仓库里的多个表存放各自数据。 

维度表的整合涉及的内容和上面介绍的几个方面相同,下面重点看一下表级别的整合,有两种表现形式。

第一种是垂直整合,即不同的来源表包含同一数据集,只是存储的信息不同。比如会员在源系统有多张表,会员基础信息表、会员扩展信息表、会员等级信息表。这些表都属于会员相关信息表,依据维度设计方法,尽量整合至会员维度模型中,丰富其维度属性。

第二种是水平整合,即不同的来源表包含不同的数据集,不同子集之间无交叉,亦可以存在部分交叉,例如阿里的数据仓库,其采集的会员数据有淘宝会员、1688会员、国际站会员、支付宝会员等,是否需要将所有的会员整合成一张会员表?如果进行整合,首先需要考虑各个会员体系是否有交叉,如果存在交叉,则需要去重;如果不存在交叉,不同子集的自然键是否存在冲突,如果不冲突,可以考虑将各子集的自然键作为整合后的表的自然键;

另外一种方式是设置超自然键,来源+各子集的自然键加工成一个字段作为超自然键。常用的方式是将来源各子集的自然键作为联合主键的方式,并且在物理实现时,将来源字段作为分区字段。

有整合就有拆分,到底是做整合还是拆分,都有哪些因素决定。下面开始讨论维度的水平拆分和垂直拆分。


(二)维度的拆分

1. 维度的水平拆分

维度通常可以按照类别或类型细分。比如阿里的商品表,根据业务线或行业等可以对商品进行细分,比如淘宝的商品、天猫的商品、1688的商品、阿里去啊的商品、淘宝海外的商品、天猫国际的商品等。不同分类的商品,其维度属性可能相同,也可能不同。比如阿里去啊的商品和普通的淘系商品,都属于商品,都有商品价格、标题、类型、上架时间、类目等维度属性,但是阿里去啊商品除了有这些公共属性,还有酒店、景点、门票、旅行等自己独特的维度属性。  

针对此问题,主要解决方案有两种:

方案1:将维度的不同分类实例化为不同的维度,同时在主维度中保存公共属性;

方案2:维护单一维度,包含所有可能的属性。

选择哪种方案?数据模型设计过程中需要考虑的因素很多,基本不可能满足各个特性指标的最优化。在设计过程中需要重点考虑以下三点原则:  

(1)扩展性:指在源系统、业务逻辑变化的时候,能通过少的成本快速扩展模型,保持核心模型的相对稳定性。软件工程中高内聚、低耦合的思想是重要的指导方针之一。

(2)效能:性能成本方面的平衡。牺牲一定的存储成本,达到性能和逻辑的优化。  

(3)易用性:模型可理解性高、访问复杂度低。用户能够方便的从模型中找到对应的数据表,并能够方便查询和分析。  

根据数据模型设计思想,在做维度的水平拆分时,主要考虑如下两个依据:  

第一个依据是,维度的不同分类的属性差异情况。当维度属性随类型变化较大时,将所有可能的属性建立在一张表中是不切合实际和没有必要的,此时建议采用方案1。定义一个主维度存放公共属性;同时定义多个子维度,除包含公共属性外,还包含各自子维度的特殊属性。比如在阿里数据仓库维度体系中,依据此方法,构建了商品维度、阿里去啊商品维度等。公共属性一般比较稳定,通过核心的商品维度,保证了核心维度的稳定性;通过扩展子维度的方式,保证了模型的扩展性。  

另外一个重要依据是,业务的关联程度。两个相关性较低的业务,耦合在一起弊大于利,对于模型的稳定性和易用性影响较大。比如阿里数据仓库维度体系中,我们将淘系商品和1688商品构建两个维度。虽然淘系和1688商品底层技术实现上是统一的,但属于不同的业务BU,业务各自发展;在数据仓库层面,淘系和1688属于不同的数据集市,一般不会相互调用,业务分析人员一般只针对本数据集市进行统计分析。如果设计成一个维度,由于不同BU业务各自发展,1688业务变更,此维度需要变更,淘宝业务变更亦然,稳定性很差;易用性方面,会给数据使用方造成困扰。

2. 维度的垂直拆分

维度属性的丰富程度直接决定了数据仓库的能力。在做维度设计时,依据维度设计的原则,尽可能丰富了维度属性,同时进行了反规范化处理。对于具体实现时可能存在问题:

一方面是水平拆分中提到的,维度属性由于维度分类的不同而存在特殊属性,通过水平拆分的方式解决此问题。

另一方面的问题是某些维度属性的来源表产出时间较早,而某些属性的来源表产出时间较晚;或者某些维度属性的热度高、使用频繁,而某些维度属性热度低、较少使用;或者某些维度属性经常变化,而某些属性比较稳定。水平拆分中提到的模型设计的三个原则同样适用于解决此问题。  

基于扩展性、产出时间、易用性等方面的考虑,设计主从维度。主维度表存放稳定、产出时间早、热度高的属性;从维度表存放变化较快、产出时间晚、热度低的属性。比如在阿里数据仓库中,我们设计了商品主维度和商品扩展维度。其中商品主维度在每日的01:30左右产出,而商品扩展维度由于冗余了产出时间较晚的商品品牌和标签信息,在每日的03:00左右产出;另外,商品扩展维度冗余了库存等变化较快的数据,对于主维度进行缓慢变化的处理较为重要。通过存储的冗余和计算成本的增加,实现了商品主模型的稳定和产出时间提前,对于整个数据仓库的稳定和下游应用的产出都有较大意义。


(三)维度的历史归档

面对海量的数据,如何设计模型、如何降低存储、如何让下游方便获取数据,成为必须要解决的问题。如此庞大的数据,现有的技术架构也很难处理。前台有一套数据归档的策略,比如将商品状态为下架或删除的且最近31天未更新的商品归档至历史库;具体逻辑根据不同业务BU有不同的算法,且有特殊的规则。  

数据仓库中,理所当然可以借用前台数据库的归档策略,定期将历史数据归档至历史维表。在实践中,阿里巴巴数据仓库设计商品维度表和历史商品维度表,每天将历史数据归档至历史商品维度表。

关于归档策略的选择,可以有以下几种方式:

(1)同前台归档策略,将前台归档算法在数据仓库中实现,定期将历史数据进行归档。但存在一些问题,一方面是前台归档策略复杂,实现成本较高;另外一方面,前台归档策略可能会经常变化,会导致数据仓库归档算法也要随之变化,维护和沟通成本较高。此方法适用于同前台归档策略逻辑较为简单,且变更不频繁的情况。

(2)同前台归档策略,但采用数据库变更日志的方式。采用的数据抽取策略一般是通过数据库binlog解析获取每日增量,通过增量merge全量的方式获取最新全量数据。可以使用增量日志的删除标记,作为前台数据归档的标记。通过此标记对数据仓库的数据进行归档。此方法不需要关注前台归档策略,简单易行。但对前台应用的要求是数据库的物理删除只有在归档时才执行,应用中的删除只是逻辑删除。

(3)数据仓库自定义归档策略。可以将归档算法用简单直接的方式实现,但此方法的原则是尽量比前台应用晚归档、比前台应用少归档。避免数据仓库中已经归档的数据再次更新的情况出现。如果技术条件允许,能够解析数据库binlog日志,建议使用归档策略2,规避前台算法。具体可以根据自身数据仓库的实际情况进行选择。

(四)维度变化的处理

数据仓库的重要特点之一是反应历史变化,所以如何处理维度的变化是维度设计的重要工作之一。在Kimball的理论中,有三种缓慢变化的处理方式以及衍生出来的几种其他处理方式。阿里巴巴在处理缓慢变化维度方面,有自己独特的特点。比如对于数据量相对可控的表采用快照的方式,通过存储成本换取计算成本的降低;对于数据量巨大的表则引入极限存储的概念,在保证易用性的同时,通过计算成本换取存储成本的降低。最后,简单介绍微型维度在避免维度快速增长方面的作用。

三、第三部分

01

缓慢变化维

仓库的重要特点之一是反应历史变化,所以如何处理维度的变化是维度设计的重要工作之一。缓慢变化维的提出是因为在现实世界中,维度的属性并不是静态的,它会随着时间的流失发生缓慢的变,这一现象称为缓慢变化的维度,简称缓慢变化维。与数据增长较为快速的事实表相比,维度变化相对缓慢。 某些情况下,保留历史数据没有什么分析价值;某些情况下,保留历史数据将会起到至关重要的作用。在Kimball的理论中,有三种缓慢变化的处理方式,下面通过简单的实例进行说明,具体细节请翻阅Kimball的相关书籍。

方法1:重写维度值。 采用此种方式,不保留历史,始终取最新数据。比如,商品的类目于2015年11月16日由类目1变成类目2。采用方法1处理方式,变化前后的数据记录如下。

表 1:变化前商品表和订单表

商品 Key

商品 ID

商品标题

所属类目

其它属性

1000

item1

title1

类目 1

...

订单 Key

日期 Key

商品 Key

交易金额

其它事实

9000

2015-11-11

1000

103.00

...

表2:变化后商品表和订单表

商品 Key

商品 ID

商品标题

所属类目

其它属性

1000

item1

title1

类目 2

...

订单 Key

日期 Key

商品 Key

交易金额

其它事实

9000

2015-11-11

1000

103.00

...

9001

2015-11-16

1000

89.00

...

方法2:插入新的维度行。 采用此种方式,保留历史,维度值变化前的事实和过去的维度值关联,维度值变化后的事实和当前的维度值关联。同上面的例子,采用方法2处理方式,变化前的数据同上,变化后的数据记录如下。

表 3:变化后商品表和订单表

商品 Key

商品 ID

商品标题

所属类目

其它属性

1000

item1

title1

类目 1

...

1001

item1

title1

类目 2

...

订单 Key

日期 Key

商品 Key

交易金额

其它事实

9000

2015-11-11

1000

103.00

...

9001

2015-11-16

1001

89.00

...

方法3:添加维度列。 方法2不能将 变化前后记录的事实归一为变化前的维度或者归一为变化后的维度,比如根据业务需求,需要将11月份的交易金额全部统计到类目2上,方法2无法实现。针对此问题,采用类型3处理方式,保留历史,可以使用任何一个属性列。同上面的例子,采用方法3处理方式,变化前后的数据记录如下。通过变化后的商品表和订单表关联,可以将根据不同的业务需求,将11月份的交易金额全部统计到类目2或类目1上。

表4:变化前商品表和订单表

商品 Key

商品 ID

商品标题

所属新类目

所属旧类目

其它属性

1000

item1

title1

类目 1

类目 1

...

订单 Key

日期 Key

商品 Key

交易金额

其它事实

9000

2015-11-11

1000

103.00

...

表5:变化后商品表和订单表

商品 Key

商品 ID

商品标题

所属新类目

所属旧类目

其它属性

1000

item1

title1

类目 2

类目 1

...

订单 Key

日期 Key

商品 Key

交易金额

其它事实

9000

2015-11-11

1000

103.00

...

9001

2015-11-16

1000

89.00

...

对于选择哪种方式处理缓慢变化维,根据业务的需求来选择适当的处理方式即可,并没有一个完全正确的答案。比如根据商品所属的类目统计淘宝 2015年11月的成交额,商品所属的类目于2015年11月16日由类目1变成类目2,假设业务需求方不关心历史,将所有的成交额都统计到最新的类目2上,则不需要保存历史数据;假设类目1属于某个业务部门,类目2属于另一个业务部门,不同业务部门需要统计各自的业绩,则需要保留历史数据。

02

快照维表

维度的基本概念中介绍了自然键和代理键的定义,在Kimball的维度建模中,必须使用代理键作为每个维度表的主键,用于处理缓慢变化维度。比如上面的例子,商品所属的类目于2015年11月16日由类目1变成类目2。采用Kimball的类型2的处理方式如下:

商品 Key

商品 ID

商品标题

所属类目

其它属性

1000

item1

title1

类目 1

...

1001

item1

title1

类目 2

...

但在阿里巴巴数据仓库建设的实践过程中,虽然我们使用的是Kimball的维度建模的理论,但实际并未使用代理键。我们是如何处理缓慢变化维度,如何记录变化历史的呢?为什么不使用代理键呢?  

首先看一下为什么不使用代理键。第一点原因是,由于阿里巴巴数据量庞大,使用的是阿里巴巴自主知识产权的分布式计算平台ODPS。对于分布式计算系统,不存在事务的概念,对于每个表的记录生成全局唯一的稳定的代理键难度很大,此处稳定指某条记录每次生成的代理键都相同。第二点原因是,使用代理键会大大增加ETL的复杂性,对ETL任务的开发和维护成本很高。  

下面接着讨论不使用代理键如何处理缓慢变化维度。阿里巴巴数据仓库实践中处理缓慢变化维度的方法是快照方式。数据仓库的计算周期一般是每天一次,基于此周期,处理维度变化的方式就是每天一份全量快照。比如商品维度,每天保留一份全量商品快照数据。任意一天的事实均可以取到当天的商品信息,也可以取到最新的商品信息,通过限定日期,采用自然键进行关联即可。此方法既有优势亦有弊端。  

优势主要有以下两点:  

1. 处理缓慢变化维度的方式,简单而有效,开发和维护成本低。  

2. 使用方便,理解性好。数据使用方只需要限定日期即可取到当天的快照数据。任意一天的事实快照和任意一天的维度快照通过维度的自然键进行关联即可。  

弊端主要体现在存储的极大浪费。比如某维度,每天的变化量占总体数据量比例很低,极端情况下,每天无变化,此情况下存储浪费很严重。此方法主要就是实现了牺牲存储获取ETL效率的优化和逻辑上的简化。但是一定要杜绝过度使用这种方法,而且必须要有对应的数据生命周期制度,清除无用的历史数据。  

综合来看,由于现在存储成本远低于CPU、内存等成本,此方法总体来说弊大于利。是否有方法既可以实现上面的优点,同时又可以很好地降低存储呢?答案是肯定的,那就是阿里巴巴的极限存储。

03

极限存储

首先来看历史拉链存储。历史拉链存储是指利用维度模型中缓慢变化维TYPE2的处理方式。这种处理方式是通过新增两个时间戳字段(start_dt和end_dt),将所有以天为粒度的变更数据都记录下来,通常分区字段也是这两个时间戳字段。  

例如,1月1号,卖家A在淘宝网发布了B、C两个商品,前端商品表将生成两条记录t1、t2;1月2号,卖家A将B商品下架了,同时又发布了商品D,前端商品表将更新记录t1,又新生成记录t3;采用全量存储方式, 在1月1号这个分区中存储t1和t2两条记录;在1月2号这个分区中存储更新后的t1以及t2、t3记录。 数据存储如下:

商品

dt

卖家

状态

其他字段

B

20160101

A

上架

...

C

20160101

A

上架

...

B

20160102

A

下架

...

C

20160102

A

上架

...

D

20160102

A

上架

...

如果我们采用历史拉链存储,数据存储如下,对于不变的数据,不再重复存储。

商品

start_dt

end_dt

卖家

状态

其他字段

B

20160101

20160102

A

上架

...

C

20160101

30001231

A

上架

...

B

20160102

30001231

A

下架

...

D

20160102

30001231

A

上架

...

这样下游应用可以通过限制 时间戳字段来获取历史数据,例如, 用户访问1月1号的数据,只需要限制:

代码语言:javascript
复制
start_dt <= 20160101 and end_dt > 20160101            

但是这种存储方式对于下游使用方的存在一定的理解障碍,特别是ODS的数据面向的下游用户包含数据分析师、前端开发等,这些人群不怎么理解维度模型的概念,因此会存在较高的解释成本。另一方面,这种存储方式用 start_dt和end_dt作分区,随着时间的推移,分区数量会极度膨胀,而现行的数据库系统都有分区数量限制。  

为了解决上述的两个问题,我们提出极限存储的方式处理:  

1. 透明化:底层的数据还是历史拉链存储,但是上层我们做一个视图操作或者在hive里做了一个hook,通过分析语句的语法树,把对极限存储前的表的查询转化成对极限存储表的查询。对于下游用户来说,极限存储表和全量存储方式是一样的:  

代码语言:javascript
复制
Select * from A where ds = 20160101;      
等价于        
Select * from A_EXST where start_dt <= 20160101 and end_dt > 20160101;        

2. 分月做历史拉链:假设我们用start_dt和end_dt做分区,并且不做限制,那么可以计算下一年下来历史拉链表最多就可能产生分区数:365*364/2=66430个。如果我们在每个月月初重新开始做历史拉链,目录结构如下 :

代码语言:javascript
复制
|-- 201410/ # 每月一个周期
|---- 20141001/ 201410_INFINITY # 历史截止自当前月仍然没有死亡的记录
|---- 20141001/20141002 # 历史截止自当前月到20141002死亡的记录
|---- 20141001/20141003 # 历史截止自当前月到20141003死亡的记录
…
|----- 20141001/20141031 # 历史截止自当前月到20141031死亡的记录
|----- 20141002/ 201410_INFINITY # 20141002新增或历史原有记录发生变更,当前月仍然没有死亡的记录
|---- -20141002/20141003 # 20141002新增或原有记录发生变更,到20141003死亡的记录
…
|---- 20141002 /20141031/ # 20141002新增或原有记录发生变更,到20141031死亡的记录
|-----20141003/ 201410_INFINITY # 20141003新增或历史原有记录发生变更,当前月仍然没有死亡的记录
…
|---- 20141031/ 201410_INFINITY # 20141031新增或历史原有记录发生变更,当前月仍然没有死亡的记录
|-- 201411/
…

再计算下,一年下来,假设按照每个月30天算,我们产生的分区数:12*(31*30)/2=5580个。  

采用极限存储的处理方式,极大的压缩了全量存储的存储成本,又可以做到对于下游用户透明的效果,是一种比较理想的存储方式。但是其本身也有一定的局限性,首先其产出效率很低,大部分极限存储通常需要t-2;其次对于变化频率高的数据并不能做到节约成本的效果。因此,在实际生产中,做极限存储需要做一些额外的处理:  

1. 极限存储前面有一个全量存储表,全量存储表仅保留最近一段时间的全量分区数据,历史数据通过映射的方式关联到极限存储表。即用户只访问全量存储表,所以对用户来说极限存储是不可见的。  

2. 对于部分变化频率频繁的字段需要在过滤,例如用户表中存在用户积分字段,这种字段的值每天都在发生变化,如果不过滤的话,极限存储就相当于每个分区存储一份全量数据,起不到节约存储成本的效果。

04

微型维度

采用极限存储,需要避免维度的过度增长。比如对于商品维表,每天20多亿数据,如果设计商品维度时,将值变化频繁的属性加入到商品维度中,极限情况是每天所有商品数据都发生变化。此时,极限存储没有意义。反之,每天所有商品数据都不发生变化,此时只需要存储一天的数据即可。  

通过将一些属性从维度表中移除,放置到全新的维度表中,可以解决维度的过度增长导致的极限存储效果大打折扣的问题。解决方法其中之一就是上一节提到的垂直拆分,保持主维度的稳定。另外一种方式是采用微型维度。  

微型维度的创建是通过将一部分不稳定的属性从主维度中移出,并将它们放置到拥有自己代理键的新表中来实现的。这些属性相互之间没有直接关联,不存在自然键。通过为每个组合创建新行的一次性过程来加载数据。比如在淘宝用户维度,用户的注册日期、年龄、性别、身份信息等基本不会发生变化,但用户VIP等级、用户信用评价等级会随着用户的行为不断发生变化。其中VIP等级共有8个值,-1~6;用户信用评价等级共有18个值。假设基于VIP等级和用户信用评价等级构建微型维度,则在此微型维度中共有8*18个组合,即144条记录,代理键可能是1~144。  

以淘宝交易事实表为例,其它维度忽略,星型模式可能表示如下:

但在阿里巴巴数据仓库实践中,并未使用此技术,主要有以下几点原因:  

1. 微型维度的局限性。微型维度是事先用所有可能值的组合加载的,需要考虑每个属性的基数,且必须是枚举值。很多属性可能是非枚举型,比如数值类型,如VIP分数、信用分数;比如时间类型,上架时间、下架时间、变更时间等等。  

2. ETL逻辑复杂。对于分布式系统中生成代理键和使用代理键进行ETL加工都非常复杂,前面章节已经有介绍,ETL开发和维护成本过高。  

3. 破坏了维度的可浏览性。买家维度和微型维度通过事实表建立联系,无法基于VIP等级和信用等级进行浏览和统计。可以通过在买家维度中添加引用微型维度的外键部分解决此问题,但带来的问题是微型维度未维护历史信息


免责声明:本公众号所发布的文章为本公众号原创,或者是在网络搜索到的优秀文章进行的编辑整理,文章版权归原作者所有,仅供读者朋友们学习、参考。对于分享的非原创文章,有些因为无法找到真正来源,如果标错来源或者对于文章中所使用的图片、连接等所包含但不限于软件、资料等,如有侵权,请直接联系后台,说明具体的文章,后台会尽快删除。给您带来的不便,深表歉意。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据学习与分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • (一)维度的聚合
  • (二)维度的拆分
  • 1. 维度的水平拆分
  • 2. 维度的垂直拆分
  • (三)维度的历史归档
  • (四)维度变化的处理
  • 01
  • 02
  • 03
  • 04
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档