İçeriğe geç
academia.sh

Ders 19 / 25

Fiziksel Çoğaltma

Günlük çerçevelerinin bir yedek sunucuya sürekli akıtılması: taban kopya ve akışın birlikte kurduğu ikinci kopya, çoğaltma gecikmesinin ölçülmesi, gecikmenin bayat okuma ve veri kaybı olarak görünmesi, senkron onayın bedeli.

İçindekiler

Önceki ders yedeğin geçerliliğini doğrulamayı kurdu. Doğrulanmış bir yedek veriyi kaybetmemeyi güvence altına alır, ama kesintiyi kısaltmaz: kırk dakika süren bir geri yükleme, kırk dakika boyunca kütüphanenin ödünç veremeyeceği anlamına gelir. Kesintiyi kısaltmanın yolu, verinin bir kopyasının başka bir makinede zaten hazır durmasıdır.

Bu kopyanın nasıl hazır tutulacağı sorusunun en doğrudan yanıtı, kursun Motor Mimarisi konusunda kurulan düzenekten gelir. Motor her değişikliği veri dosyalarına yazmadan önce günlüğe yazar. Günlük, veritabanının bütün geçmişini sırayla taşır. O günlük ikinci bir makineye akıtılır ve orada uygulanırsa, ikinci makinedeki dosyalar birincinin peşinden gelir. Fiziksel çoğaltma (physical replication) budur: değişiklikler sayfa düzeyinde, günlük kayıtları biçiminde taşınır ve yedek sunucuda yeniden oynatılır.

Taban Kopya ve Akış

Fiziksel çoğaltma iki parçadan oluşur; ikisi de yedekleme derslerinden tanıdıktır.

Taban kopya (base copy), belirli bir andaki veri dosyalarının birebir kopyasıdır — fiziksel yedeğin ta kendisi. Yedek sunucu buradan başlar.

Günlük akışı, taban kopyanın alındığı andan itibaren üretilen günlük kayıtlarının kesintisiz gönderilmesidir. Yedek sunucu gelen kayıtları sırayla uygular ve birincinin durumuna yaklaşır.

Zaman noktasına kurtarmadaki taban yedek ile günlük arşivinin aynı çifti olduğunu fark etmek önemlidir. Aradaki fark amaçtır: kurtarmada günlük bir kez, seçilen bir ana kadar uygulanır; çoğaltmada sürekli, akış devam ettiği sürece uygulanır. Aynı düzenek, iki farklı işletim ihtiyacını karşılar.

Aşağıdaki blok bunu ölçülebilir biçimde kuruyor. Kullanılan motorda günlük dosyası sabit boyutlu çerçevelerden (frame) oluşur; her çerçeve değişmiş bir sayfanın kopyasını ve bir başlığı taşır. Blok, birincil veritabanında beş parti ödünç kaydı açıyor, her partiden sonra günlük dosyasının boyutunu not ediyor, sonra taban kopyaya günlüğün yalnız ilk bölümünü uygulayarak yedek sunucunun her ara durumunu üretiyor.

Çerçeve boyutu ve sayısı bu motora özgüdür; başka motorlar günlüğü bayt konumu ya da sıra numarasıyla ölçer. Değişmeyen şey, gönderilmemiş günlük miktarının gecikmenin ölçüsü olmasıdır.

rm -f birincil.db birincil.db-wal birincil.db-shm taban.db yedek.db yedek.db-wal yedek.db-shm

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const fs = require("node:fs");
const crypto = require("node:crypto");
const SAYFA = 4096, CERCEVE = SAYFA + 24, GUNLUK_BASLIK = 32;

const birincil = new DatabaseSync("birincil.db");
birincil.exec("PRAGMA page_size=4096");
birincil.exec("PRAGMA journal_mode=WAL");
birincil.exec("PRAGMA wal_autocheckpoint=0");
birincil.exec("CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL," +
              " uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL)");
birincil.exec("PRAGMA wal_checkpoint(TRUNCATE)");

// Taban kopya: yedek sunucu bu dosyadan baslar.
fs.copyFileSync("birincil.db", "taban.db");

const ekle = birincil.prepare(
  "INSERT INTO odunc(kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?)");
