Skip to main content

Slow Power BI reports have five possible locations, measured in order: visual rendering, the DAX query, the model shape, the source or gateway, then capacity. Guessing costs weeks. EPC Group is a Houston-based Microsoft consulting firm operating since 1997, with six Microsoft Solutions Partner designations and 216+ M&A tenant migrations covering 1.83M users. This is the triage sequence we run.

Key Facts

  • Fabric smooths interactive operations over five to 64 minutes and background operations over a full 24 hours — which is why a report is slow long after the job that caused it finished.
  • Throttling has four published stages, keyed to future capacity consumed: overage protection to 10 minutes, 20-second interactive delay from 10 to 60 minutes, interactive rejection from 60 minutes to 24 hours, total rejection beyond 24 hours.
  • Fabric evaluates capacity in 30-second timepoints — 2,880 in 24 hours. The Capacity Metrics app uses the same period, so its numbers and platform behaviour agree.
  • DirectQuery enforces a one-million-row return limit and a 225-second query timeout. Reports exceeding either fail rather than degrade.
  • A single semantic model on F64 is capped at 25 GB; F128 is 50 GB, F256 is 100 GB, F512 is 200 GB.
  • The gateway has a concurrency limit of 30 refreshes. Past it, the fix is another cluster member, not a bigger machine.
  • Best Practice Analyzer checks 60+ rules across performance, DAX expressions, error prevention, maintenance, and formatting — and now runs in a Fabric notebook against a published model, alongside Memory Analyzer.

Last updated by Errin O'Connor, Founder & Chief AI Architect, EPC Group

Slow Power BI reports have five possible locations, measured in order: visual rendering, the DAX query, the model shape, the source or gateway, then capacity. Guessing costs weeks. EPC Group is a Houston-based Microsoft consulting firm operating since 1997, with six Microsoft Solutions Partner designations and 216+ M&A tenant migrations covering 1.83M users. This is the triage sequence we run.

Last updated: 2026-07-31

Key facts

Quick facts

QuestionAnswer
First tool to openPower BI Desktop Performance Analyzer
What it splitsDAX query time vs visual display time vs "other"
Second toolDAX Studio — server timings, and VertiPaq Analyzer for model size by column
Model audit toolBest Practice Analyzer (Tabular Editor, or a Fabric notebook)
Capacity toolMicrosoft Fabric Capacity Metrics app
Power Query toolQuery Diagnostics, plus Diagnose Step
Throttle stage that delays users 20 seconds10 to 60 minutes of future capacity consumed
Throttle stage that rejects user queries60 minutes to 24 hours
Interactive smoothing window5 to 64 minutes
Background smoothing window24 hours
Can Performance Analyzer measure capacity?No — it runs in Desktop and cannot monitor PPU activity or capacity

The Five-Probe Latency Trace

Every slow-report engagement EPC Group runs follows the same order, and the order is the method: each probe has a stop condition, and you do not advance until it is clean. Tuning DAX on a throttled capacity is wasted work; scaling capacity to fix a bi-directional filter is wasted money.

Probe 1 — Visual. In Power BI Desktop, start Performance Analyzer, refresh visuals, and read the split for the slowest one: DAX query duration versus visual display duration. Stop condition: if display time dominates, the problem is the page, not the data. Cut the number of visuals, apply a Top N filter to tables so they stop materialising every row, move detail to drillthrough pages and page tooltips, and time every custom visual on its own. Advance only when query time dominates.

Probe 2 — Query. Take the DAX Performance Analyzer hands you and run it in DAX Studio with server timings on. Look at the storage-engine versus formula-engine split and the number of storage engine queries. Stop condition: a single expensive measure. Usual culprits: iterators that test every row, unnecessary filter-context overrides, and TopN or measure filters that force two source queries. For DirectQuery, Performance Analyzer also surfaces native SQL for SQL Server, Oracle, and Teradata.

Probe 3 — Model. Run Best Practice Analyzer and Memory Analyzer against the model. BPA carries 60+ rules across five categories; Memory Analyzer returns size by table, column, hierarchy, partition, and relationship. Stop condition: you can name the three highest-memory columns and justify each. You are hunting high-cardinality columns, wide fact tables, and calculated columns that belong upstream. Export a .vpax for a second opinion — structure and size, no data.

