Ladění pomalých measures
Cíle kapitoly
- Identifikovat, proč je measure pomalá (SE vs. FE čas)
- Řešit Storage Engine bottleneck — agregace, kardinalita
- Řešit Formula Engine bottleneck — iterátory, podmínky
- Znát optimalizační vzory: SUMX vs. SUM, FILTER logiku, DIVIDE použití
- Používat VAR pro caching a zlepšení čitelnosti
Identifikace příčiny pomalého dotazu
Kde měřit?
- Power BI Desktop: Performance Analyzer (View tab → Performance Analyzer)
- DAX Studio: Server Timings (podrobnější analýza)
- DAX Studio Query Plan: Vidíte přesně, co dělá engine
Performance Analyzer v Power BI Desktop
Start recording → interakce s vizualizací → Stop recording
Vidíte čas pro DAX (measure evaluation), Visual rendering, Data retrieval.
DAX Studio vám ukáže SE čas vs. FE čas.
DAX Studio — Server Timings (detailní analýza)
| Parametr | Co znamená | Indikuje problém když... |
|---|---|---|
| SE CPU Time | Čas Storage Engine na CPU | Absolutní hodnota extrémně vysoká (800ms+) — vysoký podíl SE na celkovém čase je žádoucí (ideál: 80–90 % SE / 10–20 % FE) |
| FE CPU Time | Čas Formula Engine na CPU | FE čas >> SE čas (FE dominuje celkovému dotazu) — FE je single-threaded |
| Data Cache Hits | Počet přístupů z cache | Nízko → možné čtení z disku |
| CallbackDataID | FE volá SE opakovaně | Vždycky = špatný design |
Storage Engine bottleneck — řešení
Příznaky SE bottlenecku
- Server Timings: SE CPU čas je velmi vysoký (800ms+)
- Query Plan: Velký počet "Scan" operací (ne "Seek")
- Data Cache má málo hitů
Příčiny a řešení
Problém: FILTER čte všechny řádky tabulky a filtruje je (Scan operace).
TržbyPomalu := SUMX ( FILTER ( FactInternetSales, [SalesAmount] > 100 ), [SalesAmount] ) {-- Čte všech 10M řádků, pak filtruje --}
Řešení: Přidejte filtr přímo do CALCULATE jako argument, aby SE to mohl optimalizovat (predikát nad sloupcem = Seek místo Scan):
TržbyRychlé := CALCULATE ( SUM ( FactInternetSales[SalesAmount] ), FactInternetSales[SalesAmount] > 100 ) {-- CALCULATE umožní SE optimalizaci (potenciálně Seek) --}
Problém: Dotaz vrací příliš mnoho řádků z SE, FE pak musí procházet tisíce řádků.
Řešení: Přidejte Aggregations v modelu nebo reorganizujte dotaz na menší datasety.
Problém: RELATED() přes sloupec s vysokou kardinalitou → SE musí hledat v Dictionary mnohokrát.
Řešení: Ověřte, že relace je přes klíčové sloupce, nikoliv po popisných sloupcích.
Formula Engine bottleneck — řešení
Příznaky FE bottlenecku
- Server Timings: FE CPU čas je vysoký (500ms+)
- Query Plan: Mnoho "CallbackDataID" — FE volá SE v každé iteraci
- Measure s SUMX, FILTER, nebo komplexní CALCULATE logikou
Běžné problémy a řešení
TržbyPomalé := SUMX ( FactInternetSales, FactInternetSales[SalesAmount] ) {-- Iteruje zbytečně! --}
TržbyRychlé := SUM ( FactInternetSales[SalesAmount] ) {-- Storage Engine to zvládne! --}
TržbyKategoriePomalé := SUMX ( FILTER ( FactInternetSales, RELATED ( DimProductCategory[EnglishProductCategoryName] ) = "Bikes" ), [SalesAmount] )
TržbyKategorieRychlé := CALCULATE ( SUM ( FactInternetSales[SalesAmount] ), DimProductCategory[EnglishProductCategoryName] = "Bikes" )
Problém: CALCULATE v SUMX vytvořiá nový filter context v každé iteraci → velmi pomalé.
PříjemMěsíčnýVýnos := SUMX ( DimCustomer, CALCULATE ( SUM ( FactInternetSales[SalesAmount] ) ) ) {-- Context transition z DimCustomer řádku vytvoří filtr přes relaci automaticky --} {-- Pro každý zákazník generuje CallbackDataID → velmi pomalé u tisíců zákazníků --} {-- Poznámka: RELATED(FactInternetSales[CustomerKey]) ze strany One → Many je neplatné --}
Řešení: Reorganizujte jako SUMX bez CALCULATE, nebo změňte datový zdroj:
PříjemMěsíčnýVýnosRychle := SUMX ( ADDCOLUMNS ( SUMMARIZE ( FactInternetSales, DimCustomer[CustomerKey] ), "Total", SUM ( FactInternetSales[SalesAmount] ) ), [Total] ) {-- ADDCOLUMNS + SUMMARIZE: bezpečný vzor doporučený SQLBI --} {-- SUMMARIZE s výrazovými sloupci ("Total", SUM(...)) je deprecated --} {-- může vracet nesprávné výsledky kvůli interakci kontextů --}
Konkrétní optimalizační vzory
Vzor 1: Bidireční filtr — nasazujte obezřetně
Bidireční relace mezi DimDate a FactSales umožňuje filtr z obou stran. DAX engine musí propagovat filtry obousměrně — u velkých tabulek nebo složitých modelů to může být výrazně pomalejší. U malých dimenzí může být dopad zanedbatelný.
Řešení: Pokud možno, používejte jednosměrné relace. Pokud je nutný filtr z Many na One, použijte CROSSFILTER(), TREATAS() nebo obousměrnou relaci — ALL() filtry odstraňuje, nepropaguje je.
Vzor 2: DIVIDE — bezpečné dělení
ProcemaVýnosPercentage = [Profit] / [Sales]
ProcemaVýnosPercentage = DIVIDE ( [Profit], [Sales], 0 )
Hlavní výhoda DIVIDE() je bezpečnost — vrátí BLANK nebo alternativní hodnotu místo chyby při dělení nulou. Výkonnostní rozdíl oproti operátoru / není dokumentován.
VAR — caching a výkon
VAR (Variable) je mocný nástroj pro výkon a čitelnost. Deklarujete hodnotu jednou, pak ji použijete vícekrát — bez opakovaného výpočtu.
Výhody VAR
- Výkon: Hodnota se vypočítá jednou, pak se jen odkazuje
- Čitelnost: Kód je čitelnější (pojmenované proměnné místo nested funkcí)
- Údržba: Změna logiky se dělá na jednom místě
Příklad: Bez VAR vs. S VAR
TržbyVýnosPercento = DIVIDE ( SUM ( FactInternetSales[SalesAmount] ) - SUM ( FactInternetSales[Cost] ), SUM ( FactInternetSales[SalesAmount] ) ) {-- SUM(SalesAmount) se počítá 2x! --}
TržbyVýnosPercentoVAR = VAR Sales = SUM ( FactInternetSales[SalesAmount] ) VAR Cost = SUM ( FactInternetSales[Cost] ) RETURN DIVIDE ( Sales - Cost, Sales )