karawaci.kode

2026-08-18 · 10 min

Incident Response Runbook yang Benar-Benar Dipakai Tim Kecil

Saya pernah masuk ke incident jam 11 malam dan menemukan satu-satunya “runbook” yang tersedia adalah dokumen Notion yang terakhir diedit dua tahun lalu, setengahnya masih placeholder “TODO: isi ini”. Orang yang menulis dokumen itu sudah tidak ada di tim. Database connection pool habis, monitoring menunjukkan error rate naik, dan tidak ada satu pun command yang bisa saya jalankan dengan yakin tanpa takut memperburuk keadaan.

Itu bukan masalah teknologi. Itu masalah proses yang tidak pernah diperlakukan seserius kodenya.

Kenapa runbook pada akhirnya tidak dipakai

Tim kecil biasanya membangun runbook dalam dua kondisi: setelah incident besar yang menyakitkan, atau saat ada audit. Kedua kondisi itu menghasilkan runbook yang terlalu panjang, terlalu generik, atau ditulis dengan asumsi pembacanya sudah hafal arsitektur sistem. Lalu dokumen itu tersimpan, tidak pernah diuji, dan menjadi stale dalam hitungan bulan.

Ada tiga pola kegagalan yang paling sering saya lihat:

  1. Runbook ditulis sebagai dokumentasi arsitektur, bukan panduan aksi. Tiga halaman tentang “bagaimana service kami bekerja” tidak berguna saat error rate sudah 40% dan atasan mengirim pesan WhatsApp.
  2. Tidak ada trigger yang jelas. “Gunakan runbook ini jika ada masalah database” terlalu luas. Siapa yang memutuskan ini “masalah database”? Pada angka apa?
  3. Tidak pernah dijalani saat tidak ada incident. Runbook yang tidak pernah dites adalah runbook yang akan gagal persis saat paling dibutuhkan.

Struktur yang benar-benar bekerja

Setelah beberapa kali iterasi di tim yang saya dampingi, saya menyederhanakan satu runbook menjadi lima bagian yang wajib ada — tidak lebih, tidak kurang:

# Runbook: [Nama Kondisi]

## Trigger
- Alert: [nama alert spesifik di monitoring]
- Kondisi: [angka ambang batas, contoh: error rate > 5% selama 3 menit]
- Severity: SEV1 / SEV2 / SEV3

## Diagnosis Pertama (< 5 menit)
1. Cek dashboard: [URL langsung ke Grafana/Datadog panel yang relevan]
2. Jalankan: `[command siap salin]`
3. Lihat log: `[query log siap salin]`

## Mitigasi Sementara
- Langkah rollback jika ada deploy terbaru:
  `[command deploy rollback]`
- Langkah circuit breaker / feature flag jika tersedia:
  `[URL atau command]`

## Eskalasi
- Jika dalam 15 menit tidak ada kemajuan: hubungi [nama + channel]
- Jika menyangkut data/pembayaran: langsung hubungi [nama PIC bisnis]

## Root Cause & Action Item
[Diisi setelah incident selesai]

Bagian terakhir sengaja kosong — ini bukan pelupa, tapi pengingat bahwa setiap runbook yang diaktifkan harus menghasilkan setidaknya satu improvement.

Severity matrix yang tidak membingungkan

Di tim kecil tanpa SRE dedicated, terlalu banyak level severity adalah musuh keputusan cepat. Saya pakai tiga level dengan definisi berbasis dampak user, bukan perasaan:

SeverityKondisiTarget ResponsEskalasi Otomatis
SEV1Core feature tidak bisa dipakai, atau data corruption aktif15 menitLangsung semua on-call + lead
SEV2Degradasi signifikan, ada workaround1 jamSecondary on-call jika primary tidak respons 10 menit
SEV3Bug minor, dampak terbatasJam kerja berikutnyaTidak ada

Yang sering salah ditetapkan adalah SEV1. Di production kami, SEV1 hanya untuk dua kondisi: (1) tidak ada user yang bisa login atau menyelesaikan transaksi, dan (2) ada indikasi data rusak atau bocor. Error 500 di satu endpoint yang jarang dipakai bukan SEV1 meskipun terasa menakutkan.

On-call rotation untuk tim tiga orang

Ini setup yang kami pakai di salah satu klien Jakarta dengan tiga backend engineer:

# pagerduty-schedule.yml (atau OpsGenie equivalent)
rotation:
  type: weekly
  start_day: Monday 09:00 WIB
  
  layers:
    - name: primary
      users: [engineer_a, engineer_b, engineer_c]
      
    - name: secondary  
      users: [engineer_b, engineer_c, engineer_a]  # offset +1
      escalation_delay_minutes: 10
      
escalation_policy:
  - level: 1
    target: primary on-call
    timeout_minutes: 10
    
  - level: 2  
    target: secondary on-call
    timeout_minutes: 15
    
  - level: 3
    target: engineering_lead
    notify_always: true  # untuk SEV1

Dua aturan yang wajib ditulis eksplisit dan diketahui semua anggota tim:

  1. On-call hours vs after-hours berbeda. Jam kerja: respons 15 menit untuk SEV1. Malam/akhir pekan: respons 30 menit untuk SEV1, SEV3 tidak di-alert sama sekali.
  2. Ada kompensasi yang jelas. Di tim kami, satu minggu on-call dapat satu hari off di minggu berikutnya. Tanpa ini, on-call menjadi beban yang tidak kelihatan sampai seseorang resign.

Diagnosis awal yang bisa dilakukan siapapun

Bagian ini yang paling sering dilewatkan saat menulis runbook. Langkah diagnosis pertama harus bisa dilakukan oleh engineer yang baru bergabung sebulan — bukan hanya oleh orang yang membangun sistemnya.

