Databricks DE ยท Topic 2.1
Delta Lake turns plain Parquet files into a reliable table by adding a file-based transaction log โ the source of ACID, schema enforcement, fast scans, and version history. This deep dive walks each sub-topic with the mechanism and the code.
_delta_log that records every change in order. The table is the log, not the files on disk. That log is what makes object-storage files behave like a reliable warehouse table. On Databricks, every table is Delta by default โ no USING DELTA needed.โธ click a superpower
orders table; the log serializes their commits so neither reader sees a half-written result and the table is never corrupted._delta_log/A Delta table on disk is just data files + a log directory. Data files are immutable โ an "update" writes new files and tombstones old ones in the log.
/orders/ # table root โโโ part-00000-....snappy.parquet # immutable Parquet data โโโ part-00001-....snappy.parquet โโโ _delta_log/ # the transaction log โโโ 00000000000000000000.json # commit 0 (atomic unit) โโโ 00000000000000000001.json # commit 1 โโโ 00000000000000000010.checkpoint.parquet # written periodically โโโ _last_checkpoint # pointer to newest checkpoint
Inspect the log from SQL
DESCRIBE HISTORY main.sales.orders; -- one row per commit: version, op, user, metrics DESCRIBE DETAIL main.sales.orders; -- numFiles, sizeInBytes, location, format
Each JSON commit is a list of actions โ the diff applied at that version. The mental shift: a write is not an edit, it's a set of add/remove records appended to the log.
| Action | Meaning |
|---|---|
add | Data file added โ with size + column stats (min/max/nullCount). |
remove | File logically removed (tombstone; stays on disk until VACUUM). |
metaData | Schema, partitioning, table properties. |
protocol | Reader/writer protocol versions (which features are required). |
commitInfo | Audit metadata: operation, timestamp, user, metrics. |
txn | Idempotency marker for streaming (set-once per batch). |
To read the table, Delta nets add minus remove across the log (starting from the newest checkpoint) to compute the exact live file list. Checkpoints (cadence set by delta.checkpointInterval โ default is runtime-dependent: 100 on recent Databricks Runtimes, 10 in OSS Delta) keep this O(recent commits).
โธ click a version
...0000.json records the schema (metaData) + protocol.add actions for the new Parquet files. Readers now compute the v1 snapshot.remove + add atomically. No reader ever sees a half-applied merge.remove. Old files stay on disk until VACUUM โ so you can still time-travel back to v2 until then.Writers don't lock. Each writer reads a snapshot, does its work, then tries to commit the next version number with a put-if-absent write. If another writer got there first, the loser detects the conflict and retries. Walk the flow:
โธ step through two concurrent writers
Avoid conflicts by scoping writers to disjoint partitions
# Each job writes only its own region โ OCC won't treat them as conflicting (df_us.write.format("delta").mode("append") .option("replaceWhere", "region = 'US'") # disjoint predicate .saveAsTable("main.sales.orders")) # A parallel job using replaceWhere "region = 'EU'" commits cleanly in parallel.
Conflict types interviewers probe: ConcurrentAppendException (overlapping partition/predicate), ConcurrentDeleteReadException / ConcurrentDeleteDeleteException (a file you read/deleted was deleted), MetadataChangedException (schema changed under you).
Delta validates the incoming schema on every write and rejects mismatches. Enforcement rejects; evolution (opt-in) adds columns โ don't confuse them.
# This APPEND fails fast โ `bonus` is not a column in the target table bad = spark.createDataFrame([(1, "ACME", 99.0)], ["id", "name", "bonus"]) bad.write.format("delta").mode("append").saveAsTable("main.sales.customers") # AnalysisException: A schema mismatch detected when writing to the Delta table. # Opt in to evolution only when you *intend* to add the column: (bad.write.format("delta").mode("append") .option("mergeSchema", "true") # adds `bonus` to the table .saveAsTable("main.sales.customers"))
Each add stores min/max/nullCount for the first 32 columns (delta.dataSkippingNumIndexedCols). A filtered query reads those stats from the log and skips whole Parquet files whose range can't match โ often 10โ100ร less data scanned. OPTIMIZE/ZORDER/liquid clustering (Topic 3) tighten those ranges to make skipping far more effective.
-- Delta reads file-level min/max from the log and skips non-matching files SELECT * FROM main.sales.orders WHERE order_date BETWEEN '2024-01-01' AND '2024-01-07';
Because the log keeps every commit, you can read the table as of any past version/timestamp (within retention) and roll back with one command. Compare the two APIs:
SELECT * FROM main.sales.orders VERSION AS OF 12; -- by version SELECT * FROM main.sales.orders TIMESTAMP AS OF '2024-01-15'; -- by time -- Roll a bad load back (writes a NEW commit; no data loss) RESTORE TABLE main.sales.orders TO VERSION AS OF 12;
df = (spark.read
.option("versionAsOf", 12) # or .option("timestampAsOf", "2024-01-15")
.table("main.sales.orders"))
# Restore via the Delta table API
from delta.tables import DeltaTable
DeltaTable.forName(spark, "main.sales.orders").restoreToVersion(12)
catalog, schema = "main", "de_demo" spark.sql(f"CREATE SCHEMA IF NOT EXISTS {catalog}.{schema}") spark.sql(f"USE {catalog}.{schema}") # Delta is the default โ no USING DELTA needed spark.sql(""" CREATE TABLE IF NOT EXISTS orders ( order_id BIGINT, customer STRING, amount DECIMAL(10,2), order_date DATE ) TBLPROPERTIES (delta.checkpointInterval = 10) -- explicitly set checkpoint cadence """) # Each write = a new version = a new JSON commit in _delta_log/ spark.sql("INSERT INTO orders VALUES (1,'ACME', 250.00, DATE'2024-01-03')") spark.sql("DESCRIBE HISTORY orders").show(truncate=False)
| Capability | Plain Parquet/CSV dir | Delta table |
|---|---|---|
| Atomic multi-file writes | โ partial output visible | โ all-or-nothing commit |
| Concurrent writers | โ corruption / lost data | โ OCC + conflict detection |
| Schema enforcement | โ anything lands | โ validated on write |
| Update / delete / upsert | โ rewrite by hand | โ MERGE / UPDATE / DELETE |
| Time travel / audit | โ none | โ DESCRIBE HISTORY, VERSION AS OF |
| Fast filtered reads | โ scan everything | โ file-skipping via log stats |
OPTIMIZE (Topic 3). Concurrent writes to the same files raise a conflict the writer must retry (or avoid via disjoint replaceWhere).VACUUM physically deletes files past the retention window (default 7 days) โ after that, old versions aren't queryable. Delta is for analytical workloads, not high-frequency OLTP single-row point writes._delta_log/ โ corrupts the table. Always use Delta operations.