İçeriğe geç
academia.sh

Ders 13 / 15

Zamanlayıcı–Aracı–Denetçi

Yarım kalan işi kimsenin sormadığı bir iş akışının dayanıklı hâle getirilmesi: işi kalıcı kaydeden zamanlayıcı, adımı uzaktan yürüten aracı ve süresi dolan adımı bulup yeniden süren ya da telafi eden denetçi; denetçisiz ve denetçili koşumda yarım kalan iş, yeniden sürülen adım ve boşa yürütülen adım sayılarının ölçülmesi ve tarama aralığının kurtarma gecikmesiyle ödünleşmesi.

İçindekiler

Önceki dersin bütün hataları açıkça bildirildi: bir adım hata döndürdü, telafi zinciri işledi ya da işlemedi. Gerçek bir iş akışının çoğu arızası böyle konuşmaz. Yürütücü komutu gönderdikten sonra çöker, adım yanıt vermeden asılı kalır, telafi çağrısı bir kez denenir ve düşer. Böyle bir iş sonsuza kadar “yürüyor” görünür; kimse sormadığı için de kimse fark etmez.

Zamanlayıcı–aracı–denetçi (scheduler–agent–supervisor) bu boşluğu üç rolle kapatır. Zamanlayıcı, iş akışının durumunu kalıcı bir kayda yazar ve sıradaki adımı başlatır; işin nerede olduğu bellekte değil, süreç çöktükten sonra da okunabilen bir kayıttadır. Aracı, adımı uzaktaki serviste yürütür ve sonucu kayda düşer. Denetçi, kaydı düzenli aralıklarla tarar; başlatıldığı hâlde tanınan süre içinde sonucu düşmemiş adımları bulur, yeniden sürer ve deneme eşiği aşıldığında telafiyi başlatır. Her başlatılan adıma tanınan bu süreye kira süresi (lease) denir.

Üç Rolün Ayrılmasının Nedeni

Ayrımın gerekçesi tek cümlede yazılabilir: hiçbir rol kendi arızasını fark edemez. Aracı çöktüğünde “çöktüm” diyecek bir taraf yoktur; zamanlayıcı çöktüğünde başlattığı adımların sonucunu bekleyecek kimse kalmaz. Fark etme işi, işi yapmayan bir üçüncü tarafa verilir ve o taraf yalnız kalıcı kayda bakar.

Model iki arıza türünü ayırır. Asılı kalma: aracı işi yürütmüştür ama sonucu kayda düşememiştir — çağrı yanıtsız kalmış ya da süreç kayıt öncesinde durmuştur. Kalıcı hata: adım hiçbir denemede geçmez, çünkü sözleşme kaydı eksiktir ya da tarife tanımlanmamıştır. İkisi aynı görünür — kayıt düşmemiştir — ve denetçi ikisini ancak deneyerek ayırır.

// denetci/akis.mjs — zamanlayici, araci ve denetci; tur tabanli surec ici model. Tur soyut bir
// adimdir. Asili kalma ve kalici hata parametreyle tetiklenir, olculmus sureler degildir.
export const ADIM = ["tarife", "indirim", "fatura", "bildirim"];
export const ISLER = Array.from({ length: 12 }, (_, i) => `S${i + 1}`);
export const ASILI = { S2: "indirim", S5: "tarife", S9: "fatura" };  // araci isi yapti, kayit dusmedi
export const KALICI = { S7: "indirim" };                             // adim hicbir denemede gecmez

