Ana içeriğe geç
Kesit Power BI
İleri · İleri Modelleme

DAX optimizasyonu: depolama motoru, formül motoru

Sorgu planı, SE ve FE süreleri, materyalizasyon, geri çağırmalar; yavaş ölçüyü hızlandırma reçeteleri.

Uygulamada aç, 10 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

İki motor

Depolama motoru (SE, VertiPaq)Formül motoru (FE)
Ne yapar?Sıkıştırılmış sütunları tarar, filtreler, toplarDAX mantığını yürütür: IF, iç içe yineleyiciler, karmaşık ifadeler
Paralel mi?Çok çekirdekliTek iş parçacığı
ÖnbellekVar (aynı veri isteği tekrar hesaplanmaz)Yok
Hedefİşin çoğu burada yapılsınAz ve küçük ara sonuçlarla çalışsın

DAX Studio → Server Timings: toplam süre, SE süresi, FE süresi, SE sorgu sayısı ve her SE sorgusunun döndürdüğü satır sayısı. Yavaş bir ölçüde genellikle iki desen görürsün: (1) çok sayıda küçük SE sorgusu (yineleyicide bağlam geçişi), (2) milyonlarca satırlık bir ara sonucun FE'ye taşınması (materyalizasyon).

Geri çağırma (callback)

SE basit aritmetiği kendi yapabilir; ama IF, DIVIDE gibi bazı ifadeler satır satır FE'ye “geri çağırma” ister (CallbackDataID). SUMX(Satislar, IF(Satislar[Indirim] > 0, Satislar[Tutar])) yerine CALCULATE(SUM(Satislar[Tutar]), Satislar[Indirim] > 0) yazmak işi tamamen SE'ye verir.

Reçeteler

BelirtiNedenÇözüm
Ölçü tek başına hızlı, matriste yavaşHer hücrede tekrar eden pahalı alt ifadeVAR ile bir kez hesapla; mümkünse dışa taşı
Çok sayıda SE sorgusuBüyük tabloda yineleyici + ölçü çağrısıBoyutun anahtarı üzerinde dolaş: SUMX(VALUES(Urunler[UrunNo]), …)
FE süresi yüksekBüyük materyalizasyon (FILTER(Satislar…))Sütun filtresi kullan; KEEPFILTERS ile kesişim
Yüksek kardinaliteli sütun filtresi yavaşMilyonlarca farklı değerModelde sütunu böl (tarih/saat), gruplanmış sütun ekle
IFERROR / ISERROR yaygınHata yakalama satır satır FE'deDIVIDE ve doğru veri türleri
Çift yönlü ilişkilerFiltre zincirleri ve belirsizlikTek yön + gerektiğinde CROSSFILTER
Görsel çok satırlıGörselde 10.000+ satırİlk N filtresi, sayfalandırma, detay sayfası

Ölçme yöntemi

  1. Performans Analizi'nde yavaş görseli bul → Sorguyu kopyala.
  2. DAX Studio'da (ya da DAX sorgu görünümünde) sorguyu çalıştır; önbelleği temizleyerek (Clear Cache) ölç.
  3. Ölçüyü değiştir, aynı koşulda tekrar ölç. Tek değişiklik, tek ölçüm.
  4. Sonuçların aynı olduğunu doğrula (performans için doğruluktan vazgeçme).
Bu oyun alanındaDAX Playground her çalıştırmada süreyi milisaniye olarak gösterir. Motor VertiPaq değildir ama kalıplar aynı yönde davranır: Measure Lab’deki RFM segment sayımı, segment satırı başına tüm müşterileri dolaştığı için diğerlerinden çok daha yavaştır.
Derinleş

Formula Engine ve Storage Engine

Bir DAX sorgusu iki motorda çalışır. Storage Engine (VertiPaq), sıkıştırılmış sütunları çok çekirdekli ve çok hızlı tarar; ama yalnızca basit işler bilir: filtrele, grupla, topla, say. Formula Engine geri kalan her şeyi yapar: koşullar, iç içe iteratörler, karmaşık mantık. Formula Engine tek çekirdekte çalışır.

Optimizasyonun özü, işi Storage Engine’e bırakmaktır:

  • Sütun filtresi tablo filtresinden hızlıdır. Urunler[Fiyat] > 1000 doğrudan Storage Engine sorgusuna dönüşür; FILTER(Urunler, ...) çoğu zaman Formula Engine’de satır satır değerlendirilir.
  • İteratör içinde IF “geri çağırma” (callback) üretir. SUMX(Satislar, IF(...)) her satır için Formula Engine’e döner. Koşulu CALCULATE filtresine taşıyabiliyorsan taşı.
  • Değişkenler tekrar hesaplamayı önler. Aynı ölçüyü bir formülde üç kez çağırmak, üç ayrı hesaplama demektir; VAR ile bir kez hesaplanır.
  • Kardinalite her şeyden önemlidir. Bir sütundaki farklı değer sayısı sıkıştırmayı ve tarama hızını belirler. Tarih-saat sütununu tarih ve saat diye ikiye bölmek, modeli birkaç kat küçültebilir.
