Pukul 03:12 alert freshness untuk mart_penjualan berbunyi di ponsel on-call. Pipeline harian gagal, dashboard GMV berpotensi kosong saat tim finance mulai bekerja pukul 07:00, dan tanpa proses yang jelas tiga engineer akan memeriksa log yang sama sementara tim finance justru mengetahui kejadian lebih dulu dari rapat pagi. Incident response adalah proses terkoordinasi untuk situasi seperti ini: menentukan keparahan, membagi peran, mengeksekusi mitigasi, mengomunikasikan status, lalu menutup insiden dengan postmortem. Chapter ini menyusun proses tersebut untuk konteks data, lengkap dengan timeline insiden yang dibangun nyata dari berkas events JSONL.
Tingkat Keparahan untuk Insiden Data #
Severity menentukan siapa yang dibangunkan dan seberapa cepat status harus diperbarui. Insiden data punya ciri khas: pipeline bisa tampak hijau di orkestrator sementara angkanya salah secara bisnis, sehingga severity harus dinilai dari dampak ke konsumen data, bukan dari warna status task. Contoh severity untuk platform data TokoKita:
| Severity | Definisi | Contoh insiden data | Respons |
|---|---|---|---|
| SEV1 | data untuk keputusan bisnis atau pihak eksternal salah dan tidak ada solusi sementara | angka pajak yang salah sudah terkirim ke auditor; warehouse tidak bisa dibaca saat flash sale | page on-call 24/7, incident commander ditunjuk, update tiap 30 menit |
| SEV2 | satu domain data salah atau terlambat, konsumen penting terdampak, ada solusi sementara | GMV harian kosong karena nilai status upstream berubah | page on-call, update tiap 2 jam |
| SEV3 | gangguan minor tanpa konsumen real-time | mart_stok tertunda 3 jam, hanya dibaca batch berikutnya | ditangani pada jam kerja, cukup tiket |
Alert yang memicu page pada tabel di atas berasal dari praktik chapter Monitoring dan Alerting. Ketika angka dashboard terindikasi salah, penelusuran akar masalahnya mengikuti pohon diagnostik dari chapter Investigasi Kegagalan Data — incident response fokus pada proses pengelolaan kejadian, bukan pada teknik investigasinya.
Peran Saat Insiden Berlangsung #
Insiden yang dikelola baik memisahkan pengambil keputusan dari pelaksana. Pola peran berikut diadaptasi dari praktik SRE:
| Peran | Tugas utama | Bukan tugas peran tersebut |
|---|---|---|
| Incident Commander | memegang kendali, membagi tugas, memutuskan mitigasi | ikut menyelidiki akar masalah |
| Operations Lead | mengeksekusi mitigasi: pause export, rollback, reprocess | menunggu perintah selangkah demi selangkah |
| Communications Lead | mengirim status berkala ke konsumen data | menjanjikan ETA tanpa dasar data |
| Scribe | mencatat timeline: siapa, melakukan apa, kapan | merekonstruksi timeline dari ingatan |
Membangun Timeline dan Menghitung MTTA/MTTR #
Timeline insiden terbaik dicatat saat kejadian berlangsung, bukan direkonstruksi sehari kemudian. Kita menyimpan setiap kejadian sebagai satu baris JSON di events.jsonl — pola yang selaras dengan log terstruktur pada chapter Logging dan Tracing:
{"insiden": "INS-2026-11-08", "sev": "SEV2", "waktu": "2026-11-08T03:12:00", "tipe": "deteksi", "aktor": "alert-freshness (Grafana)", "detail": "mart_penjualan stale > 6 jam, page on-call"}
{"insiden": "INS-2026-11-08", "sev": "SEV2", "waktu": "2026-11-08T03:19:00", "tipe": "ack", "aktor": "Dewi (on-call DE)", "detail": "konfirmasi dashboard GMV 7 November kosong"}
{"insiden": "INS-2026-11-08", "sev": "SEV2", "waktu": "2026-11-08T03:24:00", "tipe": "mitigasi", "aktor": "Dewi", "detail": "pause export mart_penjualan ke laporan_finance"}
{"insiden": "INS-2026-11-08", "sev": "SEV2", "waktu": "2026-11-08T03:41:00", "tipe": "update", "aktor": "Siti (communications lead)", "detail": "status update #1 ke tim finance"}
{"insiden": "INS-2026-11-08", "sev": "SEV2", "waktu": "2026-11-08T04:05:00", "tipe": "identifikasi", "aktor": "Budi (SME pipeline)", "detail": "upstream API v2 mengubah nilai status, filter tidak cocok"}
{"insiden": "INS-2026-11-08", "sev": "SEV2", "waktu": "2026-11-08T04:31:00", "tipe": "resolusi", "aktor": "Dewi", "detail": "hotfix normalisasi status, run ulang sukses, GMV tampil"}
{"insiden": "INS-2026-11-21", "sev": "SEV3", "waktu": "2026-11-21T08:47:00", "tipe": "deteksi", "aktor": "digest harian", "detail": "mart_stok tertunda 3 jam, tanpa page"}
{"insiden": "INS-2026-11-21", "sev": "SEV3", "waktu": "2026-11-21T09:29:00", "tipe": "ack", "aktor": "Budi (on-call DE)", "detail": "diperiksa setelah jam kerja dimulai"}
{"insiden": "INS-2026-11-21", "sev": "SEV3", "waktu": "2026-11-21T10:05:00", "tipe": "identifikasi", "aktor": "Budi", "detail": "worker ETL kehabisan scratch saat join"}
{"insiden": "INS-2026-11-21", "sev": "SEV3", "waktu": "2026-11-21T11:52:00", "tipe": "resolusi", "aktor": "Budi", "detail": "naikkan scratch worker, run ulang sukses"}Skrip berikut membaca seluruh berkas, mencetak timeline terformat per insiden, lalu menghitung MTTA (mean time to acknowledge: deteksi ke ack) dan MTTR (mean time to resolve: deteksi ke resolusi) — dua metrik yang menilai kecepatan respons dan kecepatan pemulihan:
"""Pembangun timeline insiden: baca events JSONL, cetak timeline terformat,
hitung MTTA dan MTTR per insiden, lalu bandingkan dengan target per severity."""
import json
from datetime import datetime
from pathlib import Path
TARGET_MTTA = {"SEV1": 15, "SEV2": 15, "SEV3": 60} # menit
TARGET_MTTR = {"SEV1": 60, "SEV2": 240, "SEV3": 1440} # menit
def waktu(teks):
return datetime.fromisoformat(teks)
def durasi(menit):
jam, sisa = divmod(menit, 60)
return f"{jam}j {sisa}m" if jam else f"{sisa}m"
insiden = {}
for baris in Path("events.jsonl").read_text().splitlines():
e = json.loads(baris)
insiden.setdefault(e["insiden"], {"sev": e["sev"], "kejadian": []})
insiden[e["insiden"]]["kejadian"].append(e)
ringkasan = []
for id_ins in sorted(insiden):
data = insiden[id_ins]
kejadian = sorted(data["kejadian"], key=lambda e: e["waktu"])
deteksi = next(e for e in kejadian if e["tipe"] == "deteksi")
ack = next(e for e in kejadian if e["tipe"] == "ack")
resolusi = next(e for e in kejadian if e["tipe"] == "resolusi")
t0 = waktu(deteksi["waktu"])
print(f"=== {id_ins} ({data['sev']}) ===")
for e in kejadian:
m = int((waktu(e["waktu"]) - t0).total_seconds() // 60)
jam = waktu(e["waktu"]).strftime("%H:%M")
print(f" T+{m:>3}m {jam} {e['tipe']:<12} {e['aktor']:<24} {e['detail']}")
mtta = int((waktu(ack["waktu"]) - t0).total_seconds() // 60)
mttr = int((waktu(resolusi["waktu"]) - t0).total_seconds() // 60)
status = ("dalam target" if mttr <= TARGET_MTTR[data["sev"]] else "melebihi target")
ringkasan.append((id_ins, data["sev"], mtta, mttr, status))
print(f" -> MTTA {durasi(mtta)}, MTTR {durasi(mttr)} "
f"(target MTTR {durasi(TARGET_MTTR[data['sev']])}: {status})\n")
print("| Insiden | SEV | MTTA | MTTR | Target MTTR | Status |")
print("|---|---|---|---|---|---|")
for id_ins, sev, mtta, mttr, status in ringkasan:
print(f"| {id_ins} | {sev} | {durasi(mtta)} | {durasi(mttr)} |"
f" {durasi(TARGET_MTTR[sev])} | {status} |")
rata_mtta = sum(r[2] for r in ringkasan) / len(ringkasan)
rata_mttr = sum(r[3] for r in ringkasan) / len(ringkasan)
print(f"\nrata-rata MTTA {rata_mtta:.0f} menit, rata-rata MTTR {rata_mttr:.0f} menit")Output yang Dihasilkan:
=== INS-2026-11-08 (SEV2) ===
T+ 0m 03:12 deteksi alert-freshness (Grafana) mart_penjualan stale > 6 jam, page on-call
T+ 7m 03:19 ack Dewi (on-call DE) konfirmasi dashboard GMV 7 November kosong
T+ 12m 03:24 mitigasi Dewi pause export mart_penjualan ke laporan_finance
T+ 29m 03:41 update Siti (communications lead) status update #1 ke tim finance
T+ 53m 04:05 identifikasi Budi (SME pipeline) upstream API v2 mengubah nilai status, filter tidak cocok
T+ 79m 04:31 resolusi Dewi hotfix normalisasi status, run ulang sukses, GMV tampil
-> MTTA 7m, MTTR 1j 19m (target MTTR 4j 0m: dalam target)
=== INS-2026-11-21 (SEV3) ===
T+ 0m 08:47 deteksi digest harian mart_stok tertunda 3 jam, tanpa page
T+ 42m 09:29 ack Budi (on-call DE) diperiksa setelah jam kerja dimulai
T+ 78m 10:05 identifikasi Budi worker ETL kehabisan scratch saat join
T+185m 11:52 resolusi Budi naikkan scratch worker, run ulang sukses
-> MTTA 42m, MTTR 3j 5m (target MTTR 24j 0m: dalam target)
| Insiden | SEV | MTTA | MTTR | Target MTTR | Status |
|---|---|---|---|---|---|
| INS-2026-11-08 | SEV2 | 7m | 1j 19m | 4j 0m | dalam target |
| INS-2026-11-21 | SEV3 | 42m | 3j 5m | 24j 0m | dalam target |
rata-rata MTTA 24 menit, rata-rata MTTR 132 menitMari kita telaah baris-baris kode di atas:
- Baris 8-9 (
TARGET_MTTA,TARGET_MTTR): target respons per severity dinyatakan sebagai kamus, sehingga setiap insiden dibandingkan dengan target yang sesuai bobotnya — SEV3 memang boleh lebih lambat daripada SEV1. - Baris 20-23 (grup per insiden):
setdefaultmengelompokkan baris JSON ke satu insiden; satu berkas bisa menampung banyak insiden sekaligus. - Baris 28-30 (
next): deteksi, ack, dan resolusi dicari berdasarkantipe;sortedmemastikan timeline tercetak menurut waktu. - Baris 39-40 (MTTA/MTTR): kedua metrik dihitung sebagai selisih menit dari waktu deteksi
t0— definisi yang sama di seluruh insiden sehingga rata-ratanya bisa dibandingkan langsung.
Dua angka pada tabel ringkasan layak digarisbawahi. MTTA 7 menit untuk SEV2 menunjukkan jalur page bekerja: alert langsung membangunkan on-call. Sebaliknya, MTTA 42 menit untuk SEV3 adalah akibat kebijakan yang memang disengaja — insiden minor tidak mem-page siapa pun, sehingga baru ditangani setelah jam kerja dimulai. Keduanya masih dalam target, dan selisih terhadap target itulah yang menjadi bahan evaluasi postmortem, bukan sekadar angka mentahnya.
Postmortem Tanpa Menyalahkan #
Postmortem blameless berangkat dari asumsi: setiap orang bertindak masuk akal dengan informasi yang tersedia saat kejadian. Pertanyaan yang dicari adalah mengapa sistem memungkinkan kejadian tersebut, bukan siapa yang bersalah — budaya yang diuraikan tuntas di Google SRE Book: Postmortem Culture . Template berikut mengikat hasilnya menjadi aksi yang bisa diperiksa:
## Ringkasan
Satu paragraf: apa yang terjadi, konsumen mana yang terdampak, berapa lama.
## Dampak
- SEV2; GMV 7 November kosong pada dashboard selama 1 jam 19 menit
- Konsumen terdampak: dashboard_gmv, laporan_finance
## Timeline
(ditempel dari output timeline_insiden.py)
## Akar Masalah
Upstream merilis API v2 yang mengubah nilai status tanpa pemberitahuan;
pipeline menyalin nilai baru sehingga filter `status = 'lunas'` gagal cocok.
## Yang Sudah Berjalan Baik
- Alert freshness mem-page on-call dalam satu menit setelah ambang terlampaui
- Solusi sementara (pause export) mencegah angka salah sampai ke laporan
## Aksi Lanjutan
| Aksi | Pemilik | Tenggat |
|---|---|---|
| Validasi kosakata status di pintu masuk pipeline | Dewi | 15 Des |
| Perjanjian perubahan nilai kolom kunci dengan tim kasir | Budi | 20 Des |Kerangka pengelolaan insiden lengkap dari deteksi sampai aksi lanjutan ada di Google SRE Workbook: Incident Response . Namun insiden yang berakhir pada status “resolved” sering kali meninggalkan pekerjaan terbesar: data yang salah harus diproses ulang dan konsumen harus menerima angka yang telah dikoreksi — pokok bahasan Chapter Recovery dan Reprocessing.