export function kosum({ denetci = true, tarama = 2, kira = 2, denemeSiniri = 3, tur = 80 }) {
  const K = ISLER.map((is) => ({ is, adim: 0, hal: "bekliyor", basladi: 0, deneme: 0 }));
  const s = { tamamlanan: 0, adim: 0, yenidenSurulen: 0, telafiEdilen: 0, bosaYurutulen: 0,
    okunanKayit: 0, kurtarma: [], bittigiTur: 0 };

  for (let t = 1; t <= tur; t++) {
    for (const k of K) {                                   // araci: bir onceki turda baslayan adim
      if (k.hal !== "yuruyor" || t !== k.basladi + 1) continue;
      const ad = ADIM[k.adim];
      if (KALICI[k.is] === ad) continue;                   // kayit hicbir zaman dusmez
      if (ASILI[k.is] === ad && k.deneme === 0) continue;  // is yapildi, kayit dusmedi
      s.adim += 1; k.adim += 1; k.deneme = 0; k.hal = "bekliyor";
    }
    if (denetci && t % tarama === 0) {                     // denetci: suresi dolan adimlari bulur
      s.okunanKayit += K.length;
      for (const k of K) {
        if (k.hal !== "yuruyor" || t - k.basladi < kira) continue;
        s.yenidenSurulen += 1;
        s.kurtarma.push(t - (k.basladi + 1));              // beklenen kayittan bu yana gecen tur
        if (ASILI[k.is] === ADIM[k.adim]) s.bosaYurutulen += 1;   // adim ikinci kez yurutulecek
        k.deneme += 1; k.hal = "bekliyor";
      }
    }
    for (const k of K) {                                   // zamanlayici: siradaki adimi baslatir
      if (k.hal !== "bekliyor") continue;
      if (k.adim >= ADIM.length) { k.hal = "bitti"; s.tamamlanan += 1; s.bittigiTur = t; continue; }
      if (k.deneme >= denemeSiniri) {                      // esik asildi: tamamlanmislar telafi edilir
        s.telafiEdilen += k.adim; k.hal = "telafi"; s.bittigiTur = t; continue;
      }
      k.hal = "yuruyor"; k.basladi = t;
    }
    if (K.every((k) => k.hal === "bitti" || k.hal === "telafi")) break;   // butun isler durdu
  }
  const ort = s.kurtarma.length ? s.kurtarma.reduce((a, b) => a + b, 0) / s.kurtarma.length : 0;
  return { ...s, yarimKalanIs: K.filter((k) => k.hal === "yuruyor").length,
    telafiEdilenIs: K.filter((k) => k.hal === "telafi").length,
    ortKurtarma: ort, enKotuKurtarma: s.kurtarma.length ? Math.max(...s.kurtarma) : 0 };
}
// denetci/olc.mjs — denetcisiz ve denetcili kosum, sonra tarama araliginin taranmasi
import { kosum, ISLER, ADIM, ASILI, KALICI } from "./akis.mjs";

const D = [["denetci yok", kosum({ denetci: false })], ["denetci var", kosum({ denetci: true })]];
const OLCU = [["tamamlanan", "tamamlanan is"], ["adim", "yurutulen adim"],
  ["yenidenSurulen", "yeniden surulen adim"], ["bosaYurutulen", "bosa yurutulen adim"],
  ["telafiEdilenIs", "telafi edilen is"], ["telafiEdilen", "telafi edilen adim"],
  ["yarimKalanIs", "yarim kalan is"], ["bittigiTur", "son isin bittigi tur"]];

console.log(`${ISLER.length} is, ${ADIM.length} adim; ${Object.keys(ASILI).length} adim asili kaliyor ` +
  `(${Object.entries(ASILI).map(([i, a]) => `${i}@${a}`).join(", ")}), ` +
  `${Object.keys(KALICI).length} adim kalici hatali (${Object.entries(KALICI).map(([i, a]) => `${i}@${a}`).join(", ")})`);
console.log();
console.log(`${"olcu".padEnd(24)}${D.map(([a]) => a.padStart(14)).join("")}`);
for (const [k, ad] of OLCU)
  console.log(`${ad.padEnd(24)}${D.map(([, r]) => String(r[k]).padStart(14)).join("")}`);

