Community编程与开发github.com

ZrobDev/lakehouse

在选型或使用湖仓存储时提供决策支持——Postgres vs 数仓 vs 湖仓、Delta vs Iceberg vs DuckLake、小 Parquet 文件导致的慢查询、分区、OPTIMIZE/VACUUM/expire_snapshots、MERGE 或 schema 演化错误、Catalog 选择、PySpark collect()/UDF/shuffle/skew,以及使用 DuckDB 或 polars 本地测试表。

lakehouse 是什么?

该技能为开发者提供了一套完整的湖仓技术选型决策框架,覆盖了从架构判断到具体操作的各个层面。它首先引导用户从最经济的存储方案(如 Postgres)开始,仅在满足特定触发条件时才升级到云数仓或湖仓,避免为未量化的“未来规模”过度设计。对于数据布局,技能给出了明确的 Parquet 文件大小、行组和分区策略,并提供了 Delta Lake、Apache Iceberg 和 DuckLake 这几种主流开放表格式的选型指导,强调应根据已有引擎和 Catalog 决策而非功能清单。技能还深入讲解了 Catalog(如 Unity Catalog、AWS Glue、Iceberg REST Catalog)的选择、表维护(OPTIMIZE、VACUUM、过期快照)的最佳实践,以及 MERGE 操作、schema 演化和时间旅行中的常见陷阱。此外,对于 PySpark 4.x 用户,它提炼了延迟执行、避免 collect()、优先使用内置函数、处理数据倾斜和 Spark Connect 的关键要点。最后,技能推荐了使用 DuckDB 或 polars 进行本地开发测试的路径,并提供了一个清晰的六步操作流程,帮助用户从获取数据指标到最终验证,系统地实施湖仓方案。该技能支持 Databricks、Spark、DuckDB、Polars 等工具,并可通过 `de-vendors` 技能关联更具体的厂商文档。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ZrobDev/data-engineering-skills/tree/HEAD/skills/lakehouse

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

lakehouse 是做什么的?

面向开放表格式存储和 Spark 代码的参考级决策支持。版本和日期信息存放在 references/landscape-2026-11.md 中;在做出任何版本相关的声明之前,请先查阅该文件。如需了解 Databricks、Snowflake 或 AWS 服务的具体信息,请使用 de-vendors 技能及其指向的官方厂商技能。

1. 湖仓是否必要?

从最便宜且合适的方案开始,仅在以下列出的触发条件真实存在且可量化时才升级。

  • Postgres(或针对单一文件集的 DuckDB):当数据约小于 1 TB,仅由一个引擎读取,且并发分析用户很少时。原因:无需 Catalog、无需压缩、无需运行集群。
  • 云数仓:当大量 SQL 用户需要弹性并发,且无需第二个引擎处理相同的数据时。原因:托管存储、维护和治理已包含在价格中。
  • 湖仓:当至少满足以下条件之一时:数十 TB 或更多;多个引擎(Spark、Trino、DuckDB、Python)必须读取同一份数据;ML 或基于文件的工作负载与 SQL 共享表;对象存储的存储成本在您的数据量下显著低于数仓;开放格式是硬性要求(退出、审计、厂商中立)。
  • 不要为了一个没有具体数字的“未来规模”而选择湖仓。在推荐之前,请询问行数、每日增长量、并发读取器和引擎数量。
  • 对于仍希望使用开放格式的中小型数据:DuckLake 或纯 parquet 加 DuckDB 优于运行 Spark。

2. Parquet 布局

  • 每个文件目标大小 128 MB 至 1 GB;不确定时以 256 MB 至 512 MB 为目标。许多小于 32 MB 的文件会导致列表和打开操作的开销主导查询。
  • 每个文件的行组大小约 128 MB(未压缩)(100 万+ 行);较小的行组会削弱 min/max 剪枝和压缩效果。
  • 仅按查询用于过滤的低基数性列(日期或月份、区域)进行分区。一个好的分区在每个 writer 批次中应包含至少一个目标大小的文件;如果分区平均大小低于 1 GB,则使用更粗的粒度或改用聚簇。
  • 切勿按高基数性 ID 进行分区。对这类列使用聚簇(Delta liquid clustering、Iceberg sort order / Z-order)。
  • 在文件内按最常被过滤的列进行排序或聚簇,以便 min/max 统计信息能剪排行组。
  • 流式或频繁的微批次写入会产生小文件;计划安排压缩任务,不要依赖写入器来确定文件大小。

3. 选择表格式

根据您已有的引擎和 Catalog 来决定,而不是功能清单。

  • Delta Lake:当 Databricks 或 Spark 是主要引擎,或者 Unity Catalog 已经是治理层时选择。Spark 集成最强;其他引擎可以读取它,但写入支持各不相同。
  • Apache Iceberg:当多个引擎必须写入,或者您希望获得厂商中立的 Catalog(REST)时选择。v3 增加了删除向量、行沿袭和变量类型;在升级表之前,请确认路径中的所有引擎和 Catalog 都支持您要使用的 v3 功能,因为旧的读取器在较新的格式版本上会失败。
  • DuckLake:适用于中小型数据、单个团队以及一个您已经作为 Catalog 运行的 SQL 数据库(元数据存在于普通表中)。运行成本低廉,生态系统年轻;当 Spark、Trino 或 Snowflake 必须写入同一批表时,不要选择它。
  • 互操作层(Delta UniForm、针对 Delta 表的 Iceberg 元数据)是只读路径。为每个表选择一个格式作为记录写入器。
  • 以后更改格式意味着重写或元数据转换;请有意识地选择记录写入器。