const kesim = [];
for (let parti = 0; parti < 5; parti++) {
  birincil.exec("BEGIN");
  for (let i = 1; i <= 200; i++) ekle.run(i % 500, i % 300, i % 3, "2025-06-01");
  birincil.exec("COMMIT");
  kesim.push(fs.statSync("birincil.db-wal").size);
}

const gunluk = fs.readFileSync("birincil.db-wal");
const toplamCerceve = (gunluk.length - GUNLUK_BASLIK) / CERCEVE;
const ozet = (db) => crypto.createHash("sha256").update(
  db.prepare("SELECT id,kitap_id,uye_id,sube_id FROM odunc ORDER BY id").all()
    .map((r) => r.id + "," + r.kitap_id + "," + r.uye_id + "," + r.sube_id).join("\n")
).digest("hex").slice(0, 8);

const birincilOzet = ozet(birincil);
console.log("birincil: " + birincil.prepare("SELECT COUNT(*) c FROM odunc").get().c +
            " satir, " + toplamCerceve + " gunluk cercevesi, icerik ozeti " + birincilOzet);
console.log();
console.log("gonderilen cerceve | yedek satir | gecikme | icerik ozeti | esitlik");
console.log("-------------------|-------------|---------|--------------|--------");
for (const boyut of kesim) {
  fs.rmSync("yedek.db-wal", { force: true });
  fs.rmSync("yedek.db-shm", { force: true });
  fs.copyFileSync("taban.db", "yedek.db");
  fs.writeFileSync("yedek.db-wal", gunluk.subarray(0, boyut));
  const yedek = new DatabaseSync("yedek.db");
  const cerceve = (boyut - GUNLUK_BASLIK) / CERCEVE;
  const o = ozet(yedek);
  console.log(String(cerceve).padStart(18) + " | " +
              String(yedek.prepare("SELECT COUNT(*) c FROM odunc").get().c).padStart(11) +
              " | " + String(toplamCerceve - cerceve).padStart(7) + " | " +
              o.padStart(12) + " | " + (o === birincilOzet ? "ESIT" : "GERIDE"));
  yedek.close();
}
EOF
birincil: 1000 satir, 20 gunluk cercevesi, icerik ozeti 0a8630f5

gonderilen cerceve | yedek satir | gecikme | icerik ozeti | esitlik
-------------------|-------------|---------|--------------|--------
                 4 |         200 |      16 |     e2b63ba7 | GERIDE
                 8 |         400 |      12 |     10a42e86 | GERIDE
                12 |         600 |       8 |     cc62491f | GERIDE
                16 |         800 |       4 |     10142745 | GERIDE
                20 |        1000 |       0 |     0a8630f5 | ESIT

Tablonun her satırı bir zaman anıdır. Yedek sunucu, o ana kadar gönderilmiş çerçevelerin uygulandığı durumdadır: tutarlı, sorgulanabilir, ama geride. Son satırda gecikme sıfıra indiğinde içerik özeti birincilinkiyle aynıdır — kopya, sayfa düzeyinde birebir eşittir.

Ara satırların hiçbiri bozuk değildir. Yedek sunucu yarım bir işlemin ortasında durmaz; uygulama işlem sınırlarında ilerler, dolayısıyla her ara durum geçerli bir veritabanıdır. Bu, fiziksel çoğaltmanın temel güvencesidir: yedek geride olabilir, tutarsız olamaz.

Gecikmenin İki Görünümü

Çoğaltma gecikmesi bir sayı olarak zararsız görünür. İşletimde iki ayrı biçimde ortaya çıkar ve ikisi farklı sorunlardır.

Aşağıdaki blok bunu gösteriyor: yedek sunucu yetişmiş durumdayken ödünç masası üç yeni kayıt açıyor ve o anda akış duruyor.

rm -f birincil.db birincil.db-wal birincil.db-shm taban.db yedek.db yedek.db-wal yedek.db-shm

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const fs = require("node:fs");
const CERCEVE = 4096 + 24;

const birincil = new DatabaseSync("birincil.db");
birincil.exec("PRAGMA page_size=4096");
birincil.exec("PRAGMA journal_mode=WAL");
birincil.exec("PRAGMA wal_autocheckpoint=0");
birincil.exec("CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL," +
              " uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL)");
