Skip to content
Carmel Eve By Carmel Eve Software Engineer II · 2 min read
Optimising DAX: Data Materialisation

Hello again. In the previous post, we covered the two-engine architecture: the fast, multi-threaded storage engine, and the slow, single-threaded formula engine. The goal is to push as much work into the storage engine as possible.

But what happens when you can't? That's where data materialisation comes in, and it's one of the main causes of slow DAX queries.

What Is Data Materialisation?

The building of the data cache (the handoff between the storage engine and the formula engine) is called "data materialisation".

In the ideal case, the storage engine filters, groups, and aggregates your data efficiently, and passes a small, pre-processed result set to the formula engine. The formula engine does a bit of final calculation and you get your answer quickly.

Microsoft Fabric Weekly is a summary of the week's top news to help you build on the Microsoft Fabric Platform.

In the worst case, the storage engine decides it can't do any useful pre-processing and materialises the entire table - rebuilding it from the compressed column storage and dumping all of it into the cache for the formula engine to sort through.

Why It's Expensive

When the storage engine materialises a large table, you pay for:

The cost to reconstruct the data from compressed columns - undoing all that nice column-oriented storage. The cost to "decompress" - reversing the value, hash, and run-length encoding we covered earlier in the series. Poor memory-bandwidth utilisation - large amounts of data being moved between engines.

And then the formula engine has to process all of this data single-threaded. It's not great.

What Triggers It?

The storage engine can handle simple operations natively (scans, filters, GROUP BY, basic aggregations - as covered in the previous post). Anything beyond this can trigger materialisation.

Simple columnar filtering is fine. Each filter condition becomes a bitmap, the bitmaps are combined with a logical AND, and the result is applied to the data. Very efficient, all in the storage engine.

The best hour you can spend to refine your own data strategy and leverage the latest capabilities on Azure to accelerate your road map.

Cross-column comparisons are not. Something like Column A > Column B can't be resolved column-by-column - it requires comparing values across columns, which means materialisation.

Single-column DISTINCTCOUNT is fine. It's just a scan and count.

Multi-column DISTINCTCOUNT is not. Something like DISTINCTCOUNT(Column A + Column B) requires the storage engine to compute the expression first, which it can't do, so: materialisation.

The Goal

You want to materialise as late as possible - do as much filtering, grouping, and aggregation within the storage engine before the data cache is built. The less data that lands in the cache, the less work the formula engine has to do.

If you find yourself in a situation where a cross-column comparison is being done frequently, a practical workaround is to create a calculated column with just TRUE/FALSE for that comparison. The calculated column can then be filtered using the efficient bitmap path. It increases model size slightly, but can dramatically reduce query time.

What's Next

So now we understand why certain patterns are slow (they trigger materialisation). Look out for the final post in the series, where we'll look at specific practical examples - including the classic CALCULATE trap, variables, slicer costs, and how to isolate slow queries in the real world.

FAQs

What is data materialisation in DAX? Data materialisation is when the storage engine rebuilds an entire table and returns it to the formula engine via a data cache, rather than returning a small pre-filtered result. This is expensive because it involves reconstructing compressed data and transferring large amounts of data between engines.
What triggers data materialisation in Power BI? Complex operations that the storage engine can't handle natively, such as cross-column comparisons (Column A > Column B) or multi-column DISTINCTCOUNT operations. When the storage engine can't pre-process the data, it materialises everything and lets the formula engine handle it.

Carmel Eve

Software Engineer II

Carmel Eve

Carmel is a software engineer and LinkedIn Learning instructor. She worked at endjin from 2016 to 2021, focused on delivering cloud-first solutions to a variety of problems. These included highly performant serverless architectures, web applications, reporting and insight pipelines, and data analytics engines. After a three-year career break spent travelling around the world, she rejoined endjin in 2024.

Carmel has written many blog posts covering a huge range of topics, including deconstructing Rx operators, agile estimation and planning and mental well-being and managing remote working.

Carmel has released two courses on LinkedIn Learning - one on the Az-204 exam (developing solutions for Microsoft Azure) and one on Azure Data Lake. She has also spoken at NDC, APISpecs, and SQLBits, covering a range of topics from reactive big-data processing to secure Azure architectures.

She is passionate about diversity and inclusivity in tech. She spent two years as a STEM ambassador in her local community and taking part in a local mentorship scheme. Through this work she hopes to be a part of positive change in the industry.

Carmel won "Apprentice Engineer of the Year" at the Computing Rising Star Awards 2019.