Log terstruktur dari chapter Pipeline Logging menghasilkan data mentah yang setiap harinya bertambah. Data tersebut belum bisa menjawab pertanyaan manajemen sederhana: “apakah pipeline sehat minggu ini?”. Metrics mengubah banjir log menjadi sedikit angka yang bisa dipantau — success rate, volume baris, durasi — dan alerting mengubah angka tersebut menjadi notifikasi ketika nilai melewati batas yang kita definisikan. Chapter ini menghitung metrik dari riwayat run berformat JSONL, mengevaluasi aturan alert, lalu membahas cara menjaga notifikasi tetap dipercaya.

Metrik Inti Pipeline #

Tidak semua angka layak dipantau; metrik yang baik punya pemilik, punya arti bisnis, dan punya ambang yang jelas. Tiga metrik berikut menjadi fondasi hampir semua pipeline batch:

MetrikMengapa dipantauContoh threshold
success_raterun gagal berarti data tidak tersedia bagi pengguna< 0.90 selama 7 run
rows per runpenurunan volume menandakan sumber data terpotongturun > 40% dari rerata 7 hari
duration_p95latensi ekor, bukan rata-rata, yang dirasakan pengguna> 300 detik

Metrik-metrik ini juga menjadi bahan SLO (service level objective): komitmen terukur seperti “data pesanan tersedia sebelum 07.00 setiap hari, 29 dari 30 hari”. Begitu SLO ditetapkan, konsumsi error budget dapat dihitung lewat burn rate — seberapa cepat sisa toleransi kegagalan terpakai — dan alert bisa dibuat berdasarkan kecepatan pembakaran, bukan sekadar satu nilai yang dilanggar. Pemilihan jendela juga menentukan perilaku metrik: jendela 7 run memungkinkan pemulihan cepat setelah masalah berlalu, sedangkan jendela 30 run lebih stabil namun lebih lambat merespons perubahan.

Menghitung Metrik dari Riwayat Run #

Kita simulasikan riwayat 14 run pipeline etl_pesanan_harian dalam format JSONL — format yang sama dengan keluaran log chapter sebelumnya. Tujuh run terakhir menjadi jendela evaluasi; skenario ini sengaja memuat minggu yang bermasalah: dua run gagal, volume turun, dan durasi memburuk secara bersamaan:

python
"""Hitung metrik dari riwayat run JSONL, lalu evaluasi aturan alert."""

import json

RIWAYAT = """\
{"run_id":"r-108","tanggal":"2026-09-25","status":"success","rows":52800,"duration_s":216}
{"run_id":"r-109","tanggal":"2026-09-26","status":"success","rows":53600,"duration_s":199}
{"run_id":"r-110","tanggal":"2026-09-27","status":"success","rows":52450,"duration_s":207}
{"run_id":"r-111","tanggal":"2026-09-28","status":"success","rows":53200,"duration_s":214}
{"run_id":"r-112","tanggal":"2026-09-29","status":"success","rows":53900,"duration_s":201}
{"run_id":"r-113","tanggal":"2026-09-30","status":"success","rows":53100,"duration_s":210}
{"run_id":"r-114","tanggal":"2026-10-01","status":"success","rows":52600,"duration_s":219}
{"run_id":"r-115","tanggal":"2026-10-02","status":"success","rows":53550,"duration_s":208}
{"run_id":"r-116","tanggal":"2026-10-03","status":"success","rows":52900,"duration_s":226}
{"run_id":"r-117","tanggal":"2026-10-04","status":"success","rows":53300,"duration_s":204}
{"run_id":"r-118","tanggal":"2026-10-05","status":"failed","rows":0,"duration_s":38}
{"run_id":"r-119","tanggal":"2026-10-06","status":"success","rows":51700,"duration_s":296}
{"run_id":"r-120","tanggal":"2026-10-07","status":"failed","rows":21850,"duration_s":341}
{"run_id":"r-121","tanggal":"2026-10-08","status":"success","rows":24100,"duration_s":328}
"""


def persentil(nilai, p):
    """Persentil dengan interpolasi linear (metode numpy default)."""
    data = sorted(nilai)
    pos = (len(data) - 1) * p / 100
    bawah = int(pos)
    atas = min(bawah + 1, len(data) - 1)
    frac = pos - bawah
    return data[bawah] + (data[atas] - data[bawah]) * frac


runs = [json.loads(baris) for baris in RIWAYAT.splitlines()]
jendela = runs[-7:]

sukses = [r for r in jendela if r["status"] == "success"]
success_rate = len(sukses) / len(jendela)
durasi_p95 = persentil([r["duration_s"] for r in jendela], 95)
rows_hari_ini = runs[-1]["rows"]
rerata_rows = sum(r["rows"] for r in sukses) / len(sukses)
penurunan = 1 - rows_hari_ini / rerata_rows

print("== Metrik 7 run terakhir (pipeline etl_pesanan_harian) ==")
print(f"success_rate  : {success_rate:.3f}")
print(f"rows_hari_ini : {rows_hari_ini}")
print(f"rerata_rows   : {rerata_rows:.1f}")
print(f"durasi_p95    : {durasi_p95:.1f} detik")

