Daha önce notlarımızda belirttiğimiz gibi RDD’ ler üzerinde gerçekleştirilen dönüşümler anlık olarak gerçekleştirilmez. Spark motoru bir dönüşüm isteği geldiğini kayıt altına alır. RDD’leri veri saklayan bileşenler gibi düşünmek yerine, RDD’ lerin verinin nasıl işleneceğini belirleyen komut zincirleri olduğunu düşünmek daha doğrudur. Bu verinin hdfs,s3, hive gibi herhangi bir kaynaktan yüklenmesi esnasında da geçerli bir kuraldır. Bir hesaplama isteği (action) olmadan veri gerçekte kaynaktan yüklenmez. Hadoop üzerinde geliştirme yapan programcılar çoğu zaman kullanılacak tüm operasyonları gruplamaya çalışarak MapReduce çevrimlerini azaltmayı hedeflerler. Spark’ da kullanılan Lazy Evaluation (Tembel Değerlendirme :) ) art arda sıralanan dönüşümlerin gruplanarak tek bir operasyon gibi çalıştırılmasını sağlar. Böylece Spark’ da çevrimlerini azaltmak için karmaşık map fonksiyonlarına gerek kalmaz.
Spark dönüşüm işlemlerinin (transformations) neredeyse tamamı ilgili veri seti üzerinden yeni sonuçlar üretir. Bu dönüşümlerde yeni veri setini oluşturacak fonksiyonlar kullanılır. Aksiyonların bir bölümünde de benzer fonksiyon yaklaşımı kullanılır. Programlama diline bağlı olarak Spark dönüşümlerinde parametre olarak kullanılacak fonksiyonların yazım şekli farklılık gösterir. Örneğin Python dilinde fonksiyonları Lambda yazımı ile oluşturmak mümkündür.
Örnek:
Hatalar=tumVeri.filter(lambda x:”error” in x)
Benzer şekilde ayrı bir fonksiyon oarak
def hataicerensatirlar(satir)
return “error” in satir
Hatalar=tumVeri.filter(hataicerensatirlar)
Olarak kullanılabilir. Python’ da fonksiyon adı verilerek bir dönüşüm gerçekleştirildiğinde dikkat edilmesi gereken önemli nokta: objeye ait bir metod veya field fonksiyonda parametre olarak kullanılırsa, Spark objenin tamamını hesaplama düğümlerine yolluyor olmasıdır. Bu çok daha fazla verinin hesaplama düğümlerine yollanmasına neden olur. Hatta bu yaklaşım Python’ un bazı sınıfların içerebileceği alt objeleri nasıl işleyeceğini çözememesi nedeni ile kodun fail etmesine neden olabilir.
Bu gibi durumlara neden olmamak için objeye ait instance variableların (alanların) yerel değişkenlere atanması ve bu değişkenlerin dönüşüm fonksiyonlarında kullanılması daha doğrudur.
Java’ da dönüşümlere parametre olarak verilecek fonksiyonlar spark.aspi.java.function paketindeki fonksiyonları uygulayan (implement) nesneler ile kullanılır. Fonksiyonların geri dönüş tipine göre farklı arabirimler (interface) mevcuttur.
Örneğin: Function<T,R> R call(T) tek bir parametre alarak bir çıktı üretir, genellikle map ve filter gibi dönüşümlerde kullanılır. Function2<T1,T2,R> R call(T1,T2) 2 parametre (girdi) alarak yine tek bir sonuç üretir genelikle aggregate ve fold gibi dönüşümlerde kullanılır. Dikkat edileceği üzere farklı veri tiplerinde çalışılabilmesi için bu sınıflar generics yapısı ile template türünde oluşturulmuştur.
Örneğin:
class hataliSatirlar implements Function<String,Boolean>() {
public Boolean call (String satir) {
return satir.contains(“error”);
}
}
Bu sınıfı kullanmak için ise :
RDD<String> hatalar=tumVeri.filter(new hataliSatirlar());
Yukarıda tanımlanan sınıfı filter dönüşümünde anaonim bir sınıf olarak oluşturmak da mümkündür. Java 8 sonrası Lambda yazımı ile çok daha kısa şekilde :
RDD<String> hatalar=tumVeri.filter(satir->satir.contains(“error”));
Java sınıflarına yapıcıları (constructor) kullanarak parametre yollamak da mümkündür.
class hataliSatirlar implements Function<String,Boolean>() {
private String param;
public hataliSatirlar(String kelime) {
this.param=kelime;
}
public Boolean call (String satir) {
return satir.contains(param);
}
}
Parametrik kullanım için ise:
RDD<String> hatalar=tumVeri.filter(new hataliSatirlar (“error”));
Bir sonraki notumuzda sık kullanılan aksiyon ve dönüşümleri kapsayacak.