What a Semantic Model Actually Is#

Ask a Power BI author what a report is "built on" and the honest answer is: a semantic model. Reports, dashboards, and apps in Power BI all sit on top of a semantic model, which packages modeled data — often pulled together from several different sources — into one layer that a report can query [S1]. Long-time Power BI users will recognize this under its old name: this is the object that used to be called a "dataset," carried forward with a name that better reflects what it actually holds — a structured, relationship-aware model rather than a raw table export [S1].

That modeling layer isn't a Power BI invention built from scratch. Streaming models are the one carve-out; everything else runs on the same tabular engine that powers Analysis Services [S2]. Borrowing that engine is why a semantic model comes with a full DAX query surface, an XMLA endpoint for programmatic access, configurable security roles, and the same in-memory compression technology, whether the model in question is a five-table departmental report or a multi-terabyte model running on a large Fabric capacity.

That compression engine is also why well-designed Power BI reports feel instant to click through. Rather than hitting a database on every click, a semantic model keeps its data compressed in memory so queries resolve quickly — and unless an administrator turns on the large storage format, that in-memory model is capped at 1 GB [S3]. How that in-memory cache gets populated, how Fabric decides to evict parts of it, and what happens when a query has to reload it are questions the Internals section further down answers directly — and they explain why a model can suddenly feel sluggish on its first query after sitting idle.

Where Models Come From, and Where They Go#

There isn't one single way a semantic model ends up in the Power BI service. A model can arrive by publishing from Power BI Desktop, by uploading an Excel workbook or a CSV straight into the service, by pointing a live connection at an Analysis Services model hosted elsewhere, or by standing up a push or streaming model through the service's own APIs [S2].

One of those creation paths has recently changed shape. As of September 5, 2025, Fabric stopped automatically spinning up a default semantic model every time a lakehouse is created; any default models that already existed by that point were split off from their parent lakehouse and turned into standalone semantic models by November 30, 2025 [S4]. Anyone who has grown used to a lakehouse always shipping with an attached model should not expect that for anything created after that cutover.

A published model rarely stays limited to one report. The same model can back interactive Power BI reports, feed paginated reports, support ad-hoc exploration and DAX querying (including the newer DAX query view), and drive refreshable Excel pivot tables or flat exports through Analyze in Excel [S7].

"No special client handling" — a Direct Lake model is consumed exactly like any other semantic model [S7]

Storage Modes: Where the Data Actually Lives#

Original diagram: Power BI semantic model storage modes across Import, DirectQuery, Composite, and Direct Lake

Power BI Desktop ships with three ways to store a model — Import, DirectQuery, and Composite. The dividing line between them comes down to where the bytes physically sit at the moment a report runs a query: baked into the model ahead of time, or fetched from the source on the spot [S2]. Fabric layers a fourth option on top of these three: Direct Lake.

Import pulls a full copy of the data into the model's own compressed store, so every query is answered from that cache rather than the original source. The tradeoff is freshness: an Import model only shows what it looked like at the last refresh, and someone has to schedule or trigger that refresh for the numbers to move forward [S2]. There's also a structural constraint worth knowing: when an Import model runs in the service, the whole thing has to be resident in memory before it can answer even one query — there's no way to serve a query from a partially loaded model [S2].

DirectQuery takes the opposite approach and leaves the data where it already lives, sending a live query back to the source every time a report needs an answer, which is why the numbers a DirectQuery report shows are always current [S2]. The cost shows up as latency, since each interaction a user makes triggers a fresh round trip to the source system.

Composite models don't force an all-or-nothing choice — they let some tables be Import and others be DirectQuery within the same model, so a team can cache the tables that barely change and leave the fast-moving ones live.

Direct Lake is the mode that only exists in Fabric. Instead of copying data in ahead of time, a Direct Lake model reads columns straight out of the Delta tables sitting in OneLake, pulling them in only as a query actually needs them [S5]. Analysts and report builders don't have to treat it any differently — see the pull-quote above on how transparently it fits into the existing toolchain [S7]. It also plays well with the composite pattern — Direct Lake on OneLake can sit inside a composite model, so a huge, constantly-changing fact table can stay in Direct Lake and skip refresh entirely, while its dimension tables use Import or DirectQuery in that same model [S6].