Probe 4 — Source and gateway. For Import, use Query Diagnostics and Diagnose Step to confirm folding. For gateway-bound sources, enable performance logging and load the diagnostics template. Stop condition: folding confirmed, spool storage not full, gateway co-located with source and tenant. Latency here is invisible in every other probe.

Probe 5 — Capacity. Open the Fabric Capacity Metrics app and read three tabs in order: Utilization (over the SKU line?), Throttling (which stage?), Overages (add, burndown, cumulative, minutes-to-burndown). Stop condition: utilisation above 100% does not by itself mean throttling — bursting is a feature. Only the Throttling tab shows whether users are being hurt.

The order matters. Probes 1–4 reduce the CU cost of the workload; probe 5 tells you whether capacity can carry what is left. Run probe 5 first and you will buy a bigger SKU to host an unoptimised model, then be back in six months.

Symptom, cause, fix

SymptomRoot causeFix
Every visual on one page is slow; single visuals are fast aloneToo many visuals per page, each issuing its own queryReduce visuals per page; move detail to drillthrough pages and page tooltips
One table visual dominates load timeUnfiltered table materialising millions of rows into memory at every refreshApply a Top N filter (set generously, e.g. 10,000); the user experience is unchanged and memory use drops sharply
Slow only in the Power BI service, fast in DesktopDesktop environment differs — data volume, concurrency, storage mode, gateway, capacity, query cachingRe-test in the service; treat Desktop timings as a lower bound only
Slow every morning, fine by afternoonOvernight background jobs smoothed over 24 hours are still consuming future capacityRe-schedule refreshes; check the Overages tab for minutes-to-burndown
Users see a consistent ~20-second lagInteractive delay stage: 10 to 60 minutes of future capacity consumedReduce interactive load or scale the SKU; a bounded overage limit prevents the delay
Queries rejected with "try again later"Interactive rejection stage: 60 minutes to 24 hours of future capacity consumedScale up on demand; investigate the burst in the Metrics app timepoint drill-through
Scheduled refreshes silently stop runningBackground rejection stage (beyond 24 hours), or surge protection rejecting background jobsConfirm which in the Throttling tab; surge protection rejects background work by design to protect interactive users
Refresh fails with out-of-memoryFull refresh of a model too large for the SKU's per-model memory limitUse incremental refresh; reduce refresh parallelism; remove high-cardinality columns; consider scale-out or a larger SKU
Refresh takes hours and grows every monthFull refresh of a fact table with no partitioning policyConfigure incremental refresh with RangeStart/RangeEnd; add Detect data changes to skip unchanged periods
Incremental refresh configured but still slowFilter not folding to the source; the mashup engine is filtering locally after pulling everythingVerify query folding with step folding indicators; ensure the RangeStart/RangeEnd filter immediately follows the source step
Slicers are sluggish and show odd "with data" behaviourBi-directional cross-filter relationships expanding filter context across the modelSet relationships to single direction; enable bi-directional behaviour only inside a measure using CROSSFILTER
Model far larger than the source dataHigh-cardinality columns, unused columns, calculated columns computed at loadDrop unused columns; reduce cardinality; move calculated columns and tables upstream into ETL
DirectQuery report fails rather than slowsOne-million-row return limit or the 225-second query timeout exceededAggregate at source; add user-defined aggregations; reduce cardinality in TopN and measure filters
DirectQuery visuals each fire two source queriesMeasure filters and TopN filters resolve in two passesPre-aggregate at source; avoid TopN over very high-cardinality columns
Direct Lake report suddenly slower with no changeDirect Lake on SQL fell back to DirectQuery — a SQL view, SQL-based RLS, or a guardrail exceededOptimise the Delta tables to fit SKU guardrails; materialise views; or use Direct Lake on OneLake, which does not fall back
Refresh error: "too many refreshes occurring concurrently"Gateway concurrency limit of 30 reachedAdd gateway cluster members or a second cluster; monitor with the gateway diagnostics template
Interactive reports degrade during refresh windowsImport refreshes and DirectQuery traffic share one gatewayRun separate gateways for Import and for DirectQuery/live connection models
Paginated report fails on exportData volume limits exceeded — roughly 1,000,000 rows at 15–20 columns in the standard execution environment, double in the optimised oneAggregate in the dataset query rather than in the report; export to CSV rather than Excel for data dumps

