İçeriğe geç
academia.sh

Ders 14 / 15

Kurtarma Doğrulaması

Yedeğin geri yüklenebilir olduğunun sınanması: aynı verinin üç yedek biçiminde gerçekten geri yüklenip beş denetimden geçirilmesi, doğrulama kümesi büyütüldükçe yanlış geçme ile yanlış kalmanın sayılması, yazma yolunu sınamayan doğrulamanın kaybolan kısıtı görememesi ve geri yükleme işi veriyle büyürken doğrulama kümesinin bedelinin sabit kalması.

İçindekiler

Önceki deney sistemin çalışmaya devam eden bir parçasını bozdu; deney durdurulduğunda her şey eski hâline döndü, çünkü kaybolan bir şey yoktu. Bozulan şey veri olduğunda bu varsayım çöker: bir kopya kaybolduğunda sistemi geri getiren şey deneyin durdurulması değil, yedeğin geri yüklenmesidir.

Yedek her gün alınıyor ve alındığı her gün başarılı sayılıyor. Bu bir iddiadır. Kurtarma doğrulaması, o iddianın yedeği gerçekten geri yükleyerek sınanmasıdır. Sorulacak soru kurtarmanın ne kadar sürdüğü değildir — kurtarma hedeflerinin aritmetiği ve devralma modeli Dayanıklılık ve Güvenilirlik kursunda kuruldu ve burada tekrarlanmaz. Buradaki soru şudur: bir yedeğin geri yüklenebilir olduğunu hangi denetim kümesi söyler ve o küme neyi göremez.

Yedek Bir Dökümdür, Kapsamı Bir Karardır

Kütüphane veri tabanı üç tablo ve bir dizinden oluşur; dizin ile üçüncü tablo sonradan gelen iki göçle eklenmiştir. Yedek bir SQL dökümüdür ve dökümün kapsamı hangi nesnelerin içine alındığıyla belirlenir. Üç biçim karşılaştırılacak: şemayı sqlite_master üzerinden okuyan biçim, tablo listesi elle yazıldığı için son göçten habersiz olan biçim ve tabloları alıp dizinleri atlayan biçim.

IO20 — kaynak veri 6 kitap, 10 ödünç ve 3 ceza kaydıdır; ölçek katsayısı 10 ile onda bir oranında büyütülür. IO21 — yedek aynı süreç içinde geri yüklenir. İkisi de varsayımdır; ikincisi düzeneği bir model yapar, çünkü gerçek bir kurtarmada dosya başka bir makineye taşınır ve taşımanın kendisi ayrı bir arıza kaynağıdır.

// kutuphane.mjs — katalog ve odunc semasi, veri ve uc yedek alma bicimi. Yedek bir SQL
// dokumudur; kapsami hangi nesnelerin dokume girdigiyle belirlenir.
import { DatabaseSync } from 'node:sqlite';

export const SEMA = [
  'create table kitap(no text primary key, baslik text not null)',
  'create table odunc(id integer primary key, kitap text not null, uye text not null, gun integer not null)',
  'create unique index odunc_tek on odunc(kitap, uye)',                // goc 2
  'create table ceza(uye text primary key, tutar integer not null)',   // goc 3
];

export function kur(yol, olcek = 1) {
  const db = new DatabaseSync(yol);
  for (const s of SEMA) db.exec(s);
  const kitap = Array.from({ length: 6 * olcek }, (_, i) => `K-${901 + i}`);
  const k = db.prepare('insert into kitap values (?, ?)');
  for (const no of [...kitap].reverse()) k.run(no, `kitap ${no}`);     // eklenme sirasi ters
  const o = db.prepare('insert into odunc(kitap, uye, gun) values (?, ?, ?)');
  for (let i = 0; i < 10 * olcek; i += 1)
    o.run(kitap[i % kitap.length], `uye-${Math.floor(i / 6) % 7}`, 14 + (i % 14));
  const c = db.prepare('insert into ceza values (?, ?)');
  for (let i = 0; i < 3 * olcek; i += 1) c.run(`uye-${i}`, 5 * (i + 1));
  return db;
}

