03 · SparkSession & Your First PySpark Script¶
Installing PySpark¶
This installs the Python bindings and bundles the Spark JVM libraries. You also need a JDK on your machine (Java 11 or 17 are the safest choices for recent Spark versions) — check with:
If that fails, install a JDK (e.g. via brew install openjdk@17 on macOS, or
your OS's package manager) before continuing.
Not executed against a live cluster in this environment
The code in this module was written and carefully traced through by hand
for correctness, but this authoring environment does not have a JVM/Spark
runtime available to actually execute it. Everything shown uses
documented, standard PySpark APIs and should run as-is once you have
pyspark and a JDK installed locally.
SparkSession: the entry point¶
Everything in modern PySpark starts from a SparkSession — it's your handle
to the driver, and every DataFrame, every read, every SQL query goes through
it.
from pyspark.sql import SparkSession
spark = (
SparkSession.builder
.appName("FirstPySparkScript")
.master("local[*]") # run locally, using all available CPU cores
.getOrCreate()
)
print(spark.version)
Key parts of that builder call:
.appName(...)— a human-readable name that shows up in logs and the Spark UI. Purely cosmetic, but useful once you're debugging multiple jobs..master("local[*]")— tells Spark where to run.local[*]means "run in this one process, using all available CPU cores as if they were executors." Uselocal[4]to cap it at 4 cores, or a cluster URL likespark://host:7077/yarn/k8s://...when you have a real cluster. Every example in this level useslocal[*]..getOrCreate()— reuses an existing SparkSession in the same process if one already exists (useful in notebooks), otherwise creates a new one.
SparkSession is actually a unified entry point that wraps what used to be
several separate objects in older Spark versions (SparkContext,
SQLContext, HiveContext) — you'll see spark.sparkContext referenced
occasionally for lower-level RDD operations, covered in Module 4.
Your first script, end to end¶
Let's create a tiny in-memory DataFrame (no file needed yet — that's Module 5) and run a couple of operations on it, to see the lazy/eager split from Module 2 in practice.
from pyspark.sql import SparkSession
spark = (
SparkSession.builder
.appName("FirstPySparkScript")
.master("local[*]")
.getOrCreate()
)
# Create a small DataFrame from plain Python data — useful for
# experimenting without a file.
data = [
("Alice", "Sales", 72000),
("Bob", "Engineering", 95000),
("Carla", "Engineering", 105000),
("Deepak", "Sales", 68000),
]
columns = ["name", "department", "salary"]
df = spark.createDataFrame(data, columns) # transformation — nothing runs yet
df.printSchema() # this is an action-like call: it inspects the schema,
# which for createDataFrame is already known, so it's cheap
# root
# |-- name: string (nullable = true)
# |-- department: string (nullable = true)
# |-- salary: long (nullable = true)
df.show() # ACTION — triggers execution and prints the result
# +------+-----------+------+
# | name| department|salary|
# +------+-----------+------+
# | Alice| Sales| 72000|
# | Bob|Engineering| 95000|
# | Carla|Engineering|105000|
# |Deepak| Sales| 68000|
# +------+-----------+------+
high_earners = df.filter(df.salary > 70000) # transformation
print(high_earners.count()) # ACTION — prints 3
spark.stop() # always stop the session when your script is done,
# to release cluster resources cleanly
A few details worth calling out:
createDataFrameinfers a schema from the Python data you pass —"long"for the salary column because the values are Pythonints. Module 7 covers specifying schemas explicitly, which you'll want for real files..show()defaults to printing 20 rows, truncating long strings. Passdf.show(n=50, truncate=False)to see more, untruncated.spark.stop()is good hygiene, especially in local mode where forgetting it can leave lingering processes; it's essential in cluster mode where it releases real, shared resources back to the cluster manager.
Running it as a script¶
Save the code above as first_script.py and run it with:
or, using Spark's own launcher (recommended for anything beyond local
experimentation, since spark-submit handles packaging and cluster
deployment options python alone doesn't):
For everything in this level, plain python first_script.py is enough since
we're in local mode.
Worked example: configuring the session¶
You can pass Spark configuration properties through the builder too — a common one for local development is capping how much memory the single local-mode JVM process is allowed to use:
spark = (
SparkSession.builder
.appName("ConfiguredExample")
.master("local[*]")
.config("spark.driver.memory", "4g")
.config("spark.sql.shuffle.partitions", "8") # default is 200; too many
# for small local datasets
.getOrCreate()
)
spark.sql.shuffle.partitions controls how many partitions Spark uses after
a shuffle (e.g. after a groupBy). The default of 200 is tuned for cluster
workloads; on a laptop with a small dataset, 200 tiny partitions creates more
scheduling overhead than benefit, so it's common to lower it for local
development and testing. You'll tune this for real in Level 3.
How It Actually Works¶
Creating a SparkSession doesn't just instantiate a Python object — it
launches (or connects to) a JVM process via py4j, and every DataFrame
method you call from Python is serialized across that Python-to-JVM bridge
as a request to build another node in the logical plan tree, which lives
entirely inside the JVM. This is why PySpark DataFrame code has no Python
performance penalty for standard operations: the Python object you're
holding is a thin wrapper around a JVM Dataset reference, and .show() or
.count() are the first calls that actually cross back into the JVM to
trigger execution — everything before that (chained .select/.filter
calls) is just building up plan nodes in memory, with zero data movement or
computation happening yet. spark.stop() matters because it tears down that
JVM process and releases any executors held by the cluster manager; skipping
it in a long-running script leaks cluster resources.
Exercise¶
Write a script (don't need to run it if you don't have Spark installed yet, but reason through what it should print) that:
- Creates a SparkSession named
"ExerciseSession"running in local mode with 2 cores (local[2]). - Builds a DataFrame from this data: three rows of
(product, price)—("Laptop", 1200),("Mouse", 25),("Keyboard", 75). - Prints the schema, then filters to rows where
price > 50, then prints the count of the filtered result. - Stops the session.
Expected reasoning: the schema should show product: string and
price: long; the filter keeps "Laptop" (1200) and "Keyboard" (75), so
the final count should print 2.