birincil.exec("PRAGMA wal_checkpoint(TRUNCATE)");
fs.copyFileSync("birincil.db", "taban.db");

const ekle = birincil.prepare(
  "INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?,?)");
birincil.exec("BEGIN");
for (let i = 1; i <= 1000; i++) ekle.run(i, i % 500, i % 300, i % 3, "2025-06-01");
birincil.exec("COMMIT");

// Yedek sunucu buraya kadar yetismis durumda.
const gonderilen = fs.statSync("birincil.db-wal").size;
fs.copyFileSync("taban.db", "yedek.db");
fs.writeFileSync("yedek.db-wal", fs.readFileSync("birincil.db-wal"));
let yedek = new DatabaseSync("yedek.db");
console.log("yetismis yedek, satir sayisi: " +
            yedek.prepare("SELECT COUNT(*) c FROM odunc").get().c);
yedek.close();

// Odunc masasi uc yeni kayit acar; cerceveler henuz gonderilmemistir.
birincil.exec("BEGIN");
for (let i = 1001; i <= 1003; i++) ekle.run(i, i % 500, i % 300, 2, "2025-06-10");
birincil.exec("COMMIT");
const bekleyen = (fs.statSync("birincil.db-wal").size - gonderilen) / CERCEVE;

fs.rmSync("yedek.db-shm", { force: true });
fs.copyFileSync("taban.db", "yedek.db");
fs.writeFileSync("yedek.db-wal",
                 fs.readFileSync("birincil.db-wal").subarray(0, gonderilen));
yedek = new DatabaseSync("yedek.db");
const say = (db) => db.prepare("SELECT COUNT(*) c FROM odunc WHERE id>=1001").get().c;
console.log();
console.log("bekleyen cerceve         : " + bekleyen);
console.log("birincilde yeni kayit    : " + say(birincil));
console.log("yedekte yeni kayit       : " + say(yedek));
console.log("yedekten okuyan rapor    : " +
  yedek.prepare("SELECT COUNT(*) c FROM odunc WHERE sube_id=2 AND alis='2025-06-10'").get().c +
  " kayit gorur");
yedek.close();

// Bekleyen cerceveler de gonderilir.
fs.rmSync("yedek.db-shm", { force: true });
fs.copyFileSync("taban.db", "yedek.db");
fs.writeFileSync("yedek.db-wal", fs.readFileSync("birincil.db-wal"));
yedek = new DatabaseSync("yedek.db");
console.log("gonderim sonrasi yedekte : " + say(yedek) + " yeni kayit");
yedek.close();
EOF
yetismis yedek, satir sayisi: 1000

bekleyen cerceve         : 4
birincilde yeni kayit    : 3
yedekte yeni kayit       : 0
yedekten okuyan rapor    : 0 kayit gorur
gonderim sonrasi yedekte : 3 yeni kayit

Birinci görünüm: bayat okuma. Yedek sunucu salt okunur bir kopyadır ve okuma yükünü buraya yöneltmek çoğaltmanın en sık kullanılan yararıdır. Raporlar, arama sorguları ve gösterge panoları birinciyi meşgul etmez. Karşılığında okunan veri gecikme kadar eskidir. Yukarıda rapor sıfır kayıt görüyor; oysa üç kayıt açılmış durumda.

Bu, her uygulama için sorun değildir. Aylık ödünç sayısını hesaplayan bir rapor için birkaç saniyelik gecikme fark etmez. Ödünç masasındaki görevlinin az önce kaydettiği ödüncü hemen sorgulaması ise farklıdır: kendi yazdığını okuyamaz. Bu duruma yol açmamanın işletim kuralı basittir — yazma yapan oturumun sonraki okumaları birinciye gider, gecikmeye duyarsız okumalar yedeğe.

İkinci görünüm: devralmada veri kaybı. Birincil sunucu bu anda kaybolsaydı, yedek sunucu 1000 satırla devralırdı; gönderilmemiş dört çerçevedeki üç ödünç kaydı kaybolurdu. Gecikme, kurtarma noktası hedefinin karşılığıdır: hedef “en çok beş saniyelik veri kaybı” diyorsa, gecikmenin beş saniyeyi aşmadığının izlenmesi gerekir. İzlenmeyen gecikme, ölçülmemiş bir kurtarma noktası hedefidir.

