The bottom line
A Power BI report that got slower after a Fabric migration almost never has an exotic cause. Most common is silent DirectQuery fallback — a Direct Lake on SQL model reverting to DirectQuery because of a SQL view, SQL-based row-level security, or a breached guardrail, with no error to tell you. Then capacity throttling (the query is fast, the user is delayed), cold-cache transcoding after framing, small-file Delta tables with V-Order off by default, and per-SKU guardrail breaches. Work the cheap checks first: Performance Analyzer for fallback, the Capacity Metrics Throttling tab, repeat the visual for cold cache, count files and rows against your SKU. If all clear, it is model or report design.
In This Article
It loaded in two seconds on Import — and now it does not
Your operations report refreshed at 06:00, the plant managers opened it at 07:00, and nobody complained. You migrated to a Direct Lake semantic model, and now it is slower. That feels like a broken promise, because Direct Lake was sold on performance: Import-mode speed with DirectQuery freshness, no refresh window, no 2 a.m. failures.
Resist the urge to blame Direct Lake for two weeks. A slow Direct Lake report almost never has an exotic cause — it has one of about seven ordinary ones, and they are cheap to check in order.
The diagnostic order — cheap checks first
| Check | Tool | Rules out |
|---|---|---|
| Is the query falling back to DirectQuery? | Performance Analyzer in Power BI Desktop | Fallback |
| Is the capacity throttling? | Capacity Metrics app, Throttling tab | Delay vs slowness |
| Slow only on the first query after a refresh? | Repeat the same visual twice | Cold cache |
| How many Parquet files / row groups per table? | Lakehouse table properties / Delta log | Small-file fragmentation |
| Is V-Order on for the tables the model reads? | Table properties | Compression/transcoding cost |
| Are you inside the SKU guardrails? | Row, file, model-size counts vs the SKU table | Guardrail breach |
| What did the model inherit from Import? | Best Practice Analyzer, relationship view | Model design |
| How many visuals on the slow page? | Count them | Report design |
Each check either eliminates a cause or gives you the fix. Do not read the query plan until you have ruled out fallback and throttling.
Cause 1: DirectQuery fallback — the report is not on Direct Lake at all
Direct Lake on SQL endpoints fall back to DirectQuery when they cannot read the Delta table directly — most often a non-materialised SQL view, SQL-based row-level security, object-level security, dynamic data masking, or a table over guardrail. Nothing tells you it happened: the report still works, nobody sees an error, it is simply slower, and the team concludes Direct Lake is overrated.
Views and RLS catch migrating teams hardest, because teams carrying a SQL Server or SAP extract habit build a curated view layer — vw_Sales_Fact, vw_Inventory_Position — because that is how they have always presented data to Power BI. Detect it in Power BI Desktop: View → Performance Analyzer → Start recording → Refresh visuals, then expand the slow visual and look for a "Direct query" metric. Fix it by materialising those views into Delta tables (or setting the model's Direct Lake behaviour to DirectLakeOnly in development so fallback surfaces as an error you can find, rather than as silent slowness).
Cause 2: capacity throttling — the report is not slow, it is delayed
This is the most misdiagnosed cause, because the symptom is wrong: the DAX query completes in 400 milliseconds and the user still waits twelve seconds. Fabric smooths interactive operations over 5–64 minutes and background operations over 24 hours, then charges them against future capacity — and once smoothed usage exceeds 10 minutes of future capacity, Fabric delays every interactive job by 20 seconds at submission.
The tell is the shape: a genuine query problem is proportional (heavier visual, slower response), while throttling delays even trivial visuals by a flat amount. In most mid-market estates it is not the report — it is a Spark notebook or a full-reload pipeline on the same capacity, smoothed across 24 hours, eating the headroom the report needs at 08:00. Open the Capacity Metrics app Throttling tab for the hour users complain about; separate engineering from serving onto different capacities.
Causes 3 & 4: cold cache and Delta table health
Direct Lake loads column data from OneLake only when a query first requests that column — transcoding — and on a cold model the engine must merge local Parquet dictionaries into a global VertiPaq dictionary before answering. That is why the CFO at 07:05 waits eleven seconds and everyone at 07:20 waits two: it is the residency state, not randomness. Incremental loads beat overwrites — the Overwrite option erases the Delta log, so Direct Lake cannot use incremental framing and must reload everything; insert-only patterns let dictionaries survive. A warm-up query after the pipeline hides the cost from the first user.
Delta health compounds it: each row group across a table's Parquet files becomes a column segment, and every extra row group is another local dictionary to merge during transcoding — so small files hurt Direct Lake more than a warehouse, where file skipping makes a bad layout survivable. And V-Order is now off by default for new workspaces, a choice that favours ingestion write performance — for Direct Lake serving tables you generally want it on (ALTER TABLE … SET delta.parquet.vorder.enabled = true, then OPTIMIZE … VORDER). Near-real-time ingestion makes this worse; schedule OPTIMIZE on gold tables as a production job.
Cause 5: guardrail breaches for your SKU
Direct Lake enforces per-SKU guardrails on Parquet files, row groups, rows and model size per table.
| SKU | Parquet files / table | Row groups / table | Rows / table | Max memory |
|---|---|---|---|---|
| F2–F8 | 1,000 | 1,000 | 300 million | 3 GB |
| F16 | 1,000 | 1,000 | 300 million | 5 GB |
| F32 | 1,000 | 1,000 | 300 million | 10 GB |
| F64 / P1 | 5,000 | 5,000 | 1.5 billion | 25 GB |
| F128 / P2 | 5,000 | 5,000 | 3 billion | 50 GB |
| F256 / P3 | 5,000 | 5,000 | 6 billion | 100 GB |
The Parquet-file guardrail is the one that catches mid-market estates, and it connects straight to Delta health: an F16 workspace with a fifteen-minute incremental load and no compaction crosses 1,000 files on a busy fact table long before it approaches 300 million rows — at which point Direct Lake on OneLake falls back to DirectQuery. Compaction (OPTIMIZE) is the fix.
Causes 6 & 7: model and report design you carried over
Three model habits transfer badly from Import. Calculated columns and calculated tables that reference Direct Lake columns are not supported on either flavour. Bi-directional relationships that were tolerable in Import cost more here; Microsoft's guidance is to minimise them, especially as their number grows — replace with CROSSFILTER in measures. And high-cardinality columns increase transcoding time, because dictionary merging is the dominant cold-query cost.
And sometimes it is the report, and it always was. A landing page with a dozen KPI cards, four matrices and six slicers was slow on Import too — Import was simply more forgiving under concurrency. Microsoft's guidance is explicit: limit visuals per page to only what is necessary, and use drillthrough pages and tooltips instead of adding more. The fix is editorial: one page, one decision, the fewest visuals that support it.
Symptom to cause to fix
| Symptom | Likely cause | First fix |
|---|---|---|
| Uniformly slow, every visual, all users, all day | DirectQuery fallback | Set DirectLakeOnly, find the trigger, materialise views |
| Slow in blocks matching pipeline schedules; even trivial visuals delayed | Capacity throttling | Capacity Metrics → Throttling tab; separate engineering from serving |
| First user after refresh slow, everyone after fast | Cold cache / framing eviction | Warm-up query post-pipeline; switch overwrite to incremental |
| Gradually degraded over weeks, no code change | Small-file accumulation | OPTIMIZE … VORDER, schedule compaction |
| Fast in dev, slow in prod workspace | V-Order disabled on the newer workspace | Check delta.parquet.vorder.enabled per table |
| One large fact table slow, others fine | Guardrail breach on that table | Count files, row groups, rows against the SKU table |
| Slicer interactions much slower than card refreshes | Bi-directional relationships | Replace with CROSSFILTER in measures |
| Slow on one page only | Too many visuals | Split the page; use drillthrough |
Where this breaks, and what it does not fix
Some estates genuinely were faster on Import, and Import remains a legitimate choice — if your model is under a few GB, data lands once a night, and nobody needs intraday freshness, Import gives you a fully warm VertiPaq cache with no transcoding, framing eviction or fallback surface. This diagnostic will not fix a bad gold layer: if fact tables are wide, unmodelled and joined at query time, no storage mode saves you.
Capacity Metrics is retrospective, so a complaint at 08:10 may not surface for some minutes. The SQL analytics endpoint's metadata sync adds its own lag — normally under a minute, but the background process halts after 15 minutes of inactivity, and a workspace holding many lakehouses increases latency. Fixing fallback can even make freshness worse, since DirectQuery fallback queries always return the latest data. And none of this addresses trust — a fast report nobody believes is not a win.
What to do first
Answer these five this week, before changing anything:
- Run Performance Analyzer on the slowest page — does a "Direct query" metric appear?
- Open the Capacity Metrics Throttling tab for the hour users complain about — is the 10-minute interactive line crossing the delay threshold?
- Refresh the same visual twice — is the second run dramatically faster?
- Count Parquet files and rows on your three largest fact tables against the guardrail table for your SKU
- Check delta.parquet.vorder.enabled on those tables — if the workspace is recent, assume V-Order is off until proven otherwise
If you clear all five and the report is still slow, the problem is model or report design — a rebuild conversation, not a tuning one. Bring the Performance Analyzer output and one Capacity Metrics screenshot to a diagnostic call.
The first two checks resolve most cases: Performance Analyzer for a hidden DirectQuery fallback, and the Capacity Metrics Throttling tab for the hour users complain about. If both are clean and it is still slow, it is design, not tuning. Book a diagnostic with Amit — no slides, no pitch deck, no obligation to proceed. Bring the Performance Analyzer output and one Capacity Metrics screenshot.
Free Assessment
Where does your operation sit on the data maturity curve?
8 questions. 3 minutes. You get a scored breakdown across data infrastructure, analytics readiness, and automation potential — with a specific next step for your industry.