console.log(`\n${"tarama".padStart(7)}${"kurtarilan is".padStart(15)}${"ort kurtarma".padStart(14)}` +
  `${"en kotu".padStart(9)}${"son tur".padStart(9)}${"okunan kayit".padStart(14)}`);
for (const tarama of [1, 2, 4, 8]) {
  const r = kosum({ denetci: true, tarama });
  console.log(`${String(tarama).padStart(7)}${String(r.tamamlanan).padStart(15)}` +
    `${r.ortKurtarma.toFixed(2).padStart(14)}${String(r.enKotuKurtarma).padStart(9)}` +
    `${String(r.bittigiTur).padStart(9)}${String(r.okunanKayit).padStart(14)}`);
}
12 is, 4 adim; 3 adim asili kaliyor (S2@indirim, S5@tarife, S9@fatura), 1 adim kalici hatali (S7@indirim)

olcu                       denetci yok   denetci var
tamamlanan is                        8            11
yurutulen adim                      36            45
yeniden surulen adim                 0             6
bosa yurutulen adim                  0             3
telafi edilen is                     0             1
telafi edilen adim                   0             1
yarim kalan is                       4             0
son isin bittigi tur                 5             8

 tarama  kurtarilan is  ort kurtarma  en kotu  son tur  okunan kayit
      1             11          1.00        1        8            96
      2             11          1.33        2        8            48
      4             11          2.33        4       12            36
      8             11          5.67        7       24            36

Sayıların Okunması

Denetçisiz sütun sessiz arızanın ne demek olduğunu gösteriyor. On iki işten 8’i tamamlandı, 36 adım yürütüldü ve son iş 5. turda bitti. Görünürde her şey yolundadır: hata sayacı sıfır, telafi sıfır, kimse bir istisna görmedi. Alt satır asıl sonucu taşıyor: 4 iş yarım kaldı ve sonsuza kadar “yürüyor” durumunda duruyor. Zamanlayıcı adımı başlattı, aracı yanıt vermedi ve soruyu soracak kimse yok.

Denetçili sütunda 4 işin 3’ü kurtarıldı, biri telafi edildi ve yarım kalan iş 0’a indi. Tamamlanan iş 8’den 11’e çıktı — 12’ye değil, çünkü S7 işi kalıcı hatalıdır ve denetçi onu üç kez sürdükten sonra eşiği aşmış saydı; o işin tamamlanmış tek adımı telafi edildi. Denetçi başarısız işi başarılı yapmaz; belirsiz durumu belirli bir duruma çevirir. Bir iş ya biter ya telafi edilir; “yürüyor” durumunda kalan iş sayısı sıfırdır.

İki satır bedeli gösteriyor. Yürütülen adım 36’dan 45’e çıkıyor ve bunun 3’ü boşa yürütülen adımdır: aracı işi zaten yapmıştı, yalnız kaydı düşememişti. Denetçi bunu bilemez; asılı kalmayı kalıcı hatadan ayırmanın tek yolu yeniden denemektir. Bu yüzden zamanlayıcı–aracı– denetçi düzeninin karşılanması zorunlu bir koşulu vardır: adımlar etkisiz (idempotent) olmalıdır. Aynı adımın ikinci kez yürütülmesi ek etki üretmemelidir; adım doğal olarak bu özelliği taşımıyorsa tekillik anahtarıyla korunur. Etkisizlik ve tekillik anahtarı Web API Tasarımı ile Veri Erişim Katmanı ve İş Mantığı kurslarında kurulmuştu; burada denetçinin çalışabilmesinin ön koşuludur. Etkisiz olmayan bir fatura adımı, üç boşa yürütmede üç fazladan fatura satırı üretir.

