Data Engineer Associate — 第 2

データはどこに、どんな形で保存されるか — テーブルの実体と Delta Lake の内部

読了 27確認 15更新 2026-08-09

この章で学ぶこと

  • マネージド・外部・フォリンの保存先の違いを説明できる
  • ストレージ資格情報と外部ロケーションを順に組み立てられる
  • _delta_log とチェックポイントの役割を読み解ける
  • Time Travel・RESTORE・CLONE を条件付きで使い分けられる
  • NOT NULL・CHECK・生成列でテーブル側の品質を守れる

この章に出てくる用語

マネージドテーブル
置き場所を自分で決めず Unity Catalog に任せるテーブルです。形式は Delta Lake と Apache Iceberg だけで、保存先はスキーマ・カタログ・メタストアの順に探されます。DROP すると既定 7 日後に消えます。
外部ロケーション
s3://bucket/path のようなパスと、IAM ロールを表すストレージ資格情報を組にした Unity Catalog のオブジェクトです。READ FILES や CREATE EXTERNAL TABLE を GRANT します。
_delta_log
Delta テーブルのディレクトリ直下にあるトランザクションログです。00000000000000000000.json のように 20 桁ゼロ埋めの JSON がコミットごとに増え、add や remove を記録します。
チェックポイント
その時点のテーブル状態を書き出した Parquet です。読み手は最新の 1 件と以降の JSON だけ再生すれば済みます。間隔は delta.checkpointInterval で指定し、Delta では 10 コミットが基準です。
楽観的同時実行制御
ロックを取らず、読む・書く・検証するの 3 段階で進める方式です。衝突すると ConcurrentAppendException などで失敗します。既定の分離レベルは WriteSerializable です。
Deletion Vectors
削除のたびに Parquet を書き直さず、消えた行の印を別ファイルへ書き出す仕組みです。delta.enableDeletionVectors を true にし、Photon なしの MERGE は DBR 14.3 LTS 以上です。
生成列
GENERATED ALWAYS AS (式) で値を自動計算する列です。CAST(event_ts AS DATE) のような決定的な SQL 関数だけが使え、UDF や集約関数は使えません。パーティション列によく使います。
Volumes
PDF や画像などテーブルにしないファイルを Unity Catalog の配下で扱うオブジェクトです。/Volumes/catalog/schema/volume/ のパスで参照し、READ VOLUME などで権限を制御します。

テーブルの実体はどこにあるか — Managed / External / Foreign と Volumes / DBFS

「テーブルを DROP したらクラウドストレージのファイルまで消えた」「別のチームが DROP したのに、ファイルだけ残ってストレージ課金が止まらない」。どちらも実務で普通に起きます。そしてどちらが起きるかは、そのテーブルがマネージドテーブルなのか外部テーブルなのかという一点で決まります。ここを取り違えたまま先に進むと、後の章で組み立てるジョブも監視も、消えたデータの上に載ることになります。

テーブルは 3 種類ある

Unity Catalog のテーブルは 3 種類です。どれも `catalog.schema.table` の 3 階層名で呼ぶところは同じで、違うのはデータの実体を誰が持つかです。

比較軸マネージドテーブル外部テーブルフォリンテーブル
データ実体の管理者Databricks (Unity Catalog)自社のバケット接続先のデータベース
DDL での見分け方`LOCATION` を書かない`LOCATION` を書く接続経由で自動生成
使える形式Delta Lake と Apache Iceberg の 2 つだけDELTA / CSV / JSON / AVRO / PARQUET / ORC / TEXT接続先に依存
`DROP TABLE` の結果復旧期間の後にファイルも削除メタデータのみ削除参照定義のみ削除
自動最適化予測的最適化と自動メンテナンスの対象限定的対象外

形式の行が効きます。マネージドテーブルは Delta Lake と Apache Iceberg しか持てません。他システムが出力した CSV をファイル形式のまま Unity Catalog で管理したい、という要件なら答えは外部テーブル一択です。Iceberg にしたいときだけ `USING iceberg` を明示し、省略すれば Delta Lake になります。

自動最適化の行は、Databricks がマネージドを既定として推す理由そのものです。予測的最適化 (predictive optimization) は、`OPTIMIZE` によるファイル圧縮、`VACUUM` による不要ファイル削除、`ANALYZE` による統計収集を人手なしで走らせる機能で、マネージドテーブルが主な対象です。外部テーブルでは Unity Catalog がアクセスを統制するだけで、データの寿命や配置までは面倒を見ません。フォリンテーブルは Lakehouse Federation で PostgreSQL や Snowflake を参照する読み取り専用の窓なので、最適化の対象外です。

