İçeriğe geç
academia.sh

Ders 09 / 12

Yapı Çıktısı Depoları

Sürümlenmiş ikili ve paket saklama ölçülür: değişmez sürüm ile taşınabilir etiket ayrımı bir depo modelinde kurulur, taşınabilir etiketin kaç dağıtımda başka bir çıktıya işaret ettiği sayılır ve saklama kuralı geri alma penceresi, depo baytı ve ulaşılamaz dağıtım sayısı üzerinden taranır.

İçindekiler

Önceki ders tek bir yapı çıktısı üretti ve kimliğinin içerik özeti olduğunu ölçtü. Özet kimliktir ama ad değildir: onaltılık basamaklardan oluşan bir diziyi dağıtım kayıtlarına elle taşımak sürdürülebilir bir düzen değildir. Ayrıca çıktı bir kez üretilip her yere dağıtılacaksa, üretildiği an ile dağıtıldığı an arasında bir yerde durması ve geri alma gerektiğinde önceki çıktının hâlâ bulunabilmesi gerekir.

Bu ders o duran yeri ele alır: yapı çıktısı deposu (artifact repository). Depo, sürümlenmiş ikilileri ve paketleri saklayan, onlara ad veren ve istendiğinde geri veren bileşendir. Burada bir ürün olarak değil, tür olarak ele alınır; ölçülen şey de deponun sunduğu iki adlandırma biçiminin — değişmez sürüm ile taşınabilir etiketin — dağıtımda ne ürettiğidir.

DK13. Depo modeli iki işlevle sınırlıdır: değişmez sürüm kaydı ve ad çözümü. Erişim denetimi, çoğaltma ve ağ katmanı modelin dışındadır. DK14. Yayım çizelgesi kendi üretecimizle kurulur; tohum 20260311 görünürdür ve çizelge 24 sürümü ortalama dört günde bir yayımlar. DK15. Sürüm boyutları 38–52 MB aralığında üreteçten gelir; gerçek bir ölçüm değil, kurgunun büyüklük mertebesidir.

Depo Ne Saklar

Depo iki ayrı şey saklar: içerik ve ad. İçerik, önceki dersin ölçtüğü nesnedir; adı ise insanların dağıtım kaydına yazdığı sözdür. Bu iki şeyin ilişkisi ikiye ayrılır.

Değişmez sürüm, bir kez yayımlandıktan sonra başka bir içeriğe bağlanamayan addır. 1.5.0 bir kez yayımlandıysa, 1.5.0 her zaman aynı çıktıdır. Taşınabilir etiket ise bir sürümü gösteren ve zaman içinde başka bir sürüme taşınabilen addır: kararli, son, hazirlik-adayi gibi. İkisi de addır, ama biri kimliğe, diğeri bir işaretçiye karşılık gelir.

Aşağıdaki model iki işlevi ve değişmezlik kuralının denetimini içerir.

// olcum-agi/depo.mjs — surumlenmis yapi ciktisi deposu ve yayim cizelgesi (model)
import { createHash } from "node:crypto";