// 'tam' semayi sqlite_master'dan okur; 'eksik-tablo' elle yazilmis tablo listesini kullanir;
// 'eksik-dizin' tablolari alir ama dizinleri atlar.
export function yedekAl(db, bicim) {
  const nesne = db.prepare(
    'select type, name, sql from sqlite_master where sql is not null order by rowid').all();
  const tablo = bicim === 'eksik-tablo'
    ? ['kitap', 'odunc']                      // elle yazilmis liste: goc 3'ten haberi yok
    : nesne.filter((n) => n.type === 'table').map((n) => n.name);
  const satir = [];
  for (const n of nesne) {
    if (n.type === 'index' && bicim === 'eksik-dizin') continue;
    if (n.type === 'table' && tablo.includes(n.name) === false) continue;
    satir.push(`${n.sql};`);
  }
  for (const t of tablo) {
    const sutun = db.prepare(`select * from pragma_table_info('${t}')`).all().map((s) => s.name);
    const sira = t === 'kitap' ? ' order by no' : '';    // dokum sirasi eklenme sirasi degil
    for (const r of db.prepare(`select * from ${t}${sira}`).all()) {
      const d = sutun.map((s) => (typeof r[s] === 'number' ? r[s] : `'${r[s]}'`));
      satir.push(`insert into ${t}(${sutun.join(',')}) values (${d.join(',')});`);
    }
  }
  return `${satir.join('\n')}\n`;
}

Üç Yedek, Beş Denetim

Denetimler güçlerine göre sıralanmıştır. D1 yalnız dosyanın varlığına bakar. D2 geri yüklenen kopyadaki tablo adlarını karşılaştırır. D3 her tablonun satır sayısını kaynakla karşılaştırır. D4 yazma yolunu sınar: yinelenen bir ödünç kaydı reddediliyor mu. D5 ilk kaydı sıralama vermeden okur ve kaynakla karşılaştırır.

// dogrula.mjs — uc yedek gercekten geri yuklenir ve bes denetimden gecirilir; ardindan
// dogrulama kumesi buyutulerek yanlis gecme ile yanlis kalma sayilir.
import { writeFileSync, statSync, rmSync } from 'node:fs';
import { DatabaseSync } from 'node:sqlite';
import { kur, yedekAl } from './kutuphane.mjs';

const kaynak = kur(':memory:');
const tablolar = (db) => db.prepare(
  "select name from sqlite_master where type='table' order by name").all().map((r) => r.name);
const sayim = (db, t) => db.prepare(`select count(*) c from ${t}`).get().c;
const ILK = 'select baslik from kitap limit 1';
const BEKLENEN = { tablo: tablolar(kaynak), ilk: kaynak.prepare(ILK).get().baslik };
for (const t of BEKLENEN.tablo) BEKLENEN[t] = sayim(kaynak, t);

const DENETIM = {
  D1: (_, dosya) => statSync(dosya).size > 0,
  D2: (db) => tablolar(db).join() === BEKLENEN.tablo.join(),
  D3: (db) => BEKLENEN.tablo.every((t) => tablolar(db).includes(t) && sayim(db, t) === BEKLENEN[t]),
  D4: (db) => {                                   // yazma yolu: yinelenen odunc reddedilmeli
    const r = db.prepare('select kitap, uye from odunc limit 1').get();
    try { db.prepare('insert into odunc(kitap, uye, gun) values (?, ?, 1)').run(r.kitap, r.uye); }
    catch { return true; }
    return false;
  },
  D5: (db) => db.prepare(ILK).get().baslik === BEKLENEN.ilk,
};
const BICIM = { tam: 'saglam', 'eksik-tablo': 'kusurlu', 'eksik-dizin': 'kusurlu' };
const AD = Object.keys(DENETIM);

const sonuc = {}, olcu = {};
for (const bicim of Object.keys(BICIM)) {
  const dosya = `yedek-${bicim}.sql`;
  const dokum = yedekAl(kaynak, bicim);
  writeFileSync(dosya, dokum);
  const db = new DatabaseSync(':memory:');
  db.exec(dokum);
  sonuc[bicim] = Object.fromEntries(AD.map((a) => {
    try { return [a, DENETIM[a](db, dosya)]; } catch { return [a, false]; }
  }));
  olcu[bicim] = { satir: dokum.split('\n').length - 1, bayt: statSync(dosya).size };
  rmSync(dosya);
}

