Bulut Bilişim

PySpark’ta Join Seçimi: Hız, Maliyet, Tuzaklar

Bilmem anlatabiliyor muyum, Geçen ay İstanbul’da bir veri ekibiyle konuşurken sohbet dönüp dolaşıp yine join meselesine geldi. Hani Spark projelerinde herkes önce “cluster kaç node?” diye sorar ya… işin aslı şu ki çoğu zaman darboğaz CPU’dan değil, yanlış join seçiminden çıkıyor. Ben de bunu ilk kez 2023’te kendi küçük Databricks denememde fark etmiştim; iki masum tabloyu birleştiriyorum sanıyordum, meğer arka planda koca bir shuffle fırtınası kopuyormuş — bunu anlayınca biraz utandım açıkçası,. Saatlerce başka yerlere bakmıştım.

Garip gelecek ama, Spark dünyasında join seçimi biraz araba vitesi gibi. Şehir içinde birinci viteste gitmeye çalışırsanız motor bağırır. Otobanda üçüncü vitesle kalkmaya çalışırsanız araç tekler, siz de yolda kalırsınız. Aynı mantık burada da geçerli: küçük tabloyu büyük tablonun yanına “gizlice” taşıyıp işi bitirmek var, bir de iki tarafı da sahaya yayıp sonra sıra sıra dizmek var. Hangisinin doğru olduğu ise veri boyutuna, dağılıma ve o anki bellek durumuna bağlı — ne yazık ki tek bir formül yok.

💡 Bilgi: Spark’ta join seçimi sadece hız meselesi değil; doğrudan maliyet, bellek kullanımı ve hatta job’ın patlayıp patlamamasıyla ilgili. Yanlış strateji bazen dakikaları değil saatleri yakabiliyor.

Join Neden Bu Kadar Kritik?

Küçük bir detay: Bak şimdi… dağıtık sistemlerde veri tek bir makinede durmuyor. Parçalar halinde farklı executor’lara yayılıyor. Siz iki tabloyu eşleştirmek istediğinizde Spark’ın çoğu zaman o veriyi yeniden hareket ettirmesi gerekiyor; işte o meşhur shuffle tam da burada devreye giriyor. Shuffle kelimesi kulağa masum geliyor, hatta biraz eğlenceli bile — ama pratikte ağ trafiği, disk I/O ve ciddi gecikme demek.

Bir danışmanlık projesinde, Mart 2024’te Ankara’daki bir ekip bana “job neden 11 dakikadan 38 dakikaya çıktı?” diye sormuştu. Dışarıdan bakınca her şey aynıydı, kod değişmemişti, cluster aynıydı. Ama sonradan gördük ki yeni gelen referans tablo büyümüş. Artık broadcast sınırını aşıyordu — problem kodda yokmuş gibi görünüyordu, halbuki tam göbeğinde join stratejisi yatıyordu.

Şunu söyleyeyim, Spark’ın güzelliği şu: çoğu kararı Catalyst Optimizer ile kendi veriyor. Ama körü körüne güvenmek bazen pahalıya patlıyor. Mesela de de üretimde veri hacmi dalgalanıyorsa ya da tablolardan biri aniden şişiyorsa, “otomatik seçer nasılsa” yaklaşımı biraz naif kalıyor (inanın bana). Bence de öyle.

Küçük startup ile kurumsal yapı aynı değil

Size bir şey söyleyeyim, Küçük bir startup’ta genelde birkaç yüz milyon satırlık veri bile büyük sayılabiliyor; bu yüzden broadcast yaklaşımı sık sık işe yarıyor, rahatça çözüyor meseleyi. Kurumsal tarafta ise durum başka: günlük petabyte ölçeğine çıkınca sort merge join gibi daha dayanıklı yöntemler öne geçiyor. Yani tek doğru cevap yok, ortamın derdi neyse ilaç da o.

Hmm, bunu nasıl anlatsamdı…

Yani, Ha bu arada… benzer şeyi geçen sene İzmir’deki bir e-ticaret bir düşüneyim… ekibinde de gördüm. Ürün katalogları küçücük sanılıyordu ama bazı filtreler yüzünden key skew oluşmuştu; yani birkaç anahtar aşırı yoğunlaşıyordu. Sonuç? Bir executor ter döküyor, diğerleri kahve içiyordu resmen. Komik değil tabii, saatler gitti.