保存先の決まり方と DROP の後始末

`LOCATION` を書かずに `CREATE TABLE` すればマネージドテーブルです。ではファイルはどこへ行くのか。スキーマ、カタログ、メタストアの順に探し、最初に見つかったマネージドロケーションが使われます。下位のレベルほど優先されるので、「常にメタストアのロケーション」という覚え方は誤りです。

-- LOCATION を書かない → マネージドテーブル (既定は Delta Lake)
CREATE TABLE main.sales.orders (order_id BIGINT, amount DECIMAL(18,2));

-- LOCATION を書く → 外部テーブル
CREATE TABLE main.sales.orders_ext (order_id BIGINT, amount DECIMAL(18,2))
LOCATION 's3://acme-lake/sales/orders/';

マネージドテーブルを `DROP TABLE` しても、ファイルはその場では消えません。既定で 7 日間は `UNDROP TABLE` で戻せます。復旧期間はカタログまたはスキーマの単位で変えられ、`ALTER CATALOG my_catalog RETAIN DROPPED TO 30 DAYS;` のように 7 日から 30 日で指定するか、0 時間にして復旧を無効化します。期間が切れると Databricks は 48 時間以内にクラウドストレージのファイルを削除します。外部テーブルの `DROP TABLE` はメタデータだけを消すので、ファイルは自分で消す必要があります。なお外部テーブルの実体を Databricks 以外のクライアントが書き換えても Unity Catalog には自動同期されず、カタログの情報が実体とずれます。

実体を確かめる DESCRIBE DETAIL

目の前のテーブルがどちらの種類で、実体がどこにあるのかは `DESCRIBE DETAIL` で分かります。返るのは現在のスナップショットのメタデータ 1 行だけで、`format`、`id`、`name`、`location`、`createdAt`、`lastModified`、`partitionColumns`、`numFiles`、`sizeInBytes`、`properties` などの列が含まれます。`location` と `format` を見れば種別と置き場が確定し、`numFiles` と `sizeInBytes` は小ファイルの調査や容量監視にそのまま使えます。

似た名前のコマンドと混ぜないでください。コミットの履歴一覧は `DESCRIBE HISTORY`、列名と型の定義は `DESCRIBE TABLE`、クエリの実行プランは `EXPLAIN` です。Python からは `DeltaTable.forPath(spark, path).detail()` が `DESCRIBE DETAIL` と同じ 1 行を DataFrame で返します。

テーブルにしないファイルは Volumes へ

PDF、画像、機械学習のモデルファイル、取引先から届いた固定長テキスト。表の形をしていないのでテーブルにできませんが、放置すると誰が触ったのか分からなくなります。Unity Catalog の Volumes がこの穴を埋めます。`catalog.schema.volume` の 3 階層名を持ち、ファイルは `/Volumes/<catalog>/<schema>/<volume>/…` というパスで読み書きします。権限はテーブルの `SELECT` ではなく `READ VOLUME` と `WRITE VOLUME`、作成にはスキーマの `CREATE VOLUME` です。マネージドボリュームと外部ボリュームの 2 種類があり、Volumes の中のファイルをそのままテーブルとして登録することはできません。使い分けの詳細は既存記事「Unity Catalog Volumes」に譲ります。

DBFS を新しく使わない

古い資料には `dbfs:/mnt/raw` にマウントしてから読み書きする手順が出てきます。DBFS root はワークスペース作成時に用意される領域で、DBFS マウントは非推奨です。理由は機能ではなくガバナンスにあります。DBFS は Unity Catalog の権限、監査ログ、リネージ (どのデータからどのテーブルが作られたかの追跡) のどれも効きません。新しく作るものは Volumes か外部ロケーションを使います。両者の比較そのものは既存記事「DBFS と Volumes の違い」で扱っています。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

マネージドテーブルの保存先はスキーマ・カタログ・メタストアの順に探され、 のレベルが優先される。

CSV をファイル形式のまま Unity Catalog で管理したいときは として登録する。

テーブルの location・numFiles・sizeInBytes を 1 行で返すのは である。

外部ストレージへつなぐ — ストレージ資格情報と外部ロケーション、UC 権限とクラウド IAM の二層構造、SET / UNSET MANAGED

「AWS のコンソールからはそのバケットを開けるのに、Databricks から読むと 403 で弾かれる」。クラウドストレージにつなぐ作業でいちばん多い詰まり方です。原因はたいてい単純で、権限のチェックが 2 か所にあり、その片方しか通していないからです。かといってノートブックにアクセスキーを直書きすれば、キーは実行履歴に残り、そのノートブックを開ける人全員に同じ権限が渡ります。Unity Catalog は認証情報を 1 か所に閉じ込め、パスと組にして配ることでこれを解きます。

資格情報 → 外部ロケーション → 外部テーブルの順に作る

  • ストレージ資格情報 (storage credential): クラウド側の認証の仕組みそのものを Unity Catalog に登録したものです。AWS なら IAM ロール、Azure ならマネージド ID やサービスプリンシパル、GCP ならサービスアカウントが中身になります。
  • 外部ロケーション (external location): クラウドストレージのパスと、そこへアクセスするストレージ資格情報を組み合わせたオブジェクトです。
CREATE EXTERNAL LOCATION IF NOT EXISTS sales_raw
  URL 's3://acme-lake/sales/'
  WITH (STORAGE CREDENTIAL acme_cred)
  COMMENT '売上生データ';

関係の向きを間違えないでください。1 つのストレージ資格情報を複数の外部ロケーションが参照できます。バケット全体を許可する資格情報を 1 つ作り、その下のサブパスごとに外部ロケーションを切って権限を分ける、という設計が普通です。「外部ロケーションごとに資格情報が要る」は誤りで、不要な作業を生みます。そしてもう 1 つ、外部テーブルや外部ボリュームを作れるパスは、必ずどこかの外部ロケーションの配下でなければなりません。登録していないパスを `LOCATION` に書けば作成は失敗します。「バケットは見えているのにテーブルが作れない」の大半はこれです。

外部ロケーションに付けられる権限はテーブルとは別系統で、`READ FILES`、`WRITE FILES`、`CREATE EXTERNAL TABLE`、`CREATE EXTERNAL VOLUME`、`BROWSE`、`MANAGE` などがあります。名前の変更や資格情報の差し替えといった管理操作には `MANAGE` が要ります。

GRANT READ FILES, CREATE EXTERNAL TABLE
  ON EXTERNAL LOCATION sales_raw
  TO `data-engineers`;

作るときも読むときも権限は「組」で要る

外部テーブルを 1 つ作るのに必要な権限は 4 つです。データを置く外部ロケーションへの `CREATE EXTERNAL TABLE`、親カタログの `USE CATALOG`、親スキーマの `USE SCHEMA` と `CREATE TABLE`。スキーマの `CREATE TABLE` だけでは作れません。外部ボリュームなら、外部ロケーションの `CREATE EXTERNAL VOLUME` と親スキーマの `CREATE VOLUME` の組になります。

読み取り側も形は同じです。あるテーブルを `SELECT` するには、テーブルへの `SELECT`、親カタログの `USE CATALOG`、親スキーマの `USE SCHEMA` の 3 つが揃っていなければなりません。`SELECT` だけ渡して「見えない」と言われる相談はほぼこれです。中身は見せずに、どんなテーブルがあるかだけ探させたいときは `BROWSE` を使います。権限を渡す相手にも制約があり、ワークスペース内で作ったワークスペースローカルグループには Unity Catalog の権限を付与できません。アカウントレベルで管理されるアカウントグループを使います。

Unity Catalog の権限とクラウド IAM の二層構造

ここが本節の中心です。外部ロケーションに `READ FILES` を GRANT しても、IAM ロールにそのバケットの `s3:GetObject` が付いていなければ読めません。逆に IAM ロールがどれだけ強くても、Unity Catalog 側で GRANT されていなければ Databricks からは読めません。2 つの層は AND で効きます

設定する場所設定するもの抜けたときの症状
クラウド IAMAWS / Azure / GCP のコンソールIAM ロール、バケットポリシー、信頼関係ストレージ資格情報の検証が失敗する
Unity CatalogDatabricks の GRANTREAD FILES、CREATE EXTERNAL TABLE など資格情報は正常なのに操作が拒否される

403 が出たら、まず `SHOW GRANTS ON EXTERNAL LOCATION sales_raw;` で Unity Catalog 側を確認し、次にクラウド側のロールとバケットポリシーを見る、という順に切り分けます。`spark.read.parquet("s3://acme-lake/sales/…")` のようなパスの直読みもこの二層を通ります。裏を返せば、パス直読みを禁じたいなら `READ FILES` を配らなければよい、ということです。

