TT Lab
はじめる
学ぶ 学習パス コース

レイクハウスのテーブル形式 — Apache Iceberg をメタデータで理解する

列を追加し、名前を変え、広げ、消して足し直す — ファイルは一度も書き直さない

TT Labで続きを見る

目標

Icebergテーブルのスキーマを4通りに変更しながら(列の追加・名前の変更・型の拡張・削除して再追加)、古いデータファイルが1度も書き直されないのに正しく読み取れることを確認します。その秘密が、Parquetファイルのフッターに埋め込まれたfield_idであることを、ファイルを直接開いて確かめます。

なぜ重要なのか

列を名前で見つけるテーブル(Hive方式のParquetテーブルの大半)では、名前の変更は事故です。古いファイルには古い名前しかないので、新しい名前で読むとその列が丸ごとnullになります。列を位置で見つける形式(CSV)では、順序を変えた瞬間に、値が見当違いの列に入ります。そのため、スキーマを変えるたびにテーブル全体を書き直したり、そもそも変えられずに名前の間違った列を何年も抱えたままにしたりします。 Icebergは、列ごとに変わらないIDを与え、ファイルを書くときにそのIDも一緒に書き込みます。読み取るときは、名前ではなくIDで対応づけます。そのため、名前の変更はmetadataの1行で済み、削除した列と同じ名前で新しい列を追加しても、新しいIDなので古い値はよみがえりません。型は、値が切り捨てられない方向(int → longのような拡張)にだけ変更できます。

ステップ

  1. /root/ice/sch/base.py(アプリice-sch-base)でlake.sch.orders(format-version 2)を作成し、2026-03-01を入れてください。
  2. /root/ice/sch/add.py(アプリice-sch-add)でcoupon STRING列を追加してから、2026-03-02を入れてください。この日の行のcouponは、amount >= 100000なら'SPRING'、そうでなければnullです。
  3. /root/ice/sch/rename.py(アプリice-sch-rename)で、amountをamount_krwに変更してください。
  4. /root/ice/sch/footer.py(pyarrow・pyiceberg)で最初のコミットのデータファイルを1つ開き、フィールドID 4のファイルの中での名前を、/root/ice/sch/out/footer.jsonに書いてください。
  5. /root/ice/sch/widen.py(アプリice-sch-widen)でamount_krwをBIGINTに拡張し、さらにINTに狭めようとしたときのエラー条件の名前を、/root/ice/sch/out/narrow.txtに書いてください。
  6. /root/ice/sch/readd.pyでcouponを削除してから同じ名前で再び追加し、古いID・新しいID・現在nullではない値の数を、/root/ice/sch/out/readd.jsonに書いてください。
  7. /root/ice/sch/history.pyで、スキーマ数・現在のスキーマID・スナップショット数・有効なデータファイル数を、/root/ice/sch/out/history.jsonに書いてください。
  8. /root/ice/sch/report.mdに、## 이름 바꾸기、## 형 넓히기、## 지웠다 다시 더하기の3つの節を書いてください(3つの見出しは順に、韓国語で「名前の変更」「型の拡張」「削除して再追加」を意味する語句です)。

参考

テーブルと最初のコミット: 列ごとのID

/root/ice/sch/base.pyをアプリ名ice-sch-baseで作成し、lake.schとlake.sch.orders(列は6つ、'format-version' = '2')を作って、2026-03-01のファイルを入れてください。

テーブルを作るとき、Icebergは列ごとに1からIDを振ります(order_id 1 … order_ts 6)。このIDは、名前が変わっても変わりません。採点ツールは、metadataのスキーマでIDと名前を、最初のスナップショットで行数を確認します。

列の追加: 古いファイルはnullとして読まれる

/root/ice/sch/add.pyをアプリ名ice-sch-addで作成し、ALTER TABLE lake.sch.orders ADD COLUMN coupon STRINGを実行してから、2026-03-02のファイルにcoupon(amount >= 100000なら'SPRING'、そうでなければnull)を付けて入れてください。

