İçeriğe geç
academia.sh

Ders 11 / 22

Ekleme Günlüğü Kalıcılığı

Durumun değil yazmanın kaydedildiği kalıcılık biçimi: komut günlüğünün yazma başına kaç bayt tuttuğu, günlüğün anlık görüntüye oranla nasıl büyüdüğü, yeniden yazımın kazandırdığı yer ve her komutta, saniyede bir ya da işletim sistemine bırakarak eşzamanlamanın kayıp penceresiyle yazma maliyetine etkisi.

İçindekiler

Anlık görüntü, deponun bir andaki hâlini yazar. İki an arasında olan biten hiçbir yere kaydedilmez, bu yüzden kayıp penceresi aralık ne kadar kısaltılırsa kısaltılsın sıfıra inmez. Kütüphanenin ödünç sayaçları için bu, en kötü durumda binlerce ödünç kaydının elle geri toplanması demektir.

Bu ders ikinci biçimi ele alır: durumu değil yazmayı kaydetmek. Her değiştirici komut, uygulandığı sırayla bir dosyanın sonuna eklenir. Kurtarma, boş bir depo üzerinde bu komutları yeniden oynatır. Dosyanın sonuna eklemekten başka bir işlem yapılmadığı için yazma yolu ucuzdur; bedeli iki yerde çıkar: dosya durmadan büyür ve “eklendi” ile “diskte” arasındaki fark bir ayara dönüşür.

Yazmanın Kendisini Kaydetmek

Kayıt biçimi deponun komutunu olduğu gibi taşır: komut harfi, anahtar, değer. Yük önceki dersle aynıdır — yirmi bin üyeye dağılan altmış bin ödünç yazması, aynı tohumla — böylece iki biçimin sayıları doğrudan karşılaştırılabilir.

// gunluk.mjs — komut günlüğü kaydı ve aynı ödünç yükü
export const kayit = (a, d) => `Y\t${a}\t${d}\n`;   // Y: yazma komutu