外部テーブルをマネージドへ移す SET MANAGED

「昔 S3 に自前で作った外部テーブルを、Databricks に運用ごと任せたい」。以前は CTAS (`CREATE TABLE AS SELECT` でクエリ結果から作り直すこと) しか手がなく、履歴は失われました。今は 1 行で移せます。

ALTER TABLE main.sales.orders_ext SET MANAGED;
ALTER TABLE main.sales.orders_ext UNSET MANAGED;  -- 14 日以内なら戻せる
  • 対象は Delta Lake 形式のテーブルに限られ、実行には Databricks Runtime 17.3 LTS 以上またはサーバーレスコンピュートが要ります。
  • まずデータとトランザクションログをマネージドロケーションへコピーし、そのあとに切り替えます。コピー中も読み書きは続けられ、止まるのは切り替えの一瞬だけです。公式の見積りでは、100 GB 以下でコピー約 6 分・停止 1〜2 分以下、1 TB でコピー約 30 分・停止 1〜2 分、10 TB でコピー約 1.5 時間・停止 1〜5 分です。推奨クラスタサイズを前提にした見積りで、ファイルサイズやファイル数、コミット数で変わります。読み取り側は Databricks Runtime 16.4 LTS 以上ならダウンタイムなし、15.4 LTS 以下では発生し得ます。
  • テーブル名、設定、権限、ビュー、そして履歴が引き継がれます。変換から 14 日以内なら `UNSET MANAGED` で、メタデータが元の外部ロケーションを指す状態に戻せます。
  • Hive metastore フェデレーション経由のフォリンテーブルを変換するときだけ `SET MANAGED {MOVE | COPY}` とモードを必ず指定します。外部テーブルの変換では逆に付けてはいけません。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

外部テーブルの作成には外部ロケーションの CREATE EXTERNAL TABLE、親カタログの USE CATALOG、親スキーマの USE SCHEMA と が要る。

1 つのストレージ資格情報は の外部ロケーションから参照できる。

外部テーブルを SET MANAGED で変換した後、 日以内なら UNSET MANAGED で元に戻せる。

Delta Lake の内部 — _delta_log と 10 コミット毎チェックポイント、楽観的同時実行制御

1 億行のテーブルに追記している途中でクラスタが落ちたら、書きかけの Parquet は他の人のクエリに混ざって読まれるのでしょうか。オブジェクトストレージ自体にトランザクションという概念はないので、素の Parquet ディレクトリならまさにそれが起きます。Delta Lake がこれを防ぐ仕組みが、テーブルディレクトリ直下の `_delta_log/` です。ACID・Time Travel・Z-Order という用語の解説は既存記事「Delta Lake とは」に置き、ここでは中身の構造だけを扱います。

中身は Parquet と _delta_log だけ

s3://acme-lake/sales/orders/
├── part-00000-....snappy.parquet
├── part-00001-....snappy.parquet
└── _delta_log/
    ├── 00000000000000000000.json
    ├── 00000000000000000001.json
    ├── …
    ├── 00000000000000000010.checkpoint.parquet
    └── _last_checkpoint

コミットファイルの名前はバージョン番号を 20 桁にゼロ埋めした JSON です。バージョンは 0 から始まり、書き込みが 1 回成功するたびに 1 増えます。`VERSION AS OF 5` と書いたときの 5 がこの番号です。中身は 1 行 1 アクションで、`metaData` (スキーマやパーティション定義)、`add` (ファイルの追加)、`remove` (ファイルの除外)、`protocol`、`txn`、`commitInfo` (操作種別や実行者)、`cdc`、`domainMetadata`、`sidecar` が並びます。`protocol` のプロトコルバージョンは、読み書きに必要な機能水準を表す番号で、新しい機能を有効にすると上がり、古いクライアントからは読めなくなります。

ここで押さえるべきは、Delta が更新を Parquet の書き換えではなく `remove` `add` の組で表現する点です。古いファイルは remove されても物理的には残り続けます。これが次の節のタイムトラベルの正体です。

add の stats がファイルを読み飛ばす