// Kendi uretecimiz: dogrusal esleme. Tohum cagirana aittir, cikti yinelenebilir.
export const uretec = (tohum) => () =>
  (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648;

export const ozetle = (metin) => createHash("sha256").update(metin).digest("hex").slice(0, 12);

export const depoKur = () => {
  const surumler = new Map();   // degismez: surum -> { ozet, bayt, gun }
  const etiketler = new Map();  // tasinabilir: etiket -> surum
  const ret = [];
  return {
    surumler, etiketler, ret,
    yayimla(surum, ozet, bayt, gun) {
      const eski = surumler.get(surum);
      if (eski && eski.ozet !== ozet) { ret.push(surum); return "RET"; }
      if (eski) return "AYNI";
      surumler.set(surum, { ozet, bayt, gun });
      return "YAYIM";
    },
    etiketle: (etiket, surum) => etiketler.set(etiket, surum),
    coz(ad) {
      const surum = etiketler.get(ad) ?? ad;
      const kayit = surumler.get(surum);
      return kayit ? { surum, ozet: kayit.ozet } : null;
    },
  };
};

// n surumluk yayim cizelgesi: gun araliklari ve bayt boyutlari uretecten gelir.
export const cizelge = (n, ortalamaGun, tohum) => {
  const r = uretec(tohum);
  const liste = [];
  let gun = 0;
  for (let i = 1; i <= n; i++) {
    gun += 1 + Math.round(r() * (2 * ortalamaGun - 2));
    liste.push({
      surum: `1.${i}.0`, ozet: ozetle(`1.${i}.0-icerik`),
      bayt: 38_000_000 + Math.round(r() * 14_000_000), gun,
      temizKostu: r() > 0.45,
    });
  }
  return liste;
};

yayimla işlevinin üç yanıtı vardır ve bu üçü depo davranışının tamamıdır: yeni sürüm kaydedilir, aynı içerikle yinelenen yayım yok sayılır, aynı ad başka içerikle geldiğinde reddedilir. Üçüncüsü değişmezlik ilkesinin depodaki karşılığıdır — reddedilmezse, önceki dersin ölçtüğü bütün özet karşılaştırmaları anlamını yitirir, çünkü 1.5.0 adının hangi nesneyi gösterdiği zamana bağlı hâle gelir.

Değişmez Sürüm ve Taşınabilir Etiket

Ölçüm 24 sürümü çizelgeye göre yayımlar ve arada gelen dağıtım isteklerini karşılar.

DK16. son etiketi her yayımda taşınır; kararli etiketi yalnız gecelik toplu işi temiz koşan sürümlere taşınır. DK17. Dağıtım kaydı yalnız istenen adı saklar, çözülen sürümü saklamaz — ölçülen kusurun kaynağı budur.

// olcum-agi/etiket.mjs — tasinabilir etiketin kac dagitimda baska cikti tasidigini sayar
import { depoKur, cizelge } from "./depo.mjs";

const SURUMLER = cizelge(24, 4, 20260311);
const depo = depoKur();

// Dagitim istekleri: gun ve istenen ad. Ad ya tasinabilir etiket ya degismez surumdur.
const ISTEK = [
  [6, "kararli"], [11, "kararli"], [17, "son"], [23, "kararli"], [29, "kararli"],
  [34, "1.5.0"], [41, "kararli"], [48, "son"], [55, "kararli"], [66, "1.13.0"],
  [70, "kararli"], [79, "kararli"], [86, "son"], [92, "1.21.0"],
];

const kayitlar = [];
let sirada = 0;
for (const [gun, ad] of ISTEK) {
  while (sirada < SURUMLER.length && SURUMLER[sirada].gun <= gun) {
    const s = SURUMLER[sirada++];
    depo.yayimla(s.surum, s.ozet, s.bayt, s.gun);
    depo.etiketle("son", s.surum);
    if (s.temizKostu) depo.etiketle("kararli", s.surum);
  }
  kayitlar.push({ gun, ad, ...depo.coz(ad) });
}

console.log("gun".padEnd(5) + "istenen ad".padEnd(12) + "cozulen surum".padEnd(15) + "ozet");
for (const k of kayitlar) {
  console.log(String(k.gun).padEnd(5) + k.ad.padEnd(12) + k.surum.padEnd(15) + k.ozet);
}

const tasinabilir = kayitlar.filter((k) => k.ad === "kararli" || k.ad === "son");
const sonGorulen = new Map();
let degisim = 0;
for (const k of tasinabilir) {
  if (sonGorulen.has(k.ad) && sonGorulen.get(k.ad) !== k.ozet) degisim += 1;
  sonGorulen.set(k.ad, k.ozet);
}
const ayri = (ad) => new Set(kayitlar.filter((k) => k.ad === ad).map((k) => k.ozet)).size;

console.log(`\ntoplam dagitim ${kayitlar.length}: tasinabilir etiketle ${tasinabilir.length}, ` +
  `degismez surumle ${kayitlar.length - tasinabilir.length}`);
console.log(`"kararli" adiyla ${kayitlar.filter((k) => k.ad === "kararli").length} dagitim yapildi, ` +
  `${ayri("kararli")} ayri ciktiya isaret etti`);
console.log(`"son" adiyla ${kayitlar.filter((k) => k.ad === "son").length} dagitim yapildi, ` +
  `${ayri("son")} ayri ciktiya isaret etti`);
console.log(`ayni ad, onceki dagitimdan baska cikti: ${degisim} dagitim`);

// Degismezlik denetimi: yayimlanmis bir surum baska ozetle yeniden yayimlanmak istenirse.
console.log("\nayni surum, baska ozet: " + depo.yayimla("1.5.0", "ffffffffffff", 4e7, 99));
console.log("ayni surum, ayni ozet : " + depo.yayimla("1.5.0", depo.coz("1.5.0").ozet, 4e7, 99));
console.log("reddedilen yeniden yayim: " + depo.ret.length);
gun  istenen ad  cozulen surum  ozet
6    kararli     1.1.0          69a51066dd19
11   kararli     1.2.0          4a318efc7537
17   son         1.4.0          6c9ea8b0936a
23   kararli     1.2.0          4a318efc7537
29   kararli     1.6.0          e82d2dacf6b1
34   1.5.0       1.5.0          1d7edd1472a2
41   kararli     1.7.0          837826307ef8
48   son         1.10.0         4d590510b154
55   kararli     1.10.0         4d590510b154
66   1.13.0      1.13.0         e4438d40a263
70   kararli     1.16.0         6ed1bdfd5c1e
79   kararli     1.16.0         6ed1bdfd5c1e
86   son         1.19.0         39b3f9d7c5d7
92   1.21.0      1.21.0         90749e3c6c1c

toplam dagitim 14: tasinabilir etiketle 11, degismez surumle 3
"kararli" adiyla 8 dagitim yapildi, 6 ayri ciktiya isaret etti
"son" adiyla 3 dagitim yapildi, 3 ayri ciktiya isaret etti
ayni ad, onceki dagitimdan baska cikti: 7 dagitim

ayni surum, baska ozet: RET
ayni surum, ayni ozet : AYNI
reddedilen yeniden yayim: 1

Sayılar tek bir cümleye toplanır: on dört dağıtımın on biri bir ada yapıldı ve o ad yedisinde bir öncekinden başka bir çıktı taşıdı. kararli adıyla yapılan sekiz dağıtım altı ayrı nesneyi üretime taşımıştır; kayıtta yazan söz ise sekizinde de aynıdır.

Tabloda iki durum ayrıca okunmalıdır. Gün 11 ile gün 23 arasında kararli etiketi yerinde kalmıştır: arada yayımlanan sürümler gecelik toplu işi temiz koşmadığı için etiket taşınmamış, iki dağıtım da 1.2.0 çıktısını almıştır. Yani taşınabilir etiket her zaman taşınmaz; taşınıp taşınmadığı dağıtımı isteyen taraftan görünmez. Gün 55 ile gün 79 arasında ise tersi olmuştur: aynı ad, 1.10.0 yerine 1.16.0 çıktısını taşımıştır.

Bu, geri almanın da ölçülebilir tuzağıdır. Gün 79’da bir sorun görülüp “kararlı sürüme dönelim” denirse, çözülen nesne gün 55’te dağıtılan 1.10.0 değil, güncel 1.16.0 olur — geri alma isteği ileri bir dağıtım üretir. Geri almanın çalışması için istenen adın değişmez sürüm olması gerekir; tabloda üç dağıtım bunu yapmıştır.

Farkın saklandığı yer burada ad çözümüdür. Çözüm dağıtım anında yapılır ve kayda geçmez; dağıtım kaydında yalnız kararli yazar. Sonradan bakan biri, hangi dağıtımın hangi nesneyi taşıdığını on bir kayıtta okuyamaz. Fark bildirilmemiş değildir yalnızca — kaydedilmemiştir. Kapatılması da tek satırlıktır: dağıtım kaydına istenen adın yanına çözülen sürüm ve özet de yazılır; o zaman on bir belirsiz kaydın on biri okunabilir hâle gelir.

Çıktının son üç satırı değişmezlik denetimini gösterir. Yayımlanmış 1.5.0 başka bir özetle yeniden yayımlanmak istendiğinde depo RET döndürür; aynı özetle geldiğinde AYNI der ve yeniden yazma yapmaz. Bu ikinci davranış, yapı adımının iki kez koşması durumunda hatayla sonuçlanmamasını sağlar — yeniden üretilebilir bir yapı için doğal karşılıktır.

Girdi Tarafı ve Üstveri

Depo yalnız yapı adımının çıktısını saklamaz; aynı türden bir depo, yapı adımının girdisi olan paketleri de saklar. Ölçülen ad çözümü sorunu bu tarafta da aynen geçerlidir. Bir yapı adımı girdi paketini değişmez sürümle değil taşınabilir bir adla — “uyumlu en yeni” türünden bir ifadeyle — istiyorsa, önceki dersin dört bozucu kaynağı kapatılmış olsa bile iki koşum farklı çıktı üretebilir; çünkü aradan geçen sürede ad başka bir nesneye taşınmıştır. Bu, yeniden üretilebilirliği bozan beşinci kaynaktır ve öncekilerin hepsinden farklı olarak yapı adımının kendi içinde değil, dışarıda durur. Ölçülen orandaki gibi: adla çözülen on bir istekten yedisinde nesne değişmişti; aynı belirsizlik girdi tarafında yapı adımına taşınır. Kapatılması, girdi sürümlerinin ad değil sürüm olarak sabitlenmesiyle olur.

İkinci konu üstveridir. Önceki dersin bıraktığı boşluk, özetin çıktının hangi kaynaktan üretildiğini söylememesiydi. Depo bu boşluğu kapatabilecek yerdir: her sürüm kaydına üç alan eklenir — çıktının üretildiği kaynak sürümü, yapı koşumunun kimliği ve o koşumda geçen sınamaların sonucu. Bu üç alan yazıldığında “üretimde koşan nesne hangi kaynaktan geldi” sorusu depodan okunur; yazılmadığında yanıt yalnız insanların hatırasındadır. Üç alanın da çıktının içine değil, yanına yazıldığına dikkat edilmelidir: içine yazmak, önceki dersin ölçtüğü gibi her koşumda özeti değiştirir ve yeniden üretilebilirliği bozar.

Saklama Kuralı Neyi Kapatır

Depo sınırsız değildir. Saklama kuralı hangi sürümlerin ne kadar tutulacağını söyler. DK18. Kural sayıya bağlıdır: “son K sürüm tutulur”. Tarama, K değiştikçe üç niceliği birden ölçer — deponun baytı, geri alma penceresi ve önceki ölçümdeki dağıtımların kaçının hedefinin silinmiş olacağı.

// olcum-agi/saklama.mjs — saklama kuralini tarar: pencere, bayt ve ulasilamaz dagitim
import { cizelge } from "./depo.mjs";

const S = cizelge(24, 4, 20260311);
const son = S[S.length - 1];
// Onceki olcumde 14 dagitimin cozuldugu surumler:
const DAGITILAN = ["1.1.0", "1.2.0", "1.4.0", "1.2.0", "1.6.0", "1.5.0", "1.7.0",
  "1.10.0", "1.10.0", "1.13.0", "1.16.0", "1.16.0", "1.19.0", "1.21.0"];

const mb = (b) => (b / 1e6).toFixed(0) + " MB";
console.log("kural".padEnd(14) + "depo".padEnd(10) + "geri alma penceresi".padEnd(21) + "ulasilamaz dagitim");
for (const k of [3, 5, 8, 12, 18, 24]) {
  const tutulan = S.slice(-k);
  const bayt = tutulan.reduce((t, s) => t + s.bayt, 0);
  const adlar = new Set(tutulan.map((s) => s.surum));
  const ulasilamaz = DAGITILAN.filter((v) => !adlar.has(v)).length;
  console.log(`son ${k} surum`.padEnd(14) + mb(bayt).padEnd(10) +
    `${son.gun - tutulan[0].gun} gun`.padEnd(21) + `${ulasilamaz}/${DAGITILAN.length}`);
}

console.log("");
for (const aralik of [4, 2]) {
  const c = cizelge(24, aralik, 20260311);
  console.log(`yayim araligi ~${aralik} gun: 24 surum ${c[23].gun} gune yayiliyor, ` +
    `"son 8 surum" penceresi ${c[23].gun - c[16].gun} gun`);
}

const toplam = S.reduce((t, s) => t + s.bayt, 0);
console.log(`\nkural yoksa: ${S.length} surum ${mb(toplam)}; bu hizda yillik buyume ` +
  mb((toplam / son.gun) * 365));
kural         depo      geri alma penceresi  ulasilamaz dagitim
son 3 surum   129 MB    4 gun                14/14
son 5 surum   214 MB    11 gun               13/14
son 8 surum   352 MB    23 gun               12/14
son 12 surum  535 MB    37 gun               9/14
son 18 surum  805 MB    69 gun               6/14
son 24 surum  1073 MB   97 gun               0/14

yayim araligi ~4 gun: 24 surum 99 gune yayiliyor, "son 8 surum" penceresi 23 gun
yayim araligi ~2 gun: 24 surum 47 gune yayiliyor, "son 8 surum" penceresi 11 gun

kural yoksa: 24 surum 1073 MB; bu hizda yillik buyume 3955 MB

Tarama saklama kuralını bir tercih tablosuna çevirir. “Son sekiz sürüm” kuralı depoyu 352 MB’de tutar ama geri alma penceresini 23 güne indirir: 23 günden eski bir sürüme dönmek istendiğinde nesne artık yoktur. Aynı kural altında, ölçülen on dört dağıtımın on ikisinin taşıdığı çıktı silinmiştir; yani “hangi sürümü koşuyorduk” sorusunun yanıtı bilinse bile o sürüm geri getirilemez. Pencereyi 69 güne çıkarmak deponun 805 MB’ye, yani iki katından fazlasına büyümesini gerektirir.

Sayıya bağlı kuralın gizlediği bir bağımlılık vardır: pencere, yayım sıklığına bağlıdır. Aynı “son sekiz sürüm” kuralı, dört günde bir yayımlayan bir ekipte 23 günlük, iki günde bir yayımlayan bir ekipte 11 günlük pencere verir. Kural metni değişmemiştir, kuralın anlamı değişmiştir. Dağıtım sıklığını artırmak — teslimat ölçütlerinden birini iyileştirmek — hiçbir şey yapılmadan geri alma penceresini yarıya indirir; bu, bir ölçütü iyileştiren kararın başkasında ölçülen değişimine örnektir. Pencerenin sıklıktan bağımsız olması isteniyorsa kural süreye bağlanır (“doksan günden eski sürümler silinir”) ya da iki koşul birlikte yazılır.

Kural konmadığında da bir sayı vardır: bu hızda depo yılda yaklaşık 3955 MB büyür. Sınırsız saklama bir karar değil, ertelenmiş bir karardır; ertelendiğinde de silmeye çoğunlukla yer kalmadığı gün, yani geri alma penceresinin en çok gerektiği anda başlanır.

Özet

  • Yapı çıktısı deposu iki şey saklar: içerik ve ad. Ad iki türlüdür — bir kez bağlandıktan sonra değişmeyen sürüm ve zaman içinde başka sürüme taşınabilen etiket.
  • Değişmezlik denetimi depoda uygulanır: aynı sürüm başka içerikle yeniden yayımlanmak istendiğinde reddedilir, aynı içerikle geldiğinde yok sayılır.
  • Ölçümde 14 dağıtımın 11’i taşınabilir bir ada yapılmış, ad bunların 7’sinde bir öncekinden başka bir çıktı taşımıştır; kararli adıyla yapılan 8 dağıtım 6 ayrı nesne dağıtmıştır.
  • Fark, dağıtım anındaki ad çözümünde saklıdır ve kaydedilmediği için sonradan okunamaz. Çözülen sürümün ve özetin kayda yazılması 11 belirsiz kaydı okunabilir kılar; geri almanın çalışması için istenen adın değişmez sürüm olması gerekir.
  • Saklama kuralı üç niceliği birden belirler: “son 8 sürüm” 352 MB depo, 23 gün geri alma penceresi ve 14 dağıtımdan 12’sinin hedefinin silinmesi demektir. Sayıya bağlı kuralın penceresi yayım sıklığı arttıkça kısalır; sıklık iki katına çıkınca pencere 23 günden 11 güne inmiştir.

Sonraki Adım

Depoda duran çıktı artık tek ve adlandırılmıştır, ama önceki dersin sonunda bir borç kalmıştı: ortama göre değişen on iki değer çıktının içinden çıkarıldı ve “ortamın künyesine” konuldu. Bu değerlerin hepsi aynı türden değildir. Fatura kapısının adresi ile o kapıya bağlanmak için kullanılan anahtar aynı yerden okunur, ama biri kütüğe yazılabilir, diğeri yazılamaz; biri dağıtım kaydında görünebilir, diğeri görünemez. Sonraki ders bu sınırı çizer ve ölçer: ortamdan gelen değerlerin kaçı yapılandırma, kaçı sır sayılır, sır kaç ayrı yoldan dışarı sızabilir ve sızıntının kapatıldığı bir denetimle nasıl gösterilir?

İ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