Ders 01 / 12
Veri Ambarı
Hattan çıkan 2200 satırın bir depolama düzenine indirilip bir sorgunun o düzene dokunmak için ödediğinin sayılması: bölümlemesiz ve satırlı tabanda dönem süzgeçli sorgu 15400 hücre ve 15520 maliyet, yalnız seçilen sütunlar okunduğunda 4400 hücre ve 4520 maliyet, dönem bölümlemesinde 400 hücre ve 520 maliyet. Aynı düzen bölüm anahtarı dışındaki süzgeçte 4520 yerine 5720 veriyor, yani düzen bir sorguya kazandırırken ötekine kaybettiriyor. Yazma anında şema dayatması altı bozulma sınıfının ikisini yakalayıp 400 satırı reddediyor, ama reddedilen 400 satırın 400'ünde ölçüm bozulmamış duruyor; şema satırı atıyor, sayıyı değil. Dosya sabiti 120 bir seçimdir: dönem sorgusundaki 4000 hücrelik kazanç sabitten bağımsız, öteki sorgudaki 1200'lük kayıp ise tamamen sabitin eseri ve on dosyaya eşit.
İçindekiler
Veri Mühendisliğine Giriş kursu on derste bir hattı kurdu ve saydı. Kurgu ölçüm ağının 2400 sayaç okuması beş aşamadan geçti, tüketim aşamasının çıkışında 2200 satır kaldı, özet 55 hücreye ve sunum 5 değere indi; ardından altı bozulma sınıfının her birinin kaç aşama sonra yakalandığı ölçüldü. Bu ölçülerin tamamı hattın taşıdığı satır üzerineydi. Hiçbiri şunu sormadı: o 2200 satır nerede duruyor, hangi düzende yazılıyor ve o düzen bir sorguya ne ödetiyor.
Soru boş değildir: aynı 2200 satır birbirinden çok farklı biçimlerde diske indirilebilir ve seçilen biçim bir sorgunun bedelini kat kat değiştirir. Bu ders o bedeli ölçülebilir kılar. Bir depolama düzeni (storage layout) kurar, bir sorgunun o düzene dokunmak için ödediğini sayar ve ilk düzen kararını, yani şemanın yazma anında dayatılmasını, hem kattığı hem ödettiği yandan yazar. Kursun kuralı buradan çıkar ve on iki ders sürer: bir depolama düzeninin sayısı sakladığı satır değil, bir sorgunun ona dokunmak için ödediğidir; hangi sorgu için seçildiği yazılmayan düzen ölçülmemiş sayılır.
- DY1. Kod bir kurgudur; gerçek bir ambar ürünü, dosya biçimi, katalog ya da sorgu motoru çağrılmaz ve hiçbir ürün, satıcı ya da biçim adı yazılmaz. Yaklaşımlar sınıf adlarıyla anılır; SQL bir dildir ve serbesttir. Kaynak ile hat Veri Mühendisliğine Giriş kursundan devralınır, tohum 20260218’dir.
- DY2. Kaynak 200 abone x 12 dönem = 2400 okuma; hattan çıkan satır 2200, çünkü her abonenin ilk döneminin tüketim farkı tanım gereği yoktur.
- DY3. Bir düzen, satırların hangi dosyaya düştüğüdür.
yazsatırları bölümleme anahtarına göre dağıtır; anahtar boşsa tek dosya oluşur. - DY4. Bir sorgunun bedeli üç sayıdır: dokunulan dosya, okunan hücre ve maliyet = dosya x 120 + hücre. Süzgeç bölüm anahtarındaysa dosya budanır, değilse bütün dosyalara dokunulur.
- DY5. Dosya sabiti 120 bir seçimdir; ona duyarlı her sonuç sabitin değeriyle yazılır.
- DY6. Taban çizgisi bölümlemesiz, satırlı, tarihçesiz düzendir; bu üç eksenin yalnız birincisi bu derste oynatılır.
- DY7. Altı bozulma sınıfı Veri Mühendisliğine Giriş kursundan girdi alınır, yeniden
türetilmez:
gec_gelen,yinelenen,sema_degisti,birim_degisti,eksik_bolum,kayan_tip. Hiçbiri saldırı değildir; hepsi üretici tarafı kazasıdır. - DY8. Bölümleme, dizin ve sorgu planı yordamı tekrarlanmaz; Veri Modelleme ve İlişkisel Kuram ile Sorgu Eniyileme kurslarında ölçüldü. Eklenen tek şey dosya düzeyindeki maliyettir.
- DY9. Çözünürlük: 2200 satırda satır oranının adımı 0,0005, en çok 110 dosyada dosya oranının adımı 0,0091. Maliyet oran değildir; değerin kendisi yazılır.
Düzen, Dosya ve Maliyet
Veri ambarı (data warehouse), şemaya uyumlu ve yapılandırılmış veriyi saklayan depolama
sınıfıdır. Onu iki karar tanımlar. Birincisi şemanın yazma anında dayatılmasıdır: bir satır
ambara girebilmek için alanlarını ve türlerini önceden bildirilmiş şemaya uydurmak zorundadır.
İkincisi düzenin önceden seçilmiş olmasıdır: satırların hangi dosyaya düşeceğine yazma sırasında
karar verilir, okuma sırasında değil. İkinci karar ölçülebilir, çünkü bir sorgunun bedeli doğrudan
ondan çıkar. Aşağıdaki kod hattı kurar, çıkışını bir düzene indirir ve iki sorguyu üç düzende ölçer.
İkisi de aynı iki sütunu seçer; sorgu A’nın süzgeci donem, sorgu B’ninki tip alanındadır.
# KURGU depolama duzeni -- gercek bir ambar 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 # ---- DEPOLAMA DUZENI: yaz , sorgu , maliyet 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=()): """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} kayitlar = satirlar() taban = yaz(kayitlar, ()) duzenli = yaz(kayitlar, ("donem",)) SEC, OLCUM = ("bolge", "m3"), {} print("hattan cikan satir", len(kayitlar), " secilen sutun", len(SEC), "/", len(SUTUN)) print("duzen dosya ort.satir A.dosya A.hucre A.maliyet B.maliyet") for ad, d, b, st in [("bolumlemesiz/satirli", taban, (), False), ("bolumlemesiz", taban, (), True), ("donem", duzenli, ("donem",), True)]: a = sorgu(d, b, SEC, {"donem": "2026-07"}, sutunlu=st) v = sorgu(d, b, SEC, {"tip": "isyeri"}, sutunlu=st) OLCUM[ad] = (a, v) print(f"{ad:<22}{len(d):>6}{len(kayitlar) / len(d):>11.1f}" f"{a['dosya']:>9}{a['hucre']:>9}{a['maliyet']:>11}{v['maliyet']:>11}")
hattan cikan satir 2200 secilen sutun 2 / 7 duzen dosya ort.satir A.dosya A.hucre A.maliyet B.maliyet bolumlemesiz/satirli 1 2200.0 1 15400 15520 15520 bolumlemesiz 1 2200.0 1 4400 4520 4520 donem 11 200.0 1 400 520 5720
Tablo kursun üçlüsünü kuruyor ve bundan sonraki her derste aynı üç sayı yan yana duracak.
Düzensiz taban ilk satırdır: tek dosya, satırlı okuma, sorgu A için 15400 hücre ve 15520
maliyet. Düzenli kurulum son satırdır: donem anahtarına göre on bir dosya, sorgu A için 400
hücre ve 520 maliyet. Bedel bu derste iki kalemdir: sorgu B’nin maliyeti ile birazdan
ölçülecek reddedilen satır.
Orta satır iki ekseni ayırır. Yalnız seçilen iki sütunun okunması hücreyi 15400’den 4400’e
indiriyor; bu bir okuma biçimi kazancıdır ve kursun ilerleyen bir dersinde ayrıca ölçülecek.
Bölümleme ayrı bir eksendir: 4400’den 400’e inişin tamamı bölüm ayıklamasıdan (partition pruning)
gelir. Süzgeç donem alanında ve düzen de donem anahtarıyla kurulu olduğu için sorgu on bir
dosyanın onunu hiç açmıyor.
Son sütun kursun ikinci iddiasının ilk görünümüdür: düzen de bozar. Aynı donem düzeni,
süzgeci tip alanında olan sorgu B için 5720 veriyor; bölümlemesiz taban aynı sorguyu 4520
ile bitiriyor. Bölümleme burada hiçbir dosya budayamıyor, çünkü tip anahtarda değil; okunan hücre
iki düzende de 4400, ama on bir dosyanın her birine ayrı dokunuluyor. Bir düzen bir sorguya
kazandırırken bir başkasına kaybettirir ve iki sonuç aynı tabloda durur. Kursun kuralı tam olarak
bunu söyler: hangi sorgu için seçildiği yazılmayan düzen ölçülmemiş sayılır.
Şema Dayatmanın Reddettiği Satır
Şema yazma anında dayatıldığında ambar bir kapı kazanır: uymayan satır depoya hiç girmez. Bu kapının ne kattığı ve ne ödettiği, altı bozulma sınıfı kapıya sürüldüğünde görünür.
# ---- 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 # ---- SEMA YAZMA ANINDA DAYATILIR: uymayan satir hic yazilmaz SEMA = {"abone": str, "donem": str, "bolge": str, "tip": str, "endeks": int, "birim": str} def sema_ihlali(k): for a, t in SEMA.items(): if a not in k: return "eksik_alan" if not isinstance(k[a], t): return "yanlis_tip" return None def yaz_semali(kayitlar): kabul, red = [], {} for k in kayitlar: n = sema_ihlali(k) if n: red[n] = red.get(n, 0) + 1 continue kabul.append(k) return kabul, red def olcum_kurtarilabilir(k): """Reddedilen satirin olcumu duruyor mu: adi degismis ya da metne donmus olabilir.""" d = k.get("endeks", k.get("endeks_degeri")) try: int(d) return True except (TypeError, ValueError): return False kaynak = kaynak_uret() print(f"{'sinif':<15}{'gelen':>7}{'bozulan':>9}{'reddedilen':>12} {'neden':<12}{'yazilan':>8}") top_red, top_kurt, yakalanan = 0, 0, 0 for s in ["temiz"] + BOZULMA: gelen, n = (kaynak, 0) if s == "temiz" else boz(kaynak, s) kabul, red = yaz_semali(gelen) r = sum(red.values()) neden = "-" if not red else "/".join(sorted(red)) top_red += r top_kurt += sum(1 for k in gelen if sema_ihlali(k) and olcum_kurtarilabilir(k)) yakalanan += 1 if r else 0 print(f"{s:<15}{len(gelen):>7}{n:>9}{r:>12} {neden:<12}{len(kabul):>8}") print(f"reddedilen toplam {top_red} olcumu duran {top_kurt} " f"yakalanan sinif {yakalanan}/{len(BOZULMA)}")
sinif gelen bozulan reddedilen neden yazilan temiz 2400 0 0 - 2400 gec_gelen 2360 40 0 - 2360 yinelenen 2460 60 0 - 2460 sema_degisti 2400 200 200 eksik_alan 2200 birim_degisti 2400 200 0 - 2400 eksik_bolum 2354 46 0 - 2354 kayan_tip 2400 200 200 yanlis_tip 2200 reddedilen toplam 400 olcumu duran 400 yakalanan sinif 2/6
Kapının kattığı ilk satırda okunur: temiz kaynakta hiçbir satır reddedilmiyor, yani şema
dayatması sağlam veriyi engellemiyor. Bozuk kaynaklarda iki sınıf kapıya takılıyor. sema_degisti
alanın adını değiştirdiği için 200 satır eksik alan, kayan_tip sayıyı metne çevirdiği için 200
satır yanlış tür gerekçesiyle geri çevriliyor. Depoya giren her satırın alanları ve türleri
bilindiği için önceki bölümdeki sorgu istediği iki sütunu adıyla seçebiliyor ve 400 hücreyle
bitiriyor: şema dayatmasının kattığı, sorgunun okuduğu hücreyi küçültme hakkıdır.
Ödettiği iki kalemdir. Birincisi kapsam: altı sınıfın dördü kapıdan etkilenmeden geçiyor.
gec_gelen ile eksik_bolum satır eksiltiyor, ortada uymayan bir satır yok; şema gelmeyeni
göremez. yinelenen şemaya uygun satırı ikinci kez yazıyor, çünkü şema alan ve tür sorar, tekillik
sormaz. birim_degisti en tehlikelisidir: değeri bin katına çıkarıp birim etiketini m3 bırakıyor,
yani şemaya kusursuz uyuyor.
İkinci kalem kursun ikinci iddiasının buradaki karşılığıdır: düzen de bozar. Reddedilen 400
satırın 400’ünde ölçüm bozulmadan duruyor — sema_degisti satırında endeks_degeri adıyla,
kayan_tip satırında metin olarak, ama ikisinde de tamsayıya çevrilebilir hâlde. Şema kapısı
satırı atıyor, sayıyı değil. Bir düzen kararı burada veri kalitesini yükseltmiyor,
kurtarılabilir veriyi yok ediyor ve bunu sessizce yapıyor, çünkü reddedilen satır sayısı ayrıca
yazılmadıkça hiçbir sorguda görünmez.
Dosya Sabiti Bir Seçimdir
Maliyet formülündeki 120 sayısı ölçülmüş bir büyüklük değil, modelin bir seçimidir: her dosyaya dokunmanın hücre cinsinden bedelini temsil eder. Seçim olduğu için ondan çıkan her sonucun sabite ne kadar duyarlı olduğu ayrıca yazılmalıdır.
def maliyet(r, sabit): return r["dosya"] * sabit + r["hucre"] (A_t, B_t), (A_d, B_d) = OLCUM["bolumlemesiz"], OLCUM["donem"] print(" sabit A.taban A.duzenli A.kazanc B.taban B.duzenli B.kazanc") for s in (0, 120): print(f"{s:>6}{maliyet(A_t, s):>9}{maliyet(A_d, s):>11}{maliyet(A_t, s) - maliyet(A_d, s):>10}" f"{maliyet(B_t, s):>9}{maliyet(B_d, s):>11}{maliyet(B_t, s) - maliyet(B_d, s):>10}") print("dokunulan dosya farki A", A_d["dosya"] - A_t["dosya"], " B", B_d["dosya"] - B_t["dosya"])
sabit A.taban A.duzenli A.kazanc B.taban B.duzenli B.kazanc
0 4400 400 4000 4400 4400 0
120 4520 520 4000 4520 5720 -1200
dokunulan dosya farki A 0 B 10
İki satır sonucu kesin biçimde ayırıyor. Bölümlemenin sorgu A’ya kazandırdığı 4000 hücre sabitten bağımsızdır: sabit sıfır da olsa yüz yirmi de olsa kazanç aynı kalır, çünkü iki düzen de tek dosyaya dokunuyor ve fark tamamen okunan hücreden geliyor. Sorgu B’deki 1200 birimlik kayıp ise tamamen sabitin eseridir: sabit sıfırken kayıp yoktur, yüz yirmiyken kayıp on dosya çarpı yüz yirmidir. Bu dersin “düzen de bozar” bulgusu sabitin pozitif olmasına bağlıdır ve büyüklüğü sabitle doğru orantılıdır. Sabitin birden çok değerle süpürülmesi ve hangi sonuçların sıra değiştirdiği kursun ilerleyen bir dersinde ölçülür.
Tablodaki hiçbir sayı koşuma bağlı değildir. Hat belirlenimcidir, random kullanılmaz ve her hücre
koşum yapılmadan türetilebilir: hattan çıkan satır 200 abone x 11 dönem = 2200; donem
düzenindeki dosya sayısı tüketim hesaplanabilen dönem kadar, 11; dosya başına ortalama 2200 / 11
= 200 satır; iki sütun seçili bölümlemesiz okuma 2200 x 2 = 4400 hücre; budayan sorguda
200 x 2 = 400 hücre. Türetilebilirlik ölçünün yeterliliği için gereklidir: sonraki derslerde iki
düzen arasında görülen her fark düzen kararından gelir, koşum oynaklığından değil.
Özet
- Bir depolama düzeni satırların hangi dosyaya düştüğüdür; bedeli üç sayıdır: dokunulan dosya, okunan hücre ve maliyet = dosya x 120 + hücre. Dosya sabiti 120 bir seçimdir.
- Aynı sorgu bölümlemesiz ve satırlı tabanda 15400 hücre ile 15520 maliyet, yalnız seçilen sütunlar
okunduğunda 4400 hücre ile 4520 maliyet,
donembölümlemesinde 400 hücre ile 520 maliyet veriyor. - Aynı
donemdüzeni, süzgeci anahtarın dışında olan sorgu B için 4520 yerine 5720 veriyor: düzen bir sorguya kazandırırken bir başkasına kaybettirir ve bu kayıp tamamen dosya sabitinden gelir. - Yazma anında şema dayatması altı bozulma sınıfının ikisini yakalayıp 400 satırı reddediyor;
gec_gelen,yinelenen,eksik_bolumvebirim_degistikapıdan etkilenmeden geçiyor. - Reddedilen 400 satırın 400’ünde ölçüm bozulmadan duruyor: şema kapısı satırı atıyor, sayıyı değil.
- Kursun kuralı: bir düzenin sayısı sakladığı satır değil bir sorgunun ona dokunmak için ödediğidir; hangi sorgu için seçildiği yazılmayan düzen ölçülmemiş sayılır.
Sonraki Adım
Bu dersin ambarı kapıyı sıkı tuttu ve karşılığında sorguya ucuz bir okuma verdi. Kapının bedeli de ölçüldü: dört bozulma sınıfı zaten geçiyor, geri çevrilen 400 satırın ölçümü ise kurtarılabilir olduğu hâlde depoya hiç girmiyor. Buradan doğal bir soru çıkar: kapı hiç kurulmasaydı ne olurdu. Gelen her satır olduğu gibi, hiçbir şema dayatılmadan yazılsaydı yazma tarafı ne kazanır, okuma tarafı ne öderdi. Sonraki ders bu yaklaşımı, yani veri gölünü ölçer; ölçüsü iki sayının karşıtlığıdır: aynı sorgu ham düzende kaç hücre okutuyor, şemaya uyumlu düzende kaç. Tablonun ikinci yarısı daha rahatsız edici olacak, çünkü kapı kaldırıldığında hangi bozulma sınıfının yazma anında hiç yakalanamadığı da sayılacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.