const p = (x, n) => String(x).padStart(n);
console.log(`kaynak: ${BEKLENEN.tablo.length} tablo, ` +
  `${BEKLENEN.tablo.map((t) => `${t}=${BEKLENEN[t]}`).join(', ')} satir`);
console.log(`\n${'yedek bicimi'.padEnd(14)}${'gercek'.padEnd(10)}` +
  `${AD.map((a) => a.padStart(7)).join('')}${'dokum satiri'.padStart(14)}${'bayt'.padStart(7)}`);
for (const b of Object.keys(BICIM))
  console.log(`${b.padEnd(14)}${BICIM[b].padEnd(10)}` +
    `${AD.map((a) => (sonuc[b][a] ? 'gecti' : 'kaldi').padStart(7)).join('')}` +
    `${p(olcu[b].satir, 14)}${p(olcu[b].bayt, 7)}`);
console.log(`D1 dosya var  D2 tablo adlari  D3 satir sayimi  D4 yazma yolu  D5 ilk kayit (siralamasiz)`);

console.log(`\n${'dogrulama kumesi'.padEnd(20)}${'gecen yedek'.padStart(26)}` +
  `${'yanlis gecme'.padStart(14)}${'yanlis kalma'.padStart(14)}`);
for (let n = 1; n <= AD.length; n += 1) {
  const kume = AD.slice(0, n);
  const gecen = Object.keys(BICIM).filter((b) => kume.every((a) => sonuc[b][a]));
  const gecme = gecen.filter((b) => BICIM[b] === 'kusurlu').length;
  const kalma = Object.keys(BICIM).filter((b) => BICIM[b] === 'saglam' && gecen.includes(b) === false).length;
  console.log(`${kume.join('+').padEnd(20)}${(gecen.join(',') || '-').padStart(26)}` +
    `${p(gecme, 14)}${p(kalma, 14)}`);
}
kaynak: 3 tablo, ceza=3, kitap=6, odunc=10 satir

yedek bicimi  gercek         D1     D2     D3     D4     D5  dokum satiri   bayt
tam           saglam      gecti  gecti  gecti  gecti  kaldi            23   1469
eksik-tablo   kusurlu     gecti  kaldi  kaldi  gecti  kaldi            19   1258
eksik-dizin   kusurlu     gecti  gecti  gecti  kaldi  kaldi            22   1417
D1 dosya var  D2 tablo adlari  D3 satir sayimi  D4 yazma yolu  D5 ilk kayit (siralamasiz)

dogrulama kumesi                   gecen yedek  yanlis gecme  yanlis kalma
D1                  tam,eksik-tablo,eksik-dizin             2             0
D1+D2                          tam,eksik-dizin             1             0
D1+D2+D3                       tam,eksik-dizin             1             0
D1+D2+D3+D4                                tam             0             0
D1+D2+D3+D4+D5                               -             0             1

İlk satırın söylediği şey dersin çıkış noktasıdır: üç yedeğin üçü de vardır ve üçü de boş değildir. D1 hiçbir kusuru ayırt etmez; yedek işinin yeşil kalması bu denetimden fazlasını söylemez. Yedeğin varlığı geri yüklenebilirliğinin kanıtı değildir.

D2 ve D3 birinci kusuru yakalıyor. Elle yazılmış tablo listesi son göçle gelen ceza tablosunu almamıştır; geri yüklenen kopyada iki tablo vardır ve ceza kayıtları yoktur. Kusur sınıfının adı yedek kapsamı kaymasıdır: şema göçlerle büyür, yedeğin kapsamı elle yazılmış bir listede sabit kalır ve aradaki fark yalnız geri yükleyerek görünür.

İkinci kusur çok daha sessizdir. eksik-dizin biçiminde bütün tablolar ve bütün satırlar yerindedir: D2 geçer, D3 geçer. Kaybolan tek şey ödünç tablosundaki tekillik dizinidir. Geri yüklenen kopya okunduğunda kusursuzdur ve ancak yazıldığında bozulur — aynı üyeye aynı kitap ikinci kez ödünç verilebilir. D4 bunu yakalayan tek denetimdir. Okuma yolunu sınayan bir doğrulama, kaybolan bir kısıtı göremez.