`add` アクションには `path`、`partitionValues`、`size`、`modificationTime`、`dataChange`、`stats`、`deletionVector` が入ります。この `stats` が肝で、列ごとの最小値・最大値・NULL 件数が記録されており、クエリは読まなくてよいファイルを丸ごと飛ばせます。統計を取る列数は `delta.dataSkippingNumIndexedCols` で決まり、既定は先頭 32 列です。絞り込みに使う列を 33 列目以降に置いたテーブルが遅いのは、これが理由です。この統計の効きを最大化するようデータの並びをそろえるのが `OPTIMIZE … ZORDER BY` と Liquid Clustering で、Liquid はキーが最大 4 列、`PARTITIONED BY` とは併用できません。詳しくは後の最適化の章で扱います。

行レベルの変更を配る Change Data Feed

下流に「増えた行と消えた行だけ」を渡したい場面があります。`cdc` アクションを使う Change Data Feed がそれで、既定は無効、テーブルプロパティで有効にします。

ALTER TABLE main.sales.orders
  SET TBLPROPERTIES (delta.enableChangeDataFeed = true);

SELECT * FROM table_changes('main.sales.orders', 5, 10);

有効化した後の変更行には `_change_type`、`_commit_version`、`_commit_timestamp` の 3 列が付きます。`_change_type` の値は `insert`、`update_preimage`、`update_postimage`、`delete` の 4 つで、`UPDATE` は変更前と変更後の 2 行になります。ストリーミングで読むときは `option("readChangeFeed", "true")` に `startingVersion` か `startingTimestamp` を添えます。有効化する前のバージョンは取れません。

チェックポイントが JSON の再生を打ち切る

読み手はログを再生して「このバージョン時点のファイル一覧」を組み立てます。コミットが 5 万回あるテーブルで毎回 5 万個の JSON を読むのは無理なので、Delta はときどきその時点の状態をまるごと 1 つの Parquet にまとめたチェックポイントを書き出します。読み手は直近のチェックポイントまで飛び、その後の JSON だけを追加で読みます。どれが最新かは `_last_checkpoint` が指します。間隔はテーブルプロパティ `delta.checkpointInterval` で指定でき、Delta Lake では 10 コミットごとが基準として知られています。ただし Databricks の公式ドキュメントは生成頻度を数値で約束していません。

用語の衝突に注意してください。ここでいうチェックポイントは `_delta_log/` の中の Parquet で、テーブルの状態を復元するためのものです。ストリーミングで指定する `checkpointLocation` はまったくの別物で、こちらはクエリの進捗を保存するディレクトリです。中は `offsets`、`commits`、`state`、`metadata` のサブディレクトリに分かれています。試験ではどちらも同じ文脈に出るので、毎回どちらの話かを確かめてください。

ロックを取らない書き込み — 楽観的同時実行制御

2 つのジョブが同じテーブルへ同時に書いたらどうなるか。Delta はテーブルをロックしません。衝突はめったに起きないと楽観的に仮定して先に進み、コミットの瞬間だけ確かめる方式です。手順は 3 段階で、開始時点のバージョンを記録して対象ファイルを特定し、新しい Parquet を書き、最後に「自分が読んだ後に競合する変更が入っていないか」を検証して次の番号の JSON を書きます。同じ番号の JSON は 1 つしか作れないので、必ず片方だけが勝ちます。負けたらリトライする前提でジョブを書く必要があります

分離レベルは 2 つあり、既定は `WriteSerializable` です。何も読まずに追記するだけのブラインド追記は他の操作と衝突しないので、取り込みジョブを追記専用に設計する意味があります。より厳密にしたいときだけ切り替えます。

ALTER TABLE main.sales.orders
  SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable');
例外起きる状況
`ConcurrentAppendException`自分が読んだパーティションに他の操作がファイルを追加した
`ConcurrentDeleteReadException`自分が読んだファイルを他の操作が削除・書き換えした
`ConcurrentDeleteDeleteException`同じファイルを 2 つの操作が削除しようとした
`MetadataChangedException`処理中にスキーマやテーブルプロパティが変わった
`ProtocolChangedException`処理中にプロトコルバージョンが上がった
`ConcurrentTransactionException`同じストリーミングクエリが二重に動いた

衝突そのものを減らすのが行レベル同時実行です。同じファイルの中の別々の行を触っているだけなら、競合と判定しません。条件は Databricks Runtime 14.3 LTS 以上、Deletion Vectors が有効、テーブルがパーティション分割されていないの 3 つです。パーティションを切っていると効きません。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

_delta_log 内のコミット JSON はバージョン番号を 桁でゼロ埋めしたファイル名になる。

統計値を集める列数は delta.dataSkippingNumIndexedCols で決まり、既定は先頭 列である。

Databricks の Delta テーブルの既定の分離レベルは である。

