skip to content

Databricks Delta Lake

You will learn Delta Lake, the table format that made the lakehouse mainstream: a _delta_log of ordered commit files over Parquet giving ACID transactions, schema enforcement and time travel, plus the OPTIMIZE/VACUUM maintenance loop that keeps it fast. Interviewers ask about Delta because it is the default on Databricks and the format most candidates have actually touched.

on this pageshow

explore

questions

12

What does a Delta Lake table's _delta_log directory contain?

level: juniorimportance: must knowfreq 72%

answer

  1. the Parquet files alone are not the table
  2. one new file per successful write
  3. numbered JSON, one action per line
  4. add and remove, plus schema and protocol
  5. checkpoints so replay stays cheap

basics

~10 s

A Delta table's _delta_log holds the transaction log: numbered JSON commit files listing actions such as add, remove, metaData and protocol, plus periodic Parquet checkpoints and a _last_checkpoint pointer to the newest one.

solid answer

~50 s

A Delta Lake table is a directory of Parquet data files with a `_delta_log` subdirectory beside them, and the log is what makes it a table. Each successful write creates one commit file named for its version, zero-padded to twenty digits: `00000000000000000000.json`, `00000000000000000001.json`, and so on. A commit file is newline-delimited JSON, one *action* per line — `add` (a data file joins the table, with its size, partition values and column statistics), `remove` (a tombstone saying a file left the table), `metaData` (schema and partition columns), `protocol` (required reader/writer versions), `commitInfo` (provenance for `DESCRIBE HISTORY`). Every ten commits by default Delta also writes a `.checkpoint.parquet` summarising the whole state, and `_last_checkpoint` names the newest one. The current table is the replay of those actions, not whatever Parquet files happen to be lying in the directory.

code

text · 10 lines
text
events/
├── part-00000-3c1f9a2b.snappy.parquet
├── part-00001-77e0d4c5.snappy.parquet
└── _delta_log/
    ├── 00000000000000000000.json
    ├── 00000000000000000001.json
    ├── 00000000000000000002.json
    ├── 00000000000000000010.checkpoint.parquet
    ├── 00000000000000000010.json
    └── _last_checkpoint

go deeper

for a junior

Be ready to say that a Delta table is Parquet files plus a _delta_log directory, that each write adds one numbered JSON commit file, and that add and remove name data files entering and leaving the table.

for a middle

Explain the action types and how a snapshot is rebuilt: load the latest checkpoint, replay newer JSON commits, reconcile adds against removes by path. Know what stats on an add is used for.

for a senior

Show you use the log operationally — reading commitInfo to explain a bad job, spotting a table whose log has grown to tens of thousands of tiny commits, knowing why directory size and table size diverge.

for a principal

Own the consequences for the platform: which engines are allowed to write, why hand-placed files are never data, and how log retention interacts with the audit and time-travel guarantees you promise consumers.

