Yayın tarihi:
Kodunuzdaki Tek Bir Felsefe Değişikliği Performansı 100+ Kat Artırabilir Mi?
Saatlerce süren bir ETL işlemi, satır satır düşünmekten set tabanlı düşünmeye geçilerek 3,5 dakikaya indirildi — 100 kattan fazla performans artışının hikayesi.
Saatlerce süren ETL işlemleri, sürekli tıkanan veritabanları, ne zaman biteceği belirsiz bekleyişler... Tanıdık geliyor mu?
Geçenlerde karşılaştığımız 6+ saatlik bir işlemde tam olarak bunu yaşıyorduk.
Sorun: O Meşhur "Döngü İçinde Sorgu" Anti-Pattern'i
Kodun ilk hali, teoride masum görünen ama pratikte bir performans katili olan bir yapıya sahipti. Milyonlarca satırı işlemek için izlediği yol şuydu:
- Ana tablodan veriyi bir CURSOR ile al.
- Bir döngü (LOOP) başlat.
- Döngünün içinde, o anki tek bir satır için diğer tablolara tekrar tekrar SELECT sorguları atarak ek bilgileri bul.
- Yine döngünün içinde, bu tek satırı hedef tabloya INSERT et.
- Sonraki satıra geç ve üçüncü adımı milyonlarca kez tekrarla.
Sorun nerede mi? Milyonlarca satır, PL/SQL motoru ile SQL motoru arasında milyonlarca kez gidip gelmek demek. Bu, "context switch" adı verilen ve sistem kaynaklarını tüketen korkunç bir maliyet yaratır.
Peki ya çözümün, bu karmaşık döngüyü düzeltmek yerine, sadece o koda yaklaşımımızı değiştirmek olduğunu söylesem?
Bizim için her şeyi değiştiren "Aha!" anı buydu. O tek felsefe değişikliği ise şuydu:
Satır Satır Düşünmekten Vazgeçip, Set Tabanlı (Toplu) Düşünmeye Başlamak.
Yani, veritabanına komutları tek tek veren bir mikro-yönetici gibi değil, ne istediğini söyleyip gerisini ona bırakan bir stratejist gibi davranmak.
Bu felsefeyi koda döktüğümüzde ise ortaya çıkan tablo inanılmazdı. 6+ saatlik işlem 3,5 dakikada tamamlandı. Tam 100 kattan fazla bir performans artışı!
Peki Ama Nasıl? İşte Dokunuşlar
- Tek Sorgu, Tek Görev: Döngü içindeki onlarca SELECT sorgusunu çöpe attık. Tüm veri toplama ve zenginleştirme mantığını, WITH ve OUTER APPLY kullanan tek ve akıllı bir CURSOR'a devrettik.
- Toplu Taşıma (BULK COLLECT): Veriyi satır satır çekmek yerine, 100 binlik yığınlar halinde belleğe alarak I/O operasyonunu minimize ettik.
- Toplu Yazma (FORALL): Bellekteki 100 bin kaydı, FORALL komutuyla tek bir operasyonda hedef tabloya yazdık. Veritabanıyla olan konuşma sayımızı milyondan bire indirdik.
- Paralel Güç: PARALLEL hint'leri ile veritabanına "bu işi yapmak için elindeki tüm donanım gücünü kullan" komutunu verdik.
- Sağlam Kod Pratikleri: CURSOR%ROWTYPE, INDICES OF ve CONSTANT gibi modern PL/SQL yapılarıyla kodu hem gelecekteki değişikliklere karşı daha dayanıklı hem de bakımı daha kolay hale getirdik.
İşte bu felsefenin koda yansımış hali:
DECLARE
-- 1. Tüm mantık, set tabanlı düşünen tek bir CURSOR'da.
CURSOR c_kayitlar IS
WITH ...
SELECT
/*+ PARALLEL(16) */ ...
FROM
Kaynak_Tablo KT ...;
-- 2. Veri, yığınlar halinde tutulacak bir koleksiyonda.
TYPE t_record_list IS TABLE OF c_kayitlar%ROWTYPE;
v_KAYITLAR t_record_list;
c_BATCH_SIZE CONSTANT PLS_INTEGER := 100000;
BEGIN
OPEN c_kayitlar;
LOOP
-- 3. Veri toplu çekilir.
FETCH c_kayitlar BULK COLLECT INTO v_KAYITLAR LIMIT c_BATCH_SIZE;
EXIT WHEN v_KAYITLAR.Count = 0;
-- 4. Veri toplu yazılır.
FORALL i IN INDICES OF v_KAYITLAR
INSERT /*+ APPEND PARALLEL(8) */ INTO Hedef_Tablo VALUES v_KAYITLAR(i);
COMMIT;
END LOOP;
CLOSE c_kayitlar;
END;
Sonuçta anladık ki, en büyük performans kazancı, en karmaşık algoritmada değil, en temel yaklaşım değişikliğindeydi.
Bu yazı ilk olarak LinkedIn'de yayınlanmıştır.