Senkron Onayın Bedeli

Yukarıdaki kayıp, çoğaltmanın asenkron olmasından kaynaklanır: birincil sunucu işlemi kesinleştirir, kullanıcıya “tamam” der, günlüğü sonra gönderir. Alternatifi, kesinleştirme onayını yedek sunucunun kaydı aldığını bildirmesine bağlamaktır — senkron onay. Bu durumda kayıp sıfırdır, çünkü onay verilen her işlem iki makinededir.

Bedeli her işleme eklenen ağ gidiş-dönüş süresidir. Aşağıdaki blok yerel kesinleştirme süresini gerçekten ölçüyor, ağ gecikmesini ise modelliyor: gidiş-dönüş süreleri verilmiş sayılardır, ölçülmemiştir. Yerel süre de makineye, dosya sistemine ve eşitleme ayarına bağlıdır; sizde farklı çıkacaktır. Anlamlı olan sütunlar arasındaki orandır.

rm -f onay.db onay.db-wal onay.db-shm

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const db = new DatabaseSync("onay.db");
db.exec("PRAGMA journal_mode=WAL");
db.exec("PRAGMA synchronous=FULL");
db.exec("CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT)");
const ekle = db.prepare("INSERT INTO odunc(kitap_id,uye_id) VALUES(?,?)");

// Yerel kesinlestirme suresi olculur: her kayit ayri islem.
const sure = [];
for (let i = 0; i < 300; i++) {
  const t = process.hrtime.bigint();
  db.exec("BEGIN"); ekle.run(i % 500, i % 300); db.exec("COMMIT");
  sure.push(Number(process.hrtime.bigint() - t) / 1e6);
}
sure.sort((a, b) => a - b);
const yerel = sure[Math.floor(sure.length / 2)];
console.log("yerel kesinlestirme (ortanca): " + yerel.toFixed(3) + " ms");
console.log();
console.log("onay kipi          | ag gidis-donusu | islem suresi | saniyede islem | kayip riski");
console.log("-------------------|-----------------|--------------|----------------|------------");
const satir = (ad, rtt, kayip) => {
  const t = yerel + rtt;
  console.log(ad.padEnd(18) + " | " + (rtt ? rtt + " ms" : "-").padStart(15) + " | " +
              (t.toFixed(3) + " ms").padStart(12) + " | " +
              Math.round(1000 / t).toString().padStart(14) + " | " + kayip);
};
satir("asenkron", 0, "gecikme kadar");
satir("senkron (yerel ag)", 1, "yok");
satir("senkron (bolgeler)", 10, "yok");
satir("senkron (kitalar)", 60, "yok");
EOF
yerel kesinlestirme (ortanca): 0.040 ms

onay kipi          | ag gidis-donusu | islem suresi | saniyede islem | kayip riski
-------------------|-----------------|--------------|----------------|------------
asenkron           |               - |     0.040 ms |          24845 | gecikme kadar
senkron (yerel ag) |            1 ms |     1.040 ms |            961 | yok
senkron (bolgeler) |           10 ms |    10.040 ms |            100 | yok
senkron (kitalar)  |           60 ms |    60.040 ms |             17 | yok

Tablo tek bir bağlantının ardışık işlem hızını verir; birden çok bağlantı paralel çalıştığında toplam iş hacmi artar, ama tek bir kullanıcının beklediği süre değişmez. Kütüphane bağlamında okunuşu şudur: ödünç masasında bir kaydın açılması, aynı şehirdeki bir yedek sunucuyla milisaniyenin altında bir gecikmeyle sürer; kıtalar arası bir yedekle her ödünç işlemi altmış milisaniye bekler. İkincisi işlemez bir arayüz üretir.

Bu yüzden yaygın topoloji ikisini birleştirir: yakındaki bir yedek sunucu senkron onaylanır (veri kaybı sıfırdır), uzaktaki bir yedek sunucu asenkron beslenir (bölge çapında bir arızaya karşı kopya bulunur, gecikme fiyatı ödenmez).

