Skip to main content

Data Infrastructure

Lakehouse Performance Tuning: Optimising Multi-Petabyte Databricks Environments

Beyond Z-Order and Liquid Clustering: how to tune a multi-petabyte Databricks lakehouse in the order that pays — layout, then skipping, then compute.

By Binu Kuttappan14 min read
Layers of a Databricks lakehouse tuning stack, from Parquet file layout and Delta statistics up through data skipping to Photon compute and warehouse sizing
8
Tuning techniques in the selection matrix
5
Steps in the query-profile workflow
4
Partitioning anti-patterns at PB scale
32
Columns with Delta statistics by default

At multi-petabyte scale, the highest-leverage tuning work in a Databricks lakehouse is not query rewriting — it is file layout. Compaction, clustering and statistics determine how much data the engine can avoid reading, and that avoidance outweighs every other optimisation. Photon, caching and warehouse sizing multiply a good layout; they cannot rescue a bad one.

This guide walks the stack in the order we apply it in data engineering engagements: layout first, skipping second, compute third. Every claim below describes documented engine behaviour; the numbers that matter are the ones your own query profile produces.

#Why Standard Strategies Collapse at This Scale

What this means for you: if your tuning playbook was written when the estate held tens of terabytes, it is now actively misleading you about where the time goes.

As volumes cross the multi-petabyte threshold, the techniques that worked earlier degrade non-linearly. Date partitioning that produced hundreds of directories now produces hundreds of thousands. Occasional manual compaction cannot keep pace with continuous ingestion. Sizing by intuition stops correlating with outcomes.

The shape of the failure is consistent. Planning starts to dominate execution, and adding compute stops helping, because the bottleneck is metadata rather than CPU.

In our engagements, moving high-cardinality tables from Hive-style partitioning to Liquid Clustering has consistently reduced metadata overhead and shortened query planning — but the size of that gain is entirely dependent on how badly partitioned the table was to begin with. Treat vendor benchmarks and consultancy anecdotes alike as hypotheses to test on your own workload.

#The Four Pillars of Lakehouse Speed

What this means for you: these four areas account for nearly all the durable gains — and they are listed in the order you should attack them.

01

Storage layout

Compaction, Z-Ordering and Liquid Clustering, so that related rows sit in the same files and the engine can skip the rest.

02

Data skipping and statistics

Delta file statistics that actually cover the columns your predicates filter on. Skipping is what layout work exists to serve.

03

Compute configuration

Photon where the operator mix suits it, right-sized warehouses, and autoscaling matched to whether the problem is size or concurrency.

04

Query architecture

Join strategy, shuffle volume and spill. Real problems, but second-order until layout and skipping are sound.

Work them out of order and you will spend money without moving the needle. A bigger warehouse over a fragmented table buys a faster way to read files you should never have read.

#File Layout Fundamentals: The Small-File Problem

What this means for you: if your tables are fragmented, every other item on this page is premature.

Every Delta table is ultimately a collection of Parquet files plus a transaction log. Streaming ingestion, frequent small merges and over-parallelised writes all produce the same pathology: thousands of tiny files where hundreds of well-sized ones should be.

Each file carries fixed overhead — a footer to read, a transaction log entry, statistics to evaluate at planning time. A fragmented table is therefore slow to plan and slow to scan regardless of cluster size.

The remedies for fragmentation are documented and boring, which is exactly why they are skipped.

OPTIMIZE compacts small files into larger ones — Databricks targets roughly 1 GB files by default, tunable per table. Predictive optimisation can run this automatically on Unity Catalog managed tables. On the write path, optimised writes and auto compaction reduce fragmentation before it accumulates.

#Z-Order or Liquid Clustering: Choosing the Layout Strategy

What this means for you: for new tables this is close to a settled question, but the migration cost on existing partitioned tables is what will actually shape your plan.

Z-Ordering (OPTIMIZE ... ZORDER BY) co-locates rows with similar values across multiple columns into the same files using a space-filling curve. Each file then covers a narrow range of the Z-Ordered columns, so min/max statistics become selective and the engine can eliminate most files for filtered queries.

Its limitations are structural. Z-Ordering is not incremental — each run rewrites data, and newly ingested files sit unclustered until the next run — and its effectiveness dilutes as columns are added to the key.

Z-Ordering rewrites. Liquid Clustering adjusts. At petabyte scale, that single difference is most of the migration argument.

Liquid Clustering (CLUSTER BY) is Databricks' successor to both partitioning and Z-Ordering for most workloads. It clusters incrementally: OPTIMIZE on a liquid table clusters only what needs clustering rather than rewriting everything. Critically, clustering keys can be changed later without rewriting the table, because clustering is a property of layout rather than of directory structure.

