İçeriğe geç
academia.sh

Ders 03 / 12

Göl Evi Yaklaşımı

Ham katman korunurken şema dayatmasının okuma anına taşınmasının kazandırdığının ve kaybettirdiğinin sayılması: yazma kapısı altı bozulma sınıfının ikisini sıfır aşama gecikmeyle yakalıyor ve dördünü hiç yakalamıyor, hiçbir dayatması olmayan düzenek altısını da yakalamıyor, okuma anında dayatan düzenek altısını da yakalıyor ve ortalama gecikmesi 1,33 aşama oluyor. Ortalamanın yükselmesi kötüleşme değil, tanımsız dört değerin sonlu hâle gelmesidir. Alan adı değişimi altında ham katmanı koruyan düzenek 2200 düzenli satır üretiyor, kapıda eleyen düzenek 200 satırı reddedip 1800'de kalıyor; bedel saklanan hücrenin 12600 yerine 29800 olmasıdır. Birim değişimi hiçbir kapıya takılmadan düzenli katmana yazılıyor ve ancak dördüncü aşamada görülüyor.

İçindekiler

Önceki iki ders iki uç kararı ölçtü. Veri ambarı şemayı yazma anında dayatıyor, sorguya 520 maliyetli bir okuma veriyor ve karşılığında kurtarılabilir 400 satırı yok ediyor. Veri gölü hiçbir satırı yok etmiyor, karşılığında aynı sorguya 14520 ödetiyor ve doğru yorumu tüketiciye bırakıyor; aynı ham katmanı okuyan iki tüketicinin 48004 ile 58454 metreküp bulması bunun sonucuydu.

Göl evi (lakehouse) bu iki kararı ayırıp yeniden birleştirir. Ham katman gölde olduğu gibi korunur: gelen satır ne ise o yazılır, hiçbir şey reddedilmez. Şema dayatması ortadan kalkmaz, ama yazma anından alınıp okuma anına taşınır ve tek bir yerde toplanır; sonucu ambarınki gibi şemaya uyumlu ve bölümlenmiş bir düzenli katmandır. Bu birleşimin kazandırdığı ve kaybettirdiği tek bir eksende okunur: yakalama gecikmesi. Ölçü Veri Mühendisliğine Giriş kursunun birimiyle, yani aşama olarak yazılır, böylece üç düzenek aynı cetvelle karşılaştırılabilir.

  • DY16. Kod bir kurgudur; hiçbir göl evi ürünü, tablo biçimi, katalog ya da sorgu motoru adı yazılmaz. Kaynak, hat ve denetimler Veri Mühendisliğine Giriş kursundan devralınır, tohum 20260218.
  • DY17. Göl evi iki katmanlıdır: ham katman gelen satırı olduğu gibi tutar, düzenli katman şema dayatılmış hat çıkışını tutar. Dayatma düzenli katman kurulurken, yani okuma anında koşar.
  • DY18. Yakalama gecikmesi aşama olarak yazılır: 0 hiçbir aşama koşmadan, 1 ile 5 beş aşamanın sırası. Süre ham olarak yazılmaz.
  • DY19. Yedi denetim ve altı bozulma sınıfı Veri Mühendisliğine Giriş kursundan aynen alınır ve yordamları tekrarlanmaz. Bu dersin yeni sorusu tek satırlıktır: dayatma okuma anına taşındığında gecikme ne oluyor.
  • DY20. Ambarın yazma kapısı yalnız satır kapsamı görür, yani sema ile tip; küme ve özet kapsamındaki denetimler yazma anında koşamaz. Bu ayrım önceki derste ölçüldü.
  • DY21. Ham katman korunduğu için onarım mümkündür: alan adı eşlemesi ve tür çevrimi. Ambar kapısında onarım yoktur; uymayan satır reddedilir.
  • DY22. Sorgu maliyeti bu derste yeniden ölçülmez, önceki iki dersten girdi alınır: ham katmanda 14520, donem bölümlü düzenli katmanda 520. Dosya sabiti 120 bir seçimdir.

Dayatmayı Okuma Anına Taşımak

