数据存储层设计|拆解 data_engineering_book 的数仓/湖/湖仓架构

0 阅读8分钟

数据存储层设计|拆解 data_engineering_book 的数仓/湖/湖仓架构

本文基于《Data Engineering Book》核心内容,系统拆解数仓、数据湖、湖仓架构的核心差异,梳理存储层设计原则,并通过 Delta Lake 实现湖仓架构极简 Demo,最后总结存储选型决策树,为数据工程实践提供参考。

图2_2_数据湖仓架构.png

GitHub地址: github.com/datascale-a…

在线链接:datascale-ai.github.io/

一、数仓(Kimball/Inmon)、数据湖、湖仓的核心差异

1. 核心定义与设计理念

架构类型核心定义设计理念
数仓(Kimball)面向主题、集成的、非易失的、随时间变化的结构化数据存储,采用维度建模(星型/雪花模型)面向业务场景(如销售、风控),强调快速查询和报表生成,数据入库前完成清洗、转换(ETL)
数仓(Inmon)先构建企业级数据仓库(EDW),再基于EDW派生数据集市,采用第三范式建模强调数据一致性和企业级统一视图,适合大型企业跨部门数据整合
数据湖存储原始、结构化/半结构化/非结构化数据的海量存储库,无严格schema前置约束先存储后处理(ELT),支持多类型数据、多分析场景(批处理、流处理、机器学习)
湖仓(Data Lakehouse)融合数仓的结构化管理能力与数据湖的低成本、多类型数据存储能力的混合架构既保留数据湖的灵活性,又具备数仓的ACID事务、Schema校验、数据治理能力

2. 关键特性对比

特性Kimball/Inmon 数仓数据湖湖仓
数据类型仅结构化数据结构化/半结构化/非结构化全类型数据
Schema 约束写入时校验(Schema-on-Write)读取时校验(Schema-on-Read)写入时校验+灵活扩展
事务支持支持(如传统数仓Teradata)弱/无(早期HDFS/S3)支持(Delta Lake/Hudi/Iceberg)
数据质量高(入库前清洗)低(原始数据为主)高(支持数据校验/治理)
查询性能高(预聚合/索引)低(原始数据扫描)高(索引/缓存/优化器)
成本高(专用数仓硬件/存储)低(对象存储如S3/OSS)中低(对象存储+计算分离)
适用场景固定报表、BI分析数据探索、机器学习、原始数据存储混合场景(BI+探索+ML)

二、书中的存储层设计原则(分层、分区、生命周期)

《Data Engineering Book》强调存储层设计需兼顾可维护性、性能、成本,核心原则围绕分层、分区、生命周期三大维度:

1. 分层设计原则

数仓/湖仓的分层核心是“数据隔离、逐步净化、按需服务”,典型分层如下:

  • 原始层(Raw/ODS层):存储原始数据(如日志、数据库快照、API原始返回),保留数据原貌,支持重算;
  • 清洗层(Clean/CDM层):对原始层数据做去重、格式标准化、缺失值处理,保留结构化/半结构化格式;
  • 整合层(Integrated/DWD层):按业务主题整合数据,构建维度模型(如用户、商品、订单主题);
  • 聚合层(Aggregated/DWS/DM层):基于整合层做汇总计算(如日/周/月销售额、用户活跃度),面向BI/报表直接服务。

设计要点

  • 每层数据仅依赖下一层,避免跨层依赖;
  • 分层粒度与业务需求匹配(避免过度分层增加复杂度);
  • 每层数据保留元数据(来源、加工时间、责任人)。

2. 分区设计原则

分区的核心是“减少数据扫描范围,提升查询性能”:

  • 分区键选择:优先选查询高频字段(如时间、地域、业务线),时间字段(dt/hour)是最常用分区键;
  • 分区粒度:兼顾查询效率与分区数量(如按日分区,避免按秒分区导致百万级小分区);
  • 分区格式:统一分区命名规范(如dt=2024-05-20、region=cn-north-1),支持分区过滤下推;
  • 混合分区:大表可采用“时间+地域”复合分区(如dt=2024-05-20/region=cn)。

3. 生命周期管理(Data Lifecycle Management, DLM)

核心是“按数据价值动态调整存储策略,降低成本”:

  • 冷热数据分层
    • 热数据(近7天):存储在高性能介质(如SSD、Delta Lake 活跃分区),支持快速查询;
    • 温数据(7天~3个月):存储在低成本对象存储(如S3标准存储);
    • 冷数据(3个月以上):归档至低成本归档存储(如S3 Glacier),按需恢复;
  • 数据过期策略:非核心数据按业务规则自动删除(如日志保留6个月);
  • 数据优化策略
    • 热数据定期做Compaction(合并小文件)、Optimize(优化索引);
    • 冷数据做压缩(如Parquet Snappy压缩),降低存储占用。

三、实操:基于 Delta Lake 实现湖仓架构极简 Demo

Delta Lake 是湖仓架构的核心组件(支持ACID事务、Schema校验、时间旅行),以下基于 PySpark + Delta Lake 实现极简湖仓Demo:

1. 环境准备

需安装依赖:

pip install pyspark delta-spark

2. 核心代码实现

from pyspark.sql import SparkSession
from delta.tables import DeltaTable
import pyspark.sql.functions as F