新しい列は、新しいID(7)を受け取ります。3月1日のファイルにはID 7がないので、その行のcouponはnullとして読まれます。ファイルを書き直していないからです。採点ツールは、3月2日のコミットが書いたParquetファイルを直接開いて、ID 7の列の値が元の条件と合っているかを確認します。

名前の変更: metadataの1行

/root/ice/sch/rename.pyをアプリ名ice-sch-renameで作成し、ALTER TABLE lake.sch.orders RENAME COLUMN amount TO amount_krwを実行してください。

名前の変更は、新しいスキーマ(ID 4の名前だけが違う)をmetadataに追加する作業です。2日分のファイルはそのままなのに、sum(amount_krw)が2日分の合計として出ます。採点ツールは、ID 4の名前が変わっているかと、新しい名前で読んだ合計が元の合計と同じかを確認します。

Parquetのフッターに残った古い名前

/root/ice/sch/footer.pyで、最初のコミット(3月1日)のデータファイルを1つpyarrow.parquet.read_schemaで開き、PARQUET:field_idが4のフィールドのファイルの中での名前を見つけて、/root/ice/sch/out/footer.jsonに{"file", "name_in_file", "field_id", "name_in_table"}の形で書いてください。

ファイルは、書かれたその時点の名前(amount)をそのまま持っています。テーブルは、今の名前(amount_krw)を使います。この2つをつないでいるのが、フッターのfield_idです。最初のコミットのファイルは、pyicebergのtbl.inspect.files(첫_스냅샷_ID)(プレースホルダーは最初のスナップショットIDです)で見つけられます(パスの先頭のfile:は取り除いてください)。

型は拡張しかできない

/root/ice/sch/widen.pyをアプリ名ice-sch-widenで作成し、amount_krwをBIGINTに変更して、続けてINTに戻そうとする文をtryで囲み、例外のgetCondition()を、/root/ice/sch/out/narrow.txtの1行目に書いてください。

int → longは、どの値も切り捨てられないので、古いファイル(intで書かれた)をそのままlongとして読めます。逆は値が切り捨てられるおそれがあるので、仕様が許していません。採点ツールは、ID 4の型がlongであるかと、エラー条件の名前を確認します。

削除して、同じ名前で再追加すると

/root/ice/sch/readd.pyでcouponをDROP COLUMNしてから、同じ名前でADD COLUMN coupon STRINGを実行し、古いID・新しいID・現在のcount(coupon)を、/root/ice/sch/out/readd.jsonに{"old_id", "new_id", "non_null"}の形で書いてください。

削除した列のIDは、再利用されません。新しいcouponは新しいIDを受け取り、3月2日のファイルに残っているID 7の「SPRING」という値は、新しい列と対応しないので見えません。名前で読むテーブルだったなら、古い値がよみがえっていたはずです。IDは、pyicebergのtbl.schemas()(履歴)とtbl.schema()(現在)で読みます。

スキーマは5つ、スナップショットは2つ、ファイルはそのまま

/root/ice/sch/history.py(pyiceberg)で、スキーマ数・現在のスキーマID・スナップショット数・有効なデータファイル数を、/root/ice/sch/out/history.jsonに{"schemas", "current_schema_id", "snapshots", "data_files"}の形で書いてください。

スキーマを変えるたびにmetadataにスキーマが1つずつ積み重なりますが、スナップショットは増えず、データファイルは2つのコミットが書いたままです。採点ツールは4つの値をmetadataと比較し、現在有効なファイルが2番目のスナップショットのファイルとぴったり同じか(書き直されたファイルがないか)も確認します。

スキーマ変更のルールをチームのルールにする

/root/ice/sch/report.mdに、## 이름 바꾸기、## 형 넓히기、## 지웠다 다시 더하기の3つの節を書いてください(3つの見出しは順に、韓国語で「名前の変更」「型の拡張」「削除して再追加」を意味する語句です)。3つ目の節には、ステップ6の古いIDと新しいIDを、数字で入れてください。

このテーブルを複数のチームが読むなら、どの変更はいつでもしてよく、どの変更は知らせる必要があるかを書いてみてください。フィールドIDで読まない利用側(ファイルを直接開くスクリプト)がある場合に、何が壊れるかも書いてください。