Üç düzenek aynı bozuk kaynağı görür ve yalnız hangi denetimin nerede koştuğu bakımından ayrılır. Ambarda yazma kapısı yalnız satır kapsamındaki iki denetimi işletir. Gölde hiçbir denetim yoktur. Göl evinde yedi denetimin tamamı okuma anında, düzenli katman kurulurken koşar ve her biri kendi aşamasında ateşler.

# KURGU hat ve denetimler -- gercek bir gol evi urunu, tablo bicimi ya da sorgu
# motoru cagrilmaz. Kaynak bir olcum aginin KURGU sayac okumalaridir, tohum 20260218.
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


BOZULMA = ["gec_gelen", "yinelenen", "sema_degisti", "birim_degisti", "eksik_bolum", "kayan_tip"]


def boz(kayit, sinif, donem="2026-07"):
    """Tek bir donemi bozar , kalan donemler saglam kalir."""
    yeni, sayac = [], 0
    for k in kayit:
        if k["donem"] != donem:
            yeni.append(k)
            continue
        if sinif == "gec_gelen":
            if k["abone"] <= "A040":                # bu donemde 40 abone hic gelmedi
                sayac += 1
                continue
            yeni.append(k)
        elif sinif == "yinelenen":
            yeni.append(k)
            if k["abone"] <= "A060":                # 60 kayit iki kez geldi
                yeni.append(dict(k))
                sayac += 1
        elif sinif == "sema_degisti":
            y = dict(k)
            y["endeks_degeri"] = y.pop("endeks")    # alan adi degisti
            sayac += 1
            yeni.append(y)
        elif sinif == "birim_degisti":
            y = dict(k)
            y["endeks"] = k["endeks"] * 1000        # m3 yerine litre
            y["birim"] = "m3"                       # ETIKET DEGISMEDI , sessiz bozulma
            sayac += 1
            yeni.append(y)
        elif sinif == "eksik_bolum":
            if k["bolge"] == "kuzey":               # bir bolgenin bolumu hic yazilmadi
                sayac += 1
                continue
            yeni.append(k)
        elif sinif == "kayan_tip":
            y = dict(k)
            y["endeks"] = str(k["endeks"])          # sayi metne dondu
            sayac += 1
            yeni.append(y)
    return yeni, sayac


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):
    """Donem farkindan tuketim. Onceki donem yoksa satir hesaplanamaz."""
    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


def a4_ozet(kayit):
    top = {}
    for k in kayit:
        ad = (k["bolge"], k["donem"])
        top[ad] = top.get(ad, 0) + k["m3"]
    return top, 0


def a5_sun(ozet):
    """Sunum: bolge basina donem ortalamasi."""
    bolge = {}
    for (b, d), v in ozet.items():
        bolge.setdefault(b, []).append(v)
    return {b: round(sum(v) / len(v), 2) for b, v in bolge.items()}, 0


ASAMA = [("ayikla", a1_ayikla), ("tekille", a2_tekille), ("tuketim", a3_tuketim),
         ("ozet", a4_ozet), ("sun", a5_sun)]


# ---- DENETIMLER: her biri belirli bir asamadan SONRA kosar
def d_sema(veri, alanlar=("abone", "donem", "bolge", "endeks", "birim")):
    return any(any(a not in k for a in alanlar) for k in veri)


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


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


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


def d_bolum_tamligi(veri):
    """KUSURLU DENETIM: bolge kumesine bakar , donemi gormez."""
    return set(k["bolge"] for k in veri) != set(BOLGE)


def d_bolum_tamligi_donem(veri):
    """DOGRU DENETIM: her donemde her bolge var mi."""
    gor = set((k["bolge"], k["donem"]) for k in veri)
    return gor != set((b, d) for b in BOLGE for d in DONEM)


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


DENETIM = [
    ("sema", 0, lambda v, t: d_sema(v)),
    ("tip", 0, lambda v, t: d_tip(v)),
    ("tekillik", 0, lambda v, t: d_tekillik(v)),
    ("satir_sayisi", 0, lambda v, t: d_satir_sayisi(v, t["beklenen_satir"])),
    ("bolum", 0, lambda v, t: d_bolum_tamligi(v)),
    ("bolum_donem", 0, lambda v, t: d_bolum_tamligi_donem(v)),
    ("dagilim", 4, lambda v, t: d_dagilim(v, t["taban_ozet"])),
]


