PySpark is the Python API for Apache Spark, providing distributed analytics through both SQL and DataFrame interfaces. BenchBox supports PySpark in two execution modes:
- SQL mode:
benchbox run --platform pyspark --mode sql ... - DataFrame mode:
benchbox run --platform pyspark-df ...
| Attribute | Value |
|---|---|
| CLI Names | pyspark (SQL) / pyspark-df (DataFrame) |
| Families | SQL (PlatformAdapter) / Expression (DataFrame) |
| Execution | Lazy, distributed |
| Best For | Large datasets, distributed processing, Spark infrastructure |
| Min Version | 3.5.0 (Java 17/21 required) |
| Mode | CLI | Default | Description |
|---|---|---|---|
| SQL | benchbox run --platform pyspark --mode sql ... |
❌ | Spark SQL adapter backed by the shared SparkSessionManager. Ideal when benchmarking SQL dialect compatibility or comparing against other SQL engines. |
| DataFrame | benchbox run --platform pyspark-df ... or --platform pyspark --mode dataframe |
✅ | Expression-family adapter that uses PySpark's DataFrame API for query construction and execution. |
BenchBox defaults to DataFrame mode for historical compatibility. Use --mode sql explicitly for PySpark SQL benchmarks or append -df to force DataFrame mode from the CLI.
- Distributed execution - Scale from local mode to cluster
- Lazy evaluation - Catalyst optimizer for query planning
- Adaptive Query Execution - Runtime optimization (AQE)
- Unified analytics - Same API for batch and streaming
- Enterprise ecosystem - Integration with Hadoop, Hive, Delta Lake
Install PySpark DataFrame support with uv, with pip, or by installing PySpark directly. The last command verifies the installation:
uv add benchbox --extra pyspark
pip install "benchbox[pyspark]"
pip install pyspark pyarrow
python -c "from pyspark.sql import SparkSession; print('PySpark available')"Java runtime: Wrap PySpark benchmarks with
scripts/with_supported_java.shso BenchBox automatically exports a Java 17/21JAVA_HOME. The helper probes/usr/libexec/java_home(macOS) or any already-setJAVA_HOME, falling back tojavaonPATHif it already points at a supported runtime.
The first command runs TPC-H on PySpark DataFrame in local mode. The second sets a custom memory allocation:
scripts/with_supported_java.sh benchbox run --platform pyspark-df --benchmark tpch --scale 0.01
scripts/with_supported_java.sh benchbox run --platform pyspark-df --benchmark tpch --scale 1 \
--platform-option driver_memory=8gThis command runs the TPC-H power phase via Spark SQL:
scripts/with_supported_java.sh benchbox run --platform pyspark --mode sql --benchmark tpch --scale 0.01 \
--phases power --non-interactiveTo set a warehouse directory or create Delta tables, use the Python API, as shown under SQL Mode Configuration Highlights below.
| Option | Default | Description |
|---|---|---|
master |
local[*] |
Spark master URL |
app_name |
BenchBox-TPC-H |
Application name |
driver_memory |
4g |
Memory for driver process |
executor_memory |
None | Memory for executors (cluster mode) |
shuffle_partitions |
CPU count | Partitions for shuffle operations |
enable_aqe |
true |
Enable Adaptive Query Execution |
The CLI accepts driver_memory and shuffle_partitions through --platform-option. Set the other options as
PySparkDataFrameAdapter keyword arguments in the Python API.
Specifies the Spark cluster to connect to. In order, the examples below select local mode with all cores, local mode with 4 cores, a standalone cluster, and a YARN cluster:
PySparkDataFrameAdapter(master="local[*]")
PySparkDataFrameAdapter(master="local[4]")
PySparkDataFrameAdapter(master="spark://hostname:7077")
PySparkDataFrameAdapter(master="yarn")Memory allocated to the driver process. Increase it for large scale factors:
benchbox run --platform pyspark-df --benchmark tpch --scale 10 \
--platform-option driver_memory=16gNumber of partitions for shuffle operations (joins, aggregations). Match it to the CPU core count for local mode, and use a higher value such as 200 for cluster mode:
benchbox run --platform pyspark-df --benchmark tpch --scale 1 \
--platform-option shuffle_partitions=8
--platform-option shuffle_partitions=200Adaptive Query Execution optimizes queries at runtime. Disable it for deterministic benchmarking:
PySparkDataFrameAdapter(enable_aqe=False)PySparkSQLAdapter accepts the Spark adapter keyword arguments (master, app_name, driver_memory,
executor_memory, shuffle_partitions, and adaptive_enabled for AQE) and adds SQL-specific settings:
| Option | Default | Description |
|---|---|---|
warehouse_dir |
Spark default | Catalog warehouse directory used for managed tables. |
database |
default |
Spark database/schema name. Shared across SQL/DataFrame adapters through the session manager. |
table_format |
parquet |
File format for managed tables (parquet or delta). |
PARTITIONED BY clauses come from the tuning configuration, not from adapter options.
Example Python API overrides:
from benchbox.platforms.pyspark import PySparkSQLAdapter
adapter = PySparkSQLAdapter(
database="benchbox_pyspark_tpcds",
warehouse_dir="./spark_warehouse",
table_format="delta",
)PySpark 4.x requires Java 17 or Java 21. Java 23+ is currently incompatible due to upstream PySpark limitations. BenchBox enforces Java detection in the shared SparkSessionManager:
- Java 17/21: ✅ supported
- Java 23+: ❌ blocked with actionable error
- Unknown Java version: ❌ prompts to install a supported JDK
On macOS, BenchBox automatically attempts to locate a compatible JDK via /usr/libexec/java_home when a newer Java (e.g., 23+) is active, so you can keep multiple versions installed without manual switching.
Set JAVA_HOME to the desired JDK before running BenchBox. Verify with the command below. Expected output starts with a line such as openjdk version "21.0.2" 2024-01-16:
java -versionPySpark can handle very large datasets due to distributed execution:
| Scale Factor | Data Size | Memory Required | Notes |
|---|---|---|---|
| 0.01 | ~10 MB | 2 GB driver | Quick testing |
| 0.1 | ~100 MB | 4 GB driver | Development |
| 1.0 | ~1 GB | 8 GB driver | Full TPC-H |
| 10.0 | ~10 GB | 16 GB driver | Large workload |
| 100.0 | ~100 GB | Cluster recommended | Production scale |
- Scalability - Scales from laptop to 1000+ node clusters
- Fault tolerance - Automatic recovery from failures
- Rich ecosystem - Delta Lake, Hive, JDBC connectors
- Query optimization - Catalyst optimizer + AQE
- Memory management - Tungsten engine
- JVM overhead - Higher startup time than native Python
- Local mode limits - Single-node performance lower than Polars
- Complexity - More configuration than simpler tools
- Resource requirements - Higher baseline memory usage
-
Optimize shuffle partitions for your workload (for local mode, match CPU cores):
--platform-option shuffle_partitions=8
-
Keep AQE enabled for adaptive optimization. It is on by default; the Python API can turn it off with
enable_aqe=False. -
Allocate sufficient driver memory:
--platform-option driver_memory=8g
-
Use Parquet format for best performance (default for BenchBox)
The first command is a TPC-H smoke run in SQL mode. The second generates and loads data without executing queries:
benchbox run --platform pyspark --mode sql --benchmark tpch --scale 0.01 \
--phases power --non-interactive
benchbox run --platform pyspark --mode sql --benchmark tpch --scale 0.1 \
--phases generate,load --output ./tpch_sf01 --non-interactivefrom benchbox.platforms.pyspark import PySparkSQLAdapter
adapter = PySparkSQLAdapter(
master="local[4]",
database="benchbox_tpch_sql",
driver_memory="8g",
table_format="delta",
)
conn = adapter.create_connection()
adapter.configure_for_benchmark(conn, "tpch")
result = adapter.execute_query(conn, "SELECT COUNT(*) FROM lineitem", "Qcount", benchmark_type="tpch")
print(result["rows_returned"])
adapter.close()The execute_query call runs the SQL directly.
Best for development and small-scale testing. master="local[*]" uses all local cores:
from benchbox.platforms.dataframe import PySparkDataFrameAdapter
adapter = PySparkDataFrameAdapter(
master="local[*]",
driver_memory="4g",
)For dedicated Spark clusters:
adapter = PySparkDataFrameAdapter(
master="spark://master:7077",
driver_memory="8g",
executor_memory="16g",
shuffle_partitions=200,
)For Hadoop-integrated deployments:
adapter = PySparkDataFrameAdapter(
master="yarn",
driver_memory="8g",
executor_memory="16g",
)| Symptom | Resolution |
|---|---|
Detected Java 24... |
Install Temurin/OpenJDK 17 or 21 and export JAVA_HOME before running BenchBox. |
SparkSession already created with a different configuration |
Ensure SQL/DataFrame adapters share identical Spark options (master, memory, shuffle partitions). |
PySpark not installed |
Run uv add benchbox --extra pyspark or pip install pyspark pyarrow. |
| Cloud storage read errors | Pass the required Spark settings, such as spark.hadoop.fs.s3a.impl, as extra PySparkDataFrameAdapter keyword arguments in the Python API, and supply credentials through environment variables. |
PySpark queries use expression-based operations:
from pyspark.sql import functions as F
def q6_pyspark_impl(ctx: DataFrameContext) -> Any:
lineitem = ctx.get_table("lineitem")
start_date = ctx.cast_date(ctx.lit("1994-01-01"))
end_date = ctx.cast_date(ctx.lit("1995-01-01"))
result = (
lineitem
.filter(ctx.col("l_shipdate") >= start_date)
.filter(ctx.col("l_shipdate") < end_date)
.filter(ctx.col("l_discount") >= ctx.lit(0.05))
.filter(ctx.col("l_discount") <= ctx.lit(0.07))
.filter(ctx.col("l_quantity") < ctx.lit(24))
.select(
(ctx.col("l_extendedprice") * ctx.col("l_discount")).alias("revenue")
)
.agg(F.sum("revenue").alias("revenue"))
)
return result| Aspect | PySpark (pyspark-df) |
Polars (polars-df) |
DataFusion (datafusion-df) |
|---|---|---|---|
| Execution | Distributed | Single-node | Single-node |
| Startup | ~3-5 seconds | Instant | Instant |
| Scale | TB+ data | GB data | GB data |
| Memory | Higher overhead | Efficient | Efficient |
| Best for | Large/distributed | Medium/local | Medium/Arrow |
PySpark supports SQL-style window functions:
from benchbox.platforms.dataframe import PySparkDataFrameAdapter
adapter = PySparkDataFrameAdapter(master="local[4]")
row_num = adapter.window_row_number(
order_by=[("sale_date", True)],
partition_by=["category"]
)
running_total = adapter.window_sum(
column="amount",
partition_by=["category"],
order_by=[("sale_date", True)]
)
rank = adapter.window_rank(
order_by=[("revenue", False)],
partition_by=["region"]
)In each order_by entry, the boolean selects ascending (True) or descending (False) order, so the rank example orders by revenue descending.
java.lang.OutOfMemoryError: Java heap space
Solutions:
- Increase driver memory:
--platform-option driver_memory=8g - Reduce shuffle partitions:
--platform-option shuffle_partitions=4 - Reduce scale factor:
--scale 1
PySpark has JVM startup overhead (~3-5 seconds). For rapid iteration, use Polars for quick tests and PySpark for production scale:
benchbox run --platform polars-df --benchmark tpch --scale 0.01
benchbox run --platform pyspark-df --benchmark tpch --scale 10If you see port binding errors, configure a different Spark UI port:
adapter = PySparkDataFrameAdapter(
master="local[*]",
**{"spark.ui.port": "4050"}
)Always close the adapter when done. Run your queries inside the try block; close() stops the SparkSession:
adapter = PySparkDataFrameAdapter(master="local[4]")
try:
pass
finally:
adapter.close()Or use context manager pattern in your code.
from benchbox.platforms.dataframe import PySparkDataFrameAdapter
adapter = PySparkDataFrameAdapter(
working_dir="./benchmark_data",
master="local[*]",
app_name="MyBenchmark",
driver_memory="8g",
shuffle_partitions=8,
enable_aqe=True,
verbose=True,
)
ctx = adapter.create_context()
adapter.load_tables(ctx, data_dir="./tpch_data")
df = adapter.sql("SELECT * FROM lineitem WHERE l_quantity > 10")
result = adapter.collect(df)
from benchbox.core.tpch.dataframe_queries import TPCH_DATAFRAME_QUERIES
query = TPCH_DATAFRAME_QUERIES.get_query("Q1")
result = adapter.execute_query(ctx, query)
print(result)
pandas_df = adapter.to_pandas(df)
plan = adapter.get_query_plan(df)
print(plan["physical"])
adapter.close()- Spark SQL Platform - Spark SQL mode
- Polars Platform - Alternative expression-family adapter
- DataFusion DataFrame Platform - Arrow-native adapter
- Getting Started - BenchBox quick start