D5 ters yöndeki hatayı gösteriyor. Sıralama vermeden okunan ilk kayıt, döküm satırları farklı sırayla yazıldığı için geri yüklenen kopyada başkadır — ama geri yükleme doğrudur. Kusur denetimdedir: sıralamasız bir sorgunun sonucu bir doğrulama ölçütü olamaz. Bu denetim kümeye eklendiğinde sağlam yedek de kalır ve yanlış kalma 1’e çıkar.

Seçilen eşik tablodan okunur: doğrulama kümesi D1+D2+D3+D4 ve geçme ölçütü kümenin tamamının geçmesidir. Eşiğin kaynağı bir gereksinim değil, bu taramanın kendisidir — küme D4’e kadar büyütüldüğünde yanlış geçme 2’den 0’a iner ve yanlış kalma 0’da kalır; bir denetim daha eklendiğinde kazanç yoktur, kayıp vardır. Kararın sahibi yedek işidir: küme kırıldığında yedek başarısız sayılır ve o günün yedeği kullanılabilir kabul edilmez.

Doğrulama Kümesinin Bedeli

Kurtarma planı tek bir “geri yükle” adımı yazar ve o adıma sabit bir süre biçer. Ölçüm iki büyüklüğü ayırır: geri yüklemenin işi ile doğrulamanın işi aynı biçimde büyümez.

// bedel.mjs — geri yuklemenin ve dogrulama kumesinin bedeli, iki veri boyutunda. Sorgu
// sayilari sayacla olculur; sure ortama bagli oldugu icin basilmaz, is miktari basilir.
import { DatabaseSync } from 'node:sqlite';
import { kur, yedekAl } from './kutuphane.mjs';

const sayacli = (db) => { const s = { n: 0 }; return { s, prepare: (q) => { s.n += 1; return db.prepare(q); } }; };
const tablolar = (db) => db.prepare(
  "select name from sqlite_master where type='table' order by name").all().map((r) => r.name);
const sayim = (db, t) => db.prepare(`select count(*) c from ${t}`).get().c;
const ADIM = {
  'D2 sema': (d) => tablolar(d),
  'D3 sayim': (d) => tablolar(d).map((t) => sayim(d, t)),
  'D4 yazma': (d) => {
    const r = d.prepare('select kitap, uye from odunc limit 1').get();
    try { d.prepare('insert into odunc(kitap, uye, gun) values (?, ?, 1)').run(r.kitap, r.uye); }
    catch { /* kisit yururlukte: beklenen */ }
  },
};

const p = (x, n) => String(x).padStart(n);
console.log(`${'olcek'.padEnd(8)}${'dokum kaydi'.padStart(13)}${'geri yuklenen satir'.padStart(21)}` +
  `${'D2'.padStart(5)}${'D3'.padStart(5)}${'D4'.padStart(5)}${'kume sorgusu'.padStart(14)}`);
const olculen = {};
for (const olcek of [1, 10]) {
  const dokum = yedekAl(kur(':memory:', olcek), 'tam');
  const geri = new DatabaseSync(':memory:');
  geri.exec(dokum);
  const kayit = dokum.split('\n').filter((x) => x.startsWith('insert')).length;
  const yuklenen = tablolar(geri).reduce((a, t) => a + sayim(geri, t), 0);
  const say = [];
  for (const f of Object.values(ADIM)) { const w = sayacli(geri); f(w); say.push(w.s.n); }
  const toplam = say.reduce((a, x) => a + x, 0);
  olculen[olcek] = { kayit, toplam };
  console.log(`${String(olcek).padEnd(8)}${p(kayit, 13)}${p(yuklenen, 21)}` +
    `${say.map((x) => p(x, 5)).join('')}${p(toplam, 14)}`);
}
console.log(`\nveri 10 kat buyudugunde geri yukleme isi ` +
  `${(olculen[10].kayit / olculen[1].kayit).toFixed(2)} kat, dogrulama kumesi ` +
  `${(olculen[10].toplam / olculen[1].toplam).toFixed(2)} kat`);
olcek     dokum kaydi  geri yuklenen satir   D2   D3   D4  kume sorgusu
1                  19                   19    1    4    2             7
10                190                  190    1    4    2             7

veri 10 kat buyudugunde geri yukleme isi 10.00 kat, dogrulama kumesi 1.00 kat

