Direct Lake mode reads Delta Parquet files directly from OneLake into Power BI's in-memory engine — delivering Import-speed performance without scheduled refreshes. Learn how framing, transcoding, and fallback work, and build a production-ready Direct Lake semantic model from a Fabric lakehouse.

Picture this: you've spent weeks building a gold layer in your Microsoft Fabric lakehouse — clean, curated Delta tables that your analysts have been asking for. You wire up a Power BI semantic model, hit refresh, and then you wait. And wait. Your dataset is 50 million rows, Import mode takes 40 minutes to reload, and your stakeholders want "near real-time" dashboards. You briefly consider DirectQuery, but you've been down that road before — every single visual fires a SQL query against your source, your dashboards feel sluggish, and you spend half your day tuning aggregations to make them bearable.
Direct Lake mode is Microsoft's answer to this exact problem. It reads data directly from Delta Parquet files in OneLake — no import, no live query translation — and loads column segments into memory on demand, giving you Import-speed performance with near real-time freshness. It sounds like marketing copy, but the underlying mechanism is genuinely clever, and understanding it changes how you think about the entire data-to-dashboard pipeline in Fabric.
By the end of this lesson, you'll understand exactly how Direct Lake works at the engine level, when it makes sense over Import and DirectQuery, what its real limitations are (and there are some), and how to set one up from a Fabric lakehouse or warehouse. You'll also build a working semantic model using Direct Lake mode and know how to diagnose when Direct Lake is falling back to DirectQuery silently.
What you'll learn:
This lesson assumes you're comfortable with the basics of Microsoft Fabric and Power BI. You should have:
If you haven't built a lakehouse yet, work through Building Your First Lakehouse in Microsoft Fabric: Files, Tables, and the SQL Analytics Endpoint first and come back here.
Before we go deep on Direct Lake, let's establish a clear mental model of the three modes so the comparison is precise.
Import mode copies data from the source into Power BI's in-memory columnar engine (VertiPaq). Queries are blazing fast because everything lives in RAM, compressed and optimized for column scans. The tradeoff: you're querying a snapshot. Data is only as fresh as the last scheduled refresh, that refresh consumes Fabric capacity, and there are row limits per table (currently 1.5 billion rows in Premium/Fabric, but practically constrained by memory). For large datasets with frequent changes, Import mode becomes an operational burden.
DirectQuery keeps data in the source and translates every DAX query into SQL (or another native query language) sent to the source system. Data is always current, but every visual interaction triggers a round-trip query. Performance depends entirely on your source system's query speed, and complex DAX doesn't always translate to efficient SQL. DirectQuery also disables several DAX functions and limits model features.
Direct Lake sits between these extremes in a way that's architecturally distinct, not just a compromise. It doesn't copy data into a separate store, and it doesn't send queries back to the source on every visual interaction. Instead, it reads Delta Parquet files directly from OneLake into the VertiPaq engine's memory, on demand. Let's look at exactly how that works.
Everything in Direct Lake starts with Delta tables. When you land data in a Fabric lakehouse — whether through Dataflow Gen2, Spark notebooks, or data pipelines — Fabric stores it as Parquet files with a Delta transaction log on top. That transaction log is what makes Delta special: it tracks exactly which Parquet files constitute the "current" version of the table.
Direct Lake reads those Parquet files directly. There's no intermediate layer, no SQL translation, no proprietary binary format. OneLake stores the files; Direct Lake reads them. This is why Direct Lake is only available within Microsoft Fabric — it's a native capability of the integrated platform, not something you can retrofit onto an external data warehouse.
Key insight
The reason Direct Lake can achieve Import-like speed is that VertiPaq and Parquet are both columnar formats. When Direct Lake loads a column segment from a Parquet file, it's reading data that's already organized in a way that maps naturally to VertiPaq's internal structure. There's no row-to-column transformation penalty like you'd pay when importing from a row-store database.
One of the trickier concepts in Direct Lake is framing. Because your Delta table is a living thing — Spark jobs are writing new partitions, DML operations are updating rows, compaction jobs are rewriting files — Direct Lake needs a way to pin itself to a consistent snapshot of the table before reading it.
Framing is the act of querying the Delta transaction log to determine which Parquet files represent the current committed version of each table in the model. Power BI does this at query time (when a user opens a report or a dataset refresh is triggered). Once framed, the semantic model reads from that specific snapshot of files, even if new data arrives during a long query.
This is different from Import mode's refresh, where you explicitly trigger a full data load. Framing is lightweight — it reads the transaction log and takes note of file paths, not the data itself. The actual column data is loaded into VertiPaq memory lazily, on demand, the first time a query needs it.
When Direct Lake needs data from a column that isn't yet in memory, it reads the relevant Parquet column chunks and transcodes them into VertiPaq's internal columnar format. This transcoding step happens in the background and is the one place where you might observe a performance difference between a cold Direct Lake model and a warm one.
On first access (cold cache), transcoding takes a moment. On subsequent access (warm cache), the column data is already in VertiPaq memory and query response is indistinguishable from Import mode. This warm/cold distinction is important for user experience — if your report is accessed infrequently, the first user after a long idle period may see slower load times.
Tip
You can pre-warm your Direct Lake model by triggering a "warm-up" report load in a scheduled automation, or by configuring the semantic model to proactively load column data. We'll cover this in the performance section.
Direct Lake doesn't automatically refresh when new data arrives in the Delta table. The data becomes "available" as soon as the Delta transaction log commits a new version, but the semantic model re-frames itself either when a new query arrives or when you trigger a dataset refresh (which is now just a framing operation, not a full data copy).
In practice, this means data latency is determined by how often new commits land in your Delta table, not by a scheduled refresh interval. If your Spark pipeline writes to the lakehouse every 15 minutes, your Direct Lake model can surface that data within seconds of each commit — without a traditional refresh job running.
Let's get concrete about when to choose each mode, because "it depends" is not useful guidance.
Import mode remains the right choice when:
DirectQuery remains appropriate when:
Warning
DirectQuery over the Fabric SQL Analytics Endpoint (connecting Power BI to your lakehouse via the automatic SQL endpoint) is not the same as Direct Lake. Many teams accidentally use this pattern, get mediocre performance, and conclude that Fabric reporting is slow. The SQL Analytics Endpoint is a convenience layer for ad-hoc SQL queries and BI tool compatibility — it's not the optimized path for Power BI. Direct Lake is.
Direct Lake is your best option when all of these conditions are true:
The sweet spot for Direct Lake is fact tables in the tens of millions to billions of rows that change frequently, combined with dimension tables that are refreshed on a regular schedule. Think of a retail analytics model with a daily-updated 500-million-row sales fact table and a product dimension that changes a few thousand rows per day — this is exactly the scenario Direct Lake was designed for.
Key insight
Direct Lake also fits beautifully at the end of a medallion architecture pattern. Your gold layer tables — already clean, curated, and optimized for analytics — become Direct Lake sources. No additional ETL step to load Power BI. The lakehouse gold layer is the semantic model's source.
Direct Lake isn't available everywhere Power BI is. You need:
The capacity tier affects the guardrails on Direct Lake models. Higher F SKUs allow larger models (more columns in memory, more parquet files per query), and the limits scale with capacity. On an F2, you'll hit ceilings much faster than on an F64. Microsoft publishes the specific limits per SKU in their documentation, and they evolve as the product matures, so always check current documentation rather than hardcoded numbers.
Note
If you're evaluating Fabric for the first time, a 60-day Fabric trial gives you access to Direct Lake. This is enough to build a proof of concept with real production data volumes before committing to an F SKU purchase.
Let's walk through setting up a Direct Lake semantic model from a Fabric lakehouse. We'll assume you have a gold layer lakehouse with a fact_sales table and several dimension tables (dim_product, dim_customer, dim_date).
In your lakehouse, navigate to the Tables section in the Lakehouse explorer. Any table that appears there is a Delta table automatically managed by Fabric. Files dropped into the Files section are not Delta tables and cannot be used with Direct Lake.
If your data is currently in files (CSV, JSON, Parquet without Delta), you'll need to convert it. The cleanest way is a Spark notebook that reads the files and writes them as a managed Delta table:
# Read raw parquet files from the Files section
df = spark.read.parquet("Files/gold/fact_sales/")
# Write as a Delta table in the Tables section
df.write.format("delta") \
.mode("overwrite") \
.saveAsTable("fact_sales")
This creates a Delta table in the lakehouse's managed table store, which is what Direct Lake needs.
In the Fabric workspace, open your lakehouse. In the top ribbon, click New semantic model. Give it a meaningful name — something like Sales Analytics - Direct Lake.
You'll see a table picker showing all Delta tables available in the lakehouse. Select the tables you want to include:
fact_salesdim_productdim_customerdim_dateClick Confirm. Fabric creates the semantic model in Direct Lake mode automatically. There's no mode selector — Direct Lake is the default when you create a model directly from a lakehouse.
Tip
You can also create a Direct Lake semantic model from a Fabric warehouse. The process is identical — open the warehouse, click New semantic model, and select tables. The resulting model connects directly to the warehouse's underlying Delta files in OneLake, not to the warehouse's SQL layer.
After creating the model, the workspace opens a web-based model editor. This is where you define relationships, calculated columns, measures, and hierarchies — just like in Power BI Desktop, but in the browser.
Click Open data model (or navigate to the semantic model and select Edit). You'll land in the model diagram view. Define your relationships:
fact_sales[ProductKey] → dim_product[ProductKey] (many-to-one)fact_sales[CustomerKey] → dim_customer[CustomerKey] (many-to-one)fact_sales[DateKey] → dim_date[DateKey] (many-to-one)Set the cross-filter direction based on your reporting needs. Star schema relationships in Direct Lake behave identically to Import mode relationships for most scenarios.
Measures in Direct Lake models are written in standard DAX. There's no syntax difference from Import mode:
Total Revenue =
SUMX(
fact_sales,
fact_sales[Quantity] * fact_sales[UnitPrice]
)
Revenue YTD =
CALCULATE(
[Total Revenue],
DATESYTD(dim_date[Date])
)
Gross Margin % =
DIVIDE(
SUMX(fact_sales, fact_sales[Quantity] * (fact_sales[UnitPrice] - fact_sales[UnitCost])),
[Total Revenue],
0
)
These measures work exactly as they would in an Import mode model. The DAX engine doesn't know or care that the underlying columns are being read from Delta Parquet files rather than a pre-loaded VertiPaq store — once data is in memory, it's identical.
Create a new Power BI report in the workspace and connect it to your new semantic model. Reports connect to Direct Lake models exactly like they connect to Import models — via the semantic model's published endpoint. There's no special report configuration required.
When a user opens the report and a visual queries the fact_sales table for the first time, Direct Lake frames against the Delta log, identifies the relevant Parquet column files, and transcodes them into VertiPaq memory. Subsequent queries against the same columns hit the in-memory cache and return at Import speed.
This is the part most tutorials skip, and it's where real-world Direct Lake deployments go wrong.
Direct Lake has a guardrail mechanism called fallback. When the engine encounters a situation it can't handle natively — a DAX function that requires SQL translation, a relationship pattern it can't resolve in the Parquet layer, or a query that exceeds capacity limits — it silently falls back to DirectQuery mode, sending a SQL query to the lakehouse's SQL Analytics Endpoint instead.
Fallback works, but it gives you DirectQuery performance, not Direct Lake performance. If your reports feel slow and you can't figure out why, silent fallback is the likely culprit.
Calculated columns in the semantic model — Direct Lake can't compute calculated columns from Parquet files during framing because the computation requires a full table scan during load time. Any calculated column forces the model into import mode for that table, or triggers fallback. Use measures instead of calculated columns wherever possible, or pre-compute the column in your lakehouse using Spark.
Relationships on columns with mismatched data types — If your fact table has DateKey as a string and your date dimension has DateKey as an integer, Direct Lake may fall back when resolving that relationship. Ensure type consistency in your Delta tables.
Row-level security using certain DAX patterns — Some RLS filter expressions trigger fallback. Specifically, RLS filters that reference other tables or use functions that require SQL translation.
Exceeding capacity limits — If your model's column count or data volume exceeds the guardrails for your F SKU, Direct Lake falls back rather than failing outright.
The most reliable way to detect fallback is DAX Studio, connected to your published semantic model. Run a query against the model and inspect the query plan. Look for DirectQuery nodes in the physical query plan — their presence confirms that part of your query is falling back.
You can also check the semantic model settings in the Fabric workspace. Look for the Fallback setting under the Direct Lake configuration. You have two options:
During development and testing, disable fallback. You'll catch issues early. Before production deployment, decide whether controlled fallback is acceptable for your use case.
// In DAX Studio, check if a specific query triggers fallback
// Look for "DirectQuery" in the physical query plan output
EVALUATE
SUMMARIZECOLUMNS(
dim_date[Year],
"Revenue", [Total Revenue]
)
Run this in DAX Studio with Server Timings enabled. If you see DirectQuery in the query plan, investigate which table or measure is triggering it.
Warning
Don't assume your Direct Lake model is performing as designed just because reports load quickly in development. Development environments often have smaller datasets that fit entirely in memory. Test with production-scale data volumes to expose fallback scenarios before go-live.
The cold cache problem is real for infrequently accessed reports. One pragmatic solution is to run a scheduled Power Automate flow or Azure Logic App that opens the report (or makes a DAX query via the XMLA endpoint) at a predictable time before business hours start. This triggers column transcoding so the first real users hit a warm cache.
Direct Lake reads Parquet column chunks. The efficiency of those reads depends heavily on how your Delta tables are physically organized.
Partitioning: Partition your fact tables on columns that align with your most common filter patterns. A sales fact table partitioned by Year and Month means Direct Lake only needs to transcode Parquet files for the months being queried, not the entire table.
# Writing a fact table with partitioning optimized for Direct Lake
df.write.format("delta") \
.mode("overwrite") \
.partitionBy("Year", "Month") \
.option("delta.autoOptimize.optimizeWrite", "true") \
.saveAsTable("fact_sales")
File compaction (OPTIMIZE): Small files are the enemy of read performance. If your pipeline writes many small Parquet files (a common Spark anti-pattern), Direct Lake has to open and read many files to assemble a single column. Run Delta's OPTIMIZE command after large loads:
from delta.tables import DeltaTable
delta_table = DeltaTable.forName(spark, "fact_sales")
delta_table.optimize().executeCompaction()
You can also run OPTIMIZE from the Fabric SQL Analytics Endpoint using T-SQL syntax:
OPTIMIZE fact_sales;
Z-ordering: For columns you frequently filter in reports (like ProductCategory or Region), Z-ordering co-locates related data in the same Parquet files, reducing the number of files Direct Lake needs to read:
OPTIMIZE fact_sales ZORDER BY (ProductCategory, Region);
Tip
If you're using orchestrated loads via Fabric data pipelines, add an OPTIMIZE step at the end of your pipeline as a Fabric notebook activity. This ensures your Delta tables are always in optimal physical shape before Direct Lake reads them.
Not every column in your fact table needs to be in the semantic model. Direct Lake models that include dozens of unused columns waste memory and slow transcoding. In the semantic model editor, hide or remove columns that reports don't need. Hidden columns are still available for DAX calculations but won't be transcoded unless a measure specifically references them.
Direct Lake supports aggregation tables, the same way Import mode does. For a 2-billion-row fact table, you can build a pre-aggregated summary table in your lakehouse (daily totals by product and region, for example), expose it in the semantic model alongside the detail table, and configure aggregation rules. Power BI serves most report queries from the aggregation table without touching the full fact table, and Direct Lake only reads the detail-level Parquet files for drill-through queries that explicitly need grain-level data.
Let's put this into practice with a realistic end-to-end scenario. You'll create a Direct Lake semantic model from a gold layer lakehouse and verify it's actually using Direct Lake (not falling back).
First, make sure you have a gold layer lakehouse with at least one meaningful fact table. If you followed the medallion architecture lesson, you likely already have this. If not, create a simple fact_orders table in a Spark notebook:
from pyspark.sql import functions as F
import random
from datetime import date, timedelta
# Generate 5 million rows of synthetic order data
spark.sql("""
CREATE TABLE IF NOT EXISTS fact_orders (
OrderKey BIGINT,
CustomerKey INT,
ProductKey INT,
DateKey INT,
Quantity INT,
UnitPrice DECIMAL(10,2),
UnitCost DECIMAL(10,2)
) USING DELTA
PARTITIONED BY (DateKey)
""")
# Insert data in batches using Spark
from pyspark.sql.types import *
import pandas as pd
import numpy as np
np.random.seed(42)
n = 5_000_000
pdf = pd.DataFrame({
'OrderKey': range(1, n + 1),
'CustomerKey': np.random.randint(1, 10001, n),
'ProductKey': np.random.randint(1, 5001, n),
'DateKey': np.random.randint(20220101, 20241231, n),
'Quantity': np.random.randint(1, 20, n),
'UnitPrice': np.round(np.random.uniform(5.0, 500.0, n), 2),
'UnitCost': np.round(np.random.uniform(2.0, 400.0, n), 2)
})
sdf = spark.createDataFrame(pdf)
sdf.write.format("delta").mode("overwrite").partitionBy("DateKey").saveAsTable("fact_orders")
Orders Analytics - Direct Lakefact_orders (and any dimension tables you have)In the model editor, add a simple measure to verify DAX works:
Total Order Value =
SUMX(
fact_orders,
fact_orders[Quantity] * fact_orders[UnitPrice]
)
In the workspace, navigate to the semantic model settings (click the three-dot menu on the semantic model, then Settings). Under the Gateway and cloud connections section, you should see Direct Lake listed as the storage mode with the lakehouse name as the source.
Now open DAX Studio, connect to the semantic model via the XMLA endpoint (your workspace URL), and run:
EVALUATE
ROW("Total Order Value", [Total Order Value])
Enable Server Timings in DAX Studio before running. In the results, look at the Storage Engine timings. If you see VertiPaq as the storage engine and no DirectQuery entries, your model is running in pure Direct Lake mode. The first run will be slower (cold transcoding); run it a second time and compare — the second run should be significantly faster.
Try adding a calculated column in the model editor:
In the fact_orders table, add a new calculated column:
Order Value = fact_orders[Quantity] * fact_orders[UnitPrice]
Now re-run the DAX Studio query. Check the server timings again. You may see DirectQuery appear — this is fallback triggering because of the calculated column. Delete the calculated column and use the measure version instead. This exercise makes the impact of calculated columns viscerally clear.
"My Direct Lake model feels just as slow as DirectQuery"
Check for fallback first. Use DAX Studio server timings. If you see DirectQuery in the plan, hunt down calculated columns, problematic RLS rules, or type mismatches in relationships. Second, check if you're connecting Power BI Desktop directly to the semantic model's XMLA endpoint in DirectQuery mode — that's not the same as Direct Lake.
"I can't find the option to create a Direct Lake model"
Direct Lake models can only be created from within the Fabric portal — not from Power BI Desktop. In Power BI Desktop, you can connect to an existing Direct Lake semantic model in Live Connection mode, but you can't create one. Also verify your workspace is on Fabric capacity, not Power BI Pro.
"My data isn't updating even though new rows are in the lakehouse"
Remember that framing happens at query time, not continuously. Try opening the report in a new browser session (to clear any cached state) or manually trigger a dataset refresh from the workspace. The refresh for a Direct Lake model is fast — it's just re-framing against the Delta log, not copying data.
"OPTIMIZE and ZORDER take forever on my large table"
Run OPTIMIZE on partitions rather than the whole table. Target specific date partitions that received new data:
OPTIMIZE fact_orders WHERE DateKey = 20241215;
This limits compaction to the recently written files rather than reprocessing the entire historical table.
"I'm getting an error about the model exceeding capacity limits"
You've likely hit the column or row limits for your F SKU. Options: reduce the number of columns in the semantic model (remove unused columns), increase your Fabric capacity to an F SKU with higher limits, or split the model into multiple semantic models with composite model relationships.
Warning
Avoid using OneLake shortcuts as Direct Lake sources without understanding the implications. Shortcuts to external Delta tables (in ADLS Gen2, for example) work with Direct Lake, but shortcuts to non-Delta sources (like shortcuts to an S3 bucket with plain Parquet files) do not. The Delta transaction log is non-negotiable for Direct Lake.
Direct Lake mode represents a genuine architectural advancement in how Power BI and Fabric work together. By reading Delta Parquet files directly from OneLake and transcoding them into VertiPaq memory on demand, it eliminates the operational burden of scheduled Import refreshes while delivering query performance that DirectQuery over a SQL endpoint simply cannot match.
The key mental model to carry forward: Direct Lake is a first-class citizen of the Fabric platform, not an adapter. It works because Fabric controls the entire stack — OneLake, Delta, the lakehouse compute, and the Power BI engine — and can optimize the handoffs between them in ways that external connectors never could.
Your decision framework in practice:
To keep improving your Direct Lake setup:
The deeper you go into Fabric's data layer — whether through lakehouses vs. warehouses decisions or the broader What Is Microsoft Fabric picture — the more Direct Lake makes sense as the default reporting connectivity mode. Design your gold layer with Direct Lake in mind from the start, and your analytics layer essentially builds itself.