İçeriğe geç
academia.sh

Ders 13 / 15

Kaos Deneyleri

Üretim benzeri ortamda kontrollü bozmanın tasarımı: kararlı durum önermesinin kontrol koşumundan ölçülmesi, deney etiketiyle sınırlanan bulaşma kapsamının yığınlama yüzünden dört katına çıkması, pay marjının taranarak yanlış kalma ile geç algılamanın sayılması ve durdurma ölçütünün ardışık pencere sayısına göre biriken hasarın ölçülmesi.

İçindekiler

Önceki ders enjeksiyonu bir test düzeneği olarak kullandı: kapsam tek bir sınırdı, ortam yalnız o ders için ayağa kalktı ve bozulan şeyin kimseye zararı yoktu. Aynı enjeksiyon gerçek trafiğin aktığı bir ortamda yapıldığında üç karar öne geçer: sistemin normal davranışı nedir, deney kaç isteği etkileyecektir ve deney hangi sayıya bakılarak durdurulacaktır.

Kaos deneyi, üretim benzeri bir ortamda kontrollü bir bozma yapıp sistemin normal davranışından sapıp sapmadığını ölçmektir. Bu ders deneyin tasarımını ele alır; enjekte edilen arızanın karşısındaki kalıplar Dayanıklılık ve Güvenilirlik kursunda kuruldu ve burada yeniden anlatılmaz. Üç tasarım kararının her biri bir sayıyla verilir ve her birinin yanlış verilmesinin bir bedeli ölçülür.

Kararlı Durum Önermesi

Kararlı durum önermesi, sistemin arızasız davranışı hakkında ölçülebilir bir iddiadır ve deneyden önce yazılır. “Sistem sağlıklı” bir önerme değildir; “her 40 isteklik pencerede tam yanıt payı şu eşiğin üstündedir” bir önermedir. Eşiğin kaynağı burada bir gereksinim değil, bir ölçümdür: deney açılmadan aynı yük bir kez koşturulur ve pencere ortalaması taban kabul edilir.

Ölçüm düzeneği bir modeldir ve kendi yazılmıştır. Katalog servisi yerel bir node:http sürecidir; enjeksiyon yalnız deney etiketi taşıyan isteklere uygulanır. Ödünç servisi künye aramalarını dörtlü yığınlar hâlinde gönderir, çünkü bir ödünç listesi tek ekranda birden çok künye ister.

IO17 — yığın boyu 4, enjekte edilen gecikme 240 ms, ödünç servisinin zaman aşımı 100 ms. IO18 — etiketsiz isteklerin 0,03’ü 150 ms duraklar. İkisi de varsayımdır; gecikmenin zaman aşımından büyük seçilmesi enjeksiyonun etkisini deterministik kılar.

// kaos.mjs — deney kapsamina alinmis katalog servisi ve yigin gonderen odunc istemcisi.
// MODELDIR: gercek kaos araci ya da kume kurulmaz; enjeksiyon yalniz deney etiketi tasiyan
// yigina uygulanir ve hangi kullanicinin etiketlendigi tohumlu karmadan gelir.
import { createServer } from 'node:http';

export const YIGIN = 4, GECIKME = 240, ZAMAN_ASIMI = 100;    // IO17
export const GURULTU = { oran: 0.03, sure: 150 };            // IO18

export function karma(tohum, i) {
  let x = (tohum ^ Math.imul(i + 1, 0x9e3779b1)) >>> 0;
  x = Math.imul(x ^ (x >>> 16), 0x85ebca6b) >>> 0;
  x = Math.imul(x ^ (x >>> 13), 0xc2b2ae35) >>> 0;
  return ((x ^ (x >>> 16)) >>> 0) / 4294967296;
}

export function katalogKur() {
  const olcu = { yigin: 0, etiketli: 0, duraklama: 0 };
  const sunucu = createServer((istek, yanit) => {
    const a = new URL(istek.url, 'http://yerel');
    const etiket = a.searchParams.get('deney') === '1';
    const no = Number(a.searchParams.get('yigin'));
    olcu.yigin += 1;
    if (etiket) olcu.etiketli += 1;
    const duraklama = etiket === false && karma(31337, no) < GURULTU.oran;
    if (duraklama) olcu.duraklama += 1;
    const bekle = etiket ? GECIKME : (duraklama ? GURULTU.sure : 0);
    const gonder = () => {
      yanit.writeHead(200, { 'content-type': 'application/json' });
      yanit.end('{"kunye":"tam"}');
    };
    if (bekle === 0) gonder(); else setTimeout(gonder, bekle);
  });
  sunucu.listen(0, '127.0.0.1');
  return { sunucu, olcu };
}

export function yiginGondericisi(taban) {
  return async (no, etiketli) => {
    try {
      await fetch(`${taban}/kunye?yigin=${no}&deney=${etiketli ? 1 : 0}`,
        { signal: AbortSignal.timeout(ZAMAN_ASIMI) });
      return 'tam';
    } catch { return 'bozulmus'; }
  };
}

Bulaşma Kapsamının Sınırlanması

Bulaşma kapsamı, deneyin etkileyebileceği en geniş alandır ve deney başlamadan sayıyla yazılır. Buradaki plan şudur: kullanıcıların 0,05’i etiketlenir ve enjeksiyon yalnız onlara uygulanır. Plan bir niyettir; ölçülmesi gereken şey etiketin gerçekten o kadar isteği kapsayıp kapsamadığıdır.

IO19 — koşum 400 istek, pencere 40 istek, deney 200. istekten sonra açık, etiketli kullanıcı payı 0,05. Varsayımdır; deneyin koşumun ortasında açılması, önceki pencerelerin kontrol görevi görmesini sağlar.

// deney.mjs — kontrol kosumu ve deney kosumu gercek istekle yapilir; her istegin kaydi
// gunluk.json'a yazilir. Sayimlar deterministiktir, yalniz duvar suresi kosuma baglidir.
import { writeFileSync } from 'node:fs';
import { katalogKur, yiginGondericisi, karma, YIGIN } from './kaos.mjs';

const N = 400, PENCERE = 40, DENEY_BASI = 200, PAY = 0.05, ES = 4;   // IO19
const etiketliKullanici = (i) => karma(20260731, i) < PAY;

async function kosum(deneyAcik) {
  const { sunucu, olcu } = katalogKur();
  await new Promise((c) => sunucu.once('listening', c));
  const gonder = yiginGondericisi(`http://127.0.0.1:${sunucu.address().port}`);
  const kayit = [];
  let sira = 0;
  const isci = async () => {
    while (sira < N / YIGIN) {
      const y = sira; sira += 1;
      const uyeler = Array.from({ length: YIGIN }, (_, k) => y * YIGIN + k);
      const etiketli = deneyAcik && uyeler[0] >= DENEY_BASI && uyeler.some(etiketliKullanici);
      const sonuc = await gonder(y, etiketli);
      for (const u of uyeler) kayit.push({ istek: u, etiketli, sonuc });
    }
  };
  await Promise.all(Array.from({ length: ES }, isci));
  sunucu.closeAllConnections();
  await new Promise((c) => sunucu.close(c));
  return { kayit: kayit.sort((a, b) => a.istek - b.istek), olcu };
}

const kontrol = await kosum(false);
const deney = await kosum(true);
writeFileSync('gunluk.json', `${JSON.stringify({ N, PENCERE, DENEY_BASI,
  kontrol: kontrol.kayit, deney: deney.kayit }, null, 0)}\n`);

