1 Identitas dan Judul Dokumen

Keterangan Isi
Mata kuliah Pengantar Big Data
Nama Alya Maura Raditha
NIM 52240003
Program studi Sains Data, Institut Teknologi Sains Bandung (ITSB)
Topik Studi kasus aplikasi dompet digital BayarYuk
Teknologi Python, PySpark (Apache Spark), MongoDB, R Markdown

2 Pendahuluan

Big Data tidak hanya berarti data yang besar. Istilah ini merujuk pada data yang volumenya, kecepatan datangnya, dan ragam bentuknya sudah melampaui kemampuan satu mesin atau satu basis data relasional. Karena itu satu analisis sering harus menggabungkan beberapa teknologi, dan dokumen ini memperlihatkan hal tersebut dalam satu alur kerja:

  1. MongoDB menyimpan data merchant yang strukturnya fleksibel (schema-flexible).
  2. Apache Spark (RDD) mengolah log transaksi berukuran 400.000 baris yang sebagian barisnya rusak.
  3. Spark DataFrame dan Spark SQL mengintegrasikan data merchant dengan transaksi dan menghasilkan agregasi.
  4. Spark Structured Streaming meng-ingest transaksi baru dari landing zone secara bertahap dan menyimpannya ke format Parquet.
  5. Exploratory Data Analysis (EDA) pada data skala besar menghasilkan insight untuk keputusan bisnis.

Dokumen ini disusun dalam urutan pengerjaan: konsep dasar, lalu implementasi C1 sampai C5. Setiap bagian implementasi memuat tujuan, implementasi, serta hasil dan interpretasi. Seluruh angka pada dokumen ini keluar dari eksekusi kode saat dokumen di-knit, bukan diketik manual.

3 Deskripsi Studi Kasus BayarYuk

BayarYuk adalah aplikasi dompet digital dengan beragam aktivitas transaksi. Tim data BayarYuk diminta menganalisis tiga sumber data yang karakteristiknya berbeda, yaitu data merchant, log transaksi, dan aliran transaksi baru (streaming). Tujuannya:

  • mengolah data dari berbagai sumber;
  • menangani masalah kualitas data (nilai sistem error, nilai ekstrem, dan baris rusak);
  • mengagregasi transaksi dan mengintegrasikannya dengan data merchant;
  • membangun proses ingestion yang tahan gagal (fault tolerant);
  • menghasilkan insight yang mendukung keputusan bisnis.

4 Deskripsi Sumber Data dan Karakteristik Dataset

Sumber Wadah Karakteristik Dipakai pada
A. Merchant MongoDB, db["merchant"] 200 dokumen; _id, nama, kategori (5 jenis), kota (5 kota), rating, promo (array 0–2 elemen); sekitar 30% memiliki jam_buka C1, C3
B. Log transaksi File teks, variabel jalur_log 400.000 baris, pemisah \|: waktu\|id_trx\|id_user\|id_merchant\|nominal\|status; ada nominal -1, nominal Rp250.000.000, dan baris rusak pada setiap baris ke-5.000 C2, C3, C5
C. Landing zone Folder berisi 3 file JSON 3 file × 500 event = 1.500 event; id_trx, id_merchant, nominal, waktu; sekitar 3% nominal bernilai null C4

4.1 Persiapan lingkungan dan pembangkitan dataset

Pada tugas ini tidak ada file kode awal, sehingga seluruh dataset dibangkitkan sendiri oleh kode di bawah ini sesuai spesifikasi di atas. Semua proses acak memakai seed tetap (SEED = 42), sehingga hasilnya sama setiap kali dokumen dijalankan ulang. Dataset ini bersifat simulasi: pola di dalamnya (misalnya jam ramai atau merchant yang sering gagal) dirancang oleh pembangkit data, sehingga kesimpulan analisis berlaku untuk data simulasi ini saja.

4.1.1 Konfigurasi yang perlu disesuaikan

Konfigurasi Nilai bawaan Cara menyesuaikan
Python python3 pada PATH set BAYARYUK_PYTHON (lihat chunk setup)
Alamat MongoDB mongodb://localhost:27017 set variabel lingkungan MONGO_URI (contoh MongoDB Atlas: mongodb+srv://user:pass@cluster/...)
Folder data data_bayaryuk/ di samping file .Rmd set BAYARYUK_DATA_DIR
Java JDK 17 atau 21 (JAVA_HOME terpasang) wajib untuk Spark; cek dengan java -version
Versi Spark mengikuti paket pyspark terpasang pip install pyspark pymongo numpy pandas matplotlib
Windows perlu winutils.exe dan HADOOP_HOME untuk menulis Parquet atau jalankan lewat WSL2

4.1.2 Dataset A: merchant (MongoDB)

## {'ok': 1.0}
## Terhubung ke MongoDB 9.0.4 | dokumen merchant: 200

4.1.3 Dataset B: log transaksi (file teks)

## Log transaksi ditulis ke D:/Intro to Big Data/UTS/data_bayaryuk/log/transaksi.log (22.9 MB, 1.9 detik)

4.1.4 Dataset C: landing zone (3 file JSON)

## File pada folder landing: ['transaksi_stream_1.json', 'transaksi_stream_2.json', 'transaksi_stream_3.json']

4.2 Verifikasi dataset hasil pembangkitan

Sebelum analisis, setiap karakteristik pada spesifikasi diperiksa ulang secara independen: log dan JSON dibaca dengan Python murni (bukan Spark) dan MongoDB dihitung dengan query-nya sendiri.

## == A. MERCHANT (MongoDB) ==
## Jumlah dokumen            : 200
## Kategori distinct         : 5 ['Elektronik', 'Fashion', 'Kesehatan', 'Kuliner', 'Transportasi']
## Kota distinct             : 5 ['Bandung', 'Jakarta', 'Medan', 'Surabaya', 'Yogyakarta']
## Memiliki jam_buka         : 60 (30%)
## Panjang array promo       : {0: 55, 1: 68, 2: 77}
## Contoh dokumen            : {'_id': 'M003', 'nama': 'Klinik Sentosa 003', 'kategori': 'Kesehatan', 'kota': 'Jakarta', 'rating': 3.6, 'promo': [], 'jam_buka': {'buka': '08:00', 'tutup': '22:00'}}
## 
## == B. LOG TRANSAKSI ==
## Jumlah baris              : 400000
## 3 baris pertama           :
##     2026-10-01 00:00:14|TRX0000001|U17587|M130|53500|PENDING
##     2026-10-01 00:00:35|TRX0000002|U00910|M126|76000|SUKSES
##     2026-10-01 00:01:10|TRX0000003|U19809|M012|72500|SUKSES
## Baris rusak               : 80 | semuanya kelipatan 5.000: True
## Contoh baris rusak        : ['ERR|TRX0005000', '2026-10-01 19:21:29|TRX0010000|U11817', '2026-10-02 07:33:39|TRX0015000|U00842|M146|abc|SUKSES', '###CORRUPTED###']
## Nominal = -1              : 1200
## Nominal = 250.000.000     : 150
## Distribusi status         : {'SUKSES': 360388, 'GAGAL': 23843, 'PENDING': 15689}
## 
## == C. LANDING ZONE ==
## transaksi_stream_1.json: 500 event, nominal null = 6
## transaksi_stream_2.json: 500 event, nominal null = 15
## transaksi_stream_3.json: 500 event, nominal null = 24
## Total event: 1500 | nominal null: 45 (3.0%)

