Geçen hafta, uzun süredir canımı sıkan bir item duplikasyon (dupe) bug'ını fixlemeye karar verdim. Oyuncular, belirli bir NPC ile yaptıkları ticaret sırasında, item'ı hızlıca envantere atıp NPC'yi tekrar tıklayarak aynı itemı bedavaya alabiliyorlarmış. Klasik bir race condition ve inventory sync sorunu.
Logları inceledim, olay şuydu: NPC, item'ı verip karşılığında altını almak için iki ayrı database işlemi yapıyordu. Önce item'ı inventory'ye ekliyor, SONRA altını player'ın hesabından düşüyordu. Arada milisaniyelik bir boşluk vardı ve oyuncular tam o boşlukta item'ı hızlıca başka bir karaktere atarak veya satarak, ikinci işlemin (altın düşme) başarısız olmasını sağlıyor, ama item'ı almış oluyorlardı. Sürekli tekrarlayınca, sınırsız item ve sıfır maliyet!
"Tamam," dedim, "bu işlem atomic olmalı. Ya hep ya hiç." Database'de bir transaction bloğu açtım ve işlem sırasını DEĞİŞTİRDİM. Önce altın düşülecek, başarılı olursa item eklenecekti. Mantıken kusursuzdu.
Python:
# Eski Hatalı Mantık
give_item(player, item_id) # 1. Adım
deduct_gold(player, price) # 2. Adım
# Yeni "Mükemmel" Mantığım
start_transaction()
try:
deduct_gold(player, price) # ÖNCE altını düş
give_item(player, item_id) # SONRA itemı ver
commit_transaction()
except:
rollback_transaction()
Test ortamında denedim, dupe kesinlikle çalışmıyordu. Göğsümü gere gere canlı sunucuya attım fix'i.
Ertesi sabah discord patlamıştı. Ekonomi resmen çökmüştü. Enflasyon %5000 falan olmuş herhalde. Sebep? Benim "mükemmel" fix'im, altını DÜŞEMEDİĞİ her durumda (yetersiz bakiye olmasa bile) işlemi tamamen iptal ediyordu. Meğerse o NPC'ye ait başka, eski bir altın kontrol MEKANİZMASI daha varmış ve o mekanizma ile benim transaction'ım çakışıyordu. Sonuç: NPC hiçbir işlem yapamaz oldu, item akışı durdu, piyasada item kıtlığı başladı, fiyatlar uçtu.
Kafayı yiyecektim. Bir bug'ı fixlerken, tüm ekonomi simülasyonunun bağlı olduğu kritik bir ticaret noktasını tamamen kilitlemişim. StackOverflow'da bile böyle bir saçmalık bulamazsın.
1. Monolitik Kod Laneti: Eski, büyük sistemlerde bir yeri değiştirirken, görünmeyen bağımlılıkları mutlaka ara. Ben sadece dupe'u düşündüm, ekonomi döngüsünü unuttum.
2. Test, Test, Test: Canlıya atmadan önce, sadece o bug'ı değil, ilgili tüm sistemleri (ticaret, envanter, market) simüle eden kapsamlı senaryolarla test etmek şart.
3. Rollback Planı: "Bu fix kesin çalışır" diye düşünüp, acil geri alma (rollback) planını detaylı hazırlamamıştım. Büyük hata.
Neyse ki database yedeği vardı, 2 saatlik veri kaybıyla eski koda döndük. Sonrasında fix'i, mevcut altın kontrol mekanizmasıyla uyumlu olacak şekilde, daha izole bir şekilde yeniden yazdık.
Siz de böyle "küçük bir fix" ile tüm sistemi batırdığınız komik/üzücü anlar yaşadınız mı? Ya da böyle kritik noktalarda nasıl bir test stratejiniz var? Fikirlerinizi merak ediyorum!