def denetimli_hat(kayit, taban, acik=None):
    """Hatti kosturur ve her asamadan sonra acik denetimleri isletir.
    Donen deger: sunum , iz , ilk yakalayan denetim ve yakalama gecikmesi (asama)."""
    acik = [a for a, _, _ in DENETIM] if acik is None else acik
    veri, iz, yakalayan, gecikme = kayit, [], None, None
    for adim in range(len(ASAMA) + 1):
        if adim > 0:
            ad, f = ASAMA[adim - 1]
            veri, dusen = f(veri)
            iz.append({"asama": ad, "kalan": len(veri), "dusen": dusen})
        for dad, dasama, df in DENETIM:
            if dad not in acik or dasama != adim:
                continue
            if df(veri, taban):
                if yakalayan is None:
                    yakalayan, gecikme = dad, adim
    sunum = veri if isinstance(veri, dict) and veri and isinstance(
        list(veri.values())[0], float) else None
    return sunum, iz, yakalayan, gecikme


def taban_kur(kaynak):
    v1, _ = a1_ayikla(kaynak)
    v2, _ = a2_tekille(v1)
    v3, _ = a3_tuketim(v2)
    oz, _ = a4_ozet(v3)
    sun, _ = a5_sun(oz)
    return {"beklenen_satir": len(kaynak), "taban_ozet": oz, "taban_sunum": sun}


kaynak = kaynak_uret()
taban = taban_kur(kaynak)
DUZENEK = [("veri ambari", ["sema", "tip"]),        # kapi yalnizca satira bakar
           ("veri golu", []),                       # hicbir dayatma yok
           ("gol evi", None)]                       # butun denetimler okuma aninda


def yaz_hucre(y, g):
    return f"{y}/{g}" if y else "-"


gecikme_kaydi = {ad: [] for ad, _ in DUZENEK}
print(f"{'sinif':<15}{'veri ambari':>14}{'veri golu':>11}{'gol evi':>16}")
for s in BOZULMA:
    gelen, _ = boz(kaynak, s)
    hucre = []
    for ad, acik in DUZENEK:
        _, _, y, g = denetimli_hat(gelen, taban, acik=acik)
        if y:
            gecikme_kaydi[ad].append(g)
        hucre.append(yaz_hucre(y, g))
    print(f"{s:<15}{hucre[0]:>14}{hucre[1]:>11}{hucre[2]:>16}")
for ad, _ in DUZENEK:
    v = gecikme_kaydi[ad]
    ort = f"{sum(v) / len(v):.2f}" if v else "tanimsiz"
    print(f"{ad:<15}yakalanan {len(v)}/{len(BOZULMA)}  ortalama gecikme {ort}")
sinif             veri ambari  veri golu         gol evi
gec_gelen                   -          -       dagilim/4
yinelenen                   -          -      tekillik/0
sema_degisti           sema/0          -          sema/0
birim_degisti               -          -       dagilim/4
eksik_bolum                 -          -   bolum_donem/0
kayan_tip               tip/0          -           tip/0
veri ambari    yakalanan 2/6  ortalama gecikme 0.00
veri golu      yakalanan 0/6  ortalama gecikme tanimsiz
gol evi        yakalanan 6/6  ortalama gecikme 1.33

Son sütun Veri Mühendisliğine Giriş kursunun ilk yakalayan denetim sütununun birebir aynısıdır; göl evi düzeneği o kursun tam denetimli hattıyla aynı sonucu veriyor, çünkü dayatmanın nerede durduğu değişse de denetim kümesi değişmiyor. Karşılaştırma bu yüzden geçerlidir: üç sütun aynı cetvelle, aşama birimiyle okunuyor.

Kazanç kapsam tarafındadır. Yazma kapısı 2/6, hiçbir dayatması olmayan düzenek 0/6, okuma anında dayatan düzenek 6/6 yakalıyor. Kapının yakalayamadığı dört sınıf yalnız gözden kaçmıyor; kapı onları görebilecek konumda değil, çünkü ikisi küme, ikisi özet kapsamı gerektiriyor ve bu kapsamlar yazma anında henüz oluşmamıştır.