## The directory, and why the log is the table A Delta Lake table on disk or object storage looks like this: some Parquet files, possibly nested in partition directories, and one subdirectory called `_delta_log`. The Parquet files hold rows. The log holds *the table*. That split is the whole idea — nothing in the data directory itself tells you which Parquet files are currently part of the table, so a reader that simply lists the directory and reads every `.parquet` file it finds will read files that have been logically deleted, files left behind by a job that crashed halfway, and old files that a compaction already replaced. Correct readers never do that: they read the log and open only the files the log says are live. ## Commit files Every successful write produces exactly one new file whose name is its version number, zero-padded to twenty digits with a `.json` suffix: `00000000000000000000.json` for the table's creation, then `...0001.json`, `...0002.json`. Versions are consecutive integers with no gaps. The file is newline-delimited JSON — one JSON object per line, each object a single **action**. Commit N is atomic because the writer is allowed to create `N.json` only if that file does not already exist. Either the file appears whole or the commit did not happen; there is no partially applied change. Readers therefore never see half a write. ## The action types - **`add`** — a data file becomes part of the table. Carries `path` (relative to the table root), `partitionValues`, `size`, `modificationTime`, `dataChange`, and `stats`: a JSON string with `numRecords` and per-column `minValues`, `maxValues` and `nullCount`. Those statistics are what lets the engine skip files without opening them. - **`remove`** — a tombstone. The named file is no longer part of the table as of this version, with a `deletionTimestamp`. The Parquet file itself still exists on storage; only a later cleanup deletes the bytes, which is what keeps time travel to earlier versions possible. - **`metaData`** — the schema as a JSON string, the partition columns, the format, and table properties. Rewritten whenever schema or properties change. - **`protocol`** — `minReaderVersion` and `minWriterVersion`, plus named `readerFeatures`/`writerFeatures` on tables using table features. A client below the required version must refuse the table rather than misread it. - **`txn`** — an application id and a monotonically increasing version, used to make streaming writes idempotent: if the same appId/version is already in the log, the batch is not applied twice. - **`commitInfo`** — provenance only: timestamp, operation name (`WRITE`, `DELETE`, `MERGE`, `OPTIMIZE`), operation parameters and metrics, `readVersion`, `isBlindAppend`. This is the row you see in `DESCRIBE HISTORY`. It is informational; state reconstruction does not depend on it. A useful subtlety is the `dataChange` flag on `add`/`remove`. A compaction that only reshuffles rows between files sets `dataChange: false`, so a streaming reader knows there is no new data to emit even though the file set changed. ## Checkpoints and _last_checkpoint Replaying thousands of JSON files on every query would be slow, so every tenth commit by default (`delta.checkpointInterval`) Delta writes a checkpoint: a Parquet file named for its version, `00000000000000000010.checkpoint.parquet`, containing the complete set of live actions at that version — all current `add`s, tombstones still within retention, and the latest `metaData`, `protocol` and `txn` entries. Alongside it sits `_last_checkpoint`, a tiny JSON file naming the newest checkpoint version and its size, so a reader can jump straight there instead of listing the directory. Very large tables may split a checkpoint across multiple part files, and newer Delta versions offer a v2 checkpoint layout with sidecar files. ## Reconstructing the current table A reader opens `_last_checkpoint`, loads that checkpoint, then replays only the JSON commits with higher version numbers. Adds and removes are reconciled by file path — a path that was added and later removed is gone; `metaData` and `protocol` are last-write-wins. What comes out is a *snapshot*: an exact list of data files, a schema, and a version number. Time travel is just replaying up to an earlier version instead of the latest. ## What the log is not It is not the data, and it is not a row-level redo log — Delta records file-level changes, never individual row images (Change Data Feed, when enabled, writes separate change files). It is also not infinite: `delta.logRetentionDuration`, 30 days by default, bounds how much history is kept, and expired commit files are cleaned up once a checkpoint covers them, which is why very old versions eventually stop being readable.

  • If the log says a file was removed, why is the Parquet file still sitting in the directory?
    A `remove` action is a tombstone: it takes the file out of the current version only. The bytes stay so that time travel to earlier versions still works, and a separate retention-based cleanup deletes them later. That is also why listing the directory over-counts a Delta table's real size.
  • What happens if someone drops an extra Parquet file into a Delta table directory by hand?
    Nothing — it is invisible. No `add` action references it, so no reader includes it, and it will eventually be treated as an orphan and cleaned up by the retention job. The only supported way to add data is a commit that writes an `add` action.
  • Which action tells you who ran the last MERGE and how many rows it touched?
    `commitInfo`, the provenance line in each commit file, carries the operation name, its parameters, metrics such as rows updated and files added, and the engine identity. `DESCRIBE HISTORY` is just a rendering of those entries; it is informational and not used to rebuild table state.

The Parquet files are the warehouse shelves; _delta_log is the ledger. Only the ledger says which crates are currently part of the inventory — walking the aisles and counting what you see gives the wrong answer.

saying these in an interview costs you the question

  • Saying the current table is whatever Parquet files exist in the directory
  • Calling the log a row-level redo log of individual changed rows
  • Believing a remove action physically deletes the Parquet file immediately
  • Assuming _delta_log stores the data itself rather than metadata about files
  • Thinking commit files can be edited in place after they are written

