tutorial

  1. Studi Kasus: Aplikasi Analitik Sederhana dengan Streaming

95. Studi Kasus: Aplikasi Analitik Sederhana dengan Streaming

Seiring pertumbuhan data yang semakin pesat, kebutuhan untuk memproses dan menganalisis data secara real-time juga meningkat. Pendekatan pemrosesan batch tradisional, di mana data dikumpulkan dan diproses secara berkala, sering kali tidak cukup untuk kebutuhan bisnis modern, terutama untuk kasus seperti monitoring transaksi, fraud detection, atau analitik moda transportasi. Oleh karena itu, streaming data muncul sebagai solusi yang efektif.

Pada artikel ini, saya akan membagikan studi kasus pembangunan aplikasi analitik sederhana menggunakan streaming. Studi kasus ini kita ambil dari dunia nyata namun disederhanakan agar mudah dipahami dan direplikasi. Kita akan membahas desain arsitektur, contoh kode, hingga simulasi, tabel hasil, serta diagram alur menggunakan mermaid. Pembaca diharapkan memiliki gambaran implementasi aplikasi streaming end-to-end.


Studi Kasus: Analitik Streaming Transaksi E-commerce

Deskripsi Kasus:
Sebuah perusahaan e-commerce ingin memonitor transaksi pembayaran secara real-time untuk menganalisa rata-rata nilai transaksi tiap kota dan mendeteksi lonjakan volume transaksi. Data transaksi masuk secara terus-menerus (stream) ke sistem.


Arsitektur Solusi

Untuk studi kasus sederhana ini, kita memilih stack sebagai berikut:

  • Proses streaming: Apache Kafka (messaging) dan Apache Spark Streaming (analitik)
  • Visualisasi hasil: Konsol dan tabel sederhana

Berikut diagram alurnya:

flowchart LR
    User(Platform User) -->|Transaksi| API[API Order Service]
    API -->|Push data| Kafka[(Kafka Topic)]
    Kafka -->|Ingest| Spark[Spark Streaming Job]
    Spark -->|Hasil Analitik| Output[Console/Tabel Hasil]

Skema Data Transaksi

Setiap transaksi yang masuk memiliki struktur data berikut (format JSON):

{
  "order_id": "OID123456",
  "created_at": "2024-06-20T10:14:38Z",
  "user_id": "USER789",
  "city": "Jakarta",
  "amount": 250000.0
}

Kunci utama dalam studi kasus ini adalah field city dan amount.


Simulasi Data Streaming

Sebelum masuk ke kode analitik, kita membutuhkan simulasi data transaksi yang akan dikirim ke Kafka. Untuk tujuan studi kasus, kita akan menggunakan script Python sederhana pengirim data ke Kafka.

# transaksi_producer.py
import json
import random
import time
from datetime import datetime
from kafka import KafkaProducer

cities = ['Jakarta', 'Bandung', 'Surabaya', 'Medan']
producer = KafkaProducer(bootstrap_servers=['localhost:9092'])

def generate_transaksi():
    return {
        "order_id": f"OID{random.randint(100000, 999999)}",
        "created_at": datetime.utcnow().isoformat() + 'Z',
        "user_id": f"USER{random.randint(100, 999)}",
        "city": random.choice(cities),
        "amount": float(random.randint(50000, 500000))
    }

while True:
    transaksi = generate_transaksi()
    producer.send('transaksi_topic', json.dumps(transaksi).encode('utf-8'))
    print("Kirim:", transaksi)
    time.sleep(random.uniform(0.2, 1.0))  # simulasi data stream

Proses Analitik dengan Spark Streaming

Kita menggunakan Spark Structured Streaming (Python API: PySpark) untuk membaca data dari Kafka, lalu menghitung:

  1. Rata-rata nilai transaksi per kota per window waktu (misal 1 menit)
  2. Deteksi lonjakan volume transaksi per kota (misal: volume naik lebih dari 50% window sebelumnya)

Berikut contoh kode analitiknya:

# streaming_analitik.py
from pyspark.sql import SparkSession
from pyspark.sql.functions import from_json, col, window, avg, count
from pyspark.sql.types import StructType, StringType, FloatType, TimestampType

# Skema data
schema = StructType() \
    .add("order_id", StringType()) \
    .add("created_at", StringType()) \
    .add("user_id", StringType()) \
    .add("city", StringType()) \
    .add("amount", FloatType())

# Create Spark Session
spark = SparkSession.builder \
    .appName("TransaksiStreamingAnalitik") \
    .getOrCreate()

spark.sparkContext.setLogLevel("WARN")

