İçeriğe geç
academia.sh

Ders 12 / 16

İş Akışı Zamanlayıcıları

Hattın adımları arasındaki bağımlılığın çizge olarak yazılmasının ve bir görev düştüğünde geri kalanın kaçının hiç koşamadığının ölçülmesi: on iki dönem ile dört görev türü 48 düğüm ve 47 kenar veriyor, tüketim görevi bir önceki dönemin ayıklamasına da bağlı olduğu için ilk dönemin alma görevi düştüğünde 5 görev, yedinci dönemin ayıklaması düştüğünde 4, tüketimi düştüğünde 1 görev hiç koşamıyor, dönemler arası on bir kenar yazılmadığında toplam bulaşma kapsamı 116'dan 72'ye iniyor ve 44 görev yeniden koşturulmadan geride kalıyor, elle koşumda ise düşen tek görev için 48 görevin tamamı yeniden koşturuluyor.

İçindekiler

On bir derste hat kuruldu: kaynaktan alındı, dönüştürüldü, katmanlara ayrıldı ve geçmişe yeniden uygulandı. Bütün bu ölçümlerde tek bir sessiz varsayım vardı — hattı biri çalıştırıyor, aşamalar sırayla koşuyor, hepsi bitiyor. Varsayım elle koşturulan bir hat için doğrudur, çünkü elle koşturan kişi sırayı kendi kafasında tutar ve bir adım hata verdiğinde ne yapacağına o anda karar verir.

Bu ders o sırayı kafadan çıkarıp yazıya döker. İki soru sorulur: hattın adımları arasındaki bağımlılık eksiksiz yazıldığında ne büyüklükte bir yapı çıkıyor, ve o yapının tek bir düğümü düştüğünde geri kalanın kaçı hiç koşamıyor. İkinci sorunun yanıtı bulaşma kapsamıdır (blast radius) ve bu kursun ilk sayısıdır.

  • OR1. Kaynak, hat ve görev çizgesi kurgudur; gerçek bir orkestratör, iş akışı ürünü ya da zamanlayıcı ürünü çağrılmaz. Tohum 20260218, ölçü koşumdan bağımsızdır.
  • OR2. Kursun kuralı: bir hattın sayısı bir kez doğru koşması değil, yeniden koşturulduğunda aynı sonucu vermesidir; yeniden koşturulamayan hat ölçülmemiş sayılır.
  • OR3. Taban çizgisi orkestrasyonsuz koşumdur: tek elle tetikleme, yeniden deneme yok, atomik yazım yok, geri doldurma yok.
  • OR4. Görev, bir aşamanın belirli bir dönem için koşmasıdır. Dört görev türü vardır: al, ayikla, tuketim, ozet.
  • OR5. Yönlü çevrimsiz çizge (directed acyclic graph) ve topolojik sıralama Veri Yapıları kursunda ölçüldü; algoritma burada tekrarlanmaz. Çizge yalnızca kurulur ve kullanılır.
  • OR6. Bulaşma kapsamı, bir görev düştüğünde hiç koşamayan görev sayısıdır. Tam sayıdır; üzerine oran basılmaz.
  • OR7. Sayılan birimler: görev, engellenen görev, yeniden koşturulan görev, tüketim satırı. Ham süre yazılmaz; gecikme dönem ya da görev olarak yazılır.
  • OR8. Çizgenin kendisi elle yazılır. Kenar listesi bir varsayımdır ve eksik olabilir.

Görev, Aşama Değil

Hattın beş aşaması bir iş akışı zamanlayıcısının (workflow scheduler) birimi değildir. Zamanlayıcı için birim görevdir: bir aşamanın belirli bir dönem için koşması. ayikla bir aşamadır, ayikla@2026-07 bir görevdir. Aralarındaki fark ölçülebilir: aşama beş tanedir, görev on iki dönem boyunca kırk sekiz tanedir.

Dört görev türü hattın beş aşamasıyla şöyle örtüşür. al kaynağın o dönemki yükünü hatta koyar; K01’in aşama listesinde karşılığı yoktur, çünkü orada veri hazır kabul edilmişti. ayikla ilk iki aşamayı, yani ayıklama ile tekilleştirmeyi tek görevde toplar. tuketim ve ozet üçüncü ile dördüncü aşamadır. Beşinci aşama olan sun çizgeye girmez: sunum döneme değil, on iki dönemin tamamına bakar ve dönem başına bir kez koşmaz.

# KURGUDUR. Sayac okumalari, bes asamali hat ve gorev cizgesi ders icinde
# tanimlanir; gercek bir depo, orkestrator ya da zamanlayici 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):
    """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


# ---- KURGU gorev cizgesi: donem basina dort gorev
GOREV = ("al", "ayikla", "tuketim", "ozet")
BAGIMLILIK = {"al": (), "ayikla": ("al",), "tuketim": ("ayikla",), "ozet": ("tuketim",)}


def cizge():
    """(gorev , donem) dugumleri. `tuketim` bir onceki donemin `ayikla`sina da baglidir."""
    dugum, kenar = [], []
    for d in DONEM:
        for g in GOREV:
            dugum.append((g, d))
            for o in BAGIMLILIK[g]:
                kenar.append(((o, d), (g, d)))
        i = DONEM.index(d)
        if i > 0:
            kenar.append((("ayikla", DONEM[i - 1]), ("tuketim", d)))
    return dugum, kenar


def sirala(dugum, kenar):
    """Yonlu cevrimsiz cizgede calistirma sirasi."""
    gelen = {u: 0 for u in dugum}
    ardil = {u: [] for u in dugum}
    for a, b in kenar:
        gelen[b] += 1
        ardil[a].append(b)
    hazir = [u for u in dugum if gelen[u] == 0]
    sira = []
    while hazir:
        u = hazir.pop(0)
        sira.append(u)
        for v in ardil[u]:
            gelen[v] -= 1
            if gelen[v] == 0:
                hazir.append(v)
    return sira, ardil


def etkilenen(dugum, kenar, dusen):
    """Bir gorev basarisiz olunca hangi gorevler hic kosamaz."""
    _, ardil = sirala(dugum, kenar)
    yigin, kapali = [dusen], set()
    while yigin:
        u = yigin.pop()
        for v in ardil[u]:
            if v not in kapali:
                kapali.add(v)
                yigin.append(v)
    return kapali

Dönemler Arası Kenar

Çizgenin şaşırtıcı yeri tuketim görevinin iki ataya birden bağlı olmasıdır: kendi döneminin ayiklasına ve bir önceki dönemin ayiklasına. Bu, uydurulmuş bir bağ değildir; tüketim aşamasının tanımından çıkar. Aşama, bir abonenin endeksinden bir önceki dönemin endeksini çıkararak tüketimi bulur, yani önceki dönem elinde yoksa satırı hiç üretemez. İlk blok bunu sayıyla gösterir.

KAYNAK = kaynak_uret()
print(f"{'kosturulan donem':<22}{'girdi':>7}{'tuketim':>9}{'dusen':>7}")
for secim in (["2026-07"], ["2026-06", "2026-07"], DONEM):
    v = [k for k in KAYNAK if k["donem"] in secim]
    v1, _ = a1_ayikla(v)
    v2, _ = a2_tekille(v1)
    c, dusen = a3_tuketim(v2)
    ad = "+".join(secim) if len(secim) < 3 else "on iki donem"
    print(f"{ad:<22}{len(v):>7}{len(c):>9}{dusen:>7}")

DUGUM, KENAR = cizge()
SIRA, _ = sirala(DUGUM, KENAR)
print()
print("dugum:", len(DUGUM), " kenar:", len(KENAR), " siralanan gorev:", len(SIRA))
print("ilk uc:", SIRA[:3])
print("son uc:", SIRA[-3:])
kosturulan donem        girdi  tuketim  dusen
2026-07                   200        0    200
2026-06+2026-07           400      200    200
on iki donem             2400     2200      0

dugum: 48  kenar: 47  siralanan gorev: 48
ilk uc: [('al', '2026-01'), ('al', '2026-02'), ('al', '2026-03')]
son uc: [('ozet', '2026-10'), ('ozet', '2026-11'), ('ozet', '2026-12')]

İlk satır bağı kanıtlar: yalnız 2026-07’yi hatta koyan bir koşum 200 satır girdiden sıfır tüketim satırı üretir ve 200 satırın tamamını düşürür. İkinci satırda bir önceki dönem eklenince aynı 200 satır hesaplanabilir hâle gelir. Yani dönemler arası kenar bir tercih değil, hattın hesabının kendisidir; yazılmadığında çizge hattı değil, hattın bir yaklaşığını temsil eder.

Üçüncü satır aynı kuralın sağlam hattaki izidir. On iki dönemin tamamı elde olduğunda düşen satır sıfırdır, ama tüketim yine de 2400 değil 2200 satır üretir: ilk dönemin 200 aboneliğinin öncesi yoktur ve bu satırlar bir hata olarak değil, tanım gereği hesap dışında kalır. Zincirin başındaki bu boşluk çizgede de görünür — tuketim@2026-01 düğümü vardır ve koşar, yalnızca üretebileceği satır yoktur.

Çizge kurulduğunda 48 düğüm ve 47 kenar çıkar. Dönem içinde üç kenar vardır (alayiklatuketimozet), on iki dönemde otuz altı eder; buna on bir dönemler arası kenar eklenir. Sıralama kırk sekiz görevin tamamını yerleştirir, yani çizge çevrimsizdir. Sıralamayı üreten yordamın kendisi Veri Yapıları kursunda ölçüldü ve burada tekrarlanmaz; buradaki soru sıralamanın nasıl bulunduğu değil, çizgenin hangi bilgiyi taşıdığıdır.

Patlama Yarıçapı

Bir görev düştüğünde zamanlayıcının verdiği ilk karar, ardıllarını koşturmamaktır. Kaç görevin koşamayacağı çizgeden okunur ve bu sayı düşen görevin çizgedeki yerine göre değişir. İkinci blok üç düşme noktasını, aynı çizgeyi dönemler arası kenarları çıkarılmış hâliyle karşılaştırarak sayar.

DUZ = [(a, b) for a, b in KENAR if a[1] == b[1]]
print("gercek kenar:", len(KENAR), " yalniz donem ici kenar:", len(DUZ))
print()
print(f"{'dusen gorev':<20}{'engellenen':>11}{'duz zincirde':>14}{'yeniden kosum':>15}")
for g, d in (("al", "2026-01"), ("ayikla", "2026-07"), ("tuketim", "2026-07"),
             ("ozet", "2026-07"), ("al", "2026-12")):
    e = len(etkilenen(DUGUM, KENAR, (g, d)))
    z = len(etkilenen(DUGUM, DUZ, (g, d)))
    print(f"{g + '@' + d:<20}{e:>11}{z:>14}{e + 1:>15}")
print()
t = sum(len(etkilenen(DUGUM, KENAR, u)) for u in DUGUM)
z = sum(len(etkilenen(DUGUM, DUZ, u)) for u in DUGUM)
print("toplam patlama yaricapi:", t, " duz zincirde:", z, " fark:", t - z)
gercek kenar: 47  yalniz donem ici kenar: 36

dusen gorev          engellenen  duz zincirde  yeniden kosum
al@2026-01                    5             3              6
ayikla@2026-07                4             2              5
tuketim@2026-07               1             1              2
ozet@2026-07                  0             0              1
al@2026-12                    3             3              4

toplam patlama yaricapi: 116  duz zincirde: 72  fark: 44

Tablo tek bir cümleyi sayıya çevirir: zincirin şekli patlamanın büyüklüğünü belirler. Aynı hatta, aynı kırk sekiz görev arasında, düşen göreve göre engellenen görev sayısı 0 ile 5 arasında değişiyor. ozet@2026-07 düştüğünde hiçbir görev engellenmiyor, çünkü ardılı yok. tuketim@2026-07 düştüğünde yalnız kendi döneminin özeti duruyor. al@2026-01 düştüğünde beş görev koşamıyor ve bunların ikisi başka bir dönemin görevidir: dönemler arası kenar, 2026-01’in ayıklamasını 2026-02’nin tüketimine bağlar.

Son sütun orkestrasyonun asıl kazancını verir. Düşen görev artı engellenen görevler, yeniden koşturulması gereken kümedir: sırasıyla 6, 5, 2, 1 ve 4 görev. Bu sayı çizge olmadan bilinemez.

Üçüncü sütun ise orkestrasyonun kendi kusurudur. Dönemler arası on bir kenar yazılmamış bir çizge de kurulur, sıralanır ve çalışır; hiçbir şey hata vermez. Ama bulaşma kapsamını al@2026-01 için 5 yerine 3, ayikla@2026-07 için 4 yerine 2 gösterir. Bütün düğümler üzerinde toplandığında gerçek çizge 116, eksik çizge 72 verir: aradaki 44 görev, düştükleri gün yeniden koşturulmaz ve eski sonuçlarıyla yerlerinde kalır. Bir çizgenin çalışıyor olması doğru olduğu anlamına gelmez.

Denetim Noktası

Çizge bir görev düştüğünde neyin engellendiğini söyler. Söylemediği şey neyin bittiğidir. Zamanlayıcı her görev tamamlandığında bunu kaydeder; bu kayıt denetim noktasıdır (checkpoint) ve yeniden başlatmanın nereden devam edeceğini belirler. İki bilgi birbirinin yerine geçmez: biri aşağı doğru, öbürü geriye doğru bakar.

print(f"{'dusen gorev':<20}{'tamamlanan':>11}{'noktali':>9}{'noktasiz':>10}{'engellenen':>12}")
for g, d in (("al", "2026-01"), ("ayikla", "2026-07"), ("tuketim", "2026-07")):
    i = SIRA.index((g, d))
    print(f"{g + '@' + d:<20}{i:>11}{len(SIRA) - i:>9}{len(SIRA):>10}"
          f"{len(etkilenen(DUGUM, KENAR, (g, d))):>12}")
dusen gorev          tamamlanan  noktali  noktasiz  engellenen
al@2026-01                    0       48        48           5
ayikla@2026-07               18       30        48           4
tuketim@2026-07              30       18        48           1

ayikla@2026-07 düştüğünde sıralamada ondan önceki 18 görev bitmiştir. Denetim noktası tutuluyorsa yeniden başlatma 30 görev koşar; tutulmuyorsa hiçbir görevin bittiği bilinmediği için 48 görev koşar. tuketim@2026-07 için fark daha büyüktür: 18 görev yerine 48.

Üç satırın birlikte söylediği şey, iki sayının farklı sorulara yanıt verdiğidir. Engellenen görev sayısı hangi sonuçların artık geçersiz olduğunu verir; denetim noktası hangi işin tekrarlanmayacağını verir. al@2026-01 satırı bunu tersinden gösterir: ilk görev düştüğünde tamamlanan görev sıfırdır, denetim noktası hiçbir şey kazandırmaz, ama bulaşma kapsamı yine de beştir. Bir zamanlayıcının iki ayrı kaydı olması bu yüzdendir.

Elle Koşumun Bedeli

Orkestrasyonsuz taban çizgisi bu tablonun karşısına konur. Elle koşturan kişi bağımlılığı yazıya dökmediği için ayikla@2026-07 düştüğünde hangi görevlerin bozulduğunu bilmez. Elinde iki seçenek kalır: ya yalnız düşen görevi yeniden koşturur ve 2026-08’in tüketimini eski sonucuyla bırakır, ya da güvenli olanı yapıp kırk sekiz görevin tamamını yeniden koşturur. İkinci seçenek doğrudur ve bedeli sekiz kattır: çizgeyle 5 görev yeterken elle 48 görev koşar.

Bedelin yalnız görev sayısı olmadığı sonraki iki derste görülecek. Kırk sekiz görevi yeniden koşturmak al görevini on iki kez yeniden çalıştırmak demektir, ve hattın yazımı yinelemeye dayanıklı değilse bu koşum satır çoğaltır. Çizgenin kazancı burada ikiye çıkar: hem daha az görev koşar, hem de dokunulmayan dönemlerin verisi hiç yeniden yazılmaz.

Buna karşılık çizgenin kendisi bedava değildir. Kırk yedi kenarın tamamı elle yazılır, hattın kodu değiştikçe elle güncellenir ve doğruluğunu denetleyen bir şey yoktur. Eksik kenarın nasıl sessizce geçtiği yukarıda ölçüldü: sıralama yine tam, koşum yine yeşil, bulaşma kapsamı yine bir sayı verir — yalnızca yanlış bir sayı.

Özet

  • Orkestrasyonun birimi aşama değil görevdir: bir aşamanın belirli bir dönem için koşması. On iki dönem ve dört görev türü 48 düğüm ile 47 kenar verir, sıralama kırk sekizini de yerleştirir.
  • Tüketim görevi bir önceki dönemin ayıklamasına bağlıdır ve bu bağ ölçülür: yalnız 2026-07 koşturulduğunda 200 satır girdiden 0 tüketim satırı çıkar, 2026-06 eklenince 200 çıkar.
  • Bulaşma kapsamı düşen görevin çizgedeki yerine göre 0 ile 5 arasında değişir; al@2026-01 için 5, ayikla@2026-07 için 4, tuketim@2026-07 için 1, ozet@2026-07 için 0.
  • Dönemler arası on bir kenar yazılmadığında çizge yine çalışır ama toplam bulaşma kapsamını 116 yerine 72 gösterir; aradaki 44 görev yeniden koşturulmadan kalır.
  • Denetim noktası ayrı bir sayı verir: ayikla@2026-07 düştüğünde 18 görev bitmiştir, yeniden başlatma noktalı kurulumda 30, noktasız kurulumda 48 görev koşar.
  • Orkestrasyonsuz tabanda düşen tek görev için 48 görevin tamamı yeniden koşturulur; çizgeyle aynı düşme için 5 görev yeter. Bedel, kırk yedi kenarın elle yazılıp elle güncellenmesidir.

Sonraki Adım

Çizge sırayı veriyor ve düşen görevin kimi engellediğini söylüyor, ama bir şeyi hiç söylemiyor: zinciri kimin başlattığını. Kırk sekiz görevin otuz altısı bir başka görevin bitmesiyle tetiklenir; geriye on iki al görevi kalır ve onların atası yoktur. Bu görevleri ne çalıştırıyor sorusu göründüğünden daha pahalıdır, çünkü yanıtı sabit bir takvimse görev veri gelmeden koşar ve boş biter. Sonraki ders üç tetikleyici sınıfını aynı hatta karşılaştırır ve tek bir sayıyı ölçer: aynı işin kaç kez ve ne zaman çalıştığı.

İ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