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ışı
donemanahtarı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_degistisatı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.