Belajar Backend Developer - Message Queues & Async
Episode 10 of 28

Belajar Backend Developer - Message Queues & Async

Melepas beban dari request dengan pemrosesan asinkron: mengenal message queue (RabbitMQ, Kafka, SQS), membangun worker, pola reliable delivery, dan menerapkannya pada notifikasi order dan pembayaran

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Di episode 9 kita mempercepat request yang sinkron. Tapi ada jenis pekerjaan yang tidak boleh dilakukan di dalam request sama sekali: mengirim email, memproses pembayaran, meng-generate laporan, atau memanggil API eksternal yang lambat. Jika semua ini menunggu dalam satu request, response jadi lambat — dan jika layanan eksternal mati, request ikut gagal.

Solusinya adalah pemrosesan asinkron dengan message queue: request hanya mencatat pekerjaan ("kirim email untuk order 87"), lalu langsung merespons. Pekerjaan dijalankan di worker terpisah. Episode ini membangun konsep queue, mengenal RabbitMQ, Kafka, dan SQS, serta menerapkannya pada notifikasi order dan pembayaran.

Kenapa Async?

Bayangkan alur checkout tanpa queue: buat order → kirim email → panggil payment → update stok, semua di dalam satu request. Jika email service lambat 3 detik, user menunggu 3 detik; jika mati, checkout gagal total.

100%

Request utama selesai dalam milidetik; pekerjaan berat berjalan paralel di worker. Keuntungan lain: worker bisa diskalakan terpisah, dan pesan yang gagal bisa dicoba lagi.

Konsep Queue

Pahami lima istilah inti:

  • Producer — pihak yang mengirim pesan (API yang menerima order).
  • Queue — penyangga pesan yang menunggu diproses.
  • Consumer/Worker — pihak yang memproses pesan.
  • Message — unit pekerjaan; idealnya JSON kecil dan self-contained.
  • Acknowledgment (ack) — tanda pesan sudah diproses; tanpanya pesan dikirim ulang.

RabbitMQ: Broker Serba Guna

RabbitMQ adalah message broker yang paling umum untuk pola work queue — satu pesan dikonsumsi satu worker. Cocok untuk notifikasi, email, dan tugas-tugas yang dikerjakan sekali.

Jalankan RabbitMQ di Docker
docker run -d --name rabbit-lab -p 5672:5672 -p 15672:15672 rabbitmq:3-management

Producer

Producer - publish pesan
import amqp from "amqplib"
 
const conn = await amqp.connect(process.env.RABBITMQ_URL!)
const channel = await conn.createChannel()
 
await channel.assertQueue("notifications", { durable: true })
 
router.post("/v1/orders", async (req, res) => {
  const order = await createOrder(req.body)
  channel.sendToQueue("notifications", Buffer.from(JSON.stringify({
    type: "order.created",
    orderId: order.id,
    email: req.body.email,
  })), { persistent: true })
  res.status(201).json({ data: order })
})

Consumer (Worker)

Worker - konsumsi pesan
import amqp from "amqplib"
 
const conn = await amqp.connect(process.env.RABBITMQ_URL!)
const channel = await conn.createChannel()
 
await channel.assertQueue("notifications", { durable: true })
channel.prefetch(1)
 
channel.consume("notifications", async (msg) => {
  const { type, orderId, email } = JSON.parse(msg.content.toString())
 
  try {
    await sendEmail(email, type)
    channel.ack(msg)
  } catch (err) {
    // gagal sementara → jangan ack, broker mengirim ulang
    channel.nack(msg, false, true)
  }
})

prefetch(1) membuat worker mengambil satu pesan pada satu waktu — mencegah satu worker menumpuk pesan yang belum selesai. ack/nack adalah mekanisme kepercayaan antara worker dan broker.

Note

durable: true dan persistent: true menjamin pesan tidak hilang saat broker restart. Pola yang wajib untuk notifikasi pembayaran — pesan yang hilang berarti pelanggan tidak diberi tahu ordernya.

Kafka: Event Log Terdistribusi

Kafka berbeda dari RabbitMQ: alih-alih antrean yang habis setelah dikonsumsi, ia adalah log yang bisa dibaca berulang (event streaming). Satu pesan bisa dikonsumsi banyak consumer group — cocok untuk audit trail, analytics, dan event sourcing (episode 22).

Producer Kafka
import { Kafka } from "kafkajs"
 
const kafka = new Kafka({ brokers: ["localhost:9092"] })
const producer = kafka.producer()
 
async function publishEvent(topic: string, payload: object) {
  await producer.connect()
  await producer.send({
    topic,
    messages: [{ value: JSON.stringify(payload) }],
  })
}
 
await publishEvent("order-events", { orderId: 87, action: "created" })