Pahalı Satırlar Satışı =
CALCULATE([Net Satış], Satislar[Tutar] > 2000)

Bu yazım, aynı sonucu veren SUMX(Satislar, IF(Satislar[Tutar] > 2000, Satislar[Tutar])) gibi bir iteratörden daha ölçeklenebilirdir. Gerçek modelde farkı görmek için Performans Analizi ile sorguyu kopyalayıp DAX Studio’da “Server Timings” ile SE ve FE sürelerine bak.

Uzmanlaş

Server Timings'i okumak: bir ekranı adım adım yorumla

Bir matris 3 saniyede açılıyor. Performans Analizi'nden sorguyu kopyaladın, DAX Studio'da Clear Cache sonrası Server Timings açıkken çalıştırdın. Aşağıdaki değerler örnek bir ekrandır (bu modelden ölçülmüş değil); amaç okumayı öğrenmek:

AlanÖrnek değerNasıl okunur?
Total3.050 msSorgunun toplam süresi
FE2.700 ms (%89)Formül motoru tek iş parçacığında çalışıyor; asıl yük burada
SE350 ms (%11)Depolama motorunda geçen süre
SE CPU1.400 msSE CPU / SE ≈ 4: tarama paralel çalışmış, SE sağlıklı
SE Queries612Çok sayıda küçük SE sorgusu: yineleyici içinde bağlam geçişi
SE Cache540Sorguların çoğu önbellekten geldi; aynı veri tekrar tekrar isteniyor

Bu tablo tek başına bir teşhis koyar: işin çoğu FE'de, SE sorguları çok ve küçük. Neden genellikle bir yineleyicinin her satırında ölçü çağrılmasıdır. Sonraki adım sorgu listesine bakmaktır: her xmSQL satırında döndürülen satır sayısı (Rows) ve boyut (KB) yazar. Tek bir sorgu 1,6 milyon satır döndürüyorsa sorun materyalizasyondur: SE büyük bir ara tabloyu FE'ye taşımıştır. xmSQL metninde CallbackDataID görüyorsan SE satır satır FE'ye geri çağırma yapıyordur.

Query Plan sekmesinde mantıksal ve fiziksel plan vardır. Fiziksel planda büyük “Records” sayılı Spool satırları, FE'nin bellekte tuttuğu ara sonuçlardır. Hedef: az sayıda SE sorgusu, küçük ara sonuçlar, callback yok.

Sütun filtresi, KEEPFILTERS ve FILTER(tablo): hız ve anlam birlikte

Pazarlama bir “Web Satışı” ölçüsü istiyor ve onu Kanal satırlı bir matrise koyacak. Üç yazımın hızı ve sonucu farklıdır:

EVALUATE
SUMMARIZECOLUMNS(
    Satislar[Kanal],
    "Net Satış", [Net Satış],
    "Sütun filtresi", CALCULATE([Net Satış], Satislar[Kanal] = "Web"),
    "KEEPFILTERS", CALCULATE([Net Satış], KEEPFILTERS(Satislar[Kanal] = "Web"))
)

Web satırında üçü de 457.330,90 ₺ verir. Mobil satırında ise sütun filtresi yine 457.330,90 ₺ gösterir: Satislar[Kanal] = "Web", satırdan gelen “Kanal = Mobil” filtresinin üzerine yazar. KEEPFILTERS iki filtreyi kesiştirir; Mobil ve Mağaza satırları boş kalır. İkisi de SE'de çözülür, hız aynıdır; fark tamamen anlamdadır.

Üçüncü yol FILTER(Satislar, Satislar[Kanal] = "Web") yazmaktır. Bu, olgu tablosunu satır satır dolaşıp tüm sütunlarıyla bir tablo filtresi üretir. Büyük tabloda materyalizasyon demektir ve filtre genişletilmiş tablonun bütün sütunlarına uygulanır; sonucu tahmin etmek zorlaşır. Kural: tek sütun koşulu → sütun filtresi; mevcut seçime saygı → KEEPFILTERS; FILTER yalnızca ölçüye dayalı koşulda ve küçük bir tablo üzerinde (ör. FILTER(VALUES(Urunler[UrunNo]), [Net Satış] > 5000)).

