Direct Lake pod kapotou: Transcoding, Framing, Residency, a Eviction
- Vojtěch Šíma
- Jul 23
- 20 min read
Updated: Jul 26
TL;DR: Direct Lake je storage mode pro sémantické modely, který kombinuje čerstvost dat ve stylu DirectQuery, ne úplně doslova, s výkonem blízkým Importu. Je dostupný pouze v Microsoft Fabricu, používá Delta tabulky uložené v OneLake a data může číst buď přímo z Delta tabulek, nebo přes SQL analytics endpoint. Jakmile se model zahřeje, Direct Lake obvykle nabídne výkon srovnatelný s Importem, ale spotřebuje více capacity units (CU). Zahřívání v podstatě cachuje jednotlivé sloupce do paměti, díky čemuž potom dostaneš rychlost podobnou Importu. Samotný warm-up ale může být pomalý, takže s tím musíš počítat a ideálně model pro uživatele předem zahřát. Direct Lake funguje nejlépe nad dobře optimalizovanými a správně udržovanými Delta tabulkami. Zároveň by měl dodržovat stejné principy datového modelování jako klasický Import sémantický model.
Disclaimer: Pokud není uvedeno jinak, Direct Lake benchmarky jsou provedené nad Direct Lake on OneLake. Každý sémantický model zároveň načítá data pouze z jednoho Lakehouse, bez ohledu na storage mode.
Kvůli rozsahu článku byla většina přeložená strojově. Omlouvám se za kostrbatější češtinu.
Co je Direct Lake?
Direct Lake je jedna z možností, jak v Power BI sémantickém modelu ukládat tabulky. Rozšiřuje již existující možnosti, jako jsou Import a DirectQuery.
Pro rychlý obrázek si můžeš Direct Lake představit jako kombinaci Import režimu, protože nabízí relativně rychlé odpovědi, a DirectQuery, protože nepotřebuje "klasický" refresh sémantického modelu.
Tím ale samozřejmě celý příběh nekončí.
Direct Lake spolupracuje s Microsoft Fabricem a jeho OneLake úložištěm, přesněji s Delta tabulkami, které jsou v něm uložené. Místo toho, aby se vytvořila a uložila další kompletní kopie dat uvnitř sémantického modelu, načítá Direct Lake potřebné sloupce přímo z Delta tabulek do paměti až ve chvíli, kdy jsou potřeba.
Jakmile se sloupce načtou, uloží se do stejných VertiPaq struktur, které používá Import režim, a report je potom může obsluhovat přímo z paměti.
Ve zkratce, později to rozeberu podrobněji, zaplatíš za prvotní zahřátí cache. Potom, dokud potřebná data zůstanou zahřátá a nedojde k jejich "vyhození" (eviction), bys měl dostávat výkon podobný Import režimu.
Teoreticky.
Direct Lake on SQL
Direct Lake má aktuálně dvě varianty.
Direct Lake on SQL dává smysl hlavně tehdy, když potřebuješ objekty ze SQL analytics endpointu nebo zabezpečení postavené nad SQL. Pro zjišťování schématu a kontrolu oprávnění používá SQL analytics endpoint Lakehouse nebo Warehouse. U kompatibilních fyzických tabulek ale stále načítá podkladová Delta data přímo z OneLake.
Pokud používáš SQL views nebo SQL RLS, může/bude docházet k fallbacku na DirectQuery. Výkon pak bude přirozeně horší.
Do sémantického modelu zároveň nemůžeš přidat další datové zdroje. Výjimkou jsou další Direct Lake on SQL tabulky ze stejného Fabric datového zdroje.
Pro mě je to méně zajímavá varianta. Pokud není uvedeno jinak, zbytek článku se zaměřuje na Direct Lake on OneLake.
Direct Lake on OneLake
OneLake varianta je o něco zajímavější. Připojuje se přímo k Delta tabulkám a zbavuje se synchronizace přes SQL analytics endpoint. Na pozadí se používá jiný konektor a v podstatě čteš přímo soubory.
Přirozeně zde nedochází k fallbacku na DirectQuery, protože není žádné SQL, na které by se dalo fallbacknout. Zároveň můžeš do modelu přidat další tabulky používající Import režim.
Pokud není uvedeno jinak, článek popisuje chování Direct Lake on OneLake.
Jak vytvořit sémantický model s Direct Lake
Nejjednodušší mi přijde vytvořit model přes Fabric nebo Power BI Service, buď pomocí nového Itemu ve workspace, nebo přes možnost New semantic model uvnitř Lakehouse nebo Warehouse.
Můžeš ho vytvořit také v Power BI Desktopu, ale mně osobně přijde jednodušší začít na webu a potom model upravovat v Desktopu.




Při tomto postupu si ale nemůžeš zvolit konkrétní variantu Direct Lake. Pokud ji chceš vybrat hned od začátku, otevři Warehouse nebo Lakehouse, přejdi do jeho SQL Analytics Endpointu a klikni na New semantic model. V tomto případě dostaneš také možnost vybrat Storage Mode.


Zde nastavíš název, workspace, ve kterém bude model uložený, Storage Mode a nakonec tabulky, které chceš použít. Jakmile to dokončíš, přesuneš se do okna pro zobrazení nebo úpravu sémantického modelu. Pokud ho chceš upravovat, můžeš si vybrat mezi webem a Desktopem. Od tohoto bodu už je postup standardní.

