Skip to content

Direct Lake in Fabric: The Third Way Between Import and DirectQuery

Direct Lake gives you Import-speed queries without copying the data. How it works, how the OneLake and SQL variants differ, how to catch DirectQuery fallback, and how to get your Delta tables ready for Power BI.

Microsoft Fabric 7 min read Atılay Berber

For years, building a Power BI model meant picking between two compromises. Import is fast, but every refresh copies all of the data. DirectQuery copies nothing, but every click sends a query back to the source and things slow down. Direct Lake, which came with Microsoft Fabric, sits between the two: the data stays in Delta tables in OneLake, yet queries are answered from memory by the same engine Import uses (VertiPaq). In this post I’ll walk through how Direct Lake works, how its two flavors differ, and the performance traps people run into most often.

What Direct Lake actually does

In Import mode, a refresh reads the source from top to bottom and writes a compressed copy into the model. In Direct Lake, a refresh only touches metadata. Microsoft calls this framing: the model looks at the latest version of each Delta table and records which Parquet files to use. That’s why a refresh usually finishes in a few seconds.

The data itself is loaded on demand. The first time a query needs a column, that column is read from OneLake into memory (transcoding), and later queries use it straight from memory. Columns that go unused for a while get evicted. So a long description column that no report ever touches doesn’t take up any memory at all.

There’s a practical side effect: the first time a report opens (the “cold” state) it can be a bit slower than the next time. When you test performance, look at the first query and the ones after it separately.

There are two kinds of Direct Lake

Direct Lake currently comes in two variants, and the gap between them is bigger than most people expect:

Direct Lake on OneLakeDirect Lake on SQL
Data sourcesMultiple Fabric items (lakehouses, warehouses, shortcuts)A single SQL analytics endpoint
Fallback to DirectQueryNoneYes, automatically when conditions aren’t met
SQL viewsNot supportedSupported, but they fall back to DirectQuery
Import tables in the same modelYesNo
RLS/OLS defined in SQLNot applied; define security in the modelApplied, but queries fall back to DirectQuery

Microsoft recommends Direct Lake on OneLake for new models. Its biggest advantage is that it can pull several sources into one model and, when you need it, mix in Import tables as a composite model. If you depend on SQL views or SQL-side security, Direct Lake on SQL is still an option, but you have to accept that part of your workload will run as DirectQuery.

Building the model in Power BI Desktop

You can create a Direct Lake on OneLake model right from Power BI Desktop:

  1. Open the OneLake catalog from the ribbon.
  2. Pick a lakehouse or warehouse and select Connect.
  3. Name the model, choose the Fabric workspace, tick the tables you want and select OK.

The model is created in the workspace and you edit it in live edit mode. A few habits have to change here: there’s no publish step, changes save automatically, and there’s no undo. Instead, a version is saved in version history at the start of every editing session. If you’re about to rework relationships or measures in a big way, it’s worth knowing that before you start. To use this mode, the XMLA endpoint has to be enabled in the tenant and set to Read Write on the capacity.

The silent slowdown: falling back to DirectQuery

If you use Direct Lake on SQL, the thing to watch most closely is fallback. When the model can’t read a table through Direct Lake (because it’s based on a SQL view, has RLS defined in SQL, or goes over the capacity limits), queries against that table quietly switch to DirectQuery. The report keeps working, it just gets slower, which is exactly why it’s easy to miss.

To see which tables fall back and why, run this in DAX query view:

EVALUATE TABLETRAITS()

If the [DirectLakeFallbackInfo] column says None, the table is running in Direct Lake mode. Any other value tells you why it fell back.

You can also turn this behavior off while you develop. In Model view, open the semantic model’s Properties pane; the Direct Lake behavior setting has three options:

  • Automatic (default): quietly falls back to DirectQuery when the conditions aren’t met.
  • Direct Lake Only: raises an error instead of falling back. Great for catching problems early in development.
  • DirectQuery Only: always uses DirectQuery. Handy for measuring how bad performance would be after a fallback.

Direct Lake on OneLake doesn’t have this setting; it always behaves like Direct Lake Only. If a limit is exceeded, the model can’t be queried until the Delta tables are fixed.

What really drives performance: your Delta tables

With Import, Power BI decided how the data got compressed. With Direct Lake, that job moves to whoever writes the Delta tables, which usually means data engineering. How fast your report is depends largely on how those tables are written. Four things cover most of it:

1. V-Order. V-Order is a write-time optimization that sorts and compresses Parquet files so VertiPaq can read them efficiently. It’s on by default in Warehouse, but off by default for tables written with Spark in new workspaces. If Power BI is the main reader of a table, turn it on at the table level:

%%sql
ALTER TABLE dbo.fact_satis
SET TBLPROPERTIES ("delta.parquet.vorder.enabled" = "true");

This only affects future writes. For the files already in the table, use the OPTIMIZE command in the next point.

2. Compact small files. Frequent small appends leave you with hundreds of tiny Parquet files and row groups. Queries slow down and you creep toward the capacity limits. OPTIMIZE merges those files, and adding VORDER applies V-Order at the same time:

%%sql
OPTIMIZE dbo.fact_satis VORDER;

One caveat: because OPTIMIZE rewrites files, the affected columns in memory are invalidated and the next query has to load them again. Run it at quiet times, on weekends or overnight, rather than after every load.

3. Row group size. Microsoft recommends 1 to 16 million rows per row group. Lots of small row groups means more pieces for Direct Lake to stitch together. You can check where you stand from a notebook with the semantic-link-labs library:

%pip install semantic-link-labs

import sempy_labs as labs

result = labs.delta_analyzer("fact_satis")
display(result["Summary"])

4. Don’t rewrite the whole table every time. Load patterns that drop and rewrite the entire table (overwrite) stop framing from updating only what changed, so every refresh forces all columns to load again. Where you can, append new rows only, or partition the table on a low-cardinality column such as month and update just the affected partition.

Know your capacity limits

Direct Lake needs a Fabric capacity (an F SKU), and each SKU has per-table limits. On the smaller capacities (F2 to F32) a table can have up to 1,000 Parquet files, 1,000 row groups and 300 million rows. On F64 that goes up to 5,000 files and 1.5 billion rows. The amount of data that can be paged into memory also depends on the SKU: 3 GB on F2 to F8, 25 GB on F64. If you have a large fact table, check these numbers before you pick a capacity.

Which mode should you choose?

  • Direct Lake: when the data already lives in Fabric, in a lakehouse or warehouse; when volumes are large or growing fast; when the data needs to update several times a day.
  • Import: when you have to shape the data in Power Query, can’t change the source, don’t have a Fabric capacity, or need Import-only features like hybrid tables and model partitions.
  • DirectQuery: when queries really have to run against the source database and the source can handle the load.

These aren’t mutually exclusive. In a Direct Lake on OneLake model you can keep the big fact tables in Direct Lake and a small dimension table shaped in Power Query as Import, side by side.

One last tip

The Keep your Direct Lake data up to date option in the semantic model settings is on by default and reframes the model as soon as it detects changes in the Delta tables. For small models that update often during the day, that’s great. But if your ETL loads several tables one after another, users can end up seeing the new fact table alongside the old dimension table while the load is still running. In that case it’s safer to turn the option off and refresh the model as the last step of your pipeline.

STAY IN THE LOOP

New guides, sorted by topic in a single email.

Every Monday, the week's new posts arrive grouped by topic. No new posts, no email.

Nothing is sent until you click the link in the confirmation email. Unsubscribe anytime with one click.

Found this useful? Share it

1 comment

Leave a comment

Your email address will not be published. Required fields are marked *