Kafayı yiyecektim cidden. Guild war bitiyor, herkes heyecanla skor tablosunu bekliyor, ama bizim sistemde "İşleniyor..." yazısı iki saat boyunca dönüyor. Meğerse batch job'ımız, savaş bitince patlayan veriyi işlemekte acayip zorlanıyormuş. Gerçek zamanlı dediğimiz sistem, "gerçek iki saat"e dönüştü resmen.
Veri Tsunamisi ve Naif Kodum
Olay şu: Savaş bitimi, MySQL'deki `combat_logs` tablosuna yüz binlerce kayıt anında düşüyor. Benim yazdığım eski batch job ise, her bir guild için, tüm logları tek tek okuyup, toplam hasarı, öldürmeleri vs. hesaplıyordu. Basit bir for loop ve içinde tonla SQL sorgusu. İlk testlerde 100 kayıtla gayet iyi çalışıyordu, şaka gibi ama production'da işler değişti.
Profilörle Gelen Yüz Karası
N+1 Query Problemi denilen canavarla tam da burada tanıştım. Her guild için ayrı bir log sorgusu atıyormuşum. 100 lonca ve 500k log düşününce, olay katlanarak büyüyor. StackOverflow'da bile direk çözüm bulamadım, ta ki kendi koduma bir profilör (Py-Spy) bağlayana kadar. Gördüğüm manzara içler acısıydı, CPU değil, I/O'da kilitlenip kalmışız.
Çözüm? Toplu işlem (batch processing) ve akıllı sorgular. Tüm hesaplamaları, veritabanı seviyesinde, GROUP BY ve aggregate fonksiyonlarla (`SUM`, `COUNT`) yapıp tek bir sorguda çekmeyi öğrendim. Bir de Redis ile ara sonuçları cache'leyerek ağır yükü dağıttım.
Sonuç ve İtiraf
Yeni sistemde işlem süresi 2 saatten 4 dakikanın altına düştü. Asıl ders: "Çalışıyor" demekle "scale oluyor" demek arasında dağlar kadar fark var. Testi, gerçek production verisi boyutunda yapmamak en büyük hataydı.
Siz de "nasıl olsa az kayıt" diyerek yazdığınız bir kodun, data büyüyünce sizi nasıl yüzüstü bıraktığı oldu mu? Bu tarz batch işlerinde başka hangi hileler işe yarıyor, fikirlerinizi bekliyorum!
Olay şu: Savaş bitimi, MySQL'deki `combat_logs` tablosuna yüz binlerce kayıt anında düşüyor. Benim yazdığım eski batch job ise, her bir guild için, tüm logları tek tek okuyup, toplam hasarı, öldürmeleri vs. hesaplıyordu. Basit bir for loop ve içinde tonla SQL sorgusu. İlk testlerde 100 kayıtla gayet iyi çalışıyordu, şaka gibi ama production'da işler değişti.
Python:
for guild in all_guilds:
total_damage = 0
for log in combat_logs.where("guild_id = ", guild.id): # Felaketin başlangıcı!
total_damage += log.damage
# ... diğer hesaplamalar
N+1 Query Problemi denilen canavarla tam da burada tanıştım. Her guild için ayrı bir log sorgusu atıyormuşum. 100 lonca ve 500k log düşününce, olay katlanarak büyüyor. StackOverflow'da bile direk çözüm bulamadım, ta ki kendi koduma bir profilör (Py-Spy) bağlayana kadar. Gördüğüm manzara içler acısıydı, CPU değil, I/O'da kilitlenip kalmışız.
Çözüm? Toplu işlem (batch processing) ve akıllı sorgular. Tüm hesaplamaları, veritabanı seviyesinde, GROUP BY ve aggregate fonksiyonlarla (`SUM`, `COUNT`) yapıp tek bir sorguda çekmeyi öğrendim. Bir de Redis ile ara sonuçları cache'leyerek ağır yükü dağıttım.
Yeni sistemde işlem süresi 2 saatten 4 dakikanın altına düştü. Asıl ders: "Çalışıyor" demekle "scale oluyor" demek arasında dağlar kadar fark var. Testi, gerçek production verisi boyutunda yapmamak en büyük hataydı.
Siz de "nasıl olsa az kayıt" diyerek yazdığınız bir kodun, data büyüyünce sizi nasıl yüzüstü bıraktığı oldu mu? Bu tarz batch işlerinde başka hangi hileler işe yarıyor, fikirlerinizi bekliyorum!