Ortalama gecikme satırı dikkatle okunmalıdır. Ambarın 0,00 değeri göl evinin 1,33 değerinden küçüktür, ama bu ambarın daha hızlı yakaladığı anlamına gelmez. İki ortalama farklı sayıda sınıf üzerinden alınmıştır: ambarınki yalnız yakaladığı iki sınıfın, göl evininki altı sınıfın ortalamasıdır. Göl evinin yakaladığı iki ortak sınıf, yani sema_degisti ile kayan_tip, orada da 0 aşamada yakalanıyor; dayatmanın okuma anına taşınması bu ikisini hiç geciktirmiyor. Ortalamanın 0,00’dan 1,33’e çıkmasının tek nedeni, ambarda tanımsız olan dört değerin sonlu hâle gelmesidir: 0, 0, 4 ve 4. Bu, Veri Mühendisliğine Giriş kursundaki ayrımın depolama tarafındaki karşılığıdır: tanımsız bir gecikme sıfırdan iyi değildir, ölçülmemiş olandır. Bir düzeneğin ortalama gecikmesi, kaç sınıfı hiç yakalamadığı yazılmadan okunamaz.

Ham Katmanın Kurtardığı ve Ödettiği

Gecikme tablosu iki düzeneğin sema_degisti karşısında aynı sonucu verdiğini gösteriyor: ikisi de sıfır aşamada yakalıyor. Ayrıldıkları yer yakaladıktan sonra ne yaptıklarıdır. Ambar satırı reddeder, göl evi ham katmanda tutar ve okuma anında onarır.

SEMA = {"abone": str, "donem": str, "bolge": str, "tip": str, "endeks": int, "birim": str}
ESLEME = {"endeks_degeri": "endeks"}


def uymayan(k):
    return any(a not in k or not isinstance(k[a], t) for a, t in SEMA.items())


def kapida_ele(kayitlar):
    """Ambar: uymayan satir hic yazilmaz."""
    kabul = [k for k in kayitlar if not uymayan(k)]
    return kabul, len(kayitlar) - len(kabul)


def okuma_aninda_onar(kayitlar):
    """Gol evi: ham katman korunur , dayatma okuma aninda onarimla uygulanir."""
    y = []
    for k in kayitlar:
        if uymayan(k):
            k = dict(k)
            for eski, yeni in ESLEME.items():
                if eski in k and yeni not in k:
                    k[yeni] = k.pop(eski)
            if isinstance(k.get("endeks"), str) and k["endeks"].isdigit():
                k["endeks"] = int(k["endeks"])
        y.append(k)
    return y


def hattan(kayitlar):
    v = kayitlar
    for f in (a1_ayikla, a2_tekille, a3_tuketim):
        v, _ = f(v)
    return v


def hucre(kayitlar):
    return sum(len(k) for k in kayitlar)


bozuk, _ = boz(kaynak, "sema_degisti")
kabul, red = kapida_ele(bozuk)
onarilmis = okuma_aninda_onar(bozuk)
print("sema_degisti altinda uc duzenek")
print(f"{'duzenek':<14}{'reddedilen':>11}{'ham katman':>12}{'duzenli satir':>15}"
      f"{'saklanan hucre':>16}")
for ad, ham, duzenli_kaynak in [("veri golu", bozuk, bozuk),
                                ("veri ambari", [], kabul),
                                ("gol evi", bozuk, onarilmis)]:
    d = hattan(duzenli_kaynak)
    h = hucre(ham) + (hucre(d) if ad != "veri golu" else 0)
    print(f"{ad:<14}{red if ad == 'veri ambari' else 0:>11}{len(ham):>12}{len(d):>15}{h:>16}")
temiz = hattan(kaynak)
print("temiz kaynakta saklanan hucre  gol", hucre(kaynak), " ambar", hucre(temiz),
      " gol evi", hucre(kaynak) + hucre(temiz))
sema_degisti altinda uc duzenek
duzenek        reddedilen  ham katman  duzenli satir  saklanan hucre
veri golu               0        2400           1800           14400
veri ambari           200           0           1800           12600
gol evi                 0        2400           2200           29800
temiz kaynakta saklanan hucre  gol 14400  ambar 15400  gol evi 29800

