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:
- Proses order
- Taruh pesan di queue: "Order #1234 created"
- 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:
- Unique message ID — setiap pesan punya ID unik. Consumer cek: "ID ini udah pernah gue proses belum?"
- Idempotent operations — gunakan
SET balance = 900000(idempotent) bukanSET balance = balance - 100000(non-idempotent) - 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:
- Async by default — kalau ga perlu sync, jangan sync
- Pub/Sub untuk event broadcasting, Point-to-Point untuk task distribution
- RabbitMQ untuk simplicity dan complex routing, Kafka untuk throughput dan event streaming
- Idempotency itu wajib — consumer harus bisa handle pesan duplikat
- Dead Letter Queue itu penting — jangan biarkan poison message block queue
- Monitor consumer lag — ini early warning sebelum semuanya terlambat
- 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.