Data written before clustering is enabled is not reclustered until OPTIMIZE runs, so plan an initial clustering pass into the migration rather than discovering it afterwards. Databricks has also been extending automatic key selection for clustered tables; check whether your runtime supports it before hand-tuning keys you may not need to own.

#Partitioning Anti-Patterns at Petabyte Scale

What this means for you: Hive-style partitioning is not obsolete, but four specific habits explain a large share of the slow lakehouses we assess.

01

High-cardinality partition columns

Partitioning by customer ID or device ID produces enormous partition counts, each holding small files — the small-file problem with a directory structure wrapped around it.

02

Over-layered partition keys

year/month/day/hour/region multiplies partition counts combinatorially and forces full metadata enumeration for queries that do not filter on the leading keys.

03

Partitioning small tables at all

Databricks guidance is that most tables under roughly a terabyte do not benefit. Per-partition data should reach at least the gigabyte range to pay for itself.

04

Treating partition columns as immutable

Query patterns drift, and repartitioning means rewriting. That rigidity is precisely what Liquid Clustering exists to remove.

Partitioning still earns its place where an operational need — retention deletes, regulatory segregation, coarse date-based pruning on a genuinely low-cardinality key — makes directory boundaries useful in themselves.

#Statistics and Data-Skipping Mechanics

What this means for you: skipping can be silently broken on a table that looks perfectly well laid out, and nothing in the query plan announces it.

Data skipping is what all layout work ultimately serves. Delta collects per-file statistics — minimum, maximum and null counts — for the first 32 columns of a table by default, and query planning compares filter predicates against those ranges to eliminate files without reading them.

A filter column outside the indexed range does not degrade skipping. It removes it, quietly, with no error and no warning in the plan.

Two consequences follow. First, filter columns must fall inside the indexed column range, which is tunable through table properties such as delta.dataSkippingNumIndexedCols — wide tables with predicates buried past the cutoff lose skipping entirely. Second, statistics on long string columns are expensive to collect and rarely selective.

Restructuring column order so that statistics land where predicates live is among the cheapest interventions available at this scale, and it is routinely overlooked because it looks like a modelling decision rather than a performance one.

#Photon: When the Premium Pays

What this means for you: Photon is a rate decision as much as a performance one, and the answer is per-workload rather than per-platform.

Photon is Databricks' vectorised, C++ query engine. It accelerates the operations that dominate analytical workloads — scans, joins, aggregations, Delta writes — and pays best when queries are CPU-bound on those operations.

It does not accelerate everything. Python UDF-heavy logic, RDD-based code, and workloads bottlenecked on I/O or shuffle see modest gains, while Photon-enabled compute carries a higher DBU rate.

#Caching Layers: Three Different Mechanisms

What this means for you: conflating these three caches is a common route to misdiagnosis, because they fail in completely different ways.

01

Disk cache

Formerly the Delta cache. Keeps copies of remote Parquet data on worker-local SSDs, transparently accelerating repeated reads. Works best on storage-optimised instance types and is invalidated correctly as tables change, so it is safe by default.

02

Query result cache

On SQL warehouses, returns previously computed results for identical queries when the underlying data has not changed. A substantial win for dashboard-style repetition; invisible for ad-hoc exploration.

03

Spark cache

Application-level .cache() and persist(). Occupies executor memory and causes memory pressure when used indiscriminately. Prefer the disk cache for reuse across queries.

Caches amplify a good layout. They do not fix a bad one — a query that scans ten million files slowly will populate a cache slowly too.

#A Query-Profile-Driven Tuning Workflow

What this means for you: this is the sequence that replaces guesswork with evidence, and it is short enough to run on a single slow query this afternoon.

Tuning by folklore wastes engineering time. Tuning from the query profile does not. The workflow we run in platform engagements has five steps.

  1. Capture the profile for representative slow queries in Databricks SQL, or the Spark UI for jobs, and see where time actually goes: planning, scan, shuffle, spill.
  2. Check files pruned versus files read. A low pruning ratio on a selective query points at layout or statistics — cluster the filter columns, and verify they carry stats.
  3. Check bytes read versus bytes returned. Heavy read amplification means missing skipping or over-wide scans.
  4. Check shuffle and spill. Shuffle points at join strategy and data distribution; spill at memory sizing. These are compute problems, so fix them second.
  5. Re-measure. One change at a time, same workload, same warehouse size.

#Concurrency and Warehouse Sizing

What this means for you: the most common sizing mistake costs real money every month and does not shorten a single queue.

Sizing confuses two different levers. Scaling up — a larger warehouse size — makes individual queries faster through more parallelism, and is the right lever when single large queries are slow. Scaling out — minimum and maximum cluster autoscaling — adds capacity for concurrent queries, and is the right lever when queries are individually fine but queue at peak.