Untuk database issue, command yang selalu ada di runbook kami:

# Cek connection pool usage (contoh: PgBouncer)
psql -h $PGBOUNCER_HOST -p 6432 -U pgbouncer pgbouncer \
  -c "SHOW POOLS;" | grep -v "^$"

# Lihat query yang berjalan lama (> 30 detik)
psql $DATABASE_URL -c "
  SELECT pid, now() - pg_stat_activity.query_start AS duration, query
  FROM pg_stat_activity
  WHERE (now() - pg_stat_activity.query_start) > interval '30 seconds'
  AND state = 'active';
"

# Kill query jika perlu (isi pid dari query di atas)
psql $DATABASE_URL -c "SELECT pg_cancel_backend(<pid>);"

Untuk API yang lambat tapi tidak error:

# Cek apakah Redis tersedia (contoh: Node.js service)
redis-cli -u $REDIS_URL ping

# Lihat error rate per endpoint di Loki (query siap salin)
# {app="api-service"} | json | level="error" | rate [5m]

# Cek deployment terbaru — apakah ada deploy dalam 1 jam terakhir?
kubectl rollout history deployment/api-service -n production
# atau
railway logs --deployment --limit 50 | grep "deploy"

Kuncinya: setiap command ditulis lengkap termasuk environment variable yang dibutuhkan, bukan hanya konsepnya. “Cek database” tidak berguna; psql $DATABASE_URL -c "SELECT..." berguna.

Communication template saat incident

Satu hal yang sering membuat incident terasa lebih kacau dari yang sebenarnya adalah komunikasi yang tidak terstruktur. Setiap orang mengirim update berbeda ke channel berbeda, manajemen kebanjiran pertanyaan, dan engineer yang harusnya fokus debug malah sibuk menjawab DM.

Kami pakai template berikut di channel #incidents Slack, dipost setiap 30 menit selama incident aktif:

**[UPDATE SEV1 - 23:15 WIB]**

Status: INVESTIGATING / MITIGATING / RESOLVED

Dampak saat ini:
- [X% user] tidak bisa [melakukan apa]
- Dimulai sekitar: [waktu]

Yang sudah dilakukan:
- 23:00 - Rollback deploy #234 → tidak ada perubahan
- 23:10 - Restart connection pool → connection kembali normal, error masih ada

Dugaan root cause:
- Query baru di fitur X kemungkinan menyebabkan table lock

Langkah selanjutnya:
- Disable fitur X via feature flag dalam 5 menit
- @[nama] mengerjakan ini

Next update: 23:45 WIB

Format ini memaksa on-call untuk terus menilai situasi secara berkala, dan manajemen mendapat informasi tanpa perlu interrupt engineer.

Post-mortem yang tidak menjadi formalitas

Di tim kecil, post-mortem yang panjang tidak akan ditulis. Saya sudah menyerah mendorong format panjang — lebih baik post-mortem singkat yang benar-benar ditulis dan action item-nya masuk backlog, daripada template 20 pertanyaan yang menakutkan sehingga tidak ada yang memulainya.

Template minimum yang kami pakai:

## Post-mortem: [Judul Incident] — [Tanggal]

**Durasi:** [waktu mulai] — [waktu selesai] ([X jam Y menit])
**Severity:** SEV1/2/3
**Dampak:** [Estimasi user terdampak dan apa yang tidak bisa dilakukan]

**Root cause:**
[Satu sampai tiga kalimat — apa yang sebenarnya terjadi, bukan siapa yang salah]

**Timeline singkat:**
- HH:MM — [event]
- HH:MM — [event]

**Action items:**
| Tindakan | PIC | Deadline | Tiket |
|----------|-----|----------|-------|
| [apa yang akan dilakukan] | [nama] | [tanggal] | [link] |

Satu aturan keras: setiap post-mortem harus menghasilkan minimal satu action item yang masuk sprint berikutnya. Post-mortem tanpa action item yang dikerjakan hanya menghabiskan waktu.

Trade-off yang perlu diakui

Pendekatan ini punya asumsi dan batasan:

  • Runbook jadi stale jika tidak ada proses review. Kami jadwalkan review runbook tiap kuartal bersamaan dengan dependency update — bukan event tersendiri, karena kalau tersendiri tidak akan dilakukan.
  • Tim kecil sering tidak punya redundansi yang cukup. Tiga orang on-call rotation artinya setiap orang kena giliran tiap tiga minggu. Ini masih manusiawi, tapi jika ada anggota tim yang cuti panjang atau resign, rotasinya langsung terasa.
  • Alerting yang tidak dikalibrasi membunuh proses ini. Runbook yang baik tidak berguna jika tim sudah alert fatigue — setiap alert terasa seperti “wolf cry” yang tidak ditanggapi serius. Sebelum membangun runbook, audit dulu alert mana yang actionable dan mana yang hanya noise.

Verdict

Untuk tim tiga sampai lima orang yang baru memulai incident response yang terstruktur: mulai dari tiga hal saja. Pertama, buat satu runbook untuk incident yang paling sering terjadi — bukan yang paling dramatis, tapi yang paling repetitif. Kedua, tetapkan severity matrix dengan tiga level dan definisi yang tidak ambigu. Ketiga, pastikan on-call rotation ada di tool yang otomatis melakukan eskalasi, bukan bergantung pada orang yang secara manual menghubungi secondary.

Runbook yang dipakai adalah runbook yang pendek, spesifik, dan ditest. Sisanya adalah dokumentasi yang meyakinkan atasan tapi tidak membantu engineer jam 11 malam.

Ditulis oleh Reza Pradipta