Apache Spark - SparkContext vs SparkSession

Before Spark 2.0, developers needed to create multiple contexts depending on their use case. You'd initialize a SparkContext for core RDD operations, a SQLContext for DataFrame operations, and a...

Key Insights

  • SparkContext is the legacy entry point for Spark functionality, while SparkSession is the unified entry point introduced in Spark 2.0 that consolidates SparkContext, SQLContext, and HiveContext
  • SparkSession provides a simplified API for DataFrame and Dataset operations while maintaining backward compatibility by exposing the underlying SparkContext through the sparkContext property
  • For new applications, always use SparkSession as your primary entry point—it offers better functionality, simpler configuration, and is the recommended approach for all Spark programming

Understanding the Evolution from SparkContext to SparkSession

Before Spark 2.0, developers needed to create multiple contexts depending on their use case. You’d initialize a SparkContext for core RDD operations, a SQLContext for DataFrame operations, and a HiveContext for Hive integration. This fragmented approach created unnecessary complexity and confusion.

# Pre-Spark 2.0 approach (deprecated pattern)
from pyspark import SparkContext, SparkConf
from pyspark.sql import SQLContext, HiveContext

conf = SparkConf().setAppName("OldWay")
sc = SparkContext(conf=conf)
sqlContext = SQLContext(sc)
hiveContext = HiveContext(sc)

SparkSession unified these entry points into a single interface. It acts as a facade that internally manages all necessary contexts while providing a cleaner, more intuitive API.

# Modern Spark 2.0+ approach
from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("ModernWay") \
    .config("spark.sql.warehouse.dir", "/user/hive/warehouse") \
    .enableHiveSupport() \
    .getOrCreate()

# Access underlying SparkContext if needed
sc = spark.sparkContext

Core Differences in Functionality

SparkContext remains the foundation for low-level RDD operations, while SparkSession focuses on higher-level abstractions like DataFrames and Datasets. Understanding when to use each is crucial for effective Spark development.

from pyspark.sql import SparkSession

spark = SparkSession.builder.appName("CoreDifferences").getOrCreate()
sc = spark.sparkContext

# SparkContext operations - RDD-based
rdd = sc.parallelize([1, 2, 3, 4, 5])
squared_rdd = rdd.map(lambda x: x ** 2)
result = squared_rdd.collect()
print(f"RDD result: {result}")

# SparkSession operations - DataFrame-based
df = spark.createDataFrame([(1,), (2,), (3,), (4,), (5,)], ["value"])
squared_df = df.selectExpr("value", "value * value as squared")
squared_df.show()

# Reading data - SparkSession provides unified interface
csv_df = spark.read.csv("data.csv", header=True, inferSchema=True)
json_df = spark.read.json("data.json")
parquet_df = spark.read.parquet("data.parquet")

Configuration and Initialization Patterns

SparkSession offers a more flexible builder pattern for configuration compared to SparkContext’s reliance on SparkConf objects.

# SparkContext configuration (legacy)
from pyspark import SparkContext, SparkConf

conf = SparkConf()
conf.setAppName("LegacyApp")
conf.setMaster("local[*]")
conf.set("spark.executor.memory", "4g")
conf.set("spark.driver.memory", "2g")
sc = SparkContext(conf=conf)

# SparkSession configuration (modern)
spark = SparkSession.builder \
    .appName("ModernApp") \
    .master("local[*]") \
    .config("spark.executor.memory", "4g") \
    .config("spark.driver.memory", "2g") \
    .config("spark.sql.adaptive.enabled", "true") \
    .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
    .getOrCreate()

# Retrieve current configuration
print(spark.sparkContext.getConf().getAll())

Working with Both APIs in Practice

Real-world applications often require using both SparkSession and SparkContext. Here’s how to navigate between them effectively.

from pyspark.sql import SparkSession
from pyspark.sql.functions import col, udf
from pyspark.sql.types import IntegerType

spark = SparkSession.builder.appName("HybridApproach").getOrCreate()
sc = spark.sparkContext

# Scenario 1: Convert DataFrame to RDD for custom processing
df = spark.createDataFrame([
    ("Alice", 25, "Engineer"),
    ("Bob", 30, "Manager"),
    ("Charlie", 35, "Director")
], ["name", "age", "role"])

