• Data Engineering Home
  • Data Engineering Learning Path

Partitioning and Bucketing Strategies

Used together, they’re one of the single biggest levers for cutting both query time and cost on large datasets.


Why It Matters

Query engines that scan data in cloud storage — Amazon Athena, Spark, BigQuery these largely charge based on how much data they have to read, not how much data actually matches your query. A poorly laid-out dataset forces the engine to scan everything just to find a handful of relevant rows.

Partitioning and bucketing are the two primary techniques for avoiding that waste. Get the layout right, and a query that would have scanned terabytes might only touch a few megabytes. Get it wrong or skip it entirely and every query on a growing dataset gets slower and more expensive over time, often without anyone realizing why until the monthly cloud bill shows up.

If you’re a tech lead / an architect working with large datasets in S3, a data lake, or a warehouse, this is one of the highest leverage, lowest-effort optimizations available and one of the most commonly skipped by teams still new to the space.


Partitioning

Partitioning physically splits a dataset into separate folders or the warehouse equivalent based on the value of one or more columns — most commonly a date.

A partitioned layout in S3 looks like this:

s3://my-bucket/orders/
  year=2026/month=09/day=24/  <- one file (or more) here
  year=2026/month=09/day=25/
  year=2026/month=09/day=26/

When a query filters WHERE year=2026 AND month=9 AND day=26, the engine only reads the one matching folder and this is called partition pruning, and it’s the entire reason partitioning exists.

Choosing a partition key — the core tradeoff:

AWS Athena Example (ref)

CREATE EXTERNAL TABLE [IF NOT EXISTS]
 [db_name.]table_name [(col_name data_type [COMMENT col_comment] [, ...] )]
 [COMMENT table_comment]
 [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
 [CLUSTERED BY (col_name, col_name, ...) INTO num_buckets BUCKETS]
 [ROW FORMAT row_format]
 [STORED AS file_format] 
 [WITH SERDEPROPERTIES (...)]
 [LOCATION 's3://amzn-s3-demo-bucket/[folder]/']
 [TBLPROPERTIES ( ['has_encrypted_data'='true | false',] ['encryption_option'='SSE_S3 | SSE_KMS | CSE_KMS',] ['kms_key'='aws_kms_key_arn',] ['classification'='aws_glue_classification',] property_name=property_value [, ...] ) ]
-- Partitioned AND bucketed table, using Athena's documented CLUSTERED BY syntax
CREATE EXTERNAL TABLE orders (
    order_date     STRING,
    order_id       STRING,
    customer_id    STRING,
    amount         DOUBLE
)
PARTITIONED BY (order_date STRING)
STORED AS PARQUET
LOCATION 's3://my-bucket/orders/'
TBLPROPERTIES (
  'bucketing_format' = 'spark'
);

Bucketing

AWS Athena Example

-- Partitioned AND bucketed table, using Athena's documented CLUSTERED BY syntax
CREATE EXTERNAL TABLE orders (
    order_date     STRING,
    order_id       STRING,
    customer_id    STRING,
    amount         DOUBLE
)
PARTITIONED BY (order_date STRING)
CLUSTERED BY (customer_id) INTO 8 BUCKETS
STORED AS PARQUET
LOCATION 's3://my-bucket/orders/'
TBLPROPERTIES (
  'bucketing_format' = 'spark'
);

A query that benefits from both

SELECT SUM(amount)
FROM orders
WHERE order_date BETWEEN '2026-09-20' AND '2026-09-26'
  AND customer_id = '12345';
-- If this table is partitioned by order_date and bucketed by customer_id,
-- Athena calculates the exact bucket for this prders and reads
-- at most one file per matching partition — not the whole partition.
CREATE TABLE orders_optimized
WITH (
    format             = 'PARQUET',
    external_location  = 's3://my-bucket/orders_optimized/',
    partitioned_by      = ARRAY['order_date'],
    bucketed_by         = ARRAY['customer_id'],
    bucket_count        = 8
) AS
SELECT order_id, customer_id, amount, order_date
FROM orders_raw;
CREATE TABLE orders_copy AS SELECT * FROM orders_raw

Where This Fits in the Bigger Picture

Partitioning and bucketing decisions are made at the storage stage, but they’re really a bet on how the data will be queried later and which is why they connect directly to Data Architecture decisions as much as engineering ones:

Data engineering pipeline flow chart showing Generate, Ingest, Store, Transform

The practical rule before choosing a partition key, ask how the table will actually be queried and not what feels like the “natural” way to organize the data.

Real-World Scenarios

Company / ScenarioPartitioning / bucketing strategyWhy
Swiggy/Zomato — analyzing order historyPartition by order_date, bucket by restaurant_idMost analytics queries filter by a date range and often a specific restaurant — this layout lets both filters narrow the scan dramatically.
A bank — transaction history for compliance queriesPartition by transaction_date, bucket by account_idRegulatory and fraud-investigation queries typically ask “show me this account’s activity in this date range” — exactly the two-dimensional filter this layout is built for.