Query history makes the diagnosis observable. Queuing time indicates a scale-out problem; long-running queries with no queuing indicate a scale-up or a layout problem. Answering a concurrency problem with a bigger warehouse raises cost without shortening the queue, which is why warehouse sizing belongs inside a broader cloud cost discipline rather than in a one-off tuning ticket.

#Technique Selection Matrix

What this means for you: use the final column to sequence the work — it says when each technique should be your next move rather than merely what it does.

TechniqueProblem it solvesEffortReach for it first when
Compaction (OPTIMIZE, auto compaction)Small-file overhead in planning and scansLow, recurringFile counts are high across a table — it lifts every query, so it is the default first move
Z-OrderingSelective multi-column filters on stable query patternsMedium, recurring rewritesQuery patterns are stable and a Liquid Clustering migration is not yet justified
Liquid ClusteringHigh-cardinality keys, evolving query patternsMedium one-time migration, low ongoingYou are designing a new table, or an existing table's access pattern has already changed once
Partition redesignMetadata blow-up from over-partitioningHigh (rewrite)Partition counts are in the hundreds of thousands and planning dominates the profile
PhotonCPU-bound scans, joins, aggregationsLow (enable and measure)The profile shows time concentrated in scan, join and aggregate operators, not UDFs or I/O wait
Disk / result cachingRepeated reads and repeated dashboard queriesLowThe same queries recur on the same data — and never as a substitute for fixing layout
Statistics tuningFilter columns without usable statsLow–mediumPruning ratios are poor despite good clustering, which usually means the predicate column is past the stats cutoff
Warehouse right-sizingQueueing under concurrency; slow large queriesLowQuery history shows queuing (scale out) or long single queries with no queue (scale up)

Sequenced this way, tuning stops being a periodic firefight and becomes a property of the platform. Making that stick is a DataOps problem as much as an engineering one — the practices are covered in platform engineering and DataOps culture, and the monitoring that catches regressions before users do is covered in continuous data observability.

If the wider question is which platform should host this workload at all, the trade-offs are set out in our Microsoft Fabric versus Databricks framework. And where the estate is being rebuilt rather than tuned, cloud platform engineering is where the layout decisions above become defaults rather than remediation.

#Frequently Asked Questions

#Should I use Z-Ordering or Liquid Clustering?

For new tables on current Databricks runtimes, Liquid Clustering is the default choice: it is incremental, handles high-cardinality keys, and lets you change clustering columns without rewriting the table. Z-Ordering remains reasonable on existing tables with stable query patterns where migration is not yet justified. The two cannot be combined on one table, so treat Z-Order as the legacy path.

#Does enabling Photon always improve price-performance?

No. Photon accelerates vectorisable SQL and DataFrame operations — scans, joins, aggregations — at a higher DBU rate. Workloads dominated by Python UDFs, RDD code, or I/O wait see little speed-up while still paying the premium. Profile first, enable where the operator mix fits, and validate both cost and wall-clock time on your own workload rather than on a published benchmark.

#How often should OPTIMIZE run on high-ingest tables?

Frequently enough that fragmentation never dominates planning time. For streaming or high-frequency merge tables, that means scheduled runs rather than ad-hoc ones. Better still, let predictive optimisation manage it on Unity Catalog managed tables, and enable optimised writes so that less fragmentation is created in the first place. Compaction on a liquid-clustered table is cheaper because it works incrementally.

#Is Hive-style partitioning obsolete at petabyte scale?

Not obsolete, but demoted. It still suits very large tables with a natural, low-cardinality access pattern — commonly date — and cases where partition boundaries serve operational needs such as retention deletes. For query performance specifically, Liquid Clustering supersedes it in most new designs because it avoids the small-file and rigidity failure modes that partitioning develops at this scale.

#Why is my query slow even though the table is clustered correctly?

The usual cause is statistics rather than layout. Delta indexes the first 32 columns by default, so a predicate on a column past that cutoff gets no file-level skipping regardless of how well the data is clustered. Check the pruning ratio in the query profile, confirm the filter column carries statistics, and adjust delta.dataSkippingNumIndexedCols or column order accordingly.


Unolabs is a Data and AI first engineering consultancy, headquartered in the United Kingdom with engineering operations in Pune and active engagements across the UK, Australia, and Hong Kong. We help enterprises build the architectural foundation for autonomous AI execution — governed data platforms, semantic intelligence, and agentic systems that enterprises can stand behind.

If you are weighing a Liquid Clustering migration or trying to explain a lakehouse bill that keeps growing, book a discovery call and we will profile the workload with you.

Continue reading

Tune your lakehouse in the order that pays

Bring us your slowest workloads. We will work through layout, skipping, and compute with your engineers and tell you which lever moves your numbers first.

Book a Performance Review