Both Import and DirectQuery share one operational dependency: if the underlying source isn't reachable over the open internet, either mode needs an on-premises data gateway to bridge the connection [S2]. Power BI can also skip building a model entirely and live-connect straight to a model hosted outside the service, in SQL Server Analysis Services or Azure Analysis Services — the SSAS path needs a gateway, the Azure Analysis Services path doesn't, and either way the person viewing the report has their own identity passed through so the source system can enforce its own permissions [S2].

Note

When an organization already has a mature Analysis Services model backing its warehouse, Microsoft's documented guidance is to connect Power BI to that model live rather than rebuilding the same logic a second time inside Power BI [S2].

The Live-Connection Model: One Model, Many Reports#

When a report is built against an already-published semantic model, it does so through a live connection — meaning the report author can build and edit the report freely without ever modifying the model it's pointed at, and the only permission that gates this is Build access on the model [S7].

It's worth keeping three related-but-different things straight. Live-connecting to a published model, live-editing that model directly inside Power BI Desktop, and authoring a fully local model with its own bundled Import or DirectQuery tables are three distinct working modes, not variations on one [S7]. From a governance standpoint only the first of those — the live connection — keeps a single shared model as the source of truth behind an arbitrary number of reports.

That doesn't mean report authors are stuck with exactly what the shared model exposes. Power BI Desktop lets a report author add a report-level measure to a live-connected report, which creates a calculation that lives only in that report and never gets written back into the shared model [S7].

Row-Level Security#

A row-level security role can work one of two ways: dynamically, where the filter changes based on who is actually viewing the report, or statically, where every user assigned to the role sees the same fixed filter [S2]. Ownership matters for administration too — changing a model's gateway or cloud connection settings is restricted to the model's owner, everyone else only gets a read-only view of those settings, and ownership itself can be transferred through the API, which turns out to matter the day the person who originally owned the model leaves [S2].

How It Works / Best Practices#

Rule: point Power BI at an existing enterprise Analysis Services model with a live connection instead of rebuilding it. Why: Microsoft's own guidance calls out live-connecting to an existing model as the better move whenever the organization already has that data-warehouse modeling investment [S2] — recreating the same star schema and measures a second time in Power BI just gives you two versions of business logic to keep in sync.

text
# Wrong: rebuild the star schema natively in Power BI Desktop
#   -> duplicate dimension/measure logic, two places to maintain DAX

# Right: live-connect to the existing Analysis Services model
Get Data -> Analysis Services -> <ssas-server>\<instance>
Connect live (not Import)
# SSAS: requires an on-premises data gateway
# Azure AS: connects directly, no gateway
# Report user's identity is forwarded to enforce source-level RLS

Rule: keep report-only calculations as report-level measures, don't push them into the shared model. Why: a report-level measure only exists inside the one report it was created in and is never written back to the shared semantic model [S7], so every other report that live-connects to that model keeps a clean measure list instead of inheriting a calculation nobody else needs.

dax
// Wrong: added directly to the shared semantic model
// pollutes the measure list for every report that live-connects
[Regional Bonus Target] = [Total Sales] * 0.02

// Right: defined as a report-level measure inside the
// live-connected report that actually needs it
[Regional Bonus Target] :=
    [Total Sales] * 0.02

Rule: switch on the large semantic model storage format up front for any model that XMLA write tools will touch, even a small one. Why: the large format makes a measurable difference to XMLA write performance independent of how big the model currently is [S3] — flip it on only after the model crosses 1 GB and you're retrofitting the setting after Tabular Editor or ALM Toolkit are already wired into a pipeline.

text
# Power BI service -> Semantic model -> Settings -> Large semantic model storage format -> On
# Do this at model creation time if:
#   - Tabular Editor / ALM Toolkit / any XMLA write tool is in the CI pipeline
#   - even if current model size is well under 1 GB