Jedna věc, které jsem si všiml: pokud ještě nejsi přihlášený v Power BI Desktopu, může to vyhodit následující chybu:(Cannot access a disposed object. Object name: 'MainForm'.). Stačí otevřít prázdný Power BI Desktop, přihlásit se a zkusit to znovu.

Protože Lakehouse je, nebo brzy bude, ve výchozím nastavení schema-enabled, při vytváření tabulek se ujisti, že používáš správnou cestu /schema/table. Pokud v schema-enabled Lakehouse neuvedeš schéma, při vytváření modelu to začne házet chyby. Může jít například o tuto: "We cannot refresh this semantic model because this semantic model uses a default data connection without explicit connection credentials. Please replace the default data connection in the semantic model settings with an explicit cloud or gateway data connection." Tato chyba může ukazovat také na jiné problémy, ale tohle je jeden z možných důvodů.
Jak ověřit storage mode Direct Lake
Nejjednodušší způsob, jak ověřit použitý storage mode, je otevřít Power BI Desktop, upravit sémantický model, otevřít TMDL a při procházení modelu se podívat do části Expressions.

Už podle názvu DirectLake - default_lakehouse, kde default_lakehouse může být například název tvého Lakehouse, můžeš poznat, že jde o OneLake. Pokud si chceš zkontrolovat samotný kód, otevři expression v TMDL.

Jasným znamením je použití funkce AzureStorage.DataLake(), která ukazuje na OneLake.
Uvidíš také další anotace, například PBI_RemovedChildren. Doporučil bych na ně nesahat, protože jejich úprava nebo manuální editace nových tabulek může způsobit problémy
Pokud v expression vidíš DatabaseQuery, znamená to Direct Lake on SQL.

Pokud chceš obecně zkontrolovat, zda tabulka používá Direct Lake, opět použij TMDL.
Měl bys vidět mode: directLake


