レイクハウスのテーブル形式 — Apache Iceberg をメタデータで理解する
月で分けたテーブルを日単位に変える — 古いファイルはそのままで
目標
ソース列(order_ts)に変換ルールだけをかけて分ける隠しパーティショニングを作り、条件1つが何ファイル・何行を開くことになるかを、計画で測ります。テーブルに書き込んでいる途中で月単位を日単位に変え(パーティション進化)、古いファイルは古いルールのままにして、新しいファイルだけが新しいルールに従うことを、metadataで確認します。
なぜ重要なのか
Hive方式のテーブルは、パーティションを列として作ります。order_dateのような列を別に置き、書く人も読む人もその列を知っている必要があります。読み取る側がorder_tsだけで条件をかけるとパーティションを1つもスキップできず、書き込む側がタイムゾーンを誤って計算すると、行が見当違いのパーティションに入ります。そして、パーティションの方式を変えるには、テーブル全体を書き直す必要があります。
Icebergは、パーティションをルールとして持ちます。days(order_ts)のように、ソース列と変換だけを記述しておくと、エンジンがファイルごとにパーティション値を計算してマニフェストに記録し、読み取る側はorder_tsの条件だけでファイルをスキップします。ルールは仕様IDが付いてmetadataに積み重なるので、ルールを変えても、古いファイルは古い仕様で読み続けられます。書き直さなければならない気がするという感覚が、最もよくある誤解です。
ステップ
- /root/ice/part/month.py(アプリ
ice-part-month)でlake.part.ordersをPARTITIONED BY (months(order_ts))で作成し、3月の1か月分(31ファイル)を1回でコミットしてください。 - /root/ice/part/plan1.py(pyiceberg)で、
order_tsが2026-03-10の1日である条件の計画を立て、/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)で、4月の1か月分(30ファイル)を1回でコミットしてください。 - /root/ice/part/plan2.pyで、3月10日と4月10日の条件の計画を立て、/root/ice/part/out/plan.jsonに書いてください。
- /root/ice/part/bucket.py(アプリ
ice-part-bucket)でlake.part.customersをPARTITIONED BY (bucket(4, customer_id))で作成し、顧客を入れてください。 - /root/ice/part/specs.pyで、
lake.part.ordersのデフォルトの仕様IDと仕様別のデータファイル数を、/root/ice/part/out/specs.jsonに書いてください。 - /root/ice/part/report.mdに、
## 숨은 파티셔닝、## 파티션 진화、## 버킷の3つの節を書いてください(3つの見出しは順に、韓国語で「隠しパーティショニング」「パーティション進化」「バケット」を意味する語句です)。
参考
- パーティション値は、ディレクトリ名ではなくマニフェストにあります。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文字列を使ってください。- よくある間違いは、パーティションを変えたあとに、3月を
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で作成し、lake.part.orders(列は6つ、'format-version' = '2')をPARTITIONED BY (months(order_ts))で作って、/data/ice/orders/2026-03-*.csvの31ファイルを1回のappend()で入れてください。
months(order_ts)は、1970年1月から数えた月数をパーティション値に使います。スキーマに新しい列ができないことを確認してみてください。採点ツールは、仕様0の変換がmonthであるか、最初のコミットが3月全体であるか、ファイルのパーティション値がすべて2026年3月であるかを確認します。
1日の条件が開くファイル: 月単位の限界
/root/ice/part/plan1.py(pyiceberg)で、order_ts >= 2026-03-10T00:00:00+00:00かつ< 2026-03-11T00:00:00+00:00の条件のスキャンを作り、計画されたファイル数・そのファイルのrecord_countの合計・実際に該当する行数を、/root/ice/part/out/plan_march.jsonに{"files_planned", "records_scanned", "rows"}の形で書いてください(今のテーブルには3月だけがあります)。
条件はorder_tsだけでかけたのに、パーティション値(月)と比較されて、ほかの月のファイルは除外されます。しかし、3月10日の1日分がほしくても、3月のファイルは1か月分を丸ごと読む必要があります。records_scannedとrowsの差が、そのコストです。採点ツールは、最初のスナップショット(3月だけがあったとき)を基準に同じ計画を立て直して比較します。
パーティション進化: metadataだけが変わる
/root/ice/part/evolve.pyをアプリ名ice-part-evolveで作成し、ALTER TABLE lake.part.orders REPLACE PARTITION FIELD months(order_ts) WITH days(order_ts)を実行してください。
仕様1が新しくでき、デフォルト(default-spec-id)が1に変わります。スナップショットはできず、データファイルもそのままです。これから書くファイルだけが、新しいルールに従います。採点ツールは、仕様のリスト・デフォルトの仕様・スナップショット数を確認します。
4月のコミット: 新しいファイルだけが新しい仕様
/root/ice/part/april.pyをアプリ名ice-part-aprilで作成し、/data/ice/orders/2026-04-*.csvの30ファイルを1回のappend()で入れてください。3月のファイルは書き直さないでください。
新しいコミットのファイルは、spec_id 1として、日ごとに別々に記録されます。3月のファイルは、spec_id 0(月)のまま、同じスナップショットの中に一緒にあります。読み取るエンジンは、仕様ごとに別々に計画を立てるので、2つのルールが混ざっていても結果は同じです。採点ツールは、3月のファイルが最初のコミットのパス・仕様のままかも確認します。
同じ1日の条件でも、コストが違う
/root/ice/part/plan2.pyで、3月10日の1日分と4月10日の1日分の条件の計画をそれぞれ立て、/root/ice/part/out/plan.jsonに{"march": {"files_planned", "records_scanned", "rows"}, "april": {…}}の形で書いてください。
2つの日は行数が似ているのに、読む必要がある行数は大きく違います。3月は月のファイルを丸ごと、4月はその日のファイル1つだけを開きます。採点ツールは、今のテーブルで同じ計画を立て直して比較し、4月のほうがその日の行だけを読んでいるかを確認します。
種類の多い列はバケットで
/root/ice/part/bucket.pyをアプリ名ice-part-bucketで作成し、lake.part.customers(customer_id STRING, tier STRING, region STRING, signup_date DATE)をPARTITIONED BY (bucket(4, customer_id))で作って、/data/ice/customers.csvを入れてください。
bucket(N, 列)は、値の32ビットmurmur3ハッシュをNで割った余りを、パーティション値に使います。仕様が定めたハッシュなので、どのエンジンが計算しても同じバケットになります。採点ツールは、pyicebergで顧客ごとにバケットを計算し直して、ファイルのパーティション値と比較します。
1つのテーブルの中の2つのルール
/root/ice/part/specs.py(pyiceberg)で、lake.part.ordersのデフォルトの仕様IDと仕様別のデータファイル数を、/root/ice/part/out/specs.jsonに{"default_spec_id": 정수, "files_by_spec": {"0": 정수, "1": 정수}}(プレースホルダーは整数です)の形で書いてください。
tbl.inspect.files()のspec_id列で数えます(contentが0のものがデータファイルです)。1つのスナップショットの中に、2つの仕様のファイルが混ざっているのが、正常です。
パーティション設計をチームのルールにする
/root/ice/part/report.mdに、## 숨은 파티셔닝、## 파티션 진화、## 버킷の3つの節を書いてください(3つの見出しは順に、韓国語で「隠しパーティショニング」「パーティション進化」「バケット」を意味する語句です)。2つ目の節には、ステップ5の3月・4月のrecords_scannedの2つの値を、数字で入れてください。
最初に月で分けた選択は、間違いだったのでしょうか。それとも、データが増えて合わなくなったのでしょうか。古い3月のファイルをいつか書き直す必要があるなら、その理由は何であるべきかも書いてみてください。