Kardynalność zabija raport Power BI: jak wykryć problem, zanim pojawią się lagi
Jak kardynalność kolumn wpływa na kompresję VertiPaq i wydajność raportu Power BI. Szybka diagnostyka „winnych” pól, poprawa relacji i przebudowa modelu na gwiazdę.
Czym jest kardynalność w Power BI i dlaczego potrafi spowolnić raport bardziej niż liczba wierszy?
Kardynalność w Power BI oznacza liczbę unikalnych wartości w kolumnie (albo w zestawie kolumn), która jest używana w modelu danych, szczególnie w relacjach oraz w kolumnach uczestniczących w filtrowaniu, grupowaniu i sortowaniu. Wysoka kardynalność to sytuacja, gdy wartości są prawie zawsze różne (np. identyfikatory, timestampy, transakcje), a niska kardynalność to niewiele powtarzających się kategorii (np. kraj, status).
To potrafi spowolnić raport bardziej niż sama liczba wierszy, ponieważ silnik analityczny nie „płaci” głównie za to, ile rekordów istnieje w tabeli, tylko za to, jak duże i jak złożone struktury musi utrzymywać w pamięci oraz jak trudne są operacje filtrowania i agregacji. Kolumny o wysokiej kardynalności mają zwykle większe słowniki wartości i mniej skuteczną kompresję, co zwiększa zużycie pamięci i ilość danych, które trzeba przetworzyć przy każdym zapytaniu. Dodatkowo, gdy takie kolumny biorą udział w relacjach i propagacji filtrów, rośnie koszt budowania i przetwarzania mapowań (indeksów) potrzebnych do łączenia tabel oraz wykonywania obliczeń w kontekście filtra.
W praktyce oznacza to, że model z „niewielką” liczbą wierszy, ale z wieloma kolumnami o bardzo wysokiej kardynalności (zwłaszcza wykorzystywanymi na osiach wizualizacji, w segmentatorach i relacjach), może generować cięższe zapytania i dłuższe czasy odpowiedzi niż większy model oparty o kolumny kategoryczne o niskiej kardynalności. Innymi słowy: wiersze zwiększają wolumen, ale kardynalność często decyduje o tym, jak trudno ten wolumen skompresować i jak kosztownie da się go filtrować oraz agregować.
Jakie są pierwsze sygnały, że model ma problem z kardynalnością, zanim użytkownicy zaczną narzekać?
Kardynalność w modelu Power BI to liczba unikalnych wartości w kolumnie (oraz w praktyce także liczba unikalnych kombinacji wartości, gdy kolumny są używane razem). Im wyższa kardynalność w kolumnach używanych w relacjach, sortowaniu, grupowaniu i filtrach, tym większe słowniki kompresji i struktury indeksów w VertiPaq oraz większa „powierzchnia” pracy dla silnika zapytań. Pierwsze sygnały problemu zwykle pojawiają się więc nie jako jednoznaczne błędy, tylko jako objawy rosnącego kosztu pamięci i obliczeń.
Najwcześniej widać to w rozmiarze modelu i zachowaniu podczas odświeżania: plik PBIX lub dataset w usłudze rośnie nieproporcjonalnie do liczby wierszy, a odświeżenie zaczyna wymagać wyraźnie więcej RAM i czasu, mimo że zakres danych się istotnie nie zmienił. Typowym źródłem są kolumny z niemal unikalnymi wartościami (np. identyfikatory transakcji, długie teksty, daty i czasy w wysokiej rozdzielczości), szczególnie gdy trafiają do tabel wymiarów lub są wykorzystywane w relacjach.
Drugim wczesnym sygnałem są symptomy „mikro-lagów” w Desktopie jeszcze przed publikacją: opóźnienia przy przeciąganiu pól do wizualizacji, dłuższe „myślenie” przy zmianie slicerów, sporadyczne komunikaty o intensywnym obliczaniu, a także wyraźnie wolniejsze otwieranie pliku. To zwykle oznacza, że zapytania muszą przetwarzać duże słowniki i/lub tworzyć kosztowne grupowania po kolumnach o wysokiej liczbie unikatów.
Trzeci sygnał dotyczy jakości agregacji i zachowania filtrów: proste wizualizacje oparte na „Count Distinct” lub grupowaniu po podejrzanych kolumnach zaczynają być zauważalnie cięższe niż inne, a filtry na tych polach powodują nieproporcjonalnie duże spowolnienie w porównaniu do filtrów na typowych wymiarach (np. kategoria, region). Jeśli zauważasz, że wydajność zależy głównie od tego, „po czym” grupujesz lub filtrujesz, a nie od samej liczby wierszy, to jest to klasyczny wczesny ślad problemu z kardynalnością.
Jak szybko sprawdzić, które kolumny psują kompresję w VertiPaq?
Najszybciej zrobisz to w narzędziu pokazującym metryki pamięci modelu VertiPaq (np. w widoku statystyk pamięci), gdzie dla każdej kolumny widać jej udział w zajętości pamięci oraz cechy wpływające na kompresję. W praktyce szukasz kolumn, które mają nieproporcjonalnie duży rozmiar względem „biznesowej wartości” i jednocześnie wysoką liczbę wartości unikalnych (kardynalność), bo to one zwykle najsłabiej się kompresują.
W takim widoku przejdź tabelę po tabeli i posortuj kolumny malejąco po rozmiarze (najczęściej „Data size”/„Column size”). Następnie porównaj ten rozmiar z kardynalnością („Distinct values”) oraz relacją „Distinct values” do liczby wierszy. Kolumny, które mają dużo wartości unikalnych (często blisko liczby wierszy) i jednocześnie duży rozmiar danych, są typowymi „psującymi kompresję”. Dodatkowym sygnałem ostrzegawczym są kolumny tekstowe, GUID/identyfikatory, długie ciągi znaków oraz kolumny obliczane generujące unikalne wyniki w wielu wierszach, bo takie dane zwykle nie tworzą powtarzalnych wzorców, na których VertiPaq buduje kompresję.
Jeśli chcesz to ocenić jeszcze szybciej, skup się na kilku największych kolumnach w całym modelu (top 10–20 po rozmiarze). W większości przypadków to właśnie one odpowiadają za największą część „utraconej” kompresji, więc ich identyfikacja daje najszybszy zwrot diagnostyczny bez przekopywania całego schematu.
Dlaczego kolumny typu datetime, GUID i długie teksty są tak kosztowne i co z nimi zrobić?
Są „kosztowne”, bo w modelu kolumnowym Power BI (VertiPaq) dobrze kompresują się dane o małej liczbie powtórzeń. Kolumny datetime (często niemal unikalne do sekundy/milisekundy), GUID (praktycznie zawsze unikalne) i długie teksty (wiele wariantów, duże słowniki) mają zwykle bardzo wysoką kardynalność, przez co kompresja jest słaba. Skutkiem jest większy rozmiar modelu w pamięci oraz wolniejsze skanowanie i filtrowanie, bo silnik musi przetwarzać większe słowniki i mniej „ściśnięte” dane.
W praktyce najlepiej jest ograniczyć takie kolumny do absolutnego minimum i dostosować ich postać do raportowania. Dla dat i czasu oznacza to przechowywanie w faktach tylko tego poziomu szczegółowości, który jest faktycznie analizowany (np. data albo data+godzina), a resztę przenieść do tabeli kalendarza/czasu lub wyprowadzić jako prostsze atrybuty (rok, miesiąc, dzień, godzina) używane w filtrach i osiach. Dla GUID-ów kluczowe jest, aby nie używać ich jako kolumn do sortowania, grupowania, osi wizualizacji czy filtrów; jeśli identyfikator jest potrzebny tylko do relacji, warto zastąpić go w modelu surrogate key (liczbą całkowitą) generowaną w źródle lub w Power Query i zostawić GUID co najwyżej poza modelem lub jako pole techniczne nieużywane w raportach.
Długie teksty (opisy, komentarze, adresy URL, JSON) powinny trafiać do modelu tylko wtedy, gdy są realnie wykorzystywane w wizualizacjach. Jeśli potrzebujesz ich wyłącznie do wyświetlenia szczegółu po kliknięciu, rozważ trzymanie ich poza głównym modelem raportowym (np. w osobnej tabeli o ograniczonym użyciu) albo ograniczenie długości i liczby wariantów: wybieraj krótsze etykiety, normalizuj słowniki (oddziel „kody” od „opisów”), a do analiz używaj kodów/liczb, nie pełnych opisów. Najważniejsza zasada: w miejscach, gdzie model ma agregować i filtrować, preferuj kolumny o niskiej kardynalności i krótkich reprezentacjach, a techniczne identyfikatory i długie treści trzymaj z dala od ścieżek krytycznych wydajności.
Jak relacje i kierunki filtrowania mogą wzmacniać problemy z kardynalnością?
Kardynalność określa, ile wierszy po jednej stronie relacji może pasować do pojedynczego wiersza po drugiej stronie (1:1, 1:*, *:*). Gdy relacja nie jest „czysta” (np. klucz po stronie wymiaru nie jest unikalny albo w modelu występują relacje wiele-do-wielu), samo istnienie relacji jeszcze nie musi powodować dużych opóźnień. Problemy rosną, kiedy te relacje zaczynają aktywnie przenosić filtry w niekorzystny sposób.
Kierunek filtrowania decyduje, jak filtr propaguje się między tabelami. Przy ustawieniu dwukierunkowym lub przy relacjach *:* filtr może rozlewać się na większą część modelu, a nie zatrzymywać się na naturalnym kierunku „wymiar → fakt”. Jeśli po drodze są zduplikowane klucze, to pojedynczy wybór w slicerze może odpowiadać wielu wierszom w tabeli „pośredniej”, a następnie wyfiltrować (lub złączyć logicznie) dużo większy fragment faktów niż oczekujesz. To zwiększa liczbę kombinacji, które silnik musi rozpatrzyć, i wzmacnia koszt obliczeń oraz agregacji.
Dwukierunkowe filtrowanie szczególnie potrafi eskalować skutki nieoptymalnej kardynalności, bo uruchamia propagację filtrów „w obie strony” przez relacje, które same w sobie już generują wieloznaczność dopasowań (np. duplikaty w wymiarze albo tabela mostowa). W praktyce prowadzi to do sytuacji, w której filtr z tabeli faktów wpływa na wymiar, a następnie wraca inną ścieżką do tej samej lub innej tabeli faktów, powodując złożone, kosztowne do wyliczenia zależności.
Dodatkowym wzmacniaczem są alternatywne ścieżki filtrowania (pętle) między tabelami. Jeżeli model ma więcej niż jedną drogę, którą filtr może dotrzeć do tej samej tabeli, a relacje są dwukierunkowe lub *:*, silnik musi rozstrzygać potencjalnie niejednoznaczną propagację filtrów. W połączeniu ze słabą kardynalnością (dużo dopasowań na kluczu) zwykle oznacza to więcej pracy przy materializowaniu filtrów i większe ryzyko „rozdmuchania” zakresu danych, na których wykonywane są miary.
W skrócie: problem kardynalności staje się dużo bardziej dotkliwy, gdy kierunki filtrowania pozwalają filtrom przepływać przez tabele, w których relacje nie gwarantują jednoznaczności. Zamiast ograniczać zbiór danych wcześnie i w przewidywalny sposób, model wymusza szeroką propagację filtrów i kosztowne operacje na większych fragmentach tabel.
Jak przebudować model na gwiazdę, żeby zmniejszyć kardynalność bez utraty analizy?
Model w układzie gwiazdy zmniejsza kardynalność tam, gdzie najbardziej szkodzi wydajności: na relacjach i kolumnach używanych do filtrowania oraz grupowania. Zamiast trzymać atrybuty opisowe (np. nazwy, kategorie, regiony, statusy) w dużej tabeli transakcyjnej, przenosisz je do tabel wymiarów, a w tabeli faktów zostawiasz miary liczbowe oraz klucze. Dzięki temu relacje łączą fakt z wymiarami po kluczach o niskiej lub kontrolowanej liczbie unikalnych wartości, a nie po tekstach czy kombinacjach pól, które gwałtownie zwiększają unikalność.
Przebudowę zacznij od identyfikacji ziarna faktu (np. „jedna sprzedaż = jeden wiersz”) i upewnij się, że tabela faktów nie zawiera atrybutów opisowych, które powtarzają się w wielu transakcjach. Następnie utwórz oddzielne wymiary dla bytów, które filtrują analizę: klient, produkt, data, lokalizacja, kanał, itp. Każdy wymiar powinien mieć stabilny klucz (najlepiej liczbowy) oraz zestaw atrybutów do analizy. W tabeli faktów przechowuj wyłącznie klucze obce do wymiarów i wartości zdarzenia (np. ilość, kwota), co ogranicza liczbę różnych wartości w kolumnach używanych w relacjach i jednocześnie redukuje rozmiar słownika dla powtarzalnych tekstów.
Aby nie stracić możliwości analizy, nie usuwasz atrybutów – zmieniasz ich miejsce. Atrybuty, które wcześniej były w faktach, przenosisz do odpowiedniego wymiaru i filtrujesz po nich przez relację. Warunkiem jest poprawna relacja 1:* z kierunkiem filtrowania od wymiaru do faktu oraz jednoznaczność klucza w wymiarze. Jeśli jakaś cecha jest naprawdę zależna od transakcji (np. jednorazowy identyfikator zamówienia), zostaje w fakcie, ale nie powinna być używana jako podstawowy filtr w raportach, bo to z natury będzie wysoka kardynalność.
Jeżeli masz hierarchie lub podziały, które często są modelowane jako kilka kolumn w fakcie (np. kategoria–podkategoria–marka), przenieś je do jednego wymiaru produktu. Unikaj też łączenia tabel po wielu kolumnach lub po tekście; zamiast tego twórz klucze zastępcze w ETL/Power Query i łącz po pojedynczym kluczu. W sytuacjach, gdy pojawia się wiele kolumn „flagi” (np. IsPromo, IsReturn, IsOnline), rozważ ustandaryzowanie tego jako wymiaru atrybutu tylko wtedy, gdy realnie służy do filtrowania; w przeciwnym razie pozostaw jako proste kolumny o niskiej kardynalności.
Efektem prawidłowej gwiazdy jest to, że raport nadal pozwala kroić dane po tych samych atrybutach, ale koszt filtrowania i grupowania przenosi się z ogromnej tabeli faktów na mniejsze, wyspecjalizowane wymiary, a relacje opierają się na kluczach, które nie „eksplodują” licznością unikalnych wartości.
Kiedy warto użyć agregacji lub tabel pomocniczych, a kiedy to tylko maskuje problem?
Agregacja (np. preagregowane tabele dzienne/miesięczne, sumy po kluczowych wymiarach) i tabele pomocnicze mają sens wtedy, gdy świadomie zmieniasz ziarnistość danych pod konkretny scenariusz raportowy. To jest prawidłowa optymalizacja, gdy użytkownicy i tak analizują dane na poziomie, na którym szczegół „transakcyjny” nie jest potrzebny, a miary nie wymagają liczenia po każdym wierszu faktu. Wtedy zmniejszasz kardynalność, liczbę skanowanych wierszy i koszt obliczeń, bez utraty znaczenia biznesowego.
To samo dotyczy tabel pomocniczych, jeśli rozwiązują realny problem modelowania: porządkują relacje (np. wspólny wymiar dat), tworzą jednoznaczne klucze, izolują atrybuty do filtracji lub ograniczają zakres danych przetwarzanych przez miary. Kluczowe jest, aby te tabele nie były „obejściem” niepoprawnego modelu, tylko elementem docelowej architektury (jasne ziarno faktów, prawidłowe relacje, przewidywalna propagacja filtrów).
Agregacja lub „helper” tylko maskuje problem, gdy służy przykryciu symptomów: miary są wolne, bo model ma błędne relacje, niejednoznaczne klucze albo wymusza kosztowne iteracje, a dodanie agregatu jedynie zmniejsza ilość danych „do przemielenia”. Typowe sygnały maskowania to sytuacje, w których po zejściu na niższy poziom szczegółu raport znów zaczyna lagować, wyniki różnią się między widokami agregowanymi a szczegółowymi, albo dochodzi do dublowania/zanikania wartości przez niekontrolowane relacje many-to-many czy dwukierunkowe filtrowanie. W takim przypadku najpierw trzeba naprawić przyczynę (ziarno tabel, klucze, relacje, logikę miar), a dopiero potem ewentualnie stosować agregacje jako świadomą warstwę wydajnościową.
Jak ustawić zasady modelowania, żeby problem nie wracał przy kolejnych iteracjach?
Żeby problem kardynalności nie wracał, zasady muszą dotyczyć tego, co wolno wprowadzać do modelu i gdzie wolno to wprowadzać. Kardynalność rośnie wtedy, gdy do tabel faktów trafiają „identyfikatory zdarzeń” (np. unikalne numery transakcji, logów, sesji) i atrybuty o bardzo wielu wartościach, a następnie są używane w relacjach, slicerach lub jako osie wizualizacji. Zasada bazowa brzmi: model ma być zorganizowany tak, aby użytkownik filtrował i grupował po wymiarach o kontrolowanej liczbie wartości, a nie po kolumnach o niemal unikalnych wartościach.
W praktyce oznacza to utrzymanie konsekwentnego układu typu gwiazda: jedna tabela faktów (lub kilka faktów), wokół niej tabele wymiarów z kluczami o jednoznacznej ziarnistości, i relacje 1:* z wymiarów do faktów. Każda kolumna w wymiarze powinna mieć jasny poziom szczegółowości (np. produkt, kategoria, region), a w faktach powinny zostać tylko miary oraz klucze obce do wymiarów. Jeśli jakaś kolumna jest potrzebna „do identyfikacji rekordu”, ale nie powinna służyć do filtrowania/agregacji, to traktuj ją jako techniczną i nie dopuszczaj do roli wymiaru ani klucza relacji.
Drugim filarem jest standard relacji: pojedyncza ścieżka filtracji, kierunek filtrowania z wymiaru do faktu, bez relacji dwukierunkowych jako domyślnego rozwiązania i bez relacji wiele-do-wielu, chyba że jest to celowo zaprojektowany przypadek z kontrolą konsekwencji. Dzięki temu kolejne iteracje (dokładanie nowych tabel i pól) nie „rozszczelniają” modelu i nie generują eksplozji możliwych kombinacji w zapytaniach.
Trzecim elementem są reguły „dopuszczenia do raportu”: nowe pola do slicerów/osi muszą pochodzić z wymiarów i spełniać limit sensownej krotności w danym zastosowaniu, a jeśli wymagane jest filtrowanie po atrybucie wysokokardynalnym, to powinno być to świadomie ograniczone (np. przez dedykowaną stronę wyszukiwania lub parametr), zamiast używania go jako standardowego wymiaru analitycznego. Ustal też, że tworzenie kolumn obliczanych „dla wygody” w faktach jest wyjątkiem, bo często podnosi krotność i obciąża pamięć; preferuj przygotowanie atrybutów w ETL/Power Query w wymiarach, gdzie da się kontrolować ziarnistość.
Na koniec, żeby zasady działały w kolejnych iteracjach, muszą być egzekwowalne: zdefiniuj prostą checklistę przeglądu zmian w modelu przed publikacją (ziarnistość faktu, nowe kolumny wysokokardynalne, nowe relacje i ich typ, czy pole trafia do wymiaru czy do faktu). Taki stały „gate” sprawia, że problem nie wraca przy każdym dopisaniu nowych danych, bo ryzykowne elementy są wyłapywane na etapie modelowania, a nie dopiero po pojawieniu się lagów w raporcie.
Majczęściej zadawane pytania i odpowiedzi odnośnie Kardynalność zabija raport Power BI: jak wykryć problem, zanim pojawią się lagi
Problem z kardynalnością poznasz po tym, że raport zwalnia głównie przy filtrowaniu, grupowaniu i pracy na konkretnych kolumnach, a nie tylko przy dużym wolumenie danych. Jeśli proste wizualizacje działają dobrze, ale pola typu identyfikator, datetime lub długi tekst wywołują lagi, zwykle winna jest liczba unikalnych wartości, a nie sama liczba rekordów.
Najczęściej problem powodują kolumny z wartościami niemal unikalnymi w każdym wierszu. W praktyce są to zwykle:
- identyfikatory transakcji i rekordów,
- GUID-y,
- datetime z wysoką dokładnością,
- długie teksty, opisy i komentarze,
- kolumny obliczane generujące wiele unikalnych wyników.
Takie pola słabo się kompresują i zwiększają koszt filtrowania oraz agregacji.
Wysoka kardynalność nie zawsze oznacza konieczność usunięcia kolumny, ale zwykle wymaga ograniczenia jej roli w modelu. Jeśli pole jest potrzebne tylko technicznie lub do szczegółowego podglądu, nie powinno trafiać do slicerów, osi wizualizacji ani relacji. Często wystarczy zmienić jego postać, przenieść je poza główną ścieżkę analityczną albo zostawić jako pole pomocnicze.
Poznasz to po tym, że konkretne pole wyraźnie spowalnia interakcje w raporcie bardziej niż inne filtry. Typowe sygnały to dłuższe przełączanie slicera, wolniejsze renderowanie wizualizacji po dodaniu pola na oś oraz zauważalnie cięższe grupowanie. Jeśli problem pojawia się po wyborze pola z wieloma unikalnymi wartościami, to jest to mocna wskazówka diagnostyczna.
Tak, Count Distinct często szybciej ujawnia problem kardynalności niż proste sumy czy średnie. Taka agregacja pracuje bezpośrednio na liczbie unikalnych wartości, więc kolumny o wysokiej unikalności naturalnie generują większy koszt obliczeń. Jeśli wizualizacje z Count Distinct są wyraźnie wolniejsze od innych, warto sprawdzić właśnie kardynalność użytych pól.
Najbardziej ryzykowne są relacje wiele-do-wielu oraz relacje z filtrowaniem dwukierunkowym. Szczególnie problematyczne są sytuacje, gdy filtr rozchodzi się szeroko po modelu i trafia na niejednoznaczne dopasowania. Najczęściej warto uważać na:
- relacje *:* między dużymi tabelami,
- dwukierunkowe filtrowanie jako ustawienie domyślne,
- alternatywne ścieżki filtracji między tabelami,
- duplikaty po stronie wymiaru.
Tak, model gwiazdy zwykle realnie zmniejsza skutki kardynalności, bo przenosi filtrowanie i grupowanie na mniejsze tabele wymiarów. Dzięki temu w tabeli faktów zostają głównie miary i klucze, a atrybuty opisowe trafiają do wymiarów. Raport nadal pozwala analizować te same dane, ale silnik pracuje na bardziej przewidywalnych relacjach i prostszych ścieżkach filtracji.
Przed publikacją warto sprawdzić kilka punktów, które najczęściej ujawniają problem, zanim użytkownicy zobaczą lagi. Najlepiej przejrzeć:
- największe kolumny w metrykach VertiPaq,
- pola z wysoką liczbą unikalnych wartości,
- nowe relacje i ich kierunki filtrowania,
- czy slicery i osie pochodzą z wymiarów,
- czy do faktów nie trafiły techniczne identyfikatory jako pola analityczne.