5 Jawaban Pertanyaan Konseptual

5.1 Bagian A – Pilihan Ganda

A1. Jawaban: b. Velocity. Alasan: Dua juta pengemudi yang mengirim posisi setiap 3 detik menghasilkan sekitar 667 ribu event per detik, sehingga ciri yang paling menonjol adalah laju kedatangan data, bukan ragam format (seluruhnya data GPS seragam), keandalan, atau nilai. Konsep: 5V terdiri dari Volume (besarnya data), Velocity (kecepatan data dibuat dan harus diproses), Variety (ragam format), Veracity (kualitas dan keandalan), dan Value (manfaat bagi bisnis). Velocity yang tinggi menuntut pemrosesan aliran (stream processing) seperti Kafka dan Spark Structured Streaming, bukan pemrosesan batch berkala.

A2. Jawaban: c. Redis. Alasan: Sesi login adalah pasangan kunci–nilai berumur pendek yang sering dibaca dan ditulis dengan latensi sangat rendah, dan Redis menyediakan kedaluwarsa otomatis (TTL) bawaan pada setiap kunci. Konsep: Redis adalah penyimpanan key-value di memori. MongoDB juga memiliki TTL index, tetapi penghapusan dokumennya dijalankan proses latar belakang secara berkala dan datanya berbasis disk, sehingga kurang pas untuk sesi. Neo4j adalah basis data graf untuk relasi, dan HDFS adalah sistem berkas terdistribusi, bukan basis data dengan TTL.

A3. Jawaban: c. $unwind. Alasan: $unwind memecah field array sehingga setiap elemen menjadi dokumen tersendiri, sedangkan $match menyaring, $group mengelompokkan, dan $project memilih atau membentuk field. Konsep: Pada data merchant di atas, {"$unwind": "$promo"} mengubah merchant berpromo [cashback, diskon] menjadi dua dokumen, sehingga $group berikutnya dapat menghitung jumlah merchant per jenis promo. Secara bawaan merchant dengan array kosong ikut terbuang, kecuali opsi preserveNullAndEmptyArrays: true dipakai.

A4. Jawaban: b. 6 block, 2.100 MB. Alasan: 700 MB ÷ 128 MB = 5,47 sehingga dibutuhkan 6 block (lima block penuh 640 MB dan satu block terakhir 60 MB). Dengan replikasi 3, total data tersimpan adalah 700 × 3 = 2.100 MB, dan jumlah replika block menjadi 6 × 3 = 18. Konsep: Ukuran block HDFS adalah batas atas, bukan ukuran tetap. Block terakhir hanya memakan ruang sebesar isinya (60 MB), tidak dicadangkan penuh 128 MB. Pilihan c (2.304 MB = 6 × 128 × 3) keliru karena mengasumsikan block terakhir terisi penuh, dan pilihan d keliru dengan alasan yang sama, sedangkan 18 adalah jumlah replika, bukan jumlah block logis.

A5. Jawaban: b. NameNode. Alasan: NameNode menyimpan metadata sistem berkas (struktur direktori, izin, dan pemetaan berkas ke block) serta mengetahui DataNode mana yang memegang tiap block dari laporan block yang dikirim DataNode. Konsep: DataNode menyimpan isi block, bukan metadata. Secondary NameNode bukan cadangan siaga, melainkan pembantu yang menggabungkan fsimage dan edit log secara berkala. ResourceManager adalah komponen YARN untuk mengelola sumber daya komputasi, bukan komponen penyimpanan HDFS.

A6. Jawaban: d. count(). Alasan: count() memaksa Spark menjalankan seluruh rencana komputasi dan mengembalikan hasil ke driver, sedangkan map(), filter(), dan reduceByKey() hanya mendefinisikan transformasi baru dan tidak langsung dieksekusi. Konsep: Spark bersifat lazy. Transformation (termasuk reduceByKey yang memicu shuffle) hanya menambah rencana, sedangkan action (count, collect, take, saveAsTextFile, write) memicu eksekusi.

A7. Jawaban: b. reduceByKey(). Alasan: reduceByKey menjumlahkan nilai di tiap partisi lebih dulu sebelum shuffle (map-side combine), sehingga data yang dikirim antar node jauh lebih sedikit dibanding groupByKey yang mengirim semua nilai. Konsep: collect() membawa seluruh data ke memori driver dan berisiko out of memory, sedangkan sortByKey() mengurutkan dan tidak menjumlahkan. Pendekatan inilah yang dipakai pada C2.

A8. Jawaban: b. Alasan: Explicit schema menghindari pemindaian tambahan yang dibutuhkan inferSchema untuk menebak tipe, mencegah salah tebak (misalnya ID 007 terbaca sebagai angka, atau satu nilai kotor mengubah seluruh kolom menjadi string), dan berfungsi sebagai kontrak yang terdokumentasi. Konsep: Pada pipeline produksi, skema yang berubah diam-diam antar eksekusi dapat merusak proses hilir, sedangkan skema eksplisit membuat kesalahan terdeteksi sejak awal. Pilihan a keliru karena skema tidak memengaruhi kompresi. Pilihan c keliru karena CSV tetap dapat dibaca tanpa skema. Pilihan d keliru karena Parquet menyimpan skemanya sendiri, sehingga tidak wajib.

A9. Jawaban: b. 4. Alasan: Dalam satu consumer group, setiap partisi hanya boleh dibaca oleh satu consumer, sehingga jumlah consumer aktif paling banyak sama dengan jumlah partisi, yaitu 4. Dua consumer sisanya menganggur (siaga bila ada consumer lain gagal). Konsep: Partisi adalah satuan paralelisme Kafka. Untuk menambah throughput konsumsi, jumlah partisi yang perlu ditambah, bukan hanya jumlah consumer.

A10. Jawaban: c. Alasan: Dua miliar baris tidak muat di memori driver, sehingga toPandas() dan collect() akan gagal, sedangkan take(10) hanya memberi sampel kecil yang tidak mewakili distribusi. Pendekatan yang benar adalah membagi nilai ke dalam interval (bin) dan menghitungnya di Spark, lalu memvisualisasikan hasil agregasi yang kecil. Konsep: Prinsip “agregasi dulu di klaster, visualisasi kemudian di driver”. Contohnya groupBy(floor(jarak_km / lebar_bin)).count() menghasilkan puluhan baris saja. Prinsip yang sama dipakai pada grafik per jam di C5.

