Sertaç Yıldırımsaha notları

Ana sayfa → Teknik

Leader Election: Bu İşi Kim Yapacak?

Streaming-engine'in dört kopyası çalışıyor. Bridge’lerin sağlık kontrolünü (health check) dördü birden yapıyor. Aynı köprüye saniyede dört ping, aynı alarma dört bildirim, aynı yeniden bağlanma dört kez. Ölçekledik ama işi bölmedik.

Özet
  • Bazı işler tam olarak bir kere yapılmalı. Hepsi yaparsa gürültü, hiçbiri yapmazsa ölü köprü fark edilmez.
  • Liderlik süresiz verilmez, kiralanır. Lider yenilemeyi bırakırsa süre dolar, yerine biri geçer.
  • İki liderin aynı anda olması engellenemez, sadece zararsız hale getirilir. Cevabı fencing token.
  • Çoğu ekip bunu kendi yazmamalı. Kubernetes lease, etcd veya Postgres advisory lock zaten var.

Problem nereden çıkıyor?

Bir servisi yatayda büyütmek genelde bedava gelir: iki kopya iki kat iş yapar. Ama her iş bölünebilir değil. Bazı işler tam olarak bir kere yapılmalı ve kopya sayısı arttıkça bu işler sessizce bozulur.

Trade tarafından tanıdık gelecek örnekler:

İşHepsi yaparsaHiçbiri yapmazsa
Bridge sağlık kontrolü Köprüye 4 kat ping, 4 kat alarm, gereksiz yük Ölü köprü fark edilmez, emirler boşluğa gider
Gün sonu mutabakat işi Aynı rapor 4 kez üretilir, 4 kez mail gider Mutabakat hiç yapılmaz
Sembol listesini dış kaynaktan çekme Sağlayıcının hız sınırına takılırsın Fiyat listesi eskir
Zaman aşımına (timeout) uğramış emirleri temizleme Aynı emri 4 kopya iptal etmeye çalışır Emirler askıda kalır

Sağ sütun ilk bakışta daha korkutucu görünüyor ama sol sütun daha sinsi: hata vermez, sadece pahalıya patlar. Sağlık kontrolünün dört kopyadan gitmesi ilk gün fark edilmez; köprü sağlayıcısı hız sınırı uygulamaya başladığında fark edilir.

Kolay ama yanlış üç çözüm

1. “Sadece bir kopya çalıştıralım”

İşe yarar — o kopya ölene kadar. Kubernetes yeni bir tane açar, arada 20–60 saniye geçer ve o sürede kimse sağlık kontrolü yapmaz. Kabul edilebilir mi? Bazı işler için evet. Bridge health check için hayır.

2. “Ortam değişkeniyle işaretleyelim”

Sık görülen
if (os.getenv("IS_LEADER") == "true") {
    healthCheckDongusunuBaslat();
}

Basit ve çoğu zaman çalışıyor. Sorun şu: o kopya ölünce liderlik ölür. Kubernetes yeni pod açar ama yeni pod’un değişkeni false’tur. Kimse haber vermez; bunu gece 3’te, ölü köprü yüzünden emirler gitmeyince öğrenirsin.

3. “İlk gelen alsın, Redis’e bayrak koyalım”

Doğru yöne gidiyorsun ama yarısı eksik. SETNX lider 1 ile bayrak koyup süre vermezsen, süreç çöktüğünde bayrak sonsuza kadar kalır ve kimse lider olamaz. Bunun tamamı dağıtık kilit (distributed lock) yazısında anlatılıyor; buradaki fark, liderliğin bir kilit değil süreli bir kira olması.

Doğru model: lease (liderlik kiralanır)

Lease mantığı şu: liderlik sonsuza kadar verilmez, belirli bir süreliğine kiralanır. Lider bu kirayı düzenli olarak yeniler. Yenileyemezse — çöktüğü, ağdan koptuğu ya da takıldığı için — süre dolar ve başkası devralır.

Postgres ile en basit hali
CREATE TABLE liderlik (
  is_adi     text PRIMARY KEY,
  sahip      text NOT NULL,
  gecerlilik timestamptz NOT NULL,
  donem      bigint NOT NULL DEFAULT 0   -- ← fencing token
);

