数据存储层设计|拆解 data_engineering_book 的数仓/湖/湖仓架构
本文基于《Data Engineering Book》核心内容,系统拆解数仓、数据湖、湖仓架构的核心差异,梳理存储层设计原则,并通过 Delta Lake 实现湖仓架构极简 Demo,最后总结存储选型决策树,为数据工程实践提供参考。
GitHub地址: github.com/datascale-a…
一、数仓(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 ⭐️ 支持一下!