İçeriğe geç
academia.sh

Ders 09 / 12

Yavaş Değişen Boyutlar

Tarihçe tutmayan bir boyutun sorguyu yavaşlatmadan cevabı yanlışladığının ölçülmesi: yirmi abonenin tipi bir dönemde değiştiğinde üzerine yazan boyut geçmiş dönemleri de bugünün tipiyle niteliyor, 100 olgu satırı yanlış kovaya düşüyor ve işyeri toplamı 33145 yerine 35893 çıkarak 2748 metreküpü yanlış kovaya gönderiyor; iki kovanın toplamı 58454'te aynı kaldığı, olgu satırı hiç değişmediği ve birleştirme sayısı 2200'de sabit durduğu için şema, tür, tekillik ve satır sayısı denetimlerinin dördü de yeşil kalıyor, dağılım denetimi ise 0,25 bandında görmüyor; tarihçe tutmanın bedeli boyut satırının 200'den 2200'e ve maliyetin 7240'tan 13440'a çıkmasıdır, geçerlilik aralığı ise aynı doğru cevabı 220 satır ve 7500 maliyetle veriyor.

İçindekiler

Önceki iki ders boyut bağıntılarını sessiz bir varsayımla kurdu: abonenin bölgesi ve tipi değişmez. Boyut abone başına tek satır tuttu, o satır abonenin bilgisini verdi ve olgunun hangi dönemine bakıldığı bu bilgiyi hiç etkilemedi. Ölçülen bütün sayılar bu varsayımın üstünde duruyor.

Gerçek boyutlar böyle davranmaz. Bir mesken işyerine dönüşür, bir abone başka bölgeye taşınır, bir sınıflandırma yeniden yapılır. Bu değişimler olgu satırları kadar sık değildir, bu yüzden yavaş değişen boyut (slowly changing dimension) adını alırlar. Yavaşlık onları önemsiz yapmaz: boyut satırı bir kez üzerine yazıldığında geçmiş dönemlerin olguları da yeni bilgiyle nitelenir. Bu ders o anı ölçer ve kursun en rahatsız edici sonucunu verir. Üzerine yazan boyut sorguyu yavaşlatmaz, cevabı yanlışlar.

  • AM17. 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.
  • AM18. Değişim kurgudur ve bir kaza değildir: A001A050 aralığındaki mesken aboneler 2026-07 döneminden başlayarak işyeri sayılır. Bu, sıradan bir sınıflandırma güncellemesidir.
  • AM19. Olgu satırı 2200’dür ve değişimden etkilenmez; değişen tek şey boyut bağıntısıdır.
  • AM20. Üzerine yazan boyut abone başına tek satır tutar ve son değeri saklar. Tarihçe tutan boyut abone ile dönemin her çifti için bir satır tutar.
  • AM21. Maliyet dosya x 120 + hücre biçiminde okunur. Dosya sabiti 120 bir seçimdir.
  • AM22. Denetimlerin tanımı Veri Mühendisliğine Giriş kursunda kuruldu ve yordamı tekrarlanmaz; burada yalnızca hangisinin ateşlediği sorulur.
  • AM23. Taban çizgisi üzerine yazan boyuttur: tarihçe yok, boyut satırı abone sayısı kadar.
  • AM24. Çözünürlük: satır oranının adımı 0,0005. Toplamlar oran değil, değerin kendisi olarak yazılır; oran yalnızca dağılım bandı için basılır.

Değişimin Kurulması

İlk blok ortak tanımı kurar ve üzerine üç parça ekler. tip_degisimi değişimi olgu satırlarına işler: 2026-07 ve sonrasındaki satırlarda ilgili abonelerin tipi işyeri olur, önceki dönemlerde mesken kalır. Yani doğru tarihçe olgu satırlarının içindedir. scd1 bu satırlardan üzerine yazan boyutu üretir; sözlük abone anahtarını taşıdığı için her abone için son görülen tip kalır. scd2 tarihçe tutan boyutu üretir ve anahtarı abone ile dönemin çiftidir. tip_basina_toplam ikisi arasında yalnızca hangi anahtarla arandığı bakımından ayrılır.