En Sık Kullanılan Join Stratejileri

Spark tarafındaki isimler kulağa teknik geliyor ama mantık basit aslında. Küçük tablo varsa onu taşırsın, ikisi de büyükse sıraya sokup eşlersin, ortada kalmışsa hibrit yollar denenir. Bir de hiç koşul vermeyip nested loop’a düşerseniz — geçmiş olsun, o kısım biraz yangın alarmı gibi.

Ve işler burada ilginçleşiyor.

Strateji Ne zaman iyi? Artısı Eksi tarafı
Broadcast Hash Join Küçük tablo + büyük tablo Shuffle yok, çok hızlı Bellek sınırı var
Sort Merge Join Büyük + büyük tablolar Ölçeklenebilir Shuffle + sort maliyetli
Shuffle Hash Join Orta boy tablolar Bazen sort merge’den hızlı Belleğe duyarlı
Broadcast Nested Loop Join Nadiren; koşulsuz join durumları Neredeyse yok denecek kadar az avantaj Pahalı ve riskli

Broadcast Hash Join: Küçük tabloyu cebine koymak

Küçük referans tablonuz varsa Spark onu tüm executor’lara yollar ve büyük tabloyla yerel olarak eşleştirir. Bu işin güzelliği şu: shuffle çoğunlukla ortadan kalkar, ağ trafiği düşer, cluster rahat nefes alır. Ben bunu ilk defa kullandığımda fark neredeyse komikti; job süresi gözle görülür biçimde düştü ve “ya bu kadar mıydı yani?” dedim içimden. Keynotif’te Zor Kısım: Gürültüyü Susturmak Yetmiyor yazımızda bu konuya da değinmiştik.

from pyspark.sql.functions import broadcast
sonuc = df_büyük.join(broadcast(df_küçük), "id")

Ama dikkat… bu yöntem her küçük görünen tablo için otomatik doğru değil. Çünkü “küçük” kelimesi göreceli. Bugün rahatça sığan veri yarın log şişmesiyle belleği zorlayabilir. Kağıt üstünde süper görünüyor, pratikte ise “göreceğiz” dedirten cinsten bir şey bu.

Sort Merge Join: Büyüklerin ağır abi buluşması

Eğer iki taraf da iri kıyım ise Spark genelde sort merge join’e gider. Önce veriyi dağıtır, — itiraz edebilirsiniz tabi — sonra anahtara göre sıralar ve en sonunda merge eder. Anlatması kolay, bedeli ağır: ağ trafiği artar, disk kullanımı yükselir, job süresi uzayabilir. Hmm, çok çekici gelmiyor değil mi? (bizzat test ettim) TCS’nin Q4 Sürprizi: AI Korkusu Şimdilik Abartı mı? yazımızda bu konuya da değinmiştik.

Buna rağmen enterprise senaryolarda çoğu zaman mantıklı, çünkü ölçeklenebilirliği iyi. Mesela düzgün partition edilmiş veriyle gayet stabil çalışıyor. Bir bankacılık projesinde bunu test ettiğimde — Nisan 2024’te Levent tarafındaki ekipte — en kritik noktanın sıralamadan çok partition düzeni olduğunu gördüm. Bunu bilmeden girseydim saatler giderdi.

Shuffle Hash Join: Ortada kalanlar için ara yol

Bu yöntem biraz “ne tamamen küçük ne tamamen dev” tablolar için düşünülmüş gibi duruyor. Gerçek hayatta dikkat ister. Küçük olan taraf hash yapısına alınır. Yine shuffle vardır ama bazı senaryolarda sort merge’den daha iyi sonuç verebiliyor.

Açık konuşayım: ben bu stratejiyi herkese önermem. Bellek hassasiyeti yüksek ve iş yüküne göre davranışı değişebiliyor. İşin iyi yani performans kazanma ihtimali var… kötü yani ise ufak bir bellek taşmasında her şey dağılıyor. Risk-fayda dengesi iyi hesaplanmalı.

Broadcast Nested Loop Join: Uzak durun demek boşuna değil

