İçeriğe geç
academia.sh

Ders 10 / 27

Bağımlılıklar ve Yaşam Döngüsü

Kayıttan bağımlılık grafiği çıkarılır ve topolojik sıra çözülür: kaç kenar örtük, kaç kenar yalnız açık bildirimle var, kaydın hiç görmediği gereklilik kaç yarış üretiyor. Yaşam döngüsü kuralları eklenince planın kalem sayısı, uygulamanın adım sayısı ve kesinti saniyesi ayrı ayrı sayılır.

İçindekiler

Bir önceki ders tanımı parametreleştirdi: aynı kayıt üç ortama, ayrı değişken kümeleriyle uygulanabilir hâle geldi. Kayıt artık hangi kaynağın hangi değerlerle var olacağını söylüyor. Söylemediği tek bir şey var, ve o şey uygulamanın ilk saniyesinde sorulur: bu on dört kaynak hangi sırayla yaratılacak.

Bildirimsel bir kayıtta sıra yazılmaz. Yazılmaması, sıranın olmadığı anlamına gelmez — bir alt ağ kendi ağından önce var olamaz. Sıra, kaydın içinden çıkarılır: bir kaynağın alanı başka bir kaynağın çıktısını kullanıyorsa aralarında bir kenar vardır. Bu dersin ölçtüğü şey o çıkarımın kapsamıdır: kayıt gerçek gerekliliklerin kaçını görüyor, göremediklerinde ne oluyor, ve görmesi için elle yazılan her satır neye mal oluyor.

Ölçüm yine bölgesel ölçüm ağının kaydı üzerindedir ve kurgudur; koşan şey node ile kurulmuş bir modeldir, gerçek bir sağlayıcıya hiçbir çağrı gitmez.

BS25. Bir bölgenin kaydı on dört kaynaktan oluşur: ağ, alt ağ, güvenlik kümesi, rol, izin, nesne deposu, ayar nesnesi, veri deposu, kuyruk, iki toplayıcı makine, dengeleyici, sağlık denetimi ve DNS kaydı. BS26. Yapılandırma dilinin söz dizimi bu dersin kendi küçük biçimidir: kaynak <tür> "<ad>" { alan = ifade }. Bir alanın ifadesinde <tür>.<ad>.<çıktı> geçerse örtük bağımlılık doğar; bagli = [...] listesi açık bildirimdir. BS27. Bağımlılığı çözülmüş kaynaklar aynı turda, yani paralel yaratılır; bir tur bitmeden sonraki tur başlamaz. BS28. Makine açılışta ayar dosyasını nesne deposundan çeker ve bunun için izne muhtaçtır. Bu iki gereklilik kayıtta yalnız serbest metinde geçer, hiçbir ifadeye girmez. BS29. Süreler: makine yaratma 90, yok etme 10, dengeleyiciye ekleme ve çıkarma 5 saniyedir. İki makine tam kapasitedir, bir makine yarım. BS30. Veri deposunun yeniden oluşturulması geri alınamaz: içindeki 41 günlük sayaç okuması silinir.

Kaydın Kendi Kurduğu Sıra

// graf.mjs — kayit metninden bagimlilik grafi kurar ve topolojik sira cozer. Model;
// gercek altyapi yaratilmaz. Yapilandirma dilinin soz dizimi bu dersin kendi bicimidir.

export const KAYIT = `
kaynak ag "olcum" {
  bolge = "kuzey"
  blok  = "10.20.0.0/16"
}
kaynak nesne_deposu "yapilandirma" {
  ad = "olcum-yapilandirma"
}
kaynak rol "toplayici" {
  ad = "toplayici-rolu"
}
kaynak izin "makine_okuma" {
  hedef = nesne_deposu.yapilandirma.ad
  kime  = rol.toplayici.kimlik
}
kaynak altag "toplayici" {
  ag   = ag.olcum.kimlik
  blok = "10.20.1.0/24"
}
kaynak guvenlik_kumesi "toplayici" {
  ag    = ag.olcum.kimlik
  giris = "10.20.1.0/24"
}
kaynak veri_deposu "sayac" {
  altag    = altag.toplayici.kimlik
  guvenlik = guvenlik_kumesi.toplayici.kimlik
  boyut    = "orta"
}
kaynak kuyruk "parti" {
  altag = altag.toplayici.kimlik
}
kaynak nesne "toplayici_ayari" {
  depo         = nesne_deposu.yapilandirma.ad
  veri_adresi  = veri_deposu.sayac.adres
  kuyruk_adresi = kuyruk.parti.adres
}
kaynak makine "toplayici_1" {
  altag    = altag.toplayici.kimlik
  guvenlik = guvenlik_kumesi.toplayici.kimlik
  rol      = rol.toplayici.kimlik
  acilis   = "ayar dosyasini nesne deposundan cek"
}
kaynak makine "toplayici_2" {
  altag    = altag.toplayici.kimlik
  guvenlik = guvenlik_kumesi.toplayici.kimlik
  rol      = rol.toplayici.kimlik
  acilis   = "ayar dosyasini nesne deposundan cek"
}
kaynak dengeleyici "toplayici" {
  altag    = altag.toplayici.kimlik
  hedefler = [makine.toplayici_1.kimlik, makine.toplayici_2.kimlik]
  bagli    = [guvenlik_kumesi.toplayici]
}
kaynak saglik_denetimi "toplayici" {
  hedef = dengeleyici.toplayici.kimlik
}
kaynak dns_kaydi "toplayici" {
  adres = dengeleyici.toplayici.adres
  bagli = [saglik_denetimi.toplayici]
}
`;

// BS28: kaydin hic bilmedigi dort gereklilik. Makine acilista ayar dosyasini ceker ve
// bunun icin izne muhtactir; ikisi de yalniz "acilis" metninde geciyor, ifadede degil.
export const GIZLI = [
  ["makine.toplayici_1", "nesne.toplayici_ayari"],
  ["makine.toplayici_1", "izin.makine_okuma"],
  ["makine.toplayici_2", "nesne.toplayici_ayari"],
  ["makine.toplayici_2", "izin.makine_okuma"],
];

export function ayristir(metin) {
  const dugum = [], kenar = new Map();                    // kenar: "a->b" -> {ortuk, acik}
  const blok = /kaynak\s+(\w+)\s+"(\w+)"\s*\{([\s\S]*?)\n\}/g;
  let m;
  while ((m = blok.exec(metin)) !== null) {
    const kimlik = `${m[1]}.${m[2]}`;
    dugum.push(kimlik);
    const acik = m[3].match(/bagli\s*=\s*\[([^\]]*)\]/);
    const kalan = m[3].replace(/bagli\s*=\s*\[[^\]]*\]/, "");
    const ekle = (hedef, tur) => {
      const a = `${kimlik}->${hedef}`;
      if (!kenar.has(a)) kenar.set(a, { ortuk: false, acik: false });
      kenar.get(a)[tur] = true;
    };
    for (const r of kalan.matchAll(/\b([a-z_]+)\.([a-z_0-9]+)\.[a-z_]+\b/g))
      ekle(`${r[1]}.${r[2]}`, "ortuk");                   // ifade baska kaynagin ciktisini kullaniyor
    if (acik)
      for (const r of acik[1].matchAll(/\b([a-z_]+)\.([a-z_0-9]+)\b/g))
        ekle(`${r[1]}.${r[2]}`, "acik");                  // elle yazilmis bildirim
  }
  return { dugum, kenar };
}

// Bagimliligi cozulmus kaynaklar ayni turda, yani paralel yaratilir.
export function duzeyle({ dugum, kenar }) {
  const bagim = new Map(dugum.map((d) => [d, []]));
  for (const a of kenar.keys()) {
    const [k, h] = a.split("->");
    if (bagim.has(k) && bagim.has(h)) bagim.get(k).push(h);
  }
  const duzey = new Map();
  let kalan = [...dugum], tur = 0;
  while (kalan.length) {
    const hazir = kalan.filter((d) => bagim.get(d).every((h) => duzey.has(h)));
    if (!hazir.length) return { dongu: kalan, duzey };
    hazir.forEach((d) => duzey.set(d, tur));
    kalan = kalan.filter((d) => !hazir.includes(d));
    tur++;
  }
  return { dongu: null, duzey };
}

