Önbelleğinizi ve veritabanınızı her yazmada mükemmel uyumda tutmanın yolları.
Lazy caching konusunda bilgi sahibiyseniz, Cache-Aside pattern makalesini okumayı öneririm: sadece veriler talep edildiğinde önbelleği doldurun. Write-Through ise bu felsefeyi tersine çeviriyor.
Write-Through, her yazma işleminde verileri önbelleğe ve veritabanına aynı anda yazar. O yüzden önbelleğiniz asla güncel olmaktan çıkar; daima gerçek kaynağı günceller.
Detaylara inelim.
Temel Fikir
Temel Fikir
Write-Through, basit bir kurala sahiptir: veritabanına yazmaya karar verdiğinizde, öncelikle önbelleğe de yazmalısınız.
- Yazma → Önce önbelleği güncelleyin, sonra veritabanına yazın.
- Okuma → Önbellek her zaman sıcak. Sadece buradan okuyun.
Geçersiz kılma yok. Eski veriler penceresi yok. Yakın zamanda yazılan verilerde soğuk erişim yok.
Yazma Akışı
Yazma Akışı
Client → Write to Cache
│
└──→ Write to Database ✅
│
Both updated atomically
Bunu Cache-Aside ile karşılaştırın:
Client → Write to Database
│
└──→ Invalidate Cache key
│
Next read triggers a miss
Write-Through istekli: önbellek her zaman hazır.
Kodu Göster
Kodu Göster
Aşağıda Laravel’de Cache facade ve Eloquent kullanarak pratik bir örnek bulunmaktadır:
id}", $user, self::CACHE_TTL);
return $user;
}
public function updateUser(int $userId, array $updates): ?User
{
$user = User::find($userId);
if (!$user) {
return null;
}
// 1. Veritabanını güncelle.
$user->update($updates);
$user->refresh();
// 2. Güncellenen modeli derhal önbelleğe yazın - geçersiz veri penceresi yok.
Cache::put("user:{$userId}", $user, self::CACHE_TTL);
return $user;
}
public function deleteUser(int $userId): void
{
User::destroy($userId);
// Silinince önbellekten çıkar.
Cache::forget("user:{$userId}");
}
}Artık okumalar son derece basit. Herhangi bir yazma işleminden sonra önbellek ısınmış olduğundan remember() ile yedekleme gerekmiyor.
İleriye Taşımak: Özel Bir Repository
İleriye Taşımak: Özel Bir Repository
Daha temiz bir mimari için, Write-Through mantığını bir repository’de kapsülleyin böylece servis katmanı önbellekle hiç düşünmek zorunda kalmaz:
key($id)) ?? User::find($id);
}
public function save(User $user): User
{
$user->save();
Cache::put($this->key($user->id), $user->fresh(), self::TTL);
return $user;
}
public function delete(int $id): void
{
User::destroy($id);
Cache::forget($this->key($id));
}
}Artık controller’larınız ve servisleriniz sadece $this->users->save($user) demektedir ve önbellekleme tamamen görünmez hale gelmiştir.
Neden Sadece Cache-Aside Kullanmayalım?
Neden Sadece Cache-Aside Kullanmayalım?
İyi bir soru. Her iki model farklı problemleri çözüyor:
| Cache-Aside | Write-Through | |
|---|---|---|
| Önbellek oluşturma | Lazy (ilk okuma anında) | Eager (her yazmada) |
| Eski veri riski | Evet, yazılardan hemen sonra kısaca | Hayır |
| Soğuk başlangıç sorunu | Evet | Hayır – yazmalar her zaman önbelleği ısıtır |
| Boşa harcanan önbellek alanı | Düşük (sadece sıcak veriler önbelleğe alınır) | Daha yüksek (yazılan tüm veriler önbelleğe alınır) |
| Yazma karmaşıklığı | Basit | Biraz daha yüksek |
| En iyi senaryolar | Okuma ağırlıklı, düzensiz erişim | Yazma ağırlıklı, tutarlılık kritik |
Write-Through, geçersiz okumaya tahammül edemediğinizde öne çıkar – kullanıcı profilleri, fiyatlandırma verileri, envanter sayımları, güncel verilere ulaşmanın gerçek bir maliyeti olduğu her şey.
Write-Through’un Parladığı Anlar ✨
Write-Through’un Parladığı Anlar ✨
| Senaryo | Neden işe yarar |
|---|---|
| Tutarlılık kritik veriler | Önbellek her zaman DB ile senkronize |
| Yazma-sonrası-okuma desenleri | Veri yaratıldıktan hemen sonra sıcak olur |
| Oturum veya profil verileri | Sık sık güncellenir, sürekli okunur |
| Yüksek trafik okumaları sonrasında yazmalar | İlk okuma üzerinde thundering herd yok |
Ticari Değişimlerden Kaçınmak
Ticari Değişimlerden Kaçınmak
Her model mükemmel değildir. Write-Through’un kendine özgü dezavantajları vardır:
1. Yazma Gecikmesi
Her yazma artık iki sistemi etkiliyor. Eğer Redis yavaşsa veya kullanılamıyorsa, yazma yolu etkilenir. Bu duruma uygun tasarım yapın ve bir yazma hatası veritabanı yazmasını geri almalıdır, bunu düşünün.
public function updateUser(int $userId, array $updates): ?User
{
return DB::transaction(function () use ($userId, $updates) {
$user = User::lockForUpdate()->find($userId);
$user->update($updates);
$user->refresh();
// Yazma işlemini transaction sınırları içinde önbelleğe yazın.
Cache::put("user:{$userId}", $user, self::CACHE_TTL);
return $user;
});
}2. Önbellek Şişmesi
Cache-Aside’dan farklı olarak, Write-Through her yazdığınız şeyi önbelleğe alır. Hiçbir zaman okunmayacak kayıtlar bile. Eğer yüksek hacimli yazma yüklerine sahipseniz ve az okumalar yapıyorsanız, bellek israfı yapıyorsunuz.
Çözüm: Daha kısa TTL’ler kullanın veya Write-Through’u yalnızca en çok okunan kaynaklara seçici olarak uygulayın.
3. Başlangıçta Soğuk Önbellek
Write-Through yalnızca yazmalarda önbelleği doldurur. Eğer boş bir önbellekle yeniden başlatır ve hiç yazma işlemi gerçekleşmezse, okumalar yine de geçersiz olacaktır. Var olan verilere için bir ısıtma işlemi düşünün:
// Bir seeder'da, zamanlanmış bir komut veya kuyruk işinde
User::chunk(200, function ($users) {
foreach ($users as $user) {
Cache::put("user:{$user->id}", $user, 3600);
}
});4. Hatalarda Tutarlılık
Veritabanı yazılması başarısız olsa ancak önbellek yazılması başarılı olursa (veya vice versa), bir split-brain problemi yaşarsınız. İkisini bir transaction’a yerleştirmek DB tarafı için yardımcı olur, ancak Redis’in yerel transaction geri alma işlemi yoktur. İzleme kullanın ve bir yedekleme okuma yolu belirleyin.
Write-Through vs. Diğer Modeller
Write-Through vs. Diğer Modeller
| Model | Kim önbelleği yönetir | Veri ne zaman yüklenir | Eski risk |
|---|---|---|---|
| Cache-Aside | Uygulama | İlk okuma anında (lazy) | Evet, kısaca |
| Read-Through | Önbellek katmanı | İlk okuma anında (lazy) | Evet, kısaca |
| Write-Through | Uygulama | Her yazmada (eager) | Hayır |
| Write-Behind | Önbellek katmanı | Yazmadan sonra asenkron | Evet, kısaca |
Gerçek Dünya Mimarisi
Gerçek Dünya Mimarisi
WRITE PATH
┌─────────┐ update ┌─────────────┐
│ Client │ ───────────▶│ App Layer │
└─────────┘ └──────┬──────┘
│ write to both
┌───────────────┴───────────────┐
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ Cache (Redis) │ │ Database (MySQL) │
│ always current │ │ source of truth │
└────────────────────┘ └────────────────────┘
READ PATH
┌─────────┐ read ┌─────────────┐
│ Client │ ───────────▶│ App Layer │
└─────────┘ └──────┬──────┘
│
┌────────────▼────────────┐
│ Cache (Redis) │
│ almost always a HIT │
└─────────────────────────┘
Okuma yolu neredeyse basit hale gelir. Her istekte öncelikle Redis’ten hizmet ediyorsunuz.
Ana Noktalar
Ana Noktalar
- Write-Through isteklidir: önbellek her yazmada güncellenir, ilk okumada değil.
- Geçersiz pencereler yoktur: yazmalardan sonra yapılan okumalar her zaman taze verileri alır.
- Yazma gecikmesi bir ticari değişimdir: her yazmada iki sistem etkilendiği için buna göre tasarlayın.
- TTL ile birleştirin: “daima taze” önbelleklerin bile sonunda sona ermesi gerekmektedir.
- Cache-Aside ile en iyi şekilde birleştirin: Write-Through’u en kritik, sık okunan veriler için kullanın, diğer her şey için Cache-Aside’ı tercih edin.
Kapanış
Kapanış
Write-Through, tutarlılığın öncelikli olduğu durumlarda doğru bir araçtır. Kullanıcılarınız değişikliklerini hemen görmek bekliyorsa – ve genellikle böyle olurlar – Write-Through bu garantiyi sağlamak için etkili bir modeldir.
Yazma yolunuzdan biraz daha fazla şey talep eder, ancak karşılığında değerli bir şey sunar: güvenilir önbellek.
Bir önbellekleme stratejisi oluşturmak ve hangi modelin nerede kullanılacağı konusunda belirsizlik yaşıyorsanız, yorum bırakın – her zaman değişim hakkında konuşmaktan mutluluk duyarım.
Kaynak: Orijinal Makale


