Taming the Beast: From 4-Hour Refreshes to Fast Insights with Power BI and Microsoft Fabric
- Jihwan Kim
- Jul 13
- 13 min read
Updated: Jul 15
I. Introduction: The All-Too-Common Incremental Refresh Headache
In this writing, I’d like to share how I approach a frustratingly common scenario I’ve seen many times in enterprise projects: I meticulously configure incremental refresh on a large semantic model, but the initial refresh in the service drags on for hours, often ending in a timeout.
Incremental refresh is, without a doubt, a cornerstone feature for managing large-scale data in Power BI. It promises faster, more reliable refreshes and reduced resource consumption by only updating new or changed data. For all subsequent, day-to-day operations, it works beautifully.
However, this initial experience can be so painful that it leads many developers to question the feature's viability. This raises a critical paradox: Why does a feature designed for speed cause the initial refresh to be catastrophically slow?
The answer lies in a fundamental misunderstanding of what happens during that first operation. The term "Incremental Refresh" is, for the initial load, a misnomer. A more accurate description would be a "Partitioned Historical Load." When you first publish the model and trigger a refresh, Power BI isn't just fetching a small, incremental slice of data. It is tasked with building the entire historical dataset as defined in my policy—for example, five years of sales data. It accomplishes this by creating a separate, static partition for each historical period (e.g., one for each month or year). The truly "incremental" part, which operates on a much smaller time window (like the last seven days), only applies to future refreshes. This disconnect between expectation and reality is the primary source of the frustration and perceived failure of the feature, but the true culprit is a deeper, technical bottleneck that I must unmask.
II. The Root Cause: Unmasking the Query Folding Bottleneck
During the initial partitioned load, Power BI sends a separate query to the data source for each historical partition. For a five-year historical policy with monthly partitions, this means executing 60 individual queries. The performance of this entire operation hinges on one critical, non-negotiable capability: Query Folding.
Query Folding is the process where Power Query translates the M transformations you build in the editor into the native query language of the data source, such as SQL, and pushes the execution of those transformations to that source system. The RangeStart and RangeEnd parameters, which are mandatory for incremental refresh, are the key. When folding works correctly, these parameters are translated directly into a WHERE clause in the source query. For a partition for January 2022, the native query sent to the database would look something like this:
SELECT...
FROM FactSales WHERE OrderDate >= '2022-01-01T00:00:00' AND OrderDate < '2022-02-01T00:00:00'
This is incredibly efficient. The source database does the heavy lifting of filtering, and only a small, pre-filtered chunk of data is sent over the network to Power BI for that partition.
What Happens When Folding Breaks
The entire process collapses when query folding breaks. If I introduce a transformation step in Power Query that the data source cannot understand natively, Power BI can no longer push the RangeStart and RangeEnd filter down to the source. Instead, it is forced to adopt a brute-force approach. For every single one of the 60 historical partitions, it sends a broad, unfiltered query like SELECT * FROM FactSales to the database (without WHERE clause).
This means Power BI attempts to pull the entire, multi-billion row table into the Power BI service's memory, and only then does it apply the date filter to isolate the data for that specific partition. It repeats this disastrous process for every partition. This leads to an astronomical amount of data being transferred and processed, overwhelming the memory and CPU resources of the service and inevitably causing the refresh to fail after hitting the several-hour-time-limit (depending on the Power BI License that I have).
This reveals a crucial architectural principle: for large-scale import models, query folding is not a "performance tip" or an optional optimization. It is an architectural mandate. A non-folding query on a large fact table is not a performance issue; it is a design flaw that guarantees failure. The goal is not just to make the refresh "faster," but to make it "possible" in the first place.
Practical Diagnostics: How to Confirm Query Folding
Fortunately, diagnosing this issue is straightforward. Within the Power Query Editor, after applying all your transformation steps, right-click on the very last step in the "Applied Steps" pane.
If "View Native Query" is enabled (not grayed out): Congratulations, query folding is active for all steps up to this point. The query is being successfully translated and pushed to the source.