-- Her kopya 5 saniyede bir bunu calistirir:
INSERT INTO liderlik (is_adi, sahip, gecerlilik, donem)
VALUES ('bridge_health', :ben, now() + interval '15 seconds', 1)
ON CONFLICT (is_adi) DO UPDATE
   SET sahip      = :ben,
       gecerlilik = now() + interval '15 seconds',
       -- donem SADECE lider degisince artar
       donem      = liderlik.donem + CASE WHEN liderlik.sahip = :ben THEN 0 ELSE 1 END
 WHERE liderlik.sahip = :ben              -- ya zaten benim (yenileme)
    OR liderlik.gecerlilik < now()        -- ya da suresi dolmus (devralma)
RETURNING sahip, donem;

-- Donen sahip ben isem: lider benim, ise devam.
-- Satir donmediyse: baskasi lider, sen bekle.

Otuz satır ve ek bir altyapı yok. Dikkat edilecek üç detay var:

  • Kira süresi, yenileme aralığının en az 3 katı olmalı. Yukarıda 5 saniyede bir yenileme, 15 saniyelik kira. İki yenileme kaçarsa hâlâ liderim; üçüncüde düşerim. Bu oran daha dar olursa tek bir ağ gecikmesi liderliği boşuna el değiştirir ve sistem sürekli lider değiştirir.
  • Devralma süre dolmadan yapılamaz. WHERE satırındaki iki koşul bunu garanti ediyor.
  • donem alanı boşuna değil. Bir sonraki bölümün konusu.

Split-brain: iki lider aynı anda

Şimdi kötü haber. Yukarıdaki kod doğru ama iki kopyanın aynı anda kendini lider sanmasını engellemiyor. Ve hiçbir kod engelleyemez. Sebep:

ZamanKopya AKopya BGerçek durum
00:00lider oldu, dönem 7bekliyorlider A
00:05yeniledibekliyorlider A
00:07GC duraklaması başladıbekliyorlider A (ama uyuyor)
00:20hâlâ duraklamadasüre doldu, lider oldu, dönem 8lider B
00:22uyandı, “ben liderim” sanıyorçalışıyoriki lider!

A yanlış bir şey yapmadı; sadece uyudu ve uyandığında dünyanın değiştiğini bilmiyor. Bu duruma split-brain deniyor ve dağıtık sistemlerde kaçınılmaz.

İki liderin oluşmasını engelleyemezsin. Engelleyebileceğin şey, ikinci liderin zarar vermesi.

Çözüm: fencing token (dönem numarası)

Her liderlik devrinde artan bir numara üret ve korunan kaynağa bu numarayı yazdır. Kaynak, gördüğünden daha küçük numaralı isteği reddetsin:

Eski lider yazamıyor
-- A (donem 7) uyanip bridge durumunu guncellemeye calisiyor:
UPDATE bridge_durum
   SET saglik = 'DOWN', son_kontrol = now(), donem = 7
 WHERE bridge_id = 'LP-3'
   AND donem <= 7;          -- ← tabloda 8 yaziyor, kosul tutmaz

-- 0 satir etkilendi. A yazamadi.
-- Uygulama bunu gorup "artik lider degilim" diyip donguyu durduruyor.

Buradaki incelik: A’nın kendi liderliğini sorgulamasına gerek yok. Yazmaya kalkıyor, reddediliyor, anlıyor. Doğruluk zamana değil sıraya bağlanmış oluyor — dağıtık kilitteki fikrin aynısı.

Yan etkisi olmayan işlerde gerek yok

Her iş için fencing gerekmiyor. Ayrım şu: iş dışarıya bir etki bırakıyor mu?

Fencing gerekmez
  • Salt okuma sağlık kontrolü (ping atıp loglamak)
  • Önbellek ısıtma
  • Metrik toplama

İki kopya yaparsa sadece israf olur.

Fencing şart
  • Emir iptal etme, pozisyon kapatma
  • Bildirim/e-posta gönderme
  • Mutabakat kaydı yazma
  • Dış sisteme durum bildirme

İki kopya yaparsa para veya güven kaybı.

Bunu kendin yazma (çoğu zaman)

Yukarıdaki Postgres yöntemi öğretici ama gerçekte muhtemelen zaten elinde hazır bir araç var:

Nerede çalışıyorsunKullanNot
Kubernetes coordination.k8s.io/Lease Kütüphaneler hazır; süre yönetimini kendi yapıyor
Postgres var pg_try_advisory_lock ya da yukarıdaki tablo Bağlantı kopunca kilit kendiliğinden düşer
etcd / Consul / ZooKeeper Yerleşik lider seçimi En sağlamı; ama sırf bunun için kurulmaz
Sadece Redis var SET ... NX PX + yenileme Çalışır, ama fencing token’ı sen eklemelisin

Kendi Raft uygulamanı yazmak neredeyse hiçbir zaman doğru cevap değil. O kod doğru görünür, testlerden geçer ve altı ay sonra bir ağ bölünmesinde seni yalnız bırakır.

Sahadan: health check’e ne yaptık

Baştaki probleme dönelim. Dört streaming-engine, hepsi köprüleri yokluyor. Yaptığımız üç adım:

  1. Liderlik lease’e bağlandı. 5 saniye yenileme, 15 saniye kira. Ping trafiği dörtte bire indi.
  2. Durum yazımına dönem numarası eklendi. Bir kopya duraklamadan çıkıp “LP-3 çalışmıyor” yazmaya kalkarsa reddediliyor. Bu değişiklikten önce, uykudan uyanan bir kopyanın sağlıklı bir köprüyü ölü işaretleyip trafiği kesmesi yaşandı — en pahalı ders buydu.
  3. Liderlik metriğe döküldü. Her kopya “lider miyim” bilgisini metrik olarak yayıyor. Grafikte toplamın sürekli 1 olması gerekiyor. 0’a düşerse kimse yapmıyor, 2 olursa split-brain var. Tek bir grafik, iki farklı arızayı da gösteriyor.
Küçük ama kritik detay

Liderliği bırakırken haber ver. Kopya düzgün kapanıyorsa (deploy sırasında) kira kaydını silsin. Yoksa yeni lider 15 saniye boşuna bekler. Tek satırlık bir iş ve deploy sırasındaki boşluğu sıfırlıyor:

DELETE FROM liderlik WHERE is_adi = 'bridge_health' AND sahip = :ben;

Kaç lider olmalı? Bir tane, ama hangi konuda?

Sık yapılan bir tasarım hatası: tek bir global lider seçip bütün özel işleri ona yükleme. Sonuç, yatayda büyüyen bir sistemin içinde tek bir dar boğaz oluşması.

Daha iyisi liderliği iş bazında vermek: sağlık kontrolünün lideri ayrı, mutabakatın lideri ayrı, sembol senkronunun lideri ayrı. Böylece yük dağılır ve bir işin liderinin takılması diğerlerini etkilemez.

Bunu bir adım öteye götürmek istersen zaten sharding konusuna girmiş olursun: liderlik “tek kopya yapsın” demek, sharding “her kopya kendi payını yapsın” demek. İkisi birbirinin rakibi değil; hangi işin bölünebildiğine göre seçiyorsun.

Kontrol listesi AI agent görev listesi

Leader election kurarken
  • Bu iş gerçekten tek kopya mı istiyor, yoksa bölünebilir mi?
  • Liderlik süreli mi (lease), yoksa süresiz bir bayrak mı?
  • Kira süresi, yenileme aralığının en az 3 katı mı?
  • Lider çökerse kaç saniye içinde yerine geçiliyor? Bu süre kabul edilebilir mi?
  • İş dışarıya etki bırakıyorsa fencing token var mı?
  • Düzgün kapanışta liderlik bırakılıyor mu?
  • “Kaç lider var” metriği var mı? (Sürekli 1 olmalı.)
  • Liderlik iş bazında mı, yoksa tek global lider mi?

Sonuç

Leader election kulağa akademik geliyor ama pratik karşılığı çok basit bir soruya dayanıyor: bu işi kim yapacak? Cevabı “hepsi” olursa gürültü, “hiçbiri” olursa sessiz bir arıza çıkıyor.

Ve akılda kalacak tek şey şu olsun: liderlik bir mülk değil, bir kiradır. Süresi dolar, el değiştirir ve eski sahibi bunu bilmeyebilir. Sistemin sağlamlığı, eski sahibin geç uyandığında ne yapabildiğiyle ölçülür.