The question "should we use Databricks or open-source Spark?" comes up in almost every data engineering team at some point. The marketing answer is simple — Databricks is "Spark plus everything else." The real answer depends on your team size, cloud spend, existing infrastructure, and whether the managed layer is worth what you pay for it. This guide gives you the framework to make that decision with the vendor's published numbers, what they do and don't cover, and honest tradeoffs.
Databricks was founded by the creators of Apache Spark, so Databricks runs Spark underneath. Every Databricks cluster is a Spark cluster. The difference is everything built on top: the Photon execution engine, Delta Lake, Unity Catalog, MLflow integration, AutoLoader, and the collaborative notebook environment.
What Databricks adds on top of open-source Spark
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Databricks vs Open-Source Spark Stack │
├──────────────────┬──────────────────────────────────┬──────────────────────────────────┤
│ Layer │ Open-Source Spark │ Databricks │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ Execution Engine │ Spark SQL engine (JVM) │ Photon (C++, vectorised, SIMD) │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ Table Format │ Delta Lake — Apache-2.0, gives │ Same Delta Lake, plus Photon- │
│ │ ACID, time travel and CDF on │ optimised reads and managed │
│ │ plain Spark │ table maintenance │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ Cluster Mgmt │ EMR Managed Scaling, GKE, or │ Managed autoscaling and spot- │
│ │ self-managed │ interruption handling │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ Data Catalog │ Hive Metastore, Glue, or Unity │ Managed Unity Catalog — hosted │
│ │ Catalog (open-sourced │ governance, lineage, fine- │
│ │ Apache-2.0, June 2024) │ grained access control │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ Streaming │ Spark Structured Streaming │ + Lakeflow pipelines (formerly │
│ │ │ Delta Live Tables) │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ ML Platform │ MLflow — Apache-2.0, includes │ Managed MLflow — hosted tracking │
│ │ the Model Registry │ server, no server to run │
│ │ │ yourself │
├──────────────────┼──────────────────────────────────┼──────────────────────────────────┤
│ SQL Dialect │ Spark SQL │ Databricks SQL (superset of │
│ │ │ Spark SQL) │
└──────────────────┴──────────────────────────────────┴──────────────────────────────────┘Photon engine — the biggest performance differentiator
Databricks' Photon is a rewrite of the Spark execution engine in C++ with vectorised processing. Instead of processing one row at a time in the JVM, Photon processes columns of data in batches using SIMD CPU instructions — the same technique columnar databases like DuckDB and ClickHouse use. The performance improvement is most dramatic on aggregations, joins, and scans over large Parquet/Delta files.
What Databricks actually publishes about Photon
(quote these with their source; benchmark your own workload before budgeting)
"up to 2x faster than Databricks Runtime 8.0"
-> TPC-DS 1TB power test, Photon public preview announcement
"2-4x average speedups"
-> reported by early preview customers on SQL workloads, same announcement
"up to 5x better price/performance ... compared to other cloud data warehouses"
-> Databricks docs. Note the comparison basis: price/performance against
cloud data warehouses, NOT runtime against open-source Spark. Those two
numbers are not interchangeable and should not be quoted as if they were.
Photon is enabled per workload type - not all operations benefit equally.
Photon supports no UDFs at all, not just simple ones: "Photon doesn't support
UDFs (User Defined Functions), RDD APIs, or Dataset APIs."
Python UDFs are also not JVM-bound - they run in separate Python worker
processes, and the serialisation round trip between the JVM and those workers
is precisely why they are slow.The practical implication: if your pipelines are SQL-heavy (aggregations, joins, window functions over large datasets), Photon is where the managed platform earns its price. The size of the speedup is a property of your queries, though, so measure it on your own workload before you build a budget on it — the published figures above come from Databricks' Photon public preview announcement and the Photon documentation, on their hardware and their queries. If your pipelines are Python-heavy with custom UDFs, Photon won't help at all — consider pandas UDFs (Arrow-based) to keep data in columnar format.
Delta Lake vs Hive/Parquet — reliability you can't replicate manually
This is the feature that matters most for production pipelines and the hardest to replicate on open-source Spark alone (though Delta Lake is open-source and can be used without Databricks).
# Problem with plain Parquet: no ACID, partial writes corrupt tables
# If a job fails mid-write on Parquet:
# (note the writer lives on the DataFrame, not on the SparkSession —
# SparkSession has .read but no .write)
df.write \
.mode("overwrite") \
.parquet("s3://data-lake/orders/") # If this fails at 60%:
# -> half-written files in S3
# -> next read sees partial data
# -> no way to know what's valid without manual inspection
# Delta Lake: atomic commits, always consistent
df.write \
.format("delta") \
.mode("overwrite") \
.save("s3://data-lake/orders_delta/")
# If this fails: transaction log is not committed
# Next read sees previous complete state - always consistentDelta Lake features that matter in production
# 1. Time travel - query data as it was at any point
spark.read \
.format("delta") \
.option("versionAsOf", 42) \
.load("s3://data-lake/orders_delta/")
spark.read \
.format("delta") \
.option("timestampAsOf", "2026-01-15") \
.load("s3://data-lake/orders_delta/")
# 2. MERGE (upsert) - critical for CDC pipelines
# Python uses .alias(); "as" is a reserved word, so deltaTable.as("target")
# is a SyntaxError. The .as() form you see around the web is Scala.
deltaTable.alias("target").merge(
updates.alias("source"),
"target.order_id = source.order_id"
) \
.whenMatchedUpdateAll() \
.whenNotMatchedInsertAll() \
.execute()
# 3. Schema enforcement - rejects bad data at write time
# 4. Schema evolution - ALTER TABLE ADD COLUMN without rewrite
# 5. Change Data Feed - get only changed rows since last read.
# CDF is OFF by default; enable it on the table first, or the read below
# fails: ALTER TABLE orders_delta
# SET TBLPROPERTIES (delta.enableChangeDataFeed = true);
spark.read \
.format("delta") \
.option("readChangeFeed", "true") \
.option("startingVersion", last_processed_version) \
.load("s3://data-lake/orders_delta/")SQL dialect differences — Databricks SQL vs standard Spark SQL
Databricks SQL is a superset of Spark SQL with additional syntax borrowed from PostgreSQL, Hive, and its own extensions. If you write SQL on Databricks and need to port it to EMR Spark or another platform, watch for these differences:
-- Works on Databricks SQL, may fail on standard Spark SQL:
-- 1. QUALIFY clause (window filter — Databricks, Snowflake)
SELECT order_id, customer_id, total,
RANK() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnk
FROM orders
QUALIFY rnk = 1; -- <- QUALIFY was not available before Spark 4.2.0;
-- it landed in 4.2.0. On 3.5 / 4.0 / 4.1 this is a
-- ParseException - use the subquery rewrite below.
-- Standard Spark SQL equivalent:
SELECT * FROM (
SELECT *, RANK() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnk
FROM orders
) WHERE rnk = 1;
-- 2. PIVOT syntax is slightly different
-- Databricks: SELECT * FROM orders PIVOT (SUM(total) FOR status IN ('pending', 'done'))
-- Standard Spark SQL: identical in recent versions
-- 3. COPY INTO (Databricks-specific SQL command)
-- Not the same thing as Auto Loader: Auto Loader is the "cloudFiles"
-- Structured Streaming source. Databricks' own guidance is COPY INTO for
-- thousands of files, Auto Loader for millions or more.
COPY INTO delta.`s3://bucket/orders_delta`
FROM 's3://bucket/raw/'
FILEFORMAT = JSON;
-- No equivalent in open-source Spark
-- 4. OPTIMIZE and ZORDER (Databricks Delta)
OPTIMIZE orders_delta ZORDER BY (customer_id, created_at);
-- Both are in open-source Delta Lake: OPTIMIZE compaction since 1.2.0,
-- Z-Ordering since 2.0.0. What is Databricks-only is Photon and the managed
-- automation (Predictive Optimization) that runs maintenance for you.Cluster cost comparison — how to work it out for your own numbers
Cloud list prices move, and every figure below depends on a region, an instance type and a billing SKU. Treat this as a worked method, not a quote: put your own numbers through it and check both sides against the vendors' own calculators.
Scenario: daily batch pipeline, 4 hours runtime, 8 workers on i3.2xlarge
Count the driver. Both platforms have one and neither side of the usual
comparison includes it. Databricks: "A compute resource consists of one driver
node and zero or more worker nodes." EMR has a distinct primary node type.
Leaving it out makes every per-node figure 12.5% low on an 8-worker cluster.
Open-source Spark on EMR (AWS ap-south-1, on-demand, 1 primary + 8 core):
EC2 on-demand: 9 x $0.708/hr x 4 hrs = $25.488/day
EMR uplift: 9 x $0.10/hr x 4 hrs = $3.60/day (assumption, check yours)
Total: ~$29.09/day = ~$872.64/month
$0.708/hr is the ap-south-1 (Mumbai) i3.2xlarge on-demand rate. The commonly
copied $0.624 is the us-east-1 rate - pricing is per region, so confirm it in
the AWS Pricing Calculator for the region you are actually running in.
Databricks on AWS - how the bill is actually formed:
cost = nodes x hours x (DBU/hour for the instance type)
x ($/DBU for that SKU, tier and region)
There is no flat "$X per node-hour". "A DBU is a unit of processing
capability, billed on a per-second usage. The DBU consumption depends on
the size and type of instance." Photon changes the DBU rate again, and it
is on by default for classic all-purpose and jobs compute.
A scheduled daily batch bills on Jobs Compute, not All-Purpose - the rates
differ. Quote your rate off the Databricks pricing calculator with the SKU,
tier and region named inline, then multiply.
Beyond VM + DBU, the Databricks side also bills managed disk, blob storage
and a public IP. Both sides here bill the underlying instances - that part
is not the asymmetry.
Comparing the two honestly:
Price both sides on the same basis. An on-demand EMR figure set against a
spot Databricks figure is not a finding, it is a units error. If you use a
$0.30/hr spot rate, then EMR on spot is 8 x $0.30 x 4 = $9.60 of EC2 plus
$3.20 of uplift = $12.80/day for the workers alone, before the primary node.
Any lower "EMR on spot" number you have seen quoted is using a cheaper spot
price on one side than the other - check which.
Where the platforms genuinely differ:
-> Databricks manages spot interruptions for you; on EMR you handle them.
-> Autoscaling exists on both. EMR Managed Scaling is first-party and has
shipped since EMR 5.30.0: "Managed scaling lets you automatically increase
or decrease the number of instances or units in your cluster based on
workload." The real gap is what happens around the scaling, not the
scaling itself.
-> At sustained large scale, negotiated Databricks rates change the maths
(enterprise contract required). Get the number in writing before you
model it.Once both sides are priced on the same basis, the compute bills usually land closer together than the marketing on either side suggests. What Databricks actually sells is engineering time — not having to manage EMR, spot interruption handling, dependency management, and cluster tuning. For teams with dedicated infrastructure engineers, EMR can be cheaper. For teams where data engineers own the entire stack, Databricks often saves enough time to be worth what it costs. Which of those you are is the decision; the compute line item rarely is.
When to use open-source Spark (EMR / self-managed)
- You have a dedicated infrastructure/DevOps team comfortable with cluster management
- Your workloads are Python-heavy (custom ML models, complex UDFs) — Photon won't help
- You are already deeply invested in AWS Glue, AWS Step Functions, and Glue Data Catalog
- Cost is the primary constraint and your team has bandwidth to optimise EMR clusters
- You need full control over the Spark version and cannot tolerate Databricks runtime locking
- Your data volumes are small enough that the managed layer is mostly idle — my own rule of thumb, not a published threshold, so sanity-check it against your own pipeline
When Databricks is the clear winner
- SQL-heavy pipelines doing aggregations and joins over TB-scale data — Photon delivers real ROI
- You need ACID transactions on your data lake (Delta Lake with full Databricks support)
- Real-time streaming pipelines using Lakeflow pipelines (the product formerly known as Delta Live Tables) — far simpler than managing Structured Streaming manually
- Teams where data scientists and data engineers share the same notebooks and need collaboration
- MLOps pipelines that benefit from a hosted MLflow you don't have to run — MLflow itself is Apache-2.0 and includes the Model Registry, so the managed service is what you are buying
- You want fine-grained column-level and row-level access control governed for you — Unity Catalog was open-sourced under Apache 2.0 in June 2024, so again the managed service is the differentiator, not the technology
- Multi-cloud strategy — the same core platform runs on AWS, Azure and GCP, though not identically: Databricks maintains three separate documentation sets because feature availability, networking and regional coverage differ (GCP serverless compute, for instance, does not support Private Service Connect to Google-managed services)
Practical SQL performance tips that apply to both
-- 1. Partition pruning — always filter on partition columns first
-- Bad: full table scan
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- Good: partition pruning (if table is partitioned by date)
SELECT * FROM orders
WHERE created_date BETWEEN '2026-01-01' AND '2026-12-31';
-- 2. Avoid SELECT * in production pipelines
-- Each extra column adds shuffle data volume. Be explicit.
-- 3. Broadcast joins for small tables (under 10MB)
-- Databricks auto-broadcasts if adaptive query execution is on
-- Force it explicitly:
SELECT /*+ BROADCAST(dim_customers) */ *
FROM orders o JOIN dim_customers c ON o.customer_id = c.id;
-- 4. On Databricks: OPTIMIZE after large merges
-- OPTIMIZE is a command you invoke (or schedule) - it does not run by itself
OPTIMIZE orders_delta; -- compacts small files (the "small files problem")
OPTIMIZE orders_delta ZORDER BY (customer_id); -- co-locate related data
-- 5. Cache intermediate results that are reused
-- Databricks: persist to Delta, not .cache() (which uses RAM)
df.write.format("delta").mode("overwrite").save("/tmp/cached_result")Frequently Asked Questions
Can I use Delta Lake without Databricks?
Yes. Delta Lake is open-source (Apache License 2.0) and works with any Spark cluster, including EMR, Google Dataproc, and self-managed Spark. You get ACID transactions, time travel, schema enforcement, MERGE, and — contrary to a claim you will see repeated everywhere — OPTIMIZE and ZORDER too: OPTIMIZE compaction has been in open-source Delta since 1.2.0 and Z-Ordering since 2.0.0 ("This feature is available in Delta Lake 2.0.0 and above"). What you actually lose off Databricks is Photon and the managed automation that runs table maintenance for you. Match the Maven coordinate to your Spark version:io.delta:delta-spark_2.12:3.2.0 is for Spark 3.5, while Delta 4.x publishes Scala 2.13 artifacts under Spark-versioned names such as io.delta:delta-spark_4.1_2.13:4.3.0 — copying the 3.x coordinate onto a Spark 4.x cluster will not work. Many teams use Delta Lake on EMR as a middle ground — open table format with no Databricks dependency.
What is the "small files problem" and how does Databricks solve it?
Spark writes one output file per partition task. A pipeline running every 5 minutes over 12 hours creates 144 small files per table partition. Reading thousands of small files is slow because S3/ADLS metadata operations dominate over actual data reads. The OPTIMIZE command compacts small files into larger ones — the autotuned target is 256MB for tables under 2.56TB, rising to 1GB only above 10TB. Note that OPTIMIZE is a command you invoke or schedule; the thing that runs on its own is Predictive Optimization on Unity Catalog managed tables. On open-source Spark you run compaction yourself, or enable auto compaction, which arrived in open-source Delta Lake 3.1.0 (not 2.0). This is one area where Databricks' managed operations genuinely save significant pipeline maintenance time.
Is Databricks SQL the same as the Databricks notebook SQL?
Databricks SQL is a separate compute plane (SQL Warehouses) optimised for BI queries and dashboards, distinct from the all-purpose compute clusters used in notebooks. SQL Warehouses use Photon and are priced differently. Serverless SQL is not billed per query: it bills DBUs per hour by warehouse size (2X-Small 4, Small 12, Large 40 DBU/hour), metered per second. Auto-suspend is what keeps the idle cost down — the billing unit is still time. Notebook SQL runs on cluster compute. For BI tools like Tableau, Power BI, and Metabase connecting to Databricks, always use SQL Warehouses — they auto-suspend and handle concurrent BI users better than all-purpose clusters.
How do Databricks SQL dialects differ from Spark SQL when migrating queries from Snowflake?
Snowflake to Databricks SQL migration is common and has a few consistent friction points: Snowflake's QUALIFY clause works in Databricks SQL but not standard Spark SQL; Snowflake's FLATTEN for JSON arrays maps to Databricks'EXPLODE; Snowflake's VARIANT type maps to Databricks' own native VARIANT type (DBR 15.3+), populated with parse_json() —from_json() / get_json_object() are the pre-15.3 fallback, not the modern answer; and Snowflake's DATEADD maps to Databricks'dateadd(unit, value, expr) (DBR 10.4 LTS+), which is a 1:1 match. Do not map it to date_add — that one only adds days, so it will silently break every DATEADD(month, ...) or DATEADD(hour, ...) in your migration.
What is the recommended Delta Lake table format for large fact tables in India-based data lakes?
For large fact tables (orders, transactions, events) in Indian data lakes on AWS ap-south-1 or Azure Central India: partition by date (YYYY-MM-DD) at the top level, ZORDER by your most common filter columns (customer_id, product_id). On file sizes, the documented autotuned target is 256MB up to 2.56TB and 1GB only above 10TB; the 512MB–2GB range I tend to use is my own practice for large fact tables, not a documented recommendation, so start from the documented target unless you have a reason not to. For tables updated via CDC/MERGE, run OPTIMIZE weekly during off-peak hours (Sunday 2–4 AM IST works well — use a Databricks Job with a cron trigger). Enable Delta Lake's auto-optimize for streaming tables where small file accumulation is fastest.