Eğer join condition yoksa veya Spark başka çare bulamazsa nested loop’a kayabiliyor. Bu neredeyse cross join gibi davranıyor ve özellikle büyük veride tam bir felaket senaryosu. Bunu ancak gerçekten mecbursanız kullanırsınız; aksi halde elinizi yakar. Ciddi söylüyorum. Daha fazla bilgi için Python ile Durum Takibi: Web Scraping’in Pratik Hali yazımıza bakabilirsiniz. Ajinomoto’da ABF Savaşı: Fiyat Baskısı Neyi Değiştirir? yazımızda da bu konuya değinmiştik. Anthropic ile OpenAI Arasında Sessiz Yarış: İşte Pazarın Yeni Rüzgârı yazımızda da bu konuya değinmiştik.

Spark’ta en pahalı karar çoğu zaman “hangi algoritma?” sorusu değil, “yanlış algoritmayı neden varsayılan bırakıyorum?” sorusudur.

Spark Doğru Stratejiyi Nasıl Seçiyor?

Spark’ın karar mekanizmasının merkezinde table size statistics var diyebilirim. Catalyst Optimizer bu istatistiklere bakıyor, ardından uygun planı çıkarıyor. Eğer istatistikleri güncellememişseniz optimizer resmen sisli havada araba sürüyor; yolu tahmin ediyor. Her virajı tutturamıyor. İşte o noktada işler karışıyor.

Bir de autoBroadcastJoinThreshold var (şaşırtıcı ama gerçek). Bu eşik, hangi tablonun otomatik broadcast edileceğini belirliyor. Varsayılan değer çoğu ortamda fena değil ama veri profiliniz değiştiyse bu sayı bazen fazla agresif, bazen de fazla temkinli kalabiliyor. Benim deneyimime göre üretimde bu değeri ezbere bırakmak pek iyi fikir değil — en azından ara ara kontrol edin.

  • Table stats güncel mi?
  • Bellek kapasitesi yeterli mi? (bu kritik)
  • Veride skew var mı? — bunu es geçmeyin
  • Tahmin edilen boyut gerçekçi mi?

Zor Durumlar: Skew ve Yanlış Key Tasarımı

Neyse uzatmayalım… asıl can sıkan meselelerden biri data skew (kendi tecrübem). Tek bir key üzerinde yığılma olursa job’ın yükü birkaç node’a abanıyor; diğerleri boş boş bekliyor. Görünürde paralel işlem yapıyorsunuzdur ama fiilen tek makineye yük binmiştir. Bu durum özellikle sipariş statüsü, ülke kodu ya da kategori gibi düşük kardinaliteli alanlarda sık çıkıyor —. Fark edilmesi de kolay değil ilk bakışta.

Bunu çözmek için salting tekniği kullanılabiliyor; yani yoğun anahtarı parçalara bölüp yükü dağıtıyorsunuz. Bazı ekipler ayrıca Spark’ın skew join optimize etmeunu açıyor. İkisini birlikte kullanan ekiplerle de çalıştım; iyi ayarlanırsa bayağı rahatlatıyor ama yanlış uygulanırsa debug süreci çorba oluyor. Siz ne dersiniz? İkisi de oldu bende, inanın.

Peki Pratikte Ne Yapmalı?

Lafı gevelemeden söyleyeyim: önce ölçün, sonra müdahale edin. Geometriyi bilmeden duvar yıkmaya benziyor aksi halde. Bir sorgu yavaşsa hemen hint eklemeyin; önce planı inceleyin, stage metriklerine bakın, shuffle read/write sayılarını görün. Çoğu zaman gerçek suçlu tahmin edilenden çok farklı çıkıyor.

  1. Küçük dimension tablolarını broadcast etmeyi düşünün.
  2. Büyük fact tablolarında uygun partition tasarlayın. — bunu es geçmeyin
  3. Anahtar kolonlarda fonksiyon kullanmaktan kaçının.
  4. Düzensiz büyüyen verilerde threshold’u kontrol edin.
  5. Zor durumda explain plan ile planı okuyun.

Küçük startup senaryosu vs enterprise senaryosu