過去に戻る・複製する — Time Travel / RESTORE / shallow・deep CLONE / Deletion Vectors

本番テーブルに `WHERE` を付け忘れた `UPDATE` を流してしまった。この瞬間に何ができるかは、そのテーブルの保持設定を事前にどう決めていたかで決まります。あとから設定しても手遅れです。前の節で見たとおり Delta は古いファイルを `remove` で外すだけなので、ファイルが残っている間は過去のバージョンを読み直せます。同じ仕組みは「本番と同じデータで検証したい」という平時の要求にも効きます。

まず履歴を見て、次に読む

`DESCRIBE HISTORY` は `_delta_log/` の `commitInfo` を読み、`version`、`timestamp`、`userName`、`operation`、`operationParameters`、`operationMetrics`、`isolationLevel`、`isBlindAppend` などを新しい順に返します。事故の直前がどのバージョンかはここで特定します。サイズやパスを 1 行で返す `DESCRIBE DETAIL` とは別のコマンドです。

DESCRIBE HISTORY main.sales.orders;

SELECT * FROM main.sales.orders VERSION AS OF 123;
SELECT * FROM main.sales.orders TIMESTAMP AS OF '2026-08-01T00:00:00';
SELECT * FROM main.sales.orders@v123;

短縮記法の `@` は、バージョンなら `v` を付け、タイムスタンプなら `yyyyMMddHHmmssSSS` の形式で書きます。PySpark では `option("versionAsOf", 123)` と `option("timestampAsOf", "2026-08-01")` が同じ働きです。読むだけなのでテーブルは変わりません。本体を戻すのは `RESTORE` です。

RESTORE TABLE main.sales.orders TO VERSION AS OF 123;
RESTORE TABLE main.sales.orders TO TIMESTAMP AS OF '2026-08-01 00:00:00';

`RESTORE` は履歴を消しません。過去の状態に戻すための新しいコミットを積む実装なので、戻したこと自体も履歴に残り、戻す前の状態へもう一度移動できます。「参照だけなら `VERSION AS OF`、テーブルを書き換えるなら `RESTORE`」で選び分けます。

遡れる範囲は 168 時間で決まる

過去バージョンを読むには、`_delta_log/` のコミット記録と、そのバージョンが参照する Parquet の両方が残っている必要があります。記録の寿命は `delta.logRetentionDuration` で既定 `interval 30 days`、ファイルの寿命は `delta.deletedFileRetentionDuration` で既定 `interval 1 week`、つまり 168 時間 (7 日) です。短いほうで決まるので、既定のまま実際に遡れるのは 7 日分です。30 日と覚えていると、事故のときに読めない過去を掴みに行きます。

不要になったファイルを実際に消すのが `VACUUM` で、構文は `VACUUM table_name { { FULL | LITE } | DRY RUN }` です。既定は `FULL`、`LITE` は Databricks Runtime 16.1 以上で使え、ディレクトリを走査せずトランザクションログだけを見ます。`DRY RUN` は削除予定のファイルを最大 1000 件返すだけで、消しません。

VACUUM main.sales.orders DRY RUN;
VACUUM main.sales.orders RETAIN 168 HOURS;  -- 既定と同じ 7 日
VACUUM main.sales.orders RETAIN 12 HOURS;   -- 安全チェックに阻まれて失敗する

既定の 168 時間より短い `RETAIN` を指定すると、Databricks はエラーで止めます。長時間走っているジョブが参照中のファイルを消してしまう事故を防ぐ安全装置です。どうしても短くしたいときだけ `spark.databricks.delta.retentionDurationCheck.enabled` を `false` にして `RETAIN 1 HOURS` のように指定します。`RETAIN 0 HOURS` は過去バージョンを一掃するので本番では実行しません。`VACUUM` はアンダースコアで始まるディレクトリを飛ばすため、`_delta_log/` が消えることはありません。予測的最適化が有効なら `VACUUM` は自動で走ります。

複製は SHALLOW と DEEP で意味が違う

CREATE TABLE main.dev.orders_deep    DEEP CLONE main.sales.orders;
CREATE TABLE main.dev.orders_shallow SHALLOW CLONE main.sales.orders;
CREATE TABLE main.dev.orders_v5      SHALLOW CLONE main.sales.orders VERSION AS OF 5;