Model shape: where the time actually goes

Report tuning has a ceiling set by model design. Four decisions set it.

Star schema is the target, not a preference. Dimensions filter and group; facts summarise. Relationship cardinality sets the role — the "one" side is always a dimension, the "many" always a fact. Every visual generates a query that filters, groups, and summarises, so dimensions-plus-facts is the shape the engine already queries. Avoid half-dimension, half-fact tables, and load facts at a consistent grain.

Cardinality is the memory bill. The columnar engine compresses repeated values well and unique values badly. A transaction ID or full timestamp in a fact table can outweigh every other column combined. Memory Analyzer ranks them. Split timestamps into date and time, drop unused surrogate keys, and remove columns nobody has put on a visual.

Bi-directional filters are a last resort. Microsoft's guidance is to minimise them — they degrade query performance and confuse slicer behaviour. Three cases are legitimate: one-to-one, which has no alternative; many-to-many via a bridging table; and dimension-to-dimension analysis through a fact table acting as a bridge. Even then, leave the relationship single-directional and switch filtering on inside the measure with CROSSFILTER, so the cost is paid only where needed.

Calculated columns are usually in the wrong place. A calculated column computes at load and is stored, consuming memory forever; a measure computes at query time and stores nothing. Where a value must be materialised, push it upstream into ETL or the warehouse. Microsoft's out-of-memory remediation guidance names exactly this as a primary mitigation.

Storage mode decision matrix

ConsiderDirect Lake on OneLakeDirect Lake on SQLImportDirectQuery
Query engineVertiPaqVertiPaqVertiPaqFederated to source
Licence requiredFabric capacity onlyFabric capacity onlyAny Power BI or Fabric licenceAny Power BI or Fabric licence
"Refresh" meansFraming — metadata only, secondsFraming — metadata only, secondsFull data copy; minutes to hoursNo refresh; every query hits the source
Falls back to DirectQueryNo — fails instead if guardrails exceededYes — for SQL views, SQL RLS, guardrail breachesn/an/a
Composite modelsYesNoYesYes
Calculated columnsPreview, unmaterialisedNoYesYes
Hard limits to watchPer-SKU rows, parquet files, row groups, model sizeSame, plus fallback behaviourPer-SKU model memory; refresh duration1M row return limit; 225-second timeout
Choose whenLarge Delta data in OneLake, freshness matters, no silent fallback wantedYou need SQL endpoint security (RLS/CLS/OLS) and can accept fallbackSelf-service agility, modest volumes, Power Query needed, no Fabric capacitySource is authoritative, data must be live, volumes small per query
Do not choose whenYou depend on SQL-endpoint RLS or non-materialised viewsSilent performance regressions are unacceptableModel would exceed SKU memory, or refresh exceeds the windowConcurrency is high or visuals numerous — each re-queries the source

Direct Lake guardrails scale with the SKU. On F64: 5,000 parquet files per table, 5,000 row groups, 1,500 million rows, 25 GB memory. On F256: 6,000 million rows and 100 GB. Optimise the Delta tables before scaling the SKU — guardrails are per query except model size on disk, which is evaluated at model level and affects every query.

The capacity side

Report tuning reduces demand. Capacity mechanics determine what happens to what remains.

Bursting lets an operation temporarily consume more compute than the SKU provides, so users get fast results and a smaller capacity can run larger jobs. Smoothing then averages that consumption forward — interactive across five to 64 minutes, background across a full 24 hours. Together they make a capacity self-managing under spikes, and they are why cause and effect are separated in time: the refresh that broke this morning ran last night.

Throttling starts only once smoothed usage has consumed all available future capacity. The stages are published and worth memorising:

Future capacity consumedStageWhat users experience
Up to 10 minutesOverage protectionNothing — this is designed headroom
10 to 60 minutesInteractive delayNew interactive operations delayed 20 seconds at submission
60 minutes to 24 hoursInteractive rejectionUser-requested queries rejected; background jobs still run
Beyond 24 hoursBackground rejectionAll requests rejected until consumed CU are paid off