export function olc(metin, gizli = GIZLI) {
  const g = ayristir(metin), d = duzeyle(g);
  const say = { ortuk: 0, acikYalniz: 0, ikisi: 0 };
  for (const v of g.kenar.values())
    v.ortuk && v.acik ? say.ikisi++ : v.ortuk ? say.ortuk++ : say.acikYalniz++;
  if (d.dongu) return { g, d, say, dongu: d.dongu };
  const yaris = gizli.filter(([a, b]) => d.duzey.get(a) <= d.duzey.get(b));
  const tur = Math.max(...d.duzey.values()) + 1;
  const genislik = Math.max(...[...Array(tur).keys()].map((t) =>
    [...d.duzey.values()].filter((x) => x === t).length));
  return { g, d, say, yaris, tur, genislik, dongu: null, satir: metin.trim().split("\n").length };
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const yaz = (baslik, r) => {
    console.log(`\n${baslik}`);
    console.log(`  kaynak ${r.g.dugum.length}, kenar ${r.g.kenar.size}, kayit ${r.satir} satir`);
    console.log(`  ortuk (ifadeden) ${r.say.ortuk} | yalniz acik bildirim ${r.say.acikYalniz}` +
      ` | ikisi birden ${r.say.ikisi}`);
    console.log(`  ${r.tur} tur, en genis tur ${r.genislik} kaynak`);
    for (const [a, b] of GIZLI)
      console.log(`  ${a.padEnd(18)} <- ${b.padEnd(21)} tur ${r.d.duzey.get(a)} vs ` +
        `${r.d.duzey.get(b)}  ${r.d.duzey.get(a) <= r.d.duzey.get(b) ? "YARIS" : "sira dogru"}`);
    console.log(`  yaris: ${r.yaris.length}/${GIZLI.length}`);
  };
  yaz("A) kayit oldugu gibi — sira yalniz ortuk bagimliliktan cikiyor", olc(KAYIT));

  const BILDIRIMLI = KAYIT.replace(/ {2}acilis   = "ayar dosyasini nesne deposundan cek"/g,
    '  acilis   = "ayar dosyasini nesne deposundan cek"\n' +
    "  bagli    = [nesne.toplayici_ayari, izin.makine_okuma]");
  yaz("B) iki makineye acik bildirim eklendi (2 satir)", olc(BILDIRIMLI));

  const DONGULU = BILDIRIMLI.replace(/ {2}depo         = nesne_deposu.yapilandirma.ad/,
    "  depo         = nesne_deposu.yapilandirma.ad\n  bagli        = [makine.toplayici_1]");
  const c = olc(DONGULU);
  console.log("\nC) ayar nesnesine 'bagli = [makine.toplayici_1]' eklendi");
  const karsilikli = [...c.g.kenar.keys()]                 // birbirini bekleyen cift
    .filter((a) => c.g.kenar.has(a.split("->").reverse().join("->")));
  console.log(`  cozulemeyen kalan: ${c.dongu.length} kaynak`);
  console.log(`  halka: ${karsilikli[0].split("->").join(" ve ")} birbirini bekliyor`);
}
A) kayit oldugu gibi — sira yalniz ortuk bagimliliktan cikiyor
  kaynak 14, kenar 23, kayit 59 satir
  ortuk (ifadeden) 21 | yalniz acik bildirim 2 | ikisi birden 0
  6 tur, en genis tur 4 kaynak
  makine.toplayici_1 <- nesne.toplayici_ayari tur 2 vs 3  YARIS
  makine.toplayici_1 <- izin.makine_okuma     tur 2 vs 1  sira dogru
  makine.toplayici_2 <- nesne.toplayici_ayari tur 2 vs 3  YARIS
  makine.toplayici_2 <- izin.makine_okuma     tur 2 vs 1  sira dogru
  yaris: 2/4

B) iki makineye acik bildirim eklendi (2 satir)
  kaynak 14, kenar 27, kayit 61 satir
  ortuk (ifadeden) 21 | yalniz acik bildirim 6 | ikisi birden 0
  8 tur, en genis tur 3 kaynak
  makine.toplayici_1 <- nesne.toplayici_ayari tur 4 vs 3  sira dogru
  makine.toplayici_1 <- izin.makine_okuma     tur 4 vs 1  sira dogru
  makine.toplayici_2 <- nesne.toplayici_ayari tur 4 vs 3  sira dogru
  makine.toplayici_2 <- izin.makine_okuma     tur 4 vs 1  sira dogru
  yaris: 0/4

