数据工程设计模式——数据仓库与数据分析

82 阅读15分钟

引言(Introduction)

在当今竞争激烈的环境中,理解客户行为至关重要。本章将探讨企业如何将原始的电商数据转化为数据分析数据仓库驱动的强大洞察。我们将延续前几章的电商网站示例,讨论若干商业智能(BI)用例,并展示如何用数据分析解决这些问题。同时引入数据仓库的概念,并说明其在数据分析解决方案设计中的作用。

本章还将深入解析多款数据仓库所采用的列式存储格式性能成本上的收益;学习利用物化视图降低 BI 仪表盘绘图时延的设计模式;最后理解数据仓库中用于存储数据的**星型(Star)雪花(Snowflake)**模式。

结构(Structure)

本章涵盖以下主题:

  • 数据分析与商业智能
  • 数据仓库
  • 数据仓库的特性

目标(Objectives)

读完本章后,你将理解常见的数据分析商业智能用例,以及这些用例与日常运营型用例的差异。你还将了解数据仓库软件,并认识到其较之传统数据库更适合解决数据分析问题。你也将理解物化视图如何快速生成预定义类型的报表(如周报、月度销售报表)。

最后,你会掌握行式存储列式存储之间的取舍,以及星型/雪花模式的概念。

数据分析与商业智能(Data analytics and business intelligence)

上一章我们回顾了支撑日常业务运营的设计模式,这些模式利用了多种数据库及其特性以处理业务操作。然而,除了满足运营需求,企业还需要通过制定未来战略来实现优化与增长——这一步骤即商业智能(BI)

延续电商网站示例,业务方要制定未来策略,需要:

  • 找出现有业务中的瓶颈与问题并加以修复;
  • 识别表现良好的做法并考虑加大投入

举例如下,以更好地理解 BI 的作用:

  • 找出去年某个自然月销量排名前五的产品,并据此优化今年的库存配比;
  • 识别差评(或高退货率)的产品,评估是否下架
  • 找出发货耗时最长的地区,发起专项以定位并修复物流瓶颈
  • 运用实时预测分析为特定用户识别最佳商品推荐

数据分析帮助回答上述业务问题。本章将以这些例子来讲解数据分析的核心概念。

先从月度销售报表的例子入手:我们关心某一自然月的前五畅销品。不同月份的畅销品会受季节(夏/冬)、节假日等外部因素影响。例如,夏季人字拖与太阳镜可能热卖;而 12 月则是毛衣与圣诞装饰品名列前茅。电商企业需要预估未来数月的潜在需求,并提前备货以满足即将到来的需求。

从这个用例可以看出,我们在查看历史数据。问题是:历史数据放哪?当然可以放在运营型数据库里,但这会导致库表急剧膨胀,进而影响在线业务查询性能

例如,典型电商应用可能仅在运营库中保留近三个月的已完成订单,而更久远的数据则从运营库迁出。此类历史数据通常会从运营数据库迁移数据仓库中。

数据仓库(Data warehouse)

数据仓库的定义:一个集中式存储库,用于存放和分析历史业务数据,这些数据通常来自一个或多个事务型数据系统。常见的数据仓库软件包括:Amazon Redshift、Google BigQuery、Snowflake、Apache Hive、Apache Druid、ClickHouse 等。

延续我们的示例,来看使用数据仓库的好处,以及它与事务型数据库的差异。为了得到某年的每个月 Top 5 畅销商品,需要把订单数据从事务库传输到数据仓库。正如前几章所述,用户可以建立数据摄取(ingestion)管道来完成这一传输。快速回顾下搭建流程:

  • 确定事务库中的源表(从此表摄取数据)
  • 在目标数据仓库中创建对应目标表
  • 建立源与目标之间的连接
  • 决定摄取机制(批处理 vs. 实时;全量 vs. 增量等)

图 10.1 直观展示了事务数据库的订单表数据仓库订单历史表之间的逻辑连接:

image.png

图 10.1:将事务库 Order 表连接到数据仓库的 Order History 表

