---
title: 'Fiziksel Çoğaltma'
source: 'https://academia.sh/tr/kurslar/veritabani-yonetimi/fiziksel-cogaltma'
course: 'İlişkisel Veritabanı Yönetimi'
language: tr
updated: '2026-08-17T18:08:56+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

Ö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.

```bash
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
```

```text
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.

```bash
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
```

```text
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.

```bash
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
```

```text
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ı.
