Scala Spark versus PySpark: performance and ecosystem trade-offs?
Summary
- For DataFrame and Spark SQL code, Scala and PySpark share one engine. Spark's SQL guide states that the same execution engine is used independent of the API or language, so language choice rarely decides query speed.
- The cost difference appears when Python code has to run on executors. Python UDFs and PySpark RDD operations start Python worker processes and serialize data between the JVM and Python; Arrow-optimized and pandas UDFs batch that transfer.
- Scala offers the typed Dataset API, which Python doesn't have, plus compile-time checks and direct use of JVM libraries. The catch is matching Spark's Scala binary version, which is 2.13 since Spark 4.0.
- PySpark offers pip installation, the pandas API on Spark and the Python data and ML libraries. Since Spark 4.2.0, Arrow-optimized Python UDFs are on by default.
- A sound default: write transformations with built-in DataFrame and SQL functions in the language your team ships, and choose Scala when you need typed Datasets or JVM-only libraries.
Scala Spark versus PySpark: performance and ecosystem trade-offs?
Choose by team skills and ecosystem, not by benchmark, as long as your logic stays in DataFrames and Spark SQL. Spark's SQL guide says that when computing a result, the same execution engine is used regardless of the API or language, so a groupBy written in PySpark and the same one in Scala are planned by the same optimizer and run on the same engine. The two diverge when Python code itself has to run on the executors, through Python UDFs or the RDD API. That's the part to design around.
Where the performance difference comes from
On the driver, PySpark calls into the JVM through Py4J, and the query is planned and optimized in the JVM either way. On the executors, the PySpark debugging guide is precise: Python workers are launched only when Python native functions or data have to be handled, for example pandas UDFs or the PySpark RDD API. Pure DataFrame code never starts them.
When workers do run, serialization is the cost. Scalar Python UDFs use cloudpickle, and the PySpark docs say they hit performance bottlenecks with large inputs and outputs. Arrow-optimized Python UDFs exchange data between the JVM and Python in Arrow's columnar format instead. Spark 4.2.0 turned them on by default through spark.sql.execution.pythonUDF.arrow.enabled, and Spark 4.1.0 added Arrow-native UDF and UDTF decorators that skip pandas conversion. Scala UDFs and typed Dataset lambdas don't need a Python worker at all.
The practical order in PySpark: built-in functions first, then vectorized UDFs, then plain Python UDFs.
import pandas as pd
from pyspark.sql import functions as F
builtin = df.select(F.upper("name")) # built-in: no Python worker
@F.udf(returnType="string", useArrow=True) # Arrow-optimized Python UDF
def upper_udf(s: str) -> str:
return s.upper() if s is not None else None
@F.pandas_udf("string") # pandas UDF, one batch at a time
def upper_pandas(s: pd.Series) -> pd.Series:
return s.str.upper()
df.select(upper_udf("name"), upper_pandas("name")).show()
Ecosystem trade-offs
| Aspect | Scala | PySpark |
|---|---|---|
| DataFrame and SQL execution | Same optimizer and engine | Same optimizer and engine |
| Custom row logic | Scala UDFs and typed lambdas, no Python worker | Python workers; Arrow-optimized (default since 4.2.0) and pandas UDFs batch the transfer |
| Typed API | Dataset API with compile-time types | No Dataset API; DataFrame rows accessed by field name |
| Install and packaging | Maven coordinates; your app must use Spark's Scala version (2.13) | pip install pyspark; Spark 4.2 runs on Python 3.10+ |
| Libraries | Any JVM library | pandas API on Spark, pandas, NumPy, PyArrow |
| Spark Connect client | Supported | Supported |
| RDD API | Runs on the JVM | Runs Python functions in Python workers |
The typed API is the main reason to pick Scala for code, not speed. A typed Dataset catches a misspelled field at compile time:
import org.apache.spark.sql.functions.{col, upper}
import spark.implicits._
case class Person(name: String, age: Long)
val people = spark.read.parquet("/data/people").as[Person]
val adults = people.filter(_.age >= 18)
val names = adults.select(upper(col("name")))
The Spark guide notes that, thanks to Python's dynamic nature, many of the Dataset API's benefits are already available in PySpark, such as reading a field with row.columnName. What you give up is the compile-time check.
How Databricks approaches the choice
On Databricks, language interacts with compute. Photon, the Databricks-native vectorized engine, runs SQL and DataFrame API calls from Python, R, Scala and Java with no code changes. Photon doesn't support UDFs, RDD APIs or Dataset APIs, and an unsupported operation falls back to the Spark runtime, so built-in functions pay off twice. Serverless compute supports only Spark Connect APIs, with no RDD API, and doesn't run Scala in notebooks, though JAR tasks in jobs are supported. Databricks Connect, built on open-source Spark Connect, lets you develop in an IDE with Python, Scala or R. See Photon and serverless limitations.
FAQs
Is PySpark slower than Scala Spark?
Not for DataFrame and SQL code, which runs on the same engine. Python UDFs and the RDD API add Python worker processes and serialization, so prefer built-in functions, then Arrow-optimized or pandas UDFs.
Do new Spark features reach both languages at once?
Not always. Real-time Mode in Structured Streaming shipped in Spark 4.1.0 with Scala stateless support, and Spark 4.2.0 added the trigger to PySpark. Check the release notes for the API you need.
Does Spark Connect change the trade-off?
It moves more of the work to the server. Spark Connect sends unresolved logical plans from the client and supports PySpark and Scala applications, so analysis and execution happen on the server for both.
Which should a new team pick?
The language the team already writes well. For DataFrame-heavy pipelines either works; pick Scala if you need typed Datasets or JVM-only libraries, and PySpark if you need Python data and ML libraries.
The information provided herein is for general informational purposes only and may not reflect the most current product capabilities or configurations.