Why does a Delta Lake time-travel query fail after VACUUM runs?
answer
- history has two clocks, not one
- the commit survives, the bytes do not
- seven days versus thirty days by default
- the shorter window is the real guarantee
- one property governs deleted-file retention
basics
~20 sVACUUM 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.
solid answer
~50 sTime travel needs two things: the commit entry in `_delta_log` **and** the data files that commit referenced. The two have independent retention settings. `delta.logRetentionDuration` defaults to 30 days and governs how long commit history survives; `delta.deletedFileRetentionDuration` defaults to 7 days and governs how long a file that has been marked `remove` stays on storage before `VACUUM` may delete it. So a `VERSION AS OF 40` from ten days ago still resolves against the log, Delta computes the file list, and then the reader hits a file-not-found error on object storage. The real time-travel guarantee is the **shorter** of the two windows. If you promise consumers 30 days of history, raise `delta.deletedFileRetentionDuration` to match, and never call `VACUUM ... RETAIN 0 HOURS` — the safety check that blocks retention below 168 hours exists because in-flight writers can still be referencing those files.
code
sql · 11 lines-- See what a cleanup would remove before removing it
VACUUM analytics.events RETAIN 168 HOURS DRY RUN;
-- Make the promised time-travel window real on both clocks
ALTER TABLE analytics.events SET TBLPROPERTIES (
delta.deletedFileRetentionDuration = 'interval 30 days',
delta.logRetentionDuration = 'interval 30 days'
);
-- The query that fails once files have aged out
SELECT * FROM analytics.events VERSION AS OF 40;go deeper
Know that Delta lets you read an earlier version of a table, and that a cleanup command called VACUUM removes old data files, which is what can make those old reads stop working.
Explain the two independent retention settings and their defaults, what VACUUM is allowed to delete, and why the usable time-travel window is the shorter of the two clocks.
Diagnose the failure from the error and DESCRIBE HISTORY, set retention as an explicit contract with consumers, and explain why the 168-hour safety check protects concurrent writers and running queries.
Own the policy: how long history must be recoverable for audit and rollback, what that costs in retained storage across a whole lakehouse, and who is accountable when a retention change silently shortens someone's recovery window.
## Two clocks, not one A Delta table's history has two physical components, and people mentally merge them: 1. **The log.** `_delta_log/00000000000000000040.json` and its neighbours record what each commit added and removed, plus periodic Parquet checkpoints and `_last_checkpoint`. Retention is controlled by `delta.logRetentionDuration`, default **30 days**. Cleanup happens automatically after checkpoints. 2. **The data files.** The Parquet files themselves. Once a commit marks a file `remove`, that file is no longer part of the current table state, but it is still on storage — historical versions still point at it. It becomes eligible for deletion after `delta.deletedFileRetentionDuration`, default **7 days**, and is only actually deleted when someone runs `VACUUM`. Time travel — `SELECT * FROM t VERSION AS OF 40` or `TIMESTAMP AS OF '2026-08-05'` — needs both. The log tells Delta which files made up version 40; the storage layer must still hold them. ## The failure mode With the defaults, a table that is VACUUMed regularly gives you seven days of *usable* time travel and thirty days of *resolvable* versions. A query to a ten-day-old version therefore does not say "that version is gone"; it plans successfully and then fails part-way through the scan with a file-not-found error, usually accompanied by a message noting that files referenced by the transaction log are missing and that this can happen after a manual deletion or an aggressive VACUUM. The same applies to `RESTORE TABLE t TO VERSION AS OF 40`, which needs the same files to be re-addable, and to a Structured Streaming job resuming from an old offset in the table. A second, sharper version of the failure: a query that was **already running** when VACUUM deleted its files. It resolved a file list, then lost the files mid-scan. This is why the retention threshold is not zero by default. ## What VACUUM deletes, exactly `VACUUM tbl [RETAIN n HOURS] [DRY RUN]` deletes, from the table's directory tree: - data files that are **not referenced by the current table version** and whose modification time is older than the retention threshold, and - files under the table directory that the log does not track at all — orphans from failed or abandoned writes. It does **not** touch `_delta_log`; directories beginning with an underscore are skipped, and log cleanup is a separate, automatic mechanism driven by `delta.logRetentionDuration`. `DRY RUN` lists the candidate files without deleting, which is the correct first step on any table you have not vacuumed before. That second bullet is why untracked-file deletion is dangerous with a short window: a long-running writer's not-yet-committed output files look exactly like orphans. ## The retention safety check Delta refuses `RETAIN` values below 168 hours unless you explicitly disable the retention-duration check (`spark.databricks.delta.retentionDurationCheck.enabled = false`). Candidates who casually say "just run `VACUUM RETAIN 0 HOURS` to save storage" are describing the standard way to corrupt a table with concurrent writers, and interviewers notice. The legitimate use of a short retention is a one-off cleanup on a table you know is quiesced. ## Setting the window deliberately Treat the time-travel window as a **contract with consumers**, then set both properties to honour it: ```sql ALTER TABLE analytics.events SET TBLPROPERTIES ( delta.deletedFileRetentionDuration = 'interval 30 days', delta.logRetentionDuration = 'interval 30 days' ); ``` The cost is storage: every rewritten file — and OPTIMIZE and MERGE rewrite a lot — is retained for the whole window. A heavily compacted table with a 30-day window can hold several times its live size. That is the actual tradeoff to reason about: recovery and audit ability against object-storage bill. Also note that changing `deletedFileRetentionDuration` does not resurrect anything already deleted. If a previous VACUUM ran with a 7-day window, the past is gone and only the future window widens. ## Diagnosing it in practice `DESCRIBE HISTORY tbl` shows `VACUUM START` and `VACUUM END` operations with the number of files deleted and the retention actually applied — so you can prove which run removed the files that a failed time-travel query wanted. Pair it with the earliest still-usable version: the oldest commit whose files have not aged past the deleted-file retention. ## The one-line answer Time travel is bounded by file retention, not by log retention, and the two defaults differ. If time travel "stopped working" a week after a cleanup job was introduced, that is not a bug — it is the 7-day default doing exactly what it says.
- Which property actually bounds how far back time travel works?`delta.deletedFileRetentionDuration` in practice, because it decides when VACUUM may delete files a historical version still points at — 7 days by default. `delta.logRetentionDuration` (30 days) only keeps the commit entries resolvable. The usable window is the smaller of the two, so honouring a 30-day promise means raising the file-retention property, not just leaving log retention alone.
- Why does Delta block VACUUM with a retention below 168 hours?Because VACUUM also deletes files under the table directory that the log does not track, and an in-flight writer's uncommitted output looks exactly like an orphan. A short window can delete files a concurrent write or a running query still needs, corrupting the table. The check can be disabled with a Spark configuration, but only makes sense on a table you know is quiesced.
- Does RESTORE TABLE work on a version whose files were vacuumed?No. RESTORE creates a new commit that re-adds the file set of the target version, so those files must still exist on storage. If VACUUM removed them, the restore fails for the same reason the time-travel read does. The window in which restore is possible is the deleted-file retention window, which is why disaster-recovery expectations must be set alongside that property.
- How do you prove which job removed the files a failed time-travel query needed?`DESCRIBE HISTORY` records VACUUM START and VACUUM END commits with the retention applied and the number of files deleted, so you can line the cleanup up against the version you tried to read. Combined with the commit that first marked those files removed, that gives the full chain: rewritten by an OPTIMIZE or MERGE, aged past retention, deleted by a named VACUUM run.
The log is a library catalogue and the data files are the books. VACUUM sends old books to the shredder while the catalogue still lists them, so the record of version 40 survives long after the shelf it points to is empty.
saying these in an interview costs you the question
- Assumes 30-day log retention means 30 days of time travel
- Says VACUUM deletes _delta_log commit files
- Recommends VACUUM RETAIN 0 HOURS to save storage
- Thinks raising retention restores already-deleted files
- Believes OPTIMIZE, not VACUUM, is what breaks time travel