Skip to main content

Partitioning & Bucketing in PySpark โ€” Optimize Large Dataset Performance

At NeoMart, the data team works with billions of rows every day:

  • Sales records by date and region
  • Clickstream events by user and session
  • Product catalogs across multiple categories

Reading all data every time is slow and expensive.
Partitioning and bucketing let Spark skip irrelevant data and reduce shuffle during joins and aggregations.


1. Understanding Partitioningโ€‹

Partitioning physically splits data into directories based on column values.

  • Each partition = a subdirectory
  • Example: partition by year or region
  • Spark can prune partitions during queries

Example Datasetโ€‹

data = [
("2025-01-01", "Electronics", 12000),
("2025-01-01", "Grocery", 4000),
("2025-02-01", "Electronics", 15000),
("2025-02-01", "Fashion", 3000)
]

df = spark.createDataFrame(data, ["date", "category", "revenue"])

2. Writing Partitioned Dataโ€‹

df.write.mode("overwrite") \
.partitionBy("category") \
.parquet("/tmp/neo_partitioned")
  • Creates a folder for each category:
    /tmp/neo_partitioned/category=Electronics/
    /tmp/neo_partitioned/category=Grocery/

  • Querying category='Electronics' reads only that partition.


3. Reading Partitioned Dataโ€‹

df_read = spark.read.parquet("/tmp/neo_partitioned/category=Electronics")
df_read.show()

Outputโ€‹

datecategoryrevenue
2025-01-01Electronics12000
2025-02-01Electronics15000

Partition pruning reduces I/O and speeds up queries.


4. Partitioning Best Practicesโ€‹

โœ” Partition by high-cardinality but selective columns (like date, region)
โœ” Avoid partitioning by too many columns โ†’ too many small files
โœ” Keep less than 1000 partitions per table for efficiency
โœ” Combine with predicate filtering for query speed


5. Understanding Bucketingโ€‹

Bucketing divides data into fixed buckets using a hash function on a column.

  • Unlike partitioning, bucketed data is in files within the same folder
  • Useful for joins, aggregations, sampling
  • Bucketing enables co-located joins โ†’ no shuffle

6. Writing Bucketed Tablesโ€‹

df.write.mode("overwrite") \
.bucketBy(4, "category") \
.sortBy("revenue") \
.saveAsTable("bucketed_sales")
  • 4 buckets for category
  • Within each bucket, data sorted by revenue

7. Reading & Joining Bucketed Tablesโ€‹

bucketed_df = spark.table("bucketed_sales")

# Optimized join with another bucketed table
other_df = spark.table("bucketed_sales")
joined_df = bucketed_df.join(other_df, "category")
  • Spark can perform bucketed joins โ†’ avoids full shuffle
  • Great for large fact-dimension joins

8. Partitioning vs Bucketing โ€” Quick Comparisonโ€‹

FeaturePartitioningBucketing
Physical layoutSubdirectoriesFiles within a directory
PurposeQuery pruningOptimized joins/aggregations
Column typeLow/moderate cardinalityHigh cardinality
Shuffle impactReduces I/OReduces shuffle during join
Sort insideOptionalCan sort inside bucket

9. Best Practicesโ€‹

โœ” Partition by date for time-series data
โœ” Bucket large dimension tables by join key
โœ” Combine partitioning + bucketing for large fact tables
โœ” Use sortBy() within buckets for faster aggregation
โœ” Monitor number of files to avoid small-file problem


10. Story Exampleโ€‹

NeoMart stores billions of orders.

  • Partition by year and month โ†’ only read relevant months
  • Bucket by customer_id โ†’ join with customer master table without shuffle
  • Result โ†’ queries 5x faster, cluster utilization optimized

Summaryโ€‹

With partitioning and bucketing, you can:

  • Reduce I/O with partition pruning
  • Minimize shuffle in large joins
  • Sort data within buckets for fast aggregation
  • Build scalable, production-ready pipelines

Mastering these techniques makes PySpark efficient at scale, just like NeoMart handles billions of daily transactions.


Next Topic โ†’ Caching, Persisting, and Memory Management