If "View Native Query" is grayed out: This is the red flag. Folding has been broken (not 100% true) by this step or a previous one. The Power BI mashup engine will have to process the data locally, which is the root cause of the extreme refresh times.
III. The Classic Toolkit: Pre-Fabric Performance Tuning
Once I've identified that query folding is broken, the solution involves offloading the transformation work away from the Power BI service's mashup engine and back to a system designed for it. Before the advent of Microsoft Fabric, there were two primary strategies for this.
Tactic 1: Power Query Discipline - Maximizing Native Transformations
The first line of defense is to be meticulous in how you construct my Power Query transformations. The goal is to use only operations that can be folded. This involves avoiding common folding-breakers:
Adding an Index Column: This operation has no native equivalent in SQL and will break folding.
Complex M Functions: Using M functions for which there is no direct native source equivalent (e.g., certain text manipulations, splitting columns in complex ways) will prevent folding.
Merging Queries from Different Sources: Combining a foldable source (like a SQL table) with a non-foldable source (like a local CSV file or Excel workbook) will break folding for the entire query.
Incompatible Data Type Changes: While many data type changes fold, some can break it depending on the source system.
A key strategy here is to reorder my steps. Perform all foldable transformations—filtering rows, removing columns, performing simple arithmetic, renaming columns—at the very beginning of my query. This ensures the maximum amount of data reduction happens at the source. Any non-foldable steps, like adding a custom column that uses a complex M function, should be pushed to the very end of the applied steps list.
Tactic 2: The Staging Layer Architecture - Offloading the Burden
When Power Query discipline alone is not enough to solve complex transformation needs, the definitive classic solution is to introduce a staging layer. This architecture decouples the complex transformation logic from the Power BI semantic model entirely.
The process is as follows:
Create an Intermediate Data Store: Provision a relational database, such as Azure SQL Database or a dedicated SQL pool in Azure Synapse Analytics, to act as my staging area.
Build an ETL/ELT Pipeline: Use a dedicated data integration tool like Azure Data Factory or Synapse Pipelines. This pipeline connects to the raw source systems, extracts the data, performs all the complex, non-foldable transformations (like merging disparate sources, applying complex business rules, etc.), and loads the clean, structured, analytics-ready data into tables in your staging database.
Connect Power BI to the Staging Layer: Instead of connecting Power BI to the raw, messy source, you connect it to a simple view or table in the staging database. The Power Query source step becomes a simple SELECT * FROM view_CleanedFactSales.
This pattern guarantees that query folding will work flawlessly. All the heavy lifting is done upstream by a tool designed for it. The query that Power BI sends for each incremental refresh partition is simple, clean, and perfectly foldable, ensuring the initial historical load is as fast and efficient as the source database can deliver.
IV. The Modern Architecture: Solving the Refresh Problem with Microsoft Fabric
While the classic staging pattern is robust, it requires managing separate infrastructure. Microsoft Fabric provides a modern, integrated platform that not only refines this pattern but ultimately makes the entire problem of long refresh times obsolete.
This evolution can be seen as a maturity model for data transformation:
Level 0 (Beginner): All transformations are done in Power Query with no regard for folding. This approach is fragile and fails at enterprise scale.
Level 1 (Intermediate): Meticulous Power Query design to preserve folding. This is effective but requires deep expertise and can be complex to maintain.
Level 2 (Advanced - Classic): Use an external SQL staging database. This decouples logic effectively but adds infrastructure cost and management overhead.
Level 3 (Advanced - Fabric): Use the Fabric Lakehouse as an integrated staging layer. This provides the benefits of Level 2 but within a unified, more powerful platform.
The Evolutionary Step: The Lakehouse as a Staging Layer
The Microsoft Fabric Lakehouse serves as an optimized staging layer that replaces the need for a separate SQL database. The architecture is cleaner and more powerful.
Ingest & Transform: Within my Fabric workspace, I use a tool like Dataflows Gen2 or a Spark Notebook to connect to my raw data sources. Here, I perform all the necessary data cleansing and transformation logic.
Land as Delta Tables: Creating Reliable and Fast Data Once my data is transformed and clean, I write it into the Lakehouse. But I don't just save it as a simple file; I save it as a Delta table. Think of a Delta table not just as a table, but as a "smart" container for my data that comes with powerful, built-in safety features. This is where I get into some key concepts that make my data reliable and fast.
What is Direct Lake mode in Fabric? - Lytix
What are ACID Transactions? (And why I should care) Delta Lake provides what are known as ACID transactions, a term borrowed from the world of databases that guarantees data reliability. I don't need to be a database expert to understand the benefits. Imagine it like a bank transfer:
A (Atomicity): The entire operation succeeds, or it fails completely. Money is never just "lost" mid-transfer. For my data, this means a data load process will never leave me with a half-finished, corrupted table.
C (Consistency): My data is always in a valid, usable state. The rules I set for my data (like a column must always contain a number) are never broken.
I (Isolation): Multiple people or processes can read and write to the same table at the same time without interfering with each other. One person's query won't be disrupted by another person loading new data.
D (Durability): Once a change is successfully saved, it’s permanent and won't be lost, even if the system crashes right after.
How do I "use" ACID? The best part is, I don't have to do anything special. By simply choosing to save my data as a Delta table within Microsoft Fabric, I automatically get all these ACID guarantees. The system handles it for me, ensuring my data is always robust and trustworthy.
What is V-Order Optimization? (The secret to Power BI speed) While ACID makes my data safe, V-Order makes it incredibly fast for Power BI. V-Order is a special optimization technique that organizes the data within the Parquet files (the underlying file format for Delta tables) in a way that is tailor-made for the Power BI engine.
BI solution architecture in the Center of Excellence - Power BI | Microsoft Learn
Think of it like this: a normal library might organize books alphabetically by author. To find all books on a specific topic, I'd still have to search a lot. A V-Ordered library, however, uses a special sorting system that the librarian (the Power BI engine) understands perfectly. The librarian can go directly to the right spot and grab exactly the books needed, almost instantly, without scanning entire shelves. This is how V-Order allows Power BI to read data from the Lakehouse with extreme efficiency.
How do you "use" V-Order with Dataflow Gen2? The great news is that when using a modern tool like Dataflow Gen2 in Microsoft Fabric, I don't need to write any code or find a complex setting to get the benefits of V-Order. Fabric is designed to handle this optimization for me automatically.
Here’s how it works in practice:
Prepare my Data: Inside the Dataflow Gen2 editor, I perform all my usual data cleaning and transformation steps on a query.
Set the Destination: Once my data is ready, I need to tell the dataflow where to save it. With my final query selected, I go to the Home tab on the ribbon and use the Add data destination option to select Lakehouse.
Choose my Table: I will be prompted to select my workspace, my Lakehouse, and provide a name for the new table I am creating.
Run the Dataflow: After I save my settings and run the dataflow, the Fabric engine takes over. As it writes my transformed data into the Lakehouse, it automatically applies the V-Order optimization to the underlying Delta-Parquet files.
How to check If Delta Table in Fabric is VORDER Optimized DirectLake
That's it. There is no special "Enable V-Order" button to click within the dataflow interface. The optimization is a built-in benefit of using Dataflow Gen2 to land my data in a Fabric Lakehouse. It's worth noting that this automatic optimization is a key benefit of using a tool like Dataflow Gen2.
If you were creating tables directly in the Lakehouse using a Spark Notebook, you would need to write a bit of code to enable V-Order. We'll explore those more advanced, code-based methods in a future blog post.
To keep this performance high over time, it's also a good practice to periodically run maintenance commands like OPTIMIZE and VACUUM on your tables, which helps keep the files well-organized and removes old, unneeded data.
Dataflow Gen2 data destinations and managed settings - Microsoft Fabric | Microsoft Learn
Connect Power BI for Import: You then create your Power BI semantic model in Import mode, connecting it to the clean Delta tables in the Lakehouse. Just like with the classic staging pattern, the source is pre-processed and perfectly structured, guaranteeing that query folding works and the initial partitioned load is fast and reliable.
Connect Power BI for Import: I then create my Power BI semantic model in Import mode, connecting it to the clean Delta tables in the Lakehouse. Just like with the classic staging pattern, the source is pre-processed and perfectly structured, guaranteeing that query folding works and the initial partitioned load is fast and reliable.
The Revolutionary Leap: Eliminating the Refresh with DirectLake
The Fabric Lakehouse provides an elegant solution to the import-mode refresh problem, but the true paradigm shift comes with DirectLake mode. This innovative storage mode doesn't just speed up the refresh; it fundamentally eliminates it, making the several-hour initial load problem entirely obsolete.
DirectLake represents the physical manifestation of the Lakehouse architecture, collapsing the traditional data pipeline. Historically, data moved in hops: from a raw data lake, to a transformed data warehouse, and finally copied into Power BI's proprietary in-memory cache (VertiPaq) for an import model. Each hop introduced latency, cost, and complexity.
DirectLake shatters this model. Here’s how:
No Data Duplication: A DirectLake semantic model does not import and duplicate data. Instead, it directly reads the V-Ordered Delta-Parquet files from the single copy of data residing in OneLake, Fabric's unified storage layer.
The Best of Both Worlds: It intelligently combines the lightning-fast query performance of Import mode with the real-time data freshness of DirectQuery mode. Queries are served by the same high-performance VertiPaq engine that powers Import mode.
Near-Instant "Refresh": The concept of a long-running data refresh vanishes. When the data in the Lakehouse is updated, a DirectLake "refresh" is a metadata-only operation called "framing." It takes only a few seconds to update the pointers in the semantic model to reference the latest version of the Delta files in OneLake. The several-hour problem literally becomes a several-second operation.
Creating a DirectLake model is remarkably simple: I connect to my Lakehouse tables and choose DirectLake as the storage mode. The platform handles the rest, effectively making Power BI a native query engine on top of my data lake.
V. An Architect's Decision Framework
DirectLake is a revolutionary technology, but as with any powerful tool, choosing where to apply it requires architectural consideration. It is not a universal replacement for all scenarios just yet.
One critical nuance is the risk of "fallback." If a DirectLake model attempts an operation that is not supported in-memory (e.g., it exceeds the capacity's memory limits for a given query, or uses certain complex DAX patterns), the query can "fall back" to the much slower DirectQuery mode. This can be an unwelcome performance surprise if not anticipated. Furthermore, the operational burden shifts. Instead of managing Power BI refresh schedules, the BI team must now become stewards of the underlying Lakehouse tables, ensuring they are properly maintained with commands like OPTIMIZE and VACUUM to manage file sizes and preserve V-Order performance.
To help navigate these choices, here is a decision framework for selecting the right modern data strategy in Fabric.
Factor | Optimized Import from Lakehouse | DirectLake from Lakehouse | Suggestion |
Initial Load Time | Fast (minutes), as query folding is guaranteed against clean Delta tables. | Near-Instant (seconds), as it's a metadata update ("framing"). | For any new large-scale model, DirectLake is the clear winner. It eliminates the entire problem category of long initial loads. |
Data Latency | High. Dependent on the scheduled refresh frequency (e.g., hourly, daily). | Low. Near real-time, as changes in OneLake can be reflected automatically without a full data copy. | If business users demand sub-minute data freshness, DirectLake is the best viable path. |
Query Performance | Highest (VertiPaq). Predictable and stable once data is loaded into the in-memory cache. | Very High (VertiPaq). Comparable to Import, but with a risk of fallback to slow DirectQuery if memory limits are hit or certain queries are too complex. | For maximum, guaranteed query speed on models that comfortably fit in memory, Import remains the gold standard. For "fast enough" performance on massive models that exceed memory, DirectLake is superior. |
DAX Compatibility | 100%. All DAX functions are fully supported in the VertiPaq engine. | High, but limited by what can be processed in-memory. DAX that is not VertiPaq-friendly may trigger a fallback to DirectQuery. | Import is the safest bet for migrating existing, highly complex DAX models. Profile and test carefully before moving complex logic to DirectLake. |
Architectural Simplicity | Two-step process: Stage data in Lakehouse, then import and cache it in Power BI. | Single-step process: Connect Power BI directly to the single copy of data in the Lakehouse. | DirectLake offers a dramatically simpler, more elegant, and more integrated end-to-end architecture. |
Operational Overhead | Manage refresh schedules and monitor capacity memory usage for the imported data copy. | Manage the underlying Delta tables in the Lakehouse (V-Order, OPTIMIZE, VACUUM) and monitor for query fallback. | The nature of the work shifts from "BI Refresh Admin" to "Lakehouse Steward." Choose the skill set your team has or wants to build. |
VI. Conclusion
This journey from a several-hour refresh to near-instantaneous analysis showcases the power of modern data architecture. The slow initial refresh is not a flaw in the incremental refresh feature itself, but a symptom of an underlying architectural strain—specifically, broken query folding. By applying disciplined Power Query techniques or, more robustly, by offloading transformations to a staging layer, this classic problem can be solved.
However, Microsoft Fabric offers a more profound solution. By using the Lakehouse as an integrated staging layer, I can create cleaner and more maintainable data pipelines. And by embracing DirectLake, I can bypass the problem entirely, moving to an architecture where large-scale data is analyzed in-place with incredible speed and minimal latency.
I hope this helps having fun with taming your own data beasts in Power BI and Microsoft Fabric.
I encourage you to test these patterns in your own environment. Start by diagnosing query folding in an existing model, and for your next project, consider building it on a Fabric Lakehouse from day one.
Comments