# KURGUDUR. Sayac okumalari, hat ve boyut 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 tip_degisimi(kayitlar, donem="2026-07", abone_ust="A050"):
    """A001..A050 arasindaki mesken aboneler bu donemde isyeri oluyor."""
    yeni = []
    for k in kayitlar:
        if k["abone"] <= abone_ust and k["donem"] >= donem and k["tip"] == "mesken":
            yeni.append(dict(k, tip="isyeri"))
        else:
            yeni.append(dict(k))
    return yeni


def scd1(kayitlar):
    """Uzerine yaz: boyut yalnizca son degeri tutar."""
    return {k["abone"]: k["tip"] for k in kayitlar}


def scd2(kayitlar):
    """Tarihce tut: (abone , donem) -> tip."""
    return {(k["abone"], k["donem"]): k["tip"] for k in kayitlar}


def tip_basina_toplam(kayitlar, boyut, tarihli):
    top = {}
    for k in kayitlar:
        t = boyut[(k["abone"], k["donem"])] if tarihli else boyut[k["abone"]]
        top[t] = top.get(t, 0) + k["m3"]
    return top

Yanlış Kovaya Giden Metreküp

İkinci blok iki boyutu kurar, boyut tarafını sayar ve aynı soruyu ikisine de sorar: tip başına toplam tüketim ne kadar.

SATIR = satirlar()
GERCEK = tip_degisimi(SATIR)
B1, B2 = scd1(GERCEK), scd2(GERCEK)
degisen = {k["abone"] for k in GERCEK if ABONE[k["abone"]]["tip"] != k["tip"]}
yanlis_satir = sum(1 for k in GERCEK if B1[k["abone"]] != k["tip"])
T1 = tip_basina_toplam(GERCEK, B1, False)
T2 = tip_basina_toplam(GERCEK, B2, True)
print("olgu satiri:", len(GERCEK), " tipi degisen abone:", len(degisen))
print(f"{'boyut':<20}{'satir':>7}{'hucre':>7}{'birlestirme':>13}")
print(f"{'uzerine yazan':<20}{len(B1):>7}{len(B1) * 2:>7}{len(GERCEK):>13}")
print(f"{'tarihce tutan':<20}{len(B2):>7}{len(B2) * 3:>7}{len(GERCEK):>13}")
print()
print("uzerine yazan sonuc:", T1, " toplam", sum(T1.values()))
print("tarihce tutan sonuc:", T2, " toplam", sum(T2.values()))
print("yanlis kovaya giden:", T1["isyeri"] - T2["isyeri"], "m3")
print("yanlis nitelenen olgu satiri:", yanlis_satir)
olgu satiri: 2200  tipi degisen abone: 20
boyut                 satir  hucre  birlestirme
uzerine yazan           200    400         2200
tarihce tutan          2200   6600         2200

uzerine yazan sonuc: {'isyeri': 35893, 'mesken': 22561}  toplam 58454
tarihce tutan sonuc: {'isyeri': 33145, 'mesken': 25309}  toplam 58454
yanlis kovaya giden: 2748 m3
yanlis nitelenen olgu satiri: 100

Sonuç satırları dersin tamamıdır. Doğru cevap işyeri kovasında 33145, mesken kovasında 25309’dur. Üzerine yazan boyutla çalışan sorgu işyerini 35893, meskeni 22561 verir. Aradaki 2748 metreküp kaybolmuş değildir; yanlış kovadadır. İki kovanın toplamı iki kurulumda da 58454’tür, birebir aynı.

Mekanizma tek cümleyle yazılır. Tipi değişen 20 abonenin 2026-02 ile 2026-06 arasındaki 100 olgu satırı o dönemlerde mesken tüketimiydi; üzerine yazan boyut yalnızca son değeri tuttuğu için sorgu bu yüz satırı da işyeri sayıyor. Geçmiş, bugünün bilgisiyle yeniden nitelendi. Boyut satırı yanlış değildir: A007 bugün gerçekten işyeridir. Yanlış olan, bugünün doğrusunun geçmişe uygulanmasıdır.

