---
title: 'Yavaş Değişen Boyutlar'
source: 'https://academia.sh/tr/kurslar/veri-depolama-mimarileri/yavas-degisen-boyutlar'
course: 'Veri Depolama Mimarileri'
language: tr
updated: '2026-08-17T18:08:50+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

Ö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`–`A050` aralığındaki mesken aboneler
  `2026-07` dö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ücre` biç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.

```python
# 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.

```python
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.

```python
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.

```python
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.