Senkron onayın atlanan bir yan etkisi vardır: yedek sunucu yanıt vermeyi bıraktığında birincil sunucu da yazamaz hâle gelir, çünkü hiçbir işlem onaylanamaz. Erişilebilirliği artırmak için kurulan düzenek, tek bir yedek sunucuyla yapılandırıldığında erişilebilirliği düşürür. Bunun önlenmesi, onayı birden çok yedek sunucudan herhangi birine bağlamakla ya da yedek yanıt vermediğinde asenkron kipe düşmekle sağlanır; ikinci seçenek, kayıp riskinin geri geldiği anlamına gelir ve bilinerek seçilmelidir.

Yedek Sunucunun Sınırları

Fiziksel çoğaltma sayfa kopyalar. Bu, üç sınırı doğrudan doğurur.

Kopya bütündür, seçilemez. Yalnız ödünç tablosunu çoğaltmak olanaklı değildir; bütün veritabanı gelir. Sayfa numarası düzeyinde çalışan bir düzenek, o sayfanın hangi tabloya ait olduğunu umursamaz.

Kopya salt okunurdur. Yedek sunucuya yazmak, gelen günlük akışıyla çatışırdı. Bu nedenle yedek sunucudaki tek yazar akıştır; uygulamalar oradan yalnız okur. Yedek sunucuda eksik bir dizini tamamlamak ya da geçici bir tablo oluşturmak olanaklı değildir.

Kopya aynı sürümü ve aynı düzeni gerektirir. Günlük kayıtları sayfa yapısını anlatır; sayfa yapısını farklı yorumlayan bir sürüm onları uygulayamaz. Bu, sürüm yükseltmede belirleyici bir kısıttır ve bu konunun son dersinde geri gelecektir.

Bir sınır daha vardır ve işletimde en sık yaşananıdır: yedek sunucuda uzun süren bir okuma sorgusu, gelen değişikliklerin uygulanmasını geciktirebilir. Neden, kursun Motor Mimarisi konusunda kurulan çok sürümlü eşzamanlılık denetimidir — okuyucunun gördüğü sürümlerin korunması gerekir. Sonuç, saatlerce süren bir raporun çoğaltma gecikmesini büyütmesidir.

Özet

  • Fiziksel çoğaltma, taban kopya ile sürekli günlük akışının birleşimidir; yedek sunucu gelen günlük kayıtlarını sırayla uygulayarak birincinin peşinden gelir.
  • Yedek sunucunun her ara durumu tutarlıdır; gecikme, gönderilmemiş günlük miktarıyla ölçülür ve sıfırlandığında iki kopyanın içeriği birebir eşittir.
  • Gecikme iki biçimde görünür: yedekten okuyan raporlar bayat veri görür, devralma anında gönderilmemiş kayıtlar kaybolur.
  • Senkron onay veri kaybını sıfırlar ve karşılığında her işleme bir ağ gidiş-dönüşü ekler; uzak bir yedekle bu, işlem süresini üç mertebe büyütebilir.
  • Tek yedek sunucuya bağlı senkron onay, yedek yanıt vermediğinde birincinin de yazamaz hâle gelmesine yol açar.
  • Fiziksel kopya bütündür, salt okunurdur ve aynı sayfa düzenini gerektirir.

Sonraki Adım

Fiziksel çoğaltmanın üç sınırı da aynı yerden geliyor: taşınan şey satır değil, sayfadır. Sayfa yerine değişen satırların kendisi taşınsaydı, kopya seçilebilir olurdu — yalnız ödünç ve üye tabloları gönderilebilirdi. Hedefteki tablo farklı bir düzende, hatta farklı bir motor sürümünde olabilirdi, çünkü uygulanan şey bir sayfa görüntüsü değil “şu satır şu değerlere geçti” bilgisi olurdu. Sonraki ders mantıksal çoğaltmayı ele alıyor: seçici kopyalamanın kurulması, gönderilen veri hacminin fiziksel çoğaltmayla karşılaştırılması, farklı şemaya sahip bir hedefe yazma ve bu yolun sürüm geçişlerinde açtığı kapı.

İ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