# Bakiye güncellemede race condition: Pessimistic Lock mı, Redlock mı?

> Parayı DB'nin garantisinde tutun: atomik koşullu `UPDATE ... WHERE balance >= 40` ya da `SELECT ... FOR UPDATE` kullanın, Redlock'u DB dışına bırakın.

- Soruldu: 2026-06-05
- Yanıtlandı: 2026-06-08
- Soran: Polat
- Etiketler: veritabani, queue, redis
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/bakiye-guncellemede-race-condition-pessimistic-lock-mu-redlock-mu/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Kullanıcı bakiyesinden para düşen bir kuyruk işimiz var. Kullanıcı hızlı arka arkaya iki işlem tetiklerse kuyrukta aynı kullanıcı için iki job oluşuyor; 10 paralel worker var. Worker 1 ve 2 aynı anda eski bakiyeyi okuyor (100 TL), ikisi de 40 düşüp 60 TL yazıyor (olması gereken 20 TL).

Bu race condition'ı önlemek için Pessimistic Locking mi yoksa Redis dağıtık kilit (Redlock) mı kullanmalıyım?


Kısa cevap: Yaşadığın şey klasik **lost update** (kayıp güncelleme) problemi. Para söz konusuysa invariant'ı DB'de tut; en temiz çözüm atomik koşullu bir `UPDATE`, ikinci tercih `SELECT ... FOR UPDATE`. Redlock'a koşma.

Senaryonun özü: oku-değiştir-yaz (read-modify-write) döngüsü paralel worker'lar arasında bölünüyor. İkisi de eski değeri okuyor, ikisi de üstüne yazıyor; biri kayboluyor. Çözüm bu döngüyü atomik kılmak.

1. **En iyisi: atomik koşullu `UPDATE`.** `UPDATE accounts SET balance = balance - 40 WHERE id = ? AND balance >= 40` yaz. Okuma ve yazma tek ifadede; uygulama tarafında kilit gerekmez. DB satır kilidini kendi yönetir, hem race'i çözer hem de `balance >= 40` koşuluyla eksiye düşmeyi reddeder. Etkilenen satır 0 ise işlem yetersiz bakiyeden başarısız demektir.
2. **Alternatif: `SELECT ... FOR UPDATE`.** Mantık okuma ile yazma arasında karmaşıksa, transaction içinde bakiye satırını `FOR UPDATE` ile kilitle. İkinci worker o satıra geldiğinde birincinin commit'ini bekler; sıraya girerler, çakışma olmaz. Pessimistic locking budur.
3. **Düşük çakışmada optimistic locking de iş görür.** Satıra bir `version` kolonu ekle; güncellerken `WHERE version = ?` koy, eşleşmezse retry et. Çakışma seyrekse ucuzdur, ama yoğun rekabette retry fırtınası yaratır.
4. **Redlock'u yalnızca DB dışı kaynak için kullan.** Redis dağıtık kilidi (Redlock), korunan şey tek bir DB satırı *olmadığında* (servisler/kaynaklar arası koordinasyon) anlamlıdır — ve clock kayması/timeout gibi köşe durumları vardır. Bir bakiye DB'nin transaction'ıyla zaten korunabiliyorken onu dağıtık bir kilide emanet etme.

**Sonuç:** Atomik koşullu `UPDATE` (ya da `SELECT FOR UPDATE`) kullan; Redlock'u sadece DB dışı kaynaklar için sakla. Para için kuralı transaction'ın garanti ettiği yerde — DB'de — tut; dağıtık kilit, veritabanının kendisinin zaten zorlayabileceği bir invariant'ı korumak için fazla kırılgan bir araçtır.

## İlgili Yazılar

- [Laravel Queue ve Supervisor ile Asenkron İşlemler](/blog/laravel-queue-ve-supervisor-ile-asenkron-islemler/) — Blog
- [Redis'te canlı leaderboard için neden Sorted Set kullanmalıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/redis-veri-tipleri-leaderboard-icin-sorted-set/) — Sor Bakalım
- [Correlation ID'yi HTTP isteğinden kuyruğa düşen job'lara kadar nasıl taşımalıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/correlation-idyi-http-isteginden-kuyruga-dusen-joblara-kadar-nasil-tasimaliyim/) — Sor Bakalım
- [Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/) — Sor Bakalım
