Lazy vs Classic Queue #
Dalam sistem distribusi pesan berskala besar, salah satu tantangan terbesar bagi administrator sistem dan arsitek perangkat lunak adalah mengelola akumulasi pesan yang menumpuk (message backlog). Backlog dapat terjadi kapan saja ketika aplikasi konsumen (consumer) mengalami kerusakan, mati selama pemeliharaan, atau kalah cepat dalam memproses lonjakan lalu lintas data (traffic spike) dibandingkan dengan produsen (producer).
Di dalam RabbitMQ, bagaimana broker mengelola memori RAM dan disk di bawah tekanan backlog ditentukan oleh tipe dan konfigurasi penyimpanan antrean. Secara historis, RabbitMQ menyediakan dua mode perilaku penyimpanan untuk Classic Queue: Default (Memory-First) dan Lazy (Disk-First). Memahami perbedaan antara kedua mode ini, bagaimana runtime Erlang BEAM VM mengalokasikan memori heap, serta bagaimana evolusi arsitektur RabbitMQ versi modern (4.0+) merombak mesin penyimpanan ini, sangat penting untuk menjaga broker tetap stabil dan terhindar dari pemadaman total akibat kehabisan memori.
Model Penyimpanan Classic (Memory-First) #
Classic Queue tradisional (dijalankan dalam mode default) menggunakan strategi penyimpanan berbasis prioritas memori RAM (memory-first behavior). Tujuan utama dari desain ini adalah memaksimalkan throughput dan meminimalkan latensi pengiriman pesan dengan cara menghindari operasi I/O disk sebanyak mungkin.
Alur Kerja Ingress Pesan #
Ketika produsen mempublikasikan pesan ke Classic Queue default:
- Penyimpanan RAM: Pesan langsung diterima dan disimpan di dalam memori RAM utama broker, tepatnya di dalam memori heap proses Erlang antrean tersebut.
- Pengiriman Cepat: Jika ada consumer aktif yang siap menerima, pesan langsung dikirim dari RAM ke consumer tanpa pernah menyentuh disk fisik. Ini memberikan latensi sub-milidetik.
- Evaluasi Watermark: Jika pesan tidak langsung dikonsumsi dan mulai menumpuk, RabbitMQ akan terus menahan pesan di RAM hingga penggunaan memori broker mendekati ambang batas peringatan memori (Memory High Watermark).
Mekanisme Paging RAM-ke-Disk #
Saat penggunaan memori broker melampaui watermark (secara default dikonfigurasi sebesar 40% dari total RAM fisik server), RabbitMQ akan masuk ke dalam kondisi darurat dan mengaktifkan mekanisme Paging.
flowchart LR
Produsen["Produsen"] -->|"Publish"| RAM["RAM Heap Proses Erlang"]
RAM -->|"Watermark Terlewati"| Paging["Paging Batch"]
Paging --> Disk["Disk"]
Disk -. "Flow Control Blocked" .-> ProdusenProses paging ini berjalan dengan alur:
- Batch Serialization: Broker menghentikan sementara penerimaan pesan baru dan mulai mengumpulkan pesan-pesan tertua di memori RAM, melakukan serialisasi biner, dan menulisnya dalam bentuk batch ke disk (file page).
- Garbage Collection: Setelah pesan tertulis di disk, referensi data pesan di RAM dihapus, memicu proses Garbage Collector (GC) Erlang untuk membebaskan ruang memori heap.
- Flow Control Throttling: Selama proses paging berlangsung, broker akan mengaktifkan Flow Control pada channel produsen. Produsen diblokir sejenak dari mengirimkan pesan baru (
blocking state) untuk memberi waktu bagi broker menurunkan penggunaan RAM. Proses ini memicu lonjakan latensi (latency spikes) yang sangat mengganggu performa aplikasi produsen.
Logika Internal Lazy Queue (Disk-First) #
Untuk mengatasi ketidakstabilan broker akibat proses paging darurat di atas, RabbitMQ memperkenalkan konfigurasi Lazy Queue (dikenal juga sebagai mode "lazy"). Lazy Queue mengambil pendekatan sebaliknya, yaitu prioritas disk fisik (disk-first behavior).
Alur Kerja Penyimpanan Lazy #
Ketika antrean dideklarasikan sebagai lazy (x-queue-mode: lazy):
- Direct-to-Disk Writing: Begitu pesan diterima dari exchange, pesan tersebut langsung ditulis secara berurutan ke berkas penyimpanan di disk fisik.
- RAM Minimal: Broker tidak menahan payload isi pesan di dalam memori RAM heap proses Erlang. Memori RAM hanya digunakan untuk menyimpan indeks penunjuk pesan (message index pointers) yang sangat kecil untuk melacak urutan pesan.
- On-Demand Loading: Ketika consumer meminta pesan, RabbitMQ akan membaca pesan tersebut dari disk ke RAM secara on-demand (hanya saat dibutuhkan), mengirimkannya ke consumer, dan langsung membebaskan RAM-nya kembali.
flowchart TD
subgraph ClassicQueue["Classic Queue (Memory-First)"]
direction TB
C_In["Publish Pesan"] --> C_RAM{"RAM Cukup?"}
C_RAM -->|Ya| C_Mem["Simpan di RAM Heap (Cepat)"]
C_RAM -->|Tidak| C_Page["Paging Batch ke Disk (Overhead CPU & Flow Control)"]
end
subgraph LazyQueue["Lazy Queue (Disk-First)"]
direction TB
L_In["Publish Pesan"] --> L_Disk["Tulis Langsung ke Disk"]
L_Disk --> L_RAM["Simpan Indeks Kecil di RAM"]
L_RAM --> L_Cons["Load ke RAM Hanya Saat Dikonsumsi"]
endEliminasi Overhead Garbage Collection (GC) Erlang #
Dalam runtime Erlang BEAM, setiap antrean berjalan sebagai proses ringan yang terisolasi dengan memori heap pribadinya sendiri. Ketika sebuah antrean Classic default menampung 5 juta pesan, ukuran heap proses tersebut akan membengkak hingga ukuran gigabyte.
- Masalah GC: Setiap kali Garbage Collector Erlang berjalan untuk membersihkan memori proses tersebut, ia harus memindai seluruh graf memori heap yang besar secara linier. Proses ini mengonsumsi siklus CPU secara intensif dan memicu micro-stuttering pada broker.
- Solusi Lazy: Karena Lazy Queue memindahkan pesan dari memori heap ke disk secara langsung, ukuran heap proses Erlang antrean tetap berada di tingkat minimal (beberapa kilobyte saja). Ini menghilangkan beban Garbage Collector sepenuhnya, menjaga utilitas CPU broker tetap stabil dan konstan meskipun antrean menampung puluhan juta pesan backlog.
Evolusi Modern: Storage Engine v2 di RabbitMQ 4.0+ #
Arsitektur penyimpanan RabbitMQ telah mengalami perombakan besar sejak versi 3.10 dan mencapai puncaknya pada RabbitMQ 4.0+ dengan diperkenalkannya Classic Queue v2 (CQv2).
Apa itu CQv2? #
Sebelumnya, pesan kecil disimpan di dalam indeks pesan (queue_index) dan pesan besar disimpan di penyimpanan pesan (msg_store). CQv2 menggabungkan kedua komponen ini menjadi satu mesin penyimpanan biner berbasis segmen terpadu yang jauh lebih efisien.
Perbedaan cq versi baru ini mereduksi perbedaan fungsional antara Classic default dan Lazy:
- Auto-Paging Cerdas: CQv2 secara proaktif menulis pesan ke disk jauh lebih awal sebelum memori RAM menyentuh high watermark, tanpa memicu pemblokiran produsen (flow control).
- Read-Ahead Cache: CQv2 menyertakan mekanisme cache pembacaan internal. Jika consumer mengonsumsi pesan secara aktif, pesan diambil langsung dari RAM cache. Jika terjadi backlog, sistem CQv2 secara otomatis berubah perilaku menyerupai Lazy Queue secara transparan.
Meskipun CQv2 pada RabbitMQ 4.0+ sangat mengurangi kebutuhan untuk menyetel mode "lazy" secara manual, konfigurasi Lazy Queue tetap menjadi opsi penting saat kita menggunakan perangkat keras dengan RAM terbatas namun harus menangani penumpukan antrean skala raksasa.
Trade-off Performa: Latensi vs Kapasitas Memori #
Memilih antara Classic default dengan Lazy Queue adalah keputusan arsitektural yang menuntut keseimbangan antara performa kecepatan dan toleransi kapasitas:
| Aspek Operasional | Classic Queue (Default) | Lazy Queue (Disk-First) |
|---|---|---|
| Latensi Pengiriman | Sangat Rendah (Sub-milidetik, RAM speed) | Lebih Tinggi (Menunggu pembacaan disk) |
| Throughput Ingress | Sangat Tinggi (RAM speed) | Terbatas pada batas kecepatan tulis IOPS disk |
| Konsumsi Memori RAM | Tinggi (Proporsional dengan jumlah pesan) | Sangat Rendah dan Stabil (Konstan) |
| Efisiensi Garbage Collection | Menurun saat backlog menumpuk | Konstan dan sangat efisien |
| Risiko Flow Control | Tinggi saat terjadi lonjakan traffic mendadak | Sangat Rendah |
| Perilaku Backlog | Kurang stabil, berisiko memory pressure | Sangat stabil dan andal |
Detail Paging Erlang & Representasi Fisik di Disk #
Untuk mengapresiasi keandalan Lazy Queue di lingkungan produksi, kita harus melihat bagaimana runtime Erlang mengelola alokasi memori internal dan bagaimana pesan disimpan secara fisik pada sistem file server.
Proses Paging di Tingkat Runtime Erlang #
Di dalam broker, status memori global diawasi oleh pemantau memori vm_memory_monitor. Jika penggunaan RAM broker terdeteksi melampaui ambang batas vm_memory_high_watermark_paging_ratio (secara default disetel sebesar 50% dari Memory High Watermark, atau 20% dari total RAM fisik), RabbitMQ akan memaksa proses antrean Classic memasuki siklus evakuasi memori.
Di tingkat kode Erlang, proses rabbit_amqqueue_process memanggil modul backing_queue untuk memicu fungsi ram_duration_changed/2. Proses ini akan:
- Traversing Heap: Menelusuri seluruh data biner pesan yang tertahan di memori heap proses antrean dari yang tertua hingga yang terbaru.
- Biner Serialization: Mengonversi struktur pesan Erlang term menjadi representasi biner mentah.
- Disk Append: Mengirimkan data biner tersebut ke driver port file Erlang untuk ditulis ke berkas penyimpanan.
- Heap Reclamation: Memanggil proses Garbage Collector secara paksa pada proses antrean tersebut untuk membebaskan ruang memori heap.
Proses penelusuran heap dan pemanggilan Garbage Collector yang agresif ini mengonsumsi siklus CPU yang sangat besar. Jika broker mengalami backlog yang berkelanjutan, CPU akan terus-menerus terjebak dalam siklus penelusuran memori ini, menurunkan throughput broker secara dramatis.
Representasi Struktur Folder di Disk #
Pesan yang dialihkan ke disk (baik oleh Lazy Queue secara langsung maupun oleh Classic Queue melalui proses paging) disimpan secara fisik di bawah direktori data node RabbitMQ:
/var/lib/rabbitmq/mnesia/rabbit@hostname/msg_stores/vhosts/<vhost_uuid>/queues/<queue_uuid>/
Di dalam direktori khusus antrean tersebut, kita akan menemukan beberapa berkas penting:
.idx(Index Files): Berkas indeks yang mencatat urutan pesan, status pengakuan (acknowledgement), dan posisi fisik payload di disk..rdq(Raw Data Queues): Berkas biner segmentasi data pesan mentah. Pesan ditulis secara berurutan (append-only) untuk mengoptimalkan kecepatan tulis disk.
Lazy Queue meminimalkan overhead dengan cara menulis pesan langsung ke berkas .rdq ini begitu pesan diterima, sehingga menghindari penyimpanan sementara di RAM heap proses Erlang. Namun, karena data dibaca dari disk secara dinamis saat dikonsumsi, kecepatan disk I/O (IOPS) dan kecepatan seek SSD kita menjadi penentu utama dari total latensi konsumsi pesan.
Implementasi Go: Mendeklarasikan Lazy Queue #
Untuk mengonfigurasi antrean agar berjalan dalam mode lazy, kita harus menyertakan argumen khusus "x-queue-mode" dengan nilai "lazy" saat melakukan deklarasi antrean. Berikut adalah contoh kode lengkap menggunakan bahasa Go dan pustaka github.com/rabbitmq/amqp091-go.
package main
import (
"context"
"log"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
func main() {
// 1. Membuat koneksi ke RabbitMQ
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatalf("Gagal terhubung ke RabbitMQ: %s", err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
log.Fatalf("Gagal membuka channel: %s", err)
}
defer ch.Close()
// 2. Mendefinisikan Argumen untuk LAZY Mode
// Argumen "x-queue-mode" disetel ke "lazy"
queueArgs := amqp.Table{
"x-queue-mode": "lazy",
}
queueName := "large-backlog-lazy-queue"
// 3. Mendeklarasikan Antrean dengan Argumen Lazy
_, err = ch.QueueDeclare(
queueName,
true, // durable: antrean persisten
false, // auto-delete
false, // exclusive
false, // no-wait
queueArgs, // Mendaftarkan argumen tabel lazy!
)
if err != nil {
log.Fatalf("Gagal mendeklarasikan lazy queue: %s", err)
}
log.Printf("✓ Lazy Queue %s berhasil dideklarasikan.", queueName)
// 4. Mempublikasikan Pesan ke Lazy Queue
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
payload := []byte(`{"event":"background_job","data":"payload_besar_..."}`)
err = ch.PublishWithContext(ctx,
"", // Default Exchange
queueName,
false,
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent, // Menulis pesan secara aman ke disk
ContentType: "application/json",
Body: payload,
},
)
if err != nil {
log.Fatalf("Gagal mengirim pesan: %s", err)
}
log.Println("✓ Pesan dikirim langsung ke media penyimpanan disk fisik.")
}
Anti-Pattern yang Harus Dihindari #
Saat merancang infrastruktur penyimpanan pesan, hindari kesalahan umum berikut:
1. Menggunakan Classic Default untuk Menyimpan Backlog Raksasa #
Membiarkan jutaan pesan menumpuk di dalam Classic Queue default tanpa pengawasan, dengan asumsi RAM server akan selalu cukup.
Mengapa ini salah? #
Ketika backlog tumbuh melampaui watermark RAM, RabbitMQ akan mengaktifkan proses paging darurat. Seluruh aktivitas publikasi pesan akan terhenti akibat Flow Control, dan CPU server akan habis terkonsumsi untuk pemindaian Garbage Collector Erlang. Broker dapat mengalami kondisi tidak responsif (hanging) dan dikeluarkan dari klaster oleh node lainnya.
- Solusi: Jika antrean dirancang untuk menampung data batch berukuran besar atau consumer sering kali dimatikan dalam waktu lama, selalu deklarasikan antrean tersebut sebagai Lazy Queue sejak awal.
2. Mengaktifkan Lazy Mode pada Media Penyimpanan Disk Lambat #
Menjalankan Lazy Queue di atas server yang menggunakan hard disk drive (HDD) mekanis atau penyimpanan jaringan (network storage) dengan kapasitas IOPS yang rendah namun menuntut latensi aplikasi sub-milidetik.
Mengapa ini salah? #
Karena Lazy Queue menulis pesan ke disk saat ingress dan membaca dari disk saat egress, kecepatan pengiriman pesan akan sepenuhnya dibatasi oleh kecepatan I/O fisik disk. Menggunakan disk lambat akan menyebabkan latensi konsumsi pesan melonjak drastis dan membatasi throughput sistem kita.
- Solusi: Pastikan server RabbitMQ menggunakan media penyimpanan solid-state (SSD atau NVMe lokal) yang memiliki IOPS tinggi ketika mengaktifkan mode lazy untuk workload produksi.
Ringkasan #
- Perbedaan Storage Strategy — Classic Queue default menggunakan strategi memory-first untuk kecepatan maksimum, sedangkan Lazy Queue menggunakan disk-first untuk mengamankan stabilitas penggunaan memori RAM broker.
- Mekanisme Paging RAM — Jika Classic default melampaui high watermark RAM, broker terpaksa melakukan paging darurat ke disk dan memblokir pengiriman pesan produsen (Flow Control).
- Bebas GC Churn — Lazy Queue menjaga ukuran heap proses Erlang tetap kecil karena payload pesan dipindahkan ke disk. Ini menghilangkan beban Garbage Collector BEAM VM secara signifikan.
- RabbitMQ 4.0 CQv2 — Versi RabbitMQ modern mengimplementasikan Classic Queue v2 yang secara proaktif menyelaraskan pembacaan RAM cache dengan penulisan disk WAL awal secara otomatis dan efisien.
- Syarat Perangkat SSD — Selalu gunakan media penyimpanan SSD/NVMe dengan IOPS tinggi jika sistem kita memanfaatkan Lazy Queue guna mencegah disk I/O menjadi bottleneck latensi.
- Konfigurasi Parameter — Lazy Queue diaktifkan melalui argumen
"x-queue-mode"dengan nilai"lazy"pada saat proses deklarasi antrean dilakukan oleh klien.