Pipeline produksi punya kebiasaan yang tidak nyaman: gagal di luar jam kerja, tepat ketika tidak ada yang menatap dashboard. Tanpa pemantauan yang benar, tim data mengetahui kegagalan dari pertanyaan pengguna — “kenapa angka kemarin belum ada?” — yang berarti kegagalan tersebut sudah berdampak. Monitoring workflow menjawab tiga pertanyaan: apa yang perlu dipantau, bagaimana pemberitahuan dikirim ke orang yang tepat, dan metrik apa yang menandakan masalah sebelum berubah menjadi keluhan. Konsep pada bab ini berlaku lintas orchestrator; kita akan menutupnya dengan pelacak status run mini yang dijalankan nyata.

Apa yang Dipantau #

Empat hal berikut mencakup hampir seluruh kebutuhan pemantauan pipeline:

  • Status run — sukses, berjalan, atau gagal. Run yang gagal setelah seluruh percobaan ulang habis adalah sinyal paling mendesak, karena mekanisme retry dari Chapter Retries dan Timeouts sudah kehabisan kesempatan memperbaiki diri.
  • Durasi run — dibandingkan terhadap baseline. Durasi yang memburuk perlahan biasanya menandakan data yang membesar atau query yang melambat, jauh sebelum run benar-benar gagal.
  • SLA miss — pelanggaran Service Level Agreement: janji kepada pengguna data, misalnya “tabel penjualan terisi setiap pukul 07:00”. Run yang sukses tetapi melewati ambang waktu tetap merupakan kegagalan dari sudut pandang pengguna.
  • Pola kegagalan berulang — run yang gagal di jam atau partisi tertentu secara berulang menunjukkan masalah struktural: data sumber kosong, jadwal yang bertabrakan, atau kebutuhan backfill seperti yang kita bahas pada Chapter Backfill.

UI versus Alert #

Hampir semua orchestrator menyediakan UI untuk melihat riwayat run, log, dan graf dependency. UI adalah alat penyelidikan yang sangat baik — tetapi UI adalah alat tarik: informasi hanya terlihat ketika seseorang membuka halamannya. Deteksi kegagalan membutuhkan dorongan (push) lewat alert, dan saluran yang dipilih mengikuti urgensinya:

SaluranKapan dipakaiSifat
Emailringkasan harian, laporan berkalatidak mengganggu
Slack atau channel chatkegagalan yang perlu dilihat tim di jam kerjamengundang diskusi
PagerDuty atau layanan on-callkegagalan yang harus ditangani segera, kapan punmembangunkan orang

Alert yang baik membawa konteks: nama workflow, waktu kejadian, galat singkat, dan tautan ke run atau log. Penerima alert harus bisa memutuskan langkah berikutnya tanpa membuka UI terlebih dahulu. Contoh konkret UI orchestrator yang memajang riwayat run dan log per task dapat dilihat di dokumentasi Apache Airflow — kita akan berkenalan dengannya pada Chapter Apache Airflow.

Metrik Run yang Berguna #

Ketika run berjalan ratusan kali per pekan, kita perlu ringkasan angka, bukan daftar run:

MetrikPertanyaan yang dijawabContoh sinyal bahaya
Success rateseberapa sering pipeline sehatturun di bawah 95% dalam sepekan
Durasi p95berapa lama kasus terberat yang masih wajarnaik dua kali lipat dibanding pekan lalu
Jumlah SLA missapakah janji ke pengguna terpenuhilebih dari nol pada dua hari berturut-turut

Durasi p95 adalah persentil ke-95: 95% run diselesaikan lebih cepat dari nilai tersebut. Persentil dipilih daripada rata-rata karena rata-rata mudah dibiaskan mayoritas run cepat — kasus terberat justru yang paling perlu dilihat. Pada jumlah sampel kecil, nilai p95 mendekati durasi terpanjang; nilai ini stabil seiring run bertambah.

Pelacak Status Run Mini #

Logika di atas dapat dirangkum menjadi pelacak kecil: catat setiap run, hitung metrik, lalu susun ringkasan berkonteks yang siap dikirim ke saluran alert. Kode berikut dijalankan nyata:

python
"""Pelacak status run workflow mini — dijalankan nyata di bab ini."""

from dataclasses import dataclass


@dataclass
class Run:
    nama: str
    status: str
    durasi_detik: float
    selesai_pada: str
    catatan: str = ""


