Search…

DAX and Data Modeling

DAX Performance Optimization: How to Fix Slow Measures in Power BI

DAX Performance Optimization: How to Fix Slow Measures in Power BI

Find the slow measure with Performance Analyzer, confirm it in DAX Studio, then fix the usual causes: per-row measure calls, FILTER, repeats and the model.

Find the slow measure with Performance Analyzer, confirm it in DAX Studio, then fix the usual causes: per-row measure calls, FILTER, repeats and the model.

Written By: Sajagan Thirugnanam

Last Updated on September 23, 2026

To fix a slow measure, first find it with Performance Analyzer in Power BI Desktop, then look at how it spends its time in DAX Studio. The usual causes are an iterator that calls a measure on every row of a large table, FILTER over a whole table inside CALCULATE, the same expression calculated more than once, and a model that is not a clean star schema. Fix the model first when it is the cause, because no rewrite of a measure makes up for it.

This post is about slow DAX. If the whole report is slow, including visuals that use simple measures, start with the slow report troubleshooting guide or the Power BI performance guide.

Why DAX Measures Become Slow

A DAX query in an import model is answered by two engines:

  • The storage engine (VertiPaq) scans compressed columns. It is fast, and it can run in parallel.

  • The formula engine handles everything the storage engine cannot, such as complex logic and row-by-row evaluation. It runs on a single thread and is slower.

A measure is slow when it pushes a lot of work into the formula engine, or when it makes the storage engine run many separate scans. Common causes:

  • Iterators that call a measure for every row of a large table

  • FILTER over a whole table used as a CALCULATE filter

  • The same expression calculated several times

  • Bi-directional or long chains of relationships

  • High-cardinality columns in filters and relationships

Step 1: Identify the Slow Measure (Performance Analyzer)

Measure before changing anything.

  1. In Power BI Desktop, open the Optimize ribbon and select Performance Analyzer.

  2. Select Start recording, then Refresh visuals.

  3. Expand the slowest visual. Compare DAX query time with Visual display and Other.

  4. If DAX query time dominates, select Copy query, or Run in DAX query view to see the query the visual sends.

If Visual display or Other dominates, the measure is not the problem. Look at the number of visuals on the page instead. Use Export to save the results as a JSON file for comparison after each change.

Step 2: Reduce Iterator Usage (SUMX, FILTER, AVERAGEX)

An iterator is not slow by default. A simple expression over columns of one table is calculated by the storage engine:

Total Sales = SUMX ( Sales, Sales[Quantity] * Sales[Price] )

This measure is fine, and there is no need to replace it with a calculated column. The SUM vs SUMX post explains why it is also the correct way to write revenue.

Slow Pattern

The expensive pattern is an iterator that calls a measure on every row of a large table:

Total Margin Slow =
SUMX ( Sales, [Margin] )

Each row turns into a filter through context transition, and [Margin] is evaluated once per row. On a sales table with millions of rows, that is millions of evaluations.

Faster Approach

Iterate the smallest table that answers the question, or write the row expression with columns:

Total Margin =
SUMX ( Sales, Sales[Quantity] * ( Sales[Price] - Sales[Unit Cost] ) )

When the logic really does need a measure per item, iterate the dimension rather than the fact table:

Total Margin by Customer =
SUMX ( VALUES ( Customer[CustomerKey] ), [Margin] )

Rule: keep row expressions to column arithmetic, and iterate as few rows as possible.

Step 3: Avoid Repeated Calculations (Use Variables)

When one expression appears twice in a formula, it can be calculated twice.

Slow Measure

Sales YoY % =
DIVIDE (
    [Total Sales] - CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) ),
    CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
)

The prior-year CALCULATE appears in both the numerator and the denominator.

Optimized Version

Sales YoY % =
VAR CurrentSales = [Total Sales]
VAR PriorYearSales =
    CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
RETURN
    DIVIDE ( CurrentSales - PriorYearSales, PriorYearSales )

Variables avoid running the same calculation twice, because DAX evaluates a variable once and reuses the result. Name variables carefully: VAR Sales = ... fails in any model with a table called Sales. The DAX variables guide covers the rules.

Step 4: Minimize FILTER() Inside CALCULATE()

Slow Pattern

West Sales =
CALCULATE (
    [Total Sales],
    FILTER ( Sales, Sales[Region] = "West" )
)

FILTER walks every row of the Sales table and returns a table that CALCULATE then applies as a filter.

Faster Alternative

West Sales =
CALCULATE (
    [Total Sales],
    KEEPFILTERS ( Sales[Region] = "West" )
)

A Boolean filter on one column is what the storage engine is built for. KEEPFILTERS keeps any existing Region selection, as the FILTER version did. Leave it out if the measure should always show West whatever the slicer says.

