Sebelas chapter sepanjang section ini memperlengkapi kita dengan partition, join, streaming, shuffle, sampai manajemen memori Spark. Kini saatnya pertanyaan yang justru paling sering dijawab keliru di dunia nyata: apakah pekerjaan data di depan kita memang membutuhkan Spark? Jawaban jujurnya sering tidak — dan memilih alat yang lebih sederhana bukan kemunduran, melainkan tanda kedewasaan teknis. Pada chapter penutup ini kita mengukur biaya tetap Spark dengan benchmark nyata, membandingkannya dengan pandas dan DuckDB, lalu merangkum semuanya menjadi diagram keputusan yang bisa dipakai berulang.
Biaya Tetap yang Selalu Dibayar #
Setiap job Spark, sekecil apa pun datanya, membayar tagihan yang sama: startup JVM, perencanaan query, penjadwalan task, dan serialisasi data lintas proses. Di sisi lain, tool satu mesin tumbuh pesat — DuckDB meng-query file Parquet puluhan GB langsung dari disk tanpa server, pandas tetap andal untuk data yang muat di memori. Ketika data harian TokoKita hanya beberapa GB, seluruh biaya tetap Spark jatuh menjadi overhead murni. Ketika data ratusan GB lintas sumber dan butuh fault tolerance, biaya yang sama berubah menjadi investasi paralelisme. Ambangnya tidak bisa dikira-kira — mari kita ukur.
Benchmark Jujur di Mesin Kecil #
Skrip berikut menjalankan tugas yang identik di kedua engine: agregasi groupBy 200 ribu pesanan TokoKita menjadi jumlah dan total nilai per kota, lalu memverifikasi hasilnya sama:
"""Benchmark jujur: agregasi 200 ribu baris di pandas vs PySpark local[1]."""
import time
import pandas as pd
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
# --- pandas: agregasi groupBy di satu proses NumPy/pandas ---
df = pd.DataFrame({"pesanan_id": range(200_000)})
df["kota"] = df["pesanan_id"] % 8
df["nilai"] = (df["pesanan_id"] % 500) * 1000 + 15000
t0 = time.perf_counter()
hasil_pd = df.groupby("kota").agg(
jumlah=("pesanan_id", "size"), total=("nilai", "sum"))
durasi_pd = time.perf_counter() - t0
print(f"pandas groupBy 200 ribu baris : {durasi_pd:.3f} detik")
# --- PySpark: biaya startup sesi + agregasi yang sama ---
t0 = time.perf_counter()
spark = (SparkSession.builder
.master("local[1]")
.config("spark.ui.enabled", "false")
.getOrCreate())
spark.sparkContext.setLogLevel("ERROR")
startup = time.perf_counter() - t0
print(f"PySpark startup sesi JVM : {startup:.1f} detik")
pesanan = spark.range(200_000).select(
(F.col("id") % 8).alias("kota"),
((F.col("id") % 500) * 1000 + 15000).alias("nilai"),
)
t0 = time.perf_counter()
hasil_spark = (pesanan.groupBy("kota")
.agg(F.count("*").alias("jumlah"),
F.sum("nilai").alias("total"))
.orderBy("kota")
.collect())
durasi_spark = time.perf_counter() - t0
print(f"PySpark groupBy 200 ribu baris: {durasi_spark:.2f} detik")
# --- verifikasi hasil identik ---
sama = hasil_pd.sort_index()["jumlah"].tolist() == [r["jumlah"] for r in hasil_spark]
print(f"hasil kedua engine identik : {sama}")
print(f"jumlah baris per kota : {[r['jumlah'] for r in hasil_spark]}")
print(f"rasio Spark/pandas (agregasi) : {durasi_spark / durasi_pd:.0f}x")
spark.stop()Output yang Dihasilkan:
pandas groupBy 200 ribu baris : 0.014 detik
PySpark startup sesi JVM : 5.1 detik
PySpark groupBy 200 ribu baris: 2.43 detik
hasil kedua engine identik : True
jumlah baris per kota : [25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000]
rasio Spark/pandas (agregasi) : 172xMari kita telaah baris-baris kode di atas:
- Baris 9-11 (pembangunan data pandas): 200 ribu baris dibuat langsung sebagai kolom Series; operasi modulo per kolom berjalan vektor di dalam satu proses, tanpa serialisasi ke mana pun.
- Baris 13-17 (pengukuran pandas):
groupby().agg()diukur denganperf_counter— seluruh hasil 8 kota selesai dalam hitungan milidetik. - Baris 20-27 (pengukuran startup):
SparkSession.getOrCreate()diukur terpisah karena inilah biaya tetap yang tidak bisa dihindari job Spark apa pun. - Baris 34-41 (pengukuran Spark): agregasi yang identik plus
collect()— pengukuran mencakup perencanaan query, penjadwalan task, eksekusi, dan pengumpulan hasil. - Baris 44-47 (verifikasi): memastikan kedua engine menghasilkan 25.000 baris per kota yang sama, lalu mencetak rasio durasinya.
Hasilnya tegas: untuk 200 ribu baris, pandas menyelesaikan agregasi 172 kali lebih cepat, dan Spark masih harus membayar 5,1 detik startup sesi sebelum baris pertama diproses (run ini memakai JVM yang sudah hangat; run pertama di mesin yang sama biasanya memakan waktu berlipat). Angka pastinya bergeser antar mesin dan antar run — itulah mengapa klaim performa selalu perlu diukur ulang di lingkungan masing-masing — tetapi polanya kokoh: pada data kecil, overhead Spark jauh melampaui pekerjaannya sendiri.
Spark versus pandas versus DuckDB #
| Aspek | pandas | DuckDB | Spark |
|---|---|---|---|
| Model eksekusi | satu proses, di memori | satu proses, mampu out-of-core | lintas executor di cluster |
| Batas data praktis | memori mesin | disk satu mesin (puluhan-hingga-ratusan GB) | petabyte |
| Startup | nyaris instan | instan | detik sampai menit |
| Keunggulan | ekosistem Python, eksplorasi | SQL analitik langsung di file | skala, fault tolerance, streaming |
| Dokumentasi | pandas docs | DuckDB docs | Spark docs |
Diagram Keputusan #
Pertanyaan di diagram ini berurutan karena sengaja: ukuran data ditanya lebih dulu sebelum kebutuhan lain, sebab alat satu mesin menang telak pada data kecil apa pun fitur yang diinginkan:
Dua catatan menemani diagram. Pertama, ambang 10 GB dan 500 GB adalah titik awal diskusi, bukan hukum — pergeserannya bergantung pada kompleksitas transformasi, laju pertumbuhan data, dan keahlian tim. Kedua, Spark dalam diagram selalu berarti cluster: menghidupkan Spark di satu mesin untuk data satu mesin hanya menggabungkan kelemahan kedua dunia, seperti yang dibuktikan benchmark di atas.
Skenario Overkill dan Skenario Tepat #
Skenario ketika Spark justru merepotkan:
- Laporan bulanan dari CSV 2 GB — DuckDB menyelesaikannya dalam hitungan detik tanpa infrastruktur.
- Eksplorasi interaktif beberapa juta baris — pandas atau Polars lebih responsif dan lebih akrab bagi analis.
- Pipeline sekali jalan tanpa jadwal ulang — biaya startup dan konfigurasi tidak akan terbayar.
Skenario ketika Spark memang tepat:
- Ratusan GB sampai TB data harian lintas sumber, melebihi mesin mana pun.
- Kebutuhan fault tolerance: job panjang yang harus lanjut dari checkpoint saat node gagal.
- Streaming yang menyatu dengan batch — satu set kode DataFrame untuk dua dunia, seperti yang kita praktikkan di chapter Spark Structured Streaming.
- Ekosistem lakehouse: integrasi dengan tabel Parquet terpartisi dan table format modern.
Mengetahui kapan berhenti memakai Spark menuntut pula mengenal padanan streaming dan query engine di luar Spark. Section course Streaming Data membedah ekosistem pemrosesan event secara langsung, dan section Database Analitik dan Query Engine membahas DuckDB, Trino, ClickHouse, beserta kawan sejawatnya yang hari ini bersanding dengan Spark di lanskap data engineering.