Kazandırdığı üçüncü sütunda: göl evi 2200 düzenli satır üretiyor, öbür iki düzenek 1800. Dört yüz satırlık farkın yarısı doğrudan reddedilen kayıtlardır, öteki yarısı ise onların dolaylı sonucudur; alan adı değişen dönem düştüğünde bir sonraki dönemin tüketim farkı da hesaplanamaz hâle gelir. Ambarın kaybı bu yüzden reddettiği satır sayısından büyüktür ve bu büyümeyi kapının kendisi bildirmez. Göl evinde ham katman durduğu için eşleme tablosu yazıldığı anda her iki dönem de geri gelir.

Kaybettirdiği son sütunda: göl evi 29800 hücre saklıyor, ambar 12600, göl 14400. Temiz kaynakta oran daha nettir: 14400, 15400 ve 29800. Göl evi ikisinin toplamını saklar, çünkü aynı veriyi hem ham hem düzenli biçimde tutar. Sorgu maliyetinde ambarın seviyesindedir, yani 520; ham katmanı da 14520 ile sorgulanabilir. Yani göl evinin aldığı şey saklama hücresi karşılığında alınmış bir seçenektir: düzenli katman bozulduğunda ya da şema anlayışı değiştiğinde yeniden üretilebilir.

Kursun ikinci iddiası burada da yazılır: düzen de bozar. Göl evinin hiçbir satırı reddetmemesi, düzenli katmanının temiz olduğu anlamına gelmez. birim_degisti satırı bunun kanıtıdır: kapıya takılmıyor, onarım gerektirmiyor, düzenli katmana 2200 satırın tamamı yazılıyor ve bozulma ancak dördüncü aşamada görülüyor. O noktaya kadar düzenli katman kusursuz görünen bir şemaya sahip, tam sayıda satır taşıyan ve iki dönemi bin katına çıkmış bir tablodur. Şemaya uymak doğru olmakla aynı şey değildir; okuma anına taşınan dayatma bu farkı kapatmaz.

Özet

  • Göl evi ham katmanı korur ve şema dayatmasını yazma anından okuma anına taşıyarak tek bir yerde toplar; sonucu şemaya uyumlu bir düzenli katmandır.
  • Yakalanan bozulma sınıfı yazma kapısında 2/6, hiçbir dayatması olmayan düzenekte 0/6, okuma anında dayatan düzenekte 6/6.
  • Ortalama yakalama gecikmesi ambarda 0,00 ve göl evinde 1,33 aşamadır; ortak iki sınıf ikisinde de 0 aşamada yakalandığına göre ortalamanın yükselmesi kötüleşme değil, tanımsız dört değerin sonlu hâle gelmesidir.
  • Alan adı değişimi altında göl evi 2200 düzenli satır üretiyor, öbür iki düzenek 1800; kaybın yarısı reddedilen satır, yarısı onların düşürdüğü bir sonraki dönemdir.
  • Bedel saklanan hücredir: temiz kaynakta göl 14400, ambar 15400, göl evi 29800.
  • Okuma anında dayatma bozuk satırın düzenli katmana yazılmasını engellemez; birim_degisti hiçbir kapıya takılmadan 2200 satır olarak yazılıyor ve dördüncü aşamada görülüyor.

Sonraki Adım

Üç ders depolamanın ne sakladığını birbirinden ayırdı. Ambar şemaya uyumlu satırı, göl gelen satırın kendisini, göl evi ikisini birden saklıyor ve her seçimin sorguya, yakalamaya ve saklanan hücreye ödettiği ayrı ayrı yazıldı. Üç derste hiç sorulmayan bir soru var: bu satırlar kimin için saklanıyor. Üç düzeneğin de tek bir tüketiciye hizmet ettiği varsayıldı, oysa aynı ambardan okuyan taraflar farklı sorular sorar ve farklı sütunlara bakar. Bir bölgeyle ilgilenen taraf bütün bölgeleri taşıyan bir düzeni her sorguda baştan süzmek zorunda kalır. Sonraki ders bu soruyu sorar ve alan odaklı alt kümeleri, yani veri pazarlarını ölçer; ölçüsü iki sayının yan yana durmasıdır: alt küme ayrı yazıldığında sorgu maliyeti ne oluyor ve aynı satır kaç yerde duruyor.

İ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