`DEEP CLONE` はデータファイルまでコピーして独立したテーブルを作ります。`SHALLOW CLONE` はメタデータだけをコピーし、データファイルは元テーブルのものを参照し続けます。一瞬で終わりストレージも増えませんが、落とし穴があります。元テーブルで `VACUUM` を走らせると、クローンが参照していたファイルが消えて `FileNotFoundException` になります。検証環境を長く保つなら `DEEP CLONE` にするか、元テーブルの `delta.deletedFileRetentionDuration` を十分に長くします。Unity Catalog では既存のシャロークローンを `CREATE OR REPLACE` で上書きできず、`DROP TABLE` してから作り直します。なお行フィルタ (条件に合う行だけを見せる関数) や列マスク (値を伏せて返す関数) が設定されたテーブルでは、タイムトラベルもクローンも使えません。クローンの使い分けは既存記事「Delta Lake の CLONE」でも扱っています。

Deletion Vectors — 消すたびに書き直さない

1 行だけ `DELETE` するために 1 GB の Parquet を丸ごと書き直すのは無駄です。Deletion Vectors は「このファイルのこの行はもう無い」という印を別ファイルに書き、Parquet 本体には触りません

ALTER TABLE main.sales.orders
  SET TBLPROPERTIES ('delta.enableDeletionVectors' = true);

必要な Databricks Runtime は操作ごとに違います。読み取りは 12.2 LTS 以上、Photon ありなら `DELETE`・`UPDATE`・`MERGE` のすべてが 12.2 LTS 以上、Photon なしでは `DELETE` が 12.2 LTS 以上、`UPDATE` が 14.1 以上、`MERGE` が 14.3 LTS 以上です。印を付けただけの行を本当にファイルから消すには `REORG TABLE … APPLY (PURGE)` で書き直し、そのあと `VACUUM` で回収します。有効化するとプロトコルバージョンが上がる点にも注意してください。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

VACUUM の既定の保持期間は 時間で、これより短い RETAIN は安全チェックで拒否される。

SHALLOW CLONE 後に元テーブルへ VACUUM を実行すると、クローン側の SELECT が で失敗しうる。

Deletion Vectors で印を付けた行を Parquet から実際に消すには を実行してから VACUUM する。

テーブル側で品質を守る — NOT NULL / CHECK 制約、生成列、CONVERT TO DELTA

上流のシステムが仕様変更し、必須のはずの `order_id` に NULL が混ざるようになった。気付いたのは 3 日後、集計が合わないという問い合わせが来てからだった。パイプラインのコードで毎回チェックを書くこともできますが、そのテーブルへ書き込む経路が増えるたびに漏れが生まれます。Delta Lake はテーブル自身に条件を宣言でき、違反した書き込みをトランザクションごと失敗させます。書き手が誰であっても効く、最後の砦です。

強制されるのは NOT NULL と CHECK の 2 つだけ

ALTER TABLE people10m ALTER COLUMN ssn SET NOT NULL;
ALTER TABLE people10m ALTER COLUMN middleName DROP NOT NULL;

ALTER TABLE people10m
  ADD CONSTRAINT dateWithinRange CHECK (birthDate > '1900-01-01');
ALTER TABLE people10m DROP CONSTRAINT dateWithinRange;

後付けの `SET NOT NULL` は、既存の行に NULL があると失敗します。だから「先に既存の NULL を掃除してから制約を付ける」という順番になります。違反する書き込みが来ると、その行だけが落ちるのではなくトランザクション全体がエラーで失敗します。ここが宣言的パイプライン (Lakeflow Spark Declarative Pipelines、旧 Delta Live Tables) の Expectations との決定的な違いです。Expectations は違反行を落とす、警告だけ出す、パイプラインを止める、の 3 つから選べますが、`CHECK` 制約は止めることしかできません。違反行を別テーブルに退避したいなら、それは制約ではなく Expectations の仕事です。

付けすぎも失敗です。全カラムに `NOT NULL` を一律で付けると、NULL が正当な列まで弾いて毎晩取り込みが止まります。欠けてはいけないキー列や金額列に絞るのが定石です。なお主キー・外部キー・ユニークも宣言できますが、これらは情報目的のみで強制されません。重複した主キーを INSERT してもエラーになりません。設定済みの制約は `DESCRIBE TABLE EXTENDED` や `SHOW TBLPROPERTIES` で確認できます。

生成列 — 値を人に入れさせない

イベント時刻から日付列を作ってパーティションキーにしたい。取り込み側に毎回計算させると、書き手ごとに式がずれます。生成列は式をテーブル定義に埋め込み、書き込み時に Delta が値を計算します。

