KayaDB: Sıfırdan Dağıtık Veritabanı Yazmak

rust kayadb distributed-systems raft database jepsen open-source

Dağıtık sistemler, yazılım mühendisliğinin Everest'i gibidir. Herkes zirveyi görür, çok azı tırmanır. Ben tırmanmaya karar verdim. Sonuç: KayaDB — Rust ile yazılmış, Raft tabanlı, production'a hazırlanan dağıtık bir key-value depolama motoru.

Bu yazı, o tırmanışın günlüğü.

Neden kendi veritabanımı yazdım?

Dürüst cevap: porque puedo. (Çünkü yapabiliyorum.)

Ciddi cevap: Etcd var, Tikv var, FoundationDB var. Ama hiçbiri benim istediğim gibi değildi. İstediğim şey:

  • Sıfır operasyonel yük. Bir binary indir, çalıştır. Config dosyası yok, YAML cehennemi yok.
  • Gömülebilir. Library olarak kullanılabilmeli. kayadb = "0.1" yazıp kendi uygulamana gömebilmelisin.
  • Jepsen-tested. Kyle Kingsbury'nin testlerinden geçmemiş hiçbir dağıtık sistem iddiası ciddiye alınamaz.
  • Rust. Elle bellek yönetimiyle uğraşmadan C++ performansı.

Bunların hepsini aynı anda veren bir şey yoktu. Ben de yaptım.

Mimari

KayaDB'nin kalbinde Raft consensus algoritması var. Raft, dağıtık sistemlerde leader election, log replication ve safety garantilerini sağlayan en anlaşılır consensus protokolü — Paxos'un "insan tarafından anlaşılabilir" versiyonu.

Üç temel katman:

┌─────────────────────────────────┐
│         Client API (gRPC)       │
├─────────────────────────────────┤
│      Raft Consensus Layer       │
│  ┌─────────┐  ┌──────────────┐  │
│  │ Leader  │  │  Follower x2  │  │
│  │ Election│  │  Log Repl.   │  │
│  └─────────┘  └──────────────┘  │
├─────────────────────────────────┤
│     Storage Engine (LSM-Tree)   │
│  ┌─────────┐  ┌──────────────┐  │
│  │ MemTable│  │  SSTables    │  │
│  └─────────┘  └──────────────┘  │
└─────────────────────────────────┘

Storage engine: LSM-tree (Log-Structured Merge-tree). Yazma ağırlıklı iş yükleri için optimize edilmiş. MemTable'da biriktir, belli boyuta ulaşınca SSTable olarak diske flush'la, arkada compaction yap. RocksDB'nin yaptığının daha basit, daha anlaşılır bir versiyonu.

Raft katmanı: Leader seçer, log'ları replike eder, commit index'i ilerletir. Standard Raft — hiçbir sürpriz yok, spesifikasyona harfiyen uygun.

Client API: Şimdilik gRPC. Gelecekte native TCP protokolü de gelecek.

M1'den M13'e: 13 milestone, 6 ay

Projeyi milestone'larla yönetiyorum. Her milestone birkaç hafta sürüyor ve somut bir çıktı veriyor.

M1-M3 (Ocak 2026): Temel storage engine. Single-node, memory-only. put(key, value) ve get(key) çalışıyor. Heyecan verici değil ama temel.

M4-M6 (Şubat): Persistence. WAL (Write-Ahead Log), MemTable flush, SSTable formatı. İlk defa restart'ta veri kaybetmemeye başladık. Bu hissi tarif edemem — yazdığınız kod, kapattığınız bir programın verisini geri getiriyor.

M7-M9 (Mart): Raft implementasyonu. En zor kısım. Raft paper'ını yaklaşık 17 kez okudum. Leader election, log replication, snapshotting — her biri kendi içinde bir dünya. En büyük baş ağrısı: network partition simülasyonu. Test yazmak, implementasyondan daha zordu.

M10-M12 (Nisan-Mayıs): Jepsen testleri. Kyle Kingsbury'nin meşhur dağıtık sistem test framework'ü. Linearizability, partition tolerance, clock skew — hepsini test ediyor. Ve ilk çalıştırmada... kırmızı. Her şey kırmızıydı.

M13 (Haziran 2026): Production hazırlığı. Ve işte o an: 6/6 Jepsen gate'i yeşil.

Jepsen'in yeşil vermesi, dağıtık sistemler dünyasında bir tür şövalyelik unvanı gibidir. "Senin sistemin, ağ bölündüğünde, disk çöktüğünde, saat kaydığında bile veri tutarlılığını koruyor" demektir.

