DAX performance optimalisatie: 7 technieken voor snellere modellen
Power BI architect, LSS Black Belt. 15 jaar ervaring in data & business intelligence.

Een traag Power BI-model kan je projecten duur te staan komen. Users raken gefrustreerd door lange laadtijden, adoptie stagneert, en business stakeholders verliezen vertrouwen in je oplossing. De oorzaak? Vaak liggen het aan inefficiënte DAX-formules die veel meer resources verbruiken dan nodig.
In deze gids leer je zeven concrete technieken die DAX-performance kunnen verbeteren met 50-80%. Deze optimalisaties zijn getest in productieomgevingen bij organisaties zoals Lyreco en GGDGHOR, waar complexe datamodellen dagelijks door honderden gebruikers worden bevraagd.
Context switching elimineren
Context switching is de grootste performance killer in DAX. Het gebeurt wanneer je filter context verandert tijdens de berekening van een measure. Elke keer dat Power BI de context moet wijzigen, moet de query engine opnieuw evalueren welke rijen relevant zijn.
Een klassiek voorbeeld is deze inefficiënte formule:
Omzet Vorig Jaar =
CALCULATE(
SUM(Sales[Amount]),
DATEADD(Calendar[Date], -1, YEAR)
)
Deze formule forceert een context switch omdat DATEADD een nieuwe datum-context creëert. Een veel snellere variant:
Omzet Vorig Jaar =
VAR CurrentYear = YEAR(MAX(Calendar[Date]))
RETURN
CALCULATE(
SUM(Sales[Amount]),
Calendar[Year] = CurrentYear - 1
)
Door de jaar-kolom direct te filteren in plaats van datums te verschuiven, vermijd je context switching. In een model met 2 miljoen rijen sales data kan dit het verschil maken tussen 3 seconden en 300 milliseconden laadtijd.
Praktische tip voor consultants
Gebruik altijd DAX Studio's Server Timings om context transitions te identificeren. Zoek naar query's met veel 'VertiPaq SE Query' events — dat zijn signs van inefficiënte context switching.
Variables voor herberekeningen voorkomen
DAX evalueert complexe expressies meerdere keren als je ze niet in een variable opslaat. Dit valt vooral op bij geneste CALCULATE-functies of bij measures die andere measures aanroepen.
Inefficiënt:
Marge Percentage =
DIVIDE(
[Total Revenue] - [Total Costs],
[Total Revenue]
)
Als [Total Revenue] een complexe berekening is, wordt deze twee keer uitgevoerd. Beter:
Marge Percentage =
VAR Revenue = [Total Revenue]
VAR Costs = [Total Costs]
RETURN
DIVIDE(Revenue - Costs, Revenue)
Deze techniek is cruciaal bij complexe datamodellen waar measures meerdere lagen diep genest zijn. Bij het GGDGHOR-project reduceerden we hiermee de tijd voor epidemiologische rapportages van 45 seconden naar 8 seconden.
Table functions optimaal inzetten
Table functions zoals SUMMARIZE en ADDCOLUMNS zijn krachtig, maar kunnen performance vernietigend zijn als je ze verkeerd gebruikt. De gouden regel: minimaliseer de hoeveelheid data die je materialiseert in memory.
Slechte approach:
Top Customers =
TOPN(
10,
SUMMARIZE(
Sales,
Customer[Name],
"Revenue", SUM(Sales[Amount])
),
[Revenue], DESC
)
Deze formule materialiseert eerst alle klanten met hun omzet voordat de top 10 wordt bepaald. Bij veel klanten kost dit gigantisch veel memory.
Betere approach:
Top Customers =
TOPN(
10,
VALUES(Customer[Name]),
[Total Revenue], DESC
)
Door TOPN direct op de klantenlijst te gebruiken en een existing measure [Total Revenue] te refereren, laat je Power BI de berekening optimaliseren. Het materialiseren van grote tussentabellen wordt vermeden.
Memory management bij table functions
Let op de 'materialization trap': table functions die grote datasets in memory laden. Gebruik bij voorkeur column references ([Column]) in plaats van calculated columns binnen table functions.
Relationship navigation optimaliseren
Power BI's VertiPaq engine is geoptimaliseerd voor specifieke relationship patterns. De manier waarop je relationships navigeert heeft dramatische impact op query performance.
One-to-many relationships presteren altijd beter dan many-to-many. Als je een many-to-many relatie hebt, overweeg dan een bridge table met two-way cross filtering uit te schakelen.
Inefficiënte navigation:
Sales by Region =
SUMX(
Sales,
RELATED(Store[Region]) * Sales[Amount]
)
Efficiënte navigation:
Sales by Region =
CALCULATE(
SUM(Sales[Amount]),
Store[Region] = "Target Region"
)
RELATED forceert row-by-row evaluatie, terwijl CALCULATE de filtering op set level kan doen. Bij grote fact tables kan dit het verschil maken tussen een minute en enkele seconden.
Aggregations op grain level
Een van de meest onderschatte optimalisaties is het afstemmen van je berekeningen op de natural grain van je data. Als je datamodel sales data heeft op transactie-level, maar 90% van je rapporten werken op maand-level, dan verspil je resources.
In plaats van altijd vanaf de lowest grain te aggregeren:
Monthly Sales =
CALCULATE(
SUM(Sales[Amount]),
DATESBETWEEN(
Calendar[Date],
DATE(2024,1,1),
DATE(2024,12,31)
)
)
Overweeg pre-aggregated tables of aggregation tables voor veelgebruikte grain levels:
Monthly Sales =
CALCULATE(
SUM(MonthlySales[Amount]),
MonthlySales[YearMonth] >= 202401 &&
MonthlySales[YearMonth] <= 202412
)
Dit reduceert niet alleen query tijd, maar ook memory footprint van je model. De trade-off is model complexity versus performance — een afweging die elke consultant moet maken.
Wanneer pre-aggregatie inzetten
Pre-aggregatie loont vooral bij:
• Fact tables > 10 miljoen rijen
• Rapporten die meestal op maand/kwartaal niveau werken
• Real-time scenarios waar snelheid kritiek is
Iterator functions bewust gebruiken
Iterator functions (SUMX, AVERAGEX, COUNTX) zijn krachtig maar gevaarlijk. Ze evalueren expressies row-by-row, wat bij grote datasets tot exponentiële performance degradatie leidt.
Vermijd iterators waar mogelijk:
// Slecht - iterator
Weighted Average =
AVERAGEX(
Sales,
Sales[Quantity] * Sales[UnitPrice]
)
// Beter - aggregatie
Weighted Average =
DIVIDE(
SUM(Sales[TotalAmount]),
SUM(Sales[Quantity])
)
Als je wel iterators moet gebruiken, minimaliseer dan het row context. Filter eerst, itereer daarna:
High Value Customers =
SUMX(
FILTER(
VALUES(Customer[CustomerID]),
[Customer Revenue] > 10000
),
[Customer Revenue]
)
Door eerst te filteren op klanten met hoge omzet, beperkt je de iteratie tot een kleinere dataset.
Column store optimalisaties
VertiPaq is een column store engine. Dat betekent dat de manier waarop je columns gebruikt direct impact heeft op compression en query speed. Een paar praktische optimalisaties:
String concatenation in DAX is duur. In plaats van:
Full Name = Customer[FirstName] & " " & Customer[LastName]
Maak een calculated column in Power Query of beter nog: in je source systeem.
Boolean filters presteren beter dan string filters. In plaats van:
Active Customers =
CALCULATE(
COUNT(Customer[CustomerID]),
Customer[Status] = "Active"
)
Gebruik:
Active Customers =
CALCULATE(
COUNT(Customer[CustomerID]),
Customer[IsActive] = TRUE
)
Boolean kolommen comprimeren beter en filteren sneller dan string vergelijkingen.
Data type impact op performance
Integers filteren 3-5x sneller dan strings. Dates zijn geoptimaliseerd voor VertiPaq. Decimals zijn langzamer dan integers — gebruik integers waar possible (bijv. bedragen in centen in plaats van euros).
Query folding en DirectQuery optimalisaties
Als je werkt met DirectQuery of Microsoft Fabric, dan shift de bottleneck van DAX naar je backend database. Hier gelden andere regels.
Query folding is je beste vriend. Transformaties die naar de database kunnen worden 'gefolded' presteren veel beter dan client-side processing. Controleer in Power Query altijd of je transformatie steps nog folden.
Voor DirectQuery DAX:
• Vermijd complex calculated columns — bereken in de source
• Gebruik native SQL functions waar mogelijk
• Beperk het aantal columns in je visuals
• Cache measures waar mogelijk met CALCULATE
Bij het Lyreco finance dashboard migreerden we van Import naar DirectQuery met Fabric. Door DAX-measures te vereenvoudigen en meer logic naar SQL te verplaatsen, behielden we de performance terwijl we real-time data kregen.
Testing en monitoring van DAX performance
Performance optimalisatie zonder meten is gokken. Gebruik deze tools om DAX performance te monitoren:
DAX Studio is essentieel voor elke Power BI consultant. Server Timings geeft je inzicht in waar tijd wordt besteed. Query Plans tonen je hoe VertiPaq je query's uitvoert.
Performance Analyzer in Power BI Desktop toont visual-level performance. Gebruik dit om bottlenecks te identificeren tijdens development.
Application Insights voor Premium capacity's geeft je productie-data over query performance. Essentieel voor rapport audits bij enterprise klanten.
Performance baselines instellen
Meet altijd voor én na optimalisaties. Stel acceptable performance targets vast — bijvoorbeeld: alle visuals laden binnen 3 seconden, reports refresh binnen 15 minuten. Dit helpt bij stakeholder management en geeft je concrete doelen.
Samenvatting
DAX performance optimalisatie draait om het begrijpen van hoe VertiPaq werkt en je formules daarop afstemmen. De zeven technieken in deze gids — context switching elimineren, variables gebruiken, table functions optimaliseren, relationship navigation verbeteren, aggregations afstemmen, iterators bewust inzetten, en column store optimalisaties — kunnen je modellen 50-80% sneller maken.
Start met de grootste impact optimalisaties: elimineer context switching en gebruik variables voor hergebruik. Meet altijd je baseline performance voordat je optimaliseert. En vergeet niet dat readable code soms belangrijker is dan marginale performance gains — vind de balans die past bij je project.
Voor complexe optimalisatie scenarios kun je de Power BI Report Auditor gebruiken om automated performance checks uit te voeren. Dit bespaart tijd bij grote modellen en helpt je systematisch bottlenecks te identificeren.