CRUD uygulamalarındaki yarış durumları çift siparişlere ve yanlış toplamlara yol açabilir. Ortak çarpışma noktalarını ve kısıtlar, kilitler ve UI korumalarıyla pratik çözümleri öğrenin.

Yarış durumu, iki (veya daha fazla) isteğin neredeyse aynı anda aynı veriyi güncellemesi ve nihai sonucun zamanlamaya bağlı olması durumunda oluşur. Her istek kendi başına doğru görünür. Birlikte yanlış bir sonuç üretirler.
Basit bir örnek: iki kişi aynı müşteri kaydında bir saniye içinde Kaydet'e basar. Biri e-postayı günceller, diğeri telefon numarasını. Eğer her iki istek de tam kaydı gönderiyorsa, ikinci yazma ilkini üzerine yazabilir ve bir değişiklik hata vermeden kaybolur.
Bunu hızlı uygulamalarda daha çok görürsünüz çünkü kullanıcılar dakikada daha fazla eylem tetikleyebilir. Ayrıca yoğun anlarda artar: flash sale'ler, ay sonu raporlaması, büyük bir e-posta kampanyası ya da aynı satırlara birikmiş isteklerin çarpması.
Kullanıcılar nadiren "yarış durumu" diye şikayet eder. Belirtileri bildirirler: çift siparişler veya yorumlar, kaybolan güncellemeler ("Kaydettim ama geri gitti"), garip toplamlar (stok negatif oldu, sayaçlar geriye sıçradı) veya beklenmedik şekilde değişen durumlar (onaylandı, sonra tekrar beklemede oldu).
Yeniden denemeler işi kötüleştirir. İnsanlar çift tıklar, yavaş bir yanıttan sonra sayfayı yeniler, iki sekmeden gönderir veya kararsız ağ nedeniyle tarayıcılar ve mobil uygulamalar isteği yeniden gönderebilir. Sunucu her isteği yeni bir yazma olarak ele alırsa iki create, iki ödeme ya da bir kez olması gereken iki durum değişikliği alabilirsiniz.
Çoğu CRUD uygulaması basit görünür: bir satırı oku, bir alanı değiştir, kaydet. Sorun, uygulamanızın zamanlamayı kontrol etmemesidir. Veritabanı, ağ, yeniden denemeler, arka plan işleri ve kullanıcı davranışı hepsi üst üste biner.
Yaygın tetikleyicilerden biri iki kişinin aynı kaydı düzenlemesidir. İkisi de aynı "güncel" değerleri yükler, ikisi de geçerli değişiklikler yapar ve son kaydetme ilkini sessizce üzerine yazar. Kimse yanlış yapmadı ama bir güncelleme kayboldu.
Tek bir kişiyle de olabilir. Kaydet düğmesine çift tıklama, geri/ileri gezinme ya da yavaş bir bağlantı kişinin tekrar Gönder'e basmasına neden olabilir ve aynı yazma iki kez gönderilebilir. Uç nokta idempotent değilse çoğaltmalar, iki kez ücretlendirme veya bir durumun iki adım ilerlemesi görebilirsiniz.
Modern kullanım daha fazla üst üste binme getirir. Aynı hesaba girişli birden fazla sekme veya cihaz çakışan güncellemeler gönderebilir. E-postalar, faturalama, senkronizasyon, temizlik gibi arka plan işleri aynı satırlara dokunabilir. İstemci, yük dengeleme veya iş çalıştırıcıdaki otomatik yeniden denemeler zaten başarıyla gerçekleşen bir isteği tekrar edebilir.
Hızla özellik gönderiyorsanız, aynı kayıt herkesin hatırladığından daha fazla yerden güncellenir. Koder.ai gibi sohbet tabanlı bir üretici kullanıyorsanız uygulama daha da hızlı büyüyebilir; bu yüzden eşzamanlılığı bir kenar durumu değil normal davranış olarak ele almakta fayda var.
Yarış durumları nadiren "kayıt oluşturma" demolarında çıkar. İki isteğin aynı gerçeğe neredeyse aynı anda dokunduğu yerlerde ortaya çıkarlar. Yaygın sıcak noktaları bilmek, yazmaları baştan güvenli tasarlamanıza yardımcı olur.
"Sadece 1 ekle" gibi görünen her şey yük altında bozulabilir: beğeniler, görüntüleme sayıları, toplamlar, fatura numaraları, bilet numaraları. Riskli desen değeri oku, ekle, sonra geri yazmaktır. İki istek aynı başlangıç değerini okuyup birbirlerini üzerine yazabilir.
Taslak -> Gönderildi -> Onaylandı -> Ödendi gibi iş akışları basit görünür, ama çarpışmalar yaygındır. Sorun, iki eylem aynı anda mümkün olduğunda başlar (onayla ve düzenle, iptal et ve öde gibi). Koruma olmazsa bir kayıt adımları atlayabilir, geri dönebilir veya farklı tablolarda farklı durumlar gösterebilir.
Durum değişikliklerini bir sözleşme gibi ele alın: yalnızca bir sonraki geçerli adıma izin verin ve diğerlerini reddedin.
Kalan koltuklar, stok sayıları, randevu slotları ve "kalan kapasite" alanları klasik aşırı satış sorununu yaratır. İki alıcı aynı anda ödeme yaparken her ikisi de müsaitlik görür ve her ikisi de başarılı olur. Eğer veritabanı son hakem değilse, sonunda sahip olduğunuzdan daha fazlasını satarsınız.
Bazı kurallar mutlak: hesap başına bir e-posta, kullanıcı başına bir aktif abonelik, kullanıcı başına bir açık sepet. Bunlar genellikle önce kontrol ettiğinizde başarısız olur ("var mı?") sonra insert yaparsınız. Eşzamanlılık altında her iki istek de kontrolü geçebilir.
CRUD akışlarını hızlı yazıyorsanız (örneğin Koder.ai ile sohbet ederek uygulama oluşturuyorsanız), bu sıcak noktaları erken not edin ve sadece UI kontrolleriyle değil, kısıtlar ve güvenli yazmalarla destekleyin.
Birçok yarış durumu sıkıcı bir şeyle başlar: aynı eylem iki kez gönderilir. Kullanıcılar çift tıklar. Ağ yavaş olduğunda tekrar tıklarlar. Bir telefon iki dokunuş kaydedebilir. Bazen kasıtlı bile değildir: bir POST'tan sonra sayfa yenilenir ve tarayıcı yeniden gönderme teklif eder.
Böyle olunca backend paralel iki create veya update çalıştırabilir. Her ikisi de başarılı olursa çoğaltmalar, yanlış toplamlar veya örneğin onay+onay gibi bir durum değişikliği iki kez çalışabilir. Rastgele görünür çünkü zamanlamaya bağlıdır.
En güvenli yaklaşım derinlemesine savunmadır. UI'yı düzeltin, ama UI'nın başarısız olacağını varsayın.
Çoğu yazma akışına uygulayabileceğiniz pratik değişiklikler:
Örnek: bir kullanıcı mobilde "Faturayı Öde"ye iki kez basıyor. UI ikinci dokunuşu engellemeli. Sunucu da aynı idempotency key'i gördüğünde ikinci isteği reddetmeli ve ilk başarılı sonucu geri döndürmeli, iki kez ücretlendirme olmamalı.
Durum alanları basit hissedilir ama iki şey aynı anda onları değiştirmeye çalıştığında karmaşıklaşır. Bir kullanıcı Onayla'ya basarken otomatik bir iş aynı kaydı Süresiz olarak işaretleyebilir ya da iki ekip üyesi aynı öğeyi farklı sekmelerde işler. Her iki güncelleme de başarılı olabilir, ama nihai durum kurallarınıza değil zamanlamaya bağlı olur.
Durumu küçük bir durum makinesi gibi ele alın. İzin verilen hareketlerin kısa bir tablosunu tutun (örneğin: Taslak -> Gönderildi -> Onaylandı ve Gönderildi -> Reddedildi). Sonra her yazma şunu kontrol etsin: "Bu geçiş mevcut durumdan izinli mi?" Değilse sessizce overwrite etmek yerine reddedin.
İyimser kilitleme, diğer kullanıcıları engellemeden eski güncellemeleri yakalamanıza yardımcı olur. Bir sürüm numarası (veya updated_at) ekleyin ve kaydederken bunun eşleşmesini zorunlu kılın. Eğer başka biri sizin yükledikten sonra satırı değiştirdiyse, güncellemeniz sıfır satırı etkiler ve net bir mesaj gösterebilirsiniz: "Bu öğe değişti, yenileyip tekrar deneyin."
Durum güncellemeleri için basit bir desen:
Ayrıca durum değişikliklerini tek bir yerde tutun. Eğer güncellemeler ekranlara, arka plan işlerine ve webhook'lara yayılmışsa kuralı kaçırırsınız. Her seferinde aynı geçiş kontrollerini uygulayan tek bir fonksiyon veya endpoint arkasına koyun.
En yaygın sayaç hatası zararsız görünür: uygulama bir değeri okur, 1 ekler, sonra geri yazar. Yük altında iki istek aynı sayıyı okuyabilir ve her ikisi de aynı yeni sayıyı yazar, böylece bir artış kaybolur. Bu, testte "genellikle çalıştığı" için kolayca gözden kaçırılır.
Bir değer sadece arttırılıyor veya azaltılıyorsa, veritabanının tek ifadede yapmasına izin verin. Böylece veritabanı birden çok istek aynı anda geldiğinde değişiklikleri güvenli şekilde uygular.
UPDATE posts
SET like_count = like_count + 1
WHERE id = $1;
Aynı fikir stok, görüntüleme sayıları, yeniden deneme sayaçları ve "yeni = eski + delta" şeklinde ifade edilebilen her şey için geçerlidir.
Toplamlar genellikle türetilmiş bir sayıyı (order_total, account_balance, project_hours) sakladığınızda yanlış olur ve birden fazla yerden güncellenir. Toplamı kaynak satırlardan (kalemler, defter girdileri) hesaplayabiliyorsanız birçok sürüklenme hatasından kaçınırsınız.
Hızı için toplam saklamanız gerekiyorsa, bunu kritik bir yazma gibi ele alın. Kaynak satırların güncellenmesini ve saklanan toplamı aynı işlemde tutun. Aynı toplamı aynı anda sadece bir yazarın güncelleyebildiğinden emin olun (kilitleme, korumalı güncellemeler veya tek bir sahip yolu). İmkansız değerleri engelleyen kısıtlar ekleyin (örneğin negatif stok). Sonra ara sıra toplamı yeniden hesaplayıp uyumsuzlukları işaretleyen bir arka plan uzlaşması çalıştırın.
Somut örnek: iki kullanıcı aynı sepete aynı anda ürün ekliyor. Her istek cart_total'ı okuyup kendi ürün fiyatını ekler ve geri yazarsa bir ekleme kaybolabilir. Eğer sepetteki kalemleri ve sepet toplamını tek bir işlemde güncellerseniz, yoğun paralel tıklarda bile toplam doğru kalır.
Daha az yarış durumu istiyorsanız veritabanından başlayın. Uygulama kodu yeniden dener, zaman aşımına uğrar veya iki kez çalışır. Bir veritabanı kısıtı, iki istek aynı anda gelse bile doğru kalan son kapıdır.
Tekil kısıtlar, asla olmaması gereken çoğaltmaları durdurur: e-posta adresleri, sipariş numaraları, fatura kimlikleri veya "kullanıcı başına bir aktif abonelik" kuralı. İki kayıt aynı anda gelirse veritabanı bir satırı kabul eder diğerini reddeder.
Yabancı anahtarlar kırık referansları önler. Onlarsız, bir istek ana kaydı silerken başka bir istek o ana işaret eden bir alt kayıt oluşturabilir ve bu da sonra temizlemesi zor yetim satırlar bırakır.
Check kısıtları değerleri güvenli aralıkta tutar ve basit durum kurallarını uygular. Örneğin quantity >= 0, rating 1 ile 5 arasında veya status izin verilen bir küme ile sınırlı.
Kısıt hatalarını "sunucu hatası" gibi görmek yerine beklenen sonuçlar olarak ele alın. Tekil, yabancı anahtar ve check ihlallerini yakalayın, "Bu e-posta zaten kullanılıyor" gibi net bir mesaj döndürün ve ayrıntıları iç sızdırmadan loglayın.
Örnek: lag sırasında iki kişi "Sipariş Oluştur"a basarsa. (user_id, cart_id) üzerinde tekil bir kısıt varsa iki sipariş almazsınız. Bir sipariş ve bir temiz, açıklanabilir reddedilme alırsınız.
Bazı yazmalar tek bir ifade değildir. Bir satırı okursunuz, bir kural kontrol edersiniz, bir durumu güncellersiniz ve belki bir denetim kaydı eklersiniz. İki istek bunu aynı anda yaparsa ikisi de kontrolü geçip yazabilir. Bu klasik başarısızlık desenidir.
Çok adımlı yazmayı tek bir veritabanı işleminde sarın ki tüm adımlar birlikte başarılı olsun ya da hiç olmasın. Daha da önemlisi, işlem aynı veriyi kimlerin aynı anda değiştirebileceğini kontrol etmenize olanak verir.
Sadece bir aktörün aynı anda bir kaydı düzenleyebildiği durumlarda satır düzeyinde kilit kullanın. Örneğin: sipariş satırını kilitleyin, hâlâ "beklemede" olup olmadığını doğrulayın, sonra "onaylandı"ya çevirin ve denetim girişini yazın. İkinci istek bekler, sonra durumu yeniden kontrol eder ve durur.
Çarpışmalar ne sıklıkta oluyor ona göre seçin:
Kilidi kısa tutun. Kilidi tutarken mümkün olduğunca az iş yapın: dış API çağrıları, yavaş dosya işleri veya büyük döngüler yapmayın. Koder.ai gibi araçlarla akış oluşturuyorsanız, işlemi sadece veritabanı adımlarıyla sınırlayıp diğer işleri commit'ten sonra yapın.
Para veya güven kaybına yol açabilecek bir akışı seçin. Yaygın bir örnek: sipariş oluştur, stoğu ayır, sonra sipariş durumunu onaylandı olarak ayarla.
Kodunuzun bugün hangi adımları yaptığını sırayla yazın. Ne okundu, ne yazıldı ve "başarı" ne anlama geliyor açıkça belirtin. Çarpışmalar, okuma ile daha sonra yapılan yazma arasındaki boşlukta saklanır.
Çoğu yığın için işe yarayan sertleştirme yolu:
Düzelttiğinizi kanıtlayan bir test ekleyin. Aynı ürün ve miktar için iki isteği aynı anda çalıştırın. Tam olarak bir siparişin onaylandığını ve diğerinin kontrollü bir şekilde başarısız olduğunu doğrulayın (negatif stok yok, çift rezervasyon yok).
Koder.ai gibi hızla uygulama üreten platformlarla çalışıyorsanız bile, bu kontrol listesi en önemli yazma yollarında yapılmaya değer.
En büyük nedenlerden biri UI'ya güvenmektir. Devre dışı bırakılan düğmeler ve istemci tarafı kontroller yardımcı olur, ama kullanıcılar çift tıklayabilir, sayfayı yenileyebilir, iki sekme açabilir veya isteği kararsız bir bağlantıdan yeniden oynatabilir. Sunucu idempotent değilse çoğaltmalar içeri sızar.
Başka bir sessiz hata: bir veritabanı hatasını (örneğin tekil kısıt ihlali) yakalayıp yine de akışı devam ettirirsiniz. Bu çoğunlukla "oluşturma başarısız oldu ama yine de e-posta gönderildi" veya "ödeme başarısız oldu ama yine de sipariş ödendi olarak işaretlendi" gibi sonuçlara yol açar. Yan etkiler oluştuğunda geri almak zordur.
Uzun işlemler de tuzaktır. Email, ödeme veya üçüncü parti API çağrıları sırasında bir işlemi açık tutarsanız, kilitleri gereğinden fazla tutarsınız. Bu beklemeyi, zaman aşımını ve isteklerin birbirini engellemesini arttırır.
Arka plan işleri ile kullanıcı eylemlerini tek bir gerçek kaynağı olmadan karıştırmak split-brain durumları yaratır. Bir iş yeniden dener ve bir satırı güncellerken kullanıcı onu düzenliyorsa her iki taraf da kendini son yazan zanneder.
Gerçekten çözmeyen birkaç "çözüm":
Koder.ai ile geliştiriyorsanız da aynı kurallar geçerlidir: daha iyi UI korumaları değil, sunucu tarafı kısıtları ve net işlem sınırları isteyin.
Yarış durumları genellikle gerçek trafik altında ortaya çıkar. Yayından önce yapılan bir geçiş en yaygın çarpışma noktalarını yeniden yazmadan yakalayabilir.
Veritabanından başla. Bir şey benzersiz olmak zorundaysa (e-postalar, fatura numaraları, kullanıcı başına bir aktif abonelik), bunu gerçek bir UNIQUE kısıtı yapın, uygulama düzeyinde "önce kontrol ediyoruz" kuralı değil. Sonra kodunuzun kısıt bazen başarısız olur diye beklediğinden ve temiz, güvenli bir cevap döndürdüğünden emin olun.
Sonra duruma bakın. Herhangi bir durum değişikliği (Taslak -> Gönderildi -> Onaylandı) izin verilen geçişler kümesine karşı doğrulanmalı. İki istek aynı kaydı hareket ettirmeye çalışırsa ikinci istek reddedilmeli veya no-op olmalı, arada kalmış bir durum yaratmamalı.
Pratik bir ön sürüm kontrol listesi:
Koder.ai üzerinde akışlar oluşturuyorsanız, bunları kabul kriterleri olarak ele alın: üretilen uygulama sadece mutlu yolu geçmesin, yinelemeler ve eşzamanlılık altında güvenli şekilde başarısız olsun.
İki personel aynı satın alma talebini açar. Her ikisi de birkaç saniye içinde Onayla'ya basar. Her iki istek de sunucuya gider.
Ne yanlış gidebileceği karmaşıktır: istek iki kez onaylanır, iki bildirim gider ve onaylarla ilgili toplamlar (bütçe kullanımı, günlük onay sayısı) 2 artabilir. Her iki güncelleme kendi başına geçerli ama çarpışırlar.
PostgreSQL tarzı bir veritabanıyla iyi çalışan bir düzeltme planı:
Onayları ayrı bir tabloda saklayın ve request_id üzerinde UNIQUE kısıtı uygulayın. Böylece uygulama kodunda hata olsa bile ikinci insert başarısız olur.
Onaylarken tüm geçişi tek bir işlemde yapın:
İkinci personel gecikirse ya 0 satır güncellendiğini görür ya da bir UNIQUE kısıt hatası alır. Her iki durumda da sadece bir değişiklik kazanır.
Düzeltmeden sonra ilk personel Onaylandı görür ve normal onay mesajını alır. İkinci personel şu dostça mesajı görür: "Bu talep zaten başka biri tarafından onaylandı. Güncellemek için sayfayı yenileyin." Dönenme, tekrar bildirim yok, sessiz hata yok.
Koder.ai gibi platformlarda (Go backend + PostgreSQL) bir CRUD akışı üretiyorsanız, bu onay eylemine dair kontrolleri bir kez ekleyip diğer "tek kazanan" eylemler için yeniden kullanabilirsiniz.
Yarış durumları, tekrarlayan bir rutin olarak ele alındığında en kolay düzelir, tek seferlik bir hata avı gibi değil. En önemli birkaç yazma yoluna odaklanın ve onları diğer her şeyden önce sıkıcı ama doğru hale getirin.
Önce en çok çarpışma yapan noktaları adlandırın. Birçok CRUD uygulamasında aynı üçlü çıkar: sayaçlar (beğeniler, stok, bakiyeler), durum değişimleri (Taslak -> Gönderildi -> Onaylandı) ve çift gönderimler (çift tıklama, yeniden denemeler, yavaş ağlar).
Dayanıklı bir rutin:
Koder.ai üzerinde geliştiriyorsanız, Planning Mode her yazma akışını adımlar ve kurallar halinde haritalamak için pratiktir. Yeni kısıtlar veya kilit davranışları sunarken hızlı geri dönüş için snapshot ve rollback imkanları da faydalıdır.
Zamanla bu bir alışkanlık olur: her yeni yazma özelliği bir kısıt, bir işlem planı ve en az bir eşzamanlılık testi ile gelir. CRUD uygulamalarındaki yarış durumları böylece sürpriz olmaktan çıkar.