Runbook on-call untuk queue dan cache bukan sekadar daftar perintah saat produksi bermasalah. Tujuan utamanya adalah menurunkan beban kognitif engineer ketika menghadapi insiden berulang seperti job menumpuk, retry loop, cache stampede, lock macet, duplicate job, data stale, dan worker hang. Jika sistem hanya dioptimalkan untuk throughput, tim tetap bisa kelelahan karena harus terus-menerus memadamkan kebakaran yang sama.
Prinsip dasarnya sederhana: operasional yang sehat adalah operasional yang mengurangi keputusan manual saat kondisi buruk. Artinya, metrik harus jelas, prioritas triase harus tegas, mitigasi harus aman, dan auto-remediation hanya dipasang pada kasus yang risikonya dipahami. Konteks ini sejalan dengan realitas burnout maintainer: masalah bukan cuma volume kerja, tetapi akumulasi alarm, ambiguitas, dan tanggung jawab yang terus aktif di kepala.
Jika sebuah insiden sering terjadi dan runbook masih mengandalkan engineer senior untuk “menebak langkah berikutnya”, masalahnya bukan hanya di sistem produksi, tetapi juga di desain operasionalnya.
Mengapa queue dan cache sering jadi sumber insiden on-call
Queue dan cache berada di jalur kritis banyak sistem backend. Keduanya sering dipilih untuk meningkatkan throughput dan latensi, tetapi keduanya juga memperkenalkan mode kegagalan yang tidak selalu terlihat dari sisi API utama.
Queue: kegagalan yang tampak kecil tapi menumpuk
Masalah queue sering berawal dari satu job yang lambat, dependency eksternal yang gagal, atau perubahan schema data yang membuat worker error. Efeknya bisa berantai:
- Backlog meningkat karena throughput worker lebih kecil dari laju job masuk.
- Retry loop memperbesar beban antrian dan biaya komputasi.
- Duplicate job menyebabkan side effect ganda, misalnya email terkirim dua kali atau stok dipotong lebih dari sekali.
- Worker hang membuat sistem terlihat “hidup” tetapi tidak benar-benar memproses pekerjaan.
Cache: cepat saat sehat, brutal saat salah
Cache sering gagal dengan pola yang menyiksa on-call karena dampaknya bisa mendadak dan luas:
- Cache stampede: banyak request serentak meregenerasi key yang sama saat cache kedaluwarsa.
- Data stale: data lama tetap tersaji setelah sumber data berubah.
- Invalidasi berlebihan: cache miss melonjak dan database ikut tumbang.
- Lock macet: mekanisme proteksi regenerasi cache justru membuat proses lain menunggu tanpa akhir.
Karena queue dan cache memisahkan komputasi dari request utama, gejalanya sering muncul tidak sinkron. API bisa tampak normal, tetapi data belum terproses. Atau sebaliknya, queue sehat tetapi database overload karena cache miss serentak. Inilah alasan runbook harus berbasis gejala dan dampak, bukan sekadar berbasis komponen.
Indikator wajib yang harus dipantau
Monitoring yang terlalu banyak justru memperparah burnout. Fokus pada indikator yang membantu menjawab tiga pertanyaan: apa yang rusak, seberapa parah dampaknya, dan langkah aman apa yang tersedia.
Indikator queue
- Queue depth / backlog: jumlah job yang menunggu.
- Oldest job age: umur job tertua; sering lebih berguna daripada jumlah backlog mentah.
- Enqueue rate vs processing rate: apakah worker mengejar atau tertinggal.
- Success rate, failure rate, retry rate: untuk membedakan lonjakan volume dari loop kegagalan.
- Time in queue dan job execution time: membantu melihat bottleneck di antrean atau saat eksekusi.
- DLQ growth: indikator penting bahwa retry sudah tidak menyelesaikan masalah.
- Worker liveness dan worker concurrency utilization: worker hidup belum tentu produktif.
Indikator cache
- Hit ratio: berguna, tetapi jangan dipakai sendirian.
- Miss rate per key pattern: lebih membantu untuk mendeteksi invalidasi massal atau stampede pada subset data.
- Latency backend origin saat cache miss: untuk melihat apakah cache sedang melindungi atau justru membuka jalan ke overload.
- Eviction rate dan penggunaan memori: penting untuk mendeteksi churn.
- Lock wait time dan lock acquisition failure: krusial jika memakai distributed lock untuk regenerasi cache atau deduplikasi job.
- Stale serve count jika menerapkan stale-while-revalidate.
Indikator dampak bisnis
On-call tidak boleh buta konteks. Selain metrik teknis, pantau indikator yang mewakili dampak pengguna:
- Jumlah transaksi tertunda.
- Pesanan yang belum diproses.
- Notifikasi yang terlambat dikirim.
- Gap antara data sumber dan data yang ditampilkan ke pengguna.
- Jumlah request gagal atau timeout pada endpoint yang bergantung pada cache/queue.
Metrik ini membantu menentukan prioritas. Queue email yang menumpuk seribu job mungkin lebih aman daripada queue pembayaran yang menumpuk seratus job.
Prioritas triase: urutkan dari dampak, bukan dari bunyi alarm
Saat beberapa gejala muncul bersamaan, engineer mudah terjebak memperbaiki hal yang paling terlihat, bukan yang paling berbahaya. Gunakan urutan triase berikut.
1. Validasi dampak pengguna dan data
- Apakah pengguna gagal menyelesaikan aksi penting?
- Apakah ada risiko korupsi data, side effect ganda, atau kehilangan event?
- Apakah masalah hanya soal keterlambatan, atau sudah masuk kategori data salah?
Data salah umumnya lebih prioritas daripada data terlambat. Duplicate charge lebih kritis daripada email konfirmasi tertunda.
2. Identifikasi mode kegagalan utama
- Backlog murni: volume naik, worker lambat, tetapi success rate tetap tinggi.
- Retry storm: failure rate tinggi dan job yang sama berputar terus.
- Dependency outage: queue sehat secara internal, tetapi target eksternal gagal merespons.
- Cache collapse: hit ratio jatuh, origin latency naik, lock atau regenerasi jadi bottleneck.
- Worker stalled: tidak ada progres walau worker process terlihat aktif.
3. Pilih mitigasi dengan risiko side effect terendah
Runbook yang baik selalu menandai langkah mana yang aman, berisiko sedang, dan harus eskalasi. Contoh:
- Aman: pause consumer tertentu, turunkan concurrency job bermasalah, aktifkan stale read terbatas, alihkan job gagal ke DLQ.
- Risiko sedang: replay job, flush subset cache, scale worker, ubah timeout sementara.
- Harus eskalasi: flush cache global, hapus lock secara manual tanpa verifikasi owner/TTL, replay semua DLQ, menaikkan retry tanpa batas, menjalankan script koreksi data di produksi.
Runbook gejala per gejala
Job menumpuk
Gejala: backlog naik, oldest job age meningkat, processing rate kalah dari enqueue rate.
Penyebab umum:
- Lonjakan trafik normal.
- Worker concurrency terlalu kecil.
- Job melambat karena query atau dependency eksternal.
- Queue tercampur antara job berat dan ringan tanpa isolasi.
Langkah mitigasi aman:
- Periksa apakah success rate tetap baik. Jika iya, masalahnya mungkin kapasitas, bukan bug.
- Lihat jenis job dominan yang mengisi antrean.
- Jika memungkinkan, prioritaskan queue kritikal dan pause queue non-kritikal.
- Naikkan concurrency secara bertahap hanya jika database, CPU, dan dependency masih punya headroom.
- Jika ada job berat yang panjang, pindahkan ke queue terpisah agar tidak memblokir job cepat.
Trade-off: scaling worker memangkas backlog, tetapi bisa memperparah bottleneck di database atau service eksternal. Tambah worker bukan jawaban universal.
Retry loop
Gejala: retry rate tinggi, backlog membesar, error signature berulang, biaya komputasi naik.
Penyebab umum:
- Error permanen diperlakukan sebagai error sementara.
- Retry tanpa backoff.
- Timeout terlalu pendek sehingga job gagal sebelum dependency sempat merespons.
- Payload atau data input rusak.
Langkah mitigasi aman:
- Kelompokkan error: transient, permanent, dan unknown.
- Hentikan retry untuk error yang jelas permanen, misalnya validasi payload gagal.
- Arahkan job gagal berulang ke DLQ setelah batas percobaan yang masuk akal.
- Aktifkan exponential backoff dengan jitter untuk error sementara.
- Jika dependency eksternal sedang outage, pertimbangkan pause consumer untuk job terkait agar antrean tidak dipenuhi noise.
Batas retry yang sehat: tidak ada angka sakral untuk semua kasus, tetapi prinsipnya jelas: retry harus dibatasi dan semakin jarang seiring kegagalan berulang. Retry rapat tanpa backoff hampir selalu memperburuk insiden.
// Pseudocode retry policy yang aman secara prinsip
function shouldRetry(error, attempt) {
if (error.type === 'validation') return false;
if (error.type === 'duplicate') return false;
if (attempt >= 5) return false;
return error.type === 'timeout' || error.type === 'rate_limit' || error.type === 'temporary';
}
function nextDelayMs(attempt) {
const base = Math.min(30000, 1000 * Math.pow(2, attempt));
const jitter = Math.floor(Math.random() * 500);
return base + jitter;
}Mengapa pendekatan ini bekerja? Karena ia mencegah error permanen memenuhi queue, sekaligus memberi dependency waktu pulih tanpa diserang ulang serentak.
Cache stampede
Gejala: banyak request untuk key yang sama memicu hit ke origin bersamaan; latency origin melonjak; cache miss tiba-tiba tinggi.
Mitigasi:
- Gunakan request coalescing atau lock per key agar hanya satu proses yang meregenerasi nilai.
- Terapkan stale-while-revalidate untuk data yang boleh sedikit usang.
- Tambahkan TTL jitter agar key tidak kedaluwarsa bersamaan.
- Bedakan TTL berdasarkan biaya regenerasi dan toleransi stale.
// Pseudocode cache-aside dengan stale-while-revalidate
value = cache.get(key)
if (value is fresh) return value
if (value is stale_but_acceptable) {
triggerAsyncRefresh(key)
return value
}
if (acquireLock("refresh:" + key, ttl=30s)) {
fresh = loadFromOrigin()
cache.set(key, fresh, ttlWithJitter())
releaseLock()
return fresh
}
return fallbackOrStaleValue()Trade-off: stale-while-revalidate mengurangi tekanan ke origin dan menolong on-call, tetapi Anda menerima data yang mungkin beberapa detik atau menit tertinggal. Cocok untuk katalog, konfigurasi non-kritis, atau agregasi. Tidak cocok untuk saldo real-time atau otorisasi sensitif.
Lock macet
Gejala: job menunggu lock terlalu lama, throughput jatuh, proses lain tidak bisa maju.
Penyebab umum:
- Lock tanpa TTL.
- TTL terlalu panjang dibanding durasi kerja normal.
- Pelepasan lock tidak aman.
- Pemilik lock crash dan tidak ada recovery.
Aturan praktis:
- Selalu gunakan lock dengan TTL.
- Simpan identitas owner/token agar hanya pemilik yang bisa melepas lock.
- Ukur lock wait time dan jumlah timeout akuisisi lock.
- Dokumentasikan syarat kapan lock boleh dihapus manual.
Jangan menghapus lock manual hanya karena melihat key masih ada. Verifikasi dulu apakah owner masih aktif dan apakah proses yang berjalan benar-benar buntu. Menghapus lock sembarangan bisa memicu eksekusi ganda.
Duplicate job dan side effect ganda
Gejala: pengguna menerima notifikasi ganda, data diproses dua kali, event yang sama masuk berkali-kali.
Penyebab umum:
- Producer mengirim ulang event saat timeout tanpa idempotency key.
- Consumer gagal setelah side effect terjadi, lalu job diretry.
- Visibility timeout atau ack mekanisme tidak sesuai durasi kerja.
Mitigasi inti: buat consumer idempotent. Ini lebih penting daripada berharap sistem queue selalu exactly-once, karena dalam praktik banyak sistem distribusi lebih realistis menawarkan at-least-once delivery.
// Pseudocode idempotent consumer
function handlePaymentEvent(event) {
if (processedStore.exists(event.idempotencyKey)) {
return "already_processed";
}
beginTransaction();
if (processedStore.exists(event.idempotencyKey)) {
rollback();
return "already_processed";
}
applyBusinessChange(event);
processedStore.save(event.idempotencyKey);
commit();
}Trade-off: menyimpan jejak idempotensi menambah storage dan kompleksitas retention policy, tetapi biaya itu biasanya lebih murah daripada memperbaiki data akibat side effect ganda.
Data stale
Gejala: API sukses, tetapi pengguna melihat data lama; dashboard berbeda dengan database sumber.
Penyebab umum:
- Invalidasi cache tidak sinkron dengan write path.
- Asynchronous projection terlambat memproses event.
- Job pembaruan gagal diam-diam.
Mitigasi:
- Tentukan dulu apakah stale ini masih dalam SLA yang dapat diterima.
- Periksa lag pada queue/projection yang bertanggung jawab memperbarui cache atau read model.
- Jika perlu, lakukan rebuild parsial untuk subset data yang terdampak, bukan flush global.
- Tambahkan metadata umur data pada respons atau dashboard internal agar stale mudah dikenali.
Kesalahan umum di sini adalah menyamakan “sukses menulis ke primary database” dengan “semua turunan data sudah konsisten”. Pada arsitektur asynchronous, konsistensi sering bersifat eventual, dan runbook harus menyatakan batas waktu wajarnya.
Worker hang
Gejala: process worker ada, tetapi tidak ada job selesai; CPU rendah atau satu thread macet; heartbeat tidak berubah.
Penyebab umum:
- Deadlock di aplikasi atau database.
- Blocking I/O tanpa timeout.
- Memory leak yang membuat garbage collection atau swapping berat.
- Bug pada dependency native atau koneksi yang menggantung.
Mitigasi aman:
- Pastikan ada health signal yang lebih baik daripada sekadar process hidup, misalnya heartbeat per worker atau progress timestamp.
- Restart worker yang tidak membuat progres dalam ambang waktu tertentu.
- Gunakan timeout pada operasi eksternal.
- Jika hang berulang di tipe job tertentu, karantina job itu dan eskalasi ke pemilik layanan.
Auto-restart worker bisa sangat efektif mengurangi beban on-call, tetapi hanya aman jika job idempotent atau punya mekanisme recovery yang jelas.
Desain runbook yang benar-benar membantu manusia
Banyak runbook gagal karena terlalu panjang, terlalu umum, atau terlalu bergantung pada konteks yang hanya diketahui orang tertentu. Runbook yang baik harus bisa dipakai saat engineer lelah pada jam 03.00.
Struktur runbook yang disarankan
- Sinyal pemicu: alarm apa yang menyalakan runbook ini.
- Dampak: risiko bisnis dan teknis dari gejala tersebut.
- Checklist verifikasi cepat: 3-5 langkah untuk memastikan diagnosis awal.
- Mitigasi aman: langkah yang boleh dilakukan tanpa persetujuan tambahan.
- Mitigasi berisiko: langkah yang perlu approval atau eskalasi.
- Kondisi eskalasi: kapan harus memanggil tim lain atau incident commander.
- Rollback/undo: cara membatalkan mitigasi sementara.
- Tindak lanjut pasca-insiden: bug, test, observability, atau perubahan arsitektur yang harus dibuat.
Contoh potongan runbook singkat
Judul: Backlog Queue Pembayaran Meningkat
Pemicu:
- oldest_job_age > 5 menit
- queue_depth naik terus selama 10 menit
Risiko:
- pembayaran terlambat diproses
- potensi duplicate processing jika replay sembarangan
Verifikasi cepat:
1. Cek failure rate dan retry rate
2. Cek latency database dan dependency payment gateway
3. Cek worker heartbeat dan concurrency
4. Identifikasi top 3 job type di backlog
Mitigasi aman:
- pause consumer notifikasi non-kritikal
- arahkan permanent failures ke DLQ
- turunkan retry agresif jika dependency outage
- scale worker bertahap jika DB sehat
Jangan lakukan tanpa eskalasi:
- replay semua DLQ
- flush cache global terkait status pembayaran
- hapus lock manual tanpa verifikasi owner
Eskalasi jika:
- ada indikasi duplicate charge
- backlog terus naik setelah 15 menit mitigasi
- payment gateway error rate tinggi dan belum stabilFormat seperti ini menurunkan beban mental karena engineer tidak perlu menyusun ulang logika insiden dari nol.
Auto-remediation yang layak, dan yang sebaiknya jangan diotomatisasi
Auto-remediation berguna jika masalah berulang, diagnosisnya tegas, dan aksi perbaikannya kecil risikonya. Tujuannya bukan menghilangkan manusia sepenuhnya, tetapi memotong pekerjaan repetitif yang tidak memberi nilai.
Layak diotomatisasi
- Restart worker yang heartbeat-nya mati atau tidak membuat progres.
- Memindahkan job ke DLQ setelah retry limit tercapai.
- Menerapkan backoff otomatis untuk dependency yang rate-limited atau timeout sementara.
- Melayani stale cache terbatas ketika origin melambat.
- Menandai atau mengkarantina job type yang memicu error signature permanen tertentu.
Sebaiknya tidak diotomatisasi tanpa guardrail kuat
- Replay DLQ massal.
- Flush cache global.
- Menghapus distributed lock manual.
- Menjalankan migrasi data korektif.
- Menaikkan concurrency besar-besaran tanpa memeriksa downstream capacity.
Alasannya sederhana: aksi-aksi ini bisa mempercepat pemulihan, tetapi juga bisa mengubah insiden lokal menjadi insiden sistemik.
Retry, backoff, DLQ, dan idempotensi: fondasi operasional yang menurunkan burnout
Retry harus selektif
Jangan retry semua error. Pisahkan minimal menjadi:
- Transient: timeout, rate limit, dependency sementara gagal.
- Permanent: payload invalid, referensi data tidak ada, kontrak input rusak.
- Unknown: perlu observasi tambahan; batasi retry dan log secara kaya konteks.
Backoff harus bertambah dan diberi jitter
Tanpa jitter, banyak worker bisa retry bersamaan dan menciptakan lonjakan sinkron. Dengan jitter, permintaan tersebar dan dependency punya kesempatan pulih.
DLQ bukan tempat sampah
DLQ berguna untuk menahan job bermasalah agar antrean utama tidak tersumbat. Tetapi DLQ harus punya prosedur:
- Klasifikasi penyebab kegagalan.
- Penentuan siapa pemilik analisisnya.
- Kriteria aman untuk replay.
- Batas waktu retensi.
Kesalahan umum adalah menaruh semua job gagal ke DLQ lalu melupakannya sampai backlog baru muncul.
Idempotensi adalah asuransi utama
Jika job bisa diulang dengan hasil akhir yang sama, banyak mitigasi menjadi lebih aman: restart worker, retry, replay, bahkan beberapa bentuk auto-remediation. Tanpa idempotensi, setiap intervensi on-call punya risiko merusak data.
Kapan harus eskalasi
Eskalasi bukan tanda gagal. Justru runbook yang baik mendefinisikannya dengan jelas agar engineer tidak menunggu terlalu lama atau nekat melakukan tindakan berisiko.
Eskalasi segera jika:
- Ada potensi korupsi data atau side effect finansial ganda.
- Insiden melibatkan lebih dari satu bounded context atau tim pemilik.
- Mitigasi aman tidak menghentikan pertumbuhan backlog atau error dalam ambang waktu yang ditentukan.
- Diperlukan perubahan konfigurasi berisiko tinggi.
- Root cause mengarah ke dependency eksternal atau infrastruktur bersama.
Eskalasi juga perlu ketika beban mental on-call sudah terlalu tinggi. Salah satu pelajaran penting dari konteks burnout maintainer adalah bahwa sistem operasional harus mengakui batas manusia. Jika diagnosis masih kabur setelah beberapa langkah awal, panggil bantuan lebih cepat daripada mempertaruhkan keputusan terburu-buru.
Checklist operasional untuk on-call backend
Saat alarm masuk
- Identifikasi layanan, queue, atau cache yang terdampak.
- Nilai dampak ke pengguna dan risiko konsistensi data.
- Cek dashboard backlog, error rate, retry rate, oldest job age, hit/miss cache, lock wait, dan worker heartbeat.
- Bandingkan laju masuk dan laju proses.
- Pastikan apakah masalah kapasitas, bug, dependency, atau lock/contention.
Sebelum mitigasi
- Pastikan job yang akan direstart atau direplay aman secara idempotensi.
- Cek apakah ada lock aktif yang valid.
- Pastikan dependency downstream masih punya kapasitas.
- Pilih mitigasi dengan risiko side effect terendah.
- Catat waktu mulai insiden dan tindakan yang dilakukan.
Saat mitigasi
- Ubah satu variabel dalam satu waktu jika memungkinkan.
- Amati metrik 5-15 menit sesuai karakter sistem, bukan hanya beberapa detik.
- Jika pause queue non-kritikal, catat cara mengaktifkannya kembali.
- Jika mengarahkan ke DLQ, simpan alasan dan signature error.
- Jangan replay massal tanpa bukti penyebab utama sudah hilang.
Setelah pemulihan
- Hitung backlog drain time dan sisa risiko data stale.
- Verifikasi job kritikal kembali sukses.
- Buat tindak lanjut: test idempotensi, timeout, observability, pemisahan queue, atau perbaikan invalidasi cache.
- Perbarui runbook berdasarkan pelajaran baru.
- Evaluasi apakah alarm terlalu sensitif, terlalu lambat, atau tidak action-oriented.
Contoh trade-off yang sering muncul
Scale worker vs lindungi database
Jika backlog naik tetapi database sudah mendekati batas, menaikkan worker bisa mempercepat kerusakan. Dalam kondisi ini, lebih aman menurunkan concurrency job berat, memprioritaskan queue kritikal, atau mengaktifkan stale cache untuk menekan query.
Serve stale vs konsistensi real-time
Untuk data non-kritis, melayani data stale beberapa menit bisa jauh lebih baik daripada menjatuhkan origin dan membangunkan lebih banyak engineer. Untuk data sensitif, lebih baik gagal secara eksplisit daripada menyajikan informasi yang salah.
Replay cepat vs analisis penyebab
Replay DLQ memang menggoda karena memberi ilusi pemulihan cepat. Tetapi jika akar masalah belum selesai, replay hanya mengulang insiden. Pilih replay setelah ada bukti bahwa error permanen telah dipisahkan dan dependency sudah sehat.
Penutup
Runbook on-call untuk queue dan cache tanpa bikin tim burnout berarti merancang operasi agar manusia tidak terus-menerus menjadi komponen kompensasi atas desain sistem yang rapuh. Fokusnya bukan hanya throughput, melainkan operability: metrik yang tepat, triase yang jelas, mitigasi aman, retry yang dibatasi, DLQ yang benar-benar dikelola, idempotensi, dan auto-remediation yang dipasang dengan disiplin.
Jika sebuah insiden queue atau cache terus muncul, target perbaikannya jangan berhenti pada “alarm lebih cepat” atau “tambah worker”. Tanyakan juga: keputusan manual apa yang masih berulang, langkah apa yang masih ambigu, dan risiko apa yang bisa dipindahkan dari kepala engineer ke desain sistem. Di situlah runbook yang baik benar-benar mengurangi burnout, bukan sekadar mendokumentasikan kepanikan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!