Dasar-dasar AI
Apa yang Sebenarnya Terjadi Saat Anda Menjalankan Job Spark?

Pendahuluan
Seiring bertambahnya ukuran kumpulan data, memprosesnya secara efisien di satu mesin bisa menjadi tantangan karena keterbatasan kapasitas CPU, memori, dan kecepatan transfer penyimpanan.
Apache Spark mengatasi tantangan ini dengan menyediakan mesin pemrosesan data terdistribusi yang dapat membagi beban kerja menjadi partisi dan menjalankan tugas secara paralel di beberapa inti CPU dan mesin.
Namun, Spark bukan sekadar soal menambahkan lebih banyak mesin.
Memahami cara kerja berbagai komponennya—driver, executor, RDD, DataFrame, partisi, job, stage, dan task—sangat penting untuk merancang pipeline data yang efisien dan andal.
Dalam artikel ini, saya akan membahas arsitektur Spark, mekanisme eksekusi, dan pertimbangan utama terkait performa dengan menggunakan contoh praktis agregasi penjualan e-commerce.
1. Mengapa Apache Spark: Pemrosesan Data Paralel
Apache Spark adalah mesin komputasi terdistribusi yang dirancang untuk pemrosesan data dan rekayasa data berskala besar.
Spark memproses data besar dengan memanfaatkan pemrosesan data paralel:

Gambar A. Arsitektur tingkat tinggi klaster terdistribusi Apache Spark (klaster, driver, dan node worker), yang membandingkan eksekusi pada satu node (Dibuat oleh Kuriko IWAI)
Berbeda dengan pemrosesan pada satu node (kanan, Gambar A), Spark membagi pekerjaan menjadi beberapa task yang menggunakan banyak node, lalu menjalankannya di berbagai sumber daya pada sejumlah mesin, jika tersedia.
Dalam konteks ini, terdistribusi tidak berarti harus menggunakan banyak mesin. Spark dapat dijalankan dalam mode lokal pada satu mesin. Bahkan dalam mode tersebut, data dapat dipartisi dan diproses secara paralel.
2. Arsitektur Inti Apache Spark
Spark terdiri atas beberapa komponen utama:
- Driver Spark (driver): Proses pusat yang mengoordinasikan aplikasi Spark.
- Pengelola klaster: Layanan eksternal yang bertanggung jawab mengalokasikan sumber daya perangkat keras, seperti inti CPU dan memori.
- Node worker (worker): Mesin fisik/virtual atau node komputasi di dalam klaster.
- Executor: Proses worker terdistribusi yang dijalankan pada node worker.
- Java Virtual Machine (JVM): Lingkungan runtime yang diperlukan untuk menjalankan bytecode Spark (karena Spark ditulis dalam Scala/Java).
Pertama, driver mengoordinasikan aplikasi Spark dan mengubahnya menjadi graf asiklik berarah (DAG) yang terdiri atas task eksekusi fisik, lalu menjadwalkan task tersebut di executor.
Selanjutnya, pengelola klaster mengalokasikan sumber daya sesuai sistem pengelolaan sumber daya dalam penerapan tersebut.
Node worker menyediakan sumber daya komputasi bagi executor, yang menjalankan task dan menyimpan data selama task berlangsung.
Beberapa lingkungan terkelola, seperti Databricks, menggunakan satu executor untuk setiap worker, tetapi ini bukan aturan yang berlaku universal di Spark. Satu worker dapat menampung beberapa executor, bergantung pada konfigurasi klaster.
2-1. Contoh Kasus E-Commerce: Struktur Data Masukan
Bayangkan sebuah perusahaan e-commerce butik.
Perusahaan itu memiliki 10,000 catatan penjualan, yang masing-masing mencantumkan kode unit penyimpanan stok (SKU) dan jumlah unit yang terjual. Anggap ada tiga kemungkinan SKU: A, B, dan C.
| SKU | Jumlah Catatan | Kuantitas | Jumlah yang Diharapkan |
|---|---|---|---|
| A | 4,000 | 2 | 8,000 |
| B | 3,500 | 3 | 10,500 |
| C | 2,300 | 4 | 9,200 |
| Tidak Valid/Retur | 200 | -1 | -200 |
| Total Keseluruhan | 10,000 |
Tabel 1. Contoh perincian catatan penjualan e-commerce dan agregasi yang diharapkan.
Tujuan kita adalah menghitung total unit valid yang terjual untuk setiap SKU:
- Saring catatan yang
qty <= 0, lalu - Agregasikan sampel berdasarkan SKU.
Dalam kumpulan data lengkap sebagai ilustrasi, anggap 9,800 dari 10,000 catatan tersisa setelah penyaringan. Berikut total akhir yang diharapkan dari Spark:
[("A", 8000), ("B", 10500), ("C", 9200)]
2-2. Klaster Spark
Saya akan menetapkan konfigurasi klaster sederhana untuk contoh kasus ini.
Node Worker (Worker)
Saya akan menetapkan 3 node worker, masing-masing dengan tipe instans 8 vCPU dan RAM 32 GiB:

Gambar B. Alokasi sumber daya di seluruh node worker (Dibuat oleh Kuriko IWAI)
Secara keseluruhan, tersedia 24 vCPU di dalam sistem.
Alokasi Eksekutor Spark dan CPU/Memori
Misalnya, saya akan mengalokasikan:
- 7 eksekutor.
- 2 inti vCPU untuk setiap eksekutor.
- Memori heap JVM sebesar 3GiB untuk setiap eksekutor.
Aplikasi Spark meminta pengelola klaster untuk mengalokasikan sumber daya ke 7 eksekutor ini.
Selanjutnya, pengelola klaster menentukan alokasi CPU dan memori berdasarkan penggunaan sumber daya setiap worker.
Sebagai contoh, pengelola klaster memutuskan untuk membuat 14 slot tugas — 2 inti vCPU per eksekutor x 7 eksekutor:

Gambar C-1. Alokasi inti vCPU, memori, dan eksekutor di berbagai worker dengan ketimpangan sumber daya di Worker A
Perhatikan bahwa memori 3 GiB yang dialokasikan untuk eksekutor bukanlah total memorinya, karena eksekutor juga harus menyediakan memori tambahan untuk worker dan proses lainnya.
Dengan asumsi satu tugas memerlukan 1 vCPU (spark.task.cpus = 1), setiap worker mengalokasikan vCPU-nya:

Gambar C-2. Alokasi vCPU paralel di berbagai worker
Hal ini menunjukkan dua poin utama:
- Setiap worker memiliki tingkat penggunaan CPU yang unik (Dalam Gambar C-2, penggunaan Worker A adalah 75%, sedangkan Worker B dan C tetap 50%).
- Meskipun sistem memiliki 24 inti vCPU (8 vCPU/worker x 3 worker), sistem hanya dapat menggunakan 14 inti vCPU; 10 inti sisanya tidak digunakan untuk tugas-tugas ini.
2-3. Java Virtual Machine (JVM) di Balik Spark
Apache Spark dibangun di atas Java Virtual Machine (JVM).
JVM adalah lingkungan runtime yang diperlukan untuk menjalankan bytecode Spark.
Karena Spark ditulis dalam Scala/Java:
- Saat menggunakan Scala atau Java dalam aplikasi: Kode berjalan secara native di dalam JVM.
- Saat menggunakan PySpark (Python): Proses Python menjalankan kode, tetapi berkomunikasi dengan driver berbasis JVM melalui jembatan seperti Py4J. Eksekutor Spark pada node worker tetap berjalan di dalam JVM, yang kemudian mengelola worker Python untuk menjalankan kode Python dan fungsi buatan pengguna Python (Python UDF).
Dalam kedua kasus, JVM mengelola utas eksekusi, alokasi memori, dan pengumpulan sampah untuk driver maupun eksekutor.
JVM Driver dan Eksekutor dalam Aplikasi PySpark
Dalam aplikasi PySpark, driver Python berkomunikasi dengan driver Spark yang berjalan di JVM;
- Driver Python: Tempat kode PySpark tingkat aplikasi ditulis dan dijalankan.
- JVM Driver: Mengoordinasikan eksekusi dan penjadwalan Spark.
- JVM Eksekutor: Menjalankan tugas Spark dan mengelola sumber daya eksekusi. Proses worker Python digunakan saat tugas memerlukan eksekusi Python.
- JVM Klien (proses klien atau mesin pengirim): JVM yang berjalan di mesin lokal atau node tepi/gateway.
Seperti ditunjukkan pada gambar, JVM klien terlebih dahulu terhubung ke JVM driver, yang kemudian berkoordinasi dengan JVM-JVM eksekutor:

Gambar D. Hubungan keseluruhan antara JVM klien, driver, dan eksekutor
Dari sini, JVM driver menjadi tempat Spark Driver berjalan untuk mengelola aplikasi, sedangkan JVM-JVM eksekutor menjadi tempat eksekutor masing-masing berjalan untuk menjalankan tugas-tugas tersebut.
Setiap driver JVM memiliki heap JVM untuk menangani pemrosesan data, penyimpanan dalam cache, dan objek pengguna secara efisien.
2-4. Pembahasan Mendalam tentang Resilient Distributed Dataset (RDD)
Resilient Distributed Dataset (RDD) adalah kumpulan elemen yang tidak dapat diubah dan dibagi menjadi partisi yang dapat diproses Spark secara paralel.
RDD melacak dependensi antartransformasi, sehingga Spark dapat menghitung ulang partisi yang hilang dalam berbagai skenario kegagalan.
Nama ini mencakup tiga karakteristik:
- Tangguh: Spark mencatat linimasa—RDD mana yang bergantung pada RDD sebelumnya—dan sering kali dapat merekonstruksi partisi yang hilang alih-alih menyimpan replika setiap hasil perantara.
- Terdistribusi: Kumpulan data direpresentasikan sebagai partisi yang dapat diproses Spark pada eksekutor yang berbeda. Beberapa mesin fisik tidak diwajibkan; mode lokal juga mendukung RDD.
- Kumpulan data: RDD dapat berisi pasangan kunci-nilai, bilangan bulat, string, tupel, dan elemen lain yang didukung—bukan hanya baris relasional. API RDD mencakup operasi seperti
map,filter,reduceByKey,join, danaggregateByKey.
RDD adalah abstraksi logis untuk kumpulan dan komputasi, bukan pernyataan bahwa partisi tertentu secara permanen ditetapkan kepada pekerja tertentu.
Partisi
Partisi adalah bagian logis dari data dan biasanya berhubungan dengan satu tugas dalam suatu tahap.
Misalkan kita membuat 100 partisi (100 partisi awal ini merupakan pilihan eksplisit untuk tujuan pengajaran).
Dengan 10.000 elemen sumber, jumlahnya sekitar 100 elemen per partisi jika masukan didistribusikan kurang lebih secara merata:

Gambar E. Alokasi partisi secara paralel pada 100 pemisahan data awal
3. API RDD vs API DataFrame: Optimisasi Internal Catalyst
Contoh RDD awal membantu memahami lineage, partisi, dan shuffle.
Namun, dalam ETL terstruktur yang umum, API DataFrame menjadi titik awal yang lebih disarankan karena menyediakan kolom, informasi skema, ekspresi mirip SQL, dan operasi yang dapat dikenali oleh pengoptimal.
Diagram di bawah ini menunjukkan hubungan tersebut:

Gambar F. Hubungan antara konsep Graf Spark, lineage RDD, DataFrame, dan DAG Eksekusi Spark
Sama seperti RDD memiliki lineage RDD untuk mendefinisikan transformasi, rencana kueri DataFrame merepresentasikan kueri terstrukturnya.
Spark menyusun pekerjaan eksekusi berdasarkan dependensi dan operator fisik.
Eksekusi DataFrame mungkin menggunakan mekanisme berbasis RDD secara internal, tetapi itu bukan berarti pengguna perlu menyusun lineage RDD secara manual saat menulis kode DataFrame.
3-1. Rencana Kueri DataFrame
Rencana kueri DataFrame adalah urutan langkah logis dan fisik yang dihasilkan oleh mesin eksekusi (kotak ungu pada Gambar L).
Kueri DataFrame melewati beberapa representasi:
- Rencana logis: Deskripsi operasi yang diminta (pemindaian, pemfilteran, pengelompokan, agregasi).
- Rencana logis yang dianalisis dan dioptimalkan: Spark menyelesaikan referensi dan menerapkan optimisasi logis yang sesuai, seperti pemangkasan kolom atau pendorongan predikat.
- Rencana fisik: Spark memilih operator yang dapat dieksekusi, misalnya pemindaian, agregasi parsial/final, pertukaran, penggabungan, dan pengurutan.
- Eksekusi: Rencana fisik dijalankan menggunakan mekanisme eksekusi Spark, yang pada akhirnya melibatkan tugas-tugas pada eksekutor.
Dalam kasus penggunaan kita, rencana fisik dapat mencakup langkah-langkah berikut:
- Agregasi hash parsial,
- Pertukaran berdasarkan SKU, dan
- Agregasi hash final.
3-2. Membandingkan Tindakan: API RDD vs API DataFrame
Mari kita bandingkan API RDD dan API DataFrame dengan menggunakan agregasi SKU sebagai contoh.
API RDD
Elemennya adalah (sku, qty) pasangan. Logika agregasi Python kita dinyatakan secara eksplisit, dan Spark mengikuti dependensi RDD:
result_rdd = (
raw_rdd
.filter(lambda row: row[1] > 0)
.reduceByKey(lambda a, b: a + b, numPartitions=4)
)
rdd_results = result_rdd.collect()
# Example:
# [('A', 8000), ('B', 10500), ('C', 9200)]
API DataFrame
Anggaplah sales_df memiliki kolom sku (string) dan qty (numerik):
from pyspark.sql import functions as F
result_df = (
sales_df
.filter(F.col("qty") > 0)
.groupBy("sku")
.agg(F.sum("qty").alias("total_qty"))
)
df_results = result_df.collect()
# Example:
# [Row(sku='A', total_qty=8000),
# Row(sku='B', total_qty=10500),
# Row(sku='C', total_qty=9200)]
Di sini, kedua contoh menyelesaikan masalah bisnis yang sama:
Memfilter catatan penjualan yang tidak valid dan mengagregasikan kuantitas valid berdasarkan SKU.
Dan dalam kedua kasus, collect() memicu eksekusi Spark dan mengembalikan semua catatan hasil ke Driver.
Perbedaan utamanya terletak pada representasi hasil:
- API RDD: Mengembalikan elemen Python, seperti
(sku, total_qty)tuple. - API DataFrame: Mengembalikan objek
Rowterstruktur dengan field bernama, sepertiskudantotal_qty.
Berikut perbandingan keseluruhan antara API RDD dan DataFrame:
| API RDD | API DataFrame / SQL | |
|---|---|---|
| Representasi Data | Elemen umum, termasuk objek arbitrer yang didukung | Baris dengan kolom bernama dan skema |
| Gaya Pemrograman | Transformasi tingkat rendah | Transformasi relasional deklaratif |
| Agregasi | Ya: reduceByKey, aggregateByKey, dan sebagainya. |
Ya: groupBy, agg, SQL |
| Optimisasi Kueri | Informasi semantik relasional yang lebih sedikit terungkap | Optimasi Catalyst dan eksekusi fisik |
| Penggunaan Umum | Pemrosesan tingkat rendah khusus / pengajaran mekanisme eksekusi | ETL terstruktur, analitik, rekayasa fitur |
| Eksekusi | Job, tahap, tugas Spark | Job, tahap, tugas Spark |
Tabel 2. RDD API vs. DataFrame API – perbandingan fitur dan abstraksi: (Dibuat oleh Kuriko IWAI)
4. Penutup
Tujuan penyetelan Spark bukanlah memaksimalkan jumlah partisi atau penggunaan CPU secara terpisah.
Tujuannya adalah mengurangi biaya dan latensi eksekusi dari awal hingga akhir tanpa menimbulkan ketidakstabilan.
Dalam artikel ini, kita mempelajari hubungan-hubungan inti dalam sistem Spark:
- Sumber daya: Worker menampung executor; executor menjalankan tugas; konfigurasi CPU dan memori membatasi konkurensi.
- Pemrograman RDD: Transformasi menghasilkan definisi RDD yang tidak dapat diubah beserta lineage; tindakan memicu komputasi.
- Pemrograman DataFrame: Transformasi menghasilkan operasi kueri logis; optimasi memilih operator fisik yang dapat dieksekusi; tindakan memicu komputasi.
- Eksekusi: Spark mengatur pekerjaan ke dalam job, tahap, dan tugas, sementara tugas memproses partisi pada executor. Shuffle dan dependensi lainnya menciptakan batas koordinasi antartahap.
Memahami cara Spark mengeksekusi beban kerja terdistribusi hanyalah landasannya.
Anda dapat mempelajari alur komputasi tujuh langkah menggunakan RDD API.