Küçük bir ürün ekibinde çoğu işinizi broadcast çözer; çünkü referans tablolar genelde hafiftir ve iterasyon hızı önemli. Burada amaç karmaşık — itiraz edebilirsiniz tabi — optimizasyonlardan çok hızlı teslim etmektir, bunu atlamamak lazım (yanlış duymadınız). Ama kurumsal tarafta ETL zinciri uzun olduğundan yanlış join seçimi domino etkisi yaratır: sabah raporu gecikir, öğlen dashboard boş gelir, akşam destek ekibi alarm verir. Hepsini yaşadım ben.

Editör masasında bu konuyu yazarken aklıma Haziran 2024’te yaptığım Databricks PoC geldi. Orada ekip önce her şeyi default bırakmıştı; sonra optimize etmeye başlayınca çıktıların yalnızca %15 daha hızlı gelmediğini, aynı zamanda cluster maliyetinin de hissedilir biçimde düştüğünü gördüler. E tabi insan orada şunu soruyor: madem fark bu kadar netti, neden baştan yapılmadı? Cevap basit… alışkanlık. Hepimizde var bu.

💡 Bilgi: Hint kullanmak çözüm olabilir ama sihir değildir.
Eğer veri dağılımınız kötüysa veya istatistikler eskiyse, hint sadece semptomu gizleyebilir.
İyi sonuç için önce planı anlamak gerekir.

Kafada Tutulacak Kısa Özet

KÜÇÜK TABLO + BÜYÜK TABLO → Broadcast Hash Join
BÜYÜK + BÜYÜK → Sort Merge Join
ORTA SEVİYE VERİ → Shuffle Hash Join (dikkatle)
KOŞULSUZ JOIN → Mümkünse kaçın
SKEW VARSA → Salting / skew optimization düşün
STATISTICS ESKİYSE → Plan şaşabilir

Sıkça Sorulan Sorular

Spark’ta hangi join en hızlıdır?​‍‍‍‍‍‍‍ ‍‍ ‍‌‌‬‬‬‬‬‬⟂༶﹏᜗྿៚꙰꧅꩜〄𐌣꯭𑁍﷽? ࿐ὛӜ࿗Ῥ⟠⟊▤▥▦▧▨▶️⠀₿

Cevap olarak genelde broadcast hash join öne çıkar. Küçük tablo belleğe sığıyorsa en hızlı seçeneklerden biridir. Ama her durumda en iyi olacağını varsaymayın ; veri boyutu değiştikçe sonuç da değişir ។

Spark’ta shuffle neden pahalıdır?

‏‎‎‎‎‎‎P

broadcast(), küçük boyutlu lookup veya dimension tablolarında işe yarar. Bilhassa fact-dimension modelinde sık kullanılır. Bellek sınırını aşabilecek tablolarda ise temkinli olmak gerekir.

Aşkın KILIÇ

20+ yıl deneyimli Azure Solutions Architect. Microsoft sertifikalı bulut mimari ve DevOps danışmanı. Azure, yapay zekâ ve bulut teknolojileri üzerine Türkçe teknik içerikler üretiyor.

AZ-305AZ-104AZ-500AZ-400DP-203AI-102

Bu içerik işinize yaradı mı?

Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.

Haftalık Bülten

Her pazar özenle seçilmiş teknoloji yazıları doğrudan e-postanıza gelsin.

← Onceki Yazi
Python ile Durum Takibi: Web Scraping’in Pratik Hali
Sonraki Yazi →
Keynotif’te Zor Kısım: Gürültüyü Susturmak Yetmiyor

Yorum Yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Haftalık Bülten

Azure, DevOps ve Yapay Zeka dünyasındaki en güncel içerikleri her hafta doğrudan e-postanıza alın.

Spam yok. İstediğiniz zaman iptal edebilirsiniz.
📱
Uygulamayı Yükle Ana ekrana ekle, çevrimdışı oku
Kategoriler
Ara
Paylaş
İçindekiler
← Python ile Durum Takibi: Web S...
Keynotif’te Zor Kısım: Gürültü... →
📩

Gitmeden önce!

Her pazar özenle seçilmiş teknoloji yazıları ve AI haberleri doğrudan e-postanıza gelsin. Ücretsiz, spam yok.

🔒 Bilgileriniz güvende. İstediğiniz zaman ayrılabilirsiniz.

📬 Haftalık bülten: Teknoloji + AI haberleri