15%
TÜM EĞİTİMLERDE SEPETTE İNDİRİM
Ruvio
Rust ile yazıldı · RESP konuşur · açık kaynak
Cache, lock, rate limit, idempotency, delay queue.
Beş ayrı sistem değil, tek bir process.
Ruvio, redis-cli ile bağlandığın bir Rust veri deposu. Bir key üzerinde atomik karar vermen gereken her iş, o key’in sahibi shard’da, aynı WAL’da çözülüyor.
292
komut için tipli client metodu
6
resmi client: Rust, C#, Go, Java, Python, Node.js
~2,7M
GET/s, tek shard, Apple M4 Pro
3,6 MB
yirmi bin lease için eklenen toplam bellek
Throughput: Apple M4 Pro, evented runtime, memory durability, tek shard, 50 client, pipeline 16, 64 byte değer, sabit key, 3 turun medyanı (7 Ekim 2026). Mikro ölçümdür; kendi iş yükünün yerine geçmez.

Her backend’in başına gelen hikâye

Bir cache eklersin. Sonra “aynı anda tek worker çalışsın” diye bir lock servisi gelir. API’yi korumak için rate limiter, aynı ödeme iki kez işlenmesin diye idempotency katmanı, gecikmeli işler için bir kuyruk. Her biri ayrı process, ayrı config, ayrı monitoring, ayrı hata noktası.

Önce: 5 sistem, 5 hata noktasıCacheLock servisiRate limiterIdempotency katmanıDelay queue→Sonra: tek processRuviocache · lease · semaforrate limit · idempotencychange feed · delay queuetek protokol · tek WALtek config · tek monitoringredis-cli ile bağlan

Oysa hepsi aynı iş: bir key üzerinde atomik karar vermek. Ruvio’nun fikri bu kararı zaten o key’in sahibi olan shard’da vermek. Ayrı sistem kurmak yerine komut çağırıyorsun.

Nasıl çalışıyor: her key’in tek sahibi var

Klasik çok thread’li veri depolarında birden çok thread aynı map’e dokunur ve bedelini kilitlerle öder. Ruvio paylaşımsız çalışıyor: key’ler hash slot ile shard’lara dağılıyor, bir key’i tutan shard onun tek sahibi. Parse, WAL kaydı, uygulama ve cevap aynı thread’de oluyor.

16.384 hash slot’ushard 0tek sahip threadparseWAL’a yazbelleğe uygulacevapshard 1tek sahip threadparseWAL’a yazbelleğe uygulacevapshard 2tek sahip threadparseWAL’a yazbelleğe uygulacevapshard 3tek sahip threadparseWAL’a yazbelleğe uygulacevapHot path’te kilit yok, thread atlaması yok. Ölçeklemek için kilit değil shard ekliyorsun.

Sonuç: çalışma kümesi o çekirdeğin cache’inde sıcak kalıyor, istekler thread’ler arasında zıplamıyor. Bedeli de saklamayayım: shard’lar arası iş koordinasyon ister, bu yüzden komut başına iş sınırlı tutuluyor.

Tek process’te neler var

Bunların hepsi key’in sahibi shard’da çalışır. Kullanmayan key’in ekstra bellek maliyeti sıfırdır.

🔒
Dağıtık kilit
Key başına fencing token. Süre dolsa da token sırası kaldığı yerden sürer.
LEASE / RELEASE
🚦
Semafor
Aynı anda en fazla N sahip. Çöken worker’ın slotu TTL dolunca kendiliğinden boşalır.
SEMAPHORE
📉
Kapasite sayacı
Sınırı aşan çağrı nil döner ve hiçbir şeyi değiştirmez. Geri alan DECR yok.
INCRBY … MAX
🧾
Idempotency
Aynı ödeme iki kez çalışmaz. Atomik rezervasyon, parmak izi çakışması, kayıtlı sonucu tekrar oynatma.
IDEM
⏳
Gecikmeli kuyruk
Kayıt hemen saklanır, süre dolana kadar consumer group onu görmez.
XADD … DELAY
📡
Değişiklik akışı
Shard üzerinde SET, LEASE ve RELEASE notlarını izleyen akış.
CHANGES

“Yuh” dedirten dört senaryo

1. Aynı anda en fazla 3 import job’ı

# Slot varsa token döner, doluysa nil
SEMAPHORE job:import 3 30000

# İş bitince slotu bırak
RELEASE job:import <token>

Worker çökerse slot 30 saniye sonra kendiliğinden boşalır. Temizlik job’ı yazmana gerek yok.

2. 50 koltuk, 51. satış yok

# Sınırı aşan çağrı nil döner, hiçbir şey değişmez
INCRBY seats:show-7 1 MAX 50

Önce say sonra geri al döngüsü yok. Reddedilen çağrı hiçbir iz bırakmaz.

3. Kullanıcı başına dakikada 100 istek

# Kalan hakkı döner, pencere doluysa nil
LIMIT api:user:42 100 60000

4. Ödeme isteği iki kez çalışmasın

# İlk deneme: ["acquired"]. 24 saat saklanır.
IDEM pay:order-77 BEGIN <fingerprint> <owner-token> 86400000

# İş bitince sonucu yayınla
IDEM pay:order-77 COMPLETE <fingerprint> <owner-token> <result-bytes>

# Aynı istek tekrar gelirse: ["completed", result]
IDEM pay:order-77 BEGIN <fingerprint> <owner-token> 86400000