Rule: near the capacity's memory ceiling, scope refreshes narrowly instead of running a full refresh. Why: a model that has grown to roughly half the capacity's total memory — the documented example is a 12 GB model on a 25 GB capacity — can run the capacity out of memory mid-refresh, and the recommended fix is to issue targeted refreshes through the enhanced refresh REST API or the XMLA endpoint rather than a blanket full refresh [S3].

json
// Wrong: trigger a full-model refresh via the classic Refresh API
// on a 12 GB model against a 25 GB capacity
POST /v1.0/myorg/datasets/{id}/refreshes

// Right: use the enhanced refresh API to scope the refresh
// to specific tables/partitions
POST /v1.0/myorg/groups/{groupId}/datasets/{id}/refreshes
{
  "type": "full",
  "objects": [
    { "table": "sales_transactions", "partition": "2026Q2" }
  ]
}

What Goes Wrong#

Triggering a full refresh on a model that's already close to the capacity's memory limit. Once a model's footprint gets close to half of what the capacity offers in total — the 12 GB-on-25-GB scenario above is the specific threshold Microsoft flags — a full refresh carries real risk of exhausting capacity memory partway through [S3]. Buying a bigger SKU isn't the first fix to reach for; scoping the refresh itself, through the enhanced refresh API or XMLA rather than the classic full-refresh call, addresses the actual cause.

Large Semantic Models#

Turning on the large semantic model storage format doesn't just nudge the 1 GB ceiling — it removes it and replaces it with the size of the Fabric capacity itself (or a lower cap an administrator sets), and the feature is available on Fabric F SKUs, Premium P SKUs, Embedded A SKUs, and Premium Per User [S3].

One ceiling doesn't move, though: Power BI Desktop will still refuse to upload anything larger than 10 GB, large format or not. A model only crosses that 10 GB line once it's already living in the service, usually by growing there through an incremental refresh policy over time [S3].

Warning

Turning on the large format has a one-way consequence: a workspace that contains a large-format model can no longer move to a capacity in a different region. And the large format is off-limits entirely for push semantic models [S3].

Internals#

Architecture & design#

Structurally, a semantic model is the layer that sits between raw source data and every surface that consumes it — reports, paginated reports, DAX queries, Excel — as one governed model built on the Analysis Services tabular engine [S2]. Which storage mode a model uses decides where the actual bytes live: fully cached inside the model for Import, left at the source for DirectQuery, split across both for Composite, or — for Direct Lake — pulled straight from Delta tables in OneLake as queries need them [S5].

Composite modeling is what makes it possible for one model to use more than one of those storage strategies at the same time, table by table. Because Direct Lake on OneLake is explicitly designed to work inside a composite model, an architecture can put its largest, most volatile fact table into Direct Lake — skipping the cost of refreshing it altogether — while its smaller, more stable dimension tables stay on Import or DirectQuery within that same model [S6]. The decision diagram below carries that per-table routing logic further, alongside how row-level security roles get chosen and which capacity levers actually move the needle.

Decision diagram: composite semantic model per-table storage-mode routing (Direct Lake vs DirectQuery vs Import), row-level security dynamic-vs-static role decision, the five capacity-footprint levers, and refresh memory-risk internals with the ~50%-of-capacity OOM zone and XMLA/enhanced-refresh mitigation

Zoom out to the capacity level and five things determine how much of a footprint a model leaves: where it's hosted, which storage mode it uses, whether it depends on a gateway, how much data it imports, and how often (and how) it refreshes [S2] — the same five factors the diagram scores scenario by scenario.

How it works internally#

Flipping on the large-format toggle changes more than a size limit — it swaps the model's underlying service storage from ABF (the Analysis Services backup file format) over to Azure Premium Files, which is also the reason the large-format option is only available in Azure regions where Premium Files storage exists [S3].

Underneath that, a large-format model is chunked into VertiPaq segments of 8 million rows by default — the exact segment size Azure Analysis Services already uses — a size Microsoft chose to balance how much memory a segment consumes against how fast it can be queried, and to keep behavior predictable for anyone migrating a model over from Azure Analysis Services [S3].