5.2 Bagian B – Esai

5.2.1 B1. Scale-up vs scale-out dan mahalnya shuffle

Perbedaan. Scale-up (vertikal) menambah kekuatan satu mesin, misalnya CPU, RAM, dan disk yang lebih besar. Scale-out (horizontal) menambah jumlah mesin biasa yang bekerja bersama dalam satu klaster.

Aspek Scale-up Scale-out
Batas pertumbuhan Dibatasi spesifikasi tertinggi satu mesin Praktis dapat terus ditambah node
Biaya Naik tajam (tidak linear) pada spesifikasi tinggi Cenderung linear dengan perangkat standar
Ketahanan Satu mesin = satu titik kegagalan (single point of failure) Kegagalan satu node dapat diatasi node lain
Kompleksitas perangkat lunak Rendah, aplikasi biasa tetap berjalan Tinggi: data harus dipartisi dan kegagalan harus ditangani
Contoh Meningkatkan server PostgreSQL tunggal Klaster HDFS dan Spark, MongoDB sharded

Big Data cenderung memilih scale-out karena volume data melampaui batas satu mesin, biaya mesin sangat besar tidak masuk akal, dan perlu toleransi kegagalan. Harga yang dibayar adalah kompleksitas, yang justru diselesaikan oleh kerangka seperti HDFS (penyimpanan terdistribusi dan replikasi) serta MapReduce dan Spark (komputasi terdistribusi).

Mengapa shuffle mahal. Shuffle adalah pengelompokan ulang data antar partisi dan antar node berdasarkan key, yang diperlukan oleh operasi seperti groupByKey, reduceByKey, join, dan repartition. Biayanya berlapis:

  1. Jaringan. Setiap tugas map dapat mengirim data ke banyak tugas reduce (pola semua-ke-semua), dan jaringan jauh lebih lambat daripada memori.
  2. Disk. Keluaran map ditulis ke disk lokal (pada MapReduce dan juga pada berkas shuffle Spark) agar dapat diambil reducer dan aman bila terjadi kegagalan.
  3. Serialisasi dan pengurutan/penggabungan. Data harus diubah menjadi byte dan dikembalikan, lalu diurutkan atau digabung per key, yang memakan CPU dan memori (dapat spill ke disk).
  4. Sinkronisasi. Tahap berikutnya baru dapat mulai setelah seluruh tahap sebelumnya selesai, sehingga satu tugas lambat (misalnya karena data skew: satu key sangat dominan) menahan seluruh pekerjaan.

Karena itu praktik yang baik adalah mengurangi atau menghindari shuffle: memakai reduceByKey (penggabungan lokal dulu), broadcast join untuk tabel kecil, menyaring kolom dan baris sebelum shuffle, dan menyetel jumlah partisi. Seluruh praktik ini diterapkan pada C2 dan C3.

5.2.2 B2. Pilihan database NoSQL untuk marketplace

Kebutuhan Jenis NoSQL Contoh
1. Katalog produk dengan atribut berbeda per kategori Document store MongoDB
2. Keranjang belanja sementara Key-value store Redis
3. Rekomendasi “friends also bought” Graph database Neo4j
  1. Katalog produk: document store. Laptop memiliki atribut seperti RAM dan prosesor, kaos memiliki ukuran dan warna, dan buku memiliki penulis dan ISBN. Dokumen JSON fleksibel menyimpan atribut berbeda dalam koleksi yang sama tanpa kolom NULL atau tabel yang terus diubah. Array dan objek bersarang (misalnya daftar varian) dapat disimpan dalam satu dokumen, dan field dapat diindeks untuk pencarian. Fleksibilitas yang sama terlihat pada field jam_buka merchant BayarYuk.
  2. Keranjang belanja: key-value store. Akses selalu berdasarkan satu kunci (ID pengguna atau sesi), isinya sementara, dan harus cepat dibaca serta ditulis. Redis menyimpan data di memori dengan latensi sangat rendah dan TTL otomatis untuk menghapus keranjang yang ditinggalkan. Jika keranjang tidak boleh hilang saat restart, opsi persistensi (RDB atau AOF) dapat diaktifkan.
  3. Rekomendasi “friends also bought”: graph database. Kueri ini menelusuri relasi berantai: pengguna → teman → produk yang dibeli teman. Pada graph database, penelusuran relasi adalah operasi utama dan relasi disimpan langsung sebagai penghubung antar simpul, sehingga biaya penelusuran tidak membengkak seiring kedalaman. Pada basis data relasional, kueri yang sama membutuhkan banyak JOIN bertingkat yang makin lambat pada data besar.

Memakai beberapa jenis database sesuai kebutuhan seperti ini disebut polyglot persistence.

5.2.3 B3. Lazy evaluation dan lineage di Apache Spark

Lazy evaluation. Transformation seperti map, filter, dan join tidak langsung dijalankan. Spark hanya mencatatnya sebagai rencana (DAG), dan baru mengeksekusinya ketika ada action seperti count, collect, atau write. Pada C2, baris rdd_mentah.flatMap(parse_baris) belum membaca file sama sekali sampai count() dipanggil.

Lineage. Setiap RDD atau DataFrame mengingat dari mana ia berasal: RDD induk dan transformation yang membentuknya. Rantai riwayat ini disebut lineage, dan merupakan bentuk konkret dari DAG yang dibangun oleh lazy evaluation.

Manfaat dalam pengolahan terdistribusi:

  1. Optimasi. Karena seluruh rencana diketahui sebelum dijalankan, Spark dapat menggabungkan transformation sempit (map, filter) ke dalam satu tahap tanpa menyimpan hasil antara, menentukan batas shuffle, dan (pada DataFrame lewat Catalyst) menerapkan predicate pushdown dan column pruning.
  2. Menghemat komputasi dan memori. Pekerjaan yang tidak dibutuhkan tidak dikerjakan; misalnya take(5) hanya menghitung partisi yang diperlukan.
  3. Toleransi kegagalan tanpa replikasi data. Bila satu node gagal dan sebuah partisi hilang, Spark menghitung ulang hanya partisi itu dari sumber menurut lineage, tanpa menyimpan salinan setiap hasil antara. Ini jauh lebih murah daripada replikasi data perantara. Untuk lineage yang sangat panjang atau hasil yang dipakai berulang, cache() atau checkpoint dipakai agar pemulihan tidak mengulang dari awal.

Keduanya saling melengkapi: lazy evaluation membangun rencana dan lineage memungkinkan rencana itu dijalankan ulang secara selektif saat terjadi kegagalan.

5.2.4 B4. At-most-once vs at-least-once dan peran checkpoint