在本例中,我们在数据仓库中新建一张用于存放所有历史订单的表,可能的模式如下:

CREATE TABLE order_history(
    Id VARCHAR(32),
    ProductId VARCHAR(32),
    Quantity INT,
    OrderTime TIMESTAMP
);

要获取上一年每个月的销量前五商品,可写出如下 SQL(参数化示例,:input_month 为用户输入):

-- 下面的 SELECT 为参数化查询
-- :input_month 为用户指定的入参
SELECT ProductId, SUM(Quantity) AS totalQuantity 
FROM order_history 
WHERE EXTRACT(YEAR FROM OrderTime) = EXTRACT(YEAR FROM CURRENT_DATE) - 1 
  AND EXTRACT(MONTH FROM OrderTime) = :input_month
GROUP BY ProductId 
ORDER BY totalQuantity DESC 
LIMIT 5;

CREATE TABLESELECT 的语法看,数据仓库与数据库的 SQL 很相似;它们也都旨在高效存储与提供数据。那么,数据库数据仓库的关键差异是什么?

数据库 vs. 数据仓库的差异

借助示例用例来理解二者的核心区别,这也有助于把握一组分析类用例常见的设计模式。

1) 工作负载类型

  • 数据库(OLTP) :面向日常事务工作负载。
    例:按价格列出电视;校验库存并确认订单;保留 3–6 个月订单以便处理退货。
  • 数据仓库(OLAP) :面向分析工作负载。
    例:统计每月 Top 5 畅销商品;找出过去一年退货最多的商品;找出平均评分最低的商品。

数据库通常优化点查小范围扫描;数据仓库则优化在大数据量上执行聚合跨表关联。数据库同时优化读写;数据仓库主要优化读取

2) 数据服务时延

  • 数据库的时延直接影响用户体验(加购、下单等),通常要求亚毫秒到个位数毫秒级响应。
  • 数据仓库服务于数据分析师战略决策,查询面向更大规模数据,秒级时延通常可接受,对业务无直接负面影响。

3) 近期数据 vs. 历史数据

热门电商的历史订单量很大。把大量历史数据留在运营库会推高硬件需求和成本,且并非所有历史数据都与日常运营相关(如一年以前的已完成订单)。这类旧数据可迁移到次级存储,按需提供(第 13 章《冷热数据存储》将详述)。
分析往往需要跨月跨年的历史数据模式,因而数据仓库高效存取历史数据做了优化。

4) 原始数据 vs. 过滤与加工后的数据

运营数据库往往存放原始数据。以订单为例,原始记录可能包含收货/账单地址、配送指示、支付方式、物流/发票信息等,需按原样保存以便随时展示。
数据仓库则不必保留所有原始字段,比如:

  • 分析不需要账单地址,可不存;
  • 配送指示若对 BI 用例无用,也可不存;
  • 可能只需从收货地址中提取城市、邮编等字段入仓。

5) 数据存储格式

  • 行式存储(Row-oriented) :更适合按行读取完整对象(如展示商品全部详情时将整行或整份 JSON 放在一个存储对象中)。运营数据库常用。
  • 列式存储(Columnar) :更适合按列做聚合(如汇总某月 Quantity 之和,把所有数量列紧邻存放可显著提速)。数据仓库常用。
    本章稍后将进一步讨论行式 vs. 列式的取舍。

小结对比

表 10.1:数据库与数据仓库的差异摘要

维度数据库(Database)数据仓库(Data warehouse)
主要用途日常业务运营与执行面向长期的决策与策略
工作负载OLTP(联机事务处理)OLAP(联机分析处理)
数据形态存放日常运营所需的原始数据存放历史/分析/过滤/加工后的数据
读写优化读写均衡优化主要优化读取
访问模式点查 / 小范围扫描大数据量聚合、跨表关联等
存储格式常为行式常为列式
时延目标极低(数毫秒以内)较低(数秒以内)

以上各点共同刻画了事务型数据库分析型数据仓库用例、工作负载、数据形态、存储格式与性能预期上的系统性差异。

数据仓库的特性(Features of data warehouse)

