- Performans ayarına nereden başlanır: önce ölçün
- Shuffle ve bölüm (partition) yönetimi
- Birleştirme (join) stratejileri ve veri çarpıklığı
- Bellek mimarisi ve kaynak ayarı
- Veri düzeni, dosya formatı ve okuma optimizasyonu
- Adım adım örnek: yavaş bir günlük raporu iyileştirmek
- Sık yapılan hatalar
- Bu beceriyi Mindset programlarında nerede çalışırsınız?
- Sık sorulan sorular
Apache Spark Performansı Nasıl Ayarlanır? Teşhisten Çözüme
Apache Spark işlerini yavaşlatan shuffle, veri çarpıklığı, bellek ve dosya düzeni sorunlarını teşhis etmeyi ve adım adım gidermeyi öğrenin.
Performans ayarına nereden başlanır: önce ölçün
Spark'ta yavaş çalışan bir işi hızlandırmanın ilk kuralı, tahminle ayar yapmamaktır. Önce işin zamanının nerede harcandığını görmek gerekir. Spark arayüzü (Spark UI) bunun ana aracıdır: Job, Stage ve Task görünümleri hangi aşamanın uzun sürdüğünü, kaç görev çalıştığını, görev süreleri arasındaki dengesizliği, okunan ve yazılan veri miktarını ve diske taşan (spill) belleği gösterir.
Teşhis için birkaç soru sorulur. Süre tek bir aşamada mı yoğunlaşıyor? Görevlerin çoğu çabuk bitip birkaç görev çok mu uzuyor (veri çarpıklığı belirtisi)? Shuffle okuma ve yazma hacmi girdiden çok mu büyük? Görevlerde diske taşma var mı? Yanıtlar, ayarın hangi yönde olacağını belirler: bölüm sayısı, birleştirme stratejisi, bellek, veri düzeni ya da sorgunun kendisi.
Sorgu planı da aynı derecede önemlidir. DataFrame üzerinde explain çağrısı, fiziksel planda hangi birleştirme türünün seçildiğini, filtrelerin kaynağa itilip itilmediğini ve gereksiz karıştırma (Exchange) adımlarını gösterir.
Shuffle ve bölüm (partition) yönetimi
Spark'ta en pahalı işlem genellikle shuffle'dır: Verinin ağ üzerinden yeniden dağıtılması, diske yazılması ve okunması. GroupBy, join, distinct ve repartition gibi geniş dönüşümler shuffle üretir. Performans çalışmasının büyük kısmı, shuffle sayısını ve hacmini azaltmaktır.
Bölüm sayısı hem azken hem fazlayken hata yaratır. Çok az bölüm, paralelliği düşürür ve tek görevin belleğini zorlar, bu da diske taşmaya yol açar. Çok fazla bölüm ise çok küçük görevler üretir; zamanlama ve dosya başına yük artar. Hedef, her bölümün makul büyüklükte olması ve tüm çekirdeklerin dolu tutulmasıdır. Shuffle sonrası bölüm sayısı bir yapılandırma ayarıyla kontrol edilir; varsayılan değer her iş yükü için uygun değildir.
Birkaç pratik ilke: • Filtreyi ve gereksiz sütun seçimini mümkün olduğunca erken yapın; shuffle edilecek veri küçülür. • Yalnızca bölüm sayısını azaltmak gerekiyorsa coalesce, dengeli yeniden dağıtım gerekiyorsa repartition kullanın. Coalesce shuffle yapmaz ama dengesiz bölümler bırakabilir. • Ardışık geniş dönüşümlerde aynı anahtarla bölümlemek, tekrarlı shuffle'ı önleyebilir. • Spark'ın uyarlanabilir sorgu yürütme (adaptive query execution) özelliği, çalışma anında bölüm birleştirme ve çarpıklık yönetimi sağlar; kullandığınız sürümde etkinse temel ayarlar çoğu zaman bunun üzerine kurulur.
Birleştirme (join) stratejileri ve veri çarpıklığı
Join, shuffle'ın en yoğun yaşandığı yerdir. Spark, tabloların boyutuna göre farklı stratejiler seçer. Küçük tabloyu tüm yürütücülere kopyalayan broadcast hash join, büyük tabloyu karıştırmaya gerek bırakmaz ve çoğu zaman en hızlı seçenektir. Büyük tablolar birleştirilirken sort merge join kullanılır; bu durumda her taraf anahtara göre karıştırılıp sıralanır.
Boyut tahmini yanlışsa Spark istediğiniz stratejiyi seçmeyebilir. İstatistik güncel değilse ya da küçük tablo belirlenen eşiği aşıyorsa, broadcast ipucu (hint) vererek zorlayabilirsiniz. Tersine, broadcast edilen tablo yürütücü belleğine sığmazsa bellek hatası alırsınız; bu yüzden ipucu bilinçli kullanılmalıdır.
Veri çarpıklığı (skew), bazı anahtarların diğerlerinden çok daha fazla satıra sahip olmasıdır. Örneğin bir sipariş tablosunda "bilinmeyen müşteri" gibi tek bir anahtar satırların büyük bölümünü barındırabilir. Sonuçta tek görev saatlerce çalışırken diğerleri biter ve iş o görevi bekler. Çözümler: uyarlanabilir yürütmenin çarpık bölüm ayırma özelliğini kullanmak, anahtara rastgele bir ek (salting) ekleyip yükü dağıtmak, sorunlu anahtarı ayrı işleyip sonra birleştirmek ya da sorunlu anahtar gerçekten anlamsızsa (örneğin boş değer) önceden filtrelemek.
Bellek mimarisi ve kaynak ayarı
Yürütücü belleği, yürütme (execution) ve depolama (storage) bölgelerinden oluşan birleşik bir alanı paylaşır; ek olarak kullanıcı verisi ve sistem için ayrılmış bölge ve JVM dışı (off-heap, overhead) bellek vardır. Yürütme belleği karıştırma, sıralama ve toplama için, depolama belleği ise önbelleğe alınan veri ve broadcast değişkenleri için kullanılır. Yetersiz kaldığında Spark veriyi diske taşır; bu çalışmayı bozmaz ama belirgin biçimde yavaşlatır.
Kaynak ayarında yaygın tuzak, yürütücü sayısını ve bellek boyutunu ayrı ayrı büyütmektir. Çok büyük yürütücüler, çöp toplama (garbage collection) duraklamalarını uzatır; çok küçük yürütücüler ise broadcast ve yönetim yükünü çoğaltır. Orta büyüklükte, çekirdek başına yeterli bellek düşecek biçimde yürütücü tanımlamak genellikle dengeli bir başlangıçtır. Görev bellek hatası veriyorsa önce bölüm sayısını artırarak görev başına veriyi küçültmeyi, sonra belleği artırmayı deneyin.
Python tarafında PySpark kullanıyorsanız ek bir noktaya dikkat edin: Python UDF'leri her satır için JVM ile Python arasında veri taşır ve yavaştır. Mümkünse yerleşik fonksiyonları ya da vektörleştirilmiş (Arrow tabanlı) pandas UDF'lerini tercih edin.
Veri düzeni, dosya formatı ve okuma optimizasyonu
Performansın önemli bir kısmı veri okunmadan önce kazanılır. Sütun tabanlı biçimler (Parquet, ORC), yalnızca ihtiyaç duyulan sütunları okuma ve filtre koşullarını dosya düzeyine itme (predicate pushdown) imkânı verir. CSV ve JSON gibi satır tabanlı metin biçimlerinde bu avantaj yoktur ve şema çıkarımı ek bir okuma gerektirir.
Veriyi sorgu desenine uygun bölümleyin. Tarihe göre filtre yapıyorsanız tarihe göre bölümlenmiş tablo, ilgisiz klasörleri hiç okumaz. Fakat aşırı ince bölümleme (örneğin yüksek kardinaliteli bir sütuna göre) çok sayıda küçük dosya üretir. Küçük dosya sorunu, hem listeleme maliyetini hem görev sayısını şişirir; yazma sırasında dosya sayısını denetleyin ve düzenli olarak dosyaları birleştirin.
Önbellekleme (cache, persist) yalnızca aynı veri kümesi birden çok kez kullanılıyorsa fayda sağlar. Her veri kümesini önbelleğe almak bellek baskısı yaratır; işi bitince unpersist ile serbest bırakın. Bir kez okunan veriyi önbelleğe almak, çoğu zaman yalnızca maliyet ekler.
Adım adım örnek: yavaş bir günlük raporu iyileştirmek
Örneğin gece çalışan bir rapor, işlem hareketleri ile müşteri tablosunu birleştirip günlük özet üretiyor ve her gün daha uzun sürüyor. Süreç şöyle ilerler.
Önce Spark UI'da hangi aşamanın uzadığına bakılır. Birleştirme aşamasında görevlerin çoğunun kısa, birkaçının çok uzun sürdüğü görülür; bu, çarpıklık belirtisidir. Sonra anahtar dağılımı sayılır ve boş müşteri anahtarının satırların büyük kısmını oluşturduğu fark edilir. Bu satırlar rapor için zaten anlamsızsa join'den önce filtrelenir.
Ardından müşteri tablosu küçük olduğu için broadcast ipucuyla birleştirme stratejisi değiştirilir ve geniş karıştırma ortadan kalkar. Girdi biçimi CSV ise Parquet'e çevrilir, yalnızca raporda kullanılan sütunlar seçilir. Her değişiklikten sonra aynı veriyle süre ve shuffle hacmi karşılaştırılır; yalnızca ölçülebilir kazanç getiren değişiklik kalır. Bu disiplin, "her şeyi bir seferde ayarlama" alışkanlığından daha güvenilir sonuç verir.
Sık yapılan hatalar
Hatalar çoğunlukla ölçmeden müdahale etmekten ve belirtiyi nedenle karıştırmaktan doğar.
- Ölçmeden yapılandırma değeri değiştirmek, internetten bulunan ayarları kopyalamak.
- Veri çarpıklığını bellek sorunu sanıp yürütücü belleğini artırmak; sorun çözülmez, maliyet artar.
- Her yerde repartition çağırmak; her çağrı ek bir shuffle demektir.
- Tüm tabloyu önbelleğe almak.
- Küçük dosya sorununu yazma aşamasında önlememek.
- Python UDF'lerini yerleşik fonksiyon varken kullanmak.
- Üretim verisinden çok küçük bir örnekle test edip ölçeklendiğinde yeni darboğazlarla karşılaşmak. Test verisi, çarpıklık ve boyut açısından gerçeği yansıtmalıdır.
Bu beceriyi Mindset programlarında nerede çalışırsınız?
Spark İleri Seviye: Performans Ayarı ve Bellek Yönetimi eğitiminin modülleri bu sayfadaki başlıklarla örtüşür: Yürütme Modeli ve Performans Teşhisi, Partition Yönetimi ve Shuffle Optimizasyonu, Bellek Mimarisi ve Kaynak Ayarı, Birleştirme Stratejileri ve Veri Çarpıklığı, Veri Düzeni, Format ve Okuma Optimizasyonu ile Maliyet, İzleme ve Bitirme Çalışması.
Temel PySpark Eğitimi ise DataFrame API, veri temizleme ve dönüşümler, Spark SQL ve dağıtık sistemlerde PySpark konularıyla ayar yapabilmek için gereken temeli kurar.
İlgili Mindset programı
Spark İleri Seviye: Performans Ayarı ve Bellek Yönetimi
Sık sorulan sorular
Spark performans ayarına hangi sırayla yaklaşmalıyım?
Önce ölçüm: Spark UI'da yavaş aşamayı, görev süre dengesizliğini, shuffle hacmini ve diske taşmayı bulun. Ardından sorgu planını ve veri düzenini düzeltin, birleştirme stratejisini gözden geçirin, en son bellek ve kaynak ayarına geçin. Mimari sorunu yapılandırma değerleriyle gizlemek geçici sonuç verir.
Veri çarpıklığı olduğunu nasıl anlarım?
Spark UI'da bir aşamadaki görev sürelerine bakın. Birkaç görev diğerlerinden belirgin biçimde uzun sürüyor ve çok daha fazla veri işliyorsa çarpıklık vardır. Anahtar sütununa göre satır sayısı dağılımını çıkarmak da doğrudan kanıt verir. Bu durumda bellek artırmak yerine anahtar dağılımını düzeltmeye odaklanın.
Cache kullanmak her zaman hızlandırır mı?
Hayır. Önbellek, aynı veri kümesi birden çok işlemde yeniden kullanılacaksa yararlıdır. Bir kez okunan veriyi önbelleğe almak yalnızca bellek ve yazma maliyeti ekler; büyük veri kümelerinde diske taşabilir. Gerçekten tekrar kullanılan ara sonuçları seçin ve işiniz bitince serbest bırakın.
Broadcast join ne zaman kullanılır?
Birleştirilen tablolardan biri yürütücü belleğine rahatça sığacak kadar küçükse kullanılır. Küçük tablo tüm yürütücülere kopyalanır ve büyük tablonun karıştırılmasına gerek kalmaz. Tablo beklenenden büyükse bellek hatası riski doğar, bu yüzden ipucunu boyutu bilinen tablolarda kullanın.
PySpark ile Scala arasında performans farkı var mı?
DataFrame ve SQL API'leri kullanıldığında Python da Scala da aynı sorgu iyileştiricisinden geçer, fark küçüktür. Fark, Python UDF gibi satır bazlı özel kod yazıldığında ortaya çıkar; bu kodlar JVM ile Python arasında veri taşıdığı için yavaştır. Yerleşik fonksiyonları tercih etmek bu farkı büyük ölçüde kapatır.
İlgili rehberler
İlgili eğitimler
Kategori: Veri Mühendisliği ve MLOps