4. Catalogs

  • Catalog 是提交协调器和访问控制点;在格式细节变得重要之前先选择它。
  • Unity Catalog:以 Databricks 为中心的默认选择;为 Delta 和 Iceberg 提供治理。
  • AWS Glue 和 S3 Tables:当技术栈是 AWS 原生时选择;S3 Tables 为您管理压缩,但会将您绑定到 AWS。
  • Iceberg REST catalog(例如 Polaris):适用于多引擎、多云的中立需求。
  • DuckLake:Catalog 是一个 SQL 数据库;对于多用户使用,用 Postgres 作为其后端;对于单写入器,用 SQLite 或 DuckDB。
  • 绝不让两个 Catalog 注册同一个表路径;向一个 Catalog 提交会损坏另一个 Catalog 的视图。

5. 维护(否则表会退化)

  • 压缩小文件:Delta 的 OPTIMIZE,Iceberg 的 rewrite_data_files,DuckLake 的合并相邻文件。在大量摄入后运行,而不是每次写入。
  • 删除旧数据:Delta 的 VACUUM,Iceberg 的 expire_snapshots 加上孤儿文件删除,DuckLake 的快照过期后进行清理。保留期限必须超过您最长的读取器或时间旅行窗口,因为在正在运行的读取器下删除文件会破坏它。
  • 仅针对出现在过滤器中的列进行聚簇或 Z-order;重新聚簇会重写数据并消耗计算资源。
  • 检查前后的文件数量和平均文件大小;一个既没有改变文件数量也没有改变文件大小的维护作业做的是无用功。
  • 如果引擎支持,先对清理(vacuum)或孤儿文件删除进行空跑(dry-run)。

6. MERGE、schema 演化、时间旅行

  • MERGE:先对源数据在匹配键上进行去重(重复的源匹配会导致失败或加倍应用)。在 ON 子句中包含分区或聚簇谓词,以便引擎剪枝文件而非全表扫描。
  • 倾向于追加加上定期 MERGE 或使用幂等键,而不是在大型表上重复执行全表 MERGE。
  • Schema 演化:添加列是安全的;重命名和类型扩展取决于格式和版本(Delta 中的列映射,Iceberg 中的字段 ID)。在重命名之前,请检查相关读取器的支持情况。绝不要对不受您控制的生产流水线输入启用自动 schema 合并。
  • 时间旅行是调试、审计和回滚的工具,不是备份。它需要保留文件;保留期越长,存储成本越高,元数据操作越慢。有意设置保留期,并对关键表保留真正的备份。

7. PySpark 4.x 要点

  • 转换是惰性的;直到一个动作(action)才执行。构建一个计划,触发一次写入;重复的 count() 或 show() 会重新执行整个计划,除非您有意识地缓存数据。
  • 避免对大型数据使用 collect()、toPandas() 和 .rdd;它们会将所有数据拉取到 driver 端。请先进行限制或聚合。
  • 优先使用内置函数或 SQL 表达式,而不是 Python UDF。UDF 会阻止优化器下推操作并序列化行。如果无法避免,请使用向量化(Arrow)UDF。
  • 宽操作(join、groupBy、window、distinct)会引起数据混洗。在它们之前先过滤和投影;使用 explain() 检查执行计划。
  • 当 join 的一方适合放入 executor 内存时,将其广播(broadcast(df));不要广播一个无界表。
  • 自适应查询执行(AQE)在 Spark 4.x 中默认开启,处理分区合并、数据倾斜 join 和 join 策略切换;在阅读计划后再进行调优,而不是在此之前。
  • 数据倾斜表现为一个缓慢的任务;仅在 AQE 证明不够时,才进行加盐(salt)或隔离热点键。
  • Spark Connect 是客户端-服务器模式:代码作为瘦客户端通过远程会话运行。不要在 Connect 代码中依赖 driver 本地的 JVM 内部机制(sparkContext、RDD API)。
  • 通过压缩来显式指定目标文件大小进行写入,而不是通过猜测数量进行重新分区。

8. 本地优先路径

在付费使用集群之前,使用 DuckDB 或 polars 进行开发和测试。

  • DuckDB 通过扩展读取 Delta 和 Iceberg 并附加 DuckLake Catalog;polars 支持带谓词下推的惰性读取 Delta 和 Iceberg。
  • 使用真实表布局的一个样本或分区进行测试,以使文件剪枝行为匹配。
  • 在真实引擎上确认写入语义(MERGE、删除向量、v3 功能);本地读取器可能会滞后于格式功能。将成功的本地读取视为必要但不充分的条件。

操作流程

  1. 获取数据指标:大小、增长量、写入器、读取器、引擎。2. 通读第 1 节;在最适合的方案处停止。3. 如果选择湖仓:先选择 Catalog,再选择格式(第 4、3 节)。4. 在首次加载之前设置布局和维护计划。5. 本地原型验证(第 8 节),然后在集群上运行并审查 explain() 输出。6. 通过文件数量和查询计划进行验证,而不是依赖假设。

已知陷阱和修复:参见 references/gotchas.md。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相关技能