Satu komputer yang kuat tetap memiliki batas, yaitu kapasitas memori, jumlah inti prosesor, dan waktu yang tersedia. Ketika data yang harus diolah tumbuh melampaui batas tersebut, pekerjaan tidak lagi bisa diselesaikan dengan menaikkan spesifikasi mesin. Topik ini membahas alasan munculnya komputasi terdistribusi, cara memecah pekerjaan menjadi potongan yang dapat diolah paralel, serta demonstrasi paralelisme dengan Python pada lima juta angka.
Batas memori dan waktu satu mesin#
Batas mesin tunggal muncul dari tiga sumber daya yang tumbuh jauh lebih lambat daripada data, yaitu memori, kemampuan komputasi, dan waktu. Pemrosesan data analitik biasanya menginginkan dataset berada dalam memori kerja (RAM) agar diolah dengan cepat, sedangkan RAM satu server standar berhenti pada belasan sampai puluhan gigabyte. Dataset yang lebih besar dari itu memaksa pemrosesan turun ke disk, dan kecepatannya jatuh drastis karena pembacaan disk jauh lebih lambat daripada memori.
Jalur pertama yang biasanya dicoba adalah penskalaan vertikal (scale up), yakni membeli mesin dengan RAM dan prosesor yang lebih besar. Jalur ini bekerja sampai titik tertentu, tetapi biayanya naik tidak linier karena mesin dengan spesifikasi ekstrem jauh lebih mahal per gigabyte daripada mesin biasa. Titik jenuhnya juga tetap ada karena tidak ada mesin yang memorinya tak terbatas.
Penskalaan horizontal (scale out) memilih arah yang berbeda, yakni menambah jumlah mesin. Sepuluh mesin sedang yang bekerja bersama lebih murah dan lebih mudah diperluas daripada satu mesin raksasa. Kumpulan mesin yang bekerja sebagai satu sistem disebut cluster, dan pola kerja yang membagi satu pekerjaan ke banyak mesin disebut komputasi terdistribusi. Apache Spark adalah framework, yakni kerangka perangkat lunak yang mengurus mekanisme komputasi terdistribusi, dan modul ini membahasnya pada tingkat kesadaran.
Memecah pekerjaan menjadi chunk#
Komputasi terdistribusi menjadi mungkin ketika satu pekerjaan dapat dipecah menjadi potongan data yang saling independen, yang disebut chunk. Operasi agregasi seperti penjumlahan dan penghitungan bersifat dapat diurai, artinya hasil keseluruhan diperoleh dengan menghitung tiap chunk secara terpisah lalu menggabungkan hasilnya. Penjumlahan lima juta angka, misalnya, menghasilkan nilai yang sama apakah dihitung sekaligus atau dihitung per potongan lima ratus ribu angka lalu dijumlahkan kembali.
Tidak semua operasi memiliki sifat ini. Median seluruh data tidak dapat diperoleh dengan menggabungkan median tiap chunk, sehingga komputasi terdistribusi menuntut kehati-hatian dalam memilih cara penggabungan hasilnya. Untuk operasi yang dapat diurai, syarat pembagiannya sederhana, yaitu batas antar chunk tidak boleh tumpang tindih dan tidak boleh ada bagian data yang terlewat.
Setelah data terpecah, chunk dapat dikerjakan secara paralel karena tidak saling bergantung. Pada satu mesin, paralelisme dijalankan oleh beberapa proses yang berbagi RAM dan prosesor yang sama. Pada cluster, chunk yang sama didistribusikan ke mesin yang berbeda, dan di situlah pertukaran data lewat jaringan serta ketahanan terhadap kegagalan (fault tolerance) menjadi bagian dari pekerjaan itu sendiri.
Demonstrasi paralelisme pada satu mesin#
Skrip berikut memproses lima juta angka dengan dua cara, yaitu serial dalam satu proses dan paralel melalui empat proses pekerja pada modul multiprocessing. Rentang angka dibagi menjadi sepuluh chunk berukuran lima ratus ribu, kemudian total penjumlahan kedua cara dibandingkan.
import multiprocessing
JUMLAH_ANGKA = 5_000_000
UKURAN_CHUNK = 500_000
JUMLAH_PEKERJA = 4
def jumlah_rentang(awal: int, akhir: int) -> int:
"""Menghitung jumlah seluruh angka pada rentang [awal, akhir)."""
return sum(range(awal, akhir))
def batas_chunk() -> list[tuple[int, int]]:
"""Membagi rentang data menjadi chunk [awal, akhir) berukuran tetap."""
chunks = []
for awal in range(0, JUMLAH_ANGKA, UKURAN_CHUNK):
akhir = min(awal + UKURAN_CHUNK, JUMLAH_ANGKA)
chunks.append((awal, akhir))
return chunks
if __name__ == "__main__":
chunks = batas_chunk()
# eksekusi serial: seluruh chunk dikerjakan berurutan di satu proses
total_serial = sum(jumlah_rentang(awal, akhir) for awal, akhir in chunks)
# eksekusi paralel: chunk didistribusikan ke sejumlah pekerja tetap
with multiprocessing.Pool(JUMLAH_PEKERJA) as pool:
hasil = pool.starmap(jumlah_rentang, chunks)
total_paralel = sum(hasil)
print(f"jumlah angka : {JUMLAH_ANGKA}")
print(f"ukuran chunk : {UKURAN_CHUNK}")
print(f"jumlah chunk : {len(chunks)}")
print(f"jumlah pekerja : {JUMLAH_PEKERJA}")
print(f"total serial : {total_serial}")
print(f"total paralel : {total_paralel}")
print(f"hasil sama : {total_serial == total_paralel}")jumlah angka : 5000000
ukuran chunk : 500000
jumlah chunk : 10
jumlah pekerja : 4
total serial : 12499997500000
total paralel : 12499997500000
hasil sama : TrueKeluaran menunjukkan kedua cara menghasilkan total yang persis sama, dan itu adalah bukti bahwa pembagian chunknya benar, yaitu tidak ada angka yang terhitung dua kali atau terlewat. Pool dengan empat pekerja mendistribusikan sepuluh chunk ke empat proses, sedangkan jumlah inti prosesor yang tersedia menentukan seberapa banyak chunk yang benar-benar dikerjakan serentak. Pada beban sekecil ini, biaya mengoordinasikan proses hampir sama besarnya dengan pekerjaannya sendiri, sehingga kecepatan belum tentu meningkat. Manfaat paralelisme baru terasa ketika beban per chunk jauh lebih besar daripada biaya koordinasinya.
Dari satu mesin ke cluster#
Persoalan berpindah ke tingkat berikutnya ketika chunk yang harus dipegang setiap proses tidak lagi muat di memori satu mesin. Pada cluster, chunk disimpan di mesin yang memprosesnya, sehingga total memori yang tersedia menjadi jumlah memori seluruh mesin. Konsekuensinya, pertukaran data antarmesin harus melewati jaringan yang jauh lebih lambat daripada pertukaran antarproses, dan salah satu mesin dapat gagal di tengah pekerjaan.
Masalah jaringan dan kegagalan itulah yang diselesaikan framework komputasi terdistribusi seperti Spark. Framework mengurus pembagian data, penjadwalan pekerjaan ke mesin yang tersedia, dan pengulangan pekerjaan yang gagal, sehingga kode aplikasi cukup menyatakan transformasi yang diinginkan. Task yang gagal pada satu mesin dapat diulang pada mesin lain tanpa mengulang seluruh pekerjaan, sehingga kegagalan tunggal tidak membatalkan hasil yang sudah diperoleh. Reis dan Housley menempatkan mesin pemrosesan terdistribusi sebagai komponen tersendiri di dalam data stack modern, terpisah dari alat analisis, justru karena kemampuan tersebut. Pemahaman atas biaya tambahan ini sekaligus menjadi dasar untuk menilai kapan komputasi terdistribusi memang diperlukan.
Kesalahan umum#
Kesalahan berikut paling sering muncul ketika pola chunk baru dipraktikkan, biasanya karena kebiasaan menulis program serial masih terbawa, dan setiap butir menyertakan perbaikannya.
- Menggabungkan hasil agregat yang tidak dapat diurai. Median per chunk tidak dapat digabung menjadi median keseluruhan, sehingga hasil akhirnya menyesatkan. Perbaikannya adalah memastikan operasi bersifat dapat diurai, atau menyimpan hasil per chunk dan menghitung agregat yang tidak dapat diurai dari data aslinya.
- Membagi rentang data dengan batas yang tumpang tindih. Angka pada batas rentang terhitung dua kali dan totalnya meleset. Perbaikannya adalah memakai batas akhir yang eksklusif pada setiap chunk sebagaimana
range(awal, akhir)pada skrip demonstrasi. - Mengasumsikan versi paralel selalu lebih cepat. Biaya koordinasi proses dapat melebihi pekerjaannya pada data yang kecil. Perbaikannya adalah mengukur durasi versi serial pada ukuran data yang nyata terlebih dahulu, lalu menilai apakah biaya distribusinya terbayar.
Ringkasan#
- Penskalaan vertikal sampai pada titik jenuh karena memori dan biayanya, sehingga data skala besar memaksa penskalaan horizontal ke banyak mesin.
- Komputasi terdistribusi dimungkinkan oleh operasi yang dapat diurai, yaitu hasil keseluruhan diperoleh dari penggabungan hasil per chunk dengan batas yang tidak tumpang tindih.
- Demonstrasi
multiprocessingmembagi lima juta angka menjadi sepuluh chunk dan menghasilkan total yang identik antara eksekusi serial dan paralel. - Pada cluster, framework seperti Spark mengurus distribusi data, penjadwalan, dan ketahanan terhadap kegagalan yang tidak muncul pada satu mesin.
Referensi#
- Fundamentals of Data Engineering — O’Reilly — Buku Reis dan Housley yang membingkai posisi mesin pemrosesan terdistribusi di dalam data engineering.
- multiprocessing — Process-based parallelism — Python documentation
— Dokumentasi resmi modul
multiprocessingyang dipakai pada demonstrasi paralelisme.