Delta Lake is an open-source storage layer built on top of your existing data lake (like Azure Data Lake, S3, or GCS). It brings reliability, performance, and ACID transactions to data lakes โ turning them into โLakehouses.โ
- โ ACID Transactions (Atomicity, Consistency, Isolation, Durability)
- ๐ Schema Enforcement & Evolution
- ๐ Time Travel
- ๐ Upserts (MERGE), Deletes, Updates
- ๐งฉ Data Versioning
- โก Optimized performance with Z-Ordering
- โป๏ธ Integrates seamlessly with Spark, Databricks, and cloud storage
When you create a Delta table, it looks like this inside your storage:
/mnt/delta/sales/
โโโ part-00000-xxxx.snappy.parquet
โโโ part-00001-yyyy.snappy.parquet
โโโ _delta_log/
โโโ 00000000000000000000.json
โโโ 00000000000000000001.json
โโโ ...
- The data files are stored in Parquet format.
- The
_delta_logdirectory contains JSON log files (transaction logs). - Each JSON file records every transaction (insert/update/delete/merge).
Letโs start with some dummy data ๐
data = [
(1, "Alice", 1000),
(2, "Bob", 1500),
(3, "Charlie", 2000)
]
columns = ["id", "name", "salary"]
df = spark.createDataFrame(data, columns)
df.show()๐ Output:
| id | name | salary |
|---|---|---|
| 1 | Alice | 1000 |
| 2 | Bob | 1500 |
| 3 | Charlie | 2000 |
df.write.format("delta").mode("overwrite").save("/mnt/delta/employees")โ This command:
- Writes the DataFrame to a Delta table format.
- Creates a folder with
_delta_log.
delta_df = spark.read.format("delta").load("/mnt/delta/employees")
delta_df.show()๐ก You can also create a SQL table:
CREATE TABLE employees USING DELTA LOCATION '/mnt/delta/employees';
SELECT * FROM employees;| Mode | Description |
|---|---|
append |
Adds new records |
overwrite |
Replaces existing data |
ignore |
Skips write if table exists |
errorifexists |
Fails if table exists |
merge |
Conditional insert/update |
Example:
df.write.format("delta").mode("append").save("/mnt/delta/employees")| Feature | Parquet | Delta Lake |
|---|---|---|
| ACID Transactions | โ No | โ Yes |
| Time Travel | โ No | โ Yes |
| Schema Enforcement | โ No | โ Yes |
| Upserts & Deletes | โ No | โ Yes |
| Data Versioning | โ No | โ Yes |
| Performance Optimizations | โ ZORDER, OPTIMIZE |
Delta keeps multiple versions of data using transaction logs.
# Using version number
df_v0 = spark.read.format("delta").option("versionAsOf", 0).load("/mnt/delta/employees")
# Using timestamp
df_time = spark.read.format("delta").option("timestampAsOf", "2025-10-05T10:00:00Z").load("/mnt/delta/employees")SELECT * FROM employees VERSION AS OF 0;If you try to write a column that doesnโt exist โ Delta will throw an error.
You can add new columns automatically by enabling mergeSchema:
new_data = [(4, "David", 2500, "IT")]
new_df = spark.createDataFrame(new_data, ["id", "name", "salary", "dept"])
new_df.write.format("delta") \
.mode("append") \
.option("mergeSchema", "true") \
.save("/mnt/delta/employees")Used for updating or inserting data in a single transaction.
MERGE INTO employees AS t
USING updates AS s
ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET t.salary = s.salary
WHEN NOT MATCHED THEN
INSERT (id, name, salary) VALUES (s.id, s.name, s.salary);Equivalent PySpark code:
from delta.tables import *
deltaTable = DeltaTable.forPath(spark, "/mnt/delta/employees")
updatesDF = spark.createDataFrame([(2, "Bob", 1800)], ["id", "name", "salary"])
deltaTable.alias("t").merge(
updatesDF.alias("s"),
"t.id = s.id"
).whenMatchedUpdateAll() \
.whenNotMatchedInsertAll() \
.execute()Cleans up old files no longer needed for time travel.
VACUUM employees RETAIN 168 HOURS;๐ Default retention: 7 days (168 hours)
To force immediate cleanup (
SET spark.databricks.delta.retentionDurationCheck.enabled = false;
VACUUM employees RETAIN 0 HOURS;Used to compact small files and improve query speed.
OPTIMIZE employees;
OPTIMIZE employees ZORDER BY (dept);ZORDER sorts data by specific columns for faster queries.
Tracks changes (inserts/updates/deletes) between table versions.
Enable it:
ALTER TABLE employees SET TBLPROPERTIES (delta.enableChangeDataFeed = true);Read changes:
spark.read.format("delta")
.option("readChangeFeed", "true")
.option("startingVersion", 1)
.load("/mnt/delta/employees")df.writeStream.format("delta") \
.option("checkpointLocation", "/mnt/delta/checkpoints") \
.start("/mnt/delta/stream_data")stream_df = spark.readStream.format("delta").load("/mnt/delta/stream_data")| Type | Storage Location | Example |
|---|---|---|
| Managed | Stored inside Databricks metastore | CREATE TABLE employees (id INT) USING DELTA |
| External | Stored in user-defined path | CREATE TABLE employees USING DELTA LOCATION '/mnt/delta/employees' |
If you have an existing Parquet table:
CONVERT TO DELTA parquet.`/mnt/delta/parquet_data`- Checkpoints are Parquet summaries of transaction logs.
- They help Spark quickly rebuild table state during reads.
- Stored every 10 commits by default.
Delta uses transaction logs with optimistic concurrency control. If two jobs write simultaneously, one fails to avoid data corruption.
RESTORE TABLE employees TO VERSION AS OF 3;DLT is a Databricks-managed pipeline framework built on Delta Lake. It automates:
- Data ingestion,
- Quality enforcement (expectations),
- Pipeline orchestration.
| Scenario | How to Handle |
|---|---|
| Duplicate small files | Use OPTIMIZE |
| Deleted wrong data | Use TIME TRAVEL or RESTORE |
| Schema mismatch | Enable mergeSchema |
| Need incremental loads | Use MERGE or Change Data Feed |
| Query slow | Use ZORDER and caching |
| Feature | Command |
|---|---|
| Create Delta Table | df.write.format("delta").save() |
| Read Delta Table | spark.read.format("delta").load() |
| Time Travel | option("versionAsOf", x) |
| Merge (Upsert) | MERGE INTO |
| Vacuum | VACUUM table |
| Optimize | OPTIMIZE table |
| Z-Order | OPTIMIZE table ZORDER BY (col) |
| Change Data Feed | readChangeFeed = true |
| Restore | RESTORE TABLE ... |