Consumer Kafka melacak posisi bacanya (offset) per consumer group:

Consumer Kafka
const consumer = kafka.consumer({ groupId: "email-workers" })
await consumer.connect()
await consumer.subscribe({ topic: "order-events", fromBeginning: false })
 
await consumer.run({
  eachMessage: async ({ message }) => {
    const event = JSON.parse(message.value.toString())
    if (event.action === "created") await sendEmail(event.orderId)
  },
})

RabbitMQ vs Kafka — keputusan singkat: RabbitMQ untuk task distribution (email, upload, satu pekerjaan sekali); Kafka untuk event streaming (audit, analytics, re-read, banyak consumer).

AWS SQS

Jika aplikasi sudah di AWS, SQS adalah managed queue — tanpa perlu mengoperasikan broker sendiri. Keunggulan: skalabilitas otomatis, tidak perlu maintenance. Pola kerjanya sama: SendMessage → worker ReceiveMessage + DeleteMessage:

text
API → SQS.sendMessage(OrderCreated)
Worker → SQS.receiveMessage → proses → SQS.deleteMessage

Aturan delivery: SQS at-least-once — pesan bisa dikirim lebih dari sekali, jadi worker harus idempoten (proses dua kali = hasil sama).

Reliable Delivery: Pola Wajib

Tiga pola yang membuat pipeline async andal:

1. Idempotency

Worker harus aman dipanggil berkali-kali. Contoh: menyimpan email "sudah dikirim" sebelum benar-benar kirim, atau memakai orderId sebagai kunci unik — sehingga pengiriman ganda menghasilkan satu email.

2. Dead Letter Queue (DLQ)

Pesan yang gagal terus-menerus tidak boleh memblokir queue. Pindahkan ke DLQ setelah beberapa percobaan, lalu dianalisis manual:

Pindahkan ke DLQ setelah gagal
channel.consume("notifications", async (msg) => {
  try {
    await sendEmail(...)
    channel.ack(msg)
  } catch (err) {
    const attempts = (msg.properties.headers?.attempts ?? 0) + 1
    if (attempts >= 3) {
      await channel.sendToQueue("notifications-dlq", msg.content, { persistent: true })
      channel.ack(msg)
    } else {
      channel.nack(msg, false, false)
    }
  }
})

3. Retry dengan Backoff

Retry langsung menyemprot broker. Beri jeda bertingkat (exponential backoff): 1s, 5s, 30s — atau pakai delayed queue.

Warning

Pesan yang di-nack tanpa batas akan berputar selamanya dan memakan CPU. Selalu batasi percobaan dan arahkan kegagalan persisten ke DLQ — kalau tidak, satu pesan rusak bisa melumpuhkan seluruh queue.

Praktik: Notifikasi Order

Terapkan alur lengkap untuk toko online kita:

  1. POST /v1/orders menyimpan order lalu publish order.created.
  2. Worker email-worker mengirim konfirmasi ke user.
  3. Worker inventory-worker mengurangi stok.
  4. Pesan gagal > 3 kali masuk DLQ untuk ditinjau.
Cek queue di RabbitMQ
docker exec rabbit-lab rabbitmqctl list_queues name messages

messages menunjukkan jumlah antrean — bertambah saat order masuk, berkurang saat worker selesai.

Common Pitfalls

Queue tidak mempercepat jika API tetap menunggu hasilnya sinkron. Kalau butuh hasil segera, itu bukan pekerjaan untuk queue — itu pekerjaan untuk pola lain (misal poll status).

Tanpa Idempotency

Pesan yang dikirim ulang (karena timeout ack) diproses dua kali → email ganda, pembayaran ganda. Idempotency adalah prasyarat, bukan bonus.

Queue Tanpa Monitoring

Pesan menumpuk tanpa disadari. Monitoring panjang queue harus jadi alarm pertama (episode 11).

Penutup

Episode 10 melepas pekerjaan berat dari request: konsep producer-queue-consumer, RabbitMQ untuk task distribution, Kafka untuk event log, SQS sebagai managed queue, plus pola reliable delivery — idempotency, DLQ, dan retry backoff.

Inti yang harus dibawa pulang:

  • Pekerjaan berat dan lambat → queue + worker, bukan request sinkron.
  • RabbitMQ untuk satu pekerjaan sekali; Kafka untuk event yang dibaca banyak consumer.
  • durable + persistent + ack adalah dasar pesan tidak hilang.
  • Idempotency wajib karena delivery bisa berulang.
  • Kegagalan persisten → DLQ, jangan biarkan pesan berputar selamanya.

Di episode 11 selanjutnya kita akan membuat sistem terlihat: logging & error handling — structured logging, taksonomi error, dan tracing dasar agar produksi tidak buta. Sampai jumpa di episode 11!