Topic Exchange #
Dalam arsitektur sistem berbasis peristiwa (event-driven architecture atau EDA) berskala enterprise, tingkat kompleksitas perutean pesan sering kali tidak dapat diselesaikan hanya dengan pencocokan literal yang kaku menggunakan Direct Exchange atau penyiaran massal tanpa filter menggunakan Fanout Exchange. Kita membutuhkan mekanisme perutean yang cerdas, fleksibel, dan dinamis, namun tetap berkinerja tinggi, yang mampu mengarahkan pesan berdasarkan klasifikasi domain bisnis secara granular.
Di ekosistem RabbitMQ, kebutuhan ini dipenuhi oleh Topic Exchange (Pertukaran Topik). Topic Exchange bertindak sebagai mesin filter dinamis yang mengevaluasi ekspresi wildcard pada Binding Key antrean terhadap Routing Key pesan. Melalui Topic Exchange, konsumen dapat menentukan kriteria penerimaan pesan dengan sangat spesifik tanpa memaksa produsen mengetahui struktur antrean konsumen. Artikel ini akan membedah secara mendalam mekanisme pencocokan pola wildcard Topic Exchange, arsitektur pencarian internal berbasis struktur data Trie di Erlang, konvensi penamaan terstruktur untuk performa optimal, serta mitigasi risiko performa di lingkungan produksi.
Mekanisme Perutean Pola (Wildcard Matching) #
Topic Exchange merutekan pesan berdasarkan pencocokan pola (pattern matching) antara Routing Key yang disematkan oleh produsen pada pesan dan pola Binding Key yang didaftarkan oleh konsumen untuk antreannya. Berbeda dengan Direct Exchange yang mengharuskan pencocokan string secara persis kata demi kata (exact match), Topic Exchange mengizinkan perutean berbasis sub-kategori menggunakan pemisah titik dan karakter khusus wildcard.
Sintaksis penamaan Routing Key dan pencocokan pola pada Topic Exchange mengikuti aturan berikut:
- Segmen Kata (Tokens): Routing Key harus berupa string berisi kata-kata atau token yang dipisahkan oleh tanda titik (
.), misalnyaasia.indonesia.jakarta. Panjang maksimum string Routing Key adalah 255 karakter (byte). Setiap kata atau segmen biasanya mewakili tingkatan hierarki dalam domain bisnis kita. - Wildcard Asteris (
*): Berperan sebagai pengganti tepat satu kata di antara pembatas titik. Jika kita meletakkan*, segmen tersebut wajib diisi oleh tepat satu kata pada Routing Key pesan agar cocok. - Wildcard Hash (
#): Berperan sebagai pengganti nol atau lebih kata berturut-turut. Wildcard ini sangat fleksibel karena dapat mencocokkan segmen kosong maupun banyak segmen sekaligus hingga akhir dari Routing Key.
flowchart TD
Msg["Pesan Baru (Routing Key: 'eu.sales.order.completed')"] --> TopicEx["Topic Exchange (events.topic)"]
TopicEx -->|"Trie Traversal"| Match{"Cari Pola"}
Match -->|"Binding Key: '*.sales.order.*'"| Queue1["Queue A (billing-eu)"]
Match -->|"Binding Key: 'eu.#'"| Queue2["Queue B (eu-reporting)"]
Match -->|"Binding Key: '#.failed'"| Queue3["Queue C (dead-letter)"]
Queue1 --> ConsA["Layanan Billing EU"]
Queue2 --> ConsB["Layanan Laporan Regional"]
Queue3 --> ConsC["Layanan DLQ Global"]Evaluasi Pencocokan Pola #
Pada diagram di atas, kita dapat melacak bagaimana pesan dengan Routing Key "eu.sales.order.completed" didistribusikan ke berbagai antrean:
- Queue A terikat dengan Binding Key
*.sales.order.*. Pola ini mencocokkan pesan karena segmen pertama (eu) dicocokkan oleh wildcard*pertama, segmen kedua dan ketiga (sales.order) cocok secara literal, dan segmen keempat (completed) dicocokkan oleh wildcard*kedua. - Queue B terikat dengan Binding Key
eu.#. Pola ini mencocokkan pesan karena pesan diawali dengan segmeneu, dan sisa segmen lainnya (sales.order.completed) dicocokkan sekaligus oleh wildcard#. - Queue C terikat dengan Binding Key
#.failed. Pola ini tidak mencocokkan pesan karena pesan diakhiri dengan segmencompleted, bukanfailed. Pesan diabaikan oleh antrean ini.
Tabel Perbandingan Pencocokan Routing Key #
Untuk memberikan gambaran yang lebih komprehensif, mari kita telaah tabel evaluasi pencocokan pola berikut:
| Binding Key | Routing Key Pesan | Status | Alasan |
|---|---|---|---|
quick.orange.rabbit | quick.orange.rabbit | Cocok | Pencocokan literal secara persis (seperti Direct Exchange). |
lazy.# | lazy.pink.rabbit | Cocok | Menggantikan dua kata (pink.rabbit). |
lazy.# | lazy.brown | Cocok | Menggantikan satu kata (brown). |
lazy.# | lazy | Cocok | Menggantikan nol kata (tidak ada kata setelah lazy). |
*.orange.* | quick.orange.rabbit | Cocok | Segmen pertama dan ketiga digantikan tepat oleh satu kata. |
*.orange.* | quick.orange.male.rabbit | Tidak Cocok | Gagal karena ada empat segmen, sedangkan pola hanya meminta tiga segmen. |
*.*.rabbit | lazy.orange.rabbit | Cocok | Segmen pertama dan kedua berupa kata apa saja, diakhiri rabbit. |
#.rabbit | europe.central.germany.rabbit | Cocok | Menggantikan banyak kata di depan (europe.central.germany). |
#.rabbit | rabbit | Cocok | Menggantikan nol kata di depan. |
Edge Cases dalam Pencocokan Pola #
Saat merancang sistem produksi, kita harus memperhatikan beberapa kasus batas (edge cases) yang dapat memengaruhi perilaku perutean:
- String Kosong (
""): Jika produsen mengirim pesan dengan Routing Key kosong, pesan hanya akan cocok dengan binding yang menggunakan wildcard#saja. Pola*tidak akan cocok karena ia memerlukan minimal satu kata. - Double Dots (
a..b): Jika Routing Key dikirim dengan formata..b, Erlang akan memperlakukannya sebagai tiga segmen:["a", "", "b"]. Segmen tengah adalah string kosong. Pola sepertia.*.bakan cocok dengana..bkarena segmen tengah terhitung sebagai satu kata (meskipun kosong). Namun, ini adalah anti-pattern penamaan yang harus kita hindari. - Sensitivitas Karakter (Case Sensitivity): Topic Exchange bersifat sensitif terhadap huruf besar dan kecil (case-sensitive). Routing Key
sales.Ordertidak akan cocok dengan Binding Keysales.order. Pastikan aplikasi kita selalu menormalisasi Routing Key menjadi huruf kecil sebelum mempublikasikannya.
Di Balik Layar: Arsitektur Trie-Matching di Erlang #
Bagi sebagian besar arsitek sistem, terdapat kekhawatiran bahwa pencocokan pola dengan wildcard akan menurunkan performa perutean pesan broker secara signifikan. Untuk mengatasi penurunan performa akibat pencarian string linier, RabbitMQ tidak menggunakan mesin ekspresi reguler (regex engine) standar yang lambat dan memakan banyak CPU. Modul internal rabbit_exchange_type_topic di dalam runtime Erlang mengimplementasikan struktur data Trie (Prefix Tree) yang disimpan secara dinamis di memori RAM.
Bagaimana Trie Bekerja di RabbitMQ? #
Setiap kali antrean diikat (bound) ke Topic Exchange menggunakan Binding Key terstruktur, RabbitMQ memecah string kunci tersebut berdasarkan tanda titik dan menyusunnya ke dalam simpul-simpul pohon Trie di memori. Simpul-simpul ini menampung informasi segmen kata serta referensi ke antrean tujuan yang cocok.
Sebagai contoh, jika kita memiliki tiga binding key yang terdaftar di broker:
sales.order.*sales.#inventory.stock.updated
Erlang akan menyusun struktur pohon Trie berikut di memori RAM:
flowchart TD
Root["Root"] --> Sales["sales"]
Root --> Inventory["inventory"]
Sales --> Order["order"]
Sales --> Hash["#"]
Inventory --> Stock["stock"]
Order --> Wildcard["*"]
Stock --> Updated["updated"]Ketika pesan dengan Routing Key "sales.order.created" masuk ke broker, alur evaluasi yang dilakukan Erlang adalah:
- Tokenisasi: Proses Erlang memecah string Routing Key menjadi daftar token:
["sales", "order", "created"]. - Pencarian Trie (Trie Traversal): Broker menelusuri simpul Trie dari akar (Root). Ia langsung melompat ke cabang
"sales", lalu masuk ke cabang"order", dan terakhir mencocokkan kata"created"dengan simpul wildcard*. - Efisiensi Waktu: Kompleksitas pencarian ini bersifat $O(L)$, di mana $L$ adalah jumlah segmen kata dalam Routing Key pesan (biasanya hanya 3 hingga 5 tingkat), bukan $O(N)$ di mana $N$ adalah jumlah total binding yang terdaftar. Hal ini membuat waktu pencarian tetap konstan dan sangat cepat meskipun kita memiliki ribuan antrean yang terikat ke exchange tersebut.
Penyimpanan Mnesia vs Tabel ETS #
Di dalam klaster RabbitMQ, metadata binding Topic Exchange disimpan dalam tabel database terdistribusi bernama Mnesia. Namun, untuk keperluan pencarian cepat saat runtime ketika ribuan pesan mengalir per detik, RabbitMQ memproyeksikan struktur Trie tersebut ke dalam tabel ETS (Erlang Term Storage) yang teroptimasi untuk operasi baca paralel berkecepatan tinggi.
Setiap kali node RabbitMQ menerima pesan, ia tidak melakukan query database Mnesia, melainkan langsung membaca struktur Trie yang sudah terkompilasi di ETS RAM. Hal ini meminimalkan latensi pencarian hingga di bawah satu milidetik.
Fenomena Metadata Churning (Dynamic Recompilation) #
Meskipun pencarian Trie sangat cepat ($O(L)$), proses modifikasi struktur Trie memiliki biaya komputasi yang tinggi. Setiap kali ada penambahan antrean baru yang melakukan binding, atau antrean lama yang melakukan unbinding secara dinamis, RabbitMQ harus membangun ulang (rebuild) sebagian atau seluruh struktur pohon Trie di memori RAM dan mereplikasikannya ke seluruh node dalam klaster.
Jika aplikasi kita dirancang untuk melakukan metadata churning (misalnya, membuat antrean temporer untuk setiap koneksi pengguna baru, lalu melakukan bind/unbind ratusan kali per detik), CPU broker akan habis terkonsumsi hanya untuk melakukan kompilasi ulang Trie. Selama proses kompilasi ulang ini, thread perutean pesan dapat mengalami blocking state, menyebabkan antrean pesan tertahan dan latensi pengiriman melonjak drastis.
Konvensi Penamaan Terstruktur & Strategi Versioning #
Routing Key pada Topic Exchange bertindak sebagai kontrak API logis antara produsen dan konsumen. Perubahan format Routing Key secara sembarangan di sisi produsen akan memutus aliran data ke antrean konsumen secara senyap tanpa memicu error di sisi produsen. Oleh karena itu, kita harus menerapkan konvensi penamaan yang ketat dan matang sejak awal pengembangan sistem.
1. Pola Domain-Entity-Action #
Pola penamaan yang direkomendasikan untuk sistem event-driven produksi adalah menggunakan skema hierarki berikut:
<bounded_context>.<entity_name>.<event_action>
bounded_context(atau nama layanan): Domain bisnis utama yang menghasilkan peristiwa tersebut, ditulis dalam huruf kecil. Contoh:sales,billing,shipping,auth.entity_name: Objek atau entitas bisnis utama yang mengalami perubahan status. Contoh:order,invoice,shipment,user.event_action: Kejadian spesifik yang telah selesai terjadi, biasanya menggunakan kata kerja bentuk lampau (past-tense). Contoh:created,completed,failed,cancelled,paid.
Contoh implementasi nyata:
sales.order.created(Terjadi pembuatan pesanan baru)billing.invoice.paid(Pembayaran faktur telah diverifikasi)shipping.shipment.dispatched(Barang telah dikirim oleh kurir)
Dengan pola terstruktur ini, konsumen dapat berlangganan secara fleksibel:
- Layanan notifikasi ingin mendengar semua event order:
sales.order.* - Layanan audit global ingin mendengar semua aktivitas billing:
billing.# - Layanan analitik ingin memantau semua event sukses di seluruh domain:
*.*.completedatau*.*.paid
2. Strategi Versioning Event #
Ketika sistem berkembang, struktur payload data peristiwa sering kali harus diubah (misalnya penambahan field wajib baru). Hal ini memicu breaking change bagi konsumen lama yang belum siap memperbarui kodenya. Untuk mengatasi ini, kita harus merancang strategi versioning pada Routing Key.
Strategi A: Menyisipkan Versi pada Routing Key (Explicit Versioning) #
Kita dapat menyisipkan nomor versi skema di bagian akhir Routing Key:
sales.order.created.v1sales.order.created.v2
Konsumen lama tetap mengikat antrean mereka ke .v1, sedangkan konsumen baru yang mampu mengolah skema data baru dapat mengikat antrean mereka ke .v2. Produsen akan mempublikasikan peristiwa ke kedua rute tersebut selama masa transisi migrasi (dual publishing), atau hanya mengirim ke versi baru jika konsumen lama sudah sepenuhnya dimigrasi.
Strategi B: Memanfaatkan Wildcard untuk Transisi Paralel #
Jika kita ingin meminimalkan perubahan rute di sisi produsen, produsen dapat terus menggunakan Routing Key yang sama, namun konsumen mengikat antrean dengan wildcard untuk menangkap versi yang berbeda. Konsumen membedakan versi payload dengan membaca properti metadata headers (misalnya x-schema-version yang disematkan oleh produsen). Topic Exchange sangat mempermudah transisi paralel ini karena konsumen dapat menulis binding key seperti sales.order.created.# untuk menerima semua variasi rute di bawah topik tersebut.
Implementasi Kode: Integrasi Topic Exchange dalam Go #
Mari kita tinjau contoh implementasi lengkap menggunakan bahasa Go dan pustaka github.com/rabbitmq/amqp091-go. Contoh di bawah ini menunjukkan bagaimana mendefinisikan Topic Exchange, membuat antrean, mengikat antrean dengan pola wildcard, dan mengirim pesan dengan Routing Key terstruktur.
package main
import (
"context"
"log"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
// Helper untuk menangani error secara konsisten
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
// 1. Membuka koneksi ke broker RabbitMQ
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Gagal terhubung ke RabbitMQ")
defer conn.Close()
// 2. Membuka channel komunikasi
ch, err := conn.Channel()
failOnError(err, "Gagal membuka channel")
defer ch.Close()
// 3. Mendeklarasikan Topic Exchange
exchangeName := "sales.events.topic"
err = ch.ExchangeDeclare(
exchangeName, // Nama exchange
"topic", // Tipe exchange wajib 'topic'
true, // Durable: bertahan jika broker restart
false, // Auto-deleted: jangan hapus jika tidak digunakan
false, // Internal: bisa diakses langsung oleh produsen
false, // No-wait: tunggu konfirmasi dari broker
nil, // Arguments tambahan
)
failOnError(err, "Gagal mendeklarasikan Topic Exchange")
// 4. Mendeklarasikan Antrean untuk Layanan Notifikasi (Semua Event Order)
notificationQueue, err := ch.QueueDeclare(
"notifications-service-queue", // Nama antrean
true, // Durable
false, // Auto-delete
false, // Exclusive
false, // No-wait
nil,
)
failOnError(err, "Gagal mendeklarasikan antrean notifikasi")
// 5. Mengikat Antrean Notifikasi dengan Binding Key Pola Wildcard
// Kita ingin menangkap semua aksi yang berkaitan dengan entitas 'order'
notificationBindingKey := "sales.order.*"
err = ch.QueueBind(
notificationQueue.Name, // Nama antrean tujuan
notificationBindingKey, // Pola binding key
exchangeName, // Nama exchange asal
false,
nil,
)
failOnError(err, "Gagal mengikat antrean notifikasi")
log.Printf("✓ Antrean %s terikat ke %s dengan pola: %s", notificationQueue.Name, exchangeName, notificationBindingKey)
// 6. Mendeklarasikan Antrean untuk Layanan Analitik Global (Semua Event Sales)
analyticsQueue, err := ch.QueueDeclare(
"analytics-service-queue", // Nama antrean
true, // Durable
false, // Auto-delete
false, // Exclusive
false, // No-wait
nil,
)
failOnError(err, "Gagal mendeklarasikan antrean analitik")
// 7. Mengikat Antrean Analitik dengan Binding Key '#' (Semua Event di bawah sales)
analyticsBindingKey := "sales.#"
err = ch.QueueBind(
analyticsQueue.Name,
analyticsBindingKey,
exchangeName,
false,
nil,
)
failOnError(err, "Gagal mengikat antrean analitik")
log.Printf("✓ Antrean %s terikat ke %s dengan pola: %s", analyticsQueue.Name, exchangeName, analyticsBindingKey)
// 8. Produsen Mempublikasikan Pesan 1: sales.order.created
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
payload1 := []byte(`{"order_id":"ORD-98754","total":150000,"status":"created"}`)
routingKey1 := "sales.order.created"
err = ch.PublishWithContext(ctx,
exchangeName,
routingKey1,
false,
false,
amqp.Publishing{
ContentType: "application/json",
DeliveryMode: amqp.Persistent, // Pesan aman ditulis ke disk
MessageId: "ORD-98754", // ID unik diletakkan di metadata MessageId
Timestamp: time.Now(),
Body: payload1,
},
)
failOnError(err, "Gagal mempublikasikan pesan 1")
log.Printf("✓ Pesan 1 terkirim dengan Routing Key: %s", routingKey1)
// 9. Produsen Mempublikasikan Pesan 2: sales.payment.processed
payload2 := []byte(`{"order_id":"ORD-98754","payment_method":"go-pay","status":"processed"}`)
routingKey2 := "sales.payment.processed"
err = ch.PublishWithContext(ctx,
exchangeName,
routingKey2,
false,
false,
amqp.Publishing{
ContentType: "application/json",
DeliveryMode: amqp.Persistent,
MessageId: "PAY-11223",
Timestamp: time.Now(),
Body: payload2,
},
)
failOnError(err, "Gagal mempublikasikan pesan 2")
log.Printf("✓ Pesan 2 terkirim dengan Routing Key: %s", routingKey2)
}
Analisis Hasil Eksekusi Kode: #
- Pesan 1 (
sales.order.created) memiliki tiga segmen. Pesan ini akan dikirim kenotifications-service-queue(karena cocok dengansales.order.*) dan juga keanalytics-service-queue(karena cocok dengansales.#). - Pesan 2 (
sales.payment.processed) memiliki tiga segmen. Pesan ini hanya dikirim keanalytics-service-queue(karena cocok dengansales.#yang menangkap seluruh rute berawalansales). Pesan tidak masuk kenotifications-service-queuekarena segmen kedua adalahpayment, bukanorder.
Anti-Pattern yang Harus Dihindari #
Topic Exchange menawarkan fleksibilitas yang sangat tinggi. Namun, jika tidak digunakan secara hati-hati, ia dapat menjadi bumerang yang menurunkan performa dan merusak stabilitas sistem.
1. Menyisipkan ID Dinamis (UUID/ID Transaksi) pada Routing Key #
Ini adalah kesalahan desain paling umum yang dilakukan oleh developer yang baru menggunakan RabbitMQ. Mereka memasukkan ID dinamis unik ke dalam string Routing Key agar terlihat spesifik.
// ANTI-PATTERN: Menyertakan ID unik dinamis pada routing key Topic Exchange
func PublishOrderEventBad(ch *amqp.Channel, orderID string, payload []byte) {
// ✗ JANGAN: Memasukkan UUID/ID dinamis ke routing key.
// Ini membuat struktur Trie membengkak tanpa batas karena setiap transaksi
// menciptakan cabang pohon baru di RAM, merusak efisiensi memori Mnesia dan ETS.
routingKey := "sales.order.created." + orderID
_ = ch.Publish(
"sales.events.topic",
routingKey,
false,
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "application/json",
Body: payload,
},
)
}
Mengapa ini berbahaya? #
- Memory Leak pada Trie: Setiap kali pesan dikirim dengan Routing Key baru yang unik (misalnya berisi UUID baru), RabbitMQ harus mengevaluasi rute tersebut terhadap Trie. Jika ada antrean yang terikat menggunakan wildcard seperti
sales.order.created.#, broker harus mendaftarkan cabang rute baru di memori RAM. Seiring waktu, memori RAM yang digunakan oleh ETS dan database Mnesia untuk menyimpan struktur Trie akan terus membengkak hingga menyebabkan Out of Memory (OOM) crash pada broker. - Penurunan Performa Lookup: Dengan bertambahnya simpul baru pada Trie secara terus-menerus, kedalaman dan lebar pohon Trie akan menjadi sangat besar. Hal ini meningkatkan waktu traversal pencarian rute, menurunkan throughput pengiriman pesan secara keseluruhan.
Solusi Arsitektural: #
Jauhkan seluruh ID dinamis dari Routing Key. Routing Key harus murni berisi string klasifikasi taksonomi statis yang berhingga. ID transaksi atau UUID harus selalu dimasukkan ke dalam payload biner pesan atau diletakkan di properti metadata standar message_id atau header custom.
// BENAR: Menggunakan routing key klasifikasi statis untuk Topic Exchange
func PublishOrderEventGood(ch *amqp.Channel, orderID string, payload []byte) {
// ✓ SOLUSI: Gunakan routing key terstruktur yang statis dan berhingga
routingKey := "sales.order.created"
_ = ch.Publish(
"sales.events.topic",
routingKey,
false,
false,
amqp.Publishing{
MessageId: orderID, // Masukkan ID unik transaksi di MessageID!
DeliveryMode: amqp.Persistent,
ContentType: "application/json",
Body: payload,
},
)
}
2. Over-Wildcarding (Penyalahgunaan Karakter #)
#
Beberapa tim developer menggunakan Topic Exchange namun mendaftarkan binding key dengan pola # (atau #.) pada hampir semua antrean mereka dengan alasan kepraktisan agar tidak perlu mengelola binding key secara ketat.
Mengapa ini berbahaya? #
Jika semua antrean terikat menggunakan wildcard #, Topic Exchange akan berperilaku persis seperti Fanout Exchange karena ia akan mengirimkan setiap pesan ke seluruh antrean. Namun, performa CPU yang dikonsumsi jauh lebih besar karena broker harus melakukan kompilasi Trie, melakukan tokenisasi string Routing Key, dan menelusuri pohon Trie di memori RAM untuk setiap pesan. Ini membuang-buang siklus CPU broker secara sia-sia.
Solusi Arsitektural: #
Jika sebuah aliran data memang dirancang untuk disebarkan ke seluruh antrean tanpa memerlukan penyaringan sama sekali, gunakan Fanout Exchange secara eksplisit. Fanout Exchange melompati seluruh proses pencocokan kunci sehingga memiliki performa throughput yang jauh lebih tinggi dan hemat CPU.
Perbandingan Karakteristik Performa #
Untuk membantu kita menentukan kapan harus menggunakan Topic Exchange dibandingkan dengan jenis exchange lainnya, mari kita bandingkan karakteristik performa internalnya:
| Karakteristik | Direct Exchange | Fanout Exchange | Topic Exchange | Headers Exchange |
|---|---|---|---|---|
| Kompleksitas Pencarian | $O(1)$ (ETS Hash Lookup) | $O(1)$ (Direct Route) | $O(L)$ (Trie Traversal) | $O(K)$ (Header Attribute Scan) |
| Konsumsi CPU Broker | Sangat Rendah | Sangat Rendah | Sedang hingga Tinggi | Sangat Tinggi |
| Konsumsi Memori RAM | Sangat Rendah | Minimal | Sedang | Tinggi |
| Fleksibilitas Rute | Rendah (Hanya Literal) | Sangat Rendah (Broadcast) | Sangat Tinggi (Wildcard) | Maksimal (Multi-Atribut) |
| Kecepatan Routing | Sangat Cepat | Paling Cepat | Cepat (jika Trie stabil) | Lambat |
| Dynamic Binding Cost | Sangat Murah | Sangat Murah | Sangat Mahal (Trie Rebuild) | Sedang |
Dari tabel perbandingan di atas, kita dapat menyimpulkan bahwa Topic Exchange adalah pilihan terbaik ketika kita memerlukan fleksibilitas perutean berbasis hierarki tanpa mengorbankan performa terlalu besar, selama kita mampu mengontrol jumlah binding dinamis dan menghindari metadata churning.
Ringkasan #
- Perutean Berbasis Pola Wildcard — Topic Exchange mencocokkan Routing Key pesan terhadap pola Binding Key antrean menggunakan wildcard asteris (
*) untuk tepat satu kata dan hash (#) untuk nol atau lebih kata.- Evaluasi Trie Tree RAM — Aturan binding disimpan dalam memori RAM Mnesia dan diproyeksikan ke tabel ETS sebagai struktur data Trie. Lookup rute bersifat efisien dengan kompleksitas $O(L)$ sesuai panjang segmen kata.
- Hindari ID Dinamis pada Routing Key — Jauhkan UUID, ID user, atau ID transaksi dari Routing Key untuk mencegah kebocoran memori RAM akibat membengkaknya simpul pohon Trie di broker. Gunakan
message_idsebagai gantinya.- Waspadai Metadata Churning — Hindari melakukan operasi bind dan unbind secara dinamis berkecepatan tinggi saat runtime untuk mencegah broker sibuk menyusun ulang (rebuild) struktur data Trie di CPU.
- Gunakan Konvensi Bounded Context — Terapkan pola
<domain>.<entity>.<action>dalam huruf kecil untuk menjamin keterbacaan, skalabilitas, dan audit kontrak API rute yang bersih.- Gunakan Fanout Jika Tanpa Filter — Jika pesan selalu didistribusikan ke seluruh antrean tanpa penyaringan rute, gunakan Fanout Exchange untuk menghemat penggunaan CPU broker.