Sayılar koşumdan bağımsızdır: süre yerine iş miktarı ölçülmüştür. Geri yükleme 19 kayıttan 190 kayda, yani 10,00 kat büyüdü; doğrulama kümesi 7 sorguda kaldı, 1,00 kat. Kurtarmanın bedeli veriyle büyür, doğrulamanın bedeli büyümez. Bunun pratik sonucu şudur: doğrulamayı atlamak kurtarma süresini kayda değer ölçüde kısaltmaz, ama kurtarmanın başarılı sayılmasını bir iddiaya geri döndürür.

Aynı tablo plandaki eksik adımı da adlandırıyor. Plandaki doğrulama adımı satır sayımıydı — D3. Tatbikat iki adımın daha gerektiğini gösterdi: şema nesnelerinin sayılması ve yazma yolunun sınanması. Plan dört adım yazıyordu, ölçülen altı adım oldu ve eklenen iki adımın toplam bedeli üç sorgudur. Planın en pahalı eksiği süre tahmini değil, hiç yazılmamış doğrulama adımıdır.

Doğrulamanın Göremediği Sınıf

Bu küme dört şeyi göremez. Birincisi içerik bozulmasıdır: satır sayıları tutarken değerlerin kayması D3’e görünmez. İkincisi yedeğin alındığı andaki tutarlılıktır: yarım kalmış bir işlem sırasında alınmış döküm bütün denetimleri geçebilir. Üçüncüsü hedef ortamdır; burada yedek aynı süreçte geri yüklendi, oysa gerçek kurtarmada dosya taşınır ve taşımanın kendisi ayrı bir arıza kaynağıdır. Dördüncüsü yedeğin yaşıdır: bu denetimlerin hepsi dünkü bir yedekte de geçer ve aradaki veri kaybını hiçbiri saymaz.

Özet

  • Yedeğin varlığı geri yüklenebilirliğinin kanıtı değildir: üç yedeğin üçü de D1’i geçti, ikisi kusurluydu.
  • Elle yazılmış tablo listesi son göçün tablosunu almadı; kusur sınıfının adı yedek kapsamı kaymasıdır ve yalnız geri yükleyerek görünür.
  • Dizini almayan yedek bütün okuma denetimlerini geçti (D2, D3) ve yalnız yazma yolunu sınayan D4’te kaldı; kaybolan kısıt okunarak görülmez.
  • Doğrulama kümesi D4’e kadar büyütüldüğünde yanlış geçme 2’den 0’a indi; sıralamasız sorguya bakan D5 eklendiğinde sağlam yedek de kaldı ve yanlış kalma 1 oldu.
  • Seçilen eşik D1+D2+D3+D4 kümesinin tamamının geçmesidir; kaynağı bu taramadır ve küme kırıldığında o günün yedeği kullanılabilir sayılmaz.
  • Veri 10 kat büyüdüğünde geri yükleme işi 10,00 kat arttı, doğrulama kümesi 1,00 katta kaldı; plandaki dört adım altıya çıktı ve eklenen iki adımın bedeli üç sorgudur.

Sonraki Adım

Bu konudaki üç deney aynı biçimde kuruldu: sistemin bir parçası seçildi ve bilerek bozuldu. Katalog yavaşladı, bir yığın etiketlendi, bir tablo yedekten düştü. Her seferinde geri kalan her şey çalışmaya devam etti ve ölçülen şey, bozulan parçanın karşısındaki kalıbın ya da denetimin işini yapıp yapmadığıydı. Bu üç deneyin hiçbiri sistemi kendi sınırına kadar itmedi.

Sınırın nerede olduğu sorulmadı. Kütüphane ödünç sistemi bir yığını dört istekte topluyor, bir katalog çağrısını 100 ms’de kesiyor ve bir kurtarmayı 190 ifadede tamamlıyor — ama bu sayıların hangi yükte anlamını yitirdiği bilinmiyor. Yük artırıldığında sistem bir noktada yavaşlamaktan vazgeçip düşmeye başlar; o noktanın önce bulunması, sonra bir sayıyla yazılması gerekir. Sonraki ders yükü kademe kademe artırarak o noktayı arar ve kırılma noktasının hangi ölçütle, hangi eşikle ilan edildiğini sorar.

İ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