const pencereIci = deney.kayit.filter((k) => k.istek >= DENEY_BASI);
const planlanan = pencereIci.filter((k) => etiketliKullanici(k.istek)).length;
const etkilenen = pencereIci.filter((k) => k.etiketli).length;
const bozulan = pencereIci.filter((k) => k.sonuc === 'bozulmus').length;
const y = (a, b) => `${a}/${b} (%${((100 * a) / b).toFixed(2)})`;
console.log(`${N} istek, ${YIGIN}'luk yigin, deney ${DENEY_BASI}. istekten sonra acik`);
console.log(`planlanan bulasma kapsami : ${y(planlanan, pencereIci.length)} etiketli kullanici`);
console.log(`olculen bulasma kapsami   : ${y(etkilenen, pencereIci.length)} etiketli yigin uyesi`);
console.log(`kapsam carpani            : ${(etkilenen / planlanan).toFixed(2)} kat`);
console.log(`deney penceresinde bozulan: ${y(bozulan, pencereIci.length)}`);
console.log(`kontrol kosumunda bozulan : ${y(kontrol.kayit.filter((k) => k.sonuc === 'bozulmus').length, N)}`);
400 istek, 4'luk yigin, deney 200. istekten sonra acik
planlanan bulasma kapsami : 11/200 (%5.50) etiketli kullanici
olculen bulasma kapsami   : 44/200 (%22.00) etiketli yigin uyesi
kapsam carpani            : 4.00 kat
deney penceresinde bozulan: 52/200 (%26.00)
kontrol kosumunda bozulan : 16/400 (%4.00)

Plan 11 kullanıcı diyordu, ölçüm 44 istek buldu: kapsam çarpanı 4,00 kat. Kaynağı yığınlamadır. Etiket kullanıcıya konuyor, enjeksiyon ise yığına uygulanıyor; bir yığında tek bir etiketli üye bulunması yığının tamamını deneyin içine alıyor. Çarpanın üst sınırı yığın boyudur ve bu koşumda üst sınıra çarpılmıştır, çünkü on bir etiketli kullanıcının hepsi ayrı yığınlara düşmüştür. Bulaşma kapsamı etiketin taşındığı kadar dardır: etiketi taşımayan her toplama, kuyruklama ve önbellekleme adımı kapsamı sessizce genişletir.

Bu, bir deney tasarımı kusurudur ve yalnız ölçülerek görülür. Kapsamı gerçekten daraltmanın yolu etiketi yığın oluşturma kuralına da taşımaktır; bunun bedeli, etiketi taşımak zorunda kalan çağrı yeri sayısıdır ve o sayı deney başlamadan bilinmelidir.

Önerme, Yanlışlanma ve Durdurma Ölçütü

Deney koşumu bittiğinde elde bir istek günlüğü vardır. Önermenin eşiği, durdurma ölçütünün duyarlılığı ve bu ikisinin bedeli aynı günlük üzerinde taranır; deney yeniden koşturulmaz.

// tarama.mjs — gunluk.json uzerinde iki tarama: kararli durum onermesinin pay marji ve
// durdurma olcutunun ardisik pencere sayisi. Deney tekrar kosturulmaz, kaydi okunur.
import { readFileSync } from 'node:fs';

const g = JSON.parse(readFileSync('gunluk.json', 'utf8'));
const pencereler = (kayit) => {
  const p = [];
  for (let b = 0; b < g.N; b += g.PENCERE) {
    const d = kayit.slice(b, b + g.PENCERE);
    p.push({ tamPay: d.filter((k) => k.sonuc === 'tam').length / d.length,
      hasar: d.filter((k) => k.etiketli && k.sonuc === 'bozulmus').length });
  }
  return p;
};
const K = pencereler(g.kontrol), D = pencereler(g.deney);
const TABAN = K.reduce((a, x) => a + x.tamPay, 0) / K.length;   // esigin kaynagi: kontrol kosumu
const yanlisla = (p, m) => p.tamPay < TABAN - m;
const p = (x, n) => String(x).padStart(n);

console.log(`kararli durum onermesi: her pencerede tam yanit payi >= taban - m; taban kontrol ` +
  `kosumunun pencere ortalamasi = %${(100 * TABAN).toFixed(2)} (${K.length} pencere, ` +
  `pencere basina ${g.PENCERE} istek)`);
console.log(`\n${'pay marji m'.padEnd(13)}${'kontrolde yanlislanan'.padStart(22)}` +
  `${'deneyde yanlislanan'.padStart(20)}${'ilk yanlislanan pencere'.padStart(24)}`);
