Keandalan18 April 20267 mnt baca

Backoff Eksponensial dan Jitter untuk Percobaan Ulang (Retry) Webhook

Mengapa backoff eksponensial (exponential backoff) yang naif memicu thundering herd, cara jitter memperbaikinya, dan wujud jadwal retry kelas production.

JARAK ANTARUPAYA · EKSPONENSIALsekarang5 mnt30 mnt3 j18 jberhentistatus=failed · payload tetap ada di log · tetap dapat diputar ulang
Jadwal percobaan ulang yang makin renggang di setiap upaya. Upaya terakhir bukan kesempatan terakhir: event yang percobaan ulangnya habis tetap ada di log dan tetap dapat diputar ulang.

TL;DR

  • Backoff eksponensial yang naif (delay = base * 2^attempt) menciptakan thundering herd, karena setiap klien mencoba ulang secara bersamaan.
  • Tambahkan jitter (keacakan) untuk menyebar percobaan ulang (retry). Equal jitter (base/2 + random(base/2)) adalah titik idealnya.
  • Batasi jeda maksimum, batasi total durasi percobaan ulang, dan pisahkan 4xx (jangan dicoba ulang) dari 5xx (coba ulang).
  • Aktifkan circuit breaker di level tujuan setelah sekitar 20 kegagalan berturut-turut. Percobaan ulang tidak akan memperbaiki endpoint yang rusak.
  • Putar ulang (replay) yang murah mengalahkan percobaan ulang yang agresif: jika memutar ulang cukup satu klik, Anda hanya butuh percobaan ulang untuk menangani kegagalan sementara.

Sebagian besar kode percobaan ulang yang ditemui di lapangan terlihat seperti ini:

for (let i = 0; i < maxRetries; i++) {
  try {
    return await deliver(event);
  } catch {
    await sleep(1000 * Math.pow(2, i));  // 1s, 2s, 4s, 8s...
  }
}

Ini backoff eksponensial, dan lebih baik daripada percobaan ulang dengan interval tetap. Tetapi ini juga salah dengan cara yang hanya muncul di production, saat ratusan webhook gagal bersamaan dan semuanya mencoba ulang pada saat yang sama, membanting server Anda yang sedang pulih kembali ke tanah.

Perbaikannya adalah jitter. Berikut alasannya, dan cara melakukannya dengan benar.

Thundering herd

Misalkan endpoint Anda mati pada T+0. Dalam 60 detik berikutnya, 500 webhook Stripe tiba dan semuanya gagal. Setiap antrean percobaan ulang kini berisi 500 percobaan ulang pertama yang dijadwalkan pada T+5min (atau berapa pun jeda dasar Anda), 500 percobaan ulang kedua pada T+10min, dan seterusnya.

Endpoint Anda kembali online pada T+4min. Selamat: kini Anda kedatangan 500 percobaan ulang serentak pada T+5min, yang mendorong server Anda yang masih memulihkan diri kembali melewati titik jenuhnya. Itulah thundering herd.

TANPA JITTER · SETIAP PERCOBAAN ULANG MEMILIH SAAT YANG SAMA500 percobaan ulang, satu milidetikEQUAL JITTER · JUMLAH SAMA, KURVA SAMAtersebar di sepanjang jendela waktuT+0T+5minT+10min
Sama-sama 500 percobaan ulang, kurva backoff yang sama. Satu-satunya perbedaan: jedanya berupa angka tetap atau sebuah rentang.

Backoff deterministik punya masalah ini sejak dari desainnya. Setiap klien atau event yang gagal pada waktu yang sama akan mencoba ulang pada waktu yang sama.

Jitter

Jitter menambahkan keacakan pada waktu percobaan ulang sehingga event dicoba ulang secara tersebar, bukan menumpuk dalam satu lonjakan. Tiga strategi yang umum:

Full jitter

const base = 1000 * Math.pow(2, attempt);  // 1s, 2s, 4s, 8s
const sleep = Math.random() * base;         // 0 to base

Setiap percobaan ulang menunggu antara 0 hingga interval backoff penuh. Sebarannya maksimal, tetapi sebagian percobaan ulang terjadi hampir seketika, yang bisa terasa tidak efisien.

Equal jitter

const base = 1000 * Math.pow(2, attempt);
const sleep = base / 2 + Math.random() * (base / 2);

Setiap percobaan ulang menunggu antara setengah hingga penuh interval backoff. Batas bawahnya terjaga, sebarannya tetap baik.

Decorrelated jitter

let sleep = baseDelay;
// on each retry:
sleep = Math.min(maxDelay, baseDelay + Math.random() * (sleep * 3 - baseDelay));

Jeda setiap percobaan ulang adalah kelipatan acak dari jeda sebelumnya. Cocok jika Anda ingin backoff bertambah tanpa rumus tetap.

Untuk webhook, equal jitter adalah titik idealnya. Percobaan ulang tetap kurang lebih eksponensial, sementara thundering herd teredam.

Seperti apa jadwal percobaan ulang yang layak untuk production

