Evaluasi lazy adalah prinsip bahwa Spark tidak menjalankan transformasi saat transformasi itu ditulis, melainkan menundanya sampai sebuah action menuntut hasil. Topik ini membahas pembagian metode API menjadi dua kelompok tersebut, bentuk rencana yang dipelihara Spark selama penundaan, dan mental model lineage yang menjelaskan dari mana setiap partisi hasil berasal. Setelah membacanya, batas antara mendefinisikan dan mengeksekusi dapat dikenali pada setiap baris kode, dan kesalahan pembacaan umum tentang kapan pekerjaan benar-benar berjalan dapat dihindari.
Transformasi dan action#
Metode API DataFrame terbagi dua kelompok yang berperilaku beda. Transformasi, seperti filter, select, withColumn, dan groupBy, hanya merakit deskripsi perhitungan dan mengembalikan DataFrame baru tanpa menyentuh data. Action, seperti count, collect, show, dan write, memicu eksekusi seluruh rantai transformasi yang menyusunnya. Pemisahan ini bukan gaya penulisan, melainkan sumber kemampuan Spark menyusun rencana eksekusi terbaik sebelum pekerjaan dimulai, karena saat rencana disusun, Spark sudah mengetahui seluruh rantai yang diinginkan penulis.
Konsekuensinya, waktu yang terasa saat menulis transformasi hanyalah waktu membangun objek rencana yang ringan. Program dapat merakit rantai panjang dari data gigabita tanpa memproses satu baris pun, dan seluruh biaya komputasi berpindah ke baris action pertama. Skrip berikut memperlihatkan batas itu secara langsung, dengan mencetak penanda antara definisi dan eksekusi.
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
spark = (
SparkSession.builder
.master("local[2]")
.appName("peraga-lazy")
.config("spark.sql.shuffle.partitions", "2")
.getOrCreate()
)
df = spark.createDataFrame(
[(1, "Ali", "10A", 85), (2, "Budi", "10A", 90), (3, "Citra", "10B", 78), (4, "Dewi", "10B", 92)],
"id long, nama string, kelas string, nilai long",
)
print("=== transformasi didefinisikan, belum ada yang dihitung ===")
rantai = (
df.filter(F.col("nilai") >= 80)
.withColumn("beri_bonus", F.col("nilai") + 10)
.select("nama", "beri_bonus")
)
print("DataFrame rantai sudah terdefinisi di atas kertas, tanpa eksekusi data.")
print("=== action memicu eksekusi ===")
print("jumlah baris hasil:", rantai.count())
print("=== action kedua memakai ulang rencana yang sama ===")
rantai.orderBy("nama").show()
spark.stop()WARNING: Using incubator modules: jdk.incubator.vector
=== transformasi didefinisikan, belum ada yang dihitung ===
DataFrame rantai sudah terdefinisi di atas kertas, tanpa eksekusi data.
=== action memicu eksekusi ===
jumlah baris hasil: 3
=== action kedua memakai ulang rencana yang sama ===
+----+----------+
|nama|beri_bonus|
+----+----------+
| Ali| 95|
|Budi| 100|
|Dewi| 102|
+----+----------+Keluaran menegaskan urutan peristiwa. Kata terdefinisi tercetak sebelum data apa pun dihitung, karena tiga transformasi pada rantai hanya menambah simpul rencana. Eksekusi baru terjadi pada count(), dan setiap action berikutnya menghitung ulang rantai yang sama dari awal kecuali hasilnya di-cache, yang dibahas pada topik caching tersendiri. Action kedua pada skrip menghitung ulang karena tidak ada penyimpanan antara, dan perilaku itu normal pada Spark yang belum disuruh mengingat.
Lineage: pohon asal-usul partisi#
Karena perhitungan tidak dilakukan sebelum ada kebutuhan, Spark memelihara sesuatu yang lain sebagai penggantinya, yaitu lineage, catatan asal-usul setiap partisi hasil berupa rantai transformasi dari sumber data. Rencana bukan daftar instruksi berurutan biasa, melainkan graf asiklik terarah yang menyatakan data apa dibaca, difilter, ditransformasikan, lalu dipertukarkan antar partisi. Dari graf itu Spark tahu cara memproduksi ulang partisi mana pun bila diminta.
Sifat ini menjelaskan dua kebiasaan penting Spark. Pertama, kegagalan satu task tidak membatalkan seluruh pekerjaan, karena Spark cukup menghitung ulang partisi yang gagal dengan mengikuti lineage-nya. Kedua, DataFrame aman untuk dibagikan antar bagian program, karena bersifat immutable dan setiap cabang rencana hanya menambah simpul, bukan mengubah simpul lama. Mental model yang tepat karenanya bukan variabel yang menyimpan hasil, melainkan resep yang siap dieksekusi ulang dari sumber.
Menyaksikan rencana dengan explain#
Rencana yang disimpan Spark dapat dicetak dengan metode explain(), dan rencana itulah yang benar-benar dieksekusi ketika action datang. Bahasan lengkapnya ada pada topik job, stage, dan task, tetapi satu kebiasaan layak dimulai sejak sekarang, yaitu menjalankan explain() pada rantai yang dirakit untuk melihat bentuk akhirnya.
# tidak-dijalankan
rantai.explain()Pemanggilan itu sendiri adalah operasi yang murah karena hanya mencetak rencana, bukan mengeksekusi data. Dari cetakannya terlihat rangkaian operator yang sesuai transformasi yang ditulis, misalnya Filter, Project, dan Exchange. Perbandingan rencana dua versi kode adalah cara yang jujur untuk membuktikan mana yang lebih ringan. Kebiasaan ini menutup lingkaran pemahaman lazy, yaitu yang ditulis bukan yang dieksekusi, tetapi yang dieksekusi selalu dapat diperiksa melalui rencananya.
Kesalahan umum#
Kesalahan pertama adalah menafsirkan kecepatan definisi sebagai keberhasilan. Rantai transformasi yang salah kolomnya sering baru gagal pada action, dengan error yang merujuk rencana analisis, bukan baris transformasinya. Perbaikannya adalah membaca error dari action bersama rantai yang bersangkutan, karena lokasi error bukan lokasi penyebabnya.
Kesalahan kedua adalah memanggil action yang tidak perlu di tengah rantai, misalnya count() sekadar memastikan DataFrame berisi data. Setiap action menghitung ulang seluruh rantai di atasnya, sehingga kebiasaan memeriksa itu menggandakan pekerjaan pada data besar. Jika hasil memang dipakai berkali-kali, gunakan cache() yang dibahas pada topik caching, bukan action tambahan.
Kesalahan ketiga adalah mengira mengubah variabel DataFrame mengubah rencana yang sudah dieksekusi. DataFrame bersifat immutable, sehingga df = df.filter(...) membuat resep baru, dan pekerjaan yang sedang berjalan tidak terpengaruh. Pemahaman ini penting ketika membaca program lain, karena nama variabel yang sama dapat menampung rantai yang berbeda-beda sepanjang berkas.
Ringkasan#
- Transformasi hanya merakit rencana, sedangkan action seperti
count,show,collect, danwritememicu eksekusi seluruh rantai di atasnya. - Karena penundaan itu, Spark mengetahui seluruh maksud penulis sebelum eksekusi dan dapat menyusun rencana terbaiknya.
- Lineage adalah catatan asal-usul setiap partisi hasil, yang menjadikan perhitungan ulang parsial mungkin dan DataFrame aman dibagikan.
explain()mencetak rencana yang akan dieksekusi tanpa biaya komputasi data, dan menjadi alat pemeriksaan pertama pada rantai yang dirakit.
Referensi#
- Spark SQL Guide: Getting Started — Apache Spark — Pengenalan evaluasi lazy pada API DataFrame resmi.
- Tuning Guide: Spark SQL Performance — Apache Spark — Konteks rencana eksekusi yang disusun sebelum action dijalankan.
- RDD Programming Guide — Apache Spark — Penjelasan laziness dan lineage pada level mesin eksekusi Spark.