for (const m of [0, 0.05, 0.10, 0.20]) {
  const ilk = D.findIndex((x) => yanlisla(x, m));
  console.log(`${(`%${(100 * m).toFixed(0)}`).padEnd(13)}` +
    `${p(`${K.filter((x) => yanlisla(x, m)).length}/${K.length}`, 22)}` +
    `${p(`${D.filter((x) => yanlisla(x, m)).length}/${D.length}`, 20)}` +
    `${p(ilk < 0 ? 'yok' : ilk + 1, 24)}`);
}

const M = 0.10;                            // secilen pay marji: kontrolde yanlislanma yok
const TOPLAM = D.reduce((a, x) => a + x.hasar, 0);
const sonrasi = (w) => D.slice(w).reduce((a, x) => a + x.hasar, 0);
console.log(`\nsecilen pay marji %${100 * M}; deneyin toplam hasari ${TOPLAM} istek`);
console.log(`\n${'ardisik pencere k'.padEnd(19)}${'durdurma penceresi'.padStart(20)}` +
  `${'durdurmaya kadar hasar'.padStart(24)}${'k=1 uzerine maliyet'.padStart(21)}`);
let temel = null;
for (const k of [1, 2, 3, 4]) {
  let ardisik = 0, dur = -1;
  for (let i = 0; i < D.length; i += 1) {
    ardisik = yanlisla(D[i], M) ? ardisik + 1 : 0;
    if (ardisik >= k) { dur = i + 1; break; }
  }
  const olan = dur < 0 ? TOPLAM : TOPLAM - sonrasi(dur);
  if (temel === null) temel = olan;
  console.log(`${String(k).padEnd(19)}${p(dur < 0 ? 'durmadi' : dur, 20)}` +
    `${p(olan, 24)}${p(`+${olan - temel}`, 21)}`);
}
kararli durum onermesi: her pencerede tam yanit payi >= taban - m; taban kontrol kosumunun pencere ortalamasi = %96.00 (10 pencere, pencere basina 40 istek)

pay marji m   kontrolde yanlislanan deneyde yanlislanan ilk yanlislanan pencere
%0                             4/10                7/10                       2
%5                             4/10                7/10                       2
%10                            0/10                4/10                       6
%20                            0/10                2/10                       8

secilen pay marji %10; deneyin toplam hasari 44 istek

ardisik pencere k    durdurma penceresi  durdurmaya kadar hasar  k=1 uzerine maliyet
1                                     6                       8                   +0
2                                     9                      28                  +20
3                                    10                      44                  +36
4                               durmadi                      44                  +36

İlk tablo pay marjını tarıyor. Marj %5’in altındayken önerme kontrol koşumunda on pencerenin dördünde yanlışlanıyor — deney hiç açılmamışken. Bu bir yanlış kalmadır ve sonucu ağırdır: durdurma ölçütü bu önermeye bağlanırsa deney daha başlamadan, ikinci pencerede durdurulur. Marj %20’ye çıkarıldığında kontrol temizlenir ama deney yalnız iki pencerede yanlışlanır ve ilk yanlışlanma 8. pencereye kayar; hasarın çoğu görülmeden geçer. Seçilen eşik ikisinin arasındadır: m = %10, kaynağı kontrol koşumunun ölçülen dağılımıdır — kontrolün en düşük penceresi tabanın 6 puan altındadır, marj o gürültünün üstüne konur.

İkinci tablo durdurma ölçütünü tarıyor. Durdurma ölçütü, deneyin kendiliğinden sonlandığı kuraldır ve burada “önerme k ardışık pencerede yanlışlanırsa dur” biçimindedir. k = 1 deneyi altıncı pencerede durdurur ve o ana kadar 8 istek hasar görmüştür. k = 2 dokuzuncu pencereye kadar bekler: 28 istek, yani 20 istek fazladan. k = 3 ve k = 4 deneyin sonuna kadar hiç durdurmaz ve hasarın tamamını, 44 isteği, kabul eder. Geç durdurmanın maliyeti koşum süresiyle değil, hasar gören istek sayısıyla yazılır.

