İçeriğe geç
academia.sh

Ders 02 / 12

Veri Gölü

Ham veriyi olduğu gibi saklamanın yazma tarafına kazandırdığının ve okuma tarafına ödettiğinin sayılması: göl 2400 satırı 14400 hücreye hiçbir dönüşüm yapmadan yazıyor, ambar 2200 satırı 15400 hücreye yazmak için 7200 satır işliyor; aynı dönem sorgusu gölde 14400 hücre ve 14520 maliyet, ambarda 400 hücre ve 520 maliyet veriyor ve gölün yazmada kazandığı 1000 hücre tek bir sorguda 14000 hücre olarak geri ödeniyor. Altı bozulma sınıfının yalnız ikisi tek satıra bakılarak görülebiliyor; ikisi bütün kümeyi, ikisi özeti gerektiriyor, yani yazma anındaki hiçbir satır kapısı dördünü göremez ve birim değişimi satır ile küme kapsamının ikisinde de görünmez. Aynı göl katmanını okuyan iki tüketici 1800 ve 2200 satır, 45 ve 55 özet hücresi, 48004 ve 58454 metreküp buluyor.

İçindekiler

Önceki ders veri ambarını iki kararla tanımladı: şema yazma anında dayatılır ve düzen önceden seçilir. Kapının ne kattığı ölçüldü, sorgu 400 hücreyle bitti. Bedeli de ölçüldü: altı bozulma sınıfının dördü kapıdan etkilenmeden geçiyor ve geri çevrilen 400 satırın ölçümü kurtarılabilir olduğu hâlde depoya hiç girmiyor.

Bu ders karşıt kararı ölçer. Veri gölü (data lake), gelen veriyi olduğu gibi, hiçbir şema dayatmadan ve hiçbir dönüşüm yapmadan saklayan depolama sınıfıdır. Şema yoktur demek yanlış olur; şema okuma anına ertelenmiştir ve okuyan taraf onu kendi taşır. Bu ertelemenin iki yüzü vardır ve ikisi de sayılabilir: yazma ucuzlar, okuma pahalılaşır. Dersin ölçüsü bu iki sayının karşıtlığıdır; ikinci yarısı ise daha rahatsız edicidir, çünkü yazma anında hangi bozulma sınıfının hiç görülemediğini sayar.

  • DY10. Kod bir kurgudur; hiçbir göl ürünü, dosya biçimi, katalog ya da sorgu motoru adı yazılmaz. Kaynak ile hat Veri Mühendisliğine Giriş kursundan devralınır, tohum 20260218.
  • DY11. Ham katmanda alan kümesi bilinmez. Bu yüzden okuma sırasında sütun seçilemez ve bölüm budanamaz: her satırın bütün alanları okunur ve tek dosyaya dokunulur.
  • DY12. Ambar tarafı önceki dersteki kurulumdur: hat çıkışı donem anahtarıyla bölümlenmiş, sorgu iki sütun seçiyor. Dosya sabiti 120 bir seçimdir.
  • DY13. İşlenen satır ayrı bir kalemdir: bir aşamanın gördüğü satır sayısı. Ambar bu bedeli yazma anında bir kez, göl her okumada yeniden öder. Süre ham olarak yazılmaz.
  • DY14. Altı bozulma sınıfı Veri Mühendisliğine Giriş kursundan girdi alınır ve yakalama gecikmesi tablosu tekrarlanmaz; burada sorulan yeni soru bir bozulmanın görülmesi için hangi kapsamın gerektiğidir.
  • DY15. Çözünürlük: 2200 satırda satır oranının adımı 0,0005. Maliyet oran değildir; değerin kendisi yazılır.

Ham Katmanda Yazmak ve Okumak

Gölün yazma tarafı tek cümleyle tanımlanır: gelen satır ne ise o yazılır. Ambarın yazma tarafı ise iki iş yapar; satırları hattan geçirir ve çıkan satırları seçilmiş düzene yerleştirir. Aşağıdaki kod aynı kaynağı iki tarafa da verir ve tek bir soruyu, yani bir dönemin bölge toplamını, ikisine de sorar.

# KURGU depolama -- gercek bir gol urunu, dosya 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


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


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


