GOC644 — DAX základy (2 dny)
1. Úvod do Power BI 2. Datový model 3. Úvod do DAX 4. Oblasti použití DAX 5. Kontext výpočtu 6. Časová inteligence 7. Optimalizace DAX 8. Row Level Security 9. Nástroje
GOC6441 — Pokročilý DAX (2 dny)
1. Opakování & přehled 2. Dynamické reporty 3. DAX jako dotazovací jazyk 4. VertiPaq engine 5. Ladění výkonu 6. Composite Models 7. Workshop
Cvičení
1. Datový model 2. DAX základy 3. Kontext výpočtu 4. Časová inteligence
🏠 Rozcestník kurzů
GOC6441 · Kapitola 5

Ladění pomalých measures

Performance tuning — identifikace bottlenecků, Server Timings, optimalizační vzory a praktické řešení
⏱ 80 minut🔧 Performance Analyzer
📝
Na blogu Jiřího Neorala: Power BI - Performance Analyzer — jak číst Performance Analyzer — vizuální průvodce

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
5.1

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

1
Otevřete Home tab → Performance Analyzer

Start recording → interakce s vizualizací → Stop recording

2
Čtěte výsledky

Vidíte čas pro DAX (measure evaluation), Visual rendering, Data retrieval.

3
Pokud je DAX čas vysoký (>1000ms), copy a paste do DAX Studio pro detailnější analýzu

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
💡
Pravidlo: Optimální poměr je SE 80–90 % / FE 10–20 %. Pokud je absolutní SE čas extrémně vysoký (např. 800ms+), problém je v datovém modelu — vysoká kardinalita, chybějící agregace. Pokud FE čas dominuje (FE >> SE), problém je v DAX logice — iterátory, CallbackDataID volání.
5.2

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í

Příčina 1: FILTER na velkou tabulku s vysokou kardinalitou▼

Problém: FILTER čte všechny řádky tabulky a filtruje je (Scan operace).

DAX (Pomalé)
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):

DAX (Rychlé)
TržbyRychlé := CALCULATE (
  SUM ( FactInternetSales[SalesAmount] ),
  FactInternetSales[SalesAmount] > 100
)
{-- CALCULATE umožní SE optimalizaci (potenciálně Seek) --}
Příčina 2: Chybějící agregace nad miliony řádků▼

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.

Příčina 3: RELATED() přes špatný sloupec▼

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.

5.3

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í

Problém 1: SUMX namísto SUM▼
DAX (Pomalé — SUMX bez logiky)
TržbyPomalé := SUMX (
  FactInternetSales,
  FactInternetSales[SalesAmount]
)
{-- Iteruje zbytečně! --}
DAX (Rychlé — SUM)
TržbyRychlé := SUM ( FactInternetSales[SalesAmount] )
{-- Storage Engine to zvládne! --}
Problém 2: FILTER s RELATED v SUMX▼
DAX (Pomalé)
TržbyKategoriePomalé := SUMX (
  FILTER (
    FactInternetSales,
    RELATED ( DimProductCategory[EnglishProductCategoryName] ) = "Bikes"
  ),
  [SalesAmount]
)
DAX (Rychlejší)
TržbyKategorieRychlé := CALCULATE (
  SUM ( FactInternetSales[SalesAmount] ),
  DimProductCategory[EnglishProductCategoryName] = "Bikes"
)
Problém 3: Nevhodný CALCULATE uvnitř iterátoru▼

Problém: CALCULATE v SUMX vytvořiá nový filter context v každé iteraci → velmi pomalé.

DAX (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:

DAX (Mnohem rychlejší — ADDCOLUMNS + SUMMARIZE)
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ů --}
5.4

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í

DAX (Špatně — dělení nulou = error)
ProcemaVýnosPercentage = [Profit] / [Sales]
DAX (Správně)
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.

5.5

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

DAX (Bez VAR — opakovaný výpočet)
TržbyVýnosPercento = DIVIDE (
  SUM ( FactInternetSales[SalesAmount] ) - SUM ( FactInternetSales[Cost] ),
  SUM ( FactInternetSales[SalesAmount] )
)
{-- SUM(SalesAmount) se počítá 2x! --}
DAX (S VAR — optimalizovaný)
TržbyVýnosPercentoVAR =
VAR Sales = SUM ( FactInternetSales[SalesAmount] )
VAR Cost = SUM ( FactInternetSales[Cost] )
RETURN
DIVIDE ( Sales - Cost, Sales )
✓
Best Practice: Vždy používejte VAR pro hodnoty, které se opakují v measure. Je to výkonnější a kód je čitelnější.