Every visual on a report page fires a DAX query. When twenty visuals queue behind a parallelism cap, the bottleneck hides in Performance Analyzer's least-read column — and the escape hatch everyone reaches for can silently produce wrong numbers.
Episode 28 • 2026-07-10 Duration: 10:31
Every visual on a report page fires a DAX query. When twenty visuals queue behind a parallelism cap, the bottleneck hides in Performance Analyzer's least-read column — and the escape hatch everyone reaches for can silently produce wrong numbers.
AI-generated voices. Matthias — cloned voice. Fabia — designed AI co-host. See Matthias live on YouTube (Fabric Friday), at his meetups, and at conferences like FabCon.
Hosted by Matthias Falland — Microsoft Data Platform MVP and community architect behind the Fabric Periodic Table. New episodes every Friday.
Have an architecture decision you are wrestling with? DM Matthias on LinkedIn — find him as Matthias Falland. Three to five sentences about the decision, your team size, and your current stack. We anonymize before airing.
This podcast was generated by AI.
Brand design based on fabricperiodictable.com.
Architecture decisions for Microsoft Fabric. Anonymized real customer scenarios, cost realism, counter-arguments included. Weekly episodes aligned with Fabric Friday recordings.
Speaker 1
Every visual on a Power BI report page fires at least one DEX query. Put 20 visuals on a page, you've just scheduled 20 queries
Speaker 2
And there's a parallelism cap. Beyond it, they queue. Serially.
Speaker 1
20 visuals at 300 milliseconds each, and the page takes 12 seconds. Architecture decisions for Microsoft Fabric. This is the Fabric Architecture Podcast.
Speaker 2
Today we're pulling apart what actually happens when a report page opens in Fabric. Because the slowest pages have no slow visuals. The bottleneck hides in a place almost nobody looks. When a page loads, every visual sends its DAX query to the semantic model. The model resolves each one according to storage mode. Import answers from the VertiPak cache. Direct lake pages columns in from Delta Parquet on one lake. Direct query translates to SQL and hits the source. But query parallelism is finite. It's governed by the environment and by the model's maximum connections per data source setting. Once you exceed that limit, visuals stop running in parallel and start waiting in line.
Speaker 1
Where does that waiting time show up?
Speaker 2
Performance Analyzer breaks every visual into four main buckets, DAX query, Direct Query, Visual Display, and Other, plus a preview category for field parameters. The other bucket includes, and I'm quoting the docs here, time spent cued while other operations complete. That's the waiting time, and it's the bucket almost nobody reads
Speaker 1
Because everyone's staring at Dax query.
Speaker 2
And they'll tune a measure from 280 milliseconds down to 200, and the page still takes 9 seconds. High other plus low DAX means the page has too many visuals contending for a limited query budget. No amount of DAX tuning fixes that.
Speaker 1
The only fix is subtraction, and that's the conversation nobody wants to have. I keep running into pages with 15, 18 visuals because someone treated the report like a dashboard. Everything visible, no clicks required. But every one of those visuals is a query and they are all fighting for the same parallelism budget. At some point you have to look a stakeholder in the eye and say, this page has too many questions on it. Pick the five that matter.
Speaker 2
Microsoft frames drill-through pages and report page tooltips explicitly as performance tools. A tooltip page's visuals don't query until the user hovers. A drill-through page doesn't query until the user drills. You're moving cost from always for everyone to on-demand for the one person who asked.
Speaker 1
Most report authors I work with aren't thinking about query cost at design time. They're thinking about layout.
Speaker 2
Which is why the Optimize ribbon has presets. The default is interactivity, cross-highlighting real-time slicers Query reduction turns that off and adds an apply button. And the docs say explicitly, set it when you first create the report, not six months later. Users who've had cross-filtering for six months experience its removal as a regression, even when the page gets three times faster.
Speaker 1
That's a change management problem wearing a technical hat.
Speaker 2
We got a question from the Fabric Community Forums that goes straight to the heart of this. How to display full data of the tables when exporting Power BI report as PDF?
Speaker 1
This one shows up constantly and the answer is the part nobody wants to hear.
Speaker 2
A table visual uses data windowing. It fetches 500 rows at a time as you scroll. The visual doesn't hold the rest. It never requested them. When you export to PDF, the renderer captures what the canvas actually rendered, and it can't produce rows the visual never fetched. Microsoft's own language is unambiguous. The only rows that print are the ones you saw on the page
Speaker 1
There's no setting for this. If every row has to be in that PDF, you need a paginated report authored in Report Builder. And the genuinely expensive part isn't learning a new tool. It's discovering this constraint at delivery time after you've spent three weeks building the report. That's the cost I'd want people to internalize. Budget for the format decision before you budget for the layout.
Speaker 2
And here's where the comfortable picture stops holding People hear paginated report and think, fine, I'll embed a paginated report visual inside my Power BI report. Best of both worlds. Except that Visual inherits a 30,000-value data reduction limit, and the documentation is unusually direct about what happens past that threshold.
Speaker 1
Wait, does it truncate?
Speaker 2
It drops rows that feed report parameters, and then renders a confident, well-formatted, wrong result. The docs say incorrect results. Every other data reduction in Power BI gives you visibly incomplete output. You scroll and there's nothing more. The charts obviously sampled. This one silently drops data and produces a document that looks perfect.
Speaker 1
The one you'd hand to a CFO. The paginated output looks more authoritative than the interactive report and it's carrying the wrong numbers.
Speaker 2
Now layer direct leak into this picture. If your report sits on a Direct Lake semantic model, performance depends on whether the model actually stays in Direct Lake mode. There's a list of fallback triggers, SQL views backing a table, RLS or dynamic data masking defined at the SQL Analytics endpoint. guardrail breaches, or a model that hasn't been framed since the Delta tables changed. And the critical sentence from the docs A single table that exceeds any guardrail limit prevents direct lake mode for the entire model. One table, whole model. And I'm talking DirectLake on SQL here, where it quietly drops to DirectQuery. On the One Lake variant, the recommended one now, it doesn't drop at all. The refresh just fails.
Speaker 1
And the guardrails aren't linear across SKUs. What's the shape?
Speaker 2
F2 through F32 all cap at 300 million rows per table and a 10 to 40GB model size limit F64 jumps to 1. 5 billion rows per table and removes the size cap entirely. That's a five times increase in row capacity at one skew step. For a Direct Lake reporting estate, that single jump is the most consequential capacity decision you'll make. And the report author never sees it. They just see slow pages and get the blame.
Speaker 1
Three layers of indirection between the symptom and the fix. The report author gets blamed, the model is the cause, the SKU is the constraint. So what do I actually run to prove it's not my report?
Speaker 2
Evaluate table traits in DAX Query View. The Direct Lake Fallback Info column names the fallback reason per table. None means healthy Direct Lake. During development, set direct lake behavior to direct lake only. Then fallback throws an error instead of hiding.
Speaker 1
I want to push back on the whole thin reports shared models frame for a moment. There are situations where a local PBIX with its own model is the right call, and I think we'd be dishonest not to say so.
Speaker 2
If it's a one-off analysis, testing a hypothesis, data's in a CSV, the results a screenshot in a Teams thread that lives for a day? Wiring up a shared semantic model is overhead that buys you nothing. The consistency guarantee matters when 12 reports have to agree on revenue. For one report, one author, disposable output, local is completely fine.
Speaker 1
And prototyping I'd rather someone build a fat PBIX to prove the concept than refactor into model and report once they know what questions they're actually answering. Starting with the architecture before you know the question is how you end up with a beautiful semantic model that answers nothing anyone needs. That's a judgment call I'll always lean toward. Prove the value first, then govern it.
Speaker 2
Show me the query pattern. That's what this comes down to. A report is a query generator. Every visual is a query. Every page load is a fan out. And every performance problem lives in the gap between what the author designed and what the engine actually executed.
Speaker 1
The piece I take away is that the report author and the model owner have to be in the same conversation. I trade a week of DAX tuning for one meeting where the model owner walks the report author through what's hidden and why, what the measures assume, and where the guardrails sit. That meeting prevents more production fires than any amount of performance analyzer recordings after the fact
Speaker 2
Somewhere right now, a report page is loading 20 visuals, queuing 12 of them behind a parallelism cap, and the person watching the spinner is about to open performance analyzer, see a DAX number. And start tuning the wrong thing.
Speaker 1
If that sounds familiar, DM me on LinkedIn. Find me as Matthias Falland.
Speaker 2
Check the other bucket first.
Speaker 1
Always the other bucket. This was the Fabric Architecture Podcast. This podcast was generated by AI. New episode every Friday.