context

open as a page

In Delta Lake, what does OPTIMIZE change about a table's data files?

level: middleimportance: must knowfreq 70%

basics

~20 s

OPTIMIZE bin-packs many small Parquet files into fewer large ones (roughly 1 GB by default), committing remove and add actions in a new table version. Row content is unchanged, and the replaced files stay on storage until VACUUM deletes them.

open as a page

In a Delta Lake table, what actions does the _delta_log commit for a row-level DELETE contain?

level: middleimportance: must knowfreq 66%

basics

~20 s

A row-level DELETE on a Delta table without deletion vectors rewrites the affected Parquet file: the commit holds a remove tombstone for the old file path, an add for the freshly written file minus those rows, and a commitInfo entry describing the operation.

open as a page

Why does a Delta Lake time-travel query fail after VACUUM runs?

level: seniorimportance: must knowfreq 62%

basics

~20 s

VACUUM physically deletes data files that the current version no longer references once they are older than the retention threshold (7 days by default), while _delta_log keeps commit history for 30 days. The old version still resolves but its files are gone, so the read fails.

open as a page

Why does a Delta Lake MERGE INTO fail when the source contains duplicate keys?

level: middleimportance: should knowfreq 58%

basics

~20 s

A matched UPDATE or DELETE must have a single, deterministic outcome per target row. If two source rows match the same target row, Delta cannot decide which wins and aborts the merge. The fix is to deduplicate the source to one row per key.

open as a page

In Delta Lake, what does ZORDER BY add to an OPTIMIZE command?

level: middleimportance: should knowfreq 55%

basics

~20 s

ZORDER BY makes OPTIMIZE co-locate rows with similar values of the named columns into the same files, so per-file min/max statistics get tighter and the planner skips more files for filters on those columns. Plain OPTIMIZE only bin-packs.

open as a page

Why does Delta Lake write Parquet checkpoint files into _delta_log?

level: middleimportance: should knowfreq 52%

basics

~20 s

Checkpoints spare a Delta reader from replaying the whole JSON history: one Parquet file holds the complete set of live actions at a version, so the reader loads it plus the handful of commits written after it, found via the _last_checkpoint file.

open as a page

Two Spark jobs write to the same Delta Lake table at once — how does the transaction log settle it?

level: seniorimportance: should knowfreq 60%

basics

~20 s

Delta uses optimistic concurrency: each writer tries to create the next numbered commit file in _delta_log, and only one can. The loser re-reads the commits it missed, checks whether they logically conflict with what it read, then retries or throws a concurrency exception.

open as a page

A client refuses a Delta Lake table, citing its protocol version — what does that mean?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Every Delta table records a protocol action with minReaderVersion and minWriterVersion; a client that supports less must refuse the table rather than misread it. Reader 3 and writer 7 switch to named table features listed in readerFeatures and writerFeatures.

open as a page

How would you set OPTIMIZE and VACUUM policy across hundreds of Delta Lake tables?

level: principalimportance: should knowfreq 34%

basics

~20 s

Classify tables by write pattern and read value, then set retention as a published contract and compaction where measured query savings beat the rewrite cost. Default to auto-compaction for streaming sinks, scheduled OPTIMIZE for merge-heavy tables, and weekly VACUUM at or above the safe retention floor.

open as a page

In Delta Lake, how does liquid clustering differ from partitioning plus ZORDER BY?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

Liquid clustering is declared on the table with CLUSTER BY and applied incrementally by OPTIMIZE. Its keys can be changed without rewriting existing data, and it replaces both partitioning and ZORDER BY on that table rather than combining with them.

open as a page

With deletion vectors enabled on a Delta Lake table, what does a DELETE write?

level: seniorimportance: nice to knowfreq 36%

basics

~20 s

With deletion vectors enabled, a Delta DELETE leaves the Parquet file untouched and writes a bitmap of deleted row positions to a side file; the commit re-registers the same file path carrying a deletionVector reference instead of rewriting data.

open as a page