The Power BI Fellowship
Patterns & Playbooks · Troubleshooting

Power BI Performance Clinic

Find what is slow first (rendering, DAX, model, Power Query, source SQL, gateway or capacity), then the fixes for each layer, with the tools that prove it.

BI DeveloperSenior BI DeveloperBI EngineerPerformance AnalyzerDAX StudioVertiPaq AnalyzerDAX query viewChecked 2 Oct 2026Download .md
On this page (9)

Rule one: measure before you change anything

Performance work without a baseline is guessing. Record the time for the slow page or query (Performance Analyzer for visuals, DAX Studio for queries with a cold cache, refresh history for refreshes), change one thing, measure again, and write the before and after in a performance report. Sprint 04 is a full exercise in this.

What is slow?

  • "The report is slow." What exactly is slow?
    • Opening or interacting with a page?
      • Performance Analyzer: is the time in "DAX query"?
        • → One or two visuals dominate: DAX performance, then Model performance.
      • In "Visual display" and "Other"?
        • → Report performance: too many visuals, heavy visuals, slicers.
      • Is the model DirectQuery or composite?
        • → DirectQuery: source SQL, network and gateway.
    • Refresh takes too long or fails?
      • → Refresh: folding, partitions, incremental refresh, gateway, parallelism.
    • Everything is slow at certain times for everyone?
      • → Capacity: throttling and concurrency.

Report performance

Budget: aim for every page to render in a few seconds on the target capacity, and write the budget down so regressions are visible (performance budgets).

DAX performance

The engine has two parts: the storage engine (SE: fast, multi-threaded, scans compressed columns, caches results) and the formula engine (FE: single-threaded, handles everything the SE can't). Fast queries do most of their work in the SE.

In DAX Studio, run the query with Server Timings on and a cleared cache:

What you seeLikely causeFix
High FE time, few SE queriesComplex logic per row in the FE, large iterationsIterate a smaller table (a dimension or VALUES of a column); pre-compute in a column or upstream
Many SE queries (dozens or hundreds)Context transition inside an iterator over many rowsReduce the iterator's cardinality; replace measure calls inside iterators with columns; use variables
SE queries with CallbackDataIDThe SE calls back into the FE per row (for example IF, DIVIDE inside an iterator)Move the condition out of the iterator, or filter first and aggregate after
Huge materialisations (millions of rows returned to the FE)FILTER over a fact table, SUMMARIZE with many columnsFilter columns not tables; KEEPFILTERS; SUMMARIZECOLUMNS
The same expression computed repeatedlyNo variablesVAR it once

Senior: Don't optimise a measure until you've confirmed it is the bottleneck. A clean, readable measure that costs 50 ms isn't worth making clever.

Model performance

DirectQuery

Every visual becomes SQL against the source, every time.

Refresh

Capacity

Prove it

A performance fix is finished when you can show the before and after under the same conditions, the regression check that will catch it next time, and the budget it now meets. Use the performance review checklist.

Something missing or out of date? Open an issue or edit content/toolkit/guides/performance-clinic.md.