Dua chapter sebelumnya sudah ada yang mengambil bagian ini: Monitoring Workflow membahas pemantauan dari sisi orchestrator, dan Metrics dan Alerting di section Data Quality dan Observability menghitung metrik dari sisi isi data — volume baris, kualitas, dan label severity. Chapter ini mengambil sudut yang berbeda: pipeline sebagai beban operasional yang harus tersedia setiap hari. Pertanyaannya bukan “apakah datanya benar”, melainkan “apakah run-nya sehat”: berapa tingkat keberhasilan, berapa lama kasus terberat, apakah kegagalan menumpuk, dan apakah resource menipis. Jawaban angkanya dihitung dari run ledger; jawaban tindakannya adalah routing — notifikasi mana yang membangunkan orang, mana yang cukup menjadi tiket pagi, dan mana yang sengaja dibiarkan diam.
Golden Signals untuk Pipeline #
Empat golden signals dari praktik SRE — latensi, trafik, error, saturasi — terpetakan rapi ke dunia pipeline batch:
| Golden signal | Padanan pipeline | Sumber data |
|---|---|---|
| Latency | durasi run, khususnya p95 | durasi_menit di ledger |
| Traffic | volume baris per run | rows di ledger |
| Errors | run gagal, kegagalan berurutan | status di ledger |
| Saturation | CPU puncak, memori, concurrency | metrik resource per run |
Volume baris sebagai sinyal trafik sudah dibahas habis di chapter Metrics dan Alerting bersama deteksi penurunan; tiga sisanya adalah wilayah chapter ini, dan semuanya tersedia dari ledger yang sama.
Menghitung Kesehatan dari Run Ledger #
Skema ledger meneruskan chapter Scheduling dan Production Runs dengan satu kolom tambahan: cpu_peak_pct, saturasi yang tercatat setiap run. Riwayat berikut memuat pekan yang bermasalah — satu kegagalan di jendela rawat terjadwal, lalu dua kegagalan berurutan:
"""Kesehatan run pipeline dari ledger + aturan routing alert (page/tiket/diam)."""
import json
from datetime import datetime
RIWAYAT = """\
{"run_id":"r-115","tanggal":"2026-10-02","mulai":"02:00","status":"success","duration_s":208,"rows":53550,"cpu_peak_pct":55}
{"run_id":"r-116","tanggal":"2026-10-03","mulai":"02:00","status":"success","duration_s":226,"rows":52900,"cpu_peak_pct":60}
{"run_id":"r-117","tanggal":"2026-10-04","mulai":"02:00","status":"success","duration_s":204,"rows":53300,"cpu_peak_pct":57}
{"run_id":"r-118","tanggal":"2026-10-05","mulai":"02:10","status":"failed","duration_s":38,"rows":0,"cpu_peak_pct":12}
{"run_id":"r-119","tanggal":"2026-10-06","mulai":"02:00","status":"success","duration_s":296,"rows":51700,"cpu_peak_pct":78}
{"run_id":"r-120","tanggal":"2026-10-07","mulai":"02:00","status":"failed","duration_s":341,"rows":21850,"cpu_peak_pct":88}
{"run_id":"r-121","tanggal":"2026-10-08","mulai":"02:00","status":"failed","duration_s":328,"rows":0,"cpu_peak_pct":91}
"""
JENDELA_RAWAT = [{"nama": "upgrade PostgreSQL production",
"mulai": "2026-10-05T02:00", "akhir": "2026-10-05T03:00"}]
BATAS_CPU = 90 # persen; di atas ini pipeline dianggap jenuh
def persentil(nilai, p):
data = sorted(nilai)
pos = (len(data) - 1) * p / 100
bawah = int(pos)
atas = min(bawah + 1, len(data) - 1)
return data[bawah] + (data[atas] - data[bawah]) * (pos - bawah)
def dalam_rawat(run):
for rawat in JENDELA_RAWAT:
mulai = datetime.fromisoformat(rawat["mulai"])
akhir = datetime.fromisoformat(rawat["akhir"])
mulai_run = datetime.fromisoformat(f"{run['tanggal']}T{run['mulai']}")
if mulai <= mulai_run < akhir:
return rawat["nama"]
return NoneBagian kedua menghitung metrik, mengevaluasi aturan, lalu menyusun kiriman akhir per saluran:
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)
streak = 0
for r in reversed(jendela):
if r["status"] == "failed":
streak += 1
else:
break
durasi_p95 = persentil([r["duration_s"] for r in jendela], 95)
cpu_puncak = max(r["cpu_peak_pct"] for r in jendela)
print("== Kesehatan 7 run terakhir (etl_pesanan_harian) ==")
print(f"success_rate : {success_rate:.2f} ({len(sukses)}/{len(jendela)})")
print(f"kegagalan_berurutan : {streak}")
print(f"durasi_p95 : {durasi_p95:.1f} detik")
print(f"cpu_puncak : {cpu_puncak}% (batas {BATAS_CPU}%)")
print()
print("== Evaluasi aturan -> routing ==")
page, tiket, diam = [], [], []
for r in jendela:
if r["status"] == "failed" and dalam_rawat(r):
nama = dalam_rawat(r)
diam.append(f"[P3] {r['run_id']} gagal dalam jendela rawat: {nama}")
print(f" [P3 diam ] {r['run_id']} gagal di jendela rawat: {nama}")
if streak >= 2:
page.append(f"[P1] {streak} kegagalan berurutan (run terakhir gagal total)")
print(f" [P1 page ] {streak} kegagalan berurutan")
if success_rate < 0.85:
page.append(f"[P1] success_rate 7 hari {success_rate:.2f} < 0.85")
print(f" [P1 page ] success_rate 7 hari {success_rate:.2f} < 0.85")
if durasi_p95 > 300:
tiket.append(f"[P2] durasi_p95 {durasi_p95:.0f}s > 300s (latensi memburuk)")
print(f" [P2 tiket] durasi_p95 {durasi_p95:.0f}s > 300s")
if cpu_puncak >= BATAS_CPU:
tiket.append(f"[P2] cpu_puncak {cpu_puncak}% >= {BATAS_CPU}% (saturasi)")
print(f" [P2 tiket] cpu_puncak {cpu_puncak}% >= {BATAS_CPU}%")
print()
print("== Kiriman akhir (setelah dedup per insiden) ==")
print(f" PAGE -> on-call: {len(page)} aturan menjadi 1 notifikasi; "
f"pager dibunyikan untuk insiden etl_pesanan_harian")
print(f" TIKET -> antrean pagi: {len(tiket)} aturan menjadi 1 tiket")
print(f" DIAM -> hanya tercatat: {len(diam)} run gagal di jendela rawat, "
f"terjadwal dan diketahui")Output yang Dihasilkan:
== Kesehatan 7 run terakhir (etl_pesanan_harian) ==
success_rate : 0.57 (4/7)
kegagalan_berurutan : 2
durasi_p95 : 337.1 detik
cpu_puncak : 91% (batas 90%)
== Evaluasi aturan -> routing ==
[P3 diam ] r-118 gagal di jendela rawat: upgrade PostgreSQL production
[P1 page ] 2 kegagalan berurutan
[P1 page ] success_rate 7 hari 0.57 < 0.85
[P2 tiket] durasi_p95 337s > 300s
[P2 tiket] cpu_puncak 91% >= 90%
== Kiriman akhir (setelah dedup per insiden) ==
PAGE -> on-call: 2 aturan menjadi 1 notifikasi; pager dibunyikan untuk insiden etl_pesanan_harian
TIKET -> antrean pagi: 2 aturan menjadi 1 tiket
DIAM -> hanya tercatat: 1 run gagal di jendela rawat, terjadwal dan diketahuiMari kita telaah bagian-bagian kuncinya:
streakdihitung mundur dari run terakhir: kegagalan berurutan adalah sinyal on-call yang lebih tajam daripada success rate — satu kegagalan bisa jadi insiden acak, dua berturut-turut berarti sesuatu rusak dan run berikutnya kemungkinan besar juga gagal.dalam_rawatmembandingkan waktu mulai run dengan jendela rawat berkalender: kegagalan r-118 dikenali sebagai akibat upgrade yang terjadwal, sehingga tidak perlu membangunkan siapa pun — penyebabnya sudah diketahui sebelum run dimulai.- Dua aturan P1 menyala dari satu insiden: kegagalan berurutan dan success rate rendah berasal dari kegagalan yang sama; kiriman akhir menggabungkannya menjadi satu notifikasi agar pager tidak berbunyi dua kali untuk satu masalah.
- P2 dikumpulkan ke satu tiket: latensi dan saturasi saling terkait (CPU menipis membuat run lambat) — satu tiket pagi dengan dua temuan lebih mudah ditindaklanjuti daripada dua tiket terpisah.
Dashboard dan Mutu Alert #
Angka-angka di atas layak jadi dashboard: panel success rate dan durasi p95 per pipeline, panel kegagalan berurutan, dan panel saturasi CPU — pola yang didukung langsung oleh Grafana untuk visualisasi dan Prometheus untuk penyimpanan time series serta evaluasi aturan alertnya. Aturan routing di dunia nyata dinyatakan sebagai konfigurasi Alertmanager: pengelompokan per insiden, penundaan pengulangan, dan prioritas saluran.
Notifikasi yang benar sasaran hanyalah setengah dari kecepatan pemulihan; setengah lainnya adalah tahu ke mana melihat setelah pager berbunyi. Ketika alert P1 tiba pukul 03.00, on-call membutuhkan kronologi lengkap satu run lintas layanan dalam hitungan detik — dan itu pekerjaan logging serta tracing, materi chapter Logging dan Tracing.