理解了数据库与数据仓库的关键差异后,下面探讨数据仓库为 OLAP 应用在数据存储设计中常用的几项重要特性。这些特性是数据仓库所特有的,传统数据库中并不常见。

物化视图(Materialized views)

上一章我们学习了数据库视图(view)以及其在访问控制中的作用。数据库视图本质上是描述一张逻辑表元数据;视图中的过滤条件在查询执行时才会被评估。现在,以“识别差评商品”的分析查询为例,来理解物化视图的适用场景。

要列出差评商品,需要查看商品评价。假设每条评价包含以下字段:

  • Product ID:被评价商品的 ID
  • Product type:如智能手机、食品、饮料、文具等
  • Order ID:提交该评价的订单 ID
  • 其他订单信息:收货地、发货地等
  • Rating:评分 1–5,1 最低,5 最高

为在数据仓库中存放这些信息,可建表如下:

CREATE TABLE product_ratings(
    ProductId VARCHAR(32),
    ProductType VARCHAR(32),
    ShipLocation VARCHAR(32),
    DispatchLocation VARCHAR(32),
    OrderId VARCHAR(32),
    Rating INT
);

获取评分最低的前五个商品:

SELECT ProductId, AVG(Rating) AS avgrating 
FROM products_ratings 
GROUP BY ProductId
ORDER BY avgrating ASC
LIMIT 5;

该查询需要执行两类高消耗操作:

  1. 遍历 product_ratings 全表;2) 对每个商品计算平均分并按 ProductId 分组

对于大型电商,会有多位分析师各自关注某类商品的分析。例如,食品分析师只关心食品类,而不关心消费电子。

食品分析师的查询可能是:

-- 获取评分最差的前 5 个食品
SELECT ProductId, AVG(Rating) AS avgrating 
FROM products_ratings 
WHERE ProductType = 'Food'
GROUP BY ProductId
ORDER BY avgrating ASC
LIMIT 5;

食品销售变化快、查询频繁,若每次都重复高成本的过滤与聚合,会导致响应慢、算力与存储成本高。为此可建立物化视图

物化视图:对原始数据的预计算、预过滤结果的持久化副本,以避免每次查询重复计算。

示例:先把食品类评价预过滤:

CREATE MATERIALIZED VIEW food_ratings AS
SELECT * FROM product_ratings WHERE ProductType = 'Food';

查询引擎可选择利用 food_ratings 来执行上述食品类查询。若食品类评价只占全部评价的 10%,利用该物化视图只需扫描 10% 的数据,极大降低时间与资源消耗。代价是:磁盘会存两份(源表与物化视图),且保持视图最新也有成本。因此,是否创建物化视图始终是权衡

对于高频查询(如食品),物化视图更划算;而低频查询(如消费电子每周跑一次),直接扫全表可能更省成本。

刷新物化视图(Refreshing materialized views)

物化视图保存的是原始数据的预计算副本。当源数据更新后,视图会过期,若不刷新会产生陈旧结果,影响决策。

保持最新需要承担以下开销:

  • 读取源表重新计算的 I/O 成本;
  • 重新计算本身的计算成本;
  • 将结果写回持久化存储的成本。

典型数据仓库支持:

  • 手动刷新;或
  • 配置自动刷新间隔。

若分析小时级销售数据,可将刷新间隔设为数分钟;若是按日分析,可设为数小时。对于季度报表,可关闭自动刷新,出报表时手动刷新即可。

数据库视图 vs. 物化视图

表 10.2:数据库视图与数据仓库物化视图对比

  • 数据库视图:不保存预计算副本;仅存视图元数据不减少查询执行开销;不占额外磁盘;始终最新(无需刷新);查询时在 FROM 中直接写视图名
  • 物化视图:保存预计算、预过滤数据副本;元数据与数据落盘;显著降低查询开销;占用额外磁盘;需刷新以保持最新;查询时通常写表名,由查询优化器自动选择是否命中物化视图。

注:一些现代运营数据库(如 Oracle)也支持物化视图

列式存储格式(Columnar storage format)

