Partitioning and Bucketing in Spark Explained

“Partition” means three different things in Spark: in-memory partitions, directory partitions on disk, and buckets. What each one controls, which setting or method changes it, and how to choose partition and bucket columns without creating millions of small files.

GloryTecks Team

Training & Placement Faculty

6 May 2026 5 min read

Last updated: 24 September 2026

Share:

In Spark, "partitioning" means three different things. People mix them up, and the fix for a slow job depends on which one is at fault. In-memory partitions decide how work is split across tasks while a job runs. Directory partitioning (partitionBy) decides how files are laid out on disk, so later queries can skip them. Bucketing (bucketBy) pre-organises a table by the hash of a column, so later joins and aggregations on that column need less shuffling. This guide takes them one at a time.

1. In-memory partitions: how work is split

Each Spark task processes one partition of a DataFrame. When Spark reads files, it packs input into partitions of up to spark.sql.files.maxPartitionBytes, 128 MB by default. After a shuffle, which happens for joins and aggregations, the number of partitions comes from spark.sql.shuffle.partitions, 200 by default (Spark: performance tuning). Too few partitions and you cannot use all your cores; too many tiny ones and scheduling overhead dominates.

Adaptive Query Execution, enabled by default since Spark 3.2.0, uses runtime statistics to coalesce small post-shuffle partitions, so the fixed 200 matters less than it used to. You still control partitioning explicitly with two methods, which behave very differently:

MethodShuffle?Can increase partitions?Use it to
df.repartition(n) or df.repartition(n, "col")Yes: full shuffle, hash-partitionedYesSpread skewed data evenly, or co-locate rows by a key before writing
df.coalesce(n)No: merges existing partitions (narrow dependency)NoReduce the number of output files cheaply at the end of a job

The PySpark reference gives the trade-off. coalesce from 1,000 to 100 partitions avoids a shuffle, because each new partition simply claims 10 old ones. But a drastic coalesce, down to one partition for example, can push the whole upstream computation onto very few tasks. In that case repartition is better, despite the shuffle (PySpark: coalesce).

2. Directory partitioning with partitionBy

partitionBy writes one sub-directory per value of the partition column, named column=value (Spark: bucketing, sorting and partitioning). When a later query filters on that column, Spark reads only the matching directories. Reading the table back, Spark discovers the partition columns from the paths and infers their types (Spark: partition discovery).

python
(events
    .write
    .partitionBy("event_date")
    .mode("overwrite")
    .parquet("s3://my-lake/curated/events/"))

# Resulting layout:
#   s3://my-lake/curated/events/event_date=2026-09-01/part-00000-….parquet
#   s3://my-lake/curated/events/event_date=2026-09-02/part-00000-….parquet

daily = (spark.read.parquet("s3://my-lake/curated/events/")
              .where("event_date = '2026-09-02'"))    # reads one directory, not all

Choose the partition column by one test: do most queries filter on it, and does it have a modest number of values? Dates are the classic choice. The Spark documentation warns that because partitionBy creates a directory per value, it has "limited applicability to columns with high cardinality". Partition by customer_id and you get one directory per customer, each holding tiny files.

The small-files multiplication

Every task writes its own file into every partition directory it has rows for. A job with 200 shuffle partitions writing 365 date partitions can produce up to 73,000 files, most of them small. Repartition by the partition column first (df.repartition("event_date").write.partitionBy("event_date")…) so that each date’s rows sit in one task, giving one file per date, or a few if the data is large.

Overwriting only some partitions

Re-running yesterday's load with mode("overwrite") on a partitioned table is a well-known trap. In the default static mode, Spark deletes all partitions matching the partition specification before writing, which for a DataFrame write can mean the whole table. In dynamic mode, it replaces only the partitions the job actually writes data into (Spark: configuration):

python
(yesterday
    .write
    .option("partitionOverwriteMode", "dynamic")   # or set spark.sql.sources.partitionOverwriteMode
    .partitionBy("event_date")
    .mode("overwrite")
    .parquet("s3://my-lake/curated/events/"))

3. Bucketing with bucketBy

Bucketing splits a table into a fixed number of files by the hash of a column. Rows with the same key always land in the same bucket, however many distinct keys there are, which is why bucketing works for high-cardinality columns where partitionBy does not. Bucketing and sorting apply only to persistent tables: you write with saveAsTable, not save or parquet(path) (Spark: bucketing, sorting and partitioning).

python
(orders
    .write
    .bucketBy(16, "customer_id")
    .sortBy("customer_id")
    .mode("overwrite")
    .saveAsTable("sales.orders_bucketed"))

(customers
    .write
    .bucketBy(16, "customer_id")
    .sortBy("customer_id")
    .mode("overwrite")
    .saveAsTable("sales.customers_bucketed"))

joined = spark.table("sales.orders_bucketed").join(
    spark.table("sales.customers_bucketed"), "customer_id")
joined.explain()   # check the plan: no Exchange before the join means the shuffle was avoided

The payoff comes when both sides of a join are bucketed the same way on the join key. Spark can then use the existing layout instead of shuffling both tables. The tuning guide calls these bucket joins, and treats the newer storage-partitioned join as their generalisation (Spark: performance tuning). Always confirm with explain(): bucket counts that differ between tables, or a join key that is not the bucket column, bring the shuffle back.

You can combine both layouts, for example partitionBy("event_date").bucketBy(16, "customer_id"): dates for pruning, buckets for joins within each date.

Choosing between them

SituationUse
A job runs with a few huge tasks or thousands of tiny onesIn-memory: repartition or coalesce, or let AQE coalesce
Queries filter on a low-cardinality column (date, region, source system)partitionBy on that column
The same large tables are joined or aggregated repeatedly on a high-cardinality keybucketBy on that key, the same bucket count on both tables
Output has too many small filesrepartition by the partition column before partitionBy, or coalesce at the end
One key holds a large share of the rows (skew)In-memory: AQE skew-join handling, or salting the key; bucketing does not fix skew

Shuffles, join strategies and caching are covered in Spark performance tuning: shuffles, joins and caching. If DataFrame basics such as transformations, actions and lazy evaluation are new, start with the PySpark tutorial.

Is partitionBy the same as repartition?

repartition changes how rows are split across tasks in the running job. partitionBy is a write option that changes the directory layout on disk. They are often used together: repartition by a column, then write with partitionBy on the same column.

Why can I not use bucketBy with .parquet(path)?

Spark records bucketing in the table metadata, so bucketing and sorting are supported only for persistent tables written with saveAsTable.

How many buckets should I choose?

Enough that each bucket file is a reasonable size for your data, and the same number on tables you intend to join. The count cannot be changed without rewriting the table, so size it for expected growth.

Does partitioning help if queries do not filter on the partition column?

No. Pruning only happens when the filter is on the partition column. A layout nobody filters on only adds directories and small files.

Where to go next

Spark architecture, DataFrames, Spark SQL and optimisation are part of the Apache Spark module in our Data Engineering course.

Sources

GloryTecks Team

Training & Placement Faculty

The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.

Want to master Data Engineering?

Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.

Explore course