İçeriğe geç
academia.sh

Ders 07 / 12

Boyutsal Modelleme

Olgu ile boyutun ayrılmasının sakladığı hücreyi ve sorguya ödettiği birleştirmeyi ölçme: aynı 2200 satır tek düz tabloda 15400 hücre tutarken olgu artı iki boyut düzeninde 7236 hücre tutuyor, 8164 hücre eksiliyor ve iki düzenin ürettiği tip başına toplam birebir aynı kalıyor; karşılığında sorgu tek dosyaya dokunup maliyet 4520 öderken olgu artı boyut iki dosyaya dokunup 4800 hücre okuyor, maliyet 5040 oluyor ve sıfır birleştirme 2200'e çıkıyor; bir bölgenin adı düzeltildiğinde düz tabloda 506 satır, boyutta 46 satır güncelleniyor.

İçindekiler

Altı derste verinin nerede durduğu ayrıldı: şema dayatan katman, ham katman, ikisini birleştiren kurulum, alan odaklı alt kümeler, sahipliğin dağıtıldığı düzenler ve varlığı bulmayı sağlayan katalog. Hepsinin ortak varsayımı vardı: satırlar bir yerde durur, biri onları arar. Hiçbir ölçüm satırların hangi biçimde durduğunu sormadı.

Bu ders o soruyu sorar ve en eski karardan başlar: ölçülen değer ile ölçümü niteleyen bilgi aynı tabloda mı durmalı. Sayaç okumasından çıkan tüketim bir sayıdır; abonenin bölgesi ve tipi, dönemin yılı ve ayı o sayıyı niteler. Tek tablo ikisini yan yana taşır. Boyutsal modelleme onları ayırır. Ayrımın saklanan hücreye, sorgunun maliyetine ve çıkan sonuca ne yaptığı sayılabilir bir şeydir.

  • AM1. Kaynak, hat ve depo kurgudur; gerçek bir ambar, göl, dosya biçimi ya da sorgu motoru çağrılmaz. Tohum 20260218, ölçü koşumdan bağımsızdır.
  • AM2. Hattan çıkan satır 2200’dür: 200 abone ile on iki dönemin çarpımı 2400, ilk dönemin tüketimi hesaplanamadığı için 200 satır düşer.
  • AM3. Olgu tablosu (fact table) ölçülen değeri taşır; boyut bağıntısı (dimension table) o değeri niteleyen bilgiyi taşır. Olgunun taneciği (grain) abone ile dönemin çiftidir.
  • AM4. Saklanan hücre satır ile sütunun çarpımıdır. Bir alan bir hücredir; alanın genişliği ölçülmez.
  • AM5. Maliyet dosya x 120 + hücre biçiminde okunur. Dosya sabiti 120 bir seçimdir; dosya sayısına duyarlı her sonuç sabitin değeriyle birlikte yazılır.
  • AM6. Birleştirme (join) sayısı, sorgunun boyut bağıntısına kaç kez baktığıdır. Ham süre yazılmaz.
  • AM7. Taban çizgisi tek düz tablodur: bölümlemesiz, tek dosya, birleştirme sayısı 0.
  • AM8. Çözünürlük: satır oranının adımı 0,0005. Toplamlar oran olarak değil, değerin kendisi olarak yazılır.

Tek Tablonun Taşıdığı

Hattın beşinci aşamasından önceki satır yedi alan taşır: abone, dönem, bölge, tip, endeks, birim ve hesaplanmış tüketim. Bu satırı olduğu gibi diske indirmek en az karar gerektiren düzendir; sorgu tek bir yere bakar, hiçbir tabloyu bir başkasıyla eşlemek zorunda kalmaz. Karşılığında abonenin bölgesi o abonenin her satırında yeniden yazılır. Bir abonenin on bir satırı varsa bölge adı on bir kez saklanır.

İlk blok ortak tanımı kurar: kurgu kaynak, hattın satır üreten üç aşaması, düzen yazma ve maliyet ölçen sorgu. Boyutsal ayrım yildiz işlevindedir: olguya yalnızca abone, dönem ve tüketim kalır, niteleyen alanlar iki boyut bağıntısına çıkar.

# KURGUDUR. Sayac okumalari, hat ve depo ders icinde tanimlanir; gercek bir
# ambar, gol, dosya bicimi ya da sorgu motoru cagrilmaz.
TOHUM, M32 = 20260218, 0xFFFFFFFF


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


