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

# Mantıksal Çoğaltma

Satır düzeyinde değişikliklerin seçilen tablolar için taşınması: yayın ve abonelik, gönderilen veri hacminin fiziksel çoğaltmayla karşılaştırılması, farklı şemaya sahip hedef, çakışmanın akışı durdurması ve şema değişikliğinin uygulanma sırası.

Fiziksel çoğaltmanın üç sınırı da aynı yerden geliyordu: taşınan şey satır değil, sayfaydı.
Sayfa yerine değişen satırların kendisi taşınırsa üç sınır da kalkar. Kopya seçilebilir
olur — yalnız ödünç ve üye tabloları gönderilebilir. Hedefteki tablo farklı bir düzende,
farklı bir dizin kümesiyle, hatta farklı bir motor sürümünde bulunabilir, çünkü uygulanan
şey bir sayfa görüntüsü değil "şu satır şu değerlere geçti" bilgisidir.

**Mantıksal çoğaltma (logical replication)** budur. Motor, günlüğü okur ve içindeki sayfa
değişikliklerinden satır düzeyindeki olayları çıkarır: hangi tabloya hangi satır eklendi,
hangi satır hangi değerlere güncellendi, hangi satır silindi. Bu çıkarma işlemine
**mantıksal çözümleme (logical decoding)** denir. Sonuç, sayfa numaralarından bağımsız,
kendi başına anlamlı bir değişiklik akışıdır.

## Yayın ve Abonelik

Mantıksal çoğaltmanın iki ucu vardır.

**Yayın (publication)**, kaynak sunucuda hangi tabloların çoğaltılacağını belirtir. Kapsam
dışındaki tabloların değişiklikleri akışa hiç girmez.

**Abonelik (subscription)**, hedef sunucuda akışı okuyup uygulayan taraftır. Nereye kadar
uyguladığını bir konum bilgisiyle saklar; bağlantı koptuğunda o konumdan sürdürür.
Motorların çoğu, henüz uygulanmamış değişikliklerin silinmemesi için kaynak tarafta bir
**çoğaltma yuvası (replication slot)** tutar.

Aşağıdaki blok bunu kütüphane veritabanında kuruyor. Kaynakta dört tablo var: üye, kitap,
ödünç ve oturum kaydı. Yayın yalnız iki tabloyu kapsıyor. Değişiklik akışı gerçek bir
motorda günlükten çözümlenerek üretilir; burada aynı bilgi **tetikleyicilerle** üretiliyor.
Modelin model olan yanı budur: akışın nereden çıktığı. İçeriği, uygulanma biçimi ve kapsam
davranışı gerçeğiyle aynıdır.

```bash
rm -f birincil.db abone.db

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const crypto = require("node:crypto");

const birincil = new DatabaseSync("birincil.db");
birincil.exec(`
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sube_id INT NOT NULL);
CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, raf TEXT);
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, iade TEXT);
CREATE TABLE oturum_kaydi(id INTEGER PRIMARY KEY, hesap TEXT, an TEXT);
CREATE TABLE yayin(sira INTEGER PRIMARY KEY, tablo TEXT, islem TEXT, anahtar INT, satir TEXT);
`);

// Yayin kapsami: yalniz uye ve odunc.
for (const [tablo, alan] of [
  ["uye", "'id',NEW.id,'ad',NEW.ad,'sube_id',NEW.sube_id"],
  ["odunc", "'id',NEW.id,'kitap_id',NEW.kitap_id,'uye_id',NEW.uye_id," +
            "'sube_id',NEW.sube_id,'alis',NEW.alis,'iade',NEW.iade"]]) {
  for (const [olay, kod] of [["INSERT", "E"], ["UPDATE", "G"]]) {
    birincil.exec(`CREATE TRIGGER y_${tablo}_${kod} AFTER ${olay} ON ${tablo} BEGIN
      INSERT INTO yayin(tablo,islem,anahtar,satir)
      VALUES('${tablo}','${kod}',NEW.id,json_object(${alan})); END`);
  }
  birincil.exec(`CREATE TRIGGER y_${tablo}_S AFTER DELETE ON ${tablo} BEGIN
    INSERT INTO yayin(tablo,islem,anahtar,satir)
    VALUES('${tablo}','S',OLD.id,NULL); END`);
}

// Bir gunluk is: uye ve kitap kayitlari, odunc acilislari, iadeler, silmeler, oturum kaydi.
birincil.exec("BEGIN");
const uye = birincil.prepare("INSERT INTO uye(id,ad,sube_id) VALUES(?,?,?)");
for (let i = 1; i <= 500; i++) uye.run(i, "Uye-" + i, (i % 3) + 1);
const kitap = birincil.prepare("INSERT INTO kitap(id,baslik,raf) VALUES(?,?,?)");
for (let i = 1; i <= 300; i++) kitap.run(i, "Kitap-" + i, "R" + (i % 20));
const odunc = birincil.prepare(
  "INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?,?)");
for (let i = 1; i <= 2000; i++)
  odunc.run(i, (i % 300) + 1, (i % 500) + 1, (i % 3) + 1, "2025-06-01");
const iade = birincil.prepare("UPDATE odunc SET iade='2025-06-20' WHERE id=?");
for (let i = 1; i <= 400; i++) iade.run(i * 3);
const sil = birincil.prepare("DELETE FROM odunc WHERE id=?");
for (let i = 1; i <= 50; i++) sil.run(i * 37);
const oturum = birincil.prepare("INSERT INTO oturum_kaydi(hesap,an) VALUES(?,?)");
for (let i = 1; i <= 800; i++) oturum.run("odunc_masasi", "2025-06-10");
birincil.exec("COMMIT");

// Abone tarafinda yalniz iki tablo tanimli.
const abone = new DatabaseSync("abone.db");
abone.exec(`
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sube_id INT NOT NULL);
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, iade TEXT);
`);
const uygula = (db, k) => {
  if (k.islem === "S") { db.prepare(`DELETE FROM ${k.tablo} WHERE id=?`).run(k.anahtar); return; }
  const satir = JSON.parse(k.satir), sutun = Object.keys(satir);
  db.prepare(`INSERT OR REPLACE INTO ${k.tablo}(${sutun.join(",")})` +
             ` VALUES(${sutun.map(() => "?").join(",")})`).run(...sutun.map((s) => satir[s]));
};
const kayitlar = birincil.prepare("SELECT * FROM yayin ORDER BY sira").all();
abone.exec("BEGIN");
for (const k of kayitlar) uygula(abone, k);
abone.exec("COMMIT");

console.log("yayin akisindaki kayitlar:");
for (const r of birincil.prepare("SELECT tablo, islem, COUNT(*) n FROM yayin" +
    " GROUP BY tablo, islem ORDER BY tablo, islem").all())
  console.log("  " + r.tablo.padEnd(6) + " " + r.islem + " : " + r.n);
console.log("  toplam  : " + kayitlar.length);

const ozet = (db, t, s) => crypto.createHash("sha256").update(
  db.prepare(`SELECT ${s} FROM ${t} ORDER BY id`).all()
    .map((r) => Object.values(r).join(",")).join("\n")).digest("hex").slice(0, 8);
const say = (db, t) => { try { return db.prepare(`SELECT COUNT(*) c FROM ${t}`).get().c; }
                         catch { return "tablo yok"; } };
console.log();
console.log("tablo        | birincil |     abone | icerik ozeti (birincil / abone)");
console.log("-------------|----------|-----------|-------------------------------");
for (const [t, s] of [["uye", "id,ad,sube_id"],
                      ["odunc", "id,kitap_id,uye_id,sube_id,alis,iade"]])
  console.log(t.padEnd(12) + " | " + String(say(birincil, t)).padStart(8) + " | " +
              String(say(abone, t)).padStart(9) + " | " +
              ozet(birincil, t, s) + " / " + ozet(abone, t, s));
for (const t of ["kitap", "oturum_kaydi"])
  console.log(t.padEnd(12) + " | " + String(say(birincil, t)).padStart(8) + " | " +
              String(say(abone, t)).padStart(9) + " |");
EOF
```

```text
yayin akisindaki kayitlar:
  odunc  E : 2000
  odunc  G : 400
  odunc  S : 50
  uye    E : 500
  toplam  : 2950

tablo        | birincil |     abone | icerik ozeti (birincil / abone)
-------------|----------|-----------|-------------------------------
uye          |      500 |       500 | 840815bb / 840815bb
odunc        |     1950 |      1950 | 3ec52d07 / 3ec52d07
kitap        |      300 | tablo yok |
oturum_kaydi |      800 | tablo yok |
```

Kapsamın etkisi sayılarda görünüyor. Kaynakta dört bin elli satır değişti; akışa yalnız
iki bin dokuz yüz elli tanesi girdi. Kitap ve oturum kaydı tablolarının bin yüz
değişikliği hiç taşınmadı — ağ trafiği, hedefteki disk ve orada yapılan yazma işi bu kadar
azaldı.

Abonedeki iki tablonun içerik özeti kaynağınkiyle aynı. Bu, üç işlem türünün de doğru
uygulandığını gösterir: eklemeler, dört yüz iade güncellemesi ve elli silme. Silme
kayıtları yalnız anahtar taşır; silinen satırın diğer alanları gereksizdir. Bu, mantıksal
çoğaltmanın satırı **anahtarıyla** bulduğu anlamına gelir ve
birincil anahtarı olmayan bir tabloda çoğaltmanın neden zorlaştığını açıklar: hedefteki
satırı seçecek bir ölçüt yoktur. Motorlar bunun için tabloya bir **çoğaltma kimliği
(replica identity)** atanmasını ister.

## Taşınan Veri Hacmi

İki yöntem arasındaki en somut fark taşınan bayt miktarıdır ve bu fark iş yüküne göre
mertebe değiştirir. Aşağıdaki blok aynı iş yükünü iki ayrı veritabanında çalıştırıyor:
biri günlüğü ölçülen fiziksel taraf, diğeri değişiklik kayıtları ölçülen mantıksal taraf.

Sayılar sayfa boyutuna, satır genişliğine ve parti büyüklüğüne bağlıdır; sizde farklı
çıkacaktır. Mantıksal tarafta yalnız değişiklik gövdesi sayılıyor, kayıt başına protokol
yükü sayılmıyor. Okunması gereken şey oranların yönüdür.

```bash
rm -f fiziksel.db fiziksel.db-wal fiziksel.db-shm mantiksal.db mantiksal.db-wal mantiksal.db-shm

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");
const fs = require("node:fs");
const SEMA = "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, iade TEXT)";

function kur(dosya, tetikleyici) {
  const db = new DatabaseSync(dosya);
  db.exec("PRAGMA page_size=4096"); db.exec("PRAGMA journal_mode=WAL");
  db.exec("PRAGMA wal_autocheckpoint=0");
  db.exec(SEMA);
  db.exec("CREATE TABLE akis(sira INTEGER PRIMARY KEY, tablo TEXT, islem TEXT, satir TEXT)");
  const ekle = db.prepare(
    "INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?,?)");
  db.exec("BEGIN");
  for (let i = 1; i <= 20000; i++) ekle.run(i, i % 500, i % 300, i % 3, "2025-06-01");
  db.exec("COMMIT");
  if (tetikleyici) {
    for (const [olay, kod] of [["UPDATE", "G"], ["INSERT", "E"]])
      db.exec(`CREATE TRIGGER t_${kod} AFTER ${olay} ON odunc BEGIN
        INSERT INTO akis(tablo,islem,satir) VALUES('odunc','${kod}',
          json_object('id',NEW.id,'kitap_id',NEW.kitap_id,'uye_id',NEW.uye_id,
                      'sube_id',NEW.sube_id,'alis',NEW.alis,'iade',NEW.iade)); END`);
  }
  db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
  return db;
}

function olc(ad, isyuku) {
  const f = kur("fiziksel.db", false);
  const m = kur("mantiksal.db", true);
  isyuku(f); isyuku(m);
  const fizik = fs.statSync("fiziksel.db-wal").size;
  const mant = m.prepare("SELECT IFNULL(SUM(LENGTH(satir)),0) b, COUNT(*) n FROM akis").get();
  f.close(); m.close();
  for (const d of ["fiziksel.db", "fiziksel.db-wal", "fiziksel.db-shm",
                   "mantiksal.db", "mantiksal.db-wal", "mantiksal.db-shm"])
    fs.rmSync(d, { force: true });
  console.log(ad.padEnd(20) + " | " + String(mant.n).padStart(7) + " | " +
              String(fizik).padStart(8) + " | " + String(mant.b).padStart(9) + " | " +
              (fizik / mant.b).toFixed(1).padStart(5));
}

console.log("is yuku              | degisim | fiziksel | mantiksal | oran");
console.log("---------------------|---------|----------|-----------|------");
olc("daginik guncelleme", (db) => {
  const g = db.prepare("UPDATE odunc SET iade='2025-06-20' WHERE id=?");
  for (let p = 0; p < 100; p++) {
    db.exec("BEGIN");
    for (let i = 0; i < 20; i++) g.run(((p * 20 + i) * 7919) % 20000 + 1);
    db.exec("COMMIT");
  }
});
olc("sirali guncelleme", (db) => {
  const g = db.prepare("UPDATE odunc SET iade='2025-06-20' WHERE id=?");
  for (let p = 0; p < 100; p++) {
    db.exec("BEGIN");
    for (let i = 0; i < 20; i++) g.run(p * 20 + i + 1);
    db.exec("COMMIT");
  }
});
olc("toplu ekleme", (db) => {
  const e = db.prepare(
    "INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?,?)");
  for (let p = 0; p < 100; p++) {
    db.exec("BEGIN");
    for (let i = 0; i < 20; i++) { const n = 20001 + p * 20 + i;
      e.run(n, n % 500, n % 300, n % 3, "2025-06-10"); }
    db.exec("COMMIT");
  }
});
EOF
```

```text
is yuku              | degisim | fiziksel | mantiksal | oran
---------------------|---------|----------|-----------|------
daginik guncelleme   |    2000 |  9014592 |    181716 |  49.6
sirali guncelleme    |    2000 |   964112 |    179685 |   5.4
toplu ekleme         |    2000 |   572712 |    166790 |   3.4
```

Üç satır da iki bin satırlık değişiklik taşıyor; taşınan bayt miktarı on beş kat aralıkta
değişiyor. Nedeni fiziksel tarafın birimidir: değişen satır ne kadar küçük olursa olsun,
o satırı içeren **sayfanın tamamı** gönderilir.

Dağınık güncellemede her satır ayrı bir sayfada olduğu için iki bin sayfa taşınır; bir
tarihin yazılması, dört kilobaytın gönderilmesine mal olur. Sıralı güncellemede aynı sayfa
içindeki çok sayıda satır tek bir partide değiştiği için sayfa bir kez gönderilir ve oran
beşe düşer. Toplu eklemede satırlar zaten yan yana yerleşir; fiziksel taraf en verimli
durumundadır.

Mantıksal tarafın gizli maliyeti ise hedefte ortaya çıkar: gelen her satır normal bir
yazma işlemi olarak uygulanır, dizinler güncellenir, kısıtlar denetlenir. Fiziksel tarafta
bunların hiçbiri yoktur; sayfa olduğu gibi yerine konur. Az veri taşıyan yöntem, hedefte
daha çok iş yapar.

## Farklı Şema, Çakışma ve Şema Değişikliği

Esnekliğin üç sonucu vardır; aşağıdaki blok üçünü sırayla gösteriyor: hedefin fazladan
sütunu ve dizini vardır, yerel bir satır akışla çakışır ve kaynakta bir sütun eklenir.

```bash
rm -f kaynak.db hedef.db

node - <<'EOF'
const { DatabaseSync } = require("node:sqlite");

const kaynak = new DatabaseSync("kaynak.db");
kaynak.exec(`
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL,
                   uye_id INT NOT NULL, alis TEXT NOT NULL);
CREATE TABLE yayin(sira INTEGER PRIMARY KEY, satir TEXT);
CREATE TRIGGER y_e AFTER INSERT ON odunc BEGIN
  INSERT INTO yayin(satir) VALUES(json_object('id',NEW.id,'kitap_id',NEW.kitap_id,
    'uye_id',NEW.uye_id,'alis',NEW.alis)); END;
`);
const ekle = kaynak.prepare("INSERT INTO odunc(id,kitap_id,uye_id,alis) VALUES(?,?,?,?)");
for (let i = 1; i <= 100; i++) ekle.run(i, i % 50, i % 30, "2025-06-01");

// Hedef: fazladan sutunu ve fazladan dizini olan bir tablo.
const hedef = new DatabaseSync("hedef.db");
hedef.exec(`
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL, uye_id INT NOT NULL,
                   alis TEXT NOT NULL, gecikme_gunu INT NOT NULL DEFAULT 0);
CREATE INDEX odunc_uye ON odunc(uye_id);
`);
// Hedefte acilmis yerel bir kayit: cakismayi bu uretecek.
hedef.prepare("INSERT INTO odunc(id,kitap_id,uye_id,alis) VALUES(?,?,?,?)")
     .run(60, 9, 9, "2025-06-05");

const uygula = (kayit) => {
  const satir = JSON.parse(kayit.satir), sutun = Object.keys(satir);
  hedef.prepare(`INSERT INTO odunc(${sutun.join(",")})` +
                ` VALUES(${sutun.map(() => "?").join(",")})`).run(...sutun.map((s) => satir[s]));
};
let konum = 0;
const ilerle = () => {
  for (const k of kaynak.prepare("SELECT sira,satir FROM yayin ORDER BY sira").all()) {
    if (k.sira <= konum) continue;
    try { uygula(k); konum = k.sira; }
    catch (h) { return "sira " + k.sira + " durdu: " + h.message; }
  }
  return "akis bitti";
};
const durum = () => "   uygulanan kayit: " + konum + ", hedefteki satir: " +
                    hedef.prepare("SELECT COUNT(*) c FROM odunc").get().c;

console.log("1. gecis: " + ilerle());
console.log(durum());

// Cozum: catisan yerel satir kaldirilir, akis kaldigi yerden surer.
hedef.prepare("DELETE FROM odunc WHERE id=?").run(60);
console.log("2. gecis: " + ilerle());
console.log(durum());
console.log("   hedefe ozgu sutunun degeri: " +
  hedef.prepare("SELECT gecikme_gunu FROM odunc WHERE id=1").get().gecikme_gunu);

// Kaynakta sema degisikligi: yeni sutun yayina da girer.
kaynak.exec("ALTER TABLE odunc ADD COLUMN gecikme_bildirimi TEXT");
kaynak.exec("DROP TRIGGER y_e");
kaynak.exec(`CREATE TRIGGER y_e AFTER INSERT ON odunc BEGIN
  INSERT INTO yayin(satir) VALUES(json_object('id',NEW.id,'kitap_id',NEW.kitap_id,
    'uye_id',NEW.uye_id,'alis',NEW.alis,'gecikme_bildirimi',NEW.gecikme_bildirimi)); END`);
kaynak.prepare("INSERT INTO odunc(id,kitap_id,uye_id,alis,gecikme_bildirimi)" +
               " VALUES(?,?,?,?,?)").run(101, 7, 7, "2025-06-11", "ikinci uyari");
console.log("3. gecis: " + ilerle());

// Sema degisikligi once hedefe uygulanir.
hedef.exec("ALTER TABLE odunc ADD COLUMN gecikme_bildirimi TEXT");
console.log("4. gecis: " + ilerle());
console.log(durum());
EOF
```

```text
1. gecis: sira 60 durdu: UNIQUE constraint failed: odunc.id
   uygulanan kayit: 59, hedefteki satir: 60
2. gecis: akis bitti
   uygulanan kayit: 100, hedefteki satir: 100
   hedefe ozgu sutunun degeri: 0
3. gecis: sira 101 durdu: table odunc has no column named gecikme_bildirimi
4. gecis: akis bitti
   uygulanan kayit: 101, hedefteki satir: 101
```

**Fazladan sütun ve dizin sorun değildir.** Hedefteki `gecikme_gunu` sütunu akışta yoktur;
varsayılan değerini alır. Hedefteki dizin de kaynakta bulunmaz ve gelen her satırda
güncellenir. Bu, aynı verinin iki tarafta farklı erişim örüntülerine göre dizinlenmesine
olanak verir.

**Çakışma akışı durdurur.** Hedefte yerel olarak açılmış altmış numaralı kayıt, akıştaki
aynı numaralı kayıtla çatıştı ve uygulama elli dokuzuncu kayıtta durdu. Bu, veri
kaybetmemenin bilinçli yoludur: çakışan kaydı sessizce ezmek de, sessizce atlamak da iki
tarafı sessizce ayrıştırır. Durma, bir kararın verilmesini bekler. Karar verilip çakışan
satır kaldırıldıktan sonra akış kaldığı konumdan sürdü ve yüz kaydın tamamı uygulandı.

Çakışmanın kaynağı çoğu zaman aynıdır: hedefe yerel yazma yapılması. Mantıksal çoğaltmanın
hedefi salt okunur değildir ve bu, esnekliğin ikinci yüzüdür. Çakışmayı önlemenin işletim
kuralı, hedefteki tabloların yalnız akış tarafından yazılmasıdır; yerel yazma gerekiyorsa
anahtar aralıkları ayrılır.

**Şema değişikliği çoğaltılmaz.** Kaynakta eklenen sütun akışa girdi ve hedefte karşılığı
olmadığı için uygulama durdu. Aynı sütun hedefe eklendikten sonra akış sürdü. Buradan tek
bir işletim kuralı çıkar: **sütun ekleme önce hedefe, sonra kaynağa uygulanır**; sütun
kaldırma ise ters sırada, önce kaynaktan. Sıra karıştığında çoğaltma durur, ve durduğu
sürece kaynakta biriken değişiklikler saklanmaya devam eder.

## Sürüm Geçişindeki Rolü

Bu üç davranışın toplamı, mantıksal çoğaltmayı bir sürüm geçişi aracına dönüştürür.
Fiziksel çoğaltma hedefin aynı sayfa düzenini anlamasını gerektirir; mantıksal çoğaltma
gerektirmez. Yeni sürümde kurulmuş boş bir veritabanı, eski sürümdeki veritabanına abone
edilebilir; veri akarken eski sürüm çalışmayı sürdürür. İki taraf eşitlendiğinde
uygulamalar yeni tarafa yönlendirilir.

Bu yolun kesinti penceresi, geri yükleme süresi kadar değil, yalnız yönlendirme kadardır.
Aynı düzenek bir tabloyu ayrı bir veritabanına taşımak ya da bir rapor sunucusunu sürekli
beslemek için de kullanılır. Sürüm yükseltmenin bütünü, bu konunun son dersinin konusudur.

## Özet

- Mantıksal çoğaltma, günlükten çözümlenen satır düzeyindeki değişiklikleri taşır; yayın
  kapsamı dışındaki tabloların değişiklikleri akışa hiç girmez.
- Silme kayıtları yalnız anahtar taşır; hedefteki satırın anahtarla bulunabilmesi
  gerektiği için çoğaltma kimliği tanımlanmamış tablolar sorun çıkarır.
- Taşınan hacim iş yüküne bağlıdır: dağınık küçük güncellemelerde fiziksel çoğaltma
  mantıksala göre bir mertebe fazla veri gönderir, toplu eklemede fark küçülür.
- Az veri taşıyan yöntem hedefte daha çok iş yapar; gelen satırlar normal yazma olarak
  uygulanır, dizinler ve kısıtlar işletilir.
- Hedefin fazladan sütunu ve farklı dizinleri olabilir; çakışma akışı durdurur ve karar
  verilmesini bekler.
- Şema değişikliği çoğaltılmaz: sütun ekleme önce hedefe, sütun kaldırma önce kaynağa
  uygulanır.

## Sonraki Adım

İki çoğaltma biçimi de aynı varsayımı taşıyor: birincil sunucu kimdir sorusunun yanıtı
bellidir. Birincil kaybolduğunda bu yanıt kaybolur ve yerine yenisinin konması gerekir.
Kararı kim verir, ne zaman verir ve iki ayrı taraf aynı anda "birincil benim" derse ne
olur? Sonraki ders yüksek erişilebilirliği ele alıyor: devralma ile planlı geçişin farkı,
küme yöneticisinin rolü, çoğunluk oyunun neyi güvence altına aldığı ve bölünmüş beyin
riskinin nasıl doğduğu.