Two workload exceptions matter. Real-Time Intelligence skips the 20-second delay stage and only throttles at the rejection phase, because a delayed real-time query is a broken one. Almost all Warehouse operations are classified as background so 24-hour smoothing absorbs their peaks — meaning a slow warehouse query may never appear in your interactive throttling charts.

Surge protection limits background compute when 24-hour usage is high, prioritising user-facing queries. Read the tradeoff plainly: it rejects background jobs, those jobs may need re-running, and many operations you think of as interactive — SQL queries among them — are classified as background and can be rejected too. Microsoft is explicit that it is not a substitute for correct sizing.

Scaling down is measured, not guessed. Microsoft's worked example: to move from F128 to F64 safely, smoothed usage on the F128 should sit below 40%, because it roughly doubles to 80% at half the size. Apply the same arithmetic in reverse before scaling up.

What changed in 2026

Where EPC Group fits

EPC Group has tuned Microsoft BI estates since 1997, six Microsoft Solutions Partner designations, 11,000+ engagements, consecutive G2 Leader quarters. Errin O'Connor, Founder and Chief AI Architect, is a 4× bestselling author. Multiple models. One truth.

Related reading: Power BI consulting services · Power BI gateway · Power BI Premium · Premium vs Premium Per User · Power BI cost and licensing guide · Running totals and cumulative sums in DAX · Power BI GROUPBY · Power BI conditional formatting · Microsoft Fabric consulting services · Fabric consulting guide · Microsoft Fabric vs Databricks · Snowflake to Fabric migration · Epic and Cerner Power BI integration · Data governance consulting · Power BI consulting in Houston · Power BI consulting in Phoenix · Microsoft Frontier Company · EPC Group.

Run the trace on your slowest report

Send the report name, the SKU it runs on, and a Performance Analyzer export. EPC Group returns the probe at which it fails, the cause, and the fix — before anyone proposes a bigger capacity. Start at Power BI consulting services.

Frequently asked questions

What is the first thing to check on a slow Power BI report?

Open the report in Power BI Desktop and run Performance Analyzer. It separates DAX query duration from visual display duration for every element on the page. If display time dominates, the problem is report design. If query time dominates, the problem is the DAX or the model. Checking capacity first wastes time on most engagements.

Why is my report slow in the Power BI service but fast in Desktop?

Performance Analyzer runs on the modeller's machine, so it does not reflect service conditions. Data volume, concurrent user count, table storage modes, gateway involvement, whether a capacity is involved, query caching, and query parallelisation all differ. Treat Desktop timings as a best case and re-measure in the service.

Does capacity utilisation over 100% mean my reports are throttled?

No. Fabric uses bursting deliberately, so exceeding 100% utilisation is expected and often harmless. Throttling only begins once smoothed usage has consumed all available future capacity. Use the Throttling tab in the Fabric Capacity Metrics app, not the Utilization tab, to determine whether users are being affected.

What are the Fabric throttling stages?

There are four. Up to 10 minutes of future capacity consumed is overage protection with no user impact. From 10 to 60 minutes, new interactive operations are delayed 20 seconds. From 60 minutes to 24 hours, interactive operations are rejected while background jobs continue. Beyond 24 hours, all requests are rejected until the consumed capacity units are paid off.

Why is my report slow in the morning when nothing changed?

Background operations are smoothed over a 24-hour window, so an overnight refresh or Spark job is still consuming future capacity hours later. Interactive operations smooth over five to 64 minutes. Check the Overages tab in the Capacity Metrics app for cumulative carryforward and minutes-to-burndown, then reschedule the offending jobs.

Should I use bi-directional relationships in Power BI?

Minimise them. They degrade query performance and can produce confusing slicer behaviour. Three cases justify them: one-to-one relationships, many-to-many through a bridging table, and dimension-to-dimension analysis. Even then, prefer leaving the relationship single-directional and enabling both directions inside a specific measure using the CROSSFILTER function.

Should I use calculated columns or measures?

Prefer measures. A calculated column is computed at load and stored in the model, consuming memory permanently. A measure is computed at query time and stores nothing. Where a value must be materialised, push it upstream into ETL or the data warehouse. Microsoft names moving calculated tables and columns to the data source as a primary fix for out-of-memory refresh failures.

When should I use Direct Lake instead of Import?