BOLGE = ["dogu", "bati", "kuzey", "guney", "merkez"]
TIP = ["mesken", "isyeri"]
DONEM = [f"2026-{a:02d}" for a in range(1, 13)]
ABONE = {}
for i in range(200):
    r = uretec(TOHUM + 37 * i)
    ABONE[f"A{i + 1:03d}"] = {"bolge": BOLGE[int(r() * 5)], "tip": TIP[int(r() * 2)],
                              "baslangic": 10000 + int(r() * 60000)}


def kaynak_uret():
    kayit = []
    for i, (ab, a) in enumerate(ABONE.items()):
        e = a["baslangic"]
        for d in DONEM:
            r = uretec(TOHUM + 101 * i + 7 * DONEM.index(d))
            e += 4 + int(r() * 46)
            kayit.append({"abone": ab, "donem": d, "bolge": a["bolge"], "tip": a["tip"],
                          "endeks": e, "birim": "m3"})
    return kayit


def a1_ayikla(kayit):
    ZOR = ("abone", "donem", "bolge", "endeks", "birim")
    tut = [k for k in kayit if all(a in k for a in ZOR)]
    return tut, len(kayit) - len(tut)


def a2_tekille(kayit):
    gor, tut = set(), []
    for k in kayit:
        ad = (k["abone"], k["donem"])
        if ad in gor:
            continue
        gor.add(ad)
        tut.append(k)
    return tut, len(kayit) - len(tut)


def a3_tuketim(kayit):
    tablo = {(k["abone"], k["donem"]): k for k in kayit}
    cikti, dusen = [], 0
    for k in kayit:
        i = DONEM.index(k["donem"])
        if i == 0:
            continue
        onceki = tablo.get((k["abone"], DONEM[i - 1]))
        if onceki is None:
            dusen += 1
            continue
        try:
            f = int(k["endeks"]) - int(onceki["endeks"])
        except (TypeError, ValueError):
            dusen += 1
            continue
        cikti.append(dict(k, m3=f))
    return cikti, dusen


SUTUN = ("abone", "donem", "bolge", "tip", "endeks", "birim", "m3")
DOSYA_SABIT = 120          # her dosyaya dokunmanin sabit bedeli , hucre cinsinden


def satirlar():
    v, _ = a1_ayikla(kaynak_uret())
    v, _ = a2_tekille(v)
    v, _ = a3_tuketim(v)
    return v


def yaz(kayitlar, bolumleme=()):
    duzen = {}
    for k in kayitlar:
        anahtar = tuple(k[a] for a in bolumleme)
        duzen.setdefault(anahtar, []).append(k)
    return duzen


def sorgu(duzen, bolumleme, secilen, suzgec=None, sutunlu=True):
    suzgec = {} if suzgec is None else suzgec
    budayan = {a: v for a, v in suzgec.items() if a in bolumleme}
    kalan = {a: v for a, v in suzgec.items() if a not in bolumleme}
    dosya, hucre, satir = 0, 0, 0
    okunan_sutun = len(secilen) if sutunlu else len(SUTUN)
    for anahtar, kayit in duzen.items():
        if budayan:
            eslesme = dict(zip(bolumleme, anahtar))
            if any(eslesme[a] != v for a, v in budayan.items()):
                continue
        dosya += 1
        hucre += len(kayit) * okunan_sutun
        satir += sum(1 for k in kayit if all(k[a] == v for a, v in kalan.items()))
    return {"dosya": dosya, "hucre": hucre, "eslesen_satir": satir,
            "maliyet": dosya * DOSYA_SABIT + hucre}


def yildiz(kayitlar):
    """Olgu tablosu artı iki boyut. Niteleyen alanlar olgudan cikarilir."""
    olgu = [{"abone": k["abone"], "donem": k["donem"], "m3": k["m3"]} for k in kayitlar]
    d_abone = {k["abone"]: {"bolge": k["bolge"], "tip": k["tip"]} for k in kayitlar}
    d_donem = {d: {"yil": d[:4], "ay": d[5:]} for d in DONEM}
    return olgu, {"abone": d_abone, "donem": d_donem}

Ayrımın Sakladığı Hücre