# 1. 初始化Spark Session(集成Delta Lake)
spark = SparkSession.builder \
    .appName("DeltaLake_Lakehouse_Demo") \
    .master("local[*]") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
    .getOrCreate()

# 2. 模拟原始层(ODS)数据写入(模拟日志数据)
ods_data = spark.createDataFrame([
    ("user1", "2024-05-20", "click", "cn-north-1"),
    ("user2", "2024-05-20", "purchase", "cn-south-1"),
    ("user1", "2024-05-21", "click", "cn-north-1"),
    ("user3", "2024-05-21", "view", "cn-east-1")
], schema=["user_id", "dt", "action", "region"])

# 写入原始层(按dt+region分区)
ods_path = "./delta_lakehouse/ods/user_behavior"
ods_data.write \
    .mode("overwrite") \
    .partitionBy("dt", "region") \
    .format("delta") \
    .save(ods_path)

# 3. 清洗层(Clean):去重+格式校验
clean_path = "./delta_lakehouse/clean/user_behavior"
spark.read.format("delta").load(ods_path) \
    .dropDuplicates(["user_id", "dt", "action"])  # 去重
    .filter(F.col("dt").isNotNull())  # 过滤空时间
    .write \
    .mode("overwrite") \
    .partitionBy("dt") \
    .format("delta") \
    .save(clean_path)

# 4. 聚合层(Agg):按日统计行为次数
agg_path = "./delta_lakehouse/agg/daily_action_count"
spark.read.format("delta").load(clean_path) \
    .groupBy("dt", "action") \
    .agg(F.count("user_id").alias("user_count")) \
    .write \
    .mode("overwrite") \
    .partitionBy("dt") \
    .format("delta") \
    .save(agg_path)

# 5. 湖仓核心能力验证:ACID事务(更新聚合层数据)
delta_table = DeltaTable.forPath(spark, agg_path)
# 模拟修正2024-05-20的click数据
delta_table.update(
    condition=F.col("dt") == "2024-05-20" & F.col("action") == "click",
    set={"user_count": F.col("user_count") + 1}
)

# 6. 数据查询(验证结果)
print("聚合层最终数据:")
spark.read.format("delta").load(agg_path).show()

# 7. 生命周期管理:清理90天前的冷数据(模拟)
delta_table.vacuum(retentionHours=2160)  # 90天=2160小时

# 停止Spark Session
spark.stop()

3. 核心能力验证

  • ACID事务:update操作保证数据原子性,不会出现部分更新;
  • Schema校验:若写入数据字段类型不匹配,Delta Lake会直接报错;
  • 分区查询:查询dt=2024-05-20的数据时,仅扫描对应分区,性能提升;
  • 时间旅行:可通过versionAsOf读取历史版本数据(如spark.read.format("delta").option("versionAsOf", 0).load(agg_path))。

四、存储选型决策树(书中总结)

《Data Engineering Book》总结的存储选型决策树,核心是“从场景出发,逐步缩小选型范围”:

第一步:明确核心需求

  • 需求1:是否仅需支持结构化数据+固定BI报表?
    • 是 → 优先选传统数仓(如Snowflake、Redshift、Kimball/Inmon架构);
    • 否 → 进入下一步;
  • 需求2:是否需要存储非结构化/半结构化数据(如日志、视频、JSON)?
    • 否 → 回到传统数仓;
    • 是 → 进入下一步;

第二步:评估性能与事务需求

  • 需求3:是否需要ACID事务、Schema校验、数据版本管理?
    • 否 → 优先选纯数据湖(如S3+Hive、ADLS Gen2);
    • 是 → 进入下一步;

第三步:评估成本与架构复杂度

  • 需求4:是否接受计算与存储分离、低成本对象存储?
    • 是 → 优先选湖仓架构(Delta Lake/Hudi/Iceberg + S3/OSS);
    • 否 → 选企业级湖仓产品(如Databricks Lakehouse、Snowflake + Iceberg);

第四步:补充约束校验

  • 约束1:实时性要求(毫秒级)→ 湖仓+流处理(Delta Lake + Spark Streaming);
  • 约束2:成本敏感 → 纯数据湖(冷数据归档)+ 按需计算;
  • 约束3:数据治理要求高 → 湖仓+数据目录(Hive Metastore、AWS Glue);

选型决策树简化图

开始
├─ 仅结构化数据+固定BI → 传统数仓(Kimball/Inmon)
└─ 需多类型数据存储
   ├─ 无需ACID/事务 → 纯数据湖
   └─ 需ACID/事务/治理
      ├─ 成本敏感+计算存储分离 → 开源湖仓(Delta Lake/Hudi/Iceberg)
      └─ 企业级需求 → 商业湖仓(Databricks/Snowflake)

总结

数仓、数据湖、湖仓的核心差异在于“数据类型支持、Schema 策略、事务能力”,湖仓是当前兼顾灵活性与治理能力的主流选择;存储层设计需严格遵循分层、分区、生命周期原则,而 Delta Lake 是实现湖仓架构的轻量、高效工具;存储选型需从业务场景出发,通过决策树逐步缩小范围,平衡性能、成本、治理需求。

如果你想进一步详细了解数据存储层设计,不妨到项目仓库 github.com/datascale-a… 获取完整代码和实战文档,也欢迎在仓库中交流经验, 觉得有帮助的朋友,欢迎点个 Star ⭐️ 支持一下!