Aspek At-most-once At-least-once
Jaminan Pesan diproses 0 atau 1 kali Pesan diproses 1 kali atau lebih
Risiko Data hilang, tanpa duplikat Duplikat, tanpa kehilangan
Mekanisme Tanpa percobaan ulang; offset dicatat sebelum diproses Dicoba ulang sampai berhasil; offset dicatat setelah diproses
Contoh kegagalan Crash setelah offset dicatat tetapi sebelum diproses: pesan tidak pernah diproses Crash setelah diproses tetapi sebelum offset dicatat: pesan diproses lagi

Peran checkpoint. Checkpoint pada Structured Streaming menyimpan offset dan status kemajuan di lokasi yang tahan gagal. Sebelum sebuah micro-batch diproses, rentang offset-nya dicatat (write-ahead log), dan setelah batch selesai dicatat sebagai commit.

  • Mencegah kehilangan data. Bila sistem mati di tengah batch (offset tercatat tetapi belum di-commit), setelah restart Spark memproses ulang persis rentang offset yang sama, sehingga tidak ada data yang terlewat. Ini memerlukan sumber yang dapat dibaca ulang (file atau Kafka).
  • Mencegah pemrosesan berulang. Bila batch sudah di-commit, setelah restart Spark melanjutkan dari offset berikutnya dan tidak membaca ulang data lama. Hal ini terbukti pada C4: menjalankan ulang query dengan checkpoint yang sama menghasilkan nol batch baru dan jumlah baris Parquet tidak berubah.

Namun, pemrosesan ulang batch yang gagal berarti checkpoint saja setara at-least-once pada sisi keluaran. Untuk mendapatkan efek exactly-once (hasil akhir sama seperti diproses tepat satu kali), keluaran harus idempoten. Pada C4 hal ini dicapai dengan menulis setiap batch ke subfolder batch_id=<n> dengan mode overwrite, sehingga pemutaran ulang batch yang sama menimpa hasil lama dan tidak menggandakannya.

6 C1 – Kueri dan Agregasi NoSQL

6.1 Tujuan

Memanfaatkan fleksibilitas dokumen MongoDB untuk (a) menyaring merchant dengan beberapa syarat sekaligus, termasuk syarat pada field array (promo), (b) menghitung dokumen yang memiliki field opsional (jam_buka), dan (c) merangkum data per kategori dengan aggregation pipeline.

6.2 Implementasi

## C1(a) Jumlah merchant yang memenuhi seluruh kondisi: 2
##              nama  rating                     promo
## Dapur Mandiri 193     4.2 [gratis_ongkir, cashback]
## Warung Makmur 099     4.2 [cashback, gratis_ongkir]
## 
## C1(b) Merchant yang memiliki field jam_buka: 60 dari 200
## 
## C1(c) Agregasi per kategori:
##     kategori  jumlah_merchant  rata_rata_rating  simpangan_baku
## Transportasi               43             4.044           0.566
##   Elektronik               37             4.032           0.552
##    Kesehatan               38             3.987           0.637
##      Kuliner               37             3.959           0.557
##      Fashion               45             3.853           0.591

Fungsi query dan pipeline yang dipakai

  • find(filter, proyeksi) menyaring dokumen. Beberapa pasangan field–nilai dalam satu filter digabung dengan logika AND. Syarat "promo": "cashback" pada field array berarti “array promo mengandung elemen cashback”, tanpa perlu $in atau $elemMatch. Operator $gte dipakai untuk rating minimal 4,0. Proyeksi membatasi field yang dikirim ke klien.
  • count_documents({"jam_buka": {"$exists": True}}) menghitung dokumen yang memiliki field tersebut. Ini bekerja karena MongoDB berskema fleksibel: dokumen tanpa jam_buka tidak dianggap salah, hanya tidak memiliki field itu.
  • Pipeline $group lalu $sort memproses dokumen secara bertahap. $group mengelompokkan menurut $kategori lalu menghitung $sum: 1 (jumlah dokumen) dan $avg (rata-rata rating); $stdDevSamp ditambahkan sebagai pelengkap untuk menilai arti selisih rata-rata. $sort dengan nilai -1 mengurutkan menurun menurut rata-rata rating.

6.3 Hasil dan interpretasi

C1(a). Hanya 2 dari 200 merchant yang memenuhi keempat syarat sekaligus: Dapur Mandiri 193 dan Warung Makmur 099, keduanya berating 4,2. Hasil yang sedikit ini wajar: 200 merchant tersebar pada 25 kombinasi kota–kategori (rata-rata 8 merchant per kombinasi), lalu masih disaring oleh syarat promo dan rating. Bagi BayarYuk, daftar ini adalah kandidat paling kuat untuk kampanye cashback kuliner di Bandung karena sudah berating baik dan sudah memiliki promo tersebut.

C1(b). Sebanyak 60 dari 200 merchant (30%) memiliki field jam_buka. Di basis data relasional, field yang hanya terisi sebagian akan menjadi kolom berisi NULL untuk 70% baris. Di MongoDB, dokumen yang tidak memiliki field itu cukup tidak menyimpannya. Konsekuensinya bagi aplikasi: jam operasional hanya diketahui untuk sebagian merchant, sehingga fitur seperti “sedang buka” harus menangani ketiadaan field tersebut.

C1(c). Rata-rata rating tertinggi dimiliki kategori Transportasi (4,044) dan terendah Fashion (3,853). Selisih keduanya hanya 0,19 poin, sedangkan simpangan baku rating di dalam satu kategori berkisar 0,55 sampai 0,64. Dengan hanya 37–45 merchant per kategori, urutan ini bersifat deskriptif dan belum cukup untuk menyimpulkan bahwa satu kategori lebih berkualitas daripada kategori lain. Kategori Fashion memiliki merchant terbanyak (45) sekaligus rata-rata terendah, sehingga layak dipantau.

7 C2 – Pemrosesan Log dengan Spark RDD

7.1 Tujuan

Mengolah 400.000 baris log teks secara terdistribusi: membaca, mem-parsing menjadi tuple bertipe, membuang baris rusak sambil menghitungnya dengan accumulator, lalu menghitung agregasi (jumlah per status dan tingkat kegagalan per merchant) dengan reduceByKey tanpa menarik data mentah ke driver.

7.2 Implementasi

