---
title: 'Yüksek Erişilebilirlik ve Devralma'
source: 'https://academia.sh/tr/kurslar/veritabani-yonetimi/yuksek-erisilebilirlik-ve-devralma'
course: 'İlişkisel Veritabanı Yönetimi'
language: tr
updated: '2026-08-17T18:08:56+00:00'
license: 'CC BY-SA 4.0'
---

# Yüksek Erişilebilirlik ve Devralma

Birincil sunucunun kaybında hizmetin sürmesi: küme yöneticisinin kararı, çoğunluk kuralının bölünmüş beyni nasıl engellediği, iki birincilin ürettiği veri çatışması ve algılama süresi ile yanlış devralma arasındaki ödünleşim.

Önceki iki ders çoğaltmayı kurdu; ikisi de aynı varsayımı taşıyordu: birincil sunucunun
kim olduğu bellidir. Birincil kaybolduğunda bu yanıt da kaybolur ve yerine yenisinin
konması gerekir. Verinin ikinci bir makinede hazır durması, o makinenin kendiliğinden
hizmet vermeye başlaması anlamına gelmez — birinin karar vermesi, yedek sunucuyu
yükseltmesi ve uygulamaları oraya yönlendirmesi gerekir.

Bu dersin konusu o karardır: kim verir, hangi bilgiye dayanarak verir ve iki ayrı taraf
aynı anda "birincil benim" derse ne olur.

## Devralma, Planlı Geçiş ve Küme Yöneticisi

İki işlem birbirine karıştırılır.

**Planlı geçiş (switchover)**, birincil ve yedek rollerinin isteyerek değiştirilmesidir.
Birincil sunucu çalışır durumdadır: yeni yazma kabul etmeyi bırakır, yedek sunucunun
yetişmesi beklenir, roller değiştirilir. Veri kaybı yoktur, kesinti saniyeler
mertebesindedir. Bakım, donanım değişimi ve sürüm yükseltme bu yolla yapılır.

**Devralma (failover)**, birincil sunucu erişilemez durumdayken yedek sunucunun birincil
olarak yükseltilmesidir. Birincilin yetişmeyi bekleme olanağı yoktur; asenkron çoğaltmada
gönderilmemiş her değişiklik kaybolur. Kayıp miktarı, önceki derste ölçülen çoğaltma
gecikmesidir.

Kararı veren bileşene **küme yöneticisi (cluster manager)** denir. Veritabanı motorunun
dışında çalışır, düğümleri düzenli aralıklarla yoklar ve üç işi yapar: birincilin
erişilebilirliğini izlemek, gerektiğinde bir yedeği yükseltmek, uygulamaların gittiği
adresi yeni birincile çevirmek. Bu son adım olmadan yükseltme işe yaramaz — uygulamalar
hâlâ eski adrese bağlanmaya çalışır.

## Çoğunluk Kuralı

Küme yöneticisinin en zor sorusu, birincilin gerçekten mi öldüğü yoksa yalnız görünmez mi
olduğudur. İkisi dışarıdan aynıdır: yanıt gelmiyor. Fakat sonuçları taban tabana zıttır.
Birincil ölmüşse yedeğin yükseltilmesi gerekir; birincil çalışıyor ama ağ bölünmesi
nedeniyle görünmüyorsa, yükseltme ikinci bir birincil yaratır.

Aşağıdaki blok iki kuralı aynı bölünme senaryolarında karşılaştırıyor. Bu bir modeldir;
gerçek bir küme yazılımı çalıştırılmıyor, kararı veren kural doğrudan uygulanıyor.

```bash
node - <<'EOF'
// Kume bir ag bolunmesinde gruplara ayrilir; birincil her zaman 1 numarali dugumdedir.
const senaryo = [
  [2, [[1], [2]]],
  [3, [[1, 2], [3]]],
  [3, [[1], [2, 3]]],
  [4, [[1, 2], [3, 4]]],
  [5, [[1, 2, 3], [4, 5]]],
  [5, [[1, 2], [3, 4, 5]]],
  [5, [[1, 2], [3, 4], [5]]],
];
const satir = (a, b, c, d) => console.log(String(a).padStart(5) + " | " + b.padEnd(7) +
  " | " + c.padEnd(28) + " | " + d);

console.log("COGUNLUK KURALI: bir grup, ancak dugumlerin yarisindan fazlasini");
console.log("bir araya getiriyorsa yazma kabul eder.");
console.log();
satir("dugum", "bolunme", "cogunluk", "sonuc");
console.log("------|---------|------------------------------|--------------------------");
for (const [n, gruplar] of senaryo) {
  const cogunluk = gruplar.find((g) => g.length * 2 > n);
  const birincilde = cogunluk && cogunluk.includes(1);
  satir(n, gruplar.map((g) => g.length).join("+"),
    cogunluk ? cogunluk.join(",") + " grubu (" + cogunluk.length + "/" + n + ")" : "yok",
    cogunluk ? (birincilde ? "birincil yerinde kalir" : "devralma; eski birincil durur")
             : "hicbir tarafta yazma yok");
}

console.log();
console.log('COGUNLUKSUZ KURAL: "birincili goremiyorsam olmustur, ben birincil olurum".');
console.log();
satir("dugum", "bolunme", "birincil sayisi", "sonuc");
console.log("------|---------|------------------------------|--------------------------");
for (const [n, gruplar] of senaryo)
  satir(n, gruplar.map((g) => g.length).join("+"), gruplar.length + " birincil",
    gruplar.length > 1 ? "bolunmus beyin" : "sorun yok");
EOF
```

```text
COGUNLUK KURALI: bir grup, ancak dugumlerin yarisindan fazlasini
bir araya getiriyorsa yazma kabul eder.

dugum | bolunme | cogunluk                     | sonuc
------|---------|------------------------------|--------------------------
    2 | 1+1     | yok                          | hicbir tarafta yazma yok
    3 | 2+1     | 1,2 grubu (2/3)              | birincil yerinde kalir
    3 | 1+2     | 2,3 grubu (2/3)              | devralma; eski birincil durur
    4 | 2+2     | yok                          | hicbir tarafta yazma yok
    5 | 3+2     | 1,2,3 grubu (3/5)            | birincil yerinde kalir
    5 | 2+3     | 3,4,5 grubu (3/5)            | devralma; eski birincil durur
    5 | 2+2+1   | yok                          | hicbir tarafta yazma yok

COGUNLUKSUZ KURAL: "birincili goremiyorsam olmustur, ben birincil olurum".

dugum | bolunme | birincil sayisi              | sonuc
------|---------|------------------------------|--------------------------
    2 | 1+1     | 2 birincil                   | bolunmus beyin
    3 | 2+1     | 2 birincil                   | bolunmus beyin
    3 | 1+2     | 2 birincil                   | bolunmus beyin
    4 | 2+2     | 2 birincil                   | bolunmus beyin
    5 | 3+2     | 2 birincil                   | bolunmus beyin
    5 | 2+3     | 2 birincil                   | bolunmus beyin
    5 | 2+2+1   | 3 birincil                   | bolunmus beyin
```

Alt tablo, tek başına makul görünen bir kuralın her bölünmede iki birincil ürettiğini
gösteriyor. Sorun, bir düğümün "karşı taraf öldü" ile "karşı tarafı göremiyorum"
arasındaki farkı yerel bilgiyle ayırt edememesidir. **Çoğunluk kuralı (quorum)** bu ayrımı
başka bir yerden getirir: düğümlerin yarısından fazlasını bir araya getiren grup en fazla
bir tane olabilir, dolayısıyla yazma hakkını yalnız ona vermek iki birincili olanaksız
kılar.

Üst tablonun üç okuması vardır. Üçüncü satırda birincil azınlıkta kalıyor ve çoğunluk
tarafında devralma yapılıyor; eski birincilin yazmayı **kendiliğinden** durdurması
zorunludur. İkinci ve dördüncü satırda hiçbir grup çoğunlukta değildir ve yazma tümüyle
durur — küme kendini kullanılamaz kılarak veri çatışmasını önler. Erişilebilirlik
tutarlılığa feda edilmiştir; bu bir kusur değil, bilinçli bir seçimdir.

Dört düğümlü satır, küme boyutu hakkındaki en pratik sonucu verir: dört düğüm, üç düğüme
göre bölünme dayanıklılığı katmaz. İkisi de tek düğüm kaybını tolere eder, ama dört düğüm
2+2 bölünmesinde tamamen durur. Bu nedenle küme boyutları tek sayı seçilir. İki düğümlü
bir kurulumda veri taşımayan üçüncü bir oy verici — **tanık düğüm (witness)** —
eklenerek aynı sonuç ucuza elde edilir.

## İki Birincilin Bedeli

Bölünmüş beynin zararı soyut değildir. Aşağıdaki blok bölünmeyi gerçekten üretiyor: aynı
veritabanının iki kopyası ayrı ayrı yazma kabul ediyor ve bölünme kapandığında iki taraf
karşılaştırılıyor.

```bash
rm -f taraf-a.db taraf-b.db

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

const SEMA = `CREATE TABLE odunc(id INTEGER PRIMARY KEY AUTOINCREMENT, kitap_id INT NOT NULL,
                uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL, iade TEXT)`;
const a = new DatabaseSync("taraf-a.db");
a.exec(SEMA);
const ekleA = a.prepare(
  "INSERT INTO odunc(kitap_id,uye_id,sube_id,alis,iade) VALUES(?,?,?,?,?)");
a.exec("BEGIN");
for (let i = 1; i <= 1000; i++)
  ekleA.run(i % 400 + 1, i % 250 + 1, i % 3 + 1, "2025-06-01", "2025-06-15");
a.exec("COMMIT");
a.close();

// Bolunme ani: iki taraf ayni durumdan yola cikar.
fs.copyFileSync("taraf-a.db", "taraf-b.db");
const sol = new DatabaseSync("taraf-a.db");
const sag = new DatabaseSync("taraf-b.db");
const ac = (db, kitap, uye, sube) => db.prepare(
  "INSERT INTO odunc(kitap_id,uye_id,sube_id,alis) VALUES(?,?,?,?)")
  .run(kitap, uye, sube, "2025-06-16");
// A tarafi: 1 ve 2 numarali sube masalari. B tarafi: 3 numarali sube.
for (let i = 0; i < 40; i++) ac(sol, 101 + i, 10 + i, (i % 2) + 1);
for (let i = 0; i < 25; i++) ac(sag, 131 + i, 60 + i, 3);

const say = (db, s) => db.prepare(s).get().c;
console.log("bolunmeden sonra");
console.log("  A tarafi satir : " + say(sol, "SELECT COUNT(*) c FROM odunc"));
console.log("  B tarafi satir : " + say(sag, "SELECT COUNT(*) c FROM odunc"));

// Bolunme kapanir: iki tarafin kayitlari karsilastirilir.
sol.exec("ATTACH 'taraf-b.db' AS b");
const cakisanId = say(sol, `SELECT COUNT(*) c FROM main.odunc m JOIN b.odunc x USING(id)
                            WHERE m.kitap_id <> x.kitap_id OR m.uye_id <> x.uye_id`);
const ciftKitap = say(sol, `SELECT COUNT(*) c FROM
  (SELECT kitap_id FROM main.odunc WHERE iade IS NULL
   INTERSECT SELECT kitap_id FROM b.odunc WHERE iade IS NULL)`);
console.log();
console.log("  ayni kimlik, farkli kayit      : " + cakisanId);
console.log("  iki tarafta da odunc olan kitap: " + ciftKitap);

// Birlestirme denemesi: B tarafinin yeni kayitlari A tarafina eklenir.
let hata = 0, eklenen = 0;
for (const r of sol.prepare("SELECT * FROM b.odunc WHERE id > 1000").all()) {
  try {
    sol.prepare("INSERT INTO main.odunc(id,kitap_id,uye_id,sube_id,alis,iade)" +
                " VALUES(?,?,?,?,?,?)")
       .run(r.id, r.kitap_id, r.uye_id, r.sube_id, r.alis, r.iade);
    eklenen++;
  } catch { hata++; }
}
console.log();
console.log("birlestirme denemesi: " + eklenen + " eklendi, " + hata + " kimlik catismasi");
EOF
```

```text
bolunmeden sonra
  A tarafi satir : 1040
  B tarafi satir : 1025

  ayni kimlik, farkli kayit      : 25
  iki tarafta da odunc olan kitap: 10

birlestirme denemesi: 0 eklendi, 25 kimlik catismasi
```

Sonuçlar iki farklı zorluk gösteriyor. Yirmi beş kimlik çakışması **teknik** bir sorundur:
iki taraf da aynı sıra üretecinden aynı numaraları dağıttı. Kayıtlara yeni numara vererek
çözülebilir, ama o numaraları taşıyan makbuzlar, dış sistem kayıtları ve raporlar
geçersizleşir.

On kitabın iki tarafta da ödünçte görünmesi **teknik olmayan** bir sorundur. Aynı kitap
iki ayrı üyeye verilmiş görünüyor ve hangisinin gerçekten rafta olmadığını veritabanı
bilemez. Bu, otomatik birleştirmenin sınırıdır: çatışan iki gerçeklikten hangisinin doğru
olduğuna karar vermek veri işi değil, işletme işidir. Bölünmüş beynin maliyeti bu yüzden
kesinti maliyetiyle karşılaştırılmaz — kesinti biter, ayrışan veri kalır.

Çoğunluk kuralı bunu önlemenin birinci katmanıdır. İkinci katman **çitlemedir (fencing)**:
azınlıkta kalan eski birincilin yazmasının dışarıdan engellenmesi. Yerleşik yollar,
sunucunun ağ üzerinden kapatılması ya da paylaşılan depolamaya erişiminin iptalidir.
Gerekçesi, azınlıkta kalan düğümün kendini durduracağına güvenilemeyeceğidir: tıkanmış bir
süreç, kararı verirken zaten yanıt veremeyecek durumdadır.

## Algılama Süresi ile Yanlış Devralma Ödünleşimi

Küme yöneticisinin ayarları tek bir eksende toplanır: ne kadar hızlı karar verilecek.
Hızlı karar kesintiyi kısaltır ve geçici bir tıkanmayı arıza sanma olasılığını artırır.
Aşağıdaki blok bunu ölçüyor. Yanıt verme durumu ve tıkanma süreleri **modeldir**; gerçek
bir sunucu yoklanmıyor. Terfi, yönlendirme ve yeniden bağlanma maliyeti sabit on saniye
alınmıştır.

```bash
node - <<'EOF'
// Model: 3600 saniyelik bir pencere. Birincil sunucu ara sira 1-4 saniye yanit
// vermiyor (gecici tikaniklik); 3000. saniyede gercekten ariza veriyor.
const SURE = 3600, ARIZA = 3000, TIKANMA_OLASILIGI = 0.004;
let tohum = 20250616;
const rastgele = () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648;

const yanit = new Array(SURE).fill(true);
let tikanma = 0;
for (let t = 0; t < ARIZA; t++) {
  if (rastgele() < TIKANMA_OLASILIGI) {
    const uzunluk = 1 + Math.floor(rastgele() * 4);
    tikanma++;
    for (let i = 0; i < uzunluk && t + i < ARIZA; i++) yanit[t + i] = false;
    t += uzunluk;
  }
}
for (let t = ARIZA; t < SURE; t++) yanit[t] = false;

// Terfi, yonlendirme ve uygulamanin yeniden baglanmasi (model degeri).
const DEVRALMA_MALIYETI = 10;
console.log("gecici tikanma: " + tikanma + " kez, her biri 1-4 sn. " +
            "Gercek ariza: " + ARIZA + ". saniye.");
console.log();
console.log("kontrol | ard arda | yanlis    | algilama | kesinti");
console.log("araligi | hata     | devralma  | suresi   | (RTO)");
console.log("--------|----------|-----------|----------|--------");
for (const [aralik, k] of [[1, 1], [1, 2], [1, 3], [2, 3], [5, 2], [5, 3], [10, 3], [30, 3]]) {
  let ardisik = 0, yanlis = 0, algilama = null, tetiklendi = false;
  for (let t = aralik; t < SURE; t += aralik) {
    if (yanit[t]) { ardisik = 0; tetiklendi = false; continue; }
    ardisik++;
    if (ardisik < k || tetiklendi) continue;
    tetiklendi = true;
    if (t < ARIZA) yanlis++;
    else if (algilama === null) algilama = t - ARIZA;
  }
  console.log(String(aralik + " sn").padStart(7) + " | " + String(k).padStart(8) + " | " +
    String(yanlis).padStart(9) + " | " +
    (algilama === null ? "-" : algilama + " sn").padStart(8) + " | " +
    (algilama === null ? "-" : algilama + DEVRALMA_MALIYETI + " sn").padStart(7));
}
EOF
```

```text
gecici tikanma: 10 kez, her biri 1-4 sn. Gercek ariza: 3000. saniye.

kontrol | ard arda | yanlis    | algilama | kesinti
araligi | hata     | devralma  | suresi   | (RTO)
--------|----------|-----------|----------|--------
   1 sn |        1 |        10 |     0 sn |   10 sn
   1 sn |        2 |        10 |     1 sn |   11 sn
   1 sn |        3 |         4 |     2 sn |   12 sn
   2 sn |        3 |         0 |     4 sn |   14 sn
   5 sn |        2 |         0 |     5 sn |   15 sn
   5 sn |        3 |         0 |    10 sn |   20 sn
  10 sn |        3 |         0 |    20 sn |   30 sn
  30 sn |        3 |         0 |    60 sn |   70 sn
```

Sütunların yönü zıttır. Kontrol aralığı büyüdükçe ve üst üste beklenen hata sayısı
arttıkça yanlış devralma sıfıra iner, kesinti süresi yetmiş saniyeye çıkar. Her satır bir
saatlik pencerede on geçici tıkanma içeriyor; en agresif ayar bunların hepsini arıza sayıp
on kez gereksiz devralma yapıyor.

Gereksiz devralmanın zararsız olmadığını görmek önemlidir. Her devralma, asenkron
çoğaltmada gecikme kadar veri kaybı, bütün bağlantıların kopması ve eski birincilin kümeye
yeniden katılma işi demektir. Çalışan bir sunucunun bir saniye yavaşladığı için indirilmesi,
önlemeye çalıştığı kesintiden büyük bir kesinti üretebilir.

Ayarın seçimi kurtarma süresi hedefine bağlanır. Hedef "en çok bir dakika" ise on saniyelik
kontrol aralığı yeterlidir ve yanlış devralma riski taşımaz. Hedef saniyeler mertebesindeyse
tıkanma kaynaklarının azaltılması gerekir; agresif ayar, dengesiz bir sistemin üzerine
kurulduğunda dengesizliği büyütür.

Devralma sonrasında bir iş daha kalır: eski birincilin kümeye yedek sunucu olarak
döndürülmesi. Eski birincil, gönderilmemiş değişiklikler nedeniyle yeni birincilden
ayrışmış bir tarihe sahiptir; olduğu gibi bağlanamaz. Motorların çoğu bu ayrışmayı bulup
geri saran bir araç sağlar; sağlamıyorsa yol, eski birincili yeni birincilden alınmış bir
taban kopyayla baştan kurmaktır.

## Özet

- Planlı geçiş roller arasında isteyerek yapılan, kayıpsız bir değişimdir; devralma ise
  erişilemeyen bir birincilin yerine yedeğin yükseltilmesidir ve asenkron çoğaltmada
  gecikme kadar veri kaybettirir.
- Bir düğüm "öldü" ile "görünmüyor" arasındaki farkı yerel bilgiyle ayırt edemez; çoğunluk
  kuralı bu ayrımı düğüm sayısından türetir ve iki birincili olanaksız kılar.
- Çift sayılı küme boyutu dayanıklılık katmaz; bölünmede hiçbir taraf çoğunluk
  sağlayamayabilir. Tanık düğüm, veri taşımadan oy sayısını tekleştirir.
- Bölünmüş beynin bıraktığı kimlik çakışmaları teknik olarak çözülebilir; iki tarafta
  birden değişen gerçeklik çözülemez ve otomatik birleştirmenin sınırıdır.
- Çitleme, azınlıkta kalan eski birincilin yazmasını dışarıdan engeller; düğümün kendini
  durduracağına güvenilmez.
- Sağlık denetimi sıklaştıkça algılama süresi kısalır ve geçici tıkanmaların yanlış
  devralma üretme olasılığı artar; ayar, kurtarma süresi hedefine göre seçilir.

## Sonraki Adım

Devralmanın son adımı uygulamaların yeni birincile yönlendirilmesiydi. Bu, uygulamanın
veritabanına doğrudan değil, araya giren bir katman üzerinden bağlandığı anlamına gelir.
Aynı katmanın çözdüğü ikinci bir sorun daha vardır ve arıza olmadan da her gün
karşılaşılır: her uygulama süreci kendi bağlantısını açtığında, birkaç yüz süreç
veritabanının kaldırabileceğinden çok bağlantı üretir. Sonraki ders bağlantı
havuzlayıcılarını ele alıyor: bağlantı açmanın gerçek maliyeti, havuz boyutunun kuyruk
süresiyle ilişkisi ve oturum ile işlem düzeyinde havuzlamanın farkı.
