Deno KV ile Edge’de Anahtar-Değer Veri Saklama
Edge computing dünyasında veri saklamak her zaman bir sorun olmuştur. Geleneksel yaklaşımda bir veritabanı sunucusu kurarsın, bağlantı havuzlarını yönetirsin, latency sorunlarıyla boğuşursun. Deno Deploy üzerinde çalışırken ise işler daha da karmaşık hale gelir çünkü her şey dağıtık bir ortamda çalışıyor. İşte tam bu noktada Deno KV devreye giriyor ve “edge’de veri saklama” sorununa şaşırtıcı derecede zarif bir çözüm sunuyor.
Deno KV Nedir ve Neden Önemlidir
Deno KV, Deno runtime’ına doğrudan entegre edilmiş bir anahtar-değer veritabanıdır. Harici bir veritabanı kurmanıza, bağlantı string’leri yönetmenize ya da ayrı bir servis çalıştırmanıza gerek yoktur. Deno.openKv() diyorsunuz ve hemen kullanmaya başlıyorsunuz.
Deno Deploy üzerinde çalıştığınızda bu KV deposu otomatik olarak dağıtık hale gelir. FoundationDB üzerine inşa edilmiştir ve ACID transaction desteği sunar. Bu, şu anlama gelir: Tokyo’daki bir kullanıcı veri yazdığında Frankfurt’taki kullanıcı bunu okuyabilir, tutarlılık garantisi de beraberinde gelir.
Neden başka çözümler değil de Deno KV?
- Sıfır konfigürasyon: Kurulum, bağlantı yönetimi yok
- Strongly consistent reads: Global tutarlılık garantisi
- Atomic transactions: Birden fazla işlemi tek seferde yapabilirsiniz
- TypeScript native: Tip güvenliği kutudan çıkar çıkmaz geliyor
- Edge native: Kullanıcıya en yakın node’dan okuma yapılıyor
Yerel Geliştirme Ortamını Kurmak
Önce Deno’nun yüklü olduğundan emin olalım. Linux veya macOS kullanıyorsanız:
curl -fsSL https://deno.land/install.sh | sh
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
deno --version
Windows’ta ise:
irm https://deno.land/install.ps1 | iex
Deno KV’yi yerel ortamda test etmek için --unstable-kv flag’ini kullanmanız gerekiyor (Deno 1.x için). Deno 2.x ile bu stable hale geldi:
# Deno 1.x için
deno run --unstable-kv main.ts
# Deno 2.x için (stable)
deno run main.ts
Temel CRUD İşlemleri
Hadi direkt kodla başlayalım. KV’nin temel yapısını anlamak için basit bir örnek:
# main.ts dosyasını oluştur ve çalıştır
cat > main.ts << 'EOF'
const kv = await Deno.openKv();
// Veri yazma
await kv.set(["kullanicilar", "user_001"], {
id: "user_001",
isim: "Ahmet Yılmaz",
email: "[email protected]",
olusturulmaTarihi: new Date().toISOString(),
});
// Veri okuma
const sonuc = await kv.get(["kullanicilar", "user_001"]);
console.log("Kullanıcı bulundu:", sonuc.value);
// Veri silme
await kv.delete(["kullanicilar", "user_001"]);
const silindiktenSonra = await kv.get(["kullanicilar", "user_001"]);
console.log("Silindikten sonra:", silindiktenSonra.value); // null
kv.close();
EOF
deno run main.ts
Burada dikkat etmeniz gereken şey key yapısıdır. Deno KV’de anahtarlar dizi formatındadır. ["kullanicilar", "user_001"] şeklinde hiyerarşik bir yapı kuruyorsunuz. Bu, aslında bir namespace gibi çalışıyor.
Gerçek Dünya Senaryosu: Oturum Yönetimi
Edge uygulamalarında en sık karşılaşılan ihtiyaçlardan biri oturum (session) yönetimidir. Redis kullanmak yerine Deno KV ile bunu nasıl yapabileceğimize bakalım:
cat > session-manager.ts << 'EOF'
const kv = await Deno.openKv();
interface Session {
userId: string;
token: string;
olusturmaTarihi: string;
sonErisim: string;
ipAdresi: string;
}
// Yeni oturum oluştur
async function oturumOlustur(
userId: string,
ipAdresi: string
): Promise<string> {
const token = crypto.randomUUID();
const session: Session = {
userId,
token,
olusturmaTarihi: new Date().toISOString(),
sonErisim: new Date().toISOString(),
ipAdresi,
};
// 24 saat sonra expire olacak şekilde ayarla
await kv.set(["sessions", token], session, {
expireIn: 24 * 60 * 60 * 1000, // 24 saat ms cinsinden
});
// Kullanıcının aktif session'larını takip et
await kv.set(["user_sessions", userId, token], true, {
expireIn: 24 * 60 * 60 * 1000,
});
console.log(`Oturum oluşturuldu: ${token}`);
return token;
}
// Oturumu doğrula ve güncelle
async function oturumDogrula(token: string): Promise<Session | null> {
const result = await kv.get<Session>(["sessions", token]);
if (!result.value) {
console.log("Oturum bulunamadı veya süresi dolmuş");
return null;
}
// Son erişim zamanını güncelle (atomic)
const guncellenmisSession: Session = {
...result.value,
sonErisim: new Date().toISOString(),
};
await kv.set(["sessions", token], guncellenmisSession, {
expireIn: 24 * 60 * 60 * 1000,
});
return guncellenmisSession;
}
// Kullanıcının tüm oturumlarını listele
async function kullanicininOturumlariniListele(userId: string) {
const iter = kv.list<boolean>({ prefix: ["user_sessions", userId] });
const oturumlar: string[] = [];
for await (const entry of iter) {
// Key: ["user_sessions", userId, token]
const token = entry.key[2] as string;
oturumlar.push(token);
}
return oturumlar;
}
// Test et
const token = await oturumOlustur("user_001", "192.168.1.1");
const session = await oturumDogrula(token);
console.log("Doğrulanan oturum:", session);
const aktifOturumlar = await kullanicininOturumlariniListele("user_001");
console.log("Aktif oturumlar:", aktifOturumlar);
kv.close();
EOF
deno run session-manager.ts
Atomic Transactions ile Güvenli İşlemler
Deno KV’nin en güçlü özelliklerinden biri atomic transaction desteğidir. Bir e-ticaret uygulamasında stok yönetimini düşünelim:
cat > stok-yonetimi.ts << 'EOF'
const kv = await Deno.openKv();
interface Urun {
id: string;
ad: string;
stok: number;
fiyat: number;
}
// Başlangıç stoklarını ayarla
await kv.set(["urunler", "laptop_001"], {
id: "laptop_001",
ad: "Gaming Laptop",
stok: 5,
fiyat: 25000,
} as Urun);
// Stok güvenli azaltma - race condition olmadan
async function stokAzalt(urunId: string, miktar: number): Promise<boolean> {
// 3 deneme hakkı ver (optimistic locking pattern)
for (let deneme = 0; deneme < 3; deneme++) {
const urunEntry = await kv.get<Urun>(["urunler", urunId]);
if (!urunEntry.value) {
console.log("Ürün bulunamadı");
return false;
}
if (urunEntry.value.stok < miktar) {
console.log(`Yetersiz stok. Mevcut: ${urunEntry.value.stok}`);
return false;
}
const yeniStok = urunEntry.value.stok - miktar;
// Atomic işlem: sadece veri değişmediyse güncelle
const islem = await kv.atomic()
.check(urunEntry) // Bu versiyon hala aynı mı?
.set(["urunler", urunId], {
...urunEntry.value,
stok: yeniStok,
})
.set(["stok_hareketleri", crypto.randomUUID()], {
urunId,
miktar: -miktar,
zaman: new Date().toISOString(),
kalanStok: yeniStok,
})
.commit();
if (islem.ok) {
console.log(`Stok güncellendi. Yeni stok: ${yeniStok}`);
return true;
}
console.log(`Çakışma tespit edildi, tekrar deneniyor... (${deneme + 1})`);
}
console.log("İşlem başarısız: Çok fazla çakışma");
return false;
}
const basarili = await stokAzalt("laptop_001", 2);
console.log("Satın alma başarılı mı:", basarili);
const guncelUrun = await kv.get<Urun>(["urunler", "laptop_001"]);
console.log("Güncel ürün bilgisi:", guncelUrun.value);
kv.close();
EOF
deno run stok-yonetimi.ts
HTTP API ile KV Entegrasyonu
Şimdi bunu gerçek bir HTTP API’ye dönüştürelim. Deno Deploy’a deploy edeceğimiz bir uygulama:
cat > api-server.ts << 'EOF'
const kv = await Deno.openKv();
interface Blog {
id: string;
baslik: string;
icerik: string;
yazar: string;
olusturmaTarihi: string;
etiketler: string[];
}
async function handleRequest(req: Request): Promise<Response> {
const url = new URL(req.url);
const headers = { "Content-Type": "application/json" };
// GET /blog - Tüm yazıları listele
if (req.method === "GET" && url.pathname === "/blog") {
const yazilar: Blog[] = [];
const iter = kv.list<Blog>({ prefix: ["blog_yazilari"] });
for await (const entry of iter) {
if (entry.value) yazilar.push(entry.value);
}
// Tarihe göre sırala
yazilar.sort((a, b) =>
new Date(b.olusturmaTarihi).getTime() -
new Date(a.olusturmaTarihi).getTime()
);
return new Response(JSON.stringify(yazilar), { headers });
}
// POST /blog - Yeni yazı oluştur
if (req.method === "POST" && url.pathname === "/blog") {
const body = await req.json();
const id = crypto.randomUUID();
const yeniYazi: Blog = {
id,
baslik: body.baslik,
icerik: body.icerik,
yazar: body.yazar || "Anonim",
olusturmaTarihi: new Date().toISOString(),
etiketler: body.etiketler || [],
};
await kv.set(["blog_yazilari", id], yeniYazi);
// Etiket indeksi oluştur
for (const etiket of yeniYazi.etiketler) {
await kv.set(["etiket_indeksi", etiket, id], true);
}
return new Response(JSON.stringify(yeniYazi), {
status: 201,
headers,
});
}
// GET /blog/:id - Tek yazı getir
const blogMatch = url.pathname.match(/^/blog/([a-z0-9-]+)$/);
if (req.method === "GET" && blogMatch) {
const id = blogMatch[1];
const entry = await kv.get<Blog>(["blog_yazilari", id]);
if (!entry.value) {
return new Response(JSON.stringify({ hata: "Yazı bulunamadı" }), {
status: 404,
headers,
});
}
return new Response(JSON.stringify(entry.value), { headers });
}
return new Response(JSON.stringify({ hata: "Endpoint bulunamadı" }), {
status: 404,
headers,
});
}
console.log("Server başlatıldı: http://localhost:8000");
Deno.serve({ port: 8000 }, handleRequest);
EOF
# Çalıştır ve test et
deno run --allow-net api-server.ts &
# Yeni yazı ekle
curl -X POST http://localhost:8000/blog
-H "Content-Type: application/json"
-d '{"baslik":"Deno KV ile Edge Veri Saklama","icerik":"Harika bir konu...","yazar":"Mehmet","etiketler":["deno","edge","kv"]}'
# Tüm yazıları listele
curl http://localhost:8000/blog
Rate Limiting ile Edge Koruması
Edge uygulamalarında kötüye kullanımı engellemek kritik önem taşır. KV ile basit ama etkili bir rate limiter yazalım:
cat > rate-limiter.ts << 'EOF'
const kv = await Deno.openKv();
interface RateLimitDurumu {
istek_sayisi: number;
pencere_baslangici: number;
}
async function rateLimitKontrol(
ipAdresi: string,
limitSayisi: number = 100,
pencereSuresiMs: number = 60000 // 1 dakika
): Promise<{ izinVerildi: boolean; kalanIstek: number; resetZamani: number }> {
const simdi = Date.now();
const anahtar = ["rate_limit", ipAdresi];
const mevcut = await kv.get<RateLimitDurumu>(anahtar);
// İlk istek ya da pencere dolmuş
if (
!mevcut.value ||
simdi - mevcut.value.pencere_baslangici > pencereSuresiMs
) {
await kv.set(anahtar, {
istek_sayisi: 1,
pencere_baslangici: simdi,
} as RateLimitDurumu, {
expireIn: pencereSuresiMs,
});
return {
izinVerildi: true,
kalanIstek: limitSayisi - 1,
resetZamani: simdi + pencereSuresiMs,
};
}
// Limit aşıldı mı kontrol et
if (mevcut.value.istek_sayisi >= limitSayisi) {
const resetZamani = mevcut.value.pencere_baslangici + pencereSuresiMs;
return {
izinVerildi: false,
kalanIstek: 0,
resetZamani,
};
}
// Sayacı artır (atomic)
await kv.atomic()
.check(mevcut)
.set(anahtar, {
istek_sayisi: mevcut.value.istek_sayisi + 1,
pencere_baslangici: mevcut.value.pencere_baslangici,
} as RateLimitDurumu, {
expireIn: pencereSuresiMs,
})
.commit();
return {
izinVerildi: true,
kalanIstek: limitSayisi - (mevcut.value.istek_sayisi + 1),
resetZamani: mevcut.value.pencere_baslangici + pencereSuresiMs,
};
}
// Rate limiter middleware olarak kullanalım
async function handleRequest(req: Request): Promise<Response> {
const ip = req.headers.get("x-forwarded-for") || "127.0.0.1";
const rl = await rateLimitKontrol(ip, 10, 60000); // Dakikada 10 istek
if (!rl.izinVerildi) {
return new Response("Çok fazla istek gönderildi", {
status: 429,
headers: {
"Retry-After": String(Math.ceil((rl.resetZamani - Date.now()) / 1000)),
"X-RateLimit-Remaining": "0",
},
});
}
return new Response(`İstek işlendi. Kalan hak: ${rl.kalanIstek}`, {
headers: {
"X-RateLimit-Remaining": String(rl.kalanIstek),
"X-RateLimit-Reset": String(rl.resetZamani),
},
});
}
Deno.serve({ port: 8001 }, handleRequest);
EOF
deno run --allow-net rate-limiter.ts
Deno Deploy’a Deploy Etmek
Projenizi Deno Deploy’a almak için deployctl kullanıyoruz:
# deployctl kurulumu
deno install -gArf jsr:@deno/deployctl
# GitHub'a push ettikten sonra ya da direkt deploy
deployctl deploy --project=benim-kv-uygulamam api-server.ts
# Environment variable ile özel KV bağlantısı
# Deno Deploy dashboard'dan KV veritabanı oluşturun
# Otomatik olarak bağlı gelir, herhangi bir config gerekmez
Deno Deploy’da KV kullanımında dikkat etmeniz gerekenler:
- Ücretsiz plan: Günde 1 GB okuma, 1 GB yazma limiti var
- Veri boyutu: Tek bir value maksimum 64 KB olabilir
- Key boyutu: Maksimum 2 KB
- Batch işlemler:
getManyile aynı anda birden fazla key okuyabilirsiniz
cat > batch-okuma.ts << 'EOF'
const kv = await Deno.openKv();
// Önce test verisi ekle
await kv.set(["urunler", "001"], { ad: "Laptop", fiyat: 25000 });
await kv.set(["urunler", "002"], { ad: "Telefon", fiyat: 15000 });
await kv.set(["urunler", "003"], { ad: "Tablet", fiyat: 8000 });
// Tek seferde birden fazla oku (N+1 problemini çözer)
const urunler = await kv.getMany([
["urunler", "001"],
["urunler", "002"],
["urunler", "003"],
]);
console.log("Toplu okuma sonuçları:");
urunler.forEach((entry, index) => {
console.log(` Ürün ${index + 1}:`, entry.value);
});
// Prefix ile listeleme ve filtreleme
console.log("nTüm ürünleri listele:");
const iter = kv.list({ prefix: ["urunler"] }, { limit: 10 });
for await (const entry of iter) {
console.log(" -", entry.key, "->", entry.value);
}
kv.close();
EOF
deno run batch-okuma.ts
Güvenlik ve En İyi Pratikler
Deno KV kullanırken dikkat etmeniz gereken bazı kritik noktalar var:
Key tasarımı önemlidir. Hiyerarşik key yapısını doğru kurmak, list() operasyonlarının verimliliğini doğrudan etkiler. Kötü tasarlanmış key yapısı tüm veriyi taramak zorunda kalmanıza yol açar.
Value boyutlarını kontrol altında tutun. 64 KB limiti var diye büyük blob’lar saklamayın. Büyük dosyalar için Deno Deploy’ın object storage servisini ya da Cloudflare R2 gibi bir çözümü tercih edin.
Expire süresini akıllıca kullanın. Geçici veriler için her zaman expireIn belirtin. Aksi takdirde KV dolduktan sonra manuel temizlik yapmak zorunda kalırsınız.
Transaction’lara güvenin. Birden fazla key’i güncellerken mutlaka atomic() kullanın. Race condition’lar edge ortamında çok daha sık karşınıza çıkar çünkü aynı anda onlarca farklı region’dan istek gelebilir.
İzleme ekleyin. KV işlemlerini loglamak için basit bir wrapper yazın:
cat > kv-wrapper.ts << 'EOF'
async function kvIslemIzle<T>(
islemAdi: string,
islem: () => Promise<T>
): Promise<T> {
const baslangic = performance.now();
try {
const sonuc = await islem();
const sure = performance.now() - baslangic;
console.log(`[KV] ${islemAdi} - ${sure.toFixed(2)}ms - BASARILI`);
return sonuc;
} catch (hata) {
const sure = performance.now() - baslangic;
console.error(`[KV] ${islemAdi} - ${sure.toFixed(2)}ms - HATA:`, hata);
throw hata;
}
}
// Kullanım örneği
const kv = await Deno.openKv();
await kvIslemIzle("kullanici_kaydet", () =>
kv.set(["kullanicilar", "test"], { ad: "Test Kullanıcı" })
);
const kullanici = await kvIslemIzle("kullanici_oku", () =>
kv.get(["kullanicilar", "test"])
);
console.log("Kullanıcı:", kullanici.value);
kv.close();
EOF
deno run kv-wrapper.ts
Sonuç
Deno KV, edge computing dünyasında veri saklama sorununa gerçekten temiz bir çözüm getiriyor. Harici veritabanı bağlantısı yok, connection pool yönetimi yok, karmaşık infrastructure kurulumu yok. Sadece Deno.openKv() diyorsunuz ve çalışmaya başlıyorsunuz.
Özellikle Deno Deploy ile birlikte kullandığınızda, kodunuz otomatik olarak kullanıcıya en yakın edge node’da çalışıyor ve KV operasyonları da bu node’dan gerçekleşiyor. Bu, geleneksel merkezi veritabanı yaklaşımına kıyasla dramatik latency düşüşleri anlamına geliyor.
Tabii her teknolojinin sınırları var. Deno KV karmaşık ilişkisel sorgular gerektiren uygulamalar için doğru seçim değil. JOIN, GROUP BY gibi SQL operasyonlarına ihtiyacınız varsa Postgres ile devam edin. Ama oturum yönetimi, rate limiting, önbellekleme, feature flags, kullanıcı tercihleri gibi konularda Deno KV mükemmel bir araç.
Sysadmin perspektifinden baktığımızda, Deno KV’nin en büyük avantajı bakım yükünü neredeyse sıfıra indirmesi. Bir Redis instance’ı ayakta tutmak, yedeklerini almak, failover mekanizmalarını kurmak yerine sadece kodunuza odaklanıyorsunuz. Edge computing ile serverless’ın bu kadar iç içe geçtiği bir dönemde bu basitlik gerçekten büyük bir avantaj.