Use Direct Lake when large volumes of Delta-format data already sit in OneLake and copying them into an Import model is impractical. Direct Lake refresh copies metadata only, which takes seconds rather than the minutes or hours a full Import refresh needs. Import remains the right choice for self-service work, Power Query transformation, and models without Fabric capacity.

Why did my Direct Lake report get slower without any change?

Direct Lake on SQL endpoints falls back to DirectQuery when it cannot load data directly — for example against a non-materialised SQL view, with SQL-based row-level security, or when a per-SKU guardrail is exceeded. Queries still return results but perform like DirectQuery. Direct Lake on OneLake does not fall back; it fails instead.

What are the DirectQuery limits I should design around?

DirectQuery returns a maximum of one million rows and enforces a 225-second query timeout. Measure filters and TopN filters cause two queries to be sent to the source rather than one, and TopN retrieves all candidate values before ranking. Design aggregations at the source and avoid TopN over very high-cardinality columns.

How do I fix a Power BI refresh that runs out of memory?

Refresh only the partitions or tables that changed by configuring incremental refresh. Reduce refresh parallelism when processing multiple partitions of the same model. Reduce model complexity by removing unnecessary columns and rows, cutting high-cardinality columns, and moving calculated tables and columns upstream. Then consider semantic model scale-out or a larger F SKU.

Sources and verification

  1. Microsoft Learn — Troubleshoot report performance in Power BI (triage flowchart, six terminators)
  2. Microsoft Learn — Monitor report performance in Power BI (Performance Analyzer, Query Diagnostics, SQL Server Profiler)
  3. Microsoft Learn — Optimization guide for Power BI (visual count, Top N filter guidance, custom visual evaluation)
  4. Microsoft Learn — The Fabric throttling policy (bursting, smoothing windows, four throttle stages, workload exceptions)
  5. Microsoft Learn — Compute page in the Fabric Capacity Metrics app (Utilization, Throttling, Overages tabs; 30-second timepoints)
  6. Microsoft Learn — Plan your capacity size (CU per SKU, 30-second evaluation period)
  7. Microsoft Learn — Microsoft Fabric capacity planning guide: manage growth and governance (surge protection tradeoffs, F128→F64 scale-down arithmetic)
  8. Microsoft Learn — Understand star schema and the importance for Power BI
  9. Microsoft Learn — Bi-directional relationship guidance (minimise use; CROSSFILTER alternative)
  10. Microsoft Learn — Direct Lake overview (storage mode comparison, per-SKU guardrails, DirectQuery fallback behaviour)
  11. Microsoft Learn — Develop Direct Lake semantic models (choosing Direct Lake on OneLake vs on SQL)
  12. Microsoft Learn — DirectQuery model guidance in Power BI Desktop (measure filters, TopN filters, source optimisation)
  13. Microsoft Learn — Data refresh in Power BI (1M row limit, 225-second timeout, gateway separation, refresh scheduling)
  14. Microsoft Learn — Troubleshoot refresh scenarios (out-of-memory mitigations, scale-out, parallelism)
  15. Microsoft Learn — Configure incremental refresh and real-time data for Power BI semantic models
  16. Microsoft Learn — Monitor and optimize on-premises data gateway performance (diagnostics template, spool storage, streaming)
  17. Microsoft Learn — Troubleshoot the on-premises data gateway (30 concurrent refresh limit)
  18. Microsoft Learn — Guidance for deploying a data gateway for the Power BI service (location, latency, sizing)
  19. Microsoft Learn — External tools in Power BI Desktop (DAX Studio, Tabular Editor, ALM Toolkit)
  20. Microsoft Learn — Use notebooks with a semantic model (Best Practice Analyzer 60+ rules, Memory Analyzer)
  21. Microsoft Learn — Power BI implementation planning: data-level auditing (VertiPaq Analyzer, .vpax export, BPA rules on GitHub)
  22. Microsoft Learn — Performance and scalability considerations for paginated reports (1,000,000 rows at 15–20 columns; doubled in optimised environment)
  23. Microsoft Learn — Capacity and SKUs in Power BI embedded analytics (per-SKU semantic model memory, 25 GB on F64)
  24. Microsoft Learn — Capacity overage (preview) in Microsoft Fabric (3× pay-as-you-go billing, rolling 24-hour limit)

AI assistant — not human