class PelacakRun:
    """Kumpulkan run, hitung metrik, dan susun ringkasan siap kirim."""

    def __init__(self, ambang_sla_detik: float) -> None:
        self.ambang_sla_detik = ambang_sla_detik
        self.runs: list[Run] = []

    def catat(self, run: Run) -> None:
        self.runs.append(run)

    @property
    def tingkat_sukses(self) -> float:
        if not self.runs:
            return 0.0
        sukses = sum(1 for r in self.runs if r.status == "sukses")
        return sukses / len(self.runs)

    def durasi_p95(self) -> float:
        if not self.runs:
            return 0.0
        urut = sorted(r.durasi_detik for r in self.runs)
        indeks = round(0.95 * (len(urut) - 1))
        return urut[indeks]

    def run_gagal(self) -> list[Run]:
        return [r for r in self.runs if r.status != "sukses"]

    def pelanggaran_sla(self) -> list[Run]:
        return [r for r in self.runs if r.durasi_detik > self.ambang_sla_detik]

    def ringkasan(self) -> str:
        baris = [
            f"jumlah run      : {len(self.runs)}",
            f"tingkat sukses  : {self.tingkat_sukses:.0%}",
            f"durasi p95      : {self.durasi_p95():.0f} detik",
            f"pelanggaran SLA : {len(self.pelanggaran_sla())} run",
        ]
        for r in self.run_gagal():
            baris.append(f"  gagal: {r.nama} @{r.selesai_pada} — {r.catatan}")
        return "\n".join(baris)


def main() -> None:
    pelacak = PelacakRun(ambang_sla_detik=900)

    # 11 run sukses dengan durasi yang bervariasi
    for menit, detik in enumerate(
        [780, 810, 820, 850, 870, 890, 910, 940, 990, 1150, 1200], start=1
    ):
        pelacak.catat(
            Run("pipeline-penjualan", "sukses", detik, f"2026-01-15 06:{menit:02d}")
        )

    # satu run gagal: 2600 detik, retries habis
    pelacak.catat(
        Run(
            "pipeline-penjualan",
            "gagal",
            2600,
            "2026-01-15 06:43",
            "timeout setelah retries habis (3 percobaan)",
        )
    )

    print(pelacak.ringkasan())


if __name__ == "__main__":
    main()

Output yang Dihasilkan:

text
jumlah run      : 12
tingkat sukses  : 92%
durasi p95      : 1200 detik
pelanggaran SLA : 6 run
  gagal: pipeline-penjualan @2026-01-15 06:43 — timeout setelah retries habis (3 percobaan)

Mari kita telaah baris-baris kode di atas:

  1. Baris 6-12 (Run): satu run membawa identitas, status, durasi, waktu selesai, dan catatan — catatan inilah yang menjadi konteks pada notifikasi kegagalan.
  2. Baris 18-20 (__init__): pelacak menyimpan ambang SLA dan daftar run; pada orchestrator sungguhan, peran list ini digantikan metadata database.
  3. Baris 25-30 (tingkat_sukses): proporsi run sukses; pembagi nol dijaga lebih dahulu agar pekan tanpa run tidak menghasilkan galat.
  4. Baris 32-37 (durasi_p95): durasi diurutkan lalu diambil nilai pada indeks persentil ke-95 — 12 run menghasilkan indeks 10, sehingga p95 jatuh pada 1200 detik, tepat di bawah run terpanjang.
  5. Baris 39-43 (run_gagal dan pelanggaran_sla): dua filter yang sama penting — run gagal memicu respons insiden, sedangkan pelanggaran SLA terjadi bahkan pada run yang sukses namun lambat.
  6. Baris 45-54 (ringkasan): merangkai metrik dan daftar run gagal menjadi satu pesan siap kirim ke Slack atau email.

Ringkasan tersebut memperlihatkan pola yang sering muncul di produksi: tingkat sukses 92% terdengar sehat, tetapi enam dari dua belas run melewati ambang 900 detik — sinyal bahwa pipeline perlahan melambat meski jarang gagal. Orchestrator sungguhan menyimpan status di metadata database dan menghitung metrik lewat query, namun prinsipnya tetap sama: catat setiap run, hitung metrik agregat, dan kirim pemberitahuan yang membawa konteks. Wujud nyata dari prinsip ini akan langsung kita lihat pada UI bawaan Apache Airflow.