Modeldeki örnekHazır [Net Satış] ölçüsü Satislar[Durum] <> "İptal" sütun filtresiyle yazılmıştır. Bu yüzden Durum satırlı bir tabloda her satırda 1.250.772,45 ₺ görünür; İptal satırı dahil. Aynı ölçü KEEPFILTERS ile yazılsaydı İptal satırı boş, Teslim Edildi 1.049.627,00 ₺ olurdu. Ölçüyü hangi sütunlarla kesileceğini düşünerek yaz.

IF, SWITCH ve kısa devre

Motor, IF ve SWITCH'te çoğu zaman yalnızca gereken dalı hesaplar. Ama koşul satıra bağlıysa ve bir yineleyicinin içindeyse, SE her iki dalın verisini önceden toplayabilir. Bu bir hata değildir; bazen tek bir geniş SE sorgusu, satır başına küçük sorgulardan hızlıdır. IF.EAGER bu davranışı bilerek ister. Kendi tarafında yapabileceğin iki şey var:

  • Ucuz kontrolü öne al: IF(ISBLANK([Net Satış]), BLANK(), <pahalı ifade>). Satışı olmayan hücrelerde pahalı kısım hesaplanmaz; ayrıca boş satırlar matristen düşer.
  • SWITCH(TRUE(), …) sırası: En sık doğru olan koşulu ilk sıraya koy; koşullarda ölçü değil, değişken kullan.

Tersi de bir anti-kalıptır: IF([Net Satış] > 0, [Net Satış], 0) gibi “boş yerine 0” yazımları, satışı olmayan her ürün-gün bileşimini görünür kılar. Matris 40 ürün × 1.096 gün = 43.840 satıra çıkabilir ve yavaşlık DAX'tan değil, sıfırların kendisinden gelir.

DISTINCTCOUNT'un bedeli

[Müşteri Sayısı] ve [Sipariş Sayısı] DISTINCTCOUNT kullanır. Farklı sayım toplanamaz: ayların müşteri sayılarını toplayıp yıl bulamazsın, çünkü aynı müşteri birden çok ayda alışveriş yapar. Bu yüzden motor her hücre için sütunun sözlüğünü yeniden tarar; kardinalite yükseldikçe maliyet artar. Toplama tabloları da çoğu zaman farklı sayımı karşılayamaz.

  • COUNTROWS(VALUES(Satislar[MusteriNo])) aynı sonucu (bu modelde 136) verir ve çoğu zaman aynı planı üretir; tek başına hızlandırmaz.
  • Asıl kazanç kardinaliteden gelir: birleştirilmiş metin anahtar (“Musteri-Urun”) üzerinde farklı sayım yerine iki tam sayı sütun ya da önceden hesaplanmış bir anahtar kullan.
  • DirectQuery'de yaklaşık sayım (APPROXIMATEDISTINCTCOUNT) kaynağa itilebilir; “yaklaşık” olduğu raporda belirtilmeli. Bu oyun alanının motorunda yoktur.

Değişken: bir kez hesapla, ama doğru yerde

Değişken tanımlandığı yerdeki bağlamda bir kez hesaplanır ve sonra sabittir. Bu hem hız kazandırır hem de sık bir hataya yol açar:

Yanlış 2023 Satışı =
VAR s = [Net Satış]
RETURN CALCULATE(s, Tarih[Yil] = 2023)
Doğru 2023 Satışı =
CALCULATE([Net Satış], Tarih[Yil] = 2023)

Toplamda yanlış ölçü 1.250.772,45 ₺ (bütün yıllar), doğru ölçü 400.869,55 ₺ verir; 2024 satırında yanlış ölçü 747.065,70 ₺ gösterir. CALCULATE bir sabitin bağlamını değiştiremez. Kural: değişkeni, değerini istediğin bağlamda tanımla; CALCULATE ile değiştirilecek bir şeyse değişkene ölçünün kendisini değil, sonucu kullanılacak tabloyu ya da filtreyi koy.

Karar ağacı

  1. Önce ölç: Clear Cache + Server Timings. FE mi SE mi baskın?
  2. SE baskın ve sorgu az ama büyükse: model sorunu (kardinalite, ilişki, sütun sayısı) → Performans ve VertiPaq dersi.
  3. FE baskın ve SE sorgusu çoksa: yineleyiciyi boyut anahtarına taşı, bağlam geçişini azalt.
  4. CallbackDataID varsa: IF/koşulu CALCULATE filtresine taşı.
  5. Büyük materyalizasyon varsa: FILTER(olgu) yerine sütun filtresi ya da KEEPFILTERS.
  6. Aynı ölçü bir formülde birden çok kez geçiyorsa: VAR.
  7. Her değişiklikten sonra sonucu karşılaştır; hızlı ama yanlış ölçü, yavaş ve doğru ölçüden kötüdür.
Alıştırmalara geç (10)