Ders 14 / 27
Durum Dosyası
Kaydın gerçeğe bağlandığı yer: durum dosyası olmadan eşlemenin okuma maliyeti ve hiç eşlenemeyen kalemler, durumun kalem başına tuttuğu bilgi, silinen kaynak ile kaydın kapsamadığı kaynağın ayrımı ve durumun kaybının bedeli.
İçindekiler
Plan ile uygulamanın sonucu arasındaki fark ölçüldü: plan bir kalemi gösteriyor, uygulama başka bir sonuç üretiyordu. O karşılaştırmanın sessiz bir dayanağı vardı. Plan, “bu kaynak zaten var” ya da “bu kaynak yok” diyebilmek için gerçeğin bir yerde yazılı olduğunu varsayıyordu. O yazılı hâl nerede duruyor?
Bu ders o dosyayı gerçekten yazar, gerçekten okur ve olmadığında ne yapılamadığını sayar.
Tanım, Uygulama ve İki Çıktı
Kurgu bir bölgesel ölçüm ağı kullanılacak: üç ortam, her ortamda sayaç okumalarını toplayan iki makine, bir kuyruk, bir veri deposu, bir nesne deposu, bir alt ağ ve ağ kuralları. Ağ da, sağlayıcı da modeldir; hiçbir gerçek altyapıya dokunulmaz.
Bir uygulama iki şey üretir: sağlayıcı tarafında kaynaklar, kendi tarafında durum dosyası. Aşağıdaki betik üçünü de diske yazar.
// uygula.mjs — kurgu "bolgesel olcum agi": tanimi kurar, uygulamayi modeller, // saglayicidaki gercegi ve durum dosyasini diske yazar. Ucu de modeldir. import { writeFileSync } from "node:fs"; const ortamlar = ["deneme", "hazirlik", "uretim"]; const tanim = []; const ekle = (tur, ad, ortam, alanlar, bagimli = []) => tanim.push({ ad, tur, ortam, alanlar, bagimli }); ortamlar.forEach((o, i) => { ekle("alt_ag", `alt_ag_${o}`, o, { blok: `10.${i}.0.0/16` }); ekle("kuyruk", `okuma_kuyrugu_${o}`, o, { saklama_saat: 24, bolum: 4 }); ekle("veri_deposu", `olcum_deposu_${o}`, o, { boyut_gb: 200, yedek: true }); ekle("nesne_deposu", `arsiv_${o}`, o, { sinif: "seyrek" }); for (const n of [1, 2]) ekle("makine", `toplayici_${o}_${n}`, o, { cekirdek: 2, bellek_gb: 8 }, [`alt_ag_${o}`, `okuma_kuyrugu_${o}`]); for (const kapi of [443, 5671, 9100]) ekle("guvenlik_kurali", null, o, { yon: "giris", kapi }, [`alt_ag_${o}`]); ekle("yol_tablosu_girdisi", null, o, { hedef: "0.0.0.0/0", gecit: "kenar" }, [`alt_ag_${o}`]); }); // Uretimde yedek toplayici icin ikinci 9100 kurali: tanimda iki kez gecen ayni kalem. ekle("guvenlik_kurali", null, "uretim", { yon: "giris", kapi: 9100 }, ["alt_ag_uretim"]); // Saglayicinin, tanimda verilmeyen alanlara kendiliginden koydugu degerler. const varsayilan = { makine: { disk_gb: 40, izleme: "acik" }, kuyruk: { sifreleme: "acik" }, veri_deposu: { bakim_penceresi: "pazar" }, nesne_deposu: { surum_tut: false }, alt_ag: { ipv6: false }, guvenlik_kurali: {}, yol_tablosu_girdisi: {}, }; const adsiz = new Set(["guvenlik_kurali", "yol_tablosu_girdisi"]); const kimlikler = tanim.map((_, i) => `kyn-${String(i + 1).padStart(4, "0")}`); const adDizini = new Map(tanim.map((k, i) => [k.ad, kimlikler[i]])); const gercek = [], durum = []; tanim.forEach((k, i) => { const alanlar = { ...varsayilan[k.tur], ...k.alanlar }; const ustAd = k.bagimli.find((b) => b.startsWith("alt_ag")) ?? null; gercek.push({ kimlik: kimlikler[i], tur: k.tur, alanlar, etiket: adsiz.has(k.tur) ? {} : { ad: k.ad, ortam: k.ortam }, ust: ustAd ? adDizini.get(ustAd) : null, }); durum.push({ kayit_adi: k.ad ?? `${k.tur}.${k.ortam}[${i}]`, tur: k.tur, ortam: k.ortam, kimlik: kimlikler[i], alanlar, bagimli: k.bagimli, saglayici_surumu: "3.4.0", }); }); writeFileSync("tanim.json", JSON.stringify(tanim, null, 1)); writeFileSync("saglayici.json", JSON.stringify(gercek, null, 1)); writeFileSync("durum.json", JSON.stringify( { bicim_surumu: 4, seri: 1, kalemler: durum }, null, 1)); const alanSayisi = durum.reduce((t, k) => t + Object.keys(k.alanlar).length, 0); const tanimAlan = tanim.reduce((t, k) => t + Object.keys(k.alanlar).length, 0); const kenar = durum.reduce((t, k) => t + k.bagimli.length, 0); console.log(`tanim: ${tanim.length} kalem, ${tanimAlan} alan degeri`); console.log(`durum.json: ${durum.length} kalem, ${alanSayisi} alan degeri, ` + `${kenar} bagimlilik kenari, ${alanSayisi - tanimAlan} alan yalniz durumda`); console.log(`kalem basina kayit: kimlik 1, alan ${(alanSayisi / durum.length).toFixed(1)}, ` + `bagimlilik ${(kenar / durum.length).toFixed(1)}, saglayici surumu 1`); console.log(`ornek kalem: ${JSON.stringify(durum[4])}`);
DM1. Sağlayıcı kimlikleri uygulama sırasına göre veriyor; modelde kimlik kyn-0001den
başlayarak sırayla atanır. DM2. Güvenlik kuralı ile yol tablosu girdisi etiket taşımayan
türlerdir; sağlayıcı bu kalemlere yalnız kimlik verir. DM3. Sağlayıcı, tanımda verilmeyen
alanlara varsayılan koyar ve bu değerler uygulamanın yanıtında geri döner.
node uygula.mjs
tanim: 31 kalem, 56 alan degeri
durum.json: 31 kalem, 80 alan degeri, 25 bagimlilik kenari, 24 alan yalniz durumda
kalem basina kayit: kimlik 1, alan 2.6, bagimlilik 0.8, saglayici surumu 1
ornek kalem: {"kayit_adi":"toplayici_deneme_1","tur":"makine","ortam":"deneme","kimlik":"kyn-0005","alanlar":{"disk_gb":40,"izleme":"acik","cekirdek":2,"bellek_gb":8},"bagimli":["alt_ag_deneme","okuma_kuyrugu_deneme"],"saglayici_surumu":"3.4.0"}
İlk sayı çifti ilginç: tanım 56 alan değeri yazıyor, durum 80 tutuyor. Aradaki 24 alan yalnızca durumda var; onları tanım yazmadı, sağlayıcı koydu. Kaydın “ne istediği” ile gerçeğin “ne olduğu” daha uygulamanın ilk saniyesinde ayrışıyor ve durum dosyası bu ayrımın tutulduğu tek yer.
Elle Müdahaleden Sonra
Uygulamanın üzerinden zaman geçti. Bir gece nöbetinde birileri sağlayıcı arayüzünden altyapıya dokundu.
// mudahale.mjs — saglayicidaki gercege elle dokunulmasini modeller: iki kalem silinir, // dort kalem kayit disi acilir, bir alan degistirilir. Durum dosyasina dokunulmaz. import { readFileSync, writeFileSync } from "node:fs"; const g = JSON.parse(readFileSync("saglayici.json", "utf8")); const kimlikle = (ad) => g.find((k) => k.etiket.ad === ad).kimlik; const altHazirlik = kimlikle("alt_ag_hazirlik"), altUretim = kimlikle("alt_ag_uretim"); const sil = (kosul) => { const i = g.findIndex(kosul); const [k] = g.splice(i, 1); return k.kimlik; }; const s1 = sil((k) => k.etiket.ad === "arsiv_deneme"); const s2 = sil((k) => k.tur === "yol_tablosu_girdisi" && k.ust === altHazirlik); g.push({ kimlik: "kyn-9001", tur: "makine", alanlar: { cekirdek: 4, bellek_gb: 16, disk_gb: 40 }, etiket: { ad: "gecici_toplayici", ortam: "uretim" }, ust: altUretim }); g.push({ kimlik: "kyn-9002", tur: "nesne_deposu", alanlar: { sinif: "sicak", surum_tut: false }, etiket: { ad: "elle_arsiv", ortam: "uretim" }, ust: null }); g.push({ kimlik: "kyn-9003", tur: "guvenlik_kurali", alanlar: { yon: "giris", kapi: 22 }, etiket: {}, ust: altUretim }); g.push({ kimlik: "kyn-9004", tur: "guvenlik_kurali", alanlar: { yon: "giris", kapi: 443 }, etiket: {}, ust: altUretim }); const m = g.find((k) => k.etiket.ad === "toplayici_uretim_1"); m.alanlar.bellek_gb = 16; writeFileSync("saglayici.json", JSON.stringify(g, null, 1)); console.log(`silinen: ${s1}, ${s2} | kayit disi acilan: 4 | elle degisen alan: 1`); console.log(`saglayicidaki kalem sayisi: ${g.length}`);
node uygula.mjs > /dev/null # duzenek bastan kurulur node mudahale.mjs
silinen: kyn-0004, kyn-0020 | kayit disi acilan: 4 | elle degisen alan: 1 saglayicidaki kalem sayisi: 33
Durum Olmadan Eşleme
Şimdi soru şu: durum dosyası hiç olmasaydı, kayıt ile gerçeği eşlemek için ne gerekirdi? Tek yol sağlayıcıyı taramak ve mantıksal adları etiketlerle eşleştirmektir.
// eslesme.mjs — durum dosyasina bakmadan, kaydi gercege eslemeye calisir. import { readFileSync } from "node:fs"; const tanim = JSON.parse(readFileSync("tanim.json", "utf8")); const gercek = JSON.parse(readFileSync("saglayici.json", "utf8")); const adsiz = new Set(["guvenlik_kurali", "yol_tablosu_girdisi"]); const turler = [...new Set(gercek.map((k) => k.tur))]; let liste = turler.length; // her tur icin bir listeleme let ayrinti = gercek.filter((k) => adsiz.has(k.tur)).length; // adsizlarin her adayi okunur // Adiyla esleme: liste yaniti etiketi tasidigi icin ek okuma gerektirmez. const adDizini = new Map(gercek.filter((k) => k.etiket.ad).map((k) => [k.etiket.ad, k])); const kullanilan = new Set(); let adla = 0, adlaYok = 0; for (const k of tanim.filter((k) => k.ad)) { const bul = adDizini.get(k.ad); if (bul) { adla++; kullanilan.add(bul.kimlik); } else adlaYok++; } // Adsizlar: yalniz icerik ve bagli olduklari alt ag ile ayirt edilebilir. const imza = (tur, ust, alanlar) => `${tur}|${ust}|${Object.keys(alanlar).sort().map((a) => `${a}=${alanlar[a]}`).join(",")}`; const gImza = new Map(); for (const k of gercek.filter((k) => adsiz.has(k.tur))) gImza.set(imza(k.tur, k.ust, k.alanlar), (gImza.get(imza(k.tur, k.ust, k.alanlar)) ?? 0) + 1); const tImza = new Map(); for (const k of tanim.filter((k) => adsiz.has(k.tur))) { const ust = adDizini.get(k.bagimli.find((b) => b.startsWith("alt_ag")))?.kimlik ?? null; const i = imza(k.tur, ust, k.alanlar); tImza.set(i, (tImza.get(i) ?? 0) + 1); } let tekil = 0, belirsiz = 0, adsizYok = 0; for (const [i, adet] of tImza) { const g = gImza.get(i) ?? 0; if (g === 0) adsizYok += adet; else if (adet === 1 && g === 1) tekil++; else belirsiz += adet; } const sahipsiz = gercek.filter((k) => !kullanilan.has(k.kimlik) && !adsiz.has(k.tur)).length; const sahipsizAdsiz = [...gImza].reduce((t, [i, g]) => t + Math.max(0, g - (tImza.get(i) ?? 0)), 0); console.log(`durumsuz esleme — okuma: ${liste} listeleme + ${ayrinti} ayrinti = ${liste + ayrinti}`); console.log(` adiyla eslesen: ${adla}/${tanim.filter((k) => k.ad).length}`); console.log(` icerikle tekil eslesen: ${tekil}/${tanim.filter((k) => adsiz.has(k.tur)).length}`); console.log(` hic eslenemeyen (adsiz, imzasi cakisiyor): ${belirsiz}`); console.log(` kayitta var gercekte yok: ${adlaYok + adsizYok} -> silinmis mi, hic olusturulmamis mi: ayirt edilemez`); console.log(` gercekte var kayitta yok: ${sahipsiz} adli + ${sahipsizAdsiz} adsiz`);
DM4. Tür başına bir listeleme bir okuma sayılır; etiket listede döner, adsız kalemin alanları ise ancak ayrıntı okumasıyla görülür.
node eslesme.mjs
durumsuz esleme — okuma: 7 listeleme + 14 ayrinti = 21 adiyla eslesen: 17/18 icerikle tekil eslesen: 9/13 hic eslenemeyen (adsiz, imzasi cakisiyor): 3 kayitta var gercekte yok: 2 -> silinmis mi, hic olusturulmamis mi: ayirt edilemez gercekte var kayitta yok: 2 adli + 2 adsiz
Yirmi bir okumadan sonra elde kalan tablo eksiktir. Otuz bir kalemin 26’sı eşleşti, 3 kalem hiç eşlenemedi. Üçü de adı olmayan kaynak: üretimdeki iki 9100 kuralı birbirinin tıpatıp aynısı, 443 kuralının ise sağlayıcıda iki karşılığı var — biri tanımdan, biri elden. İçerikleri aynı olan iki kalem arasında hangi kimliğin hangi kayıt satırına ait olduğunu söyleyecek bir bilgi yok.
İkinci kayıp daha ağırdır: eksik iki kalem için “silinmiş” ile “hiç oluşturulmamış” ayrımı yapılamıyor. Kayıt ikisini de aynı biçimde görür ve ikisine de aynı kararı verir — oluştur. Silinen kaynağın silindiği hiçbir zaman öğrenilmez, yalnız yeniden yaratılır.
Durum Dosyası Neyi Tutuyor
Örnek kalem, durumun kalem başına dört ayrı bilgi tuttuğunu gösteriyor: kimlik
(kyn-0005), alan değerleri (dördü; ikisi tanımdan, ikisi sağlayıcı varsayılanından),
bağımlılık (iki kayıt adı) ve sağlayıcı sürümü (3.4.0). Otuz bir kalem için toplam:
31 kimlik, 80 alan değeri, 25 bağımlılık kenarı, 31 sürüm damgası.
Bu dört kalemin her biri ayrı bir işi yapar. Kimlik eşlemeyi tekilleştirir. Alan değerleri sapmanın karşılaştırma tabanıdır — sağlayıcının koyduğu varsayılan da kaydedildiği için, varsayılan sağlayıcı sürümüyle değişirse fark görünür olur. Bağımlılık kenarı silme sırasını verir; kayıt dosyasındaki metinden değil, uygulanmış hâlden okunur. Sürüm damgası ise aynı tanımın farklı sağlayıcı sürümünde farklı sonuç ürettiğini saptamayı sağlar.
Durumla Karşılaştırma
// durum-oku.mjs — durum dosyasindaki kimlikleri saglayicidaki gercekle karsilastirir. import { readFileSync } from "node:fs"; const d = JSON.parse(readFileSync("durum.json", "utf8")); const gercek = new Map(JSON.parse(readFileSync("saglayici.json", "utf8")).map((k) => [k.kimlik, k])); let okuma = 1, eslesen = 0; // 1: durum dosyasinin kendisi const silinen = [], sapan = []; for (const k of d.kalemler) { okuma++; // kimlikle tek kalem okumasi const g = gercek.get(k.kimlik); if (!g) { silinen.push(`${k.kayit_adi} (${k.kimlik})`); continue; } eslesen++; for (const [alan, deger] of Object.entries(k.alanlar)) if (g.alanlar[alan] !== deger) sapan.push(`${k.kayit_adi}.${alan} ${deger} -> ${g.alanlar[alan]}`); } const durumKimlikleri = new Set(d.kalemler.map((k) => k.kimlik)); const kayitDisi = [...gercek.values()].filter((g) => !durumKimlikleri.has(g.kimlik)); console.log(`durumla karsilastirma — okuma: 1 durum dosyasi + ${okuma - 1} kimlik = ${okuma}`); console.log(` eslesen: ${eslesen}/${d.kalemler.length}, hic eslenemeyen: 0`); console.log(` silinmis (durumda kimlik var, saglayicida yok): ${silinen.length} -> ${silinen.join("; ")}`); console.log(` sapan alan: ${sapan.length} -> ${sapan.join("; ")}`); console.log(` kaydin kapsamadigi (saglayicida var, durumda yok): ${kayitDisi.length} -> ` + kayitDisi.map((g) => g.etiket.ad ?? `${g.tur}/${g.kimlik}`).join(", "));
node durum-oku.mjs
durumla karsilastirma — okuma: 1 durum dosyasi + 31 kimlik = 32 eslesen: 29/31, hic eslenemeyen: 0 silinmis (durumda kimlik var, saglayicida yok): 2 -> arsiv_deneme (kyn-0004); yol_tablosu_girdisi.hazirlik[19] (kyn-0020) sapan alan: 1 -> toplayici_uretim_1.bellek_gb 8 -> 16 kaydin kapsamadigi (saglayicida var, durumda yok): 4 -> gecici_toplayici, elle_arsiv, guvenlik_kurali/kyn-9003, guvenlik_kurali/kyn-9004
Okuma sayısı 21’den 32’ye çıktı — durum eşlemeyi ucuzlatmıyor, kesinleştiriyor. Kazanılan üç
sayı şudur: eşlenemeyen kalem 3’ten 0’a indi, iki silinmiş kalem adıyla ve kimliğiyle bulundu,
bir sapmış alan (bellek_gb 8 yerine 16) ortaya çıktı. Bu üçü durumsuz eşlemede görünmüyordu.
Dördüncü sayı kaydın kapsamını verir: sağlayıcıdaki 33 kalemin 4’ü hiçbir kaydın içinde değil. İki makine ve nesne deposu adlarıyla, iki güvenlik kuralı yalnız kimlikleriyle duruyor. Kayıt bunları ne planlar, ne siler, ne de sapma sayar; altyapının bu bölümü kaydın dışındadır ve orada olduğu ancak kimse bakmadan silinmediği sürece bilinir.
Durumun Kaybı
Durum dosyası, altyapının kendisi kadar kritik bir kayıttır. Kaybının bedeli sayılabilir.
// kayip.mjs — durum dosyasi kaybolursa neyin geri kazanilabildigini sayar. import { readFileSync, rmSync, existsSync } from "node:fs"; const d = JSON.parse(readFileSync("durum.json", "utf8")); const kayip = { kimlik: d.kalemler.length, kenar: 0, surum: 0, alan: 0 }; for (const k of d.kalemler) { kayip.kenar += k.bagimli.length; kayip.surum += k.saglayici_surumu ? 1 : 0; kayip.alan += Object.keys(k.alanlar).length; } rmSync("durum.json"); console.log(`durum.json silindi; dosya duruyor mu: ${existsSync("durum.json")}`); console.log(`kaybolan bilgi: ${kayip.kimlik} kimlik, ${kayip.alan} alan degeri, ` + `${kayip.kenar} bagimlilik kenari, ${kayip.surum} saglayici surumu`); console.log(`geriye kalan tek yol: durumsuz esleme`);
node uygula.mjs > /dev/null && node mudahale.mjs > /dev/null # duzenek bastan kurulur node kayip.mjs
durum.json silindi; dosya duruyor mu: false kaybolan bilgi: 31 kimlik, 80 alan degeri, 25 bagimlilik kenari, 31 saglayici surumu geriye kalan tek yol: durumsuz esleme
Dosya gittiğinde altyapı gitmez; yönetilebilirliği gider. Geriye kalan tek yol yukarıdaki durumsuz eşlemedir ve onun tavanı bellidir: 26 kalem tek tek içe aktarılarak geri kazanılabilir, 3 kalem hiçbir biçimde eşlenemez. İki silinmiş kalemin silinmiş olduğu bilgisi de dosyayla birlikte gitmiştir. Kayıt yeniden kurulana kadar her uygulama, var olan kaynakları yeniden oluşturmayı planlar.
DM5. Durum dosyası tek bir çalışma dizininde, düz metin olarak duruyor. Bu, kaydın kendi riskinin ikinci yüzüdür: dosya 80 alan değerini olduğu gibi tutar. Değişkenle verilen bir bağlantı dizesi ya da erişim ölçütü tanımda gizlenmiş olsa bile uygulama sonrası hâliyle burada yazılıdır ve dosyayı okuyabilen herkes onu okur. DM6. Dosyayı yazan tek şey uygulamadır; elle düzeltilmesi durumu gerçekten uzaklaştırır, çünkü sağlayıcı tarafında karşılığı yoktur.
Özet
- Uygulama iki çıktı üretti: sağlayıcıdaki 31 kaynak ve bunların kaydı olan durum dosyası; tanım 56 alan değeri yazarken durum 80 tuttu, 24 alanı sağlayıcı koydu.
- Durum olmadan eşleme 21 okuma sürdü ve 3 kalem hiç eşlenemedi; üçü de adı olmayan kaynaktı, imzaları birbirinin aynısıydı.
- Durum olmadan “silinmiş” ile “hiç oluşturulmamış” ayrılamıyor; durumla iki silinmiş kalem kimliğiyle bulundu ve bir sapmış alan ortaya çıktı.
- Durum kalem başına dört bilgi tutuyor: kimlik, alan değerleri, bağımlılık, sağlayıcı sürümü — 31 kalem için 31 kimlik, 80 alan, 25 kenar, 31 sürüm damgası.
- Sağlayıcıdaki 33 kalemin 4’ü kaydın dışında kaldı; kayıt onları ne planlıyor ne sayıyor.
- Durumun kaybında 26 kalem içe aktarmayla geri gelir, 3 kalem gelmez; kayıp altyapıyı yok etmez, yönetilemez kılar.
Sonraki Adım
Bu dersin tamamı tek bir kişinin tek bir dizinde çalıştığını varsaydı. Durum dosyası yerel diskte duruyordu ve ona aynı anda tek bir süreç dokunuyordu. Ekipte iki kişi aynı ortama aynı anda uygulama başlattığında bu varsayım düşer: ikisi de aynı durumu okur, ikisi de değiştirir, ikincisi birincinin yazdığının üstüne yazar. Sonraki ders iki eşzamanlı uygulamayı gerçekten koşturur, kilitsiz durumda kaybolan yazmayı sayar, kilitle bekleme turlarını ölçer ve kilidi bırakmadan düşen sürecin ardında bıraktığı artık kilidi ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.