Alttaki tablo tarama aralığının ne satın aldığını gösteriyor. Kurtarılan iş sayısı dört aralıkta da 11: denetçi er ya da geç bulur. Değişen, ne kadar geç bulduğudur. Ortalama kurtarma gecikmesi 1,00 turdan 5,67 tura, en kötüsü 1’den 7’ye çıkıyor; toplu işin son işi 8. turdan 24. tura kayıyor. Karşılığında denetçinin okuduğu kayıt 96’dan 36’ya iniyor. Aralık iki katına çıktığında gecikme kabaca iki katına çıkar ve okuma yarıya iner; sık tarama kurtarmayı hızlandırır, seyrek tarama kalıcı kaydın üzerindeki okuma yükünü azaltır. Bu ödünleşim belirlenimlidir ve modelin içinden okunur; ortama bağlı bir süre ölçümü değildir.

Hesaba Dönüş

Modelin 12 işi bir hacim değildir. Hacim K01’den gelir: günde 4000 iş akışı ve iş başına dört adım, yani günde 16.000 adım. Önceki dersin KK2 varsayımı (gün sonu iş akışının kalıcı hata oranı 0,02) sürer. Bu ders bir varsayım daha ekler.

KK3 — bir adımın asılı kalma oranı 0,005. Gerekçesi aracı süreçlerinin yeniden başlatılması, kayıt öncesinde kesilen çağrılar ve zaman aşımına uğrayıp yanıtı kaybolan adımların sürekli bir taban üretmesidir. K01’in tablosuna eklenmez; duyarlılığı 0,01 ile hesaplanır.

// denetci/hesap.mjs — modelin oranlarini K01'in gun sonu is hacmine uygular
import { kosum, ADIM } from "./akis.mjs";

const FATURA_SATIRI = 4000;                 // K01: gunluk fatura satiri (hesap) = gunluk is akisi
const ADIM_GUN = FATURA_SATIRI * ADIM.length;
const KK2 = 0.02;                           // onceki ders: gun sonu is akisinin kalici hata orani
const DENEME = 3;                           // denetcinin esigi (model parametresi)
const r = kosum({ denetci: true, tarama: 2 });
const bosaOran = r.bosaYurutulen / r.yenidenSurulen;   // yeniden surmelerin bosa giden payi

console.log(`gunluk is akisi ${FATURA_SATIRI}, adim/gun ${ADIM_GUN}, ` +
  `modelde bosa giden yeniden surme orani ${bosaOran.toFixed(3)}`);
console.log();
console.log(`${"KK3".padStart(6)}${"asili adim/gun".padStart(16)}${"yeniden surme/gun".padStart(19)}` +
  `${"bosa yurutulen/gun".padStart(20)}${"denetcisiz yarim is".padStart(21)}${"is orani".padStart(10)}`);
for (const KK3 of [0.005, 0.01]) {          // KK3: bir adimin asili kalma orani
  const asili = ADIM_GUN * KK3;
  const kalici = FATURA_SATIRI * KK2;
  const yarim = asili + kalici;
  console.log(`${KK3.toFixed(3).padStart(6)}${asili.toFixed(0).padStart(16)}` +
    `${(asili + kalici * DENEME).toFixed(0).padStart(19)}${asili.toFixed(0).padStart(20)}` +
    `${yarim.toFixed(0).padStart(21)}${`%${(100 * yarim / FATURA_SATIRI).toFixed(2)}`.padStart(10)}`);
}

console.log(`\ntarama araliginin bedeli (model olcumu, belirlenimli):`);
for (const tarama of [1, 8]) {
  const k = kosum({ denetci: true, tarama });
  console.log(`  tarama ${tarama}: ort kurtarma ${k.ortKurtarma.toFixed(2)} tur, ` +
    `en kotu ${k.enKotuKurtarma}, son tur ${k.bittigiTur}, okunan kayit ${k.okunanKayit}`);
}
gunluk is akisi 4000, adim/gun 16000, modelde bosa giden yeniden surme orani 0.500

   KK3  asili adim/gun  yeniden surme/gun  bosa yurutulen/gun  denetcisiz yarim is  is orani
 0.005              80                320                  80                  160     %4.00
 0.010             160                400                 160                  240     %6.00