## ============================================================
## HASIL PEMROSESAN LOG TRANSAKSI
## ============================================================
## Baris mentah (textFile)       : 400,000
## Baris valid hasil parsing     : 399,920
## Baris rusak (accumulator)     : 80
## Persentase baris rusak        : 0.02%
## 
## Contoh tuple hasil parsing:
## (datetime.datetime(2026, 10, 1, 0, 0, 14, tzinfo=datetime.timezone.utc), 'TRX0000001', 'U17587', 'M130', 53500, 'PENDING')
## 
## ============================================================
## DISTRIBUSI STATUS TRANSAKSI
## ============================================================
## SUKSES    :    360,388 (90.12%)
## GAGAL     :     23,843 (5.96%)
## PENDING   :     15,689 (3.92%)
## 
## Tingkat kegagalan keseluruhan: 5.96%
## 
## ============================================================
## 5 MERCHANT DENGAN TINGKAT KEGAGALAN TERTINGGI
## ============================================================
## id_merchant              nama  total_transaksi  jumlah_gagal  tingkat_gagal_%
##        M186  Gerai Makmur 186             2135           605            28.34
##        M141 Klinik Berkah 141             1607           425            26.45
##        M146   Apotek Jaya 146             1682           435            25.86
##        M151 Gadget Berkah 151             1954           497            25.44
##        M010    Ojek Prima 010             2301           522            22.69
## 
## ============================================================
## KONTRIBUSI TOP 5 MERCHANT
## ============================================================
## Total transaksi top 5       : 9,679
## Total transaksi gagal top 5 : 2,484
## Porsi transaksi top 5       : 2.42%
## Porsi kegagalan top 5       : 10.42%
## 
## ============================================================
## RINGKASAN AKHIR C2
## ============================================================
## Jumlah transaksi mentah      : 400,000
## Jumlah transaksi valid       : 399,920
## Jumlah transaksi rusak       : 80
## Jumlah transaksi sukses      : 360,388
## Jumlah transaksi gagal       : 23,843
## Jumlah transaksi pending     : 15,689
## Tingkat kegagalan global     : 5.96%
## Jumlah seluruh merchant      : 200
## Merchant dengan >= 1.500 trx : 168
## 
## C2 selesai dijalankan.

Catatan teknis. flatMap dipilih karena satu baris dapat menghasilkan nol (baris rusak) atau satu tuple, sedangkan map selalu menghasilkan tepat satu. Accumulator hanya dapat dipercaya jika dibaca di driver setelah action selesai. Pembaruan di dalam transformation dapat terhitung ganda bila tahap tersebut dihitung ulang, sehingga RDD di-cache dan hanya satu action pertama (count) yang menjalankan parsing. Agregasi memakai reduceByKey karena penjumlahan dilakukan di tiap partisi sebelum shuffle (map-side combine), sedangkan groupByKey mengirim seluruh nilai lintas jaringan. Satu-satunya data yang dibawa ke driver adalah tiga baris status, lima baris top-5, dan dua hitungan.

7.3 Hasil dan interpretasi

Kualitas parsing. Dari 400.000 baris mentah, 80 baris dibuang oleh accumulator (0,020%), sehingga 399.920 baris valid tersisa. Angka 80 sama persis dengan 400.000 ÷ 5.000, sesuai ketentuan bahwa setiap baris ke-5.000 rusak, jadi tidak ada baris rusak yang lolos dan tidak ada baris sehat yang terbuang. Kehilangan data sekecil ini tidak mengubah hasil analisis secara berarti.

Status transaksi. SUKSES 360.388 (90,12%), GAGAL 23.843 (5,96%), dan PENDING 15.689 (3,92%). Satu dari kira-kira 17 transaksi berakhir gagal. Catatan: status GAGAL di sini mencakup juga transaksi bernominal -1 (kesalahan sistem), sehingga angka ini belum memisahkan kegagalan bisnis dari kegagalan sistem.

Tingkat kegagalan per merchant. Dari 200 merchant, 168 memenuhi syarat minimal 1.500 transaksi. Syarat ini penting karena tingkat kegagalan merchant bertransaksi sedikit mudah menyesatkan: satu kegagalan pada 10 transaksi sudah berarti 10%. Lima merchant tertinggi memiliki tingkat kegagalan 22,69% sampai 28,34%, yaitu 3,8 sampai 4,8 kali tingkat kegagalan keseluruhan. Kelimanya hanya menyumbang 2,4% dari seluruh transaksi, tetapi 10,4% dari seluruh transaksi gagal. Artinya kegagalan terkonsentrasi pada segelintir merchant, bukan tersebar merata. Bagi operasional BayarYuk, kelima merchant (Gerai Makmur 186, Klinik Berkah 141, Apotek Jaya 146, Gadget Berkah 151, Ojek Prima 010) adalah titik awal investigasi, misalnya integrasi pembayaran atau stabilitas koneksi di sisi merchant. Analisis ini menunjukkan siapa yang bermasalah, belum mengapa.

8 C3 – Spark DataFrame dan SQL

8.1 Tujuan

Mengubah RDD hasil C2 menjadi DataFrame bertipe jelas, menggabungkannya dengan dimensi merchant dari MongoDB, lalu menjawab pertanyaan bisnis: kategori mana yang memberi nominal terbesar di tiap kota dan seberapa dominan kategori tersebut.

8.2 Implementasi

