土法炼钢兴趣小组的算法知识备份

【数据湖与开放表格式】Iceberg、Delta、Hudi 对照与互通

文章导航

分类入口
databasestorage
标签入口
#iceberg#delta-lake#hudi#uniform#xtable

目录

到这里,三种开放表格式的内核都拆过了:第 8–11 章 的 Iceberg 快照树与提交协议、第 12 章 的 Delta 事务日志、第 13 章 的 Hudi timeline 与索引。这一章不再讲单个格式怎么工作,而是回答两个工程问题:

  1. 它们到底差在哪? 把元数据模型、行级更新、并发、引擎生态四个维度并排,每个维度标清对比口径。
  2. 能不能不二选一? 讲 UniForm 和 XTable 两条互通路线,以及一棵选型决策树。

底线先摆明:不做「谁更好」的排名。三家是同一道题(对象存储上的 ACID 表)的不同解法,差异来自不同的设计出发点,选型取决于你的写入模式、引擎栈和更新频率,而不是某个通用分数。

版本锚定:Iceberg 表规范 V2 主线(V3 特性标注)、Delta Lake 3.x、Apache Hudi 1.x;互通方案以 Delta UniForm 官方文档与 Apache XTable(Incubating)官方文档为准。下面凡涉及具体行为均标注来源,不写未经核对的「谁比谁快多少」。


一、对照前先对齐口径

跨格式对比最容易翻车的地方,是拿不同口径的东西硬比。先把几个反复出现的概念对齐,后面的表格才有意义。

记住这四条,下面四维对照表里的每个格子才是可核对的,而不是营销话术。


二、四维对照

2.1 元数据模型

口径:「表当前由哪些文件组成」这个事实存在哪、怎么更新、怎么加速读取

Iceberg Delta Hudi
真相载体 不可变 snapshot 树:metadata.json → manifest list → manifest → data file 有序事务日志 _delta_log:JSON commit + Parquet checkpoint timeline(.hoodie/):instant 三态 + file group/file slice
一次提交 写新 manifest + 新 metadata.json,catalog swap 指针 追加一个版本号递增的 JSON commit(put-if-absent) 在 timeline 写一个 instant(commit/deltacommit),状态置 COMPLETED
加速全量读 manifest list 直接列出快照的 manifest checkpoint 压平日志 + _last_checkpoint 指针 metadata 表 files 分区 + LSM timeline history
文件级裁剪 manifest 里 lower/upper bound、null count add.stats(min/max/null,默认前 32 列) metadata 表 column_stats 分区
分区 隐藏分区(partition spec,查询不写分区谓词也能裁,第 9 章 Hive 目录式分区(分区列可见) Hive 目录式分区 + file group

三者的共性抽象是同一个(第 7 章):不可变数据文件 + 可变元数据指针 + 不 LIST 目录的 planning。差异在元数据怎么组织:Iceberg 是树、Delta 是日志、Hudi 是 timeline+file group。Iceberg 的隐藏分区是它独有的一招,省去「分区列写错就不裁剪」的坑。

2.2 行级更新

口径:update/delete/upsert 的落地机制与读写放大

Iceberg Delta Hudi
copy-on-write 支持(重写文件) 支持(默认,重写文件) CoW 表类型(重写 base 文件)
merge-on-read V2 position/equality delete;V3 deletion vector(第 10 章 deletion vector(特性 deletionVectors第 12 章 第七节) MoR 表类型:base + log file + compaction
主键 upsert 无内建主键索引,靠 MERGE / equality delete 无内建主键索引,靠 MERGE record key + 多种索引(bloom/simple/RLI/bucket),upsert 是一等公民
写放大(删少量行) CoW 高;MoR 低 CoW 高;DV 低 CoW O(file_groups_written);MoR O(records_changed)
读放大 MoR 需合并 delete DV 需读时过滤 MoR O(records_changed);read-optimized 为 0

结论性判断:行级更新这件事,三家都从「重写整文件」走向了「标记/增量」(Iceberg DV、Delta DV、Hudi MoR log),收敛方向一致。真正的分野在 upsert:Hudi 把主键索引做进了表格式本身,对「按主键高频更新」结构性领先;Iceberg/Delta 做 upsert 要靠 MERGE,缺少内建的「key→file」索引,大表随机更新代价更高(第 13 章 第六节)。

2.3 并发控制

口径:冲突检测粒度 + 原子提交来源 + 对外部协调的依赖

Iceberg Delta Hudi
模型 乐观并发(OCC),快照隔离/串行化 乐观并发(OCC),Serializable/WriteSerializable OCC(写者间)+ 表服务非阻塞;1.x 有 NBCC
原子提交来源 catalog 对元数据指针 CAS(DB 锁 / REST 后端 / 条件写,第 11 章 put-if-absent 抢版本号文件(HDFS rename / S3 条件写 / coordinator) 时间线 instant 原子落盘 + 外部锁提供者(ZK/HMS/DynamoDB)
冲突检测 比对快照间的文件级变更 重放并比对并发 commit 的 action 比对重叠 file group
后台服务并发 compaction/expire 作为新快照提交 OPTIMIZE 作为新 commit compaction/clustering/cleaning 基于 instant 计划异步执行,尽量不阻塞写

三家都是 OCC,都把「原子性」外包给某种存储/catalog 原语。差异:Iceberg 把原子点收敛到 catalog(所以 第 15 章 catalog 之争对 Iceberg 尤其重要);Delta 把它放在日志文件的 put-if-absent;Hudi 因为要让后台表服务和写入并发,在 timeline 三态上做了更多文章(NBCC)。

2.4 引擎生态

口径:设计偏向决定的引擎中立性(不给版本快照清单)。

Iceberg Delta Hudi
设计偏向 引擎中立,规范 + REST Catalog 规范公开 起于 Spark/Databricks,协议公开,delta-kernel 推动可移植 自带写入栈(Hudi Streamer),偏流式 upsert
读支持广度 最广(Trino/Spark/Flink/DuckDB/CH 等) 较广,UniForm 进一步补互通 读支持增长中,写仍偏自有栈
写支持 多引擎可写 多引擎可写(kernel) 写最佳路径是 Hudi 自有 writer(Spark/Flink)
内建入湖工具 无(依赖外部框架) 无(依赖外部框架) Hudi Streamer(Kafka/DFS/JDBC,continuous 模式)

结构性判断:Iceberg 在「引擎中立性」上设计最彻底,2024–2026 事实上的收敛点;Delta 通过 delta-kernel 和 UniForm 在补中立性Hudi 的写最佳体验在自有栈,读侧靠 metadata 表能力增长。这不是优劣,是定位:Iceberg 赌「多引擎共享一张表」,Hudi 赌「把入湖 upsert 一条龙做厚」。

flowchart TB
  PROB["对象存储上的 ACID 表"] --> ICE["Iceberg: 快照树 + 引擎中立"]
  PROB --> DEL["Delta: 事务日志 + Spark 生态/kernel"]
  PROB --> HUD["Hudi: timeline+索引 + upsert/流式"]
  ICE -. 互通 .- UNI["UniForm / XTable"]
  DEL -. 互通 .- UNI
  HUD -. 互通 .- UNI

三、互通路线一:Delta UniForm

「选型」之外还有第三条路:让一份数据同时被多种格式的客户端读。因为三家底层都是 Parquet 数据文件 + 一层元数据,理论上可以「数据只存一份,元数据生成多套」。UniForm 走的就是这条路。

3.1 UniForm 是什么

Delta Universal Format(UniForm)让 Delta 表能被 Iceberg 和 Hudi 客户端读取(官方文档 Universal Format (UniForm))。它利用「Delta/Iceberg/Hudi 都由 Parquet 数据文件 + 元数据层组成」这一事实:写仍然是写 Delta,UniForm 在 Delta 提交后异步生成对应的 Iceberg/Hudi 元数据,于是 Iceberg/Hudi 客户端能把这张 Delta 表当成自己格式的表来读。数据文件只有一份。

3.2 怎么开

启用 Iceberg / Hudi 互通的表属性(文档原文):

-- Iceberg
CREATE TABLE T(c1 INT) USING DELTA TBLPROPERTIES(
  'delta.enableIcebergCompatV2' = 'true',
  'delta.universalFormat.enabledFormats' = 'iceberg');

-- Hudi(预览)
ALTER TABLE T SET TBLPROPERTIES ('delta.universalFormat.enabledFormats' = 'hudi');

-- 两者都开
ALTER TABLE T SET TBLPROPERTIES(
  'delta.enableIcebergCompatV2' = 'true',
  'delta.universalFormat.enabledFormats' = 'iceberg,hudi');

要求(文档 Requirements):

3.3 元数据何时生成、怎么追踪

Iceberg 元数据 JSON 路径形如 <table-path>/metadata/v<version>-uuid.metadata.json,BigQuery 等用 metadata JSON 路径注册外部 Iceberg 表的客户端可以直接指。

3.4 限制

文档明确列出(Limitations),这些是工程上最容易踩的:

flowchart LR
  W[Delta writer] -->|写| DLOG[_delta_log]
  DLOG -->|提交后异步| GEN[UniForm 生成元数据]
  GEN --> IMETA["metadata/v..-uuid.metadata.json (Iceberg)"]
  GEN --> HMETA["Hudi 元数据"]
  IR[Iceberg reader] -->|只读| IMETA
  HR[Hudi reader] -->|只读| HMETA
  DATA[(同一份 Parquet)] --- DLOG
  DATA --- IMETA
  DATA --- HMETA

一句话定位 UniForm:「写 Delta,让别人用 Iceberg/Hudi 读」,单向、异步、有限制,但数据不复制。


四、互通路线二:Apache XTable

UniForm 是「Delta 主动生成别的格式」,绑定在 Delta 写入栈上。Apache XTable(Incubating) 走另一条路:做一个独立的元数据转换工具,在任意两种格式之间转换元数据,不绑定某个写入引擎。

4.1 XTable 是什么

XTable 是一个独立工具(官方文档 Creating your first interoperable table):给定一张某格式的源表,它读源表的元数据,生成目标格式的元数据文件,指向同一批底层数据文件,从而让源表能被目标格式的引擎查询——不复制、不移动数据,并尽量保留 commit 历史以支持时间旅行。

和 UniForm 的关键区别:

4.2 怎么用

XTable 跑一次 sync(文档示例)。先写一张源表(这里是 Hudi):

hudi_options = {
   'hoodie.table.name': "people",
   'hoodie.datasource.write.partitionpath.field': 'city',
   'hoodie.datasource.write.hive_style_partitioning': 'true'}
df.write.format("hudi").options(**hudi_options).save("file:///tmp/hudi-dataset/people")

写一个转换配置 my_config.yaml

sourceFormat: HUDI
targetFormats:
  - DELTA
  - ICEBERG
datasets:
  -
    tableBasePath: file:///tmp/hudi-dataset/people
    tableName: people
    partitionSpec: city:VALUE

运行 bundled jar:

java -jar xtable-utilities/target/xtable-utilities_2.12-0.2.0-SNAPSHOT-bundled.jar \
  --datasetConfig my_config.yaml

跑完,源表目录里会多出 Delta、Iceberg 的元数据(schema、commit 历史、分区、列统计),三种格式的引擎都能查同一份数据。配合 catalog 注册(文档 Registering your interoperable tables across multiple catalogs)可让目标表在多个 catalog 里可见。

4.3 边界

flowchart LR
  SRC["源表 (Hudi/Delta/Iceberg)"] --> X["XTable sync (独立 jar)"]
  X --> T1[Delta 元数据]
  X --> T2[Iceberg 元数据]
  X --> T3[Hudi 元数据]
  T1 & T2 & T3 --- D[(同一份 Parquet 数据)]

五、UniForm vs XTable 怎么选

两条互通路线不是替代关系,定位不同:

UniForm XTable
触发方 Delta writer 写时异步 独立工具事后运行
源格式 只能 Delta Hudi/Delta/Iceberg 任意
目标格式 Iceberg、Hudi(读) Delta/Iceberg/Hudi 任意
绑定 绑 Delta 写入栈 不绑写入引擎
新鲜度 异步、可能合并提交 取决于 sync 频率
典型场景 主用 Delta,想让 Iceberg/Hudi 引擎也能读 已有某格式表,想低成本暴露成另一格式

共同的硬约束要记牢:互通的是「读」——别指望让两个不同格式的 writer 同时写同一份数据,那会打架。互通方案解决的是「写一份、多格式读」,不是「多格式写」。还有一条:高级特性(DV、复杂 MoR、CDF 等)常常转不过去,用互通往往意味着退回到「特性的最大公约数」


六、选型决策树

不做排名,给一棵按「写入模式 → 引擎栈 → 更新频率」展开的决策树。每个分支给倾向,不给绝对答案——真要定还得按 第 20 章 的口径做 POC。

START
├─ 主负载是「高频按主键 upsert / CDC 入湖」吗?
│   ├─ 是 → 倾向 Hudi(record index + MoR + Hudi Streamer,第 13 章)
│   │       └─ 但下游引擎不支持 Hudi 写/读?→ 看是否用 XTable 暴露成 Iceberg
│   └─ 否 → 继续
├─ 引擎栈是否以 Spark/Databricks 为中心?
│   ├─ 是 → 倾向 Delta(生态成熟;需被 Iceberg/Hudi 读则开 UniForm)
│   └─ 否 → 继续
├─ 是否要「多引擎(Trino/Flink/DuckDB/CH…)共享同一张表」、引擎中立优先?
│   ├─ 是 → 倾向 Iceberg(规范最完整、收敛点、隐藏分区,第 8–11 章)
│   └─ 否 → 继续
├─ 更新频率高但不是主键 upsert(偶发删改 + 大量读)?
│   ├─ 是 → Iceberg(V3 DV) 或 Delta(DV) 都行,按引擎栈定
│   └─ 否(基本只追加 + 快照读)→ 三家都可,按团队已有技能/catalog 定
END

把判断拆成几条可执行的提问:

  1. 写入模式:是「批量灌 + 快照读」,还是「持续 upsert + 增量消费」?后者强烈指向 Hudi;前者三家都行。
  2. 引擎栈:你的查询/计算引擎是什么?引擎中立优先选 Iceberg;Spark 为中心 Delta 顺手;要 Hudi 的 upsert 又要别的引擎读,考虑 XTable/UniForm 互通。
  3. 更新频率与方式:随机主键更新(Hudi 强)、偶发删改(DV 类即可)、纯追加(无所谓)。
  4. catalog 与治理:Iceberg 的 catalog 生态(REST/Polaris/Unity/Nessie)最丰富,这点 第 15 章 单独讲;catalog 选择会反过来约束格式选择。
  5. lock-in 与退路:选定后想换怎么办?互通方案(UniForm/XTable)能降低 lock-in,但要接受「最大公约数」的特性退化。
flowchart TD
  A{高频主键 upsert / CDC?} -->|是| H[Hudi]
  A -->|否| B{Spark/Databricks 中心?}
  B -->|是| D[Delta]
  B -->|否| C{多引擎中立优先?}
  C -->|是| I[Iceberg]
  C -->|否| E{偶发删改 + 大量读?}
  E -->|是| DV[Iceberg DV 或 Delta DV]
  E -->|否| ANY[三家皆可, 按技能/catalog]
  H -.下游要别的格式.-> X[XTable/UniForm 互通]
  D -.要被 Iceberg/Hudi 读.-> U[UniForm]

七、schema 演进与时间旅行对照

四维对照之外,还有两个工程上天天用到、却最容易踩坑的维度:schema 怎么演进、历史怎么回看。

7.1 schema 演进

口径:能否安全地增/删/改名/重排/拓宽列而不重写历史数据

Iceberg Delta Hudi
演进依据 field ID(不靠位置,第 16 章 schema 字符串;安全改名/删列需开 column mapping(第 12 章 第十节) Avro schema 演进
加列 安全,老文件读补 null 安全 安全
删列/改名 安全(按 ID) 需 column mapping(name/id 模式) 支持完整 schema 演进
类型拓宽 规范允许的拓宽(如 int→long) 支持部分 支持部分
重写数据 不需要 不需要(开 column mapping) 不需要

共性:三家都做到「演进不重写历史」,这正是相对 Hive 表的核心进步。差异在机制:Iceberg 从第一天就用 field ID,删列改名最干净;Delta 默认按物理列名,要安全改名/删列必须开 column mapping(这也是 UniForm Iceberg 要求开 column mapping 的根因,第三节);Hudi 走 Avro schema 演进。

7.2 时间旅行

口径:怎么定位历史版本、能回溯多远、受什么约束

Iceberg Delta Hudi
定位方式 snapshot id 或时间戳 版本号或时间戳 as.of.instant(时间点)
历史保留 snapshot 保留 + expire 日志保留 + VACUUM第 12 章 第十一节) cleaner 策略(第 13 章 第五节)
回溯下限 expire snapshots 后不可读 VACUUM 删文件 / 日志过期后不可读 cleaner 清理后不可读
增量读 incremental scan(快照差) Change Data Feed incremental query(一等公民,含 CDC 格式)

共性:三家都支持「按版本/时间读历史」,且都受「保留策略 vs 存储成本」的同一约束——保留越久越能回溯,但过期文件越多。差异在增量读:Hudi 的 incremental query 是设计核心,Delta 用 CDF、Iceberg 用快照差实现类似能力,但 Hudi 在「自某 instant 以来变了哪些记录」这件事上做得最顺(第 13 章第七节)。运维上三个清理操作(expire / VACUUM / clean)是同一类,调不好都会「时间旅行回不去」或「孤儿文件堆积」(第 20 章)。


八、统计信息与文件裁剪对照

「不读数据就裁文件」是三家共同的性能基石,但统计信息存在哪、有多丰富不同。

Iceberg Delta Hudi
基础列统计 manifest 里 lower/upper bound、null/value count add.stats(min/max/null,默认前 32 列) metadata 表 column_stats 分区
进阶统计 Puffin 文件里的 NDV sketch(如 Theta)等(第 17 章 文件级 stats 为主 metadata 表多模索引(bloom/record/expr/secondary,第 13 章第八节)
裁剪层级 分区裁剪 → manifest 裁剪 → 文件裁剪 → Parquet page 裁剪 日志 stats 文件裁剪 → Parquet page 裁剪 metadata 裁剪 + 索引定位 → Parquet page 裁剪
统计何时算 写入时写进 manifest 写入时写进 add 写入时维护进 metadata 表

三家都把「文件级裁剪」做进了元数据,最后都落到 Parquet 内部的 row group/page 裁剪(第 2 章),这条两级裁剪链路是 第 18 章 评估引擎读湖能力的核心。差异:Iceberg 有 Puffin 承载 NDV 这类高级统计,对优化器估基数有用;Hudi 的 metadata 多模索引把统计和索引统一在一张内部表里,还能加二级索引/表达式索引,把「裁剪」扩展到非主键列。注意所有格式都受「stats 只覆盖部分列」的现实约束(Delta 默认前 32 列),高选择性过滤列要排前面。


九、迁移:格式之间怎么搬

选型不是一锤子买卖,要考虑「搬进来」和「搬出去」两个方向。

9.1 从 Hive 表迁入

三家都提供「就地迁移」(in-place,不重写数据,只生成元数据指向已有 Parquet),代价低但要求数据文件本就兼容:

目标 典型路径
→ Iceberg add_files / migrate 过程,把已有 Parquet 纳入新表元数据(第 20 章
→ Delta CONVERT TO DELTA,对已有 Parquet 目录生成 _delta_log
→ Hudi bootstrap(可只生成元数据指向老文件,或全量重写)

就地迁移的风险:老文件的 schema/分区布局必须和新表一致;迁移后老的写入路径(直接往目录扔文件)必须停掉,否则绕过元数据写入会让表「分裂」。

9.2 格式之间互迁

方式 重写数据? 单/双向 说明
UniForm Delta→(Iceberg/Hudi),只读 第三节;不能与 DV 共存
XTable 任意互转 第四节;周期性 sync
全量重写(CTAS) 任意 读源表写新格式表,代价最高但最干净,特性不受互通限制

判断原则:只要「读侧多格式」就够,优先 UniForm/XTable(不重写数据)要彻底换格式、且要用目标格式的全部特性,才做全量重写。互通方案省钱省时,但前面说过——会退化到特性的最大公约数。

flowchart LR
  HIVE[Hive 目录表] -->|add_files/CONVERT/bootstrap| LF[湖表格式]
  LF -->|UniForm/XTable, 不重写| OTHER[另一格式只读视图]
  LF -->|CTAS 全量重写| NEW[另一格式完整表]

十、互通与选型的常见误区

把工程上反复出现的误区集中澄清,每条都对应前面的证据:

误区 澄清
「选了某格式就被锁死」 互通(UniForm/XTable)能让数据被多格式读,lock-in 比想象的低;但要接受特性最大公约数
「Hudi 总是更快」 只在「高频主键 upsert / 增量消费」上结构性领先;纯追加 + 快照读,三家差异不大,Iceberg/Delta 更简单(第二节)
「UniForm 是双向互通」 错。Iceberg/Hudi 客户端只能 UniForm 表,写必须回 Delta(第三节限制)
「开了 DV 还能用 UniForm」 不行。UniForm 与 deletion vector 互斥,要互通就放弃 DV 或先 REORG PURGE(第三节)
「互通=实时一致」 UniForm 异步、可能合并提交;XTable 周期 sync。都有延迟(第三、四节)
「多个格式可以同时写一份数据」 不要。互通解决「写一份多格式读」,多 writer 跨格式写同一数据会冲突(第五节)
「列统计自动覆盖所有列」 Delta 默认前 32 列;高选择性过滤列要排前面,否则裁剪退化(第八节)
「换格式必须重写数据」 不一定。读侧多格式用 UniForm/XTable 不重写;只有要全部特性才全量重写(第九节)

这些误区的共同根源,是把「互通」当成「免费的银弹」。互通是有边界的工程手段:它降低 lock-in、支持多格式读,但不消除格式差异,也不让你同时享受所有格式的高级特性


十一、把 08–13 串起来

这一章是第三、四部分的收口,回看前面每一章在「对照」里的位置:

往后:catalog(第 15 章)决定这些格式怎么被统一管理和并发提交;查询引擎(第 18 章)决定本章「引擎生态」一列的实际落地;选型迁移运维(第 20 章)把本章决策树落到 POC 与迁移路径上。


十二、小结


返回 系列目录 | 上一篇:Apache Hudi | 下一篇:Catalog 之争

参考资料

  1. Delta Lake 文档,Universal Format (UniForm)(docs.delta.io,latest)— 启用方式、要求、异步生成、限制(含 DV 不兼容、只读)。
  2. Apache XTable(Incubating)文档,Creating your first interoperable table(xtable.apache.org)— sync 配置、源/目标格式、bundled jar 用法。
  3. Apache Hudi 文档,Table & Query Types / Indexes(hudi.apache.org,1.x)— CoW/MoR、索引体系(对照 2.2/2.4 口径)。
  4. Apache Iceberg 表规范 V2/V3 与 apache/iceberg(1.x);Delta 协议 PROTOCOL.md(master)。
  5. 本系列 第 8–11 章 Iceberg 内核第 12 章 Delta 事务日志第 13 章 Apache Hudi第 15 章 Catalog 之争

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-30 · database / storage

【数据湖与开放表格式】表格式为什么存在

目录式分区表(Hive 表)在对象存储上有三处硬伤:并发写部分提交、list planning 太贵、缺快照隔离与原子提交。本文拆开放表格式补上的四件事——原子提交、快照隔离、文件级统计裁剪、schema 与分区演进,并抽象出三家共有的『元数据指针 + 不可变数据文件』骨架。

2026-06-29 · database / storage

【数据湖与开放表格式】Parquet · Iceberg · Delta · Hudi 内核拆解

拆解 lakehouse 的两层基础:列式文件格式(Parquet/ORC/Arrow)与开放表格式(Iceberg/Delta/Hudi)。讲清没有数据库进程时,如何在对象存储上做 ACID、行级更新、快照与并发,以及 catalog、查询引擎、流式入湖如何拼成可运维的湖仓。面向数据平台工程师与从 OLAP/数仓转型的开发者。

2026-06-30 · database / storage

【数据湖与开放表格式】Lakehouse 全景:从 Hive 表到开放表格式

Hive 目录式分区表把『表』等同于『一组目录加 metastore 里的分区行』,于是没有原子提交、planning 要 LIST 目录、schema 与分区演进常要重写。本文用这三个硬伤切入,讲清 lakehouse 把表拆成『不可变数据文件 + 可变元数据指针 + catalog』三层后各自解决了什么,并给出全系列的分层地图。

2026-06-30 · database / storage

【数据湖与开放表格式】Iceberg 元数据树

拆解 Iceberg 的四层元数据:catalog 指针 → metadata.json → manifest list(snapshot)→ manifest file → data file。讲清 snapshot 与 manifest 里的分区数据和列级 stats(lower/upper bound、null/value count)如何让一次查询不 list 目录就收敛到文件集合,并给出表规范 V1/V2/V3 的版本边界。基于 pyiceberg 0.11.1 真实建表逐层 dump。


By .