Kdybys expression zkopíroval a pokusil se vytvořit Direct Lake model kompletně od nuly bez použití UI, nebude to fungovat. Alespoň ne přes Power BI Desktop. Nepomůže ani to, že budeš mít úplně stejné queries a definice. Vždy tedy nejdříve projdi přes UI.
S OneLake storage mode můžeš také úplně normálně přidávat další data přes Power Query, stejně jako jsi zvyklý u Import nebo DirectQuery režimu.
Jak Direct Lake funguje?
Teď se pojďme podívat na to, jak celá věc funguje. Mým cílem je dát ti více informací, než kolik najdeš v oficiální dokumentaci. Základní definice vycházejí z dokumentace Microsoftu s mojí vlastní interpretací. Další testy, zjištění a benchmarky už jdou za hranice dokumentace.
Transcoding neboli načítání sloupců
Tohle je jedna z hlavních operací Direct Lake.
Když odešleš DAX query, typicky při otevření Power BI reportu nebo při přímém dotazování sémantického modelu, engine zkontroluje všechny sloupce potřebné k dokončení požadavku a chybějící sloupce načte do paměti.
Patří sem sloupce používané v measures, filtrech, relationships, groupingu a v podstatě kdekoliv jinde, kde jsou potřeba pro dokončení query.
Tento proces je obvykle nejpomalejší částí cold Direct Lake query. Probíhá ale pouze tehdy, když je sloupec požadovaný poprvé nebo už není načtený v paměti (resident).
Opakování stejné query po prvotním načtení může dosáhnout výkonu podobného Importu, protože jakmile jsou data v paměti, funguješ v podstatě stejně jako u Import režimu.
Rychlost transcodingu závisí především na kardinalitě sloupce a také na tom, jak jsou data rozložená mezi soubory a row groups. Například sloupec obsahující textová GUID bude trvat výrazně déle než numerický sloupec s nízkou kardinalitou.
Sloupec zůstává resident v paměti, dokud nemá důvod odejít. K tomu může dojít například tehdy, když:
změníš podkladová data,
sloupec dlouhou dobu nedotazuješ,
engine potřebuje vytvořit místo pro urgentnější sloupce.
Všechny tyto scénáře rozeberu později podrobněji.
Framing
Framing je lowkey pouze refresh sémantického modelu.
Přesněji řečeno určuje, kterou verzi jednotlivých Delta tabulek má Direct Lake používat. Delta tabulka je v podstatě seznam Parquet souborů sledovaných v Delta logu. Když chceš načíst konkrétní verzi, Delta použije log k identifikaci souborů, které byly v daném momentu aktivní.
Framing vytvoří tento point-in-time odkaz pro Direct Lake. Kvůli tomu nemusí framed verze vždy odpovídat nejnovější verzi Delta tabulky.
Z pohledu residency framing určuje, která data se případně načtou do paměti.
Ve výchozím nastavení probíhá synchronizace automaticky. Když zapíšeš nová data, Direct Lake se obvykle během krátké doby přesune na nejnovější Delta verzi. Záleží na objemu dat, ale může se to stát například během deseti sekund.
Automatickou synchronizaci můžeš také vypnout a framing řídit manuálně nebo programově.
To se hodí, když nepotřebuješ nejnovější verzi okamžitě nebo chceš, aby se několik aktualizací tabulek zpřístupnilo najednou.
Ověření framingu
Pokud chceš porovnat nejnovější Delta verzi s verzí, kterou aktuálně používá Direct Lake, musíš je načíst ze dvou různých míst.
Nejnovější verzi Delta tabulky získáš přímo z Delta logu. Můžeš použít Spark nebo non-Spark knihovnu, například deltalake, která používá delta-rs.
Ve Sparku můžeš použít Spark Delta Lake API:
from delta.tables import DeltaTable
delta_version = (
DeltaTable.forName(spark, "dbo.dim_customer")
.history(1)
.select("version")
.first()["version"]
)Bez Sparku můžeš použít knihovnu deltalake a otevřít tabulku přímo přes její OneLake cestu:
from deltalake import DeltaTable
delta_table = DeltaTable(table_path)
delta_version = delta_table.version()Framed verze se načítá samostatně ze sémantického modelu:
EVALUATE
INFO.DELTATABLEMETADATASTORAGES()Důležitým sloupcem je CurrentVersion, který ukazuje Delta verzi aktuálně drženou sémantickým modelem.
Následně jsem vypnul automatickou synchronizaci, zaznamenal obě verze a dotázal sémantický model. Potom jsem přidal nové řádky do všech Delta tabulek, znovu zkontroloval verze, chvíli počkal a zopakoval stejnou query. Nakonec jsem automatickou synchronizaci opět zapnul a všechno změřil ještě jednou.
from sempy_labs import directlake
directlake.set_autosync(
dataset="directlake_on_onelake",
workspace="direct_lake_consumption",
enable=False
)Fáze | Autosync | Delta verze | Framed verze | Viditelné nové řádky |
Před zápisem | Vypnutý | 2 | 0 | 0 |
Po zápisu | Vypnutý | 3 | 0 | 0 |
Po 30 sekundách | Vypnutý | 3 | 0 | 0 |
Po zapnutí autosync | Zapnutý | 3 | 3 | 9 |
Row groups
Jak jsem zmiňoval dříve, Delta tabulka se skládá z Parquet souborů. Parquet soubor ukládá data ve sloupcovém formátu, zároveň je ale horizontálně rozdělený do jedné nebo více row groups.
Row group je v podstatě dávka řádků uvnitř Parquet souboru. Každá row group obsahuje data pro všechny sloupce, každý sloupec je ale uložený samostatně ve vlastním column chunku.
Počet row groups v jednom souboru závisí na tom, jak byl soubor zapsaný. Roli hraje například velikost souboru, cílová velikost row group, použitý writer engine nebo způsob batchování.
V našem příkladu obsahuje hlavní Parquet soubor 750 000 řádků a pouze jednu row groupu. Tři malé append soubory obsahují pouze tři řádky každý, ale každý soubor má stále vlastní row group.
Typ souboru | Řádky | Row groups | Sloupce | Column segments |
Hlavní soubor | 750 000 | 1 | 7 | 7 |
Každý ze 3 malých append souborů | 3 | 1 | 7 | 7 |
Všechny 4 aktivní soubory | 750 009 | 4 | 7 | 28 |
Optimalizovaný layout | 750 009 | 1 | 7 | 7 |
Column chunks a column segments
V Parquetu se sloupcová část row group nazývá column chunk. Když Direct Lake transcoduje data do VertiPaq, tyto column chunky se uvnitř sémantického modelu změní na column segments.
VertiPaq je in-memory sloupcový storage engine, který Power BI používá pro rychlé zpracování DAX queries. V Direct Lake se potřebné Parquet column chunky transcodují do VertiPaq column segmentů až ve chvíli, kdy jsou potřeba. Celý model se tedy neimportuje předem.
Pokud má jedna row groupa sedm sloupců, obsahuje sedm column chunků a vytvoří sedm column segmentů. Pokud má Delta tabulka čtyři row groupy, každý sloupec sémantického modelu bude mít čtyři segmenty.
Právě tyto segmenty může Direct Lake načítat do paměti, držet jako resident nebo evictovat. Pokud query potřebuje pouze několik sloupců, engine musí načíst pouze segmenty těchto sloupců, nikoliv celou tabulku.
To je také důvod, proč mohou být malé soubory a příliš mnoho row groups otravné.
V našem příkladu vytvoří každý třířádkový append soubor sedm nových column chunků a sedm nových column segmentů, přestože v něm v podstatě žádná data nejsou.
Každý column chunk obsahuje také metadata, například kompresi, encoding, počet null hodnot, minimum, maximum a komprimovanou velikost.
Sloupec | Řádky v chunku | Komprimovaná velikost | Min | Max |
customer_key | 750 000 | 3,6 MB | 1 | 750 000 |
customer_code | 750 000 | 3,8 MB | CUST-0000001 | CUST-0750000 |
customer_name | 750 000 | 3,8 MB | Customer 0000001 | Customer 0750000 |
segment | 750 000 | 191 KB | Consumer | Small Business |
country | 750 000 | 284 KB | Austria | Slovakia |
is_active | 750 000 | 87 KB | false | true |
Tohle je pěkný příklad toho, proč sloupcové ukládání funguje tak dobře. Sloupec s nízkou kardinalitou, například is_active, je velmi malý. Textové sloupce jako customer_name jsou naopak podstatně větší.
Pokud DAX query potřebuje pouze segment a is_active, Direct Lake načte jen segmenty těchto sloupců, nikoliv celý Parquet soubor.
Neoptimalizovaná tabulka s appendy by potom mohla pro sloupec customer_key vypadat například takto:
Typ souboru | Řádky v souboru | Row group | Hodnoty segmentu | Komprimovaná velikost | Min | Max |
Hlavní soubor | 750 000 | 0 | 750 000 | 3 566 266 bytes | 1 | 750 000 |
Malý soubor 1 | 3 | 0 | 3 | 85 bytes | 750001 | 750003 |
Malý soubor 2 | 3 | 0 | 3 | 85 bytes | 750004 | 750006 |
Malý soubor 3 | 3 | 0 | 3 | 57 bytes | 750007 | 750009 |
Lokální a globální dictionaries
Poslední věcí, kterou chci probrat před přechodem k residency, jsou dictionaries.
Dictionaries nejsou pouze záležitostí VertiPaq. Parquet i VertiPaq mohou používat dictionary encoding. Místo ukládání celé hodnoty pro každý jednotlivý řádek uloží dictionary každou unikátní hodnotu pouze jednou a přiřadí jí numerické ID.
Velikost dictionary je proto přímo spojená s kardinalitou sloupce. Sloupec jako is_active potřebuje pouze dvě hodnoty. Jméno zákazníka nebo textový GUID sloupec může naopak potřebovat stovky tisíc, případně i miliony hodnot.
V Parquetu jsou dictionaries lokální pro jednotlivé column chunks. Pokud je sloupec rozdělený mezi čtyři row groups, Direct Lake může potřebovat zpracovat čtyři samostatné lokální dictionaries pro jediný sloupec.
VertiPaq naopak používá jednu globální dictionary pro celý sloupec sémantického modelu. V Import režimu se tato globální dictionary vytvoří během refreshu. V Direct Lake vzniká během transcodingu kombinováním lokálních Parquet dictionaries a mapováním jejich lokálních ID na globální ID.
Zdroj | Lokální dictionary | Uložená lokální ID | Globální ID |
Row group 1 | Consumer = 0, Small Business = 1 | 0, 0, 1 | 0, 0, 2 |
Row group 2 | Enterprise = 0, Consumer = 1 | 0, 1, 1 | 1, 0, 0 |
Row group 3 | Consumer = 0 | 0, 0, 0 | 0, 0, 0 |
Globální VertiPaq dictionary | Consumer = 0, Enterprise = 1, Small Business = 2 |
Více row groups tedy může znamenat také více lokálních dictionaries, které musí Direct Lake zpracovat a sloučit. Další důvod, proč vytváření tisíců malých souborů a row groups pravděpodobně není úplně nejlepší nápad.
Ověření dictionaries
Lokální dictionaries můžeš ověřit tak, že Parquet načteš přímo přes Python:
parquet_file = parquet.ParquetFile(
parquet_file_path,
read_dictionary=["segment"]
)
dictionary_array = (
parquet_file
.read_row_group(0, columns=["segment"])
.column("segment")
.chunk(0)
)
dictionary_array.dictionary.to_pylist()
dictionary_array.indices.to_pylist()U globální dictionary nemůžeš získat pěkné propojení indexu s konkrétními hodnotami.
Nějakou představu můžeš získat pomocí:
EVALUATE
INFO.DICTIONARYSTORAGES()V kombinaci s:
DISTINCT('dim_customer'[segment])Residency a temperature
Jakmile Direct Lake transcoduje Parquet column chunk, výsledný VertiPaq column segment se může stát resident (doslova ubytovaný v paměti). To znamená, že je aktuálně načtený v paměti a další queries ho mohou znovu použít. Ve zkratce je sloupec připravený obsloužit data bez dalšího zpoždění. Samozřejmě kromě zpoždění způsobeného tvým vlastním DAXem a modelem, ale s tím ti Direct Lake úplně nepomůže.
Nejjasnější způsob kontroly je hodnota ISRESIDENT z:
INFO.STORAGETABLECOLUMNSEGMENTS()Samotné načítání může dominovat době odezvy vizuálu. V jednom z mých testů trvala první query 33,11 sekundy, zatímco se potřebná data načítala. Jakmile byl stejný working set resident, následující spuštění se dokončila přibližně za jednu sekundu.
Poznámka: Stovky sekund v některých dalších testech byly primárně výsledkem stress testingu. Zároveň ale pěkně ukázaly, jak moc dokáže špatně navržený Parquet layout rozbít transcoding.
Jakmile jsou potřebné struktury resident, další queries je mohou rovnou znovu použít.
Právě proto je možné předem načíst části Direct Lake modelu spuštěním queries nad sloupci a relationships, které budou uživatelé pravděpodobně potřebovat. K tomu se ještě vrátím.
Každý resident segment má také temperature, která se mění podle toho, jak nedávno a jak často byl použitý. Nedávno dotazovaný segment má obvykle vyšší temperature. Segment, kterého se nikdo dlouho nedotkl, postupně chladne.
Podle mých testů nebyla temperature přímým prediktorem délky query, dokud potřebné segmenty zůstávaly resident. Nejdůležitější je právě to, zda jsou požadovaná data resident. Jakmile segmenty resident byly, mohla jejich temperature výrazně klesnout, aniž by se doba query nějak zásadně změnila.
V jednom testu klesla průměrná temperature z 1.5244 na 0.0114, zatímco všech 4 374 segmentů zůstalo resident. Stejná query se nadále dokončovala přibližně za jednu sekundu.
V tomto testu trvalo pozorování přibližně sedm hodin a temperature se dostala téměř na nulu. Pozdější queries ale zůstaly rychlé a nedošlo k transcoding prodlevě, protože většina segmentů byla stále resident.
V mém aktuálně nejdelším pozorování zůstaly některé column segmenty resident téměř dva dny.Přirozeně už šlo pouze o malou část původních segmentů.
Segment s temperature téměř na nule tedy může být stále okamžitě znovu použitý.
Direct Lake ho nemusí znovu načítat a transcodovat jen proto, že má nízkou temperature. To je potřeba až ve chvíli, kdy segment přestane být resident.
Fáze | Uplynulý čas | Průměrná temperature | Resident segmenty | Délka probe |
Prvotní načtení | 0,0 min | 1.4260 | 3 023 | 61,66 s |
Idle checkpoint 1 | 60,2 min | 0.02196 | 3 023 | 1,90 s |
Idle checkpoint 2 | 183,0 min | 0.000332 | 3 023 | 1,80 s |
Idle checkpoint 3 | 424,4 min | 0.000000083 | 3 023 | 2,48 s |
Po závěrečných probes | 425,8 min | 1.4022 | 3 023 | 1,80 s |
Testováno na sémantickém modelu s fact tabulkou obsahující 500 milionů řádků a několika sloupci s vysokou kardinalitou.
Eviction
K eviction (vyhození) dochází, když Direct Lake odstraní již načtené segmenty z paměti.
Když je pozdější query znovu potřebuje, musí se znovu načíst a transcodovat. Tím se může vrátit zpoždění typické pro první query.
Dříve jsem zmínil tři možné důvody, proč k tomu může dojít. Teď si každý z nich rozebereme podrobněji.
Změnila se podkladová data
Když Direct Lake provede framing nové verze Delta tabulky, porovná fyzický stav úložiště reprezentovaný Delta logem. Existující Parquet row groups, které zůstávají součástí tabulky, mohou nadále sloužit jako podklad pro stejné resident column segments.
Row groups, které byly odstraněné nebo nahrazené, už znovu použít nelze. Odpovídající segmenty proto přestanou být resident.
Důležitý je tedy fyzický rozsah operace.
Jednoduchý append ponechá existující Parquet soubory a row groups na místě. Direct Lake může zachovat existující resident working set a pouze přidat segmenty pro nově appendovaná data.
Overwrite, update, částečný rewrite nebo compaction naopak nahrazují existující soubory. Jakmile model provede framing nové Delta verze, segmenty založené na odstraněných souborech přestanou být platné. To platí i v případě, že nové soubory obsahují logicky úplně stejné hodnoty.
Kdybychom si chtěli hrát na slovíčkaření, technicky to data neevictuje. Prostě zničíš to, co tam bylo předtím, takže původní segmenty už nelze použít. Já bych to ale pořád počítal jako eviction, protože výsledkem je stejně to, že musíš column segments znovu načíst.
Storage operace | Fyzická změna | Logická změna | Residency po framingu | Zjištění |
Append 20M řádků do nových souborů | Existující soubory zachovány, nové soubory přidány | Existující working set zachován, celková residency se zvýšila | Append nezneplatnil existující segmenty | |
Full overwrite stejných 20M řádků | Všechny 3 soubory nahrazeny 3 novými soubory | Žádná | Medium workload: 147 → 21; high-cardinality workload: 105 → 21 | Nahrazení souborů zneplatnilo residency navzdory identickým logickým datům |
Rewrite 1 ze 100 bucketů | 1 soubor odstraněn a nahrazen | Identické nebo změněné hodnoty | Numeric: 700 → 695; high cardinality: 600 → 596 | Invalidace odpovídala nahrazenému souboru |
Rewrite 10 ze 100 bucketů | 10 souborů odstraněno a nahrazeno | Identické nebo změněné hodnoty | Numeric: 700 → 650; high cardinality: 600 → 560 | Větší fyzický rewrite zneplatnil větší rozsah |
Rewrite 25 ze 100 bucketů | 25 souborů odstraněno a nahrazeno | Identické nebo změněné hodnoty | Numeric: 700 → 575; high cardinality: 600 → 500 | Ztráta residency dál odpovídala rozsahu rewritu |
Compact 250 souborů do 3 | 250 starých souborů nahrazeno 3 novými | Žádná | Medium workload: 1 750 → 20; high cardinality: 1 250 → 20 | Fyzická optimalizace způsobila téměř kompletní invalidaci |
Overwrite jedné dimenze | Soubory dimenze nahrazeny | Hodnoty dimenze přepsány | Dimension: 5 → 1; fact zůstala na 500 | Invalidace zůstala omezená na změněnou tabulku |
Sloupec nebyl dlouho dotazovaný
Když sémantický model vůbec nedotazuješ nebo dotazuješ pouze jeho část, nedotčené sloupce mohou časem přijít o resident data.
Důležité slovo je zde časem.
Pasivní unloading jsem opakovaně pozoroval u modelů s 5M, 10M a 50M řádky na F2 i F64. Nepodařilo se mi ale najít fixní timeout, použitelný temperature threshold ani v podstatě jakoukoliv jinou hodnotu, podle které by se dalo konzistentně předpovědět, kdy k unloadingu dojde.
Hodnoty 50M, 10M nebo 5M představují počet řádků ve fact tabulce modelu. Model mohl navíc obsahovat další menší dimenze. Všechny modely byly vygenerované a obsahovaly kombinaci textových a numerických sloupců s vysokou až nízkou kardinalitou.
Níže je několik testů a pozorování. Hned pod tabulkou je zkusím rychle vysvětlit.
Test | Průběh residency | Celkový čas do eviction |
50M F64 | 100 % po 18,8 h → 47 % po 19,3 h → 43 % po 22,8 h → 0 % po 46,8 h | 46,8 h |
5M F64, run 1 | 100 % po 1,66 h → 94 % → 61 % → 27 % → 7 % → 0 % po 2,89 h | 2,89 h |
5M F64, run 2 | 100 % po 1,37 h → 84 % po 1,62 h → 82 % po 3,37 h → 9 % → 0 % po 3,89 h | 3,89 h |
5M F2, run 1 | 100 % po 3,65 h → 0 % po 4,15 h | 4,15 h |
5M F2, run 2 | 100 % po 7,30 h → 0 % po 7,55 h | 7,55 h |
10M F2, F64, run 1 | F64: 100 % → 0 % přibližně po 4,9 h; F2: 100 % → 69 % → 65 % → 29 % → 0 % po 12,16 h | F64: 4,90 h; F2: 12,16 h |
10M F2, F64, run 2 | F2: 100 % → 61 % → 0 % po 9,47 h; F64: 100 % → 74 % → 73 % → 61 % → 60 % → 0 % po 14,72 h | F2: 9,47 h; F64: 14,72 h |
5M F2 keepalive | 100 % po 17,83 h → 6,9 % po 18,07 h → 6,9 % po 27,33 h | Nedotčené sloupce: 18,07 h; aktivní sloupec nebyl evictovaný ani po 27,33 h |
Abych benchmarky a měření správně vysvětlil, ne každé měření probíhalo kompletně izolovaně na vlastní kapacitě bez dalších operací běžících na pozadí. U F2 testů tomu tak většinou bylo. U F64 testů už tolik ne. Ber toto prosím v úvahu. Ať už ale test izolovaný byl, nebo nebyl, neexistuje jasný pattern určující, jak dlouho data zůstanou resident.
Z výsledků by se dalo argumentovat, že větší kapacity mohou držet data resident déle, ale zatím bych si to netroufl tvrdit. Možná zajímavější zjištění je, že udržování některých sloupců aktivních může výrazně oddálit kompletní eviction.
Za zmínku stojí také to, že dictionaries byly obvykle evictovány jako poslední. Mezi poslední eviction segmentu a eviction dictionaries mohlo uplynout několik hodin.
To by mohlo být důležité, protože neevictované dictionaries by technicky mohly zrychlit pozdější warm-up přibližně o 10 %. Šlo ale pouze o korelaci. Nemohu říct, že je to fakt.
Jediné, co mohu na základě testů říct s větší jistotou, je pattern samotné eviction, alespoň z pohledu toho, co bývá evictováno. V případech, kdy nedošlo k velkému pádu ze 100 % rovnou na 0 %, byly prvními evictovanými column segments obvykle textové sloupce s vysokou kardinalitou z fact tabulky. Po nich následovaly sloupce se střední až nízkou kardinalitou. Numerické sloupce a sloupce dimenzí byly obvykle evictovány jako poslední.
Z pohledu samotného sloupce Direct Lake přirozeně nerozlišuje mezi fact a dimension sloupcem. Pravděpodobnější příčinou je kombinace vysoké kardinality a opakovaných hodnot, kvůli které jsou tyto sloupce obvyklými podezřelými pro dřívější odstranění.
Engine potřebuje vytvořit místo pro urgentnější sloupce
„Urgentní sloupce“ zde neznamenají žádnou business prioritu, důležitost ani speciální flag. Znamená to jednoduše, že query požaduje sloupec, který aktuálně není resident.
Pokud není dostatek paměti pro načtení tohoto sloupce a současné zachování všeho, co už je načtené, může Direct Lake některé resident data segments evictovat a vytvořit tím místo.
Každá capacity SKU poskytuje Direct Lake sémantickému modelu jiný maximální objem dat, který může napagovat do paměti. V době psaní článku je dokumentované maximum 3 GB pro F2 a 25 GB pro F64.
Množství paměti spotřebované jednotlivými sloupci ovlivňuje kardinalita a délka hodnot, především u textových sloupců. Roli ale hraje také datový typ, distribuce hodnot, komprimovatelnost, počet řádků a segmentace. Pro jednoduchou představu budou krátké a často se opakující hodnoty obvykle vyžadovat méně paměti než stejné množství dlouhých a unikátních textových hodnot.
Udělal jsem fakt hodně testů, ale najít jasné a vždy platné patterny bylo opravdu těžké. Alespoň jeden pattern se mi ale podařilo najít a vypadá opakovatelně. Zároveň je trochu očividný.
Recency
Nejčistším patternem, který jsem našel, je recency. Sloupce, které byly krátce před vznikem memory pressure (tlak na paměť) scanované nebo dotazované, měly výrazně vyšší šanci přežít načtení nových sloupců. V kontrolovaných F2 a F64 page testech si nedávno scanovaná stránka pod stejným tlakem udržela přibližně o 20 až 30 segmentů ze 100 více než porovnávaná stránka.
Recency zároveň korelovala s temperature segmentů. Čím nedávněji byly segmenty použité, tím bývaly teplejší a tím častěji přežily. Upřímně, přesně tohle by asi mělo být hlavním přínosem interního sledování temperature. Nenašel jsem ale přesný temperature threshold, který by garantovaně způsobil eviction. Záleží tedy na celkové distribuci column segmentů a jejich temperature.
Ani tohle samozřejmě není garantované. Záleží na typu memory pressure, kterému čelíš, takže mohou být evictovány i zahřáté věci. Je to pouze pattern, ve který mám na základě testů poměrně vysokou důvěru.
Další patterny
Na začátku testování jsem měl několik prvotních nápadů. Nejintuitivnější bylo, že sloupce s vysokou kardinalitou mohou odcházet dříve, protože často spotřebují více paměti. Ve 12 historických porovnáních ale přežily sloupce s nižší kardinalitou minimálně stejně dobře jako sloupce se střední a vysokou kardinalitou.
Pořadí načítání nevytvořilo žádnou opakovatelnou preferenci. Skupinové nahrání vypadalo v jednom z prvních runů slibně, ale cílený follow-up test to nepotvrdil. Ani přítomnost na aktivně dotazované stránce nebyla spolehlivá, pokud bylo možné query zodpovědět z cache. Skutečné nahrání z Direct Lake cache bylo důležitější.
Každopádně je to pro mě velmi zajímavé téma, takže v něm budu dál pátrat a výsledky přinesu v dalších článcích.
Zahřátý Direct Lake a jeho konzumace CU
Editace 26. 7. 2026: Celou sekci jsem přepsal na základě výrazně většího počtu běhů, tentokrát bez jakéhokoli throttlingu. Jednotlivé dotazy mají také nižší frekvenci, což podle mě mnohem lépe vysvětluje chování zahřátých modelů.
V této části jsem chtěl ověřit, zda se Import a Direct Lake liší ve spotřebě CU při spouštění typických DAX queries, například těch generovaných Power BI vizuály.
Extrémně důležitá poznámka: Tato čísla pokrývají pouze, řekněme, nejlepší možný scénář zahřátého výkonu. Nezahrnují náklady na refresh Import modelu ani na zahřátí Direct Lake. Tomu se budu věnovat v samostatném článku. Berte je tedy jako provozní náklady plně zahřátých sémantických modelů.
Pro test jsem použil stejná data a stejnou strukturu sémantického modelu napříč pěti modely. Na testovací kapacitě F64 jsem testoval Import, Direct Lake on OneLake a Direct Lake on SQL. Na kapacitě F2 jsem testoval samostatné modely Direct Lake on OneLake a Import. Předpokládal jsem, že Import bude nejlevnější variantou, ale nečekal jsem, že se Direct Lake bude lišit až tolik. Veřejně dostupných testů porovnávajících jednotlivé varianty Direct Lake nebo stejný model napříč různými velikostmi kapacity navíc není mnoho, takže tato porovnání byla obzvlášť zajímavá.
Import poskytuje nejjasnější výchozí bod pro interpretaci výsledků. Na F64 spotřeboval zahřátý běh 48 dotazů podobných těm z vizualizací v režimu Import mediánově 8,56 CU(s). Výsledek vychází z 24 opakování sesbíraných během čtyř samostatných testovacích relací.
Direct Lake on OneLake spotřeboval konzistentně o 29 % více než výchozí hodnota.
U Direct Lake on SQL se to začíná komplikovat. Nemá jednu stabilní úroveň spotřeby. Ve dvou třetinách případů skončí blízko OneLake, přibližně 21 % nad Importem. Ve zbývající třetině případů vyskočí zhruba na dvacetinásobek výchozí hodnoty, přičemž mezi těmito dvěma úrovněmi není prakticky nic. Kontroloval jsem denní dobu, latenci dotazů i aktivitu na pozadí na kapacitě a v Lakehouse, ale nic z toho toto rozdělení nevysvětluje. Mohlo by jít o tichý fallback do DirectQuery, přestože kapacita nebyla throttlovaná, tempo těchto dotazů bylo hluboko v rámci všech limitů a SQL části neovlivňovaly žádné úlohy běžící na pozadí. Každopádně jsem test zopakoval dostatečně mnohokrát na to, aby bylo vidět, že jde o dvě stabilní hodnoty, nikoli pouze o náhodný šum.
F2 vykazovala podobný relativní vzorec jako F64. Import byl opět nejlevnější a Direct Lake on OneLake byl znovu dražší než Import, přibližně o stejný rozdíl jako na F64.
Je potřeba poznamenat, že tyto běhy byly velmi nekonzistentní, přestože používaly stejné nastavení a stejný typ prostředí. Následující tabulka kombinuje 24 běhů, přičemž každý běh obsahoval pro každý model přibližně 50 dotazů a celé testování bylo rozložené do více než 20 hodin.
Jediný rozumný závěr, který z toho dokážu vyvodit, je, že stejné zátěže mohou mít velmi rozdílnou spotřebu, ale Import je při zahřátém běhu konzistentně levnější. Ty malé rozdíly mezi F2 a F64 v režimu Import bych neinterpretoval jako něco dramatického, přestože by mohly znamenat, že F2 je levnější než F64. Samotná účtovaná spotřeba však může občas vystřelit extrémně vysoko, zatímco spodní hranice klesá pod medián pouze mírně, ani zdaleka ne tak dramaticky jako výkyvy směrem nahoru.
Kapacita | Model | Median CU(s) na testovací okno | Median CU(s) na query | Rozsah (CU(s) testovací okno) | vs Import |
F64 | Import | 8.56 | 0.178 | 4.30 - 217.07 | Baseline |
F64 | Direct Lake on OneLake | 11.02 | 0.230 | 7.84 - 217.17 | +29% |
F64 | Direct Lake on SQL* | 12.77 | 0.266 | 6.81 - 237.86 | +49% |
F2 | Import | 7.34 | 0.153 | 2.51 - 161.73 | Baseline |
F2 | Direct Lake on OneLake | 11.02 | 0.230 | 7.15 - 139.75 | +50% |
SQL část jsem také rozdělil do dvou vrstev, abyste viděli, jak by měla vypadat. Anomálie jsem ale ponechal, protože stojí za zmínku, že k nim skutečně může dojít.
Kapacita | Model | Median CU(s) za testovací okno | Median CU(s) za query | Rozsah (CU(s) za testovací okno) | vs Import |
F64 | Direct Lake on SQL, typické (67% běhů) | 10.39 | 0.217 | 6.81 - 13.97 | +21% |
F64 | Direct Lake on SQL, zvýšené (33% běhů) | 181.75 | 3.786 | 165.04 - 237.86 | +2023% |
Prewarming Direct Lake
Prewarming je v podstatě dotazování sémantického modelu dříve, než to udělají uživatelé. Potřebné column segments jsou díky tomu už resident ve chvíli, kdy uživatel otevře report. Automatizovat to můžeš ve Fabric notebooku pomocí Semantic Link Labs.
Například, warm_direct_lake_cache_perspective() načte sloupce definované v perspective, zatímco warm_direct_lake_cache_isresident() obnoví sloupce, které byly resident před reframingem. Pod kapotou tyto funkce stále odesílají lehké DAX queries.
Stejnou věc můžeš udělat také pomocí sempy.fabric.evaluate_dax(), Power BI Execute Queries REST API nebo XMLA klienta.
Prewarming dává smysl po framingu, velkých aktualizacích dat, optimalizaci tabulek nebo před důležitým obdobím používání. Místo zahřívání celého modelu bych se zaměřil na sloupce, measures a relationships používané na nejdůležitějších report pages.
Zahřátí celého modelu může zbytečně plýtvat pamětí a CU. Načtené segmenty nejsou permanentně připnuté a později mohou být stejně evictovány.
Výsledek můžeš vždy ověřit pomocí: INFO.STORAGETABLECOLUMNSEGMENTS(). Díky tomu uvidíš, jestli prewarming skutečně proběhl tak, jak jsi chtěl.
V-Order
V-Order by měl teoreticky zlepšovat výkon Direct Lake, především cold načítání sloupců.
Jde o write-time optimalizaci navrženou tak, aby zlepšila čtení z Delta tabulek. Ve čtyřech mých cold-loading testech, které používaly velký model s několika tabulkami i menší model s jednou tabulkou, ale V-Order konzistentně dosahoval horších výsledků než odpovídající verze bez V-Orderu. Ověřil jsem, že byl V-Order aplikovaný na fyzické soubory. Před každým testem jsem vyčistil cached values, před načítáním potvrdil nulovou column residency a následně ověřil kompletní residency.
Testování bylo stále omezené, ale nenašel jsem žádný zjevný problém v metodologii, který by výsledky vysvětloval. Důkladnější vyšetřování by už tak dlouhý článek ještě více natáhlo, takže si ho nechám na později.
Prozatím tedy nepředpokládej, že ti V-Order automaticky zlepší výkon Direct Lake.
CU consumption během warm-upu
Změřil jsem také, jak nákladná může být širší prewarming query. Před každým testem jsem kompletně vyčistil cache, ověřil její stav a následně spustil jednu visual-like DAX query odkazující na všech 11 sloupců fact tabulky. Test jsem zopakoval třikrát pro každý model.
Model | Průměrné prewarming CU(s) | Medián prewarming CU(s) | Dříve zahřátý workload |
F64 Direct Lake on OneLake | 798.58 | 795.89 | 11.02 CU(s) na 48 queries |
F64 Direct Lake on SQL | 817.34 | 817.26 | 12.77 CU(s) na 48 queries |
F2 Direct Lake on OneLake | 819.94 | 819.71 | 11.02 CU(s) na 48 queries |
Sémantické modely byly stejné jako v předchozím testu běžných queries.
Obě F64 Direct Lake varianty měly prakticky identické prewarming costs.
Ještě důležitější je, že načtení všech relevantních sloupců fact tabulky spotřebovalo výrazně více CU než předchozí zahřáté workloady, přestože tyto workloady obsahovaly 48 visual-like queries.



Comments