## 
## Skema DataFrame transaksi:
## root
##  |-- waktu: timestamp (nullable = true)
##  |-- id_trx: string (nullable = true)
##  |-- id_user: string (nullable = true)
##  |-- id_merchant: string (nullable = true)
##  |-- nominal: long (nullable = true)
##  |-- status: string (nullable = true)
## Jumlah baris trx_df: 399,920
## 
## Skema DataFrame merchant:
## root
##  |-- _id: string (nullable = false)
##  |-- kategori: string (nullable = true)
##  |-- kota: string (nullable = true)
## Jumlah baris merchant_df: 200
## 
## =================================================================
## JUMLAH TRANSAKSI SETELAH FILTER
## =================================================================
## 360,252
## 
## =================================================================
## OPTIMASI PROSES JOIN
## =================================================================
## Metode join       : BroadcastHashJoin
## Kolom penghubung  : id_merchant = _id
## Jenis join        : Inner Join
## Data yang di-broadcast: Data merchant
## Status optimasi   : Berhasil digunakan
## 
## Keterangan:
## Spark menggunakan strategi broadcast join untuk menggabungkan data transaksi dengan data merchant. Data merchant yang relatif kecil dikirimkan ke partisi yang membutuhkan proses join.
## 
## =================================================================
## TOTAL NOMINAL TRANSAKSI PER KOTA DAN KATEGORI
## =================================================================
## Jumlah kombinasi kota-kategori: 25
## Jumlah transaksi yang dianalisis: 360,252
## 
## Rata-rata nominal per transaksi antar kombinasi kota-kategori:
## Rp 96,630 s.d. Rp 102,681
## 
## Tabel total nominal (Rp):
##       kota     kategori total_nominal jumlah_trx
##    Bandung      Kuliner 2,338,306,500     23,836
##    Bandung   Elektronik 1,965,813,500     19,943
##    Bandung    Kesehatan 1,349,101,500     13,583
##    Bandung      Fashion 1,238,715,500     12,330
##    Bandung Transportasi 1,090,238,000     11,010
##    Jakarta      Kuliner 1,878,737,500     19,232
##    Jakarta Transportasi 1,732,040,500     17,837
##    Jakarta   Elektronik 1,711,576,000     17,324
##    Jakarta    Kesehatan 1,685,516,500     16,931
##    Jakarta      Fashion   814,589,500      8,430
##      Medan      Fashion 2,098,380,500     20,989
##      Medan    Kesehatan 1,445,246,000     14,558
##      Medan   Elektronik 1,399,371,500     13,878
##      Medan Transportasi   831,361,000      8,582
##      Medan      Kuliner   564,868,000      5,755
##   Surabaya      Fashion 2,522,657,500     25,356
##   Surabaya Transportasi 2,232,461,000     22,682
##   Surabaya    Kesehatan 1,251,791,000     12,455
##   Surabaya      Kuliner 1,119,553,000     11,391
##   Surabaya   Elektronik   671,565,500      6,822
## Yogyakarta Transportasi 1,836,916,500     18,534
## Yogyakarta      Fashion 1,455,950,500     14,946
## Yogyakarta    Kesehatan 1,235,670,500     12,537
## Yogyakarta      Kuliner   654,858,000      6,688
## Yogyakarta   Elektronik   474,696,000      4,623
## 
## =================================================================
## KATEGORI DENGAN TOTAL NOMINAL TERTINGGI PER KOTA
## =================================================================
##       kota     kategori total_nominal    total_kota  kontribusi_pct
##    Bandung      Kuliner 2,338,306,500 7,982,175,000           29.29
##    Jakarta      Kuliner 1,878,737,500 7,822,460,000           24.02
##      Medan      Fashion 2,098,380,500 6,339,227,000           33.10
##   Surabaya      Fashion 2,522,657,500 7,798,028,000           32.35
## Yogyakarta Transportasi 1,836,916,500 5,658,091,500           32.47
## 
## =================================================================
## VERIFIKASI HASIL DATAFRAME DAN SPARK SQL
## =================================================================
## Hasil Spark SQL identik dengan window function DataFrame: True
## 
## =================================================================
## RINGKASAN AKHIR C3
## =================================================================
## Jumlah transaksi awal          : 399,920
## Transaksi setelah filter       : 360,252
## Jumlah merchant                : 200
## Jumlah kombinasi kota-kategori : 25
## Kontribusi kategori teratas    : 24.02% - 33.10%
## BroadcastHashJoin terpakai     : True
## Hasil SQL dan DataFrame sama   : True
## 
## C3 selesai dijalankan.

Alasan teknis

  • Skema eksplisit. Spark tidak perlu memindai data untuk menebak tipe, tipe yang salah gagal sejak awal (bukan diam-diam menjadi string), dan kontrak data terdokumentasi. waktu bertipe timestamp memungkinkan fungsi waktu seperti hour(), sedangkan nominal bertipe long mencegah pembulatan dan menjamin penjumlahan bilangan bulat yang tepat.
  • Broadcast join. merchant_df hanya 200 baris, sedangkan transaksi ratusan ribu baris. Dengan broadcast(), tabel kecil disalin ke setiap executor sehingga tabel besar tidak perlu di-shuffle berdasarkan id_merchant. Rencana eksekusi di atas memastikan Spark benar-benar memakai BroadcastHashJoin.
  • Window function / SQL. Kita membutuhkan peringkat kategori di dalam tiap kota sekaligus total kota pada baris yang sama. GROUP BY biasa menghilangkan baris detail, sedangkan SUM() OVER (PARTITION BY kota) dan ROW_NUMBER() mempertahankan baris kategori dan menambahkan konteks kota. Persentase kontribusi dihitung sekali jalan tanpa self-join. row_number dipilih (bukan rank) agar tepat satu kategori terpilih per kota meskipun terjadi nilai seri.

8.3 Hasil dan interpretasi

Struktur dan join. trx_df memiliki enam kolom dengan tipe sesuai skema (waktu timestamp, nominal long), dan rencana eksekusi memastikan join memakai BroadcastHashJoin. Dari 399.920 transaksi valid, 360.252 (90,1%) lolos filter analitik. Sisanya dikeluarkan karena berstatus GAGAL atau PENDING, bernominal -1, atau bernominal Rp250 juta. Kombinasi kota–kategori terisi lengkap (25 dari 25 kelompok).

Kategori teratas per kota. Kategori pemimpin berbeda antar kota: Kuliner di Bandung dan Jakarta, Fashion di Medan dan Surabaya, serta Transportasi di Yogyakarta. Kontribusinya terhadap total kota berkisar 24,02% (Jakarta) sampai 33,10% (Medan). Jakarta paling merata: empat dari lima kategorinya berada pada kisaran nominal yang rapat (sekitar Rp1,7 sampai Rp1,9 miliar), sehingga ketergantungan pada satu kategori paling rendah. Di Medan, Surabaya, dan Yogyakarta, kategori teratas menyumbang sekitar sepertiga nominal kota, sehingga perubahan pada satu kategori itu berdampak besar bagi kota tersebut.

Apa yang menggerakkan peringkat. Rata-rata nominal per transaksi hampir seragam antar kombinasi (RpInf sampai Rp-Inf), sehingga peringkat total nominal terutama mengikuti jumlah transaksi, bukan besarnya nilai per transaksi. Dengan kata lain, kategori dominan adalah kategori yang paling sering dipakai pengguna. Hasil Spark SQL identik dengan hasil window function, sehingga kedua pendekatan dapat dipakai bergantian sesuai preferensi tim. Seluruh temuan berlaku untuk periode 1–30 Oktober 2026 pada data simulasi.

9 C4 – Structured Streaming

9.1 Tujuan

Membangun proses ingestion dari landing zone yang berjalan per berkas, memisahkan event tidak valid (nominal null), menyimpan event valid ke Parquet, mencatat jumlah valid/tidak valid per batch, dan membuktikan bahwa checkpoint mencegah pemrosesan ulang.

9.2 Implementasi

## Catatan pemrosesan setiap batch:
##  batch_id  event_masuk  event_valid  event_tidak_valid
##         0          500          494                  6
##         1          500          485                 15
##         2          500          476                 24
## 
## Total: masuk = 1500 | valid = 1455 | tidak valid = 45
## 
## Jumlah baris tersimpan di Parquet (dibaca ulang dari disk): 1455
## Baris per batch di Parquet: {0: 494, 1: 485, 2: 476}
## Jumlah baris di karantina (tidak valid)                  : 45
## Pembanding (baca batch langsung dari JSON)             : 1455 -> konsisten dengan Parquet: True
## 
## Uji checkpoint: batch baru setelah menjalankan ulang = 0 | total Parquet sekarang = 1455