Capacity memory isn't a fixed allocation per model; it's actively managed. Fabric will evict a semantic model from capacity memory once it goes inactive, which is how the combined size of every model assigned to a capacity can add up to well more than that capacity's memory limit — though any single model is still capped by the SKU's own memory ceiling, and pulling an evicted model back into memory adds a noticeable delay to whatever query triggered the reload [S3]. On-demand load, which is turned on automatically for large-format models, softens that penalty by pulling in only the specific data pages a query actually touches instead of reloading the entire model from scratch, so a model that was evicted comes back to answering queries much faster than a full cold reload would take [S3].

That page-level behavior is also something you can observe directly. When on-demand load is active, querying the DISCOVER_STORAGE_TABLE_COLUMN_SEGMENTS dynamic management view returns per-column Temperature and Last Accessed figures, which show exactly which columns have actually been pulled into memory and how the model is genuinely being queried in practice [S3]. That same set of DMVs can size a model's memory footprint: add up DICTIONARY_SIZE from DISCOVER_STORAGE_TABLE_COLUMNS and USED_SIZE from DISCOVER_STORAGE_TABLE_COLUMN_SEGMENTS, run over the XMLA endpoint from SQL Server Management Studio [S3].

Performance characteristics#

Coming soon — this depth isn't in the knowledge base yet. It needs an L4/L5 source such as a Microsoft engineering blog or benchmark publishing measured VertiPaq query-latency numbers by segment size, on-demand-load column-paging throughput, or eviction/reload timing curves for large-format semantic models. Tracked in content/queue.md.

Worked Example: A Composite Sales Model on a Large Capacity#

Picture a retailer with 50 GB of sales transaction history sitting as Delta tables in OneLake, plus small product, store, and date dimension tables that get refreshed from a warehouse every night. The goal is fast interactive reports, fact data that's never stale, and no refresh that risks blowing up the capacity.

Inference: the steps below are one reasonable way to route each table by its real freshness and size profile, built entirely out of the verified storage-mode and capacity behavior described above — nothing here is a documented Microsoft reference architecture.

text
1. Create the semantic model. Turn on the large storage format immediately,
   since XMLA tools (Tabular Editor) will be used during development  <sup id="cite-3"><a href="#src-3" class="cite">[S3]</a></sup>.

2. Route sales_transactions to Direct Lake mode. Its columns are read
   straight from Delta tables in OneLake on demand -- no import, no
   scheduled refresh  <sup id="cite-5"><a href="#src-5" class="cite">[S5]</a></sup>.

3. Keep dim_product, dim_store, dim_date on Import inside the same
   composite model -- Direct Lake on OneLake is built to mix with
   Import/DirectQuery dimension tables in one model  <sup id="cite-6"><a href="#src-6" class="cite">[S6]</a></sup>.

4. Define two RLS roles: a dynamic role that filters by the signed-in
   user's region, and a static role for the board report that always
   sees national totals  <sup id="cite-2"><a href="#src-2" class="cite">[S2]</a></sup>.

5. Publish. Reports connect via live connection (Build permission
   required); report authors add report-level measures locally without
   touching the shared model  <sup id="cite-7"><a href="#src-7" class="cite">[S7]</a></sup>.

6. Watch capacity memory. If the model's footprint climbs toward half
   the capacity's total memory, switch any refresh path to the enhanced
   refresh REST API or the XMLA endpoint, scoped to specific
   tables/partitions, instead of a full refresh  <sup id="cite-3"><a href="#src-3" class="cite">[S3]</a></sup>.

7. To size the model in memory, sum DICTIONARY_SIZE from
   DISCOVER_STORAGE_TABLE_COLUMNS with USED_SIZE from
   DISCOVER_STORAGE_TABLE_COLUMN_SEGMENTS over the XMLA endpoint  <sup id="cite-3"><a href="#src-3" class="cite">[S3]</a></sup>.

The end state: the biggest, fastest-changing table in the model never gets refreshed at all, the small dimensions stay cheap to join in memory, and the operational levers — RLS, refresh scoping, storage format — are all set correctly from day one instead of being patched in after the first capacity incident.