HP Bergetar Jam 2 Pagi, Padahal Nggak Ada Yang Harus Saya Lakukan
Alert CPU jam 2 pagi yang ternyata cuma spike backup. Pelajaran debounce 3 sampel, hysteresis 0.75, dan dedupe insiden buat alert Discord.
Ringkasan
Author bangun jam 2 pagi gara-gara alert CPU staging padahal cuma spike backup sesaat. Alertnya jadi dianggap spam sampai di-mute, dan alert yang di-mute lebih bahaya daripada nggak ada sama sekali. Solusinya: debounce 3 sampel, hysteresis recover di 0.75x, dedupe per insiden, plus pastikan alertnya beneran actionable.
Jam 2 pagi. HP bergetar. Notifikasi Discord: CPU server staging tembus 80%. Saya bangun, buka laptop, ngecek, dan ternyata itu cuma lonjakan sesaat dari nightly backup. Lima menit kemudian turun sendiri. Nggak ada yang perlu dilakukan. Paginya saya sadar hal yang lebih berbahaya: mulai hari itu, notifikasi alert dari server saya saya perlakukan kayak notifikasi promo e-commerce. Dilihat sekilas, terus di-swap.
Di titik itu saya percaya alert yang benar itu harus langsung bunyi begitu threshold tersentuh. Makin cepat tahu, makin baik. Lagipula kan itu gunanya monitoring, kalau CPU udah lewat 80% ya harus tahu dong, siapa tau ada masalah.
Ternyata pemikiran itu yang bikin alert saya mati secara sosial. Bukan karena broken, tapi karena berisik terus sampai saya mute channel-nya. Alert yang mati karena di-mute itu lebih berbahaya daripada nggak ada alert sama sekali: kamu merasa sudah dimonitor, padahal nggak.
Debounce dan Hysteresis Versi Rumah
Perbaikannya ada di alerts.py, sekarang 351 baris, yang menangani alert threshold buat fleet kecil saya. Tiga keputusan intinya.
Pertama, debounce sebelum firing. Alert nggak dihitung dari satu sampel, tapi dari tiga sampel terakhir (DEBOUNCE_N = 3). Fungsi should_fire(avg3, votes, warn) cuma mengembalikan true kalau rata-rata tiga sampel sudah menyentuh warn, ATAU mayoritas 2 dari 3 sampel juga di atas warn. Satu spike sesaat dari backup job nggak akan bisa bikin alert bunyi sendirian.
Kedua, hysteresis sebelum recover. Ini bagian yang dulu nggak pernah kepikiran sama saya. Kalau alert buka di warn 80 tapi recover di angka yang sama, threshold itu jadi kayak saklar lampu rusak: CPU naik-turun tipis di 80, alert on-off-on-off. Solusinya recover harus lebih rendah dari warn. Saya pakai faktor 0.75: should_recover(avg3, rec) cuma true kalau avg3 < rec, jadi warn 80 baru pulih di bawah 60. Rasio 0.75 ini pilihan lokal, bukan standar industri. Prometheus aja pakai durasi waktu lewat klausa keep_firing_for, bukan rasio [4].
Ketiga, dedupe per insiden. State disimpan di dict in-memory _STATE dengan key format server:kind. Satu pelanggaran threshold berarti satu alert, nggak spam berulang tiap sampel. Kalau kondisi pulih, dikirim catatan recovery singkat, selesai.
Konsepnya ini sebenernya bukan penemuan baru. Prometheus punya masalah yang sama persis dan menyelesaikannya dengan dua klausa: for buat menunda firing (alert mangkrak di status pending dulu, mirip debounce saya) dan keep_firing_for buat menahan alert tetap menyala sesudahnya, jebatan buat cegah flapping dan false resolution [4]. Jadi saya nggak lagi mengarang pola aneh, cuma mengimplementasi ulang versi sederhananya untuk skala kecil.
Alert Wajib Actionable
Google SRE punya aturan yang sekarang jadi pagarnya: setiap alert yang mengganggu manusia harus lolos pertanyaan "apakah aturan ini mendeteksi kondisi yang urgent, actionable, dan aktif atau hampir terlihat oleh user?" Kalau nggak lolos, itu disebut alert spam dan akan jadi noise yang nggak dibaca lagi [5]. Lonjakan CPU 30 detik karena backup job gagal di semua kriteria itu. Nggak urgent, nggak actionable, nggak kelihatan user.
Ada satu detail kecil yang saya senengin dari desain ini: recovery adalah event kelas satu, bukan sekadar alert yang hilang. Ini satu semangat dengan freshness check di Nagios yang memperlakukan pemulihan sebagai keadaan yang sah dan penting dicatat [3]. Tiap insiden dicatat satu baris di tabel alert_events, dan saat pulih baris itu di-UPDATE, bukan di-insert baru. Riwayatnya rapi: kapan mulai, kapan selesai.
Kategori alert juga dipetakan biar embed dan riwayatnya bisa dikelompokkan: ssh_bruteforce masuk security, apa pun berawalan container: masuk availability, sisanya default ke performance. Discord embed sendiri punya batas keras yang harus dihormati, MAX_FIELDS = 25 dan nilai field mentok di 1024 karakter, jadi formatter-nya memotong dulu sebelum Discord menolak.
Satu keputusan yang saya pegang: alerting ini fail-open. Kalau Discord lagi down atau store gagal, error cuma di-log dan dibuang. Collector loop nggak pernah boleh keblokir gara-gara sistem alertnya sendiri mati. Monitoring yang bisa bikin monitoring-mu down itu lucu sekali ceritanya.
Opini Saya Sekarang
Alert yang tidak actionable bukan alert. Itu spam yang perlahan melatihmu mengabaikan sistemmu sendiri, dan itu lebih berbahaya daripada buta sama sekali karena kamu merasa aman. Setiap kali mau menambah rule alert baru, saya sekarang nanya satu hal dulu: kalau ini bunyi jam 2 pagi, apa yang akan saya lakukan? Kalau jawabannya cuma lihat terus tutup, ya jangan dikirim ke HP. Catat di dashboard aja.
Debounce 3 sampel, hysteresis 0.75, dedupe per server:kind, angka-angkanya nggak sakral dan bisa berubah. Tapi prinsipnya nggak: alert harus menanggung beban pembuktian sebelum boleh mengganggu manusia.
Sumber
- Alerting rules — Prometheus Documentation. "The optional for clause causes Prometheus to wait for a certain duration between first encountering a new expression output vector element and counting an alert as firing for this element." dan "There is also an optional keep_firing_for clause that tells Prometheus to keep this alert firing for the specified duration after the firing condition was last met." Diakses 2026-09-16.
- Monitoring Distributed Systems — Google SRE Book. "Does this rule detect an otherwise undetected condition that is urgent, actionable, and actively or imminently user-visible?" dan "Every page should be actionable." Diakses 2026-09-16.
- Host and Service Freshness Checks — Nagios Core Documentation. "If the age of the last check result is greater than the freshness threshold, the check result is considered stale." Diakses 2026-09-16.