假设分析师希望识别退货的模式,会提出如下问题:

  • 某个用户占总退货量的比例?
  • 某个地区的退货量占比?
  • 某个商品/品类的退货量?

这是探索性分析:分析师会调整参数反复查询,直到发现规律。探索性分析常需访问大量数据,成本高。为降低成本,应选择合适的存储格式——此处为列式存储

以退货表为例:

CREATE TABLE returns(
    OrderId VARCHAR(32) PRIMARY KEY,
    ProductId VARCHAR(32),
    Region VARCHAR(32),
    UserId VARCHAR(32),
    ProductType VARCHAR(32)
);

示例查询:

-- 总退货数
SELECT COUNT(*) FROM returns;

-- 某品类的总退货数
SELECT COUNT(*) FROM returns WHERE ProductType = 'Food';

-- 某地区的总退货数
SELECT COUNT(*) FROM returns WHERE Region = 'NY';

这些查询每次只用到单列,却需在大数据量上扫描。用列式存储可优化。

行式 vs. 列式:一个示例

仍以 returns 表为例。磁盘以固定大小的页(page)存放数据,查询成本最终取决于读取的页数

假设:

  • 页大小 256B
  • 每行 5 个 VARCHAR(32)(每值 32B),单行 160B

行式存储会把同一行的 5 列放在一起,导致在按列统计(如统计 ProductType='Food' 的数量)时,需要读完所有页
列式存储将同一列的数据连续存放,此时只需读取存放 ProductType那几页即可完成统计。

由于列数有限,列式引擎很容易在表元数据中记录每一列的起始页位置。

列式存储的优劣:

  • 优势:除扫描页数减少带来的性能与成本优势外,列式还可获得更高压缩比(相似数据相邻存放)。
  • 限制:重建完整行记录的开销更高(各列相距较远,需要“拼接”)。

星型与雪花模式(Star schema & Snowflake schema)

数据仓库存放历史数据,数据量大、查询代价高。一个可控的权衡是采用合适的建模模式。两种常见模式:星型模式雪花模式

继续订单示例。事务应用在下单时,往往把所有细节存在同一行,以简化业务逻辑,例如:

CREATE TABLE orders(
    OrderId VARCHAR(32) PRIMARY KEY,
    ProductId VARCHAR(32),
    ProductType VARCHAR(32),
    ProductSize INT,
    ProductColor VARCHAR(32),
    Quantity INT,
    UserId VARCHAR(32),
    ShippingName VARCHAR(32),
    ShippingAddress VARCHAR(256),
    BillingName VARCHAR(32),
    BillingAddress VARCHAR(256),
    ShippingInstructions VARCHAR(256)
);

但这会在表内产生大量重复信息,导致存储成本上升。入仓时,数工往往选择规范化(Normalization),将一张事实表拆分为事实表 + 若干维表(如订单详情、商品详情、账单详情、配送详情),整体结构呈星形,即星型模式

若还需进一步按邮编、城市等维度分析,可继续把维表拆成子维表,这些子维表与维表相连(而非与事实表直连),结构呈雪花,即雪花模式

星型 vs. 雪花:如何取舍?

从“只有事实表”演进到星型,可避免大量重复;进一步到雪花,可尽量消除重复、降低存储成本。
但代价是查询时需要更多 JOIN,JOIN 会占用更多内存并增加计算开销。设计时需在存储成本查询性能之间做权衡

小结

本章从多种 BI / 数据分析用例出发,明确了数据仓库的必要性,并通过实例对比了数据库 vs. 数据仓库。随后深入三项数据仓库关键特性:物化视图、列式存储、星型/雪花模式

  • 解释了数据库视图物化视图的差异与权衡、以及刷新的成本影响;
  • 对比了行式列式的性能/成本取舍;
  • 讨论了规范化对存储与查询的影响,并给出了星型 vs. 雪花的设计权衡。

下一章将介绍数据湖架构,以旅游聚合网站为例探讨数据湖的应用场景,并结合勋章(Medallion)架构讲解数据富化(Enrichment)用例及其在各层预处理存储的收益。