print()
print("== Evaluasi aturan alert ==")
alert = []

if success_rate < 0.90:
    pesan = (f"ALERT [P1] success_rate 7 hari {success_rate:.2f} < 0.90 "
             f"({len(sukses)}/{len(jendela)} run sukses)")
    alert.append(pesan)
    print(pesan)
else:
    print(f"OK    success_rate {success_rate:.2f} >= 0.90")

if durasi_p95 > 300:
    pesan = f"ALERT [P2] durasi_p95 {durasi_p95:.0f}s > 300s - pipeline melambat"
    alert.append(pesan)
    print(pesan)
else:
    print(f"OK    durasi_p95 {durasi_p95:.0f}s <= 300s")

if penurunan > 0.40:
    pesan = (f"ALERT [P1] rows hari ini turun {penurunan:.0%} "
             f"dari rerata 7 hari ({rows_hari_ini} vs {rerata_rows:.0f})")
    alert.append(pesan)
    print(pesan)
else:
    print(f"OK    penurunan rows {penurunan:.0%} <= 40%")

print()
print(f"Total alert aktif: {len(alert)}")
print("Ketiga alert menunjuk satu insiden: sumber upstream terbatas.")

Output yang Dihasilkan:

text
== Metrik 7 run terakhir (pipeline etl_pesanan_harian) ==
success_rate  : 0.714
rows_hari_ini : 24100
rerata_rows   : 47110.0
durasi_p95    : 337.1 detik

== Evaluasi aturan alert ==
ALERT [P1] success_rate 7 hari 0.71 < 0.90 (5/7 run sukses)
ALERT [P2] durasi_p95 337s > 300s - pipeline melambat
ALERT [P1] rows hari ini turun 49% dari rerata 7 hari (24100 vs 47110)

Total alert aktif: 3
Ketiga alert menunjuk satu insiden: sumber upstream terbatas.

Mari kita telaah bagian-bagian kuncinya:

  1. jendela = runs[-7:]: evaluasi selalu memakai jendela bergulir, bukan seluruh riwayat, agar metrik mencerminkan kondisi terkini dan pulih otomatis setelah masalah berlalu.
  2. persentil(nilai, 95): implementasi interpolasi linear yang sama dengan numpy.percentile; pada tujuh nilai terurut, posisi p95 jatuh di antara dua nilai terbesar.
  3. rerata_rows dihitung hanya dari run sukses: run gagal memiliki rows: 0 karena tidak memproses data, dan memasukkan nol tersebut ke rata-rata akan mengecilkan pembanding sehingga penurunan asli tersamar.
  4. Urutan success_rate -> durasi_p95 -> penurunan: tiga aturan independen dievaluasi berurutan; setiap aturan mencetak baris ALERT atau OK sehingga laporan selalu utuh.

Alert Fatigue dan Jenis Notifikasi #

Sistem alert paling sering gagal bukan karena kurang berbunyi, melainkan karena terlalu sering berbunyi. Ketika notifikasi datang setiap jam untuk hal yang tidak ditindaklanjuti, manusia belajar mengabaikannya — dan saat insiden sungguhan datang, notifikasi ikut diabaikan. Kondisi tersebut disebut alert fatigue, dan pemulihannya tidak lewat menaikkan threshold sembarangan, melainkan lewat disiplin isi notifikasi. Tiga praktik menjaga kualitas notifikasi:

  • Setiap alert harus actionable. Bila penerima hanya bisa berkata “ya, memang begitu” tanpa langkah lanjutan, alert tersebut adalah laporan, bukan alert.
  • Gunakan severity. P1 membangunkan orang tengah malam; P2 masuk antrean pagi. Pipeline melambat tetap layak diketahui tanpa layak membangunkan.
  • Deduplikasi per insiden. Tiga alert pada output di atas berasal dari satu penyebab (sumber upstream terbatas); sistem produksi seperti Alertmanager mengelompokkannya menjadi satu notifikasi.

Terakhir, setiap alert yang aktif harus punya runbook: paragraf singkat yang menjelaskan apa yang diperiksa pertama kali, siapa yang dihubungi, dan kapan masalah boleh dieskalasi. Alert tanpa runbook memindahkan beban berpikir ke momen paling buruk — tengah malam, saat orang baru terbangun.

Dalam penerapan production, perhitungan seperti di atas jarang ditulis manual. Prometheus (https://prometheus.io/docs/alerting/latest/overview/ ) mengevaluasi aturan alert secara berkala terhadap time series, sementara instrumentasi berstandar OpenTelemetry (https://opentelemetry.io/docs/ ) menyatukan pengiriman metric, log, dan trace dari kode pipeline ke backend observability.

Threshold statis pada metrik volume bekerja baik untuk kasus ekstrem, tetapi pola musiman membuatnya mudah salah: volume pesanan TokoKita naik tiap akhir pekan dan tanggal gajian. Untuk membedakan lonjakan wajar dari anomali sungguhan, kita butuh deteksi yang belajar dari pola data itu sendiri — materi chapter Anomaly Detection untuk Data.