def yaz(kayitlar, bolumleme=()):
    """Kayitlari bolumleme anahtarina gore dosyalara dagitir."""
    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: {alan: deger}. Bolumleme anahtarindaki alanlar dosya budar ,
    obur alanlar dosyanin tamamini okutur."""
    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}


# ---- HAM KATMAN: sema yok , bolumleme yok , sutun secilemez
def ham_oku(kayitlar):
    """Alan kumesi bilinmedigi icin her satirin butun alanlari okunur."""
    hucre = sum(len(k) for k in kayitlar)
    return {"dosya": 1, "hucre": hucre, "maliyet": 1 * DOSYA_SABIT + hucre}


def hattan_gecir(kayitlar):
    """Uc asama; her asamanin gordugu satir islenen olarak sayilir."""
    islenen, v = 0, kayitlar
    for f in (a1_ayikla, a2_tekille, a3_tuketim):
        islenen += len(v)
        v, _ = f(v)
    return v, islenen


kaynak = kaynak_uret()
gol = ham_oku(kaynak)
ambar_satir, donusum = hattan_gecir(kaynak)
duzenli = yaz(ambar_satir, ("donem",))
amb = sorgu(duzenli, ("donem",), ("bolge", "m3"), {"donem": "2026-07"})
SATIR = [("yazilan satir", len(kaynak), len(ambar_satir)),
         ("yazilan hucre", gol["hucre"], sum(len(k) for k in ambar_satir)),
         ("yazma aninda islenen satir", 0, donusum),
         ("sorgu: dokunulan dosya", gol["dosya"], amb["dosya"]),
         ("sorgu: okunan hucre", gol["hucre"], amb["hucre"]),
         ("sorgu: maliyet", gol["maliyet"], amb["maliyet"]),
         ("okuma aninda islenen satir", donusum, 0)]
print(f"{'olcu':<28}{'veri golu':>11}{'veri ambari':>13}")
for ad, a, b in SATIR:
    print(f"{ad:<28}{a:>11}{b:>13}")
print("yazmada golun kazanci", SATIR[1][2] - SATIR[1][1],
      " tek sorguda ambarin kazanci", gol["maliyet"] - amb["maliyet"])
olcu                          veri golu  veri ambari
yazilan satir                      2400         2200
yazilan hucre                     14400        15400
yazma aninda islenen satir            0         7200
sorgu: dokunulan dosya                1            1
sorgu: okunan hucre               14400          400
sorgu: maliyet                    14520          520
okuma aninda islenen satir         7200            0
yazmada golun kazanci 1000  tek sorguda ambarin kazanci 14000

Tablonun üst yarısı gölün iddiasını doğruluyor. Göl 2400 satırı 14400 hücreye yazıyor ve yazma sırasında hiçbir satır işlemiyor; ambar 2200 satırı 15400 hücreye yazıyor ve bunun için hattın üç aşamasında toplam 7200 satır işliyor. Yazma tarafında gölün kazancı iki kalemdir: 1000 hücre ve 7200 satırlık dönüşüm.

Alt yarı bu kazancı geri alıyor. Aynı soru gölde 14400 hücre okutuyor, ambarda 400; maliyet 14520’ye karşı 520. Fark üç ayrı nedenden gelir ve üçü de şemanın yokluğundan doğar. Alan kümesi bilinmediği için ham katmanda sütun seçilemez; hangi satırın hangi dönemde olduğu düzenden okunamadığı için bölüm budanamaz; ve tüketim hesaplanmamış olduğu için hat okuma anında yeniden koşar, yani 7200 satır her sorguda yeniden işlenir.

Son satır iki kazancı aynı birimde karşılaştırıyor: gölün yazmada kazandığı 1000 hücre, tek bir sorguda 14000 hücre olarak geri ödeniyor. Bu modelde ambar kendini ilk sorgudan önce amorti ediyor ve sorgu sayısı arttıkça fark doğrusal büyüyor. Buradan çıkan sonuç gölün gereksiz olduğu değildir; gölün savunmasının maliyet tablosunda olmadığıdır. Göl, şeması henüz bilinmeyen veriyi kabul edebildiği ve hiçbir satırı reddetmediği için tercih edilir. Bu iki özelliğin sayısı bir sonraki tabloda.

Yazma Anının Göremediği

Bir bozulmanın görülebilmesi için ne kadar veriye bakmak gerektiği, o bozulmanın yazma anında yakalanıp yakalanamayacağını belirler. Yazma anındaki bir kapı tek satır görür: elindeki satırı kabul eder ya da reddeder. Bütün kümeye ya da özete bakan bir denetim yazma anında koşamaz, çünkü küme henüz tamamlanmamıştır. Aşağıdaki kod altı sınıfı bu üç kapsama göre ayırıyor.

# ---- KURGU bozulma siniflari. Hicbiri saldiri degildir; hepsi uretici tarafi kazasidir.
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 a4_ozet(kayit):
    top = {}
    for k in kayit:
        ad = (k["bolge"], k["donem"])
        top[ad] = top.get(ad, 0) + k["m3"]
    return top, 0


# ---- UC KAPSAM: satir , kume , ozet
SEMA = {"abone": str, "donem": str, "bolge": str, "tip": str, "endeks": int, "birim": str}


def satir_kapsami(k):
    """Yazma aninda tek satira bakilarak gorulebilen: alan ve tur."""
    return int(any(a not in k or not isinstance(k[a], t) for a, t in SEMA.items()))


def kume_kapsami(veri):
    """Butun kume elde olmadan gorulemeyen: tekillik , satir sayisi , bolum tamligi."""
    ad = [(k["abone"], k["donem"]) for k in veri]
    tekillik = len(ad) != len(set(ad))
    sayi = abs(len(veri) - 2400) / 2400 > 0.05
    gor = set((k["bolge"], k["donem"]) for k in veri)
    bolum = gor != set((b, d) for b in BOLGE for d in DONEM)
    return int(tekillik or sayi or bolum)


def ozet_kapsami(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 1
    return 0


taban_ozet, _ = a4_ozet(ambar_satir)
print(f"{'sinif':<15}{'gelen':>7}{'satir':>7}{'kume':>6}{'ozet':>6}  "
      f"{'kapsam':<8}{'golde':>7}{'ambarda':>9}")
for s in ["temiz"] + BOZULMA:
    gelen, _ = (kaynak, 0) if s == "temiz" else boz(kaynak, s)
    sa = sum(satir_kapsami(k) for k in gelen)
    ku = kume_kapsami(gelen)
    oz = ozet_kapsami(a4_ozet(hattan_gecir(gelen)[0])[0], taban_ozet)
    kapsam = "satir" if sa else ("kume" if ku else ("ozet" if oz else "-"))
    print(f"{s:<15}{len(gelen):>7}{sa:>7}{ku:>6}{oz:>6}  "
          f"{kapsam:<8}{len(gelen):>7}{len(gelen) - sa:>9}")


# ---- Ayni gol katmanini iki okur farkli yorumluyor
def okur_onarimli(kayitlar):
    """Alan adi esleme tablosu tasiyan okur: eski ad yeni adla karsilanir."""
    y = []
    for k in kayitlar:
        if "endeks" not in k and "endeks_degeri" in k:
            k = dict(k)
            k["endeks"] = k.pop("endeks_degeri")
        y.append(k)
    return y


bozuk, _ = boz(kaynak, "sema_degisti")
for ad, veri in [("onarimsiz okur", bozuk), ("onarimli okur", okur_onarimli(bozuk))]:
    v, _ = hattan_gecir(veri)
    oz, _ = a4_ozet(v)
    print(f"{ad:<16}satir {len(v):>5}  ozet hucre {len(oz):>3}  toplam m3 {sum(oz.values()):>6}")
sinif            gelen  satir  kume  ozet  kapsam    golde  ambarda
temiz             2400      0     0     0  -          2400     2400
gec_gelen         2360      0     0     1  ozet       2360     2360
yinelenen         2460      0     1     0  kume       2460     2460
sema_degisti      2400    200     0     0  satir      2400     2200
birim_degisti     2400      0     0     1  ozet       2400     2400
eksik_bolum       2354      0     1     0  kume       2354     2354
kayan_tip         2400    200     0     0  satir      2400     2200
onarimsiz okur  satir  1800  ozet hucre  45  toplam m3  48004
onarimli okur   satir  2200  ozet hucre  55  toplam m3  58454

Kapsam sütunu dersin ana sonucunu taşıyor. Altı sınıfın yalnız ikisi tek satıra bakılarak görülebiliyor; ikisi bütün kümeyi, ikisi de özeti gerektiriyor. Yani yazma anındaki hiçbir satır kapısı dördünü göremez ve bu bir uygulama eksikliği değil, kapsamın kendisinden gelen bir sınırdır: gelmeyen satır bir kapıdan geçmez, ikinci kez gelen satırın tekrar olduğu ancak birincisi hatırlanırsa bilinir, bir bölgenin eksik dönemi ancak bütün dönemler yazıldıktan sonra görülür.

birim_degisti satırı en sessiz olanıdır: satır kapsamında 0, küme kapsamında da 0. Değer bin katına çıkmış, birim etiketi m3 kalmış ve satır şemaya kusursuz uyuyor; tekillik bozulmamış, bölümler tam, satır sayısı doğru. Bu bozulmayı gören tek kapsam özettir, yani hat koşup toplamlar çıktıktan sonrası. Depolama açısından sonucu şudur: bu sınıfa karşı ambarın yazma kapısı ile gölün kapısızlığı arasında hiçbir fark yoktur; ikisi de 2400 satırı yazar.

Göl ile ambarın ayrıldığı yer son iki sütundur ve fark yalnız iki satırdadır: sema_degisti ile kayan_tip gölde 2400, ambarda 2200 satır olarak duruyor. Gölün ödediği yakalama farkı bu iki sınıftan ibarettir; buna karşılık gölde kalan 200 satır kaybolmamıştır. Son iki satır bunun sonucunu gösteriyor: aynı göl katmanını okuyan iki tüketici farklı cevap üretiyor. Onarımsız okur alan adını tanımadığı için 200 satırı düşürüyor, ardından bir sonraki dönemin farkı hesaplanamadığı için 200 satır daha kaybediyor; elinde 1800 satır, 45 özet hücresi ve 48004 m3 kalıyor. Alan adı eşlemesi taşıyan okur 2200 satır, 55 hücre ve 58454 m3 buluyor. Aradaki 10450 m3, diskteki baytların değil okuma anındaki yorumun farkıdır.

Kursun ikinci iddiası burada da geçerlidir: düzen de bozar. Ham düzen hiçbir satırı yok etmiyor, buna karşılık doğru cevabın ne olduğunu depolama katmanı söylemiyor. Şema dayatmasını okuma anına ertelemek onu ortadan kaldırmaz, yalnızca çoğaltır: her tüketici kendi şemasını taşır ve tüketici sayısı kadar cevap oluşur.

Özet

  • Veri gölü gelen veriyi olduğu gibi saklar; şema ortadan kalkmaz, okuma anına ertelenir ve okuyan taraf onu kendi taşır.
  • Göl 2400 satırı 14400 hücreye hiçbir dönüşüm yapmadan yazıyor; ambar 2200 satırı 15400 hücreye yazmak için 7200 satır işliyor.
  • Aynı dönem sorgusu gölde 14400 hücre ile 14520 maliyet, ambarda 400 hücre ile 520 maliyet veriyor; gölün yazmada kazandığı 1000 hücre tek bir sorguda 14000 olarak geri ödeniyor.
  • Altı bozulma sınıfının ikisi satır, ikisi küme, ikisi özet kapsamında görülüyor; yazma anındaki bir satır kapısı dördünü göremez ve birim_degisti satır ile küme kapsamının ikisinde de görünmez.
  • Göl ile ambarın yazma farkı yalnız iki sınıftadır; buna karşılık aynı göl katmanını okuyan iki tüketici 1800 ve 2200 satır, 48004 ve 58454 m3 buluyor.

Sonraki Adım

İki ders iki uç kararı ölçtü. Ambar şemayı yazma anında dayatıyor, sorguya ucuz bir okuma veriyor ve karşılığında kurtarılabilir satırı yok ediyor. Göl hiçbir satırı yok etmiyor, karşılığında her sorguya hattı yeniden koşturuyor ve doğru yorumu tüketiciye bırakıyor. İki kararın istenen yanları birbirini dışlamıyor gibi görünür: ham katman korunabilir ve şema dayatması yine de tek bir yerde toplanabilir, yalnız yazma anında değil okuma anında. Sonraki ders bu birleşimi, yani göl evi yaklaşımını ölçer. Sorusu tek bir sayıya bağlanır: dayatma okuma anına taşındığında bozulma kaç aşama sonra yakalanıyor. Ölçü Veri Mühendisliğine Giriş kursunun birimiyle, yani aşama olarak okunacak; kazanılanın yanına kaybedilenin de yazılabilmesi için saklanan hücre ayrıca sayılacak.

İ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