İçeriğe geç
academia.sh

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. yaz satı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 duruyorsema_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, donem bölümlemesinde 400 hücre ile 520 maliyet veriyor.
  • Aynı donem dü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_bolum ve birim_degisti kapı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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat