Delta Lake Time Travel
Learn time travel with Delta Lake in this Databricks tutorial — hands-on steps, troubleshooting, and what to study next.
Focus: learn time travel with delta lake
You've just loaded a critical dataset into a Delta table, ran a transformation, and then realized — that transformation was a mistake. Every record is now corrupted. In traditional data warehousing, you'd scramble to restore from a backup or pray that someone had a point-in-time copy. With Delta Lake, you don't pray — you travel. Delta Lake's time travel capability lets you query, restore, and audit your data at any point in history, turning 'oops' into a five-second fix. This lesson is your practical guide to mastering this game-changing feature on Databricks.
The problem this lesson solves
The core pain point is simple: data is volatile and mistakes are inevitable. You might overwrite a table with bad data, run a buggy upsert, or accidentally DELETE millions of rows. Without versioning, recovery is a nightmare — you'd have to restore from external backups (if they exist), reprocess raw source data, or lose hours of work. Time travel eliminates this panic by making every version of a Delta table queryable and restorable. It also solves subtle problems like reproducibility: if your nightly ETL runs at 2 AM, but a colleague asks for the exact dataset from yesterday at noon, you can serve it instantly. Regulatory audits, customer dispute investigations, and A/B test analyses all rely on the ability to see data as it existed at a specific moment. Without time travel, these queries require complex CDC (change data capture) pipelines or slow, expensive full re-reads. Delta Lake's built-in versioning makes 'what was the data at this timestamp?' a first-class query.
Core concept / mental model
Think of a Delta table as a journal with a clear undo button. Every transaction — INSERT, UPDATE, DELETE, MERGE — writes a new entry (a version) to the table's transaction log. This log, stored in the _delta_log directory, is the heart of time travel. It's not a copy of the data; it's a record of changes. When you time travel, you're asking the query engine to reconstruct the table state by replaying (or, more efficiently, directly reading) the files referenced by a specific version.
Here's the key visualization:
- Current state = the latest version's list of data files.
- Version N = a snapshot that includes only files valid up to version N.
- Timestamp = a specific point in time; Delta maps it to the closest version at or before that moment.
Delta Lake stores multiple versions of your data files, often using data files + deletion vectors (for small changes) or complete rewrites (for large updates). This design gives you flexibility: you can travel back weeks or months, depending on your retention settings. Unlike a database snapshot, which is a separate, heavy copy, Delta's versioning is incremental and lightweight — it stores deltas, not entire copies.
Pro tip: Think of
VERSION AS OFas a database cursor andTIMESTAMP AS OFas a time machine. Both are equally valid; choose the one that fits your use case.
How it works step by step
Time travel relies on two core concepts: versions and timestamps. Here's the flow:
- Every write creates a version. The first write creates version 0, the next version 1, and so on. The
_delta_logdirectory holds JSON files (e.g.,00000000000000000000.json) that describe each transaction. - Choose your travel method. Use
VERSION AS OFto query a specific version number, orTIMESTAMP AS OFto query as of a date/time. The syntax is the same regardless of whether you're using Spark SQL, PySpark, or Scala. - Delta scans the log. For a given version, Delta reads the transaction log to determine which files are valid. It then reads only those files, ignoring newer changes.
- You query, or you restore. You can
SELECTfrom a historical version (read-only time travel) or useRESTOREto overwrite the current state with a previous one. - Cleanup is controlled. Old versions aren't stored forever. Delta uses
VACUUMto delete files that are no longer needed, according to your retention policy.
The beauty is that this is all metadata-driven. The data files themselves are immutable; time travel just changes the 'lens' you view them through.
Hands-on walkthrough
Let's put this into practice with a PySpark example on Databricks. We'll create a Delta table, perform several operations, and then time travel.
Step 1: Create a versioned table
First, we set up a table and make a few changes:
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
# Create table with initial data (version 0)
df = spark.range(1, 5).toDF("id")
df.write.format("delta").mode("overwrite").saveAsTable("time_travel_demo")
# Make a change (version 1)
df_append = spark.range(5, 9).toDF("id")
df_append.write.format("delta").mode("append").saveAsTable("time_travel_demo")
# Overwrite with wrong data (version 2)
df_wrong = spark.range(100, 105).toDF("id")
df_wrong.write.format("delta").mode("overwrite").saveAsTable("time_travel_demo")
print("Current data:")
spark.table("time_travel_demo").show()
Expected output:
Current data:
+---+
| id|
+---+
|100|
|101|
|102|
|103|
|104|
+---+
Step 2: Query a historical version
Now we travel back:
# Query version 1 (after the append)
print("Version 1 data:")
spark.sql("SELECT * FROM time_travel_demo VERSION AS OF 1").show()
# Query timestamp from earlier
from datetime import datetime
past_time = "2025-01-01T12:00:00" # adjust to your timeline
print("Data as of timestamp:")
spark.sql(f"SELECT * FROM time_travel_demo TIMESTAMP AS OF '{past_time}'").show()
Expected output (for version 1):
+---+
| id|
+---+
| 1|
| 2|
| 3|
| 4|
| 5|
| 6|
| 7|
| 8|
+---+
The timestamp query returns the data as it existed at that point — if 2025-01-01T12:00:00 is before the first write, you might get an error (see troubleshooting).
Step 3: Restore to a previous version
When you're ready to fix the bad overwrite, restore version 1:
spark.sql("RESTORE TABLE time_travel_demo TO VERSION AS OF 1")
print("After restore, current data:")
spark.table("time_travel_demo").show()
Expected output:
+---+
| id|
+---+
| 1|
| 2|
| 3|
| 4|
| 5|
| 6|
| 7|
| 8|
+---+
After restore, the table's current version is now a new version (version 3) whose state matches version 1. This is important: RESTORE doesn't delete history; it creates a new version that points to the old data files.
Step 4: Check history
Use DESCRIBE HISTORY to see all versions:
spark.sql("DESCRIBE HISTORY time_travel_demo").select("version", "timestamp", "operation", "operationParameters").show(truncate=False)
Expected output (simplified):
+-------+-------------------+---------+----------------------------------------------------------------------+
|version|timestamp |operation|operationParameters |
+-------+-------------------+---------+----------------------------------------------------------------------+
|3 |2025-01-15 10:30:00|RESTORE |{...}
|2 |2025-01-15 10:20:00|WRITE |{...}
|1 |2025-01-15 10:10:00|WRITE |{...}
|0 |2025-01-15 10:00:00|WRITE |{...}
+-------+-------------------+---------+----------------------------------------------------------------------+
This history is your audit trail — it shows exactly what operation changed the table and when.
Compare options / when to choose what
Time travel is powerful, but it's not the only recovery/audit tool. Here's how it stacks up against alternatives:
| Option | Use case | Pros | Cons |
|---|---|---|---|
Time Travel (VERSION/TIMESTAMP AS OF) |
Read-only historical queries, quick restore | Built-in, zero extra infrastructure, very fast | Retention limited by logRetentionDuration and deletedFileRetentionDuration |
RESTORE TABLE |
Make a previous version the current state | Simple one-liner, keeps history | Creates a new version — doesn't erase your mistake; requires write permissions |
Full Table Backup (e.g., OPTIMIZE + export) |
Disaster recovery beyond retention window | Durable, independent of table's retention | Extra storage cost, manual process, can be stale |
| External CDC / audit tables | Regulatory compliance with custom requirements | Flexible, custom joins | High engineering effort, complex pipelines |
When to choose what:
- Quick 'oops' fix: Use
RESTORE TABLE. You'll be back in business in seconds. - Audit query: Use
TIMESTAMP AS OF— perfect for 'what did we send this customer on June 1?' - Long-term archival: Time travel won't hold data forever. Set up a scheduled export or use Delta's
VACUUMwith a long retention for critical tables. - Version control for analytics: If you're building reproducible ML training sets, time travel ensures you can recreate the exact dataset that trained a model.
Troubleshooting & edge cases
Time travel is smooth, but here are common pitfalls and how to fix them:
1. Version doesn't exist
Error: The provided version 5 is not valid. The current version is 3.
Cause: You specified a version higher than the latest. Fix: Check DESCRIBE HISTORY to find a valid version. Use VERSION AS OF with a number between 0 and the latest, or omit it to get current data.
2. Timestamp out of range
Error: The provided timestamp (2025-01-01T12:00:00) is before the earliest available version.
Cause: Delta can't reconstruct data before the first recorded version. Fix: Use a later timestamp, or query DESCRIBE HISTORY to find the earliest version's timestamp.
3. VACUUM deleted your old versions
Symptom: You can travel back a week, but not a month.
Cause: VACUUM removes files older than deletedFileRetentionDuration (default 7 days). Fix: For critical tables, increase retention: ALTER TABLE ... SET TBLPROPERTIES ('delta.deletedFileRetentionDuration' = 'interval 30 days'). Remember: VACUUM is irreversible — don't run it blindly.
4. Wrong data after timestamp query
Symptom: TIMESTAMP AS OF returns data that doesn't match your expectation.
Cause: Timestamps map to the closest version at or before that time, which might be an earlier write than you intended. Fix: Use VERSION AS OF for precision, or refine the timestamp to align with a known write time (check DESCRIBE HISTORY).
5. Restore doesn't free up storage
Symptom: After RESTORE, your table still uses the same disk space.
Cause: RESTORE doesn't delete the 'bad' version's files; it just points the current state to older files. Those files still exist until you VACUUM (and even then, only after the retention period). Fix: Run VACUUM after you're certain you won't need the bad version again.
What you learned & what's next
You now know how to learn time travel with Delta Lake — from the underlying versioning mechanism to practical queries and restores. You can explain the core idea (a transaction log of immutable versions), complete a hands-on exercise (creating versions, querying with VERSION AS OF and TIMESTAMP AS OF, and restoring), and troubleshoot common issues like invalid versions or retention limits. You've also seen how it fits into a broader data engineering toolbox.
Ready for more? The next lesson in this Databricks track builds on your skills — you'll move from querying history to managing table history with VACUUM and retention policies, or perhaps dive into optimizing performance with Z-order and compaction. Either way, you're now equipped to treat every Delta table as a time machine, ready for any accidental overwrite or tricky audit request.
Practice recap
Try this mini-exercise: create a Delta table, append data, overwrite it with wrong data, then query version 0 and version 1. After that, restore to version 1 and run DESCRIBE HISTORY to confirm the version count increased. Finally, set delta.deletedFileRetentionDuration to '7 days' and run VACUUM to clean up unused files — just be sure you don't need that bad version anymore.
Common mistakes
- Assuming time travel is permanent: by default,
VACUUMremoves files older than 7 days, so you can't query beyond that unless you adjust retention settings. - Using
TIMESTAMP AS OFwith a timestamp before the table's first write — Delta throws an error; always checkDESCRIBE HISTORYfor the earliest version. - Running
RESTOREand expecting it to delete the bad version — it creates a new version that reuses old files, so you still needVACUUMto reclaim storage. - Forgetting that
VERSION AS OFis 0-indexed; version 0 is the first write, not 1, so counting fromDESCRIBE HISTORYcan be off by one.
Variations
- In addition to
VERSION AS OFandTIMESTAMP AS OFin Spark SQL, you can use the PySpark DataFrame API withspark.read.table('table').option('versionAsOf', 1). - For automatic data retention, you can set
delta.logRetentionDurationanddelta.deletedFileRetentionDurationto control how long time travel is available. - You can also use
RESTORE TABLEwithTIMESTAMP AS OFto restore to a specific point in time, not just a version number.
Real-world use cases
- A data engineer restores a production table after a bad ETL run overwrites it with corrupted data —
RESTORE TABLE ... TO VERSION AS OFbrings it back in seconds. - An analyst audits a customer refund dispute by querying the transactional table as of a specific timestamp to show what was in the account that day.
- A machine learning team reproduces a model's training dataset by using
TIMESTAMP AS OFto recreate the exact feature snapshot from the day the model was trained.
Key takeaways
- Every write to a Delta table creates a new version, tracked in the
_delta_log, enabling time travel. - Use
VERSION AS OForTIMESTAMP AS OFto query historical data without altering the current state. RESTORE TABLElets you make a past version the current one, but it does not delete the old version.- Time travel is not unlimited —
VACUUMand retention settings control how far back you can go. - Always check
DESCRIBE HISTORYto understand your table's version timeline before querying or restore. - Time travel is invaluable for reproducible analytics, audits, and accidental data loss recovery.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.