İkinci blok iki düzeni yan yana kurar ve saklanan hücreyi sayar. Ölçümün dürüst olması için bir ara satır gerekir. yildiz işlevi olguyu kurarken endeks ile birim alanlarını hiç taşımaz, çünkü tüketim hesaplandıktan sonra ikisine de gerek kalmaz. Bu, boyutsal ayrımın değil alan düşürmenin kazancıdır ve ayrı sayılmazsa ayrıma yazılır. Tablo bu yüzden düz tabloyu iki kez listeler: yedi alanla ve olgunun taşıdığı beş alanla. Ardından aynı soru iki düzene de sorulur: tip başına toplam tüketim ne kadar.

SATIR = satirlar()
OLGU, BOYUT = yildiz(SATIR)
TASINAN = ("abone", "donem", "bolge", "tip", "m3")
print(f"{'tablo':<22}{'satir':>7}{'sutun':>7}{'saklanan hucre':>16}")
for ad, n, s in (("duz tablo , yedi alan", len(SATIR), len(SUTUN)),
                 ("duz tablo , bes alan", len(SATIR), len(TASINAN)),
                 ("olgu", len(OLGU), 3),
                 ("boyut abone", len(BOYUT["abone"]), 3),
                 ("boyut donem", len(BOYUT["donem"]), 3)):
    print(f"{ad:<22}{n:>7}{s:>7}{n * s:>16}")
duz7 = len(SATIR) * len(SUTUN)
duz5 = len(SATIR) * len(TASINAN)
yil = len(OLGU) * 3 + len(BOYUT["abone"]) * 3 + len(BOYUT["donem"]) * 3
print(f"{'yildiz toplam':<22}{'':>7}{'':>7}{yil:>16}")
print("alan dusurmenin kazandirdigi:", duz7 - duz5)
print("ayrimin kazandirdigi:", duz5 - yil, " boyut satiri:",
      len(BOYUT["abone"]) + len(BOYUT["donem"]))


def duz_tip_toplam(kayitlar):
    top = {}
    for k in kayitlar:
        top[k["tip"]] = top.get(k["tip"], 0) + k["m3"]
    return top, 0


def yildiz_tip_toplam(olgu, boyut):
    top, birlestirme = {}, 0
    for o in olgu:
        t = boyut["abone"][o["abone"]]["tip"]
        birlestirme += 1
        top[t] = top.get(t, 0) + o["m3"]
    return top, birlestirme


t_duz, b_duz = duz_tip_toplam(SATIR)
t_yil, b_yil = yildiz_tip_toplam(OLGU, BOYUT)
print()
print("duz sonuc   :", t_duz, " birlestirme:", b_duz)
print("yildiz sonuc:", t_yil, " birlestirme:", b_yil)
print("sonuc birebir ayni:", t_duz == t_yil)
tablo                   satir  sutun  saklanan hucre
duz tablo , yedi alan    2200      7           15400
duz tablo , bes alan     2200      5           11000
olgu                     2200      3            6600
boyut abone               200      3             600
boyut donem                12      3              36
yildiz toplam                                   7236
alan dusurmenin kazandirdigi: 4400
ayrimin kazandirdigi: 3764  boyut satiri: 212

duz sonuc   : {'isyeri': 29932, 'mesken': 28522}  birlestirme: 0
yildiz sonuc: {'isyeri': 29932, 'mesken': 28522}  birlestirme: 2200
sonuc birebir ayni: True

Toplam düşüş 15400’den 7236’ya, yani 8164 hücre. Bu sayının 4400’ü ayrıma ait değildir: endeks ile birim alanlarının artık taşınmaması iki sütunu, yani 2200 satırda 4400 hücreyi düşürüyor. Aynı işlem düz tabloda da yapılabilirdi ve yapıldığında düz tablo 11000 hücreye inerdi. Boyutsal ayrımın kendi kazancı bu satırla yıldız toplamının farkıdır: 3764 hücre.

Bu 3764 hücre tekrarın kendisidir. Bölge ve tip alanları 2200 satırda değil 200 satırda duruyor, dönemin yılı ve ayı 2200 satırda değil on iki satırda. Boyut tablolarının tamamı 212 satır tutuyor. Kazanç, olgu satırının boyut satırına oranından gelir: her abonenin on bir dönemi olduğu için niteleyen her alan on bir kez değil bir kez yazılıyor. Bu oran birebire yaklaşsaydı, yani her abonenin tek dönemi olsaydı, ayrımın saklama tarafında kazandıracağı bir şey kalmazdı.

Sonuç satırı ayrımın ilk sınavıdır ve geçer: iki düzen aynı sözlüğü üretiyor. Boyutsal modelleme veriyi özetlemez, sıkıştırmaz, örneklemez; yalnızca nerede yazıldığını değiştirir. Sonuç tarafında hiçbir borç bırakmaması, bundan sonraki bütün ölçümlerin tek bir eksene indirgenmesini sağlar: aynı cevap, farklı bedel.

Sorgunun Ödediği Birleştirme

Bedel sonuçta değil, sonuca ulaşma yolundadır. Düz tabloda tip ile tüketim aynı satırda durduğu için sorgu tek dosyayı okur ve hiçbir eşleme yapmaz. Olgu tablosunda tip yoktur; sorgu her olgu satırı için boyut bağıntısına bakmak zorundadır. Üçüncü blok iki yolun dokunduğu dosyayı, okuduğu hücreyi ve ödediği maliyeti sayar.

d_abone = [{"abone": a, **v} for a, v in BOYUT["abone"].items()]
q_duz = sorgu(yaz(SATIR, ()), (), ("tip", "m3"))
q_olgu = sorgu(yaz(OLGU, ()), (), ("abone", "m3"))
q_boyut = sorgu(yaz(d_abone, ()), (), ("abone", "tip"))
print(f"{'okuma':<22}{'dosya':>7}{'hucre':>7}{'maliyet':>9}{'birlestirme':>13}")
print(f"{'duz tablo':<22}{q_duz['dosya']:>7}{q_duz['hucre']:>7}"
      f"{q_duz['maliyet']:>9}{0:>13}")
print(f"{'olgu':<22}{q_olgu['dosya']:>7}{q_olgu['hucre']:>7}{q_olgu['maliyet']:>9}{'':>13}")
print(f"{'boyut abone':<22}{q_boyut['dosya']:>7}{q_boyut['hucre']:>7}"
      f"{q_boyut['maliyet']:>9}{'':>13}")
print(f"{'olgu + boyut':<22}{q_olgu['dosya'] + q_boyut['dosya']:>7}"
      f"{q_olgu['hucre'] + q_boyut['hucre']:>7}"
      f"{q_olgu['maliyet'] + q_boyut['maliyet']:>9}{len(OLGU):>13}")
print("dosya sabiti:", DOSYA_SABIT, "hucre")
print()
print(f"{'degisiklik':<26}{'duz tablo':>11}{'boyut':>7}")
print(f"{'A007 tipi duzeltiliyor':<26}"
      f"{sum(1 for k in SATIR if k['abone'] == 'A007'):>11}{1:>7}")
print(f"{'kuzey adi degisiyor':<26}"
      f"{sum(1 for k in SATIR if k['bolge'] == 'kuzey'):>11}"
      f"{sum(1 for v in BOYUT['abone'].values() if v['bolge'] == 'kuzey'):>7}")
print()
BOYUT_SABIT = len(BOYUT["abone"]) * 3 + len(BOYUT["donem"]) * 3
print("boyutun sabit hucresi:", BOYUT_SABIT)
print(f"{'olgu satiri':>12}{'duz bes alan':>14}{'yildiz':>9}{'kazanc':>9}")
for n in (550, 1100, 2200, 4400):
    print(f"{n:>12}{5 * n:>14}{3 * n + BOYUT_SABIT:>9}{2 * n - BOYUT_SABIT:>9}")
okuma                   dosya  hucre  maliyet  birlestirme
duz tablo                   1   4400     4520            0
olgu                        1   4400     4520             
boyut abone                 1    400      520             
olgu + boyut                2   4800     5040         2200
dosya sabiti: 120 hucre

degisiklik                  duz tablo  boyut
A007 tipi duzeltiliyor             11      1
kuzey adi degisiyor               506     46

boyutun sabit hucresi: 636
 olgu satiri  duz bes alan   yildiz   kazanc
         550          2750     2286      464
        1100          5500     3936     1564
        2200         11000     7236     3764
        4400         22000    13836     8164

Tablonun ilk yarısı kursun kuralını bu derse indirir. Saklamada kazanılan 8164 hücre okumada geri ödenmiyor, üstüne konuyor: aynı soruyu yanıtlamak düz tabloda 4520, olgu artı boyutta 5040 maliyet istiyor. Fark 520’dir ve iki parçadan oluşur: boyut bağıntısının 400 hücresi ve ikinci dosyaya dokunmanın 120 hücrelik sabit bedeli. Dosya sabiti 120 seçildiği için ikinci dosya bu kadar tutuyor; sabit daha büyük seçilseydi ayrımın okuma tarafındaki bedeli de büyürdü.

Birleştirme sütunu farkın nereden geldiğini söyler. Düz tabloda 0, olgu artı boyutta 2200: her olgu satırı için boyuta bir kez bakılıyor. Bu, hattan çıkan satır sayısıyla birebir aynı sayıdır ve tesadüf değildir; boyutsal düzenin okuma tarafındaki bedeli olgu bağıntısının satır sayısına bağlıdır. Düzenin sayısı sakladığı satır değil, bir sorgunun ona dokunmak için ödediğidir; bu sorgu için ödenen 5040’tır ve hangi sorgu olduğu yazılmadan bu sayının anlamı yoktur.

Değişikliğin Dokunduğu Satır

Tablonun ikinci yarısı ayrımın okuma dışındaki karşılığını verir. Bir abonenin tipi yanlış kaydedilmişse düz tabloda o abonenin 11 satırı düzeltilir, boyutta 1 satır. Bir bölgenin adı değişirse düz tabloda 506 satır güncellenir, boyutta 46. İkisinde de değişen bilgi tektir; değişen satır sayısı, o bilginin kaç kez yazıldığına eşittir.

Bu, ayrımın en somut kazancıdır ve tekrarın doğrudan sonucudur. Aynı bilgi 506 yerde duruyorsa güncellemenin 506 yerin hepsine ulaşması gerekir; ulaşmadığı yer kalırsa aynı bölge iki adla görünür. Boyut tablosu güncellemenin dokunduğu yüzeyi küçültür, yani tutarsızlığın oluşabileceği yeri küçültür. Karşılığı sorgu başına 2200 birleştirmedir ve bu takas, boyutsal modellemenin tamamıdır.

Son tablo iki bedelin ölçekle ilişkisini verir. Boyut tablolarının hücresi 636’dır ve olgu büyüdükçe büyümez: abone sayısı sabit kaldığı sürece 200 satır 200 satır kalır. Beş alanlı düz tablo n satırda 5n hücre tutarken yıldız 3n + 636 tutar, yani ayrımın kazancı 2n - 636’dır. Ölçülen 2200 satırda bu 3764 eder ve blok bunu doğrular. Sayı 550 satırda 464’e iner, 4400 satırda 8164’e çıkar. Okuma tarafındaki bedel ise ölçekten bağımsızdır: boyut hangi sorguda olursa olsun bir kez okunur ve 520 tutar. Ayrımın küçük tabloda tartışmalı, büyük tabloda kaçınılmaz görünmesinin nedeni budur; iki durumda da ölçülen şey aynı iki sayıdır.

Özet

  • Olgu tablosu ölçülen değeri, boyut bağıntısı o değeri niteleyen bilgiyi taşır; taneciği abone ile dönemin çifti olan 2200 satır iki düzende de aynı veriyi anlatır.
  • Saklanan hücre 15400’den 7236’ya iner, ama düşüşün 4400’ü artık taşınmayan iki alana aittir; ayrımın kendi kazancı 3764 hücredir ve boyut bağıntılarının tamamı 212 satır tutar.
  • İki düzenin ürettiği tip başına toplam birebir aynıdır; boyutsal modelleme sonucu değil, sonuca ulaşma yolunu değiştirir.
  • Bedel okumada ödenir: aynı sorgu düz tabloda 4520, olgu artı boyutta 5040 maliyet ister ve birleştirme 0’dan 2200‘e çıkar; farkın 120’si dosya sabitidir.
  • Ayrımın okuma dışındaki karşılığı güncellemedir: bir bölge adı düz tabloda 506, boyutta 46 satıra dokunur, yani tutarsızlığın oluşabileceği yüzey on bir kat küçülür.

Sonraki Adım

Bu derste boyut bağıntıları düzleştirilmiş kuruldu: abonenin bölgesi bölge adının kendisi olarak, 200 satırın her birinde yazıldı. Bölge kümesi beş elemanlıdır, yani aynı beş metin boyut bağıntısında kırk kez tekrarlanıyor. Düz tabloda tekrarı kaldırmak için uygulanan işlem burada da uygulanabilir: bölgeyi ayrı bir tabloya çıkarıp boyutta yalnızca bir başvuru bırakmak. Bu, olgu ile boyut arasında bir değil iki adımlık bir zincir kurar. Sonraki ders bu zinciri sayar: normalleştirme derecesi arttıkça kaç satır kazanılıyor, karşılığında kaç birleştirme ödeniyor ve sonuç kımıldıyor mu.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat