Logo
HOMEABOUTPROJECTSBLOGCONTACT

Luthfi A. Pratama

Enterprise integration specialist building scalable, mission-critical solutions for modern businesses.

LINKS

  • About
  • Projects
  • Blog
  • Contact

CONNECT

GitHubLinkedInEmail

© 2026 Luthfi A. Pratama. All rights reserved.

Designed and built with passion.

Back to all articles
Infrastructure

Message Queue: Kenapa Sistem Besar Ga Boleh Ngobrol Langsung

Catatan pembelajaran tentang Message Queue - dari kenapa synchronous communication itu bahaya di skala besar, konsep pub/sub vs point-to-point, Kafka vs RabbitMQ, sampai pattern yang sering dipake di production.

Luthfi A. Pratama

Luthfi A. Pratama

Software Engineer

2026-03-07
14 min read
Message Queue: Kenapa Sistem Besar Ga Boleh Ngobrol Langsung

Masalah: Synchronous Communication

Bayangin kamu punya e-commerce. User klik "Bayar" dan ini yang terjadi secara synchronous:

sequenceDiagram
    participant U as User
    participant O as Order Service
    participant P as Payment Service
    participant I as Inventory Service
    participant N as Notification Service

    U->>O: Place Order
    O->>P: Process Payment
    P-->>O: Payment OK
    O->>I: Update Inventory
    I-->>O: Inventory Updated
    O->>N: Send Email
    N-->>O: Email Sent
    O-->>U: Order Confirmed ✅

Semua harus selesai berurutan sebelum user dapet response. Masalahnya:

1. Coupling ketat

Order Service harus tahu tentang Payment, Inventory, dan Notification. Kalau nambah service baru (Analytics, Loyalty Points), Order Service harus diubah.

2. Cascading failure

Payment Service down? Semua order gagal. Notification Service lambat? User nunggu. Satu titik lemah bikin seluruh chain jatuh.

3. Bottleneck

User harus nunggu semua proses selesai. Payment 200ms + Inventory 150ms + Notification 300ms = 650ms minimum. Padahal user cuma perlu tahu: "Order berhasil."

4. Ga bisa handle spike

Flash sale tiba-tiba 100x traffic? Semua service harus bisa handle 100x load secara bersamaan. Kalau satu ga kuat, semuanya tumbang.

Solusi: Message Queue

Message Queue (MQ) itu perantara antara service. Alih-alih service A langsung panggil service B, service A taruh pesan di queue, dan service B ambil pesan dari queue kapan dia siap.

graph LR
    Producer["Producer<br/>(Order Service)"] -->|publish| Q["Message Queue"]
    Q -->|consume| C1["Consumer 1<br/>(Payment)"]
    Q -->|consume| C2["Consumer 2<br/>(Inventory)"]
    Q -->|consume| C3["Consumer 3<br/>(Notification)"]

Sekarang Order Service cuma perlu:

  1. Proses order
  2. Taruh pesan di queue: "Order #1234 created"
  3. Return ke user: "Order berhasil!" ✅

Payment, Inventory, Notification proses sendiri-sendiri secara asynchronous. User ga perlu nunggu.

Konsep Dasar

Producer dan Consumer

  • Producer: service yang kirim pesan ke queue
  • Consumer: service yang baca/ambil pesan dari queue
  • Message/Event: data yang dikirim (biasanya JSON)
  • Queue/Topic: "tempat" pesan disimpan sementara

Acknowledgement (Ack)

Setelah consumer proses pesan, dia kirim ack ke queue: "Gue udah selesai proses pesan ini."

Kalau consumer crash sebelum ack, queue tahu pesan belum diproses dan bisa kirim ulang ke consumer lain.

sequenceDiagram
    participant Q as Queue
    participant C as Consumer

    Q->>C: Deliver message
    C->>C: Process message
    C->>Q: ACK ✅
    Note over Q: Hapus message dari queue

    Q->>C: Deliver message 2
    C-xC: Crash! ❌
    Note over Q: No ACK received
    Note over Q: Re-deliver ke consumer lain

Ini yang bikin message queue reliable — pesan ga hilang walaupun consumer mati.

Point-to-Point vs Pub/Sub

Point-to-Point (Queue)

Satu pesan hanya diproses oleh satu consumer.

graph LR
    P[Producer] --> Q[Queue]
    Q --> C1["Consumer 1 ✅"]
    Q -.->|"ga dapet"| C2[Consumer 2]
    Q -.->|"ga dapet"| C3[Consumer 3]

Pesan "Order #1234" masuk queue → Consumer 1 ambil → Consumer 2 dan 3 ga dapet pesan itu.

Use case: task distribution. Misal processing upload foto — kamu punya 10 worker, setiap foto cukup diproses sekali.

Publish/Subscribe (Pub/Sub)

Satu pesan dikirim ke semua subscriber.

graph LR
    P[Publisher] --> T[Topic: order-created]
    T --> S1["Subscriber 1<br/>(Payment) ✅"]
    T --> S2["Subscriber 2<br/>(Inventory) ✅"]
    T --> S3["Subscriber 3<br/>(Notification) ✅"]

Pesan "Order #1234" di-publish ke topic → semua subscriber dapet copy pesan tersebut.

Use case: event broadcasting. Order created → Payment service proses bayar, Inventory service kurangi stok, Notification service kirim email. Semua perlu tahu.

Kapan Pakai yang Mana?

Kebutuhan Point-to-Point Pub/Sub
Satu pesan = satu proses ✅
Satu pesan = banyak proses ✅
Work distribution ✅
Event broadcasting ✅
Load balancing antar consumer ✅

RabbitMQ vs Apache Kafka

Dua message broker paling populer. Tapi mereka fundamentally berbeda.

RabbitMQ — Smart Broker, Simple Consumer

RabbitMQ itu traditional message broker. Dia yang atur semuanya: routing, delivery, ack, retry.

graph LR
    P[Producer] -->|publish| X["Exchange<br/>(routing logic)"]
    X -->|route| Q1[Queue 1]
    X -->|route| Q2[Queue 2]
    Q1 --> C1[Consumer A]
    Q2 --> C2[Consumer B]

Konsep kunci:

  • Exchange: komponen yang terima pesan dari producer dan route ke queue yang tepat
  • Binding: aturan routing dari exchange ke queue
  • Queue: tempat pesan menunggu untuk di-consume

Exchange Types:

Type Routing Logic
Direct Kirim ke queue dengan exact matching routing key
Fanout Broadcast ke semua queue yang terikat
Topic Pattern matching (order.*, #.payment)
Headers Routing berdasarkan header attributes

Karakter RabbitMQ:

  • Pesan dihapus setelah di-consume (secara default)
  • Push model — broker push pesan ke consumer
  • Bagus untuk task queue dan complex routing
  • Implement AMQP protocol (standar industri)
  • Relatif mudah di-setup dan dioperasikan

Apache Kafka — Dumb Broker, Smart Consumer

Kafka itu distributed event streaming platform. Bukan cuma message queue — dia juga bisa jadi event store.

graph LR
    P1[Producer] -->|write| T["Topic: orders<br/>Partition 0 | Partition 1 | Partition 2"]
    T --> CG1["Consumer Group A<br/>(Payment Service)"]
    T --> CG2["Consumer Group B<br/>(Analytics Service)"]

Konsep kunci:

  • Topic: kategori pesan (mirip folder)
  • Partition: topic dipecah jadi beberapa partition untuk parallelism
  • Offset: posisi consumer di dalam partition (kayak bookmark)
  • Consumer Group: sekelompok consumer yang bagi-bagi partition

Karakter Kafka:

  • Pesan ga dihapus setelah di-consume — disimpan sesuai retention period (default 7 hari, bisa forever)
  • Pull model — consumer yang pull pesan dari broker
  • Append-only log — pesan ditulis secara sequential, immutable
  • Sangat bagus untuk event streaming dan replay
  • Throughput sangat tinggi — bisa jutaan message/detik
  • Lebih complex untuk di-setup dan maintain

Perbandingan Head-to-Head

Aspek RabbitMQ Kafka
Model Message Broker Event Streaming Platform
Delivery Push (broker → consumer) Pull (consumer → broker)
Message retention Dihapus setelah ack Disimpan sesuai retention
Ordering Per queue Per partition
Throughput ~10K-50K msg/s ~100K-1M+ msg/s
Routing Complex (exchange types) Simple (topic + partition)
Replay ❌ (pesan udah dihapus) ✅ (bisa replay dari offset)
Use case Task queue, RPC, complex routing Event sourcing, streaming, log aggregation
Complexity Lebih simple Lebih complex
Protocol AMQP Custom (Kafka protocol)

Kapan pakai RabbitMQ?

  • Task queue (background job processing)
  • Complex routing requirements
  • Request-reply pattern (RPC over message)
  • Aplikasi yang butuh message acknowledgment yang reliable
  • Tim yang butuh setup cepat dan simple

Kapan pakai Kafka?

  • Event streaming dan event sourcing
  • Log aggregation (kumpulin log dari banyak service)
  • Butuh replay (bisa baca ulang pesan)
  • High throughput (jutaan message/detik)
  • Data pipeline (ETL, analytics)
  • Audit trail yang immutable

Patterns yang Sering Dipake

1. Work Queue (Competing Consumers)

Banyak consumer bagi-bagi task dari satu queue.

graph LR
    P[Producer] --> Q[Queue]
    Q --> W1["Worker 1"]
    Q --> W2["Worker 2"]
    Q --> W3["Worker 3"]

Setiap pesan cuma diproses satu worker. Kalau satu worker lambat, yang lain tetep jalan. Mau scale? Tambah worker.

Contoh: processing upload gambar, kirim email bulk, generate report.

2. Event-Driven Architecture

Service berkomunikasi lewat event, bukan direct call.

graph TB
    OS["Order Service"] -->|"OrderCreated"| MQ["Message Broker"]
    MQ --> PS["Payment Service"]
    MQ --> IS["Inventory Service"]
    MQ --> NS["Notification Service"]
    MQ --> AS["Analytics Service"]
    
    PS -->|"PaymentCompleted"| MQ
    MQ --> OS
    MQ --> NS

Order Service ga perlu tahu siapa yang dengerin. Dia cuma publish event. Mau nambah service baru? Tinggal subscribe. Zero changes di Order Service.

3. Saga Pattern

Mengelola distributed transaction lewat series of events.

Misalkan: user pesan tiket pesawat + hotel. Dua service berbeda. Kalau booking hotel gagal, booking pesawat harus di-rollback.

sequenceDiagram
    participant O as Orchestrator
    participant F as Flight Service
    participant H as Hotel Service
    participant MQ as Message Queue

    O->>MQ: Book Flight
    MQ->>F: Book Flight
    F-->>MQ: Flight Booked ✅
    MQ-->>O: Flight Booked

    O->>MQ: Book Hotel
    MQ->>H: Book Hotel
    H-->>MQ: Hotel Failed ❌
    MQ-->>O: Hotel Failed

    Note over O: Compensate!
    O->>MQ: Cancel Flight
    MQ->>F: Cancel Flight
    F-->>MQ: Flight Cancelled ✅

Kalau satu step gagal, jalankan compensating actions untuk rollback step sebelumnya.

4. Dead Letter Queue (DLQ)

Pesan yang gagal diproses berkali-kali dipindahin ke queue khusus untuk investigasi.

graph LR
    Q[Main Queue] --> C[Consumer]
    C -->|"gagal 3x"| DLQ[Dead Letter Queue]
    DLQ --> Admin["Admin / Alert System"]

Tanpa DLQ, pesan yang corrupt atau ga bisa diproses bakal di-retry terus-terusan (infinite loop) dan block pesan lainnya. Ini disebut poison message.

DLQ fungsinya:

  • Isolasi pesan bermasalah
  • Memungkinkan investigasi manual
  • Prevent blocking main queue

5. Request-Reply Pattern

Kadang kamu butuh response dari consumer. Misal: "Cek saldo user ini, balikin hasilnya."

graph LR
    P["Service A"] -->|request| RQ["Request Queue"]
    RQ --> C["Service B"]
    C -->|reply| RPQ["Reply Queue"]
    RPQ --> P

Producer kirim pesan + reply-to queue. Consumer proses dan kirim hasilnya ke reply queue. Producer listen di reply queue.

RabbitMQ punya built-in support untuk ini (RPC pattern).

Idempotency: Masalah Duplikat

Message queue bisa deliver pesan yang sama lebih dari sekali (at-least-once delivery). Ini intentional — lebih baik proses dua kali daripada ga proses sama sekali.

Tapi ini bisa bikin masalah:

Pesan: "Kurangi saldo Rp100.000"
Diproses 2x = saldo berkurang Rp200.000 😱

Solusi: Bikin Consumer Idempotent

Consumer harus bisa proses pesan yang sama berkali-kali tanpa efek samping.

Caranya:

  1. Unique message ID — setiap pesan punya ID unik. Consumer cek: "ID ini udah pernah gue proses belum?"
  2. Idempotent operations — gunakan SET balance = 900000 (idempotent) bukan SET balance = balance - 100000 (non-idempotent)
  3. Database constraint — unique constraint di database untuk prevent duplicate processing

Message Ordering

Masalah

Kalau urutan pesan penting (misal: transfer uang → update saldo → kirim notifikasi), gimana caranya pastiin pesan diproses sesuai urutan?

Solusi di Kafka

Kafka guarantee ordering per partition. Pesan dalam satu partition pasti diproses secara berurutan.

Trik: pakai partition key yang sama untuk pesan yang harus urut.

Pesan untuk User A → Partition 0 (selalu)
Pesan untuk User B → Partition 1 (selalu)
Pesan untuk User C → Partition 0 (selalu)

Semua pesan user A selalu masuk partition 0, jadi urutannya terjaga.

Solusi di RabbitMQ

RabbitMQ guarantee ordering per queue selama ada satu consumer per queue. Kalau ada banyak consumer di satu queue, ordering ga guaranteed.

Monitoring & Operasional

Metrics yang Harus Di-monitor

Metric Artinya Bahaya Kalau
Queue depth Jumlah pesan menunggu Terus naik = consumer ga kuat
Consumer lag Seberapa jauh consumer dari pesan terbaru Lag besar = processing terlalu lambat
Message rate Throughput (msg/detik) Drop tiba-tiba = ada masalah
Error rate Pesan yang gagal diproses Naik = bug atau dependency issue
DLQ count Pesan di dead letter queue Ada = perlu investigasi

Consumer Lag

Ini metric terpenting di Kafka. Kalau consumer lag terus naik, artinya consumer ga bisa keepup dengan rate producer.

Producer:  Offset 1000 (pesan terbaru)
Consumer:  Offset 850
Lag:       150 messages

Solusi: tambah consumer (scale out), tambah partition, atau optimize processing di consumer.

Ringkasan

Konsep Penjelasan
Message Queue Perantara async antara service
Producer Service yang kirim pesan
Consumer Service yang proses pesan
Point-to-Point Satu pesan → satu consumer
Pub/Sub Satu pesan → semua subscriber
RabbitMQ Smart broker, push model, complex routing
Kafka Event streaming, pull model, high throughput
DLQ Queue untuk pesan yang gagal berkali-kali
Idempotency Consumer harus toleran terhadap pesan duplikat
Saga Distributed transaction via compensating events

Kesimpulan

Message Queue itu salah satu building block paling penting di arsitektur distributed. Tanpa ini, system coupling jadi ketat dan satu failure bisa cascade ke mana-mana.

Yang perlu diingat:

  1. Async by default — kalau ga perlu sync, jangan sync
  2. Pub/Sub untuk event broadcasting, Point-to-Point untuk task distribution
  3. RabbitMQ untuk simplicity dan complex routing, Kafka untuk throughput dan event streaming
  4. Idempotency itu wajib — consumer harus bisa handle pesan duplikat
  5. Dead Letter Queue itu penting — jangan biarkan poison message block queue
  6. Monitor consumer lag — ini early warning sebelum semuanya terlambat
  7. Ordering itu tricky — pakai partition key di Kafka, single consumer di RabbitMQ

Message queue bukan silver bullet. Dia nambah complexity (eventual consistency, debugging lebih susah, infra tambahan). Tapi di scale yang besar, dia bukan lagi pilihan — dia kebutuhan.

Topics

InfrastructureSoftware Engineering

Related Articles

OAuth 2.0 & OpenID Connect: Gimana "Login dengan Google" Itu Bekerja

Catatan pembelajaran tentang OAuth 2.0 dan OpenID Connect - dari kenapa kita butuh authorization framework, Authorization Code Flow step-by-step, PKCE, ID token vs access token, sampai common mistakes di production.

Caching: Kenapa Sistem Cepat Itu Sistem yang Males Ngitung Ulang

Catatan pembelajaran tentang caching - dari kenapa cache itu penting, strategi caching, Redis vs Memcached, cache invalidation, CDN, sampai pitfalls yang sering kejadian di production.

Clustering: Gimana Banyak Server Kerja Bareng Jadi Satu Sistem

Catatan pembelajaran tentang konsep clustering - dari kenapa satu server ga cukup, gimana cluster milih leader, sampai consensus algorithm dan real-world implementations.