CREATE TABLE main.sales.events (
  event_id   BIGINT,
  event_ts   TIMESTAMP,
  event_date DATE GENERATED ALWAYS AS (CAST(event_ts AS DATE))
)
PARTITIONED BY (event_date);

構文は `GENERATED ALWAYS AS (式)` です。式に使えるのは決定的な SQL 関数だけで、四則演算、`CONCAT` や `SUBSTRING`、`CAST` や `YEAR` は使えますが、ユーザー定義関数、集約関数、ウィンドウ関数、複数行を返す関数は使えません。あえて値を渡す場合は `(<value> <=> <generation expression>) IS TRUE` を満たす必要があり、満たさなければ書き込みが失敗します。生成列でパーティションを切ると、基底列だけを条件に書いたクエリからも Delta が可能な範囲でパーティションフィルタを導出します。分析者に `event_date` を意識させずに枝刈りを効かせられる、というのが狙いです。

既存の Parquet を Delta にする CONVERT TO DELTA

CONVERT TO DELTA parquet.`s3://acme-lake/legacy/orders`;
CONVERT TO DELTA parquet.`s3://acme-lake/legacy/orders` NO STATISTICS;

構文は `CONVERT TO DELTA table_name [ NO STATISTICS ] [ PARTITIONED BY clause ]` です。この操作は Parquet ファイル自体を書き直しません。すべての Parquet のフッタを読んでスキーマを推論し、`_delta_log/` を作るだけなので、大規模テーブルでも短時間で終わります。既定では各ファイルの統計値も集めるため、変換直後からファイル単位の読み飛ばしが効きます。`NO STATISTICS` を付けると統計収集を省いて変換を早く終わらせられます。パス指定でパーティションがある場合は `PARTITIONED BY` が必須です。基盤の形式が Parquet なら Apache Iceberg のテーブルも変換でき、その場合はパス指定のみが対象です。`ALTER TABLE … SET TBLPROPERTIES ('format'='delta')` のような書き換えでは `_delta_log/` が作られず、変換にはなりません。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

Databricks が実際に強制する Delta の制約は NOT NULL と の 2 種類である。

生成列は 構文で宣言し、決定的な SQL 関数だけを式に使える。

CONVERT TO DELTA に を付けると統計値の収集を省いて変換を早く終わらせられる。

この章のまとめ

  1. マネージドは DROP でファイルも消え、外部はメタデータだけ消える
  2. 外部接続はストレージ資格情報と外部ロケーションの 2 段構えで作る
  3. _delta_log の JSON とチェックポイントがテーブルの実体を決める
  4. 遡れるのは既定 168 時間 (7 日) で、ログ側の 30 日ではない
  5. 品質は NOT NULL / CHECK / 生成列でテーブル側に埋め込む

この端末にだけ保存されます(登録不要)

この章の根拠

最終確認 2026-08-09 / 対応バージョン DEA 2026-05-04 改訂版

コース全体

  1. Databricks とレイクハウスの全体像 — なぜ必要か、何がどこに属し、どの計算資源で動くか
  2. データはどこに、どんな形で保存されるか — テーブルの実体と Delta Lake の内部
  3. 計算資源を作り込み、コードを書く場所を決める — クラスタ設定とノートブック/ローカル IDE
  4. Spark はどう動き、SQL で何をどこまで書けるか
  5. PySpark でデータを加工し、遅いコードを見抜く
  6. 流れ込むデータを受け止める — Structured Streaming と Auto Loader のファイル検出
  7. 取り込み時にスキーマをどう扱うか — Auto Loader のスキーマ進化と COPY INTO
  8. ファイル以外からも取り込む — read_files・Lakeflow Connect・JDBC と API
  9. 生データを使える形に育てる — メダリオン設計と宣言的パイプライン
  10. 処理を1つのジョブに束ねる — タスク種別と依存関係の設計
  11. ジョブを動かし、失敗から立て直す — トリガー・リトライ・パラメータ
  12. 書いたものを安全に本番へ届ける — Git 連携とデプロイの自動化
  13. 誰に何を見せるか — Unity Catalog のアクセス制御とアイデンティティ統制
  14. 誰が何をしたか、いくらかかったか、どこで詰まったかを見る
  15. テーブルを保守して速くする — 運用コマンド・保持期間・レイアウト設計・障害復旧
  16. DEA 直前仕上げ — 方式選定・出題範囲対応表・数値総まとめ・引っかけの型