C) ayar nesnesine 'bagli = [makine.toplayici_1]' eklendi
  cozulemeyen kalan: 6 kaynak
  halka: nesne.toplayici_ayari ve makine.toplayici_1 birbirini bekliyor

İlk satır oranı verir: kayıt yirmi üç kenarın yirmi birini hiç kimse yazmadan buldu. Alt ağın ag.olcum.kimlik yazması, ağın alt ağdan önce yaratılması gerektiğini söylemeye yeter; kimse “önce ağı kur” demedi. Sıralamanın ana kaynağı bildirimin kendisidir, ve bu bedavadır — zaten yazılması gereken ifadenin yan ürünüdür.

Yalnız açık bildirimle var olan iki kenar da anlamlıdır. Dengeleyici güvenlik kümesinin çıktısını hiçbir alanında kullanmaz, ama güvenlik kuralı yokken açılan bir dengeleyici trafiği düşürür; DNS kaydı da sağlık denetiminin çıktısını kullanmaz, ama denetim kurulmadan isim yayınlanırsa istek bozuk düğüme gider. İkisi de veri akışı olmayan ama zaman ilişkisi olan bağlardır, ve tek yolu elle yazmaktır. Örtük çıkarımın kör noktası tam olarak budur.

Kaydın Görmediği Gereklilik

Alt bölüm oranın öbür yüzünü verir. Kayıt on dört kaynağı ve yirmi üç kenarı biliyor; gerçekte yirmi yedi gereklilik var. Dördü kayda hiç girmemiş, çünkü ifadeye değil serbest metne yazılmışlar: makine açılışta ayar dosyasını çekiyor ve bunun için izne muhtaç. Kayıt gerçeğin 23/27’sini, yani yüzde 85’ini görüyor.

Görmediği dördün ikisi tesadüfen doğru sırada çözüldü. İzin, nesne deposu ve rolden türediği için birinci turda yaratılıyor; makineler ikinci turda. Kimse bunu istemedi, öyle denk geldi. Kalan ikisi denk gelmedi: ayar nesnesi veri deposunun ve kuyruğun adresini kullandığı için üçüncü tura düşüyor, makineler ise ikinci turda açılıyor. İki makine de var olmayan bir dosyayı çekmeye çalışıyor. Bu bir hata değil, bir yarıştır — kayıt iki kaynağın sırasını serbest bıraktı, sağlayıcı da onları sırasız yarattı.

Onarım iki satırdır ve B bölümü bedelini gösterir: yarış sıfırlandı, kenar sayısı 23’ten 27’ye çıktı, ama tur sayısı altıdan sekize, en geniş turdaki paralellik dörtten üçe düştü. Makineler artık ayar nesnesini beklediği için grafiğin en uzun yolu iki tur uzadı. Doğru sıranın fiyatı, uygulamanın uzaması ve paralelliğin daralmasıdır.

C bölümü açık bildirimin öbür riskini sayar. Ayar nesnesine yanlış yönde tek bir bagli satırı yazıldığında halka kapanır: ayar nesnesi makineyi, makine ayar nesnesini bekler. Çözücü hiçbir tur üretemez ve altı kaynak — halkadaki ikisi ve onlara bağlı dört kaynak — hiç yaratılamaz. Örtük bağımlılık halka kuramaz, çünkü bir ifade henüz var olmayan bir çıktıyı okuyamaz; halka ancak elle yazılan bildirimle kurulur. Açık bildirim, örtük çıkarımın göremediğini kapatır ve karşılığında yeni bir hata sınıfı açar.

Yaşam Döngüsü Kuralları Planı Nasıl Değiştiriyor

// yasam.mjs — yasam dongusu kurallari plani ve uygulamayi nasil degistiriyor. Model.
const OP = { cikar: 5, yoket: 10, yarat: 90, ekle: 5 };   // BS29: saniye