Di luar jitter, inilah yang dilakukan tim berpengalaman.

Batasi jeda maksimum. Jangan biarkan upaya ke-10 menunggu 17 menit. Tetapkan batas atas, misalnya 60 detik, dan setelah mencapai batas itu, tetap di sana sampai Anda menyerah.

Batasi total durasi percobaan ulang, bukan hanya jumlah upaya. “Coba ulang selama 24 jam” adalah SLA yang lebih jelas daripada “coba ulang 8 kali”, karena yang kedua bergantung pada hitungan jeda yang tidak diingat siapa pun.

Pisahkan error sementara dari error permanen. Respons 4xx dari tujuan, terutama 401, 403, atau 404, tidak akan berhasil jika dicoba ulang. Server sedang memberi tahu Anda bahwa permintaannya salah. Coba ulang hanya untuk 5xx, 429, timeout, dan error koneksi.

Tampilkan status percobaan ulang. Pengguna Anda perlu tahu bahwa sebuah event sedang berada di upaya ke-3 dari 5, dengan percobaan ulang berikutnya pada waktu tertentu. Jika tidak terlihat, debugging jadi menyiksa.

Pasang circuit breaker di level tujuan. Jika sebuah tujuan sudah gagal 20 kali berturut-turut, berhenti mencobanya ulang untuk sementara. Ada yang salah dan percobaan ulang tidak akan memperbaikinya, dan Anda hanya membakar komputasi.

Cara AnyHook menanganinya

Kebijakan percobaan ulang AnyHook disesuaikan per paket dan memakai infrastruktur percobaan ulang terkelola dari QStash di baliknya. Upaya pengiriman berjumlah 3 di Free, 5 di Pro, dan 10 di Scale, dengan tangga eksponensial tetap: 5 menit, 30 menit, 3 jam, 18 jam, lalu 24 jam untuk setiap upaya sesudahnya. Tidak ada jitter yang ditambahkan pada jeda tersebut. Batas waktu pengiriman masing-masing 60 detik, 120 detik, dan 5 menit. Setelah 20 kegagalan berturut-turut, circuit breaker terpicu dan menjeda pengiriman ke tujuan itu (10 menit, lalu 30 menit, 2 jam, dan 24 jam jika terpicu berulang) serta mengirimi Anda email, jadi tidak ada banjir percobaan ulang. Respons 4xx selain 408 dan 429 sama sekali tidak dicoba ulang, dan peringatan kegagalannya menjelaskan alasannya, karena percobaan ulang tidak akan membantu di sana dan Anda perlu memperbaiki bentuk permintaannya.

Percobaan ulang yang habis tidak menghilangkan event. Event tetap tersimpan di log Anda dengan status=failed, dan Anda bisa memutarnya ulang secara manual dari dashboard atau API kapan pun dalam masa putar ulang paket Anda (3 hari di Free, 30 di Pro, 90 di Scale).

Jalan pintasnya

Wawasan yang mengubah cara Anda memandang percobaan ulang: Anda tidak butuh percobaan ulang yang agresif jika punya putar ulang yang murah.

Jika memutar ulang cukup satu klik, mencoba ulang 3 kali dalam satu jam sudah cukup. Sebagian besar kegagalan sementara tertangani secara otomatis, dan untuk gangguan panjang yang jarang terjadi, seseorang memutar ulang batch yang gagal setelah server sehat kembali.

Bandingkan dengan sistem yang hanya mengandalkan percobaan ulang, ketika Anda memperpanjang rentang percobaan ulang sampai 72 jam untuk berjaga-jaga, yang berarti 72 jam pengiriman sia-sia ke endpoint yang sudah Anda tahu sedang mati.

Putar ulang yang murah jelas lebih baik daripada percobaan ulang yang agresif, dan prinsip itulah yang menjadi dasar AnyHook.

Kesimpulan

Perlakukan percobaan ulang sebagai jalur cepat untuk kegagalan sementara, bukan pengganti persistensi dan putar ulang. Sistem yang tetap bertahan di minggu yang buruk adalah sistem yang pengiriman gagalnya selalu bisa dipulihkan, karena event-nya sudah dicatat sebelum pernah dikirim.

Jadwal sebenarnya dari setiap pengirim ada di referensi provider webhook kami. Percobaan ulang juga punya batas keras yang tidak dikendalikan siapa pun: jika terus gagal, Shopify bisa menghapus langganannya, sementara Stripe menonaktifkan endpoint-nya. Dan percobaan ulang itulah alasan duplikat dan pengiriman yang tidak berurutan pasti terjadi, bukan hal langka: Anda Menerima Webhook yang Sama Dua Kali.

Semua artikel18 April 2026 · 7 mnt

Berhenti kehilangan webhook.

Ganti satu URL. Dapatkan percobaan ulang, log event, dan putar ulang sekali klik. Atau lihat dulu: daftar dan jalankan latihan gangguan 30 detik, lalu saksikan event tetap selamat saat server Anda mati. Tidak perlu server.