# Baca stream dari Kafka
raw_df = spark \
    .readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "localhost:9092") \
    .option("subscribe", "transaksi_topic") \
    .load()

# Parsing JSON
transaksi_df = raw_df.selectExpr("CAST(value AS STRING) as json_str") \
    .select(from_json(col("json_str"), schema).alias("data")) \
    .select("data.*") \
    .withColumn("created_at", col("created_at").cast(TimestampType()))

# Windowed average per city
avg_df = transaksi_df \
    .groupBy(
        window(col("created_at"), "1 minute", "30 seconds"),
        col("city")
    ).agg(
        avg("amount").alias("avg_amount"),
        count("order_id").alias("volume_transaksi")
    )

query = avg_df.writeStream \
    .outputMode("update") \
    .format("console") \
    .option("truncate", False) \
    .start()

query.awaitTermination()

Output Sample (Tabel Konsol)

window_startwindow_endcityavg_amountvolume_transaksi
2024-06-20 10:14:002024-06-20 10:15:00Jakarta2450008
2024-06-20 10:14:002024-06-20 10:15:00Bandung3020005
2024-06-20 10:14:002024-06-20 10:15:00Surabaya1980007
2024-06-20 10:14:002024-06-20 10:15:00Medan1550003

Deteksi Lonjakan Volume Transaksi

Untuk deteksi lonjakan (spike) volume, kita perlu menyimpan volume window sebelumnya dan membandingkannya. Pada implementasi produksi, umumnya digunakan Redis, Cassandra atau stateful streaming. Namun untuk studi kasus sederhana, kita cukup menambah logika pengecekan lonjakan pada Spark Structured Streaming. Berikut ilustrasinya memakai window 1 menit rolling:

from pyspark.sql.window import Window
from pyspark.sql.functions import lag, col, expr

# Tambahkan windowed dataframe
windowed_df = avg_df

# Menggunakan lag (tidak support di Structured Streaming directly, solusi: simpan ke sink lalu proses di batch/layer lain)
# Alternatif: Tulis ke sink (contoh: database) untuk kemudian dibandingkan

# Simulasi pseudo-code:
# volume_lama = read_previous_window(city)
# lonjakan = volume_baru > 1.5 * volume_lama

Pada sistem nyata, Spark streaming biasanya push hasil ke storage (misal Redis), lalu alert logic membaca storage tersebut untuk mendeteksi spike.


Diskusi: Sisi Engineer

Sebagai engineer, beberapa pelajaran penting dari studi kasus real-time streaming ini:

  • Single Source of Truth: Kafka sebagai central event log membuat sistem lebih mudah diskalakan dan fault-tolerant.
  • Window dan State: Windowing pada batch interval penting untuk agregasi dan deteksi trend/spike. Namun stateful streaming membutuhkan storage/performance tuning lebih lanjut.
  • Pemrosesan Late Data: Handling out-of-order/lateness sangat penting, terutama jika data berasal dari banyak sumber atau device IoT.
  • Alert system (Notif): Deteksi spike volume bisa dihubungkan ke notifikasi webhook/email untuk alert dalam sistem nyata.

Diagram Alur End-to-End

sequenceDiagram
    participant User as User
    participant API as API Service
    participant Kafka as Kafka Broker
    participant Spark as Streaming Analytic
    participant DB as Storage/Console

    User->>API: Kirim Transaksi
    API->>Kafka: Publish Event
    Kafka->>Spark: Stream Data Transaction
    Spark->>DB: Tulis hasil agregat/window
    Spark->>Spark: Analisis Spike
    Spark-->>DB: Notifikasi/Alert Lonjakan

Kesimpulan

Lewat studi kasus ini, kita melihat bagaimana pipeline sederhana streaming analitik dibangun mulai dari ingest data, pemrosesan dan agregasi real-time, hingga deteksi anomali sederhana. Pendekatan di atas bisa dipakai dan dikembangkan untuk berbagai kebutuhan, mulai dari aplikasi monitoring finansial, prediksi kebutuhan logistik, hingga IoT.

Produksi solusi streaming membutuhkan integrasi sistem yang matang, monitoring yang baik, dan penanganan berbagai edge-case seperti data duplicate, late-arriving, scaling dan lain-lain. Namun pondasinya tetap pada desain event-driven dan stateful streaming, sebagaimana dicontohkan di atas.

Semoga studi kasus ini membuka wawasan dan bisa menjadi starter kit bagi engineer yang ingin merambah dunia streaming analytics!


Silahkan eksplorasi kode contoh dan modifikasi sesuai kebutuhan proyekmu. Happy coding!

comments powered by Disqus