Skip to main content

Performance Tuning โ€” Partitions, Repartition, and Coalesce in PySpark

At NeoMart, large-scale pipelines often process billions of rows.
Efficient partitioning is critical to:

  • Reduce shuffle and disk I/O
  • Improve join and aggregation performance
  • Balance task distribution across executors

This chapter explains partitions, repartition, and coalesce with practical examples.


1. Understanding Partitionsโ€‹

  • Spark divides data into partitions โ†’ processed in parallel
  • Number of partitions affects parallelism and performance
  • Default partition count depends on cluster configuration and source
print(df.rdd.getNumPartitions())
  • Returns the number of partitions for df

2. Repartition โ€” Increase or Shuffle Partitionsโ€‹

  • repartition(n) โ†’ reshuffles data into n partitions
  • Useful for parallelizing wide transformations or joins
# Repartition to 8 partitions
df_repart = df.repartition(8)
print(df_repart.rdd.getNumPartitions())

Storyโ€‹

NeoMart joins two large datasets.

  • Original partitions = 2 โ†’ tasks underutilized
  • Repartition to 8 โ†’ tasks distributed evenly โ†’ faster execution

3. Coalesce โ€” Reduce Partitions Without Shuffleโ€‹

  • coalesce(n) โ†’ reduces partitions without full shuffle
  • Ideal after filtering or aggregation
# Reduce partitions to 2 without shuffle
df_coalesce = df_repart.coalesce(2)
print(df_coalesce.rdd.getNumPartitions())
  • Avoids expensive shuffle operation โ†’ faster than repartition when reducing partitions

4. Practical Exampleโ€‹

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = SparkSession.builder.appName("NeoMart").getOrCreate()

data = [(i, f"product_{i}", i*10) for i in range(1, 101)]
df = spark.createDataFrame(data, ["id", "name", "price"])

# Check initial partitions
print("Initial partitions:", df.rdd.getNumPartitions())

# Increase partitions for join
df_repart = df.repartition(10)
print("After repartition:", df_repart.rdd.getNumPartitions())

# Filter expensive operation
df_filtered = df_repart.filter(F.col("price") > 500)

# Reduce partitions after filtering
df_final = df_filtered.coalesce(3)
print("After coalesce:", df_final.rdd.getNumPartitions())

Outputโ€‹

Initial partitions: 4
After repartition: 10
After coalesce: 3
  • Shuffles only occur in repartition, not in coalesce
  • Balanced partitions โ†’ better task parallelism

5. Best Practicesโ€‹

โœ” Repartition before wide transformations or joins โ†’ balance tasks
โœ” Coalesce after filtering or aggregations โ†’ avoid small files and shuffle
โœ” Avoid too many partitions โ†’ overhead of task scheduling
โœ” Avoid too few partitions โ†’ underutilized cluster resources
โœ” Combine with partitioning on columns for large datasets


6. Story Exampleโ€‹

NeoMart runs nightly ETL on 500 million rows:

  • Original partitions = 50 โ†’ join tasks uneven
  • Repartition to 200 โ†’ full cluster utilization โ†’ faster joins
  • After filtering high-value products โ†’ coalesce to 50 โ†’ fewer small files
  • Result โ†’ 3x faster ETL runtime

Summaryโ€‹

  • Partitions โ†’ control parallelism
  • Repartition(n) โ†’ increase partitions with shuffle
  • Coalesce(n) โ†’ decrease partitions without shuffle
  • Proper tuning โ†’ faster joins, reduced shuffle, optimized cluster utilization

NeoMart pipelines achieve high throughput and low latency by carefully repartitioning and coalescing DataFrames.


Next Topic โ†’ Introduction to Structured Streaming.