Birleştirme sütunu iddianın ikinci yarısını verir. İki kurulumda da 2200 birleştirme yapılıyor. Üzerine yazan boyutla çalışan sorgu bir adım bile fazla iş yapmıyor, hatta okuduğu boyut 400 hücre, ötekininki 6600. Yanlış cevap veren kurulum daha hızlı ve daha ucuzdur. Sorguya bakarak, plana bakarak, süreye bakarak bu hata bulunamaz, çünkü ortada bir yavaşlama yoktur.

Hiçbir Denetimin Görmediği

Üçüncü blok, önceki kursta tanımlanan denetimleri bu kuruluma uygular. Dördü de olgu satırları üzerinde koşar: alan, tür, tekillik ve satır sayısı. Ardından dağılım denetimi sunulan iki kovayı doğru cevapla karşılaştırır.

def d_sema(v, alanlar=("abone", "donem", "bolge", "endeks", "birim")):
    return any(any(a not in k for a in alanlar) for k in v)


def d_tip(v):
    return any(not isinstance(k.get("endeks"), int) for k in v)


def d_tekillik(v):
    ad = [(k["abone"], k["donem"]) for k in v]
    return len(ad) != len(set(ad))


def d_satir_sayisi(v, beklenen=2200, pay=0.05):
    return abs(len(v) - beklenen) / beklenen > pay


def d_dagilim(ozet, taban, pay=0.25):
    for ad, v in ozet.items():
        t = taban.get(ad)
        if t and abs(v - t) / max(abs(t), 1) > pay:
            return True
    return False


print("olgu satirlari uzerinde kosan denetimler")
for ad, sonuc in (("sema", d_sema(GERCEK)), ("tip", d_tip(GERCEK)),
                  ("tekillik", d_tekillik(GERCEK)),
                  ("satir_sayisi", d_satir_sayisi(GERCEK))):
    print(f"  {ad:<14}{'atesledi' if sonuc else 'yesil'}")
print("iki kurulumun okudugu olgu satiri:", len(GERCEK), "ve", len(GERCEK))
print("iki kovanin toplami:", sum(T1.values()), "ve", sum(T2.values()))
print()
print("dagilim denetimi , tarihce tutan sonuca karsi")
for pay in (0.25, 0.15, 0.10, 0.05):
    print(f"  pay {pay:.2f}   {'atesledi' if d_dagilim(T1, T2, pay) else 'yesil'}")
print(f"{'kova':<10}{'tarihceli':>10}{'uzerine yazan':>15}{'sapma':>8}")
for kova in sorted(T2):
    print(f"{kova:<10}{T2[kova]:>10}{T1[kova]:>15}"
          f"{abs(T1[kova] - T2[kova]) / T2[kova]:>8.4f}")
olgu satirlari uzerinde kosan denetimler
  sema          yesil
  tip           yesil
  tekillik      yesil
  satir_sayisi  yesil
iki kurulumun okudugu olgu satiri: 2200 ve 2200
iki kovanin toplami: 58454 ve 58454

dagilim denetimi , tarihce tutan sonuca karsi
  pay 0.25   yesil
  pay 0.15   yesil
  pay 0.10   atesledi
  pay 0.05   atesledi
kova       tarihceli  uzerine yazan   sapma
isyeri         33145          35893  0.0829
mesken         25309          22561  0.1086

Dört denetimin dördü de yeşildir ve bu bir kusur değildir. Denetimler olgu satırlarına bakar, olgu satırları kusursuzdur: 2200 satır, eksik alan yok, tür kayması yok, yinelenen anahtar yok. Hata satırların içinde değil, satırla boyut arasındaki birleştirmenin içindedir. Veri kalitesinin satır düzeyinde tanımlanan hiçbir boyutu bunu göremez, çünkü ölçtükleri yerde bir bozulma yoktur.