function senaryo({ onceYarat, paralel }) {
  const adim = [], hizmet = [], varlik = [];
  let imlec = 0;
  for (const m of ["toplayici_1", "toplayici_2"]) {
    let t = paralel ? 0 : imlec;
    if (onceYarat) {
      adim.push([m, "yarat (yeni ad)", t, OP.yarat]); varlik.push([t, +1]); t += OP.yarat;
      adim.push([m, "hedefe ekle", t, OP.ekle]); hizmet.push([t + OP.ekle, +1]); t += OP.ekle;
      adim.push([m, "cikar (eski)", t, OP.cikar]); hizmet.push([t + OP.cikar, -1]); t += OP.cikar;
      adim.push([m, "yok et (eski)", t, OP.yoket]); varlik.push([t + OP.yoket, -1]); t += OP.yoket;
    } else {
      adim.push([m, "hedeften cikar", t, OP.cikar]); hizmet.push([t + OP.cikar, -1]); t += OP.cikar;
      adim.push([m, "yok et", t, OP.yoket]); varlik.push([t, -1]); t += OP.yoket;
      adim.push([m, "yarat", t, OP.yarat]); varlik.push([t, +1]); t += OP.yarat;
      adim.push([m, "hedefe ekle", t, OP.ekle]); hizmet.push([t + OP.ekle, +1]); t += OP.ekle;
    }
    imlec = t;
  }
  const sure = Math.max(...adim.map(([, , t, s]) => t + s));
  let sifir = 0, yarim = 0, enCok = 2;
  for (let t = 0; t <= sure; t++) {
    const h = 2 + hizmet.filter(([a]) => a <= t).reduce((s, [, d]) => s + d, 0);
    const v = 2 + varlik.filter(([a]) => a <= t).reduce((s, [, d]) => s + d, 0);
    if (h === 0) sifir++; else if (h === 1) yarim++;
    enCok = Math.max(enCok, v);
  }
  return { adim: adim.length, sure, sifir, yarim, enCok };
}

console.log("iki toplayici makinesi yeniden olusturuluyor (makine tipi degisti)");
console.log("kural                              adim  sure  sifir kapasite  yarim  en cok makine");
for (const [ad, k] of [
  ["varsayilan, sirayla", { onceYarat: false, paralel: false }],
  ["varsayilan, paralel", { onceYarat: false, paralel: true }],
  ["once_yarat, sirayla", { onceYarat: true, paralel: false }],
  ["once_yarat, paralel", { onceYarat: true, paralel: true }],
]) {
  const r = senaryo(k);
  console.log(`${ad.padEnd(35)}${String(r.adim).padStart(4)}${String(r.sure).padStart(6)}` +
    `${String(r.sifir).padStart(16)}${String(r.yarim).padStart(7)}${String(r.enCok).padStart(15)}`);
}

// BS30: ikinci degisiklik — veri deposunun bolgesi kayitta degisti; yerinde guncellenemez.
const DEGISIKLIK = [
  ["ag.olcum", "degistir", false],
  ["veri_deposu.sayac", "yeniden olustur", true],        // uzerinde yok_etmeyi_engelle var
  ["makine.toplayici_1", "yeniden olustur", false],
  ["makine.toplayici_2", "yeniden olustur", false],
  ["dengeleyici.toplayici", "degistir", false],
  ["dns_kaydi.toplayici", "degistir", false],
];

const engelli = DEGISIKLIK.filter(([, k, e]) => e && k === "yeniden olustur");
console.log(`\nplan: ${DEGISIKLIK.length} kalem — ` +
  `${DEGISIKLIK.filter(([, k]) => k === "yeniden olustur").length} yeniden olustur, ` +
  `${DEGISIKLIK.filter(([, k]) => k === "degistir").length} degistir`);
console.log(`yok_etmeyi_engelle kurali tasiyan kalem: ${engelli.length} ` +
  `(${engelli.map(([a]) => a).join(", ")})`);
console.log(`kural yokken uygulanan: ${DEGISIKLIK.length}/${DEGISIKLIK.length} kalem, ` +
  `geri alinamayan 1 (41 gunluk sayac okumasi)`);
console.log(`kural varken uygulanan: 0/${DEGISIKLIK.length} kalem — plan tumden reddedilir; ` +
  `bekleyen zararsiz kalem ${DEGISIKLIK.length - engelli.length}`);
iki toplayici makinesi yeniden olusturuluyor (makine tipi degisti)
kural                              adim  sure  sifir kapasite  yarim  en cok makine
varsayilan, sirayla                   8   220               0    210              2
varsayilan, paralel                   8   110             105      0              2
once_yarat, sirayla                   8   220               0      0              3
once_yarat, paralel                   8   110               0      0              4