# Use RDD for complex transformations
rdd = df.rdd.map(lambda row: (row.name, row.age * 12, row.role))
result_df = spark.createDataFrame(rdd, ["name", "months", "role"])
result_df.show()

# Scenario 2: Broadcast variables (SparkContext feature)
lookup_dict = {"Engineer": 1, "Manager": 2, "Director": 3}
broadcast_var = sc.broadcast(lookup_dict)

def map_role(role):
    return broadcast_var.value.get(role, 0)

map_role_udf = udf(map_role, IntegerType())
df_with_code = df.withColumn("role_code", map_role_udf(col("role")))
df_with_code.show()

# Scenario 3: Accumulators (SparkContext feature)
error_accumulator = sc.accumulator(0)

def process_with_tracking(value):
    try:
        return value * 2
    except Exception:
        error_accumulator.add(1)
        return None

df.rdd.foreach(lambda row: process_with_tracking(row.age))
print(f"Errors encountered: {error_accumulator.value}")

Performance and Optimization Considerations

SparkSession provides access to Catalyst optimizer and Tungsten execution engine, offering significant performance advantages over raw RDD operations.

from pyspark.sql import SparkSession
from pyspark.sql.functions import avg, count
import time

spark = SparkSession.builder \
    .appName("PerformanceComparison") \
    .config("spark.sql.adaptive.enabled", "true") \
    .getOrCreate()

# Generate sample data
data = [(i, i % 100, i * 2) for i in range(1000000)]

# RDD approach
start_time = time.time()
rdd = spark.sparkContext.parallelize(data)
rdd_result = rdd.map(lambda x: (x[1], x[2])) \
    .groupByKey() \
    .mapValues(lambda x: sum(x) / len(list(x))) \
    .collect()
rdd_time = time.time() - start_time

# DataFrame approach
start_time = time.time()
df = spark.createDataFrame(data, ["id", "category", "value"])
df_result = df.groupBy("category").agg(avg("value")).collect()
df_time = time.time() - start_time

print(f"RDD time: {rdd_time:.2f}s")
print(f"DataFrame time: {df_time:.2f}s")

# View execution plan
df.groupBy("category").agg(avg("value")).explain(True)

Migration Strategy from SparkContext to SparkSession

When modernizing legacy Spark applications, follow this systematic approach to migrate from SparkContext-centric code to SparkSession.

# Legacy code pattern
from pyspark import SparkContext
from pyspark.sql import SQLContext

sc = SparkContext(appName="LegacyApp")
sqlContext = SQLContext(sc)

# Step 1: Create SparkSession while maintaining SparkContext
from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("MigratedApp") \
    .getOrCreate()
sc = spark.sparkContext  # Reuse existing context

# Step 2: Replace SQLContext operations
# Old: df = sqlContext.read.json("data.json")
# New:
df = spark.read.json("data.json")

# Step 3: Replace createDataFrame calls
# Old: sqlContext.createDataFrame(data, schema)
# New:
df = spark.createDataFrame(data, schema)

# Step 4: Update SQL operations
# Old: sqlContext.sql("SELECT * FROM table")
# New:
df.createOrReplaceTempView("table")
result = spark.sql("SELECT * FROM table")

# Step 5: Maintain RDD operations as-is
rdd_data = sc.textFile("input.txt")
processed = rdd_data.map(lambda x: x.upper())

Singleton Pattern and Resource Management

SparkSession implements a singleton pattern through getOrCreate(), preventing multiple active sessions and ensuring proper resource management.

from pyspark.sql import SparkSession

# First call creates new session
spark1 = SparkSession.builder \
    .appName("App1") \
    .config("spark.executor.cores", "2") \
    .getOrCreate()

# Second call returns existing session
spark2 = SparkSession.builder \
    .appName("App2") \
    .config("spark.executor.cores", "4") \
    .getOrCreate()

print(spark1 == spark2)  # True - same session

# Create truly new session (only if needed)
spark1.stop()
spark_new = SparkSession.builder \
    .appName("NewApp") \
    .getOrCreate()

# Proper cleanup
spark_new.stop()

SparkSession represents the modern standard for Spark development. While SparkContext remains accessible for specialized use cases involving RDDs, broadcast variables, and accumulators, SparkSession should be your default entry point. Its unified API, superior optimization capabilities, and cleaner syntax make it the clear choice for contemporary Spark applications.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.