FILTER is still needed when the condition uses a measure, for example FILTER ( VALUES ( 'Date'[Month] ), [Profit] > 0 ). Filter a small column like that, not the fact table.

Step 5: Optimize Data Model Design (Most Important)

Many slow measures are slow because of the model under them. A clean star schema often helps more than rewriting DAX.

  • Use a star schema: fact tables related to dimension tables. The star vs snowflake post compares the two.

  • Avoid bi-directional relationships unless a calculation needs one.

  • Keep relationship chains short.

  • Remove columns no report uses.

  • Relate tables on integer keys rather than long text keys.

Step 6: Reduce Cardinality

A column with many distinct values compresses worse and costs more to filter and to relate on. Typical examples:

  • Transaction IDs

  • GUIDs

  • Date-time columns with seconds

Ways to reduce it:

  • Split a date-time column into a date column and a time column.

  • Remove unique ID columns that no report uses.

  • Round or aggregate values upstream when the detail is not needed.

The relationships and cardinality post goes into relationship cardinality specifically.

Step 7: Avoid Complex Nested CALCULATE()

Nesting CALCULATE inside CALCULATE is not expensive in itself. The cost comes from CALCULATE running once per row. That happens every time a measure is referenced inside an iterator, because each measure reference is wrapped in an implicit CALCULATE:

High Value Customers =
COUNTROWS (
    FILTER ( Customer, [Total Sales] > 10000 )
)

This evaluates [Total Sales] once per customer. On a few thousand customers that is fine. On millions of rows it is not.

To keep it fast:

  • Iterate dimension tables or VALUES of a key column, not fact tables.

  • Move shared results into variables before the iterator starts.

  • Combine several filters into one CALCULATE instead of layering them.

Step 8: Use Measures Instead of Calculated Columns (When Appropriate)

Calculated columns:

  • Are stored for every row and use memory permanently

  • Add time to every refresh

Measures:

  • Are calculated at query time

  • Add nothing to model size

A calculated column is worth its memory when you slice, filter, group or relate on it. For arithmetic you only ever aggregate, a measure is usually the better choice. The measures vs calculated columns post has the full comparison.

Step 9: Understand ALL vs ALLSELECTED Performance Impact

Inside CALCULATE, ALL and REMOVEFILTERS remove the same filters, so switching between them changes readability, not speed. What matters is how much you remove:

  • ALL ( Sales ) on a fact table clears filters on every related dimension. The engine may then aggregate far more rows.

  • REMOVEFILTERS ( 'Product'[Category] ) clears one column and leaves everything else in place.

Remove filters from the narrowest columns that give the right answer. ALLSELECTED is harder to reason about inside iterators, so test measures that use it on their own before combining them. The ALLSELECTED vs ALL post explains the difference in results.

Step 10: Test with DAX Studio (Advanced Optimization)

DAX Studio is a free tool that runs a query against your model and shows where the time goes. Paste in the query from Performance Analyzer, turn on Server Timings, clear the cache, and run it.

Look at:

  • Total time, split into storage engine and formula engine

  • The number of storage engine queries

  • Whether any storage engine query contains a callback to the formula engine

A good result is mostly storage engine time from a small number of queries. High formula engine time, or dozens of storage engine queries for one visual, points to the patterns in Steps 2, 4 and 7. Change one thing at a time and rerun the same query to compare.

DAX Performance Optimization Checklist

Before publishing a report, check:

  • The model is a star schema

  • No iterator calls a measure over a fact table

  • CALCULATE filters are Boolean where possible

  • Repeated expressions are stored in variables

  • High-cardinality columns are reduced or removed

  • Slow visuals were checked in Performance Analyzer

FAQs

Why is my Power BI report slow even with simple visuals?

Check Performance Analyzer first. If DAX query time is high, a measure or the model is the cause, often a measure that iterates a large table or a bi-directional relationship. If Visual display or Other time is high, the page has too many visuals or the visuals are waiting on each other.

Are iterator functions always bad?

No. SUMX over column arithmetic on one table is handled efficiently by the storage engine. Iterators become slow when they call a measure or run complex logic on every row of a large table.

What is the biggest performance improvement in Power BI?

A clean star schema with single-direction relationships and no unused high-cardinality columns. After that, removing per-row measure calls from iterators on fact tables usually gives the biggest gain in DAX.

Sources

Related to DAX and Data Modeling

Want Power BI expertise in-house?

Get in Touch With Us

Turn your team into Power BI pros and establish reliable, company-wide reporting.

Berlin, DE

powerbi@casewhen.co

Follow us on

© 2026 CaseWhen Consulting
© 2026 CaseWhen Consulting
© 2026 CaseWhen Consulting