plan: 6 kalem — 3 yeniden olustur, 3 degistir
yok_etmeyi_engelle kurali tasiyan kalem: 1 (veri_deposu.sayac)
kural yokken uygulanan: 6/6 kalem, geri alinamayan 1 (41 gunluk sayac okumasi)
kural varken uygulanan: 0/6 kalem — plan tumden reddedilir; bekleyen zararsiz kalem 5

Bu tablonun asıl bulgusu, planın gösterdiği ile uygulamanın yaptığı arasındaki mesafedir. Plan iki kalem yazar: iki makine yeniden oluşturulacak. Uygulama sekiz adım koşar ve ilk satırda 210 saniye yarım kapasiteyle çalışır. Ne sekiz adım ne 210 saniye planda görünür; plan kalemin kararını söyler, kalemin bedelini söylemez.

Dört satır iki ekseni birden tarar. Paralellik süreyi yarıya indirir — 220 saniyeden 110’a — ama varsayılan kuralla birlikte hizmeti tamamen düşürür: 105 saniye sıfır kapasite, yani sayaç okumalarını kimse toplamaz. once_yarat kuralı ikisini de kurtarır: yeni makine hazır olup hedefe girmeden eski çıkarılmaz, sıfır ve yarım kapasite sütunları boş kalır. Faturası son sütundadır: aynı anda ayakta duran makine sayısı ikiden üçe, paralel koşumda dörde çıkar. Kesintisiz yeniden oluşturmanın fiyatı, geçici olarak iki kat kaynaktır — ve kaynak adı çakışmasın diye yeni makinenin adı üretilmek zorundadır.

Alt blok ikinci kuralı ölçer. Veri deposunun bölgesi kayıtta değiştiğinde plan altı kalem çıkarır; üçü yeniden oluşturma, biri veri deposunun kendisi. O kalem uygulanırsa 41 günlük sayaç okuması geri dönmemek üzere silinir. yok_etmeyi_engelle kuralı bunu durdurur, ama tanesini değil planın tamamını: altı kalemin sıfırı uygulanır, zararsız beş kalem de bekler. Kaydın koruması, kaydın akışkanlığından düşülür.

Özet

  • Kayıt 14 kaynak ve 23 kenar biliyor; 23 kenarın 21’i örtük, yani hiç kimsenin yazmadığı, ifadenin yan ürünü olan bağımlılıktır. Sıralamanın ana kaynağı bedavadır.
  • Yalnız açık bildirimle var olan 2 kenar veri akışı olmayan zaman ilişkileridir; örtük çıkarımın kör noktası budur ve tek yolu elle yazmaktır.
  • Gerçek gereklilik 27, kaydın gördüğü 23: yüzde 85. Görülmeyen 4 gerekliliğin 2’si tesadüfen doğru sıraya düştü, 2’si yarış üretti — makineler var olmayan ayar dosyasını çekti.
  • 2 satır açık bildirim yarışı sıfırladı; karşılığında tur sayısı 6’dan 8’e, en geniş turdaki paralellik 4’ten 3’e gitti. Yanlış yönde yazılan tek bir bildirim halka kurdu ve 6 kaynak hiç yaratılamadı.
  • Plan 2 kalem der, uygulama 8 adım koşar ve 210 saniye yarım kapasiteyle çalışır; ikisi de planda yazmaz. once_yarat kesintiyi sıfırlar, bedeli aynı anda 3 (paralelde 4) makinedir.
  • yok_etmeyi_engelle 41 günlük veriyi kurtarır ama planı tümden reddeder: 6 kalemin 0’ı uygulanır, zararsız 5 kalem de bekler.

Sonraki Adım

Bu ders sırayı ve yaşam döngüsünü ölçtü, ama kaydın kendisine bakıldığında bir şey göze çarpar: iki toplayıcı makine, dört satırı harfi harfine aynı olan iki ayrı blok olarak yazıldı. Bölge büyüyüp makine sayısı ona çıktığında bu blok on kez mi kopyalanacak? Sonraki ders aynı on kaynağı üç ayrı biçimde yazar — elle, sayı temelli yinelemeyle, küme temelli yinelemeyle — ve üçünü aynı kalemlerde karşılaştırır: kaç satır, ortadan bir eleman çıkarıldığında kaç kaynak yeniden oluşturuluyor, okunabilirlik ne kaybediyor.

İ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