Penjelasan komponen

  • format("json") dengan schema(skema_event) membaca file JSON dengan tipe yang pasti. Streaming file memang mewajibkan skema agar tidak perlu menebak tipe pada data yang belum datang.
  • maxFilesPerTrigger=1 membatasi satu file per micro-batch, sehingga tiga file diproses sebagai tiga batch terpisah.
  • trigger(availableNow=True) memproses seluruh data yang ada lalu menghentikan query. Cocok untuk ingestion terjadwal (misalnya tiap jam) yang berperilaku seperti streaming tetapi tidak berjalan terus-menerus.
  • checkpointLocation menyimpan offset dan daftar file yang sudah diproses. Setelah restart, Spark melanjutkan dari titik terakhir: data tidak hilang (karena lanjut dari offset terakhir) dan tidak diproses ulang.
  • foreachBatch memberi akses penuh pada API DataFrame untuk satu batch, sehingga pemisahan valid/tidak valid, penulisan Parquet, dan pencatatan dapat dilakukan dalam satu fungsi.

9.3 Hasil dan interpretasi

Per batch. Spark memproses tiga file sebagai tiga micro-batch (batch_id 0, 1, 2) yang masing-masing berisi 500 event. Jumlah event valid per batch adalah 494, 485, 476, sedangkan event tidak valid (nominal null) adalah 6, 15, 24. Total tidak valid 45 dari 1.500 event (3,0%), sesuai karakteristik dataset. Kenaikan jumlah tidak valid dari batch pertama ke ketiga sebaiknya tidak ditafsirkan sebagai tren, karena baru tiga titik pengamatan. Pemantauan lebih panjang diperlukan sebelum menyimpulkan kualitas sumber memburuk.

Verifikasi. Parquet dibaca ulang dari disk dan berisi 1.455 baris, sama dengan penjumlahan catatan batch dan sama dengan hitungan pembanding yang membaca JSON langsung secara batch. Karena 1.455 valid + 45 di karantina = 1.500, tidak ada event yang hilang. Event tidak valid tidak dibuang diam-diam, melainkan disimpan terpisah sehingga dapat ditelusuri (misalnya apakah nominal null berasal dari satu merchant tertentu).

Peran checkpoint. Saat query yang sama dijalankan ulang dengan checkpoint yang sama, tidak ada batch baru yang diproses dan jumlah baris Parquet tetap 1.455. Checkpoint mengingat file yang sudah diproses, sehingga ingestion terjadwal tidak membaca ulang data lama. Perlu dicatat bahwa checkpoint sendiri menjamin Spark tidak mengulang batch yang sudah selesai, sedangkan keamanan terhadap kegagalan di tengah batch datang dari penulisan idempoten per batch_id pada fungsi foreachBatch (lihat jawaban B4).

10 C5 – EDA Skala Besar dan Insight

10.1 Tujuan

Memeriksa kualitas data transaksi, memahami sebaran nominal tanpa menarik data mentah ke driver, dan menemukan pola waktu transaksi sukses. Dari analisis ini ditarik dua insight yang dapat ditindaklanjuti.

10.2 Implementasi

10.2.1 C5(1): Laporan kualitas data (satu operasi select)

## total_baris_valid   :   399,920  (100.000%)
## nominal_negatif     :     1,200  (0.300%)
## nominal_min_50jt    :       150  (0.038%)
## status_pending      :    15,689  (3.923%)

10.2.2 C5(2): Distribusi nominal dengan approxQuantile

## Persentil nominal transaksi (relativeError = 0,0001):
## P5  : Rp         11,000   (eksak, relativeError = 0: Rp       11,000)
## P50 : Rp         59,500   (eksak, relativeError = 0: Rp       59,500)
## P95 : Rp        312,000   (eksak, relativeError = 0: Rp      312,000)
## P99 : Rp        615,500   (eksak, relativeError = 0: Rp      616,500)
## 
## Rata-rata nominal mentah  : Rp      192,220
## Rata-rata nominal bersih  : Rp       98,785   (0 < nominal < Rp50 juta)
## Nominal maksimum          : Rp  250,000,000
## Total nominal SUKSES mentah: Rp   69,599,981,500
## Total nominal SUKSES bersih: Rp   35,599,981,500
## Selisih akibat nilai ekstrem: Rp 34,000,000,000 (48.9% dari total mentah)
## Transaksi SUKSES bernominal >= Rp50 juta: 136 | nominal negatif berstatus GAGAL: 1200
## Nominal maksimum = 406 kali P99

10.2.3 C5(3): Transaksi SUKSES berdasarkan jam

## jam                 0       1        2        3        4        5         6         7         8        9         10        11        12        13        14        15        16        17        18        19        20       21        22       23
## jumlah_sukses  4190.00  3251.0  2144.00  2144.00  3316.00  6555.00  10882.00  15246.00  17464.00  16217.0  16318.00  20942.00  25998.00  21535.00  16194.00  15204.00  16252.00  19539.00  24765.00  30457.00  29407.00  21992.0  12872.00  7504.00
## persen            1.16     0.9     0.59     0.59     0.92     1.82      3.02      4.23      4.85      4.5      4.53      5.81      7.21      5.98      4.49      4.22      4.51      5.42      6.87      8.45      8.16      6.1      3.57     2.08
## 
## Jam puncak: 19:00 (30,457 transaksi) | jam tersepi: 2:00 (2,144 transaksi)
## Rasio puncak/tersepi: 14.2x
## Tiga jam teratas: 19:00, 20:00, 12:00 -> 23.8% dari seluruh transaksi sukses

10.3 Hasil dan interpretasi

Laporan kualitas data. Dari 399.920 baris valid, terdapat 1.200 nominal negatif (0,300%), 150 nominal di atas atau sama dengan Rp50 juta (0,038%), dan 15.689 transaksi PENDING (3,92%). Masalah kualitas bernominal tergolong sangat sedikit secara persentase, tetapi seperti terlihat di Insight 2, dampaknya terhadap nilai agregat tidak kecil.

Distribusi nominal. Median transaksi sekitar Rp59.500, P95 sekitar Rp312.000, dan P99 sekitar Rp616.500. Selisih P50 terhadap P95 yang besar menunjukkan distribusi menjulur ke kanan: sebagian besar transaksi bernilai kecil dan sebagian kecil bernilai besar. Hasil approxQuantile dengan relativeError = 0,0001 sangat dekat dengan hasil eksak (P99 Rp615.500 berbanding Rp616.500), tetapi tidak selalu identik karena galat dibatasi pada peringkat data. Pada data miliaran baris, selisih sekecil ini jauh lebih murah dibanding pengurutan penuh. Persentil bertahan dari nilai ekstrem, sedangkan rata-rata tidak: rata-rata mentah Rp192.220 hampir dua kali rata-rata bersih Rp98.785.

Transaksi SUKSES per jam. Grafik menunjukkan dua puncak, siang (jam 12) dan malam (jam 19–20), serta lembah di dini hari. Tabel di atas memuat jumlah dan persentase tiap jam.

10.4 Insight dan rekomendasi