Dağılım denetimi tek adaydır ve onun da iki koşulu vardır. Birincisi bir taban gerektirmesidir; bu ölçümde taban tarihçe tutan kurulumun cevabıdır, yani atılan tarihçe. Üzerine yazan boyutu seçen bir kurulumda o taban zaten yoktur, çünkü onu üretecek satırlar saklanmamıştır. İkincisi banttır: sapma işyerinde 0,0829, meskende 0,1086. Alışılmış 0,25 bandı bunu görmez; band 0,10’a indirildiğinde ateşler, ama o bandda düzgün hattın olağan dalgalanması da ateşleyecektir.

Bu, önceki kursun sunulan sayı sessizce yanlış olabilir iddiasının depolama tarafındaki karşılığıdır ve iki taraf arasındaki fark önemlidir. Orada bozulma üretici tarafında oluyordu ve hattın bir aşaması onu yutuyordu. Burada hiçbir bozulma yoktur: kaynak doğru, hat doğru, satır doğru. Yanlış olan tek şey düzen kararıdır ve o karar bir tabloya kaç satır yazılacağıdır.

Tarihçenin Ucuz Biçimi

Doğru cevabın bedeli tabloda görünüyor: tarihçe tutan boyut 2200 satır taşıyor, üzerine yazan 200. On bir kat. Bu, tarihçe tutmanın zorunlu bedeli değildir. Tarihçe tutan boyut her dönem için bir satır yazıyor, oysa yirmi abone dışında hiçbir şey değişmiyor. Dördüncü blok üçüncü bir biçimi ölçer: boyut yalnızca değişim noktalarını yazar ve her satır o değerin hangi dönemden itibaren geçerli olduğunu taşır.

def scd_aralik(kayitlar):
    """Gecerlilik araligi: abone basina yalnizca degisim noktalari yazilir."""
    son, boyut = {}, []
    for k in sorted(kayitlar, key=lambda k: (k["abone"], k["donem"])):
        if son.get(k["abone"]) != k["tip"]:
            boyut.append({"abone": k["abone"], "tip": k["tip"], "gecerli": k["donem"]})
            son[k["abone"]] = k["tip"]
    return boyut


def aralik_topla(kayitlar, boyut):
    dizin = {}
    for s in boyut:
        dizin.setdefault(s["abone"], []).append(s)
    top, birlestirme = {}, 0
    for k in kayitlar:
        t = None
        for s in dizin[k["abone"]]:
            birlestirme += 1
            if s["gecerli"] <= k["donem"]:
                t = s["tip"]
        top[t] = top.get(t, 0) + k["m3"]
    return top, birlestirme


ARALIK = scd_aralik(GERCEK)
T3, b3 = aralik_topla(GERCEK, ARALIK)
olgu = [{"abone": k["abone"], "donem": k["donem"], "m3": k["m3"]} for k in GERCEK]
q_olgu = sorgu(yaz(olgu, ()), (), ("abone", "donem", "m3"))
print(f"{'boyut':<20}{'satir':>7}{'hucre':>7}{'maliyet':>9}{'birlestirme':>13}")
for ad, kayit, secilen, bir in (
        ("uzerine yazan", [{"abone": a, "tip": t} for a, t in B1.items()],
         ("abone", "tip"), len(GERCEK)),
        ("tarihce tutan", [{"abone": a, "donem": d, "tip": t} for (a, d), t in B2.items()],
         ("abone", "donem", "tip"), len(GERCEK)),
        ("gecerlilik araligi", ARALIK, ("abone", "tip", "gecerli"), b3)):
    q = sorgu(yaz(kayit, ()), (), secilen)
    print(f"{ad:<20}{len(kayit):>7}{q['hucre']:>7}"
          f"{q['maliyet'] + q_olgu['maliyet']:>9}{bir:>13}")
