4. S3数据湖分层设计

在S3上构建数据湖,最容易失败的方式不是选错存储类型,而是把所有数据都堆进同一个前缀下,靠文件名区分来源和状态。分层设计的核心目的,是让每一层承担明确的数据责任:原始区保存不可变的事实,清洗区承载标准化过程,可信区提供经过校验的权威数据,消费区面向具体查询和下游应用做裁剪与聚合。四层之间通过目录前缀、分区键、文件格式和生命周期规则形成一条可追溯、可重跑、可治理的数据链路。

四层目录规划:用前缀表达数据责任

推荐以存储桶为边界,按层建立顶层前缀,再按“来源系统/数据集/分区”组织。一个可落地的目录结构如下:

s3://company-data-lake/
  raw/            # 原始区:只追加,不修改
    orders/
      source=mysql/
      dt=2025-03-01/
        orders_20250301_001.json.gz
  cleaned/        # 清洗区:标准化、去重、类型转换
    orders/
      dt=2025-03-01/
        part-00000.parquet
  trusted/        # 可信区:校验通过、口径统一
    orders/
      dt=2025-03-01/
        part-00000.parquet
  consumption/    # 消费区:面向主题的聚合与宽表
    sales_daily/
      dt=2025-03-01/
        part-00000.parquet

原始区必须保持不可变:同一批数据重复写入时,用唯一文件名或写入时间戳区分,而不是覆盖旧文件。这样当清洗逻辑出错时,可以回到原始区重跑,而不依赖上游系统重新推送。清洗区和可信区允许按分区覆盖重写,但每次重写应记录批次ID或版本号,便于回溯。消费区则按查询模式组织,例如按天聚合的销售宽表、按用户维度的画像表,避免让BI工具直接扫描可信区的明细。

分区策略:分区键决定扫描成本

S3本身没有索引,分区是减少扫描量的主要手段。分区键应选择查询中高频出现的过滤字段,且基数适中。常见组合是“日期 + 业务维度”,例如 dt=2025-03-01/region=cn-north/。日期分区几乎总是必要的,因为大多数分析都带时间范围;业务维度分区则取决于查询模式,如果经常按地区、渠道、租户过滤,就值得加入。

需要避免两个极端:分区过粗,比如只按年分区,单分区文件过大,查询扫描量高;分区过细,比如按用户ID分区,会产生大量小文件,元数据操作和列举成本反而上升。经验上,单个分区文件数量控制在几十到几百个、单文件大小在128MB到1GB之间比较理想。如果原始数据是流式小文件,应在清洗区通过合并任务压缩成较大Parquet文件。

文件格式:从JSON到Parquet的演进

原始区通常保留上游原貌,JSON、CSV、日志文本都可以,压缩方式推荐gzip或zstd,兼顾压缩率和可读性。清洗区开始引入列式格式,Parquet是默认选择,因为它支持列裁剪、谓词下推和高效压缩。可信区继续使用Parquet,并配合Glue Data Catalog或Lake Formation注册表结构,使Athena、Redshift Spectrum、EMR都能直接查询。消费区根据场景选择:面向BI的聚合表用Parquet,面向实时导出的可以用CSV或JSON,但应明确标注为导出格式而非分析格式。

一个常见的教学示例是:原始区每天到达约20GB的JSON订单数据,清洗后转为Parquet并压缩,体积通常降到3~5GB;可信区再做一次分区整理和列裁剪,扫描量进一步下降。这个比例不是固定值,但能说明格式选择对查询成本的影响。

生命周期管理:让数据按价值迁移

四层的数据价值随时间衰减速度不同。原始区需要长期保留以满足审计和重跑需求,但可以迁移到低频访问或归档存储;清洗区是中间产物,保留周期可以短一些,例如30~90天;可信区是核心资产,保留时间最长;消费区按业务需要保留,过期后可删除或重建。生命周期规则应直接绑定到前缀,例如:

raw/         → 30天后转STANDARD_IA,180天后转GLACIER
cleaned/     → 30天后转STANDARD_IA,90天后过期删除
trusted/     → 90天后转STANDARD_IA,365天后转GLACIER
consumption/ → 60天后过期删除(可按需重建)

这些参数是示例,实际应根据合规要求、重跑窗口和查询频率调整。关键原则是:生命周期规则要显式、可审计,并且与数据责任匹配,不要让原始区被意外删除,也不要让消费区长期占用高成本存储。

S3数据湖四层架构与数据流 原始区 raw/ 不可变 · 只追加 JSON / CSV / 日志 gzip / zstd 压缩 source=mysql/ dt=2025-03-01/ 保留:长期/归档 清洗区 cleaned/ 标准化 · 去重 · 类型转换 Parquet 列式存储 小文件合并 dt=2025-03-01/ 批次ID / 版本号 保留:30~90天 可信区 trusted/ 校验通过 · 口径统一 Parquet + Catalog Athena / Spectrum dt=2025-03-01/ 数据质量规则 保留:长期/核心资产 消费区 consumption/ 聚合 · 宽表 · 导出 sales_daily/ user_profile/ dt=2025-03-01/ BI / 下游应用 保留:按需/可重建 抽取 清洗 聚合 生命周期迁移策略(示例) raw/ STANDARD STANDARD_IA GLACIER cleaned/ STANDARD IA 删除 trusted/ STANDARD STANDARD_IA GLACIER 关键设计原则 • 原始区不可变,支持重跑与审计 • 分区键匹配查询过滤条件,控制单分区文件数量与大小 • 清洗区起用Parquet,可信区注册Catalog,消费区按主题聚合 • 生命周期规则绑定前缀,显式可审计 • 每层保留周期与数据责任匹配 • 消费区可重建,不承担唯一数据源角色

落地时的检查要点

在真正创建存储桶和生命周期规则之前,建议先确认几件事:原始区的写入是否只追加、不覆盖;清洗任务是否按分区幂等重写;可信区是否有明确的数据质量校验规则和失败处理路径;消费区的查询是否真的只扫描必要分区。如果使用Athena,可以通过 EXPLAIN 或查询统计查看扫描量,验证分区裁剪是否生效。如果使用Glue ETL,注意作业书签与分区覆盖的配合,避免重复处理或漏处理。

分区键一旦确定,后期变更成本较高。建议在原始区保留足够的上游字段,使清洗区可以灵活选择分区维度,而不是在原始区就丢掉可能用于分区的列。

四层设计不是必须严格四层,小型场景可以合并清洗区和可信区,但“原始不可变、中间可重跑、消费可重建”这三个原则应当保留。目录规划、分区策略、文件格式和生命周期管理四者相互约束:分区决定扫描成本,格式决定存储与查询效率,生命周期决定长期成本,而目录结构决定这些规则能否被清晰表达和自动执行。