10.4.1 Insight 1 – Beban transaksi sangat terkonsentrasi pada jam makan dan malam

  • Temuan. Jam tersibuk adalah pukul 19:00 dengan 30.457 transaksi sukses. Tiga jam teratas (19:00, 20:00, 12:00) menyumbang 23,8% dari seluruh transaksi sukses, padahal hanya 12,5% dari 24 jam. Jam tersepi adalah pukul 02:00 dengan 2.144 transaksi (pukul 03:00 sama persis).
  • Bukti. Rasio jam puncak terhadap jam tersepi adalah 14,2 kali. Pukul 19:00 dan 20:00 masing-masing menyumbang sekitar 8% dari total, sedangkan pukul 00:00–05:00 masing-masing kurang dari 2% (lihat tabel dan grafik batang).
  • Interpretasi. Beban sistem BayarYuk tidak datar sepanjang hari, melainkan melonjak berkali-kali lipat pada jam tertentu. Pola dua puncak ini sejalan dengan jam makan siang dan makan malam, tetapi analisis ini belum memisahkan menurut kategori merchant, sehingga penyebabnya masih dugaan yang perlu diuji. Kapasitas server, antrean pembayaran, dan layanan pelanggan yang dirancang untuk rata-rata harian akan kekurangan saat puncak dan berlebih saat dini hari.
  • Rekomendasi. (1) Siapkan kapasitas (misalnya auto-scaling) dan siagakan tim dukungan untuk rentang 11:00–13:00 dan 18:00–21:00. (2) Jadwalkan perawatan sistem dan rilis versi pada rentang 01:00–04:00 saat volume terendah, agar gangguan menyentuh paling sedikit pengguna. (3) Gunakan ambang peringatan (alert) yang sadar jam, karena 100 kegagalan pada pukul 02:00 dan pada pukul 19:00 memiliki arti yang sangat berbeda. (4) Uji sebagai hipotesis, bukan kesimpulan, apakah promo khusus di luar jam sibuk dapat meratakan beban.

10.4.2 Insight 2 – Sebagian kecil nilai ekstrem menggelembungkan hampir separuh nilai transaksi sukses

  • Temuan. Terdapat 150 transaksi bernominal Rp250 juta (0,038% dari data), dan 136 di antaranya berstatus SUKSES. Selain itu terdapat 1.200 transaksi bernominal -1, yang seluruhnya (1.200) berstatus GAGAL.
  • Bukti. Total nominal SUKSES mentah Rp69.599.981.500 turun menjadi Rp35.599.981.500 setelah nilai ekstrem dikeluarkan, sehingga 48,9% dari total mentah berasal dari hanya 136 transaksi. Nominal maksimum sekitar 406 kali P99 (Rp616.500). Rata-rata nominal mentah Rp192.220 berbanding Rp98.785 setelah dibersihkan.
  • Interpretasi. Nilai Rp250 juta berada jauh di luar sebaran transaksi normal, sehingga lebih mungkin kesalahan input atau transaksi yang perlu diverifikasi daripada transaksi wajar. Bila tidak dibersihkan, laporan omzet dan rata-rata nilai transaksi akan menyesatkan manajemen. Nominal -1 adalah kode kesalahan sistem, bukan nilai uang. Karena statusnya digabung dengan GAGAL, tingkat kegagalan pada C2 (5,96%) ikut mencakup 1.200 kegagalan sistem (5,0% dari seluruh transaksi GAGAL) sehingga angka kegagalan bisnis menjadi tidak murni.
  • Rekomendasi. (1) Terapkan validasi di titik masuk data (seperti pemisahan ke karantina pada C4): tolak atau tandai nominal ≤ 0 dan nominal di atas batas wajar. (2) Tinjau 136 transaksi SUKSES bernominal Rp250 juta secara manual sebelum masuk laporan keuangan, termasuk kemungkinan penipuan atau kesalahan input. (3) Pisahkan kode kesalahan sistem dari status bisnis, misalnya dengan kolom kode_error tersendiri, agar tingkat kegagalan merchant tidak tercemar. (4) Laporkan ukuran transaksi tipikal dengan median dan persentil, bukan rata-rata.

Catatan. Dataset pada dokumen ini adalah simulasi yang dibangkitkan dengan seed tetap. Pola seperti jam puncak, merchant dengan kegagalan tinggi, dan nilai ekstrem dirancang pada pembangkit data, sehingga insight di atas menggambarkan data simulasi ini dan bukan perilaku pengguna BayarYuk yang sesungguhnya. Yang divalidasi di sini adalah metode analisisnya.

11 Kesimpulan

Dokumen ini membangun, menjalankan, dan menganalisis alur Big Data BayarYuk dari tiga sumber berbeda dengan teknologi yang sesuai bagi masing-masing sumber. Seluruh angka di bawah berasal dari eksekusi kode pada dokumen ini.

  1. NoSQL (C1). MongoDB memudahkan penyaringan multi-syarat termasuk pada field array, menangani field opsional (jam_buka hanya pada 60 dari 200 merchant), dan merangkum data lewat aggregation pipeline. Hanya 2 merchant memenuhi seluruh syarat kampanye cashback kuliner di Bandung.
  2. Spark RDD (C2). Dari 400.000 baris log, 80 baris rusak terdeteksi oleh accumulator dan dibuang. Agregasi dikerjakan dengan reduceByKey tanpa menarik data mentah ke driver. Tingkat kegagalan keseluruhan 5,96%, dan lima merchant dengan kegagalan tertinggi menyumbang 10,4% dari seluruh transaksi gagal.
  3. DataFrame dan SQL (C3). Skema eksplisit dan broadcast join menghasilkan total nominal per kota dan kategori. Kategori teratas berbeda antar kota dengan kontribusi 24,0% sampai 33,1%, dan hasil window function sama dengan hasil Spark SQL.
  4. Structured Streaming (C4). Tiga file diproses sebagai tiga batch dengan checkpoint. Sebanyak 1.455 event valid tersimpan di Parquet dan 45 event tidak valid dikarantina, tanpa kehilangan data. Menjalankan ulang query tidak menambah batch baru.
  5. EDA skala besar (C5). Persentil, laporan kualitas, dan agregasi per jam dihitung di Spark, dan hanya hasil agregasi kecil yang dibawa ke driver. Dua insight utama: beban transaksi sangat terkonsentrasi pada jam tertentu, dan 136 transaksi ekstrem menggelembungkan 48,9% dari nilai transaksi sukses.

Keterbatasan. Dataset adalah simulasi dengan pola yang dirancang pada pembangkitnya, sehingga insight berlaku untuk data ini dan bukan untuk pengguna BayarYuk yang sebenarnya. Analisis juga baru mencakup satu bulan data dan belum memisahkan jam menurut kategori merchant. Penyebab (mengapa) merchant tertentu sering gagal tidak dapat disimpulkan dari data yang tersedia dan perlu diselidiki lebih lanjut.