Ders 09 / 12
Yavaş Değişen Boyutlar
Tarihçe tutmayan bir boyutun sorguyu yavaşlatmadan cevabı yanlışladığının ölçülmesi: yirmi abonenin tipi bir dönemde değiştiğinde üzerine yazan boyut geçmiş dönemleri de bugünün tipiyle niteliyor, 100 olgu satırı yanlış kovaya düşüyor ve işyeri toplamı 33145 yerine 35893 çıkarak 2748 metreküpü yanlış kovaya gönderiyor; iki kovanın toplamı 58454'te aynı kaldığı, olgu satırı hiç değişmediği ve birleştirme sayısı 2200'de sabit durduğu için şema, tür, tekillik ve satır sayısı denetimlerinin dördü de yeşil kalıyor, dağılım denetimi ise 0,25 bandında görmüyor; tarihçe tutmanın bedeli boyut satırının 200'den 2200'e ve maliyetin 7240'tan 13440'a çıkmasıdır, geçerlilik aralığı ise aynı doğru cevabı 220 satır ve 7500 maliyetle veriyor.
İçindekiler
Önceki iki ders boyut bağıntılarını sessiz bir varsayımla kurdu: abonenin bölgesi ve tipi değişmez. Boyut abone başına tek satır tuttu, o satır abonenin bilgisini verdi ve olgunun hangi dönemine bakıldığı bu bilgiyi hiç etkilemedi. Ölçülen bütün sayılar bu varsayımın üstünde duruyor.
Gerçek boyutlar böyle davranmaz. Bir mesken işyerine dönüşür, bir abone başka bölgeye taşınır, bir sınıflandırma yeniden yapılır. Bu değişimler olgu satırları kadar sık değildir, bu yüzden yavaş değişen boyut (slowly changing dimension) adını alırlar. Yavaşlık onları önemsiz yapmaz: boyut satırı bir kez üzerine yazıldığında geçmiş dönemlerin olguları da yeni bilgiyle nitelenir. Bu ders o anı ölçer ve kursun en rahatsız edici sonucunu verir. Üzerine yazan boyut sorguyu yavaşlatmaz, cevabı yanlışlar.
- AM17. Kaynak, hat ve depo kurgudur; gerçek bir ambar, göl, dosya biçimi ya da sorgu motoru çağrılmaz. Tohum 20260218, ölçü koşumdan bağımsızdır.
- AM18. Değişim kurgudur ve bir kaza değildir:
A001–A050aralığındaki mesken aboneler2026-07döneminden başlayarak işyeri sayılır. Bu, sıradan bir sınıflandırma güncellemesidir. - AM19. Olgu satırı 2200’dür ve değişimden etkilenmez; değişen tek şey boyut bağıntısıdır.
- AM20. Üzerine yazan boyut abone başına tek satır tutar ve son değeri saklar. Tarihçe tutan boyut abone ile dönemin her çifti için bir satır tutar.
- AM21. Maliyet
dosya x 120 + hücrebiçiminde okunur. Dosya sabiti 120 bir seçimdir. - AM22. Denetimlerin tanımı Veri Mühendisliğine Giriş kursunda kuruldu ve yordamı tekrarlanmaz; burada yalnızca hangisinin ateşlediği sorulur.
- AM23. Taban çizgisi üzerine yazan boyuttur: tarihçe yok, boyut satırı abone sayısı kadar.
- AM24. Çözünürlük: satır oranının adımı 0,0005. Toplamlar oran değil, değerin kendisi olarak yazılır; oran yalnızca dağılım bandı için basılır.
Değişimin Kurulması
İlk blok ortak tanımı kurar ve üzerine üç parça ekler. tip_degisimi değişimi olgu satırlarına
işler: 2026-07 ve sonrasındaki satırlarda ilgili abonelerin tipi işyeri olur, önceki dönemlerde
mesken kalır. Yani doğru tarihçe olgu satırlarının içindedir. scd1 bu satırlardan üzerine
yazan boyutu üretir; sözlük abone anahtarını taşıdığı için her abone için son görülen tip kalır.
scd2 tarihçe tutan boyutu üretir ve anahtarı abone ile dönemin çiftidir. tip_basina_toplam ikisi
arasında yalnızca hangi anahtarla arandığı bakımından ayrılır.
# KURGUDUR. Sayac okumalari, hat ve boyut ders icinde tanimlanir; gercek bir # ambar, gol, dosya bicimi ya da sorgu motoru cagrilmaz. 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): 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 satirlar(): v, _ = a1_ayikla(kaynak_uret()) v, _ = a2_tekille(v) v, _ = a3_tuketim(v) return v def yaz(kayitlar, bolumleme=()): 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 = {} 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} def tip_degisimi(kayitlar, donem="2026-07", abone_ust="A050"): """A001..A050 arasindaki mesken aboneler bu donemde isyeri oluyor.""" yeni = [] for k in kayitlar: if k["abone"] <= abone_ust and k["donem"] >= donem and k["tip"] == "mesken": yeni.append(dict(k, tip="isyeri")) else: yeni.append(dict(k)) return yeni def scd1(kayitlar): """Uzerine yaz: boyut yalnizca son degeri tutar.""" return {k["abone"]: k["tip"] for k in kayitlar} def scd2(kayitlar): """Tarihce tut: (abone , donem) -> tip.""" return {(k["abone"], k["donem"]): k["tip"] for k in kayitlar} def tip_basina_toplam(kayitlar, boyut, tarihli): top = {} for k in kayitlar: t = boyut[(k["abone"], k["donem"])] if tarihli else boyut[k["abone"]] top[t] = top.get(t, 0) + k["m3"] return top
Yanlış Kovaya Giden Metreküp
İkinci blok iki boyutu kurar, boyut tarafını sayar ve aynı soruyu ikisine de sorar: tip başına toplam tüketim ne kadar.
SATIR = satirlar() GERCEK = tip_degisimi(SATIR) B1, B2 = scd1(GERCEK), scd2(GERCEK) degisen = {k["abone"] for k in GERCEK if ABONE[k["abone"]]["tip"] != k["tip"]} yanlis_satir = sum(1 for k in GERCEK if B1[k["abone"]] != k["tip"]) T1 = tip_basina_toplam(GERCEK, B1, False) T2 = tip_basina_toplam(GERCEK, B2, True) print("olgu satiri:", len(GERCEK), " tipi degisen abone:", len(degisen)) print(f"{'boyut':<20}{'satir':>7}{'hucre':>7}{'birlestirme':>13}") print(f"{'uzerine yazan':<20}{len(B1):>7}{len(B1) * 2:>7}{len(GERCEK):>13}") print(f"{'tarihce tutan':<20}{len(B2):>7}{len(B2) * 3:>7}{len(GERCEK):>13}") print() print("uzerine yazan sonuc:", T1, " toplam", sum(T1.values())) print("tarihce tutan sonuc:", T2, " toplam", sum(T2.values())) print("yanlis kovaya giden:", T1["isyeri"] - T2["isyeri"], "m3") print("yanlis nitelenen olgu satiri:", yanlis_satir)
olgu satiri: 2200 tipi degisen abone: 20
boyut satir hucre birlestirme
uzerine yazan 200 400 2200
tarihce tutan 2200 6600 2200
uzerine yazan sonuc: {'isyeri': 35893, 'mesken': 22561} toplam 58454
tarihce tutan sonuc: {'isyeri': 33145, 'mesken': 25309} toplam 58454
yanlis kovaya giden: 2748 m3
yanlis nitelenen olgu satiri: 100
Sonuç satırları dersin tamamıdır. Doğru cevap işyeri kovasında 33145, mesken kovasında 25309’dur. Üzerine yazan boyutla çalışan sorgu işyerini 35893, meskeni 22561 verir. Aradaki 2748 metreküp kaybolmuş değildir; yanlış kovadadır. İki kovanın toplamı iki kurulumda da 58454’tür, birebir aynı.
Mekanizma tek cümleyle yazılır. Tipi değişen 20 abonenin 2026-02 ile 2026-06 arasındaki
100 olgu satırı o dönemlerde mesken tüketimiydi; üzerine yazan boyut yalnızca son değeri
tuttuğu için sorgu bu yüz satırı da işyeri sayıyor. Geçmiş, bugünün bilgisiyle yeniden nitelendi.
Boyut satırı yanlış değildir: A007 bugün gerçekten işyeridir. Yanlış olan, bugünün doğrusunun
geçmişe uygulanmasıdır.
Birleştirme sütunu iddianın ikinci yarısını verir. İki kurulumda da 2200 birleştirme yapılıyor. Üzerine yazan boyutla çalışan sorgu bir adım bile fazla iş yapmıyor, hatta okuduğu boyut 400 hücre, ötekininki 6600. Yanlış cevap veren kurulum daha hızlı ve daha ucuzdur. Sorguya bakarak, plana bakarak, süreye bakarak bu hata bulunamaz, çünkü ortada bir yavaşlama yoktur.
Hiçbir Denetimin Görmediği
Üçüncü blok, önceki kursta tanımlanan denetimleri bu kuruluma uygular. Dördü de olgu satırları üzerinde koşar: alan, tür, tekillik ve satır sayısı. Ardından dağılım denetimi sunulan iki kovayı doğru cevapla karşılaştırır.
def d_sema(v, alanlar=("abone", "donem", "bolge", "endeks", "birim")): return any(any(a not in k for a in alanlar) for k in v) def d_tip(v): return any(not isinstance(k.get("endeks"), int) for k in v) def d_tekillik(v): ad = [(k["abone"], k["donem"]) for k in v] return len(ad) != len(set(ad)) def d_satir_sayisi(v, beklenen=2200, pay=0.05): return abs(len(v) - beklenen) / beklenen > pay 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 print("olgu satirlari uzerinde kosan denetimler") for ad, sonuc in (("sema", d_sema(GERCEK)), ("tip", d_tip(GERCEK)), ("tekillik", d_tekillik(GERCEK)), ("satir_sayisi", d_satir_sayisi(GERCEK))): print(f" {ad:<14}{'atesledi' if sonuc else 'yesil'}") print("iki kurulumun okudugu olgu satiri:", len(GERCEK), "ve", len(GERCEK)) print("iki kovanin toplami:", sum(T1.values()), "ve", sum(T2.values())) print() print("dagilim denetimi , tarihce tutan sonuca karsi") for pay in (0.25, 0.15, 0.10, 0.05): print(f" pay {pay:.2f} {'atesledi' if d_dagilim(T1, T2, pay) else 'yesil'}") print(f"{'kova':<10}{'tarihceli':>10}{'uzerine yazan':>15}{'sapma':>8}") for kova in sorted(T2): print(f"{kova:<10}{T2[kova]:>10}{T1[kova]:>15}" f"{abs(T1[kova] - T2[kova]) / T2[kova]:>8.4f}")
olgu satirlari uzerinde kosan denetimler sema yesil tip yesil tekillik yesil satir_sayisi yesil iki kurulumun okudugu olgu satiri: 2200 ve 2200 iki kovanin toplami: 58454 ve 58454 dagilim denetimi , tarihce tutan sonuca karsi pay 0.25 yesil pay 0.15 yesil pay 0.10 atesledi pay 0.05 atesledi kova tarihceli uzerine yazan sapma isyeri 33145 35893 0.0829 mesken 25309 22561 0.1086
Dört denetimin dördü de yeşildir ve bu bir kusur değildir. Denetimler olgu satırlarına bakar, olgu satırları kusursuzdur: 2200 satır, eksik alan yok, tür kayması yok, yinelenen anahtar yok. Hata satırların içinde değil, satırla boyut arasındaki birleştirmenin içindedir. Veri kalitesinin satır düzeyinde tanımlanan hiçbir boyutu bunu göremez, çünkü ölçtükleri yerde bir bozulma yoktur.
Dağılım denetimi tek adaydır ve onun da iki koşulu vardır. Birincisi bir taban gerektirmesidir; bu ölçümde taban tarihçe tutan kurulumun cevabıdır, yani atılan tarihçe. Üzerine yazan boyutu seçen bir kurulumda o taban zaten yoktur, çünkü onu üretecek satırlar saklanmamıştır. İkincisi banttır: sapma işyerinde 0,0829, meskende 0,1086. Alışılmış 0,25 bandı bunu görmez; band 0,10’a indirildiğinde ateşler, ama o bandda düzgün hattın olağan dalgalanması da ateşleyecektir.
Bu, önceki kursun sunulan sayı sessizce yanlış olabilir iddiasının depolama tarafındaki karşılığıdır ve iki taraf arasındaki fark önemlidir. Orada bozulma üretici tarafında oluyordu ve hattın bir aşaması onu yutuyordu. Burada hiçbir bozulma yoktur: kaynak doğru, hat doğru, satır doğru. Yanlış olan tek şey düzen kararıdır ve o karar bir tabloya kaç satır yazılacağıdır.
Tarihçenin Ucuz Biçimi
Doğru cevabın bedeli tabloda görünüyor: tarihçe tutan boyut 2200 satır taşıyor, üzerine yazan 200. On bir kat. Bu, tarihçe tutmanın zorunlu bedeli değildir. Tarihçe tutan boyut her dönem için bir satır yazıyor, oysa yirmi abone dışında hiçbir şey değişmiyor. Dördüncü blok üçüncü bir biçimi ölçer: boyut yalnızca değişim noktalarını yazar ve her satır o değerin hangi dönemden itibaren geçerli olduğunu taşır.
def scd_aralik(kayitlar): """Gecerlilik araligi: abone basina yalnizca degisim noktalari yazilir.""" son, boyut = {}, [] for k in sorted(kayitlar, key=lambda k: (k["abone"], k["donem"])): if son.get(k["abone"]) != k["tip"]: boyut.append({"abone": k["abone"], "tip": k["tip"], "gecerli": k["donem"]}) son[k["abone"]] = k["tip"] return boyut def aralik_topla(kayitlar, boyut): dizin = {} for s in boyut: dizin.setdefault(s["abone"], []).append(s) top, birlestirme = {}, 0 for k in kayitlar: t = None for s in dizin[k["abone"]]: birlestirme += 1 if s["gecerli"] <= k["donem"]: t = s["tip"] top[t] = top.get(t, 0) + k["m3"] return top, birlestirme ARALIK = scd_aralik(GERCEK) T3, b3 = aralik_topla(GERCEK, ARALIK) olgu = [{"abone": k["abone"], "donem": k["donem"], "m3": k["m3"]} for k in GERCEK] q_olgu = sorgu(yaz(olgu, ()), (), ("abone", "donem", "m3")) print(f"{'boyut':<20}{'satir':>7}{'hucre':>7}{'maliyet':>9}{'birlestirme':>13}") for ad, kayit, secilen, bir in ( ("uzerine yazan", [{"abone": a, "tip": t} for a, t in B1.items()], ("abone", "tip"), len(GERCEK)), ("tarihce tutan", [{"abone": a, "donem": d, "tip": t} for (a, d), t in B2.items()], ("abone", "donem", "tip"), len(GERCEK)), ("gecerlilik araligi", ARALIK, ("abone", "tip", "gecerli"), b3)): q = sorgu(yaz(kayit, ()), (), secilen) print(f"{ad:<20}{len(kayit):>7}{q['hucre']:>7}" f"{q['maliyet'] + q_olgu['maliyet']:>9}{bir:>13}") print("olgu okumasi her ucunde ayni:", q_olgu["maliyet"], " dosya sabiti:", DOSYA_SABIT) print("gecerlilik araligi sonucu:", T3) print("tarihce tutan sonuca esit:", T3 == T2)
boyut satir hucre maliyet birlestirme
uzerine yazan 200 400 7240 2200
tarihce tutan 2200 6600 13440 2200
gecerlilik araligi 220 660 7500 2420
olgu okumasi her ucunde ayni: 6720 dosya sabiti: 120
gecerlilik araligi sonucu: {'isyeri': 33145, 'mesken': 25309}
tarihce tutan sonuca esit: True
Üçüncü satır dersin çıkış yoludur. Geçerlilik aralığı 220 satır tutuyor: 200 abonenin ilk değeri, artı tipi değişen yirmi abonenin ikinci satırı. Ürettiği sonuç tarihçe tutan boyutunkiyle birebir aynıdır, yani doğrudur. Maliyet 7500’dür; üzerine yazan boyutun 7240’ının yalnızca 260 üstünde, tarihçe tutanın 13440’ının çok altında. Bedelin kalanı birleştirmede görünür: 2420, çünkü iki satırlı abonelerde iki bakış yapılıyor.
Kursun kuralı bu tabloyu okur: düzenin sayısı sakladığı satır değil, bir sorgunun ona dokunmak için ödediğidir. Üç boyut biçimi aynı olgu bağıntısını paylaşıyor, aynı 2200 satırı okuyor ve 6720 maliyetlik olgu okumasını her birinde aynen ödüyor. Ayrıldıkları yer boyut tarafıdır ve orada ödenen fark, doğru cevabın fiyatıdır. Bu fiyat 260’tır. Üzerine yazan boyutu seçmek bu ölçümde 260 maliyet biriminden tasarruf etmek için 2748 metreküpü yanlış kovaya göndermektir ve hangi sorgu için seçildiği yazılmadığında bu takas hiç görünmez.
Özet
- Yavaş değişen bir boyut üzerine yazıldığında geçmiş dönemlerin olguları bugünün değeriyle nitelenir; yirmi abonenin tipi değiştiğinde 100 olgu satırı yanlış kovaya düşer.
- Sonuç sessizce yanlışlanır: işyeri toplamı 33145 yerine 35893 çıkar, 2748 metreküp yanlış kovaya gider ve iki kovanın toplamı 58454’te birebir aynı kalır.
- Yanlış cevap veren kurulum daha ucuzdur: birleştirme iki kurulumda da 2200, okunan boyut hücresi 400’e karşı 6600. Sorguya, plana ya da süreye bakarak bu hata bulunamaz.
- Satır düzeyinde koşan şema, tür, tekillik ve satır sayısı denetimlerinin dördü de yeşildir, çünkü olgu satırlarında hiçbir bozulma yoktur; hata birleştirmenin içindedir.
- Dağılım denetimi tek adaydır ama hem atılan tarihçeyi taban olarak ister hem de 0,25 bandında görmez: sapma işyerinde 0,0829, meskende 0,1086.
- Tarihçenin bedeli boyut satırının 200’den 2200’e, maliyetin 7240’tan 13440’a çıkmasıdır; geçerlilik aralığı aynı doğru cevabı 220 satır ve 7500 maliyetle verir.
Sonraki Adım
Üç derste tartışılan şey tablonun şekliydi: hangi alan hangi tabloda duruyor, kaç tablo var, boyut kaç satır tutuyor. Her ölçümde saklanan hücre ve okunan hücre sayıldı, ama hücrenin diske nasıl indiği hiç sorulmadı. Sorgu bir tabloyu okurken satırların tamamını mı alıyor yoksa yalnızca seçtiği sütunları mı, bu seçim okunan hücreyi kaça bölüyor ve tekrarlanan değerler yer tarafında bir şey kazandırıyor mu. Bu sorular şemadan bağımsızdır: aynı yıldız şeması iki farklı biçimde yazıldığında aynı cevabı çok farklı maliyetlerle verir. Sonraki ders bu biçimi ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.