把按月分区的表改成按天 — 旧文件保持不动
目标
只对源列(order_ts)设置转换规则来划分,建立隐藏分区,并通过规划测出一个条件会打开多少个文件、多少行。在向表写入的中途,把按月改成按天(分区演进),并通过 metadata 确认:旧文件仍按旧规则原样保留,只有新文件才遵循新规则。
为什么重要
Hive 风格的表把分区做成列。要另外设置 order_date 这样的列,而且写入的人和读取的人都得知道这一列。读取一方如果只对 order_ts 设置条件,就一个分区也跳不过;写入一方如果时区算错了,行就会进入错误的分区。而且,想改变分区方式,就得把整张表重写一遍。
Iceberg 把分区做成规则。像 days(order_ts) 这样,只写源列和转换,引擎就会为每个文件计算分区值并写进清单,读取的一方仅凭 order_ts 条件就能跳过文件。规则带着 partition spec 的 ID 累积在 metadata 中,所以即使改变规则,旧文件仍按旧 spec 继续读取。觉得“好像得重写”,是最常见的误解。
步骤
- 用 /root/ice/part/month.py(应用
ice-part-month)以PARTITIONED BY (months(order_ts))创建lake.part.orders,并把三月整月(31 个文件)一次性提交。 - 用 /root/ice/part/plan1.py(pyiceberg)对
order_ts为 2026-03-10 这一天的条件做规划,写入 /root/ice/part/out/plan_march.json。 - 用 /root/ice/part/evolve.py(应用
ice-part-evolve)把months(order_ts)改为days(order_ts)。 - 用 /root/ice/part/april.py(应用
ice-part-april)一次性提交四月整月(30 个文件)。 - 用 /root/ice/part/plan2.py 对 3 月 10 日和 4 月 10 日的条件做规划,写入 /root/ice/part/out/plan.json。
- 用 /root/ice/part/bucket.py(应用
ice-part-bucket)以PARTITIONED BY (bucket(4, customer_id))创建lake.part.customers,并写入客户数据。 - 用 /root/ice/part/specs.py 把
lake.part.orders的默认 spec ID 和各 spec 的数据文件数写入 /root/ice/part/out/specs.json。 - 在 /root/ice/part/report.md 中写出
## 숨은 파티셔닝、## 파티션 진화、## 버킷三个小节。
参考
- 分区值在清单里,不在目录名里。在 Spark 中用
SELECT spec_id, partition, record_count FROM lake.part.orders.files查看。 - pyiceberg 的
tbl.scan(row_filter=…).plan_files()不打开文件,只凭清单(分区值、列统计信息)来选取要读取的文件。 order_ts是带时区的时间戳(timestamptz)。Python 条件请使用带+00:00的 ISO 字符串。- 常见错误:分区修改之后,用
rewrite_data_files重写三月——在这个实验中,旧文件保持原样才是正确答案,重写会被扣分。想恢复,请在执行DROP TABLE lake.part.orders PURGE之后从第 1 步重新开始。 - 官方文档:Partitioning · Evolution — Partition evolution · Spec — Partition Transforms · Spark DDL — REPLACE PARTITION FIELD
按月划分的表——没有分区列
创建 /root/ice/part/month.py,应用名称为 ice-part-month,以 PARTITIONED BY (months(order_ts)) 创建 lake.part.orders(六个列,'format-version' = '2'),并用一次 append() 写入 /data/ice/orders/2026-03-*.csv 共 31 个文件。
months(order_ts) 使用自 1970 年 1 月起计数的月数作为分区值。请确认 schema 中不会出现新列。评分器会检查 spec 0 的转换是不是 month,第一次提交是不是整个三月,以及这些文件的分区值是否全部是 2026 年 3 月。
一天的条件会打开的文件——按月的局限
用 /root/ice/part/plan1.py(pyiceberg),为 order_ts >= 2026-03-10T00:00:00+00:00 且 < 2026-03-11T00:00:00+00:00 的条件创建扫描,把规划的文件数、这些文件的 record_count 之和、实际命中的行数,按 {"files_planned", "records_scanned", "rows"} 的格式写入 /root/ice/part/out/plan_march.json(现在表里只有三月)。
条件只针对 order_ts 设置,却也会与分区值(月)比较,从而排除其他月份的文件。但即使只想要 3 月 10 日这一天,三月的文件也必须把整个月的数据整体读取。records_scanned 与 rows 的差距就是这部分成本。评分器会以第一个快照(只有三月的时候)为准重新做同样的规划来对照。
分区演进——只有 metadata 会变
创建 /root/ice/part/evolve.py,应用名称为 ice-part-evolve,并运行 ALTER TABLE lake.part.orders REPLACE PARTITION FIELD months(order_ts) WITH days(order_ts)。
会新增 spec 1,默认值(default-spec-id)变为 1。不会产生快照,数据文件也保持不变——只有之后写入的文件才遵循新规则。评分器会检查 spec 列表、默认 spec 和快照数。
四月提交——只有新文件使用新 spec
创建 /root/ice/part/april.py,应用名称为 ice-part-april,用一次 append() 写入 /data/ice/orders/2026-04-*.csv 共 30 个文件。不要重写三月的文件。
新提交的文件是 spec_id 1,每一天分别记录。三月的文件仍是 spec_id 0(月),与它们共存于同一个快照中。读取的引擎会针对每个 spec 分别做规划,所以即使两种规则混在一起,结果也一样。评分器还会检查三月的文件是否仍是第一次提交的路径和 spec。
同样是一天的条件,成本不同
用 /root/ice/part/plan2.py 分别对 3 月 10 日这一天和 4 月 10 日这一天的条件做规划,按 {"march": {"files_planned", "records_scanned", "rows"}, "april": {…}} 的格式写入 /root/ice/part/out/plan.json。
两天的行数差不多,但需要读取的行数相差很大。三月要把整个月的文件整体打开,四月只打开那一天的一个文件。评分器会用现在的表做同样的规划来对照,并检查四月这一边是否只读取了当天的行。
基数高的列用 bucket
创建 /root/ice/part/bucket.py,应用名称为 ice-part-bucket,以 PARTITIONED BY (bucket(4, customer_id)) 创建 lake.part.customers(customer_id STRING, tier STRING, region STRING, signup_date DATE),并写入 /data/ice/customers.csv。
bucket(N, 列) 把值的 32 位 murmur3 哈希对 N 取余,作为分区值。这是规范规定的哈希,所以不管哪个引擎来计算,得到的都是同一个桶——评分器会用 pyiceberg 为每位客户重新计算桶,并与文件的分区值对照。
一张表中的两种规则
用 /root/ice/part/specs.py(pyiceberg),把 lake.part.orders 的默认 spec ID 和各 spec 的数据文件数,按 {"default_spec_id": 정수, "files_by_spec": {"0": 정수, "1": 정수}} 的格式写入 /root/ice/part/out/specs.json(占位符均为整数)。
用 tbl.inspect.files() 的 spec_id 列来统计(content 为 0 的是数据文件)。一个快照中混有两个 spec 的文件,是正常的。
把分区设计变成团队规则
在 /root/ice/part/report.md 中写出 ## 숨은 파티셔닝、## 파티션 진화、## 버킷 三个小节。第二节以数字写入第 5 步中三月和四月的两个 records_scanned 值。
一开始选择按月划分是错了吗?还是数据增多之后不再适合了?另外请写出,如果有朝一日必须重写旧的三月文件,理由应该是什么。