export function uretec(tohum) {
  let a = tohum >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

export function yukUret(sayi, tohum, uyeSayisi = 20000) {
  const r = uretec(tohum), liste = [];
  for (let i = 0; i < sayi; i++) {
    const u = Math.floor(r() * uyeSayisi);
    liste.push([
      'odunc:u' + String(u).padStart(5, '0'),
      `sube=${1 + (u % 8)};gun=${[7, 14, 28][i % 3]};yenileme=${i % 4}`,
    ]);
  }
  return liste;
}

Günlüğün Büyümesi ve Yeniden Yazım

Aşağıdaki koşum yükü gerçekten diske yazar ve yirmi binde bir durup iki büyüklüğü yan yana koyar: o ana kadarki günlük dosyası ve aynı durumun anlık görüntüsü.

// buyume.mjs — günlüğün büyümesi ve yeniden yazımın kazandırdığı yer
import { appendFileSync, writeFileSync, statSync, rmSync } from 'node:fs';
import { kayit, yukUret } from './gunluk.mjs';

const YUK = yukUret(60000, 20260731);
rmSync('odunc.log', { force: true });
const durum = new Map();
let tampon = '', gunlukBayt = 0;
console.log('yazma   günlük bayt  anahtar  görüntü bayt  günlük/görüntü');
for (let i = 0; i < YUK.length; i++) {
  const [a, d] = YUK[i];
  tampon += kayit(a, d);                       // her yazma günlüğün sonuna eklenir
  durum.set(a, d);
  if ((i + 1) % 20000 === 0) {
    appendFileSync('odunc.log', tampon);
    gunlukBayt += Buffer.byteLength(tampon);
    tampon = '';
    let g = 0;
    for (const [k, v] of durum) g += Buffer.byteLength(k) + Buffer.byteLength(v) + 2;
    console.log(String(i + 1).padStart(5), String(gunlukBayt).padStart(12),
      String(durum.size).padStart(8), String(g).padStart(13),
      (gunlukBayt / g).toFixed(2).padStart(15));
  }
}
console.log('dosya:', statSync('odunc.log').size, 'bayt,', YUK.length, 'kayıt,',
  (statSync('odunc.log').size / YUK.length).toFixed(1), 'bayt/yazma');

let yeni = '';                                  // yeniden yazım: yalnız yaşayan anahtarlar
for (const [a, d] of durum) yeni += kayit(a, d);
writeFileSync('odunc-yeni.log', yeni);
const eski = statSync('odunc.log').size, sonra = statSync('odunc-yeni.log').size;
console.log('yeniden yazım:', durum.size, 'kayıt,', sonra, 'bayt |',
  'kazanç', eski - sonra, 'bayt (%' + (((eski - sonra) / eski) * 100).toFixed(1) + ')');
yazma   günlük bayt  anahtar  görüntü bayt  günlük/görüntü
20000       793333    12717        479015            1.66
40000      1586666    17330        652842            2.43
60000      2380000    19040        717260            3.32
dosya: 2380000 bayt, 60000 kayıt, 39.7 bayt/yazma
yeniden yazım: 19040 kayıt, 755340 bayt | kazanç 1624660 bayt (%68.3)

Yazma başına maliyet sabittir: 39,7 bayt. Günlük dosyası yazma sayısıyla doğrusal büyür ve neyi yazdığına bakmaz; aynı üyenin ödünç kaydının onuncu kez güncellenmesi de dosyaya tam bir kayıt ekler. Anlık görüntü ise anahtar sayısıyla büyür ve üzerine yazılan değerlerin geçmişini taşımaz. Son sütun aradaki makası verir: yirmi bin yazmada günlük görüntünün 1,66 katıdır, altmış binde 3,32 katı. Kütüphane çalışmayı sürdürdükçe oran büyümeye devam eder, çünkü paydaki sayı durmaz, paydadaki sayı yirmi bin üyede doyar.

Bu büyüme, ekleme günlüğünün doğrudan sonucudur ve tek çaresi vardır: yeniden yazım. Deponun o anki durumu taranır ve yaşayan her anahtar için tek bir kayıt üretilir; eski dosya bununla değiştirilir. Ölçümde 60.000 kayıt 19.040 kayda iner, dosya 2.380.000 bayttan 755.340 bayta düşer, kazanç yüzde 68,3’tür.

İki ayrıntı burada okunur. Birincisi, yeniden yazılmış günlük (755.340 bayt) aynı durumun anlık görüntüsünden (717.260 bayt) tam 38.080 bayt büyüktür; bu, 19.040 kaydın her birine düşen iki baytlık komut önekidir. Yeniden yazımdan sonra günlük, biçim farkı dışında bir anlık görüntüdür. İkincisi ve daha önemlisi: yeniden yazım da bütün girişleri taramak zorundadır. Yani önceki dersteki görüntü alma maliyeti — duraklatmak ya da kopyalamak — bu yolda da vardır, yalnız daha seyrek ortaya çıkar. Ekleme günlüğü görüntü almayı ortadan kaldırmaz; ne zaman yapılacağını dosyanın boyutuna bağlar.

Eşzamanlama Ayarı

Bir kaydın “yazıldı” sayılması üç ayrı yerde gerçekleşebilir. Kayıt önce uygulamanın kendi tamponuna girer. Tampon boşaltıldığında işletim sisteminin tamponuna geçer; süreç bu noktadan sonra çökse bile kayıt yaşar, çünkü onu artık işletim sistemi tutmaktadır. Kaydın kalıcı ortama geçtiğinin garantilenmesi ise ayrı bir istektir; makinenin elektriği kesildiğinde yalnız o adımı geçmiş kayıtlar durur.

Ayar bu üç durak arasında seçim yapar. Aşağıdaki koşum aynı bin beş yüz kaydı üç kiple yazar ve koşumdan bağımsız nicelikleri basar: yazma çağrısı sayısı, eşzamanlama sayısı, dosya boyutu. Süre de ölçülür, ama tek başına bir sayı olarak değil sıralama olarak basılır; süre diskin türüne ve makinenin yüküne bağlıdır.

KB3. Kütüphanenin en yoğun saatinde depoya saniyede 3.000 yazma gelir. KB4. Uygulama tamponu 65.536 bayttır ve işletim sistemi kendi tamponunu beş saniyede bir kalıcı ortama geçirir.

// eszamanlama.mjs — üç eşzamanlama ayarının maliyeti ve kayıp penceresi
import { openSync, writeSync, fsyncSync, closeSync, statSync, rmSync } from 'node:fs';
import { kayit, yukUret } from './gunluk.mjs';

const N = 1500, HIZ = 3000, TAMPON = 65536, GERIYAZMA = 5;   // KB3, KB4
const YUK = yukUret(N, 20260731);

function kos(ad, donem, tamponla) {
  rmSync(ad, { force: true });
  const fd = openSync(ad, 'w');
  let cagri = 0, es = 0, t = '', tb = 0;
  const bas = process.hrtime.bigint();
  for (let i = 0; i < N; i++) {
    const s = kayit(YUK[i][0], YUK[i][1]);
    if (tamponla) {                              // uygulama tamponu dolunca yazılır, eşzamanlama yok
      t += s; tb += Buffer.byteLength(s);
      if (tb >= TAMPON) { writeSync(fd, t); cagri++; t = ''; tb = 0; }
    } else {
      writeSync(fd, s); cagri++;
      if ((i + 1) % donem === 0) { fsyncSync(fd); es++; }
    }
  }
  if (t) { writeSync(fd, t); cagri++; }
  if (!tamponla && N % donem !== 0) { fsyncSync(fd); es++; }
  const sure = Number(process.hrtime.bigint() - bas);
  closeSync(fd);
  return { cagri, es, bayt: statSync(ad).size, sure };
}

const kipler = [
  ['her yazmada', kos('a.log', 1, false)],
  ['saniyede bir', kos('b.log', HIZ, false)],
  ['işletim sistemine bırak', kos('c.log', 0, true)],
];
console.log(`ölçüm: ${N} kayıt, aynı içerik, üç kip`);
console.log('kip                      yazma çağrısı  eşzamanlama  dosya baytı');
for (const [ad, s] of kipler)
  console.log(ad.padEnd(24), String(s.cagri).padStart(13), String(s.es).padStart(12),
    String(s.bayt).padStart(12));
console.log('süre sıralaması (yavaştan hızlıya):',
  [...kipler].sort((x, y) => y[1].sure - x[1].sure).map(([a]) => a).join(', '));

const bpk = kipler[0][1].bayt / N;              // kayıt başına bayt
const T = 60000;
console.log(`\n${T} yazmalık yük, saniyede ${HIZ} yazma, ${TAMPON} baytlık tampon,`,
  `${GERIYAZMA} saniyelik geri yazma`);
console.log('kip                      eşzamanlama  en kötü kayıp  ortalama kayıp');
const satir = (ad, es, kotu) => console.log(ad.padEnd(24), String(es).padStart(11),
  String(kotu).padStart(14), String(Math.round(kotu / 2)).padStart(15));
satir('her yazmada', T, 0);
satir('saniyede bir', Math.ceil(T / HIZ), HIZ);
satir('işletim sistemine bırak', 0, Math.floor(TAMPON / bpk) + GERIYAZMA * HIZ);
ölçüm: 1500 kayıt, aynı içerik, üç kip
kip                      yazma çağrısı  eşzamanlama  dosya baytı
her yazmada                       1500         1500        59500
saniyede bir                      1500            1        59500
işletim sistemine bırak              1            0        59500
süre sıralaması (yavaştan hızlıya): her yazmada, saniyede bir, işletim sistemine bırak

60000 yazmalık yük, saniyede 3000 yazma, 65536 baytlık tampon, 5 saniyelik geri yazma
kip                      eşzamanlama  en kötü kayıp  ortalama kayıp
her yazmada                    60000              0               0
saniyede bir                      20           3000            1500
işletim sistemine bırak            0          16652            8326

Üç kip aynı dosyayı üretir: 59.500 bayt, aynı içerik. Ayrılan tek şey, o baytların ne zaman ve hangi garantiyle yerine geçtiğidir.

Her yazmada eşzamanlama, altmış bin yazmalık yükte altmış bin ayrı eşzamanlama demektir ve kayıp penceresini sıfıra indirir. Bu ölçümün alındığı makinede eşzamanlama başına yaklaşık 3,8 milisaniye harcandı; sayı diske ve yüke göre değişir, bu yüzden çıktıya süre değil sıralama basıldı. Değişmeyen nicelik eşzamanlama sayısıdır ve tabloda üç kip arasındaki fark dört büyüklük mertebesindedir. Sıfır kayıp penceresinin bir koşulu daha vardır: eşzamanlama, yanıt istemciye dönmeden önce bitmelidir. Kayıt diske geçmeden “ödünç alındı” yanıtı verilirse pencere yeniden açılır.

Saniyede bir eşzamanlama, aynı yükte yirmi eşzamanlamaya iner; yazma yolundan üç bin kat daha az bekleme çıkarır. Karşılığında en kötü durumda bir saniyelik iş, yani 3.000 ödünç yazması yiter. Ortalama kayıp bunun yarısıdır.

İşletim sistemine bırakmak eşzamanlamayı tümüyle kaldırır ve yazma çağrısı sayısını da düşürür: bin beş yüz kayıt tek bir çağrıyla yazıldı, çünkü hepsi 65.536 baytlık tampona sığdı. En ucuz kip budur ve en kötü kaybı da odur: 16.652 yazma. Bu sayının nereden geldiği önemli, çünkü iki ayrı gecikme toplanıyor — uygulama tamponundaki 1.652 kayıt ve işletim sistemine verilmiş olup henüz kalıcı ortama geçmemiş beş saniyelik iş.

Ayarın Karşılığı

Üç ayar aynı sorunun üç yanıtıdır: bir ödünç yazmasının kaybının bedeli, o yazmayı diske geçirmenin bedelinden büyük mü? Kütüphanenin oturum kayıtları için değildir; oturumu yiten görevli yeniden açar. Ödünç sayaçları için sınırdadır. Ceza hesabına giren gecikme kayıtları için büyüktür, çünkü kaybedilen kayıt paraya karşılık gelir ve geri toplanamaz.

Ekleme günlüğünün anlık görüntüye karşı asıl üstünlüğü, kayıp penceresini yazma başına ayarlanabilir kılmasıdır. Bedeli de dosyanın boyutudur: aynı durum için 3,32 kat yer, ve o yeri geri almak için düzenli yeniden yazım. İki biçimin birbirinin eksiğini kapattığı buradan görünür, ama hangisinin ne zaman seçileceği henüz tek bir tabloda toplanmadı.

Özet

  • Ekleme günlüğü durumu değil komutu kaydeder; ölçümde yazma başına 39,7 bayt tuttu ve dosya yazma sayısıyla doğrusal büyüdü.
  • Günlük yazma sayısıyla, anlık görüntü anahtar sayısıyla büyür: aynı veri için oran 20.000 yazmada 1,66, 60.000 yazmada 3,32 kat oldu.
  • Yeniden yazım 60.000 kaydı 19.040 kayda indirdi ve dosyayı yüzde 68,3 küçülttü; sonuçta ortaya çıkan dosya, iki baytlık komut öneki dışında bir anlık görüntüdür ve aynı tam tarama maliyetini taşır.
  • Eşzamanlama ayarı kayıp penceresini belirler: her yazmada 0 yazma ve 60.000 eşzamanlama; saniyede bir 3.000 yazma ve 20 eşzamanlama; işletim sistemine bırakıldığında 16.652 yazma ve hiç eşzamanlama.
  • Üç kip aynı 59.500 baytlık dosyayı üretti; aralarındaki fark içerikte değil, kaydın hangi durakta durduğu garantisindedir.

Sonraki Adım

İki kalıcılık biçimi de ölçüldü ve ikisinin de kendi başına eksik kaldığı görüldü: anlık görüntü kayıp penceresini kapatamıyor, ekleme günlüğü boyutunu tutamıyor. Sonraki ders seçimi tek bir tabloda toplar. Üç ölçü karşılaştırılır: sürecin yeniden başlamasından hizmete hazır olmasına kadarki kurtarma adımı, kayıp penceresi ve sürekli maliyet. İki yolun birlikte kullanıldığı düzen de aynı tabloya girer — görüntü tabanı, üzerine son görüntüden sonraki kayıtlar — ve bu düzenin kurtarmayı neden hem kısalttığı hem de kayıp penceresini dar tuttuğu sayıyla 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