BEGIN dört cevaptan birini verir:

acquired
İlk istek. İşi sen yapacaksın.
pending
Aynı istek zaten çalışıyor. Tekrar çalıştırma.
conflict
Aynı key, farklı içerikle geldi.
completed
Bitmiş. Kayıtlı sonucu aynen al.

Not: bu mekanizma tekrarı saklama süresi ve dayanıklılık kadar engeller; dış sistemlere giden yan etkiler kendi idempotency mekanizmanı yine ister.

Kullanmadığın özellik için ödeme yapmıyorsun

Projenin en sert kuralı bu: yeni bir özellik onu kullanmayan key’lere maliyet bindirmez. Lease, semafor, versiyon ve idempotency kaydı ancak ilgili key onları kullanınca bellek ayırır. GET, SET ve INCR sıcak yolda bu durumlara hiç bakmaz.

20 bin lease
~3,6 MB
20 bin versiyonlu 64 B string
~7,2 MB (gövde dahil)
Özelliği kullanmayan key’ler bu bedeli hiç ödemez.

Dayanıklılığı sen seçiyorsun

Durability modunu başlangıçta açıkça belirliyorsun. Bir yazma önce kontrol edilir, WAL’a eklenir, en son belleğe uygulanır. WAL kaydı alamazsa key eski halinde kalır ve sonraki yazmalar MISCONF döner.

En hızlıdan en güvenliye
memory
Saf bellek. Process kapanınca her şey gider.
snapshot
Periyodik kalıcı checkpoint.
everysec
WAL, yaklaşık saniyede bir fsync.
always
Onaylanmadan önce kalıcı.
ModKaybedilebilecek onaylı yazma
memoryTüm bellek durumu (process ömrü)
snapshotSon kalıcı checkpoint’ten sonraki yazmalar
everysecKernel’in kabul edip henüz sync etmediği yazmalar (varsayılan pencere 1000 ms)
alwaysYalnızca hiç onaylanmamış bir yazma

Dene: bir dakika

Docker ile ayağa kaldır, redis-cli ile bağlan:

docker run --rm -p 6379:6379 \
  -e RUVIO_REQUIREPASS=change-me \
  -v ruvio-data:/data \
  salihcantekin/ruvio:linux

redis-cli -h 127.0.0.1 -p 6379 -a change-me PING

Altı dilde resmi client

Hepsi tek seed adresten cluster yönlendirmesini kendisi öğreniyor, tüm 292 komut için tipli metot sunuyor.

Rust
cargo add ruvio-client
C#
dotnet add package Ruvio.Client
Node.js
npm install ruvio-client
Python
pip install ruvio-client
Java
io.github.salihcantekin:ruvio-client
Go
go get github.com/salihcantekin/ruvio/clients/go
use ruvio_client::Client;

let mut db = Client::connect("127.0.0.1", 6379)?;
db.set("session:ada", "hello")?;
let value = db.get_string("session:ada")?;
db.incr("visits")?;
using Ruvio.Client;

await using var connection = await RuvioConnection.ConnectAsync("127.0.0.1", 6379);
var db = await connection.GetDefaultDatabaseAsync();
await db.SetAsync("session:ada", "hello");
string? value = await db.GetStringAsync("session:ada");

.NET tarafında hazır entegrasyonlar

IDistributedCacheOutput caching + tag invalidationData Protection key-ringDağıtık rate limitingHTTP idempotency[RuvioLock] ile dağıtık kilit

Operatör arayüzü

Çalışan bir sunucuyu tarayıcıdan gezmek için ayrı bir arayüz imajı var: veri, komut formları, Pub/Sub ve ACL kullanıcıları. Konsol sekmesi komutları doğrudan çalıştırır, yıkıcı komutlardan önce onay ister.

Operatör arayüzü: shard sağlığı, key sayısı, bellek ve komut sayaçları
Data ekranında JSON belge ağaç olarak
Console ekranında komut çalıştırma

Dürüst olalım

Bedelsiz bir şey yok. Sahiplik modeli önemli: shard’lar arası işler koordinasyon ister, yavaş bir shard kendi üzerindeki istekleri geciktirebilir. Tam bir Redis klonu değil; modüller, Sentinel ya da listede olmayan bir komut gerekiyorsa başka bir araç doğru seçim.

Sık sorulanlar

Redis client’ım çalışır mı?
RESP2 alt kümesini konuşuyor; redis-cli ve mevcut RESP client’ları bağlanır. Desteklenen komut listesi CI’da denetlenen uyumluluk matrisinde açık.
Veriler kalıcı mı?
Sen seçiyorsun: memory, snapshot, everysec ya da always. WAL kaydı alamazsa key eski halinde kalır.
Cluster var mı?
Statik 16.384 slot var; yanlış shard’a gelen key yönlendirilir. Cluster bus, canlı slot taşıma, otomatik failover ve seçim yok. Replikasyon WAL kuyruğunu sınırlı sayıda replikaya kopyalar.
Lisansı ne?
Sunucu AGPL-3.0, resmi client’lar ve .NET entegrasyonları MIT. Değiştirilmemiş sunucuyu çalıştırmak ya da ona kendi uygulamandan bağlanmak uygulamanı AGPL altına sokmaz.
Ruvio
Denersen ve kırarsan, söyle.
Kod, doküman ve sürümler açık. Bir issue, bir yıldız ya da bir “şu komut eksik” yeter.
ruviodb.comGitHub
Created with