Yedinci pencere ikinci tablonun neden yanıltıcı olabileceğini gösteriyor: deney açıkken bile o pencerede önerme yanlışlanmadı, çünkü pencere ortalaması eşiğin üstünde kaldı. Deney beş pencere boyunca sürdü ve önerme dördünde yanlışlandı; yanlışlanma oranı 4/5’tir, 5/5 değil. Pencere ortalaması seyrek hasarı yutar ve k’yı büyütmek bu boşlukları birleştirmeyi zorlaştırır.

Bu testin sonucu bir yayım kararı değildir; deneyin kendisine ait bir karardır. Durdurma ölçütü kırıldığında deney durdurulur, etiket geri alınır ve yanlışlanan pencere bir kayıt olarak açılır.

Deneyin Yakalamadığı Sınıf

Deney yalnız önermenin ölçtüğü şeyi yanlışlayabilir. Önerme tam yanıt payı üzerine kurulduğu için gecikmeye dönüşen hasar görünmez: zaman aşımı 400 ms olsaydı istekler tam yanıt sayılır ve önerme sessiz kalırdı — önceki dersteki gevşek yapılandırmanın aynısı. İkinci sınıf pencere dışına taşan hasardır: deney durdurulduktan sonra kuyrukta bekleyen iş ya da yeniden denenen istek günlüğe girmez. Üçüncüsü kapsamın dışındaki bağımlılıklardır: bu deney yalnız katalog sınırını bozdu; bildirim kanalı hiç sınanmadı ve tablo yine yeşil çıkardı.

Özet

  • Kaos deneyi üç kararla tanımlanır: kararlı durum önermesi, bulaşma kapsamı ve durdurma ölçütü; üçü de deney başlamadan sayıyla yazılır.
  • Önermenin eşiği ölçümden gelir: kontrol koşumunun pencere ortalaması %96,00 taban kabul edildi ve pay marjı bu tabanın altına konan bir paydır.
  • Planlanan bulaşma kapsamı 11 kullanıcıydı, ölçülen 44 istek çıktı: kapsam çarpanı 4,00 kat ve üst sınırı yığın boyudur; kapsam yalnız etiketin taşındığı kadar dardır.
  • Pay marjı %5’in altındayken önerme kontrol koşumunda 4/10 pencerede yanlışlandı (yanlış kalma); %20’de deneyde yalnız 2/10 pencere yanlışlandı ve ilk yanlışlanma 8. pencereye kaydı.
  • Seçilen marj %10’da durdurma ölçütü k = 1 iken hasar 8 istek, k = 2 iken 28, k = 3 ve sonrasında 44’tür; geç durdurmanın maliyeti hasar gören istek sayısıyla ölçülür.
  • Deney beş pencere sürdü ve önerme dördünde yanlışlandı; pencere ortalaması seyrek hasarı yutar.

Sonraki Adım

Bu deney sistemin çalışmaya devam eden bir parçasını bozdu: katalog yavaşladı, ödünç akışı zarif bozuldu, deney durduruldu ve her şey eski hâline döndü. Geri dönüş kendiliğinden oldu, çünkü kaybolan hiçbir şey yoktu. Bozulan şey veri olduğunda bu varsayım çöker: bir kopya kaybolduğunda, bir kayıt bozulduğunda ya da bir birim tümüyle düştüğünde sistemi geri getiren şey deneyin durdurulması değil, yedeğin geri yüklenmesidir. Yedek her gün alınmakta ve alındığı her gün başarılı sayılmaktadır — ama bu bir iddiadır ve bu konuda hiçbir yerde sınanmadı. Sonraki ders o iddiayı sınar: yedeğin var olması geri yüklenebilir olduğunu göstermez, planlanan kurtarma adımları ile gerçekten gereken adımlar aynı değildir ve doğrulama kümesinin neyi denetlediği kadar neyi denetlemediği de sayılabilir.

İ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