Kodeloka
  1. Kodeloka
  2. Kelas RabbitMQ
  3. Materi

Modul 5 · Modul 5 dari 15

Acknowledgement & Prefetch: Mengendalikan Aliran Pesan

Mengatur berapa banyak pekerjaan yang boleh dipegang satu konsumer, dan memahami apa yang sebenarnya terjadi saat pesan ditolak.

Tiga Cara Menutup Sebuah Pesan

PerintahArtiNasib pesan
basic.ackBerhasil diprosesDihapus dari queue
basic.nack(requeue=true)Gagal, coba lagiKembali ke queue
basic.nack(requeue=false)Gagal, jangan diulangKe DLX, atau dibuang
basic.rejectSama seperti nack, tapi satu pesan sajaSesuai flag requeue

Perbedaan nack dan reject: nack mendukung flag multiple untuk menutup banyak pesan sekaligus. Dalam praktik, nack hampir selalu yang dipakai.

⛔ requeue=true tanpa penundaan = poison message loop

Bila pemrosesan gagal karena payload memang rusak, mengembalikannya ke queue berarti ia akan langsung diambil lagi, gagal lagi, dikembalikan lagi — ribuan kali per detik.

text
pesan rusak → nack(requeue=true) → head of queue → ambil lagi
   → gagal lagi → nack lagi → ...  CPU 100%, queue macet total

Pesan itu juga kembali ke depan antrean, sehingga memblokir pesan sehat di belakangnya. Solusinya: requeue=false dengan DLX (bab 7), atau batas x-delivery-limit pada quorum queue.

Prefetch: Setelan Paling Berdampak yang Sering Dilewatkan

basic.qos(prefetch_count=N) membatasi berapa pesan boleh berstatus unacked pada satu konsumer. Tanpa ini, broker mendorong pesan sebanyak yang bisa ditampung jaringan.

pengaruh prefetchtext
prefetch = 0 (tanpa batas)
  broker  ──── 50.000 pesan ────> konsumer   memori klien meledak
                                             pembagian beban rusak

prefetch = 1
  broker  ──── 1 pesan ────> konsumer
          <─── ack ─────────
  paling adil, tetapi ada jeda jaringan pada setiap pesan
  → throughput rendah untuk tugas yang cepat

prefetch = 20
  broker  ──── 20 pesan ────> konsumer  (pipeline terisi)
  keseimbangan yang baik untuk mayoritas kasus
🎯 Cara memilih nilai prefetch
text
Tugas lambat, durasi bervariasi (mis. panggil API pihak ketiga, 2–30 detik)
  → prefetch = 1..5
  Alasan: pembagian beban yang adil lebih berharga daripada throughput.
          Prefetch besar membuat satu konsumer memegang pekerjaan
          yang seharusnya dikerjakan konsumer lain yang sedang menganggur.

Tugas cepat dan seragam (mis. transformasi JSON, < 50 ms)
  → prefetch = 100..300
  Alasan: latensi jaringan mendominasi; pipeline perlu tetap terisi.

Rumus awal yang berguna:
  prefetch ≈ (waktu bolak-balik jaringan / waktu proses per pesan) + 1
  lalu ukur dan sesuaikan.

Prefetch Global vs Per Konsumer

cakupan prefetchtext
channel.qos(prefetch, global=false)   ← DEFAULT dan hampir selalu benar
  batas berlaku PER KONSUMER pada channel itu

channel.qos(prefetch, global=true)
  batas berlaku untuk SELURUH channel, dibagi semua konsumer di dalamnya

Contoh: 1 channel, 3 konsumer, prefetch 10
  global=false → tiap konsumer boleh 10 unacked  (total 30)
  global=true  → ketiganya berbagi jatah 10      (total 10)

Siklus Redelivery

kapan pesan dikirim ulangtext
Pesan dikembalikan ke queue bila:
  • konsumer memanggil nack/reject dengan requeue=true
  • channel ditutup sebelum ack
  • koneksi terputus (jaringan, restart aplikasi, deploy)
  • konsumer dibatalkan sementara pesan masih unacked

Pesan yang dikirim ulang ditandai:  redelivered = true
⚠️ redelivered=true bukan penghitung percobaan

Flag ini hanya bernilai benar atau salah — ia tidak memberi tahu berapa kali pesan sudah dicoba. Pesan yang gagal 500 kali punya flag yang sama dengan yang gagal sekali.

Untuk menghitung percobaan, gunakan salah satu:

  • Quorum queue dengan x-delivery-limit — broker menghitung sendiri, cara paling andal.
  • Header hitungan yang Anda kelola sendiri saat mem-publish ulang lewat DLX (bab 7).

Konsekuensi bagi Deployment

Saat konsumer di-deploy ulang, semua pesan unacked miliknya kembali ke queue dan diproses ulang oleh instance lain. Dengan prefetch 300 dan lima konsumer, satu deploy bisa berarti 1.500 pesan diproses dua kali.

graceful shutdownjavascript
let sedangMati = false;

process.on('SIGTERM', async () => {
  sedangMati = true;
  // 1. berhenti menerima pesan baru
  await channel.cancel(consumerTag);
  // 2. beri waktu pesan yang sedang diproses untuk selesai
  await tungguSampaiSelesai({ timeoutMs: 25000 });
  // 3. baru tutup
  await channel.close();
  await koneksi.close();
  process.exit(0);
});

Pasangkan dengan terminationGracePeriodSeconds di Kubernetes yang lebih besar dari timeout di atas, agar pod tidak dibunuh paksa di tengah pekerjaan.

Membaca Angka Unacked

diagnosisbash
rabbitmqctl list_queues name messages messages_ready messages_unacknowledged consumers
Pola angkaArtinya
ready tinggi, unacked 0, consumers 0Tidak ada konsumer — aplikasi mati atau gagal connect
ready tinggi, unacked = prefetch × konsumerKonsumer ada tapi terlalu lambat — tambah konsumer
unacked tinggi dan diamKonsumer menggantung, lupa ack, atau deadlock
ready naik-turun stabilSehat