İçeriğe geç
academia.sh

Ders 21 / 25

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.

İçindekiler

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

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

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

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

İ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