print("olgu okumasi her ucunde ayni:", q_olgu["maliyet"], " dosya sabiti:", DOSYA_SABIT)
print("gecerlilik araligi sonucu:", T3)
print("tarihce tutan sonuca esit:", T3 == T2)
boyut                 satir  hucre  maliyet  birlestirme
uzerine yazan           200    400     7240         2200
tarihce tutan          2200   6600    13440         2200
gecerlilik araligi      220    660     7500         2420
olgu okumasi her ucunde ayni: 6720  dosya sabiti: 120
gecerlilik araligi sonucu: {'isyeri': 33145, 'mesken': 25309}
tarihce tutan sonuca esit: True

Üçüncü satır dersin çıkış yoludur. Geçerlilik aralığı 220 satır tutuyor: 200 abonenin ilk değeri, artı tipi değişen yirmi abonenin ikinci satırı. Ürettiği sonuç tarihçe tutan boyutunkiyle birebir aynıdır, yani doğrudur. Maliyet 7500’dür; üzerine yazan boyutun 7240’ının yalnızca 260 üstünde, tarihçe tutanın 13440’ının çok altında. Bedelin kalanı birleştirmede görünür: 2420, çünkü iki satırlı abonelerde iki bakış yapılıyor.

Kursun kuralı bu tabloyu okur: düzenin sayısı sakladığı satır değil, bir sorgunun ona dokunmak için ödediğidir. Üç boyut biçimi aynı olgu bağıntısını paylaşıyor, aynı 2200 satırı okuyor ve 6720 maliyetlik olgu okumasını her birinde aynen ödüyor. Ayrıldıkları yer boyut tarafıdır ve orada ödenen fark, doğru cevabın fiyatıdır. Bu fiyat 260’tır. Üzerine yazan boyutu seçmek bu ölçümde 260 maliyet biriminden tasarruf etmek için 2748 metreküpü yanlış kovaya göndermektir ve hangi sorgu için seçildiği yazılmadığında bu takas hiç görünmez.

Özet

  • Yavaş değişen bir boyut üzerine yazıldığında geçmiş dönemlerin olguları bugünün değeriyle nitelenir; yirmi abonenin tipi değiştiğinde 100 olgu satırı yanlış kovaya düşer.
  • Sonuç sessizce yanlışlanır: işyeri toplamı 33145 yerine 35893 çıkar, 2748 metreküp yanlış kovaya gider ve iki kovanın toplamı 58454’te birebir aynı kalır.
  • Yanlış cevap veren kurulum daha ucuzdur: birleştirme iki kurulumda da 2200, okunan boyut hücresi 400’e karşı 6600. Sorguya, plana ya da süreye bakarak bu hata bulunamaz.
  • Satır düzeyinde koşan şema, tür, tekillik ve satır sayısı denetimlerinin dördü de yeşildir, çünkü olgu satırlarında hiçbir bozulma yoktur; hata birleştirmenin içindedir.
  • Dağılım denetimi tek adaydır ama hem atılan tarihçeyi taban olarak ister hem de 0,25 bandında görmez: sapma işyerinde 0,0829, meskende 0,1086.
  • Tarihçenin bedeli boyut satırının 200’den 2200’e, maliyetin 7240’tan 13440’a çıkmasıdır; geçerlilik aralığı aynı doğru cevabı 220 satır ve 7500 maliyetle verir.

Sonraki Adım

Üç derste tartışılan şey tablonun şekliydi: hangi alan hangi tabloda duruyor, kaç tablo var, boyut kaç satır tutuyor. Her ölçümde saklanan hücre ve okunan hücre sayıldı, ama hücrenin diske nasıl indiği hiç sorulmadı. Sorgu bir tabloyu okurken satırların tamamını mı alıyor yoksa yalnızca seçtiği sütunları mı, bu seçim okunan hücreyi kaça bölüyor ve tekrarlanan değerler yer tarafında bir şey kazandırıyor mu. Bu sorular şemadan bağımsızdır: aynı yıldız şeması iki farklı biçimde yazıldığında aynı cevabı çok farklı maliyetlerle verir. Sonraki ders bu biçimi ölçer.

İ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