tarama araliginin bedeli (model olcumu, belirlenimli):
  tarama 1: ort kurtarma 1.00 tur, en kotu 1, son tur 8, okunan kayit 96
  tarama 8: ort kurtarma 5.67 tur, en kotu 7, son tur 24, okunan kayit 36

Sayı okunduğunda karar kendini veriyor. Binde beşlik bir asılı kalma oranı günde 80 asılı adım demektir; kalıcı hatalarla birlikte denetçisiz bir düzende günde 160 iş akışı yarım kalır ve bu, günlük 4000 fatura satırının yüzde 4’üdür. Oran 0,01’e çıkarsa 240 ve yüzde 6 olur. Bu işlerin hiçbiri bir hata sayacı artırmaz; ilk belirtileri ay sonu mutabakatının tutmamasıdır.

Denetçinin ödettiği sayı aynı tabloda: günde 320 yeniden sürme ve bunların 80’i boşa yürütülen adım. Boşa yürütmelerin oranı modelde 0,500’dür, çünkü yeniden sürmelerin yarısı kalıcı hatalı işi tekrar denemekten gelir. Sekseni etkisizlikle karşılanır; karşılanmazsa günde 80 fazladan fatura satırı çıkar — yani denetçi, düzeltmeyi amaçladığı hatanın tam yarısı kadar yeni hata üretir. Zamanlayıcı–aracı–denetçi düzeni bu yüzden tek başına bir kalıp değildir; etkisiz adımlarla birlikte tek bir karardır.

Özet

  • Üç rol ayrılır çünkü hiçbir rol kendi arızasını fark edemez: zamanlayıcı işi kalıcı kayda yazıp adımı başlatır, aracı adımı yürütür, denetçi yalnız kayda bakar.
  • Denetçisiz koşumda 12 işin 4’ü hiçbir hata üretmeden “yürüyor” durumunda kaldı; hata sayacı sıfırdı ve tamamlanan iş 8’de durdu.
  • Denetçi belirsiz durumu belirliye çevirir: 3 iş kurtarıldı, 1 iş telafi edildi, yarım kalan iş 4’ten 0’a indi, tamamlanan iş 8’den 11’e çıktı.
  • Bedel yürütülen adımdadır (36 → 45) ve 3’ü boşa yürütmedir; bu yüzden adımların etkisiz olması düzenin ön koşuludur.
  • Tarama aralığı kurtarma gecikmesiyle okuma yükünü takas eder: aralık 1’den 8’e çıktığında ortalama kurtarma 1,00’den 5,67 tura, son iş 8. turdan 24. tura kayarken okunan kayıt 96’dan 36’ya iniyor.
  • K01’e dönüş: 16.000 adım/gün ve KK3 = 0,005 ile denetçisiz düzen günde 160 yarım kalan iş bırakır (fatura satırlarının yüzde 4’ü); denetçi bunu sıfırlar ve karşılığında günde 320 yeniden sürme ile 80 boşa yürütme getirir.

Sonraki Adım

Bu dersin denetçisi tek bir taraf sayıldı. Gerçekte denetçi de, gün sonu işini başlatan zamanlayıcı da birden çok düğümde koşar — çünkü tek düğümde koşan bir denetçi, düştüğünde kimsenin fark etmediği bir iş akışının kendisi olur. Ama zamanlayıcının işi tekildir: gün sonu ücretlendirmesi bir kez başlatılmalıdır. İki düğüm aynı anda başlatırsa aynı satıcı-gün için iki fatura satırı üretilir ve bu, telafiyle değil ancak elle düzeltilir. Sonraki ders bu tekil sorumluluğu bir düğüme veren düzeneği kurar: düğümlerden birinin kira süresi boyunca sorumluluğu üstlenmesi, o düğüm düştüğünde devralmanın kaç turda tamamlandığı ve kira süresinin yanlış ayarlanmasının iki düğümü aynı anda çalıştırdığı turların sayılması.

İ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