Jepsen'le dans

Jepsen testleri yazmak, normal test yazmaya benzemez. Normal testte "şu input'a şu output" dersiniz. Jepsen'de "şu kaotik senaryoda sistemin davranışı linearizable mı?" dersiniz. Aradaki fark, deprem simülasyonuyla bina test etmek gibidir.

Bir Jepsen testi şunları yapar:

  1. Bir küme başlatır (genelde 5 node)
  2. Rastgele işlemler gönderir (append, read, cas)
  3. Bu sırada nemesis'leri devreye sokar:
    • partition-random-node: Ağı rastgele böler
    • kill-random-node: Rastgele node'u öldürür
    • clock-skew: Sistem saatini kaydırır
    • pause-random-node: Node'u dondurur (SIGSTOP)
  4. Test sonunda tüm işlem geçmişini analiz eder
  5. "Bu geçmiş lineerize edilebilir mi?" sorusunu cevaplar

Eğer cevap "hayır" ise, bir yerde tutarlılık garantisini ihlal etmişsinizdir. Ve inanın, ilk denemede cevap her zaman "hayır"dır.

Benim en büyük hatam neydi? fsync'i atlamak.

İtiraf ediyorum: WAL yazarken fsync() çağırmayı unutmuştum. Veriyi write() ile yazıyordum, sayfa önbelleğinde duruyordu, diskte değil. Node crash olduğunda... WAL boştu. Jepsen bunu hemen yakaladı: "Senin sistemin durability garantisi yok."

Fix: Her WAL yazımından sonra fsync(). Performans düştü mü? Evet, %30. Ama veri kaybetmemeye başladık. Trade-off buydu.

Bir diğer hata: Read Index optimizasyonunu yanlış implemente etmek. Raft'ta read'ler için leader'a gitmek gerekir, ama her read için bir log entry yazmak pahalıdır. Read Index tekniği, read'leri log yazmadan güvenli hale getirir. Ama ben lease süresini yanlış hesaplamıştım — partitioned leader hala "ben leader'ım" sanıp stale read dönebiliyordu. Jepsen: kırmızı.

Fix: Lease tabanlı leader kontrolü + her read öncesi heartbeat confirmation.

Böyle 11 tane Jepsen hatası yakaladım. Her biri ayrı bir ders.

Ne öğrendim?

1. Dağıtık sistemlerde "works on my machine" diye bir şey yoktur. Network partition'da, disk hatasında, clock skew'de çalışmayan kod, çalışmıyordur. Jepsen bunu suratınıza vurur.

2. Raft paper'ını okumak yetmez, implemente etmek gerekir. Leader election'ın ne kadar edge case içerdiğini, paper'ın 5. sayfasındaki bir cümlenin ne anlama geldiğini ancak implemente ederken anlıyorsunuz.

3. fsync her şeydir. Durability = fsync. Başka yolu yok. Ne kadar batch yaparsanız yapın, eninde sonunda o sistem çağrısını yapmak zorundasınız.

4. Test altyapısı, ürün kodundan daha önemlidir. KayaDB'nin test kodu, ürün kodundan daha fazla satır. Network partition simülasyonları, fault injection, chaos engineering — hepsi test tarafında.

Sırada ne var?

M14 — Jepsen test suite final + prod deployment. Hedef: kendi makinemde değil, gerçek bir sunucuda, gerçek trafik altında KayaDB çalıştırmak.

Sonrası:

  • gRPC API stabilizasyonu
  • Rust client library (kayadb-client)
  • Go client library (belki)
  • Benchmark'ları yayınlamak — etcd, Tikv ile karşılaştırmalı

Deneyin

git clone https://github.com/Tuntii/KayaDB
cd KayaDB
cargo build --release
./target/release/kayadb --id node1 --peers node2:5001,node3:5001

3 node, 3 terminal, Raft cluster'ınız hazır.

GitHub: github.com/Tuntii/KayaDB Dokümantasyon: tuntii.github.io/KayaDB

Dağıtık sistemler zor. Ama yapılabilir. Ve yapınca, bilgisayar biliminin en temel problemlerinden birini kendi ellerinizle çözmüş oluyorsunuz. O his paha biçilemez.

Bir sonraki yazıda Hyperembed'i anlatacağım — Pure-Rust BERT embedding inference motoru. Ya da belki SemantiCAD. Bakalım hangisi önce biter.

← Blog