---
title: 'Veri Soyağacı'
source: 'https://academia.sh/tr/kurslar/veri-muhendisligine-giris/veri-soyagaci'
course: 'Veri Mühendisliğine Giriş'
language: tr
updated: '2026-08-17T18:08:52+00:00'
license: 'CC BY-SA 4.0'
---

# Veri Soyağacı

Bozulmuş bir sunum değerinden kaynağa gitmek için bakılan satır sayısının soyağacı kaydı varken ve yokken sayılması: beş aşamalı hat altı katmanda 9.460 satır taşırken soyağacı kaydı 11.455 bağ tutuyor, çünkü her tüketim satırının iki atası var; kayıt yokken bir sunum değerini açıklamak 9.260 satır okumayı gerektiriyor ve bu sayı hangi değerden başlandığına bağlı değil, kayıtla aynı arama 94 ile 155 satır arasında bitiyor. İki koşum iki ayrı kayıp aşaması veriyor: geç gelen kayıtta kayıp kaynakta, şema değiştiğinde ayıklama aşamasında. Sunum sapmasının büyüklüğü bozulmanın büyüklüğünü vermiyor: beş kat fazla satır bozulduğunda sunum daha az oynuyor ve iki değer yukarı gidiyor.

Önceki üç ders denetimi satırın kendisine baktırdı. Kalite boyutları satırın taşıdığı değeri
ölçtü, şema sözleşmesi satırın alanlarını, yeniden işleme aynı satırın ikinci kez geçmesini.
Üçünde de denetimin baktığı nesne elindeki satırdı. Bir soru hiç sorulmadı: bu satır **nereden
geldi**. Sunumda beş değer duruyor; bir tanesi beklenenden uzaklaştığında o değeri üreten satırlar
hangileridir ve onları bulmak için kaç satır okunur.

Bu dersin ölçtüğü şey bir arama maliyetidir. Bozulmuş bir sunum değerinden kaynağa gitmek iki
biçimde yapılabilir: hattın her katmanını baştan okuyarak ya da hattın koşarken yazdığı **soyağacı
(lineage)** kaydını geriye izleyerek. İkisi de aynı yanıtı bulur; bakılan satır sayısı arasında iki
mertebe fark vardır. Kaydın kendisinin de bir bedeli vardır ve o da sayılır.

- **VK41.** Kaynak, beş aşamalı hat ve bozulma sınıfları **kurgudur** ve ders içinde tanımlanır.
  Hiçbir depo, dosya biçimi, katalog ürünü ya da orkestratör çağrılmaz. Tohum 20260218.
- **VK42.** **Soyağacı kaydı**, her aşamanın kendi çıktı anahtarından o çıktıyı üreten girdi
  anahtarlarına yazdığı **bağ** kümesidir. Satır anahtarı abone ile dönemdir, özet anahtarı bölge
  ile dönemdir, sunum anahtarı bölgedir.
- **VK43.** Arama iki biçimde ölçülür. **Kayıt yokken** sunum değerini açıklamak için altı katmanın
  tamamı baştan okunur. **Kayıt varken** her katmanda yalnız kayıtlı atalar okunur. Sayılan birim
  **bakılan satırdır**; süre yazılmaz.
- **VK44.** Özet hücresinde sapma bandı 0,02 alınmıştır. Özet katmanı 55 hücredir, yani hücre
  oranlarının en küçük adımı 0,0182; band bu adımın hemen üstündedir. **Sunum üzerinde oran
  basılmaz**, değerin kendisi ve farkı yazılır.
- **VK45.** Gözlemlenebilirlik, iz ve günlükleme yordamı Backend Geliştirme müfredatındaki
  Gözlemlenebilirlik ve Güvenilirlik kursunda ve DevOps ve Platform Mühendisliği müfredatındaki
  Gözlemlenebilirlik ve İşletim kursunda ölçüldü; **burada tekrarlanmaz**. İzin nesnesi zaman
  içinde ilerleyen bir istektir; soyağacının nesnesi bir **satırın dayandığı satırlardır**.
- **VK46.** Soyağacı kaydı **olmuş koşumdan** çıkar. Olması gerekeni bilmez; beklenen ata kümesi
  sağlam koşumun kaydından gelir.

## Soyağacı Kaydı ve Kaç Bağ Tuttuğu

Hattın beş aşaması zaten satır düşürdüğünü sayıyor. Soyağacı bundan fazlasını ister: her çıktı
satırının hangi girdi satırlarından doğduğunu ada ada yazmasını. Kayıt aşama işlevlerinin içine
değil, girdileriyle çıktıları arasına konur; hat aynı hattır.

```python
# KURGUDUR. Olcum aginin sayac okumalari, bes asamali hat ve bozulma siniflari
# ders icinde tanimlanir; hicbir depo, dosya bicimi ya da orkestrator 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 boz(kayit, sinif, donem="2026-07"):
    """Bozulma siniflari. Hicbiri saldiri degil, uretici tarafi kazasidir."""
    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 == "sema_degisti":
            y = dict(k)
            y["endeks_degeri"] = y.pop("endeks")    # alan adi degisti
            sayac += 1
            yeni.append(y)
        else:
            yeni.append(k)
    return yeni, sayac


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


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


def a5_sun(ozet):
    bolge = {}
    for (b, d), v in ozet.items():
        bolge.setdefault(b, []).append(v)
    return {b: round(sum(v) / len(v), 2) for b, v in bolge.items()}, 0


KATMAN = ["kaynak", "ayikla", "tekille", "tuketim", "ozet", "sun"]


def ad(k):
    return (k["abone"], k["donem"])


def soyagaci(kayit):
    """Hatti kosturur ve her asamada cikti anahtarindan girdi anahtarlarina bag yazar."""
    v1, _ = a1_ayikla(kayit)
    v2, _ = a2_tekille(v1)
    v3, _ = a3_tuketim(v2)
    oz, _ = a4_ozet(v3)
    sun, _ = a5_sun(oz)
    bag = {"ayikla": {ad(k): [ad(k)] for k in v1},
           "tekille": {}, "tuketim": {}, "ozet": {}, "sun": {}}
    for k in v1:
        bag["tekille"].setdefault(ad(k), []).append(ad(k))
    for k in v3:
        i = DONEM.index(k["donem"])
        bag["tuketim"][ad(k)] = [ad(k), (k["abone"], DONEM[i - 1])]
        bag["ozet"].setdefault((k["bolge"], k["donem"]), []).append(ad(k))
    for b, d in oz:
        bag["sun"].setdefault(b, []).append((b, d))
    satir = {"kaynak": len(kayit), "ayikla": len(v1), "tekille": len(v2),
             "tuketim": len(v3), "ozet": len(oz), "sun": len(sun)}
    return sun, oz, bag, satir


kaynak = kaynak_uret()
sunum, ozet, bag, satir = soyagaci(kaynak)
print("katman   ", "  ".join(f"{a:>8}" for a in KATMAN))
print("satir    ", "  ".join(f"{satir[a]:>8}" for a in KATMAN))
print("bag      ", "  ".join(f"{sum(len(v) for v in bag[a].values()) if a in bag else 0:>8}"
                             for a in KATMAN))
toplam_bag = sum(sum(len(v) for v in bag[a].values()) for a in bag)
print(f"\nsatir toplami {sum(satir.values())} , bag toplami {toplam_bag}")
print("\nsunum degeri   deger   ata kaynak satiri")
for b, v in sunum.items():
    ata = {x for c in bag["sun"][b] for k in bag["ozet"][c]
           for t in bag["tuketim"][k] for a2 in bag["tekille"][t] for x in bag["ayikla"][a2]}
    print(f"{b:<13} {v:>7}   {len(ata):>17}")
```

```
katman      kaynak    ayikla   tekille   tuketim      ozet       sun
satir         2400      2400      2400      2200        55         5
bag              0      2400      2400      4400      2200        55

satir toplami 9460 , bag toplami 11455

sunum degeri   deger   ata kaynak satiri
merkez        1321.45                 588
kuzey         1212.55                 552
dogu           881.36                 408
bati           833.09                 372
guney         1065.55                 480
```

Kayıt, hattın taşıdığı veriden **büyüktür**. Altı katman toplam 9.460 satır tutuyor, soyağacı ise
11.455 bağ; satır başına 1,21 bağ. Fazlalığın tamamı tek aşamadan geliyor: `tuketim` 2.200 satır
üretiyor ama 4.400 bağ yazıyor, çünkü bir tüketim satırı iki okumanın **farkıdır** ve iki atası
vardır. Bir aşamanın kaç bağ yazacağını satır sayısı değil, çıktının kaç girdiye dayandığı belirler.

Son tablo soyağacının en doğrudan okumasıdır: sunulan beş değerin her biri kaç kaynak satırının
üstünde duruyor. Merkez 588, kuzey 552, güney 480, doğu 408, batı 372; toplamları 2.400, yani
kaynağın tamamı. Sunum katmanında beş sayı görünüyor; onların altında iki bin dört yüz satır var
ve hangi satırın hangi sayıya gittiği ancak yazıldıysa bilinir.

## Bozulmuş Değerden Kaynağa: İki Koşum

Arama şöyle işler. Şüpheli sunum değerinin kayıtlı ataları okunur; sağlam koşumun karşılık gelen
hücrelerinden sapan ya da hiç bulunmayan özet hücreleri seçilir; yalnız onların ataları bir alt
katmanda okunur. Her katmanda okunan satır sayısı ayrı sayılır. Kayıt yoksa böyle bir daraltma
yoktur; sunum değerini açıklamak için altı katmanın tamamı okunur.

```python
# Onceki blogun tanimlari surer: soyagaci, boz, sunum, ozet, bag, satir.
def geri_yuru(hedef, bag_s, ozet_s, kaynak_s, pay=0.02):
    """Sunum degerinden kaynaga: her katmanda yalniz kayitli atalar okunur."""
    bakilan, kayip = {"sun": 1}, None
    aday = bag["sun"][hedef]
    bakilan["ozet"] = len(aday)
    supheli = [c for c in aday if c not in bag_s["ozet"]
               or abs(ozet_s[c] - ozet[c]) / max(abs(ozet[c]), 1) > pay]
    for ust, alt in (("ozet", "tuketim"), ("tuketim", "tekille"), ("tekille", "ayikla")):
        aday = sorted({x for c in supheli for x in bag[ust][c]})
        bakilan[alt] = len(aday)
        yok = [x for x in aday if x not in bag_s[alt]]
        supheli, kayip = (yok, alt) if yok else (aday, kayip)
    aday = sorted({x for c in supheli for x in bag["ayikla"][c]})
    bakilan["kaynak"] = len(aday)
    hic = [x for x in aday if x not in kaynak_s]
    return bakilan, supheli, ("kaynak" if hic else kayip)


for sinif in ("gec_gelen", "sema_degisti"):
    bozuk, n = boz(kaynak, sinif)
    sunum_s, ozet_s, bag_s, satir_s = soyagaci(bozuk)
    kaynak_s = {ad(k) for k in bozuk}
    kayitsiz = sum(satir_s.values())
    print(f"== {sinif} , bozulan kaynak satiri {n} , kayit yokken bakilan {kayitsiz} satir")
    print("sunum      deger     fark    sun ozet tuketim tekille ayikla kaynak"
          "  toplam  kat  koken  kayip asamasi")
    for b in BOLGE:
        bak, sup, asama = geri_yuru(b, bag_s, ozet_s, kaynak_s)
        t = sum(bak.values())
        print(f"{b:<9} {sunum_s[b]:>8.2f} {sunum_s[b] - sunum[b]:>8.2f}"
              f" {bak['sun']:>6} {bak['ozet']:>4} {bak['tuketim']:>7} {bak['tekille']:>7}"
              f" {bak['ayikla']:>6} {bak['kaynak']:>6} {t:>7} {kayitsiz / t:>4.0f}"
              f" {len(sup):>6}  {asama}")
    print()
```

```
== gec_gelen , bozulan kaynak satiri 40 , kayit yokken bakilan 9260 satir
sunum      deger     fark    sun ozet tuketim tekille ayikla kaynak  toplam  kat  koken  kayip asamasi
dogu        837.18   -44.18      1   11      68      27      9      9     125   74      9  kaynak
bati        811.18   -21.91      1   11      62      12      4      4      94   99      4  kaynak
kuzey      1185.09   -27.46      1   11      92      21      7      7     139   67      7  kaynak
guney      1011.27   -54.28      1   11      80      33     11     11     147   63     11  kaynak
merkez     1284.00   -37.45      1   11      98      27      9      9     155   60      9  kaynak

== sema_degisti , bozulan kaynak satiri 200 , kayit yokken bakilan 8650 satir
sunum      deger     fark    sun ozet tuketim tekille ayikla kaynak  toplam  kat  koken  kayip asamasi
dogu        878.11    -3.25      1   11      68     102     34     34     250   35     34  ayikla
bati        845.89    12.80      1   11      62      93     31     31     229   38     31  ayikla
kuzey      1218.67     6.12      1   11      92     138     46     46     334   26     46  ayikla
guney      1072.56     7.01      1   11      80     120     40     40     292   30     40  ayikla
merkez     1318.56    -2.89      1   11      98     147     49     49     355   24     49  ayikla
```

Kayıt yokken bakılan satır sayısı **9.260**'tır ve bu sayı beş sunum değerinin hangisinden
başlandığına bağlı değildir. Aynı şey ikinci koşumda da geçerlidir: orada da tek bir sayı vardır,
8.650. Kayıtsız aramanın maliyeti hedefe göre değişmez, çünkü daraltacak bir şey yoktur; hattın o
koşumda tuttuğu her satır okunur. Soyağacıyla aynı arama ilk koşumda 94 ile 155 satır arasında
bitiyor, ikincisinde 229 ile 355 arasında. Güney değeri için oran 63 kat, batı için 99 kat.

Bağ sayısının nasıl daraldığı sütunlarda görülüyor. Özet katmanında her zaman **11** hücre okunuyor;
on iki dönem var ama tüketim ilk dönemi düşürdüğü için bölge başına on bir özet hücresi kalıyor.
Tüketim katmanında okunan satır bölgenin abone sayısının iki katıdır: merkez 98, doğu 68. Bunlar
sapan iki özet hücresinin, yani `2026-07` ile `2026-08`'in ataları. Bozulma tek dönemi bozuyor ama
tüketim bir farktır ve bozulan dönem **iki** dönemin farkına girer.

İki koşumun asıl ayrıldığı yer son sütundur. `gec_gelen` koşumunda kayıp aşaması **kaynak**: şüpheli
anahtarlar hattın hiçbir katmanında yok, kaynak kaydında da yok, yani o satırlar hiç gelmedi.
`sema_degisti` koşumunda kayıp aşaması **ayikla**: aynı anahtarlar kaynakta duruyor, ama ayıklama
aşamasından sonra bağı yok. Kayıt bu iki durumu birbirinden ayırır. Kayıtsız arama ikisinde de aynı
9.260 ya da 8.650 satırı okur ve sonunda yalnız "eksik" der.

Sunum sapmasının büyüklüğü bozulmanın büyüklüğünü vermiyor. `sema_degisti` beş kat fazla satır
bozuyor (200 satır, 40 yerine) ama sunumu daha az oynatıyor: en büyük fark 12,80, ilk koşumda 54,28
idi. Dahası iki değer **yukarı** gidiyor. Bunun nedeni ikinci koşumda bozulan satırların dönem
ortalamasından tamamen çıkması, ilk koşumda ise ortalamaya eksik bir toplamla girmesidir. Sunuma
bakarak bozulmanın çapını okumak iki koşumda da yanlış sonuç verir.

## Soyağacının Söylemediği

Kayıt bir arama alanı daraltır; bir yanıt vermez. Bunun en açık kanıtı `gec_gelen` koşumunun kaynak
sütunudur. Orada dokuz, on bir, dört anahtar okunuyor ve hepsi bulunamıyor; ama bu anahtarların
listesi bozuk koşumun kaydından çıkmadı, çünkü orada onlara ait **hiçbir bağ yok**. Liste sağlam
koşumun kaydından geldi. Soyağacı olmuş olanı yazar; olmamış bir satırı ancak beklenen ata kümesiyle
karşılaştırma görünür kılar. Beklenen küme yoksa kayıt yalnız var olanı gösterir ve eksik satır
kayıtta da sessizdir.

Kaydın ikinci sınırı bedelidir. 11.455 bağ her koşumda yeniden yazılır; yeniden işlenen bir dönem
için hat ikinci kez koştuğunda kayıt da ikinci kez yazılır. Bir önceki derste yeniden işlemenin
satırı çoğalttığı ölçülmüştü; soyağacı bu çoğalmayı da taşır ve hangi koşumun bağı olduğu ayrıca
yazılmazsa iki koşumun bağları birbirine karışır.

Üçüncü sınır bu dersin dışına çıkar. Kayıp aşaması `kaynak` çıktığında hat kendi içinde temizdir:
hiçbir aşama satır düşürmedi, satır hiç gelmedi. Soyağacı bunu doğru söyler ve orada durur. Kırk
satırın neden gelmediğini, kimin göndermesi gerektiğini ve bulgunun kime iletileceğini bilmez.
Kaynak, hattın **sahibi olmadığı** bir yerdir.

## Özet

- Soyağacı kaydı, her aşamanın çıktı anahtarından girdi anahtarlarına yazdığı bağ kümesidir; hat
  altı katmanda 9.460 satır taşırken kayıt 11.455 bağ tutar, satır başına 1,21.
- Bağ sayısını satır sayısı değil çıktının kaç girdiye dayandığı belirler: `tuketim` 2.200 satır
  üretir, 4.400 bağ yazar, çünkü bir tüketim satırı iki okumanın farkıdır.
- Kayıt yokken bir sunum değerini açıklamak 9.260 satır okumak demektir ve bu sayı hangi değerden
  başlandığına bağlı değildir; kayıtla aynı arama 94 ile 155 satır arasında biter.
- İki koşum iki ayrı kayıp aşaması verir: `gec_gelen` için kaynak, `sema_degisti` için ayıklama.
  Kayıtsız arama ikisini birbirinden ayıramaz.
- Sunum sapmasının büyüklüğü bozulmanın büyüklüğünü vermez: beş kat fazla satır bozulduğunda en
  büyük fark 54,28'den 12,80'e düşer ve iki değer yukarı gider.
- Soyağacı hiç gelmemiş satıra bağ yazamaz; eksik satırı ancak sağlam koşumun beklenen ata kümesiyle
  karşılaştırma görünür kılar.

## Sonraki Adım

Bu derste arama kaynağa kadar indi ve orada durdu. Kayıp aşaması `kaynak` çıktığında yanıtlanan soru
"hangi satırlar" oldu; yanıtlanmayan soru "kim" idi. Hattın beş aşaması var, kaynak da sayılırsa
altı; bunların kaçı bir sahip taşıyor, sahibi olmayan bir aşamada yakalanan bozulmanın bulgusu kime
gidiyor ve o kişi düzeltebileceği aşamadan kaç adım uzakta duruyor. Aynı soru geriye doğru da
sorulur: soyağacı bir dönemin satırlarına bağ yazmış olsa bile, o satırlar saklama süresi dolduğu
için silinmişse bağ nereye işaret eder. Sonraki ders sahipliği, erişimi ve saklamayı üç sayıya
çevirir ve kursu kapatır.
