Erlang VM #

Banyak tim rekayasa perangkat lunak mengoperasikan RabbitMQ setiap hari tanpa pernah menyadari satu fakta arsitektural yang paling mendasar: RabbitMQ berjalan di atas mesin virtual Erlang BEAM VM. BEAM VM bukan sekadar detail implementasi kecil yang bisa diabaikan begitu saja; runtime inilah yang secara eksklusif memberikan karakteristik kestabilan legendaris, toleransi kegagalan ekstrem, dan kemampuan menangani ratusan ribu koneksi konkuren secara simultan pada RabbitMQ. Tanpa memahami cara kerja internal Erlang VM, kita akan sering kali salah mendiagnosis masalah kinerja, salah mengonfigurasi alokasi memori, atau salah dalam merancang arsitektur kluster yang andal. Artikel ini akan membedah secara mendalam implikasi arsitektural Erlang VM terhadap operasional RabbitMQ di lingkungan produksi.

Model Konkurensi Proses Ringan (Lightweight Process Model) #

Kekuatan utama Erlang BEAM VM terletak pada model konkurensinya yang didasarkan pada proses ringan (Lightweight Processes). Proses Erlang tidak sama dengan proses tingkat sistem operasi (OS Process) dan juga sangat berbeda dengan utas sistem operasi (OS Thread).

Berikut adalah perbandingan karakteristik konkurensi di tingkat sistem operasi dengan Erlang BEAM:

KarakteristikOS ThreadErlang Process
Ukuran Memori Awal~1 MB hingga 2 MB~2 KB hingga 4 KB
Overhead PembuatanSangat mahal (butuh syscall kernel)Sangat murah (hanya alokasi RAM lokal)
Context SwitchingLambat (melibatkan CPU cache-flush)Sangat cepat (sub-mikrodetik, dikelola VM)
Kapasitas MaksimalRibuan per serverJutaan per server

Di dalam runtime RabbitMQ, model proses ringan ini dipetakan secara granular ke setiap komponen:

  • Setiap koneksi TCP client yang terhubung dikelola oleh 1 proses Erlang.
  • Setiap Channel logis di dalam koneksi tersebut dikelola oleh 1 proses Erlang tersendiri.
  • Setiap antrean (Queue) dikelola oleh 1 proses Erlang utama.

Karena biaya pembuatan dan pemeliharaan proses Erlang sangat murah, satu node RabbitMQ dapat menangani lebih dari 100.000 koneksi TCP aktif secara konkuren tanpa mengalami kehabisan utas OS (thread exhaustion). Penjadwal internal Erlang VM (Erlang Scheduler) secara otomatis memetakan jutaan proses ringan ini ke core CPU fisik server secara dinamis dan paralel.


Manajemen Memori: Per-Process Garbage Collection #

Bagi pengembang yang terbiasa dengan runtime seperti Java Virtual Machine (JVM) atau runtime Go, masalah lonjakan latensi akibat pembersihan memori (Garbage Collection / GC) adalah tantangan klasik. Pada JVM, ketika alokasi memori penuh, GC akan menjalankan fase Stop-The-World yang menghentikan eksekusi seluruh program sementara waktu untuk menyisir memori. Hal ini dapat memicu lonjakan latensi (latency spike) yang fatal pada sistem pesan real-time.

Erlang BEAM VM memecahkan masalah ini dengan pendekatan yang revolusioner: Per-Process Garbage Collection.

flowchart TD
    subgraph BEAM_VM ["Erlang BEAM VM Memory"]
        direction LR
        subgraph Process_A ["Process A (Queue 1)"]
            HeapA["Local Heap (GC Mandiri)"]
        end
        subgraph Process_B ["Process B (Channel 1)"]
            HeapB["Local Heap (GC Mandiri)"]
        end
        subgraph Process_C ["Process C (Connection 1)"]
            HeapC["Local Heap (GC Mandiri)"]
        end
    end

Di dalam BEAM VM:

  1. Tidak Ada Global Heap: Tidak ada ruang memori besar yang digunakan bersama oleh semua proses. Setiap proses Erlang memiliki alokasi memori (heap dan stack) lokalnya sendiri yang terisolasi.
  2. GC Terlokalisasi: Proses Garbage Collection hanya berjalan di tingkat proses individual. Ketika sebuah antrean menyelesaikan tugasnya dan membuang pesan, GC hanya menyapu memori lokal milik proses antrean tersebut.
  3. Bebas Stop-The-World: Karena GC berjalan secara terisolasi pada masing-masing proses, node RabbitMQ secara keseluruhan tidak pernah mengalami jeda global (Stop-The-World pause). Latensi pengiriman pesan tetap stabil pada tingkat sub-milidetik meskipun server sedang berada di bawah beban memori yang tinggi.

Pohon Pengawasan (Supervision Tree) dan Filosofi Let It Crash #

Salah satu kontribusi terbesar dari kerangka kerja OTP (Open Telecom Platform) milik Erlang adalah pengenalan pola Supervision Tree (Pohon Pengawasan) dan filosofi desain “Let It Crash” (Biarkan Saja Crash).

Dalam bahasa pemrograman tradisional, pengembang menulis banyak kode penanganan kesalahan defensif menggunakan blok try-catch di setiap baris untuk mencegah aplikasi mati mendadak. Namun, dalam sistem terdistribusi yang kompleks, sangat sulit memprediksi seluruh jenis kegagalan.

Erlang mengambil pendekatan sebaliknya:

  • Let It Crash: Jika sebuah proses mengalami keadaan yang tidak valid (misalnya, parsing payload pesan rusak atau database mengalami timeout), proses tersebut dibiarkan langsung mati secara instan.
  • Isolasi Kegagalan: Karena setiap proses terisolasi secara ketat, matinya proses channel atau koneksi tertentu tidak akan pernah memengaruhi kestabilan koneksi client lainnya.
  • Pohon Supervisi: Setiap proses pekerja dipantau oleh proses pengawas (Supervisor). Jika pekerja mati, pengawas akan mendeteksinya dan melakukan pemulihan berdasarkan strategi yang didefinisikan (misalnya, me-restart proses pekerja tersebut dengan state bersih dari database Mnesia).
flowchart TD
    Main["Supervisor Utama Node"] --> ConnSup["Conn Supervisor"]
    Main --> QueueSup["Queue Supervisor"]
    ConnSup --> ConnProc["Connection Proc<br/>'(Crash & Restart)'"]
    QueueSup --> QueueProc["Queue Proc (Billing)<br/>'(Tetap Aktif Stabil)'"]

Jika terjadi gangguan jaringan sementara yang merusak status koneksi TCP, proses koneksi tersebut akan mati, dibersihkan oleh pengawas, dan konsumen akan terputus secara bersih. Begitu konsumen mencoba terhubung kembali, proses baru akan dibuat tanpa ada sisa state sampah (stale state) yang tertinggal.


Kemampuan RabbitMQ untuk membentuk kluster multi-node secara native didasarkan pada fitur distribusi bawaan Erlang VM. Node-node Erlang dapat saling berkomunikasi dan mengirimkan pesan lintas jaringan tanpa membutuhkan pustaka tambahan.

1. Protokol Distribusi Erlang (Erlang Distribution) #

Node kluster berkomunikasi menggunakan protokol biner Erlang melalui port TCP internal (default port 25672). Protokol ini digunakan untuk menyinkronkan data Mnesia dan mereplikasi pesan Quorum Queues.

Untuk memastikan bahwa hanya node resmi yang dapat bergabung ke dalam kluster, Erlang menggunakan sistem otentikasi berbasis token rahasia bernama Erlang Cookie.

  • Cookie ini adalah string teks sederhana yang biasanya disimpan di file /var/lib/rabbitmq/.erlang.cookie pada sistem Linux.
  • Setiap node di dalam kluster wajib memiliki isi file cookie yang identik secara karakter. Jika ada satu node memiliki cookie berbeda, Erlang VM akan menolak jabat tangan (handshake) koneksi, dan kluster akan gagal terbentuk.

3. Port Jaringan yang Wajib Dibuka #

Saat mengonfigurasi firewall atau sistem keamanan jaringan kluster, kita harus membuka beberapa port penting yang digunakan oleh Erlang VM:

  • 4369 (epmd - Erlang Port Mapper Daemon): Layanan DNS internal Erlang untuk memetakan nama node ke port fisik.
  • 25672: Port komunikasi antar-node RabbitMQ.
  • 15672: Port HTTP API dan Management dashboard.
  • 5672: Port AMQP client.

Batasan Throughput Node Tunggal Akibat Batasan Utas Tunggal Antrean #

Meskipun Erlang VM sangat efisien, kita harus menyadari satu batasan arsitektural penting yang melekat pada cara RabbitMQ mendesain antreannya.

[!IMPORTANT] Satu antrean klasik (Classic Queue) atau satu instansi Quorum Queue secara internal dijalankan oleh satu proses Erlang tunggal.

Dalam runtime Erlang BEAM, satu proses Erlang tidak dapat dibagi ke beberapa core CPU; ia hanya dapat dieksekusi oleh satu core CPU fisik pada satu waktu. Implikasi operasionalnya adalah:

  • Jika kita mengirimkan pesan dengan kecepatan sangat tinggi ke dalam satu antrean tunggal pada server canggih yang memiliki 64 core CPU, performa antrean tersebut akan tetap terhambat (bottleneck) ketika core CPU yang mengelola proses antrean tersebut mencapai penggunaan 100%. Sisa 63 core CPU lainnya akan menganggur.
  • Solusi Desain: Untuk memanfaatkan seluruh core CPU server secara optimal, kita harus mendesain sistem kita agar menggunakan banyak antrean (multiple queues), atau memanfaatkan plugin Consistent Hash Exchange untuk menyebarkan pesan secara merata ke puluhan antrean berbeda di belakang layar untuk skalabilitas horizontal optimal.

Manajemen Penjadwalan (BEAM Scheduler) dan Preemptive Scheduling #

Untuk mengerti bagaimana Erlang BEAM VM mendistribusikan beban kerja secara adil lintas prosesor, kita harus mempelajari sistem penjadwalan internalnya (BEAM Scheduler). BEAM VM secara otomatis mendeteksi jumlah core CPU fisik pada server kita saat menyala, lalu membuat tepat satu utas penjadwal (Scheduler Thread) untuk setiap core CPU fisik tersebut.

Berbeda dengan runtime lain yang mengandalkan penjadwalan kooperatif (cooperative scheduling), di mana proses yang sedang berjalan harus secara sukarela menyerahkan kontrol CPU kembali ke sistem, Erlang menggunakan penjadwalan berbasis Preemptive/Reduction-Based Scheduling.

Mekanisme kerja penjadwalan ini adalah sebagai berikut:

  1. Konsep Reduksi (Reductions): Setiap operasi kerja kecil di Erlang (seperti pemanggilan fungsi, penulisan ke soket, atau pencarian pola) diberikan nilai biaya yang disebut Reduction. Satu reduksi setara dengan satu unit kerja kecil CPU.
  2. Batas Kuota Kerja: Setiap proses Erlang yang terpilih untuk berjalan di penjadwal diberikan kuota maksimal sebanyak 2.000 Reductions.
  3. Pengalihan Paksa (Preemption): Begitu proses tersebut menghabiskan 2.000 unit kerja reduksinya, BEAM VM secara paksa akan menangguhkan (yield) proses tersebut, menyimpannya kembali ke antrean siap jalan (run queue), dan langsung menjalankan proses lain yang sedang mengantre.

Dalam konteks operasional RabbitMQ, preemptive scheduling menjamin keadilan distribusi daya komputasi (fairness):

  • Meskipun satu antrean sedang memproses data dalam volume masif (misalnya melakukan kompresi data biner yang padat CPU), antrean tersebut tidak akan pernah bisa memonopoli core CPU.
  • Koneksi client lain yang bertugas mengirimkan sinyal Heartbeat atau pesan kecil akan tetap mendapatkan jatah waktu eksekusi CPU secara berkala, mencegah terjadinya pemutusan koneksi palsu (false disconnection) akibat CPU starvation.

Arsitektur Kotak Surat (Mailbox) Proses Erlang dan Risiko Mailbox Bloat #

Setiap proses Erlang di dalam runtime RabbitMQ bertindak seperti aktor independen yang berkomunikasi murni melalui pengiriman pesan asinkron. Setiap proses memiliki antrean memori internalnya sendiri yang disebut Mailbox (Kotak Surat).

flowchart LR
    P1["Proses Pengirim A"] -->|"Kirim Pesan"| MB["Mailbox (RAM lokal)"]
    P2["Proses Pengirim B"] -->|"Kirim Pesan"| MB
    subgraph TargetProcess ["Proses Penerima Erlang"]
        MB -->|"Pola Pencocokan (Pattern Matching)"| Engine["Engine Kerja"]
    end

Ketika proses pengirim (misal, Connection Process) ingin mengirimkan data ke proses tujuan (misal, Queue Process):

  1. Pesan disalin ke memori RAM lokal milik Mailbox proses tujuan.
  2. Proses tujuan membaca pesan satu per satu dari Mailbox secara berurutan menggunakan mekanisme pencocokan pola (Pattern Matching).
  3. Pesan yang berhasil dicocokkan dan diproses akan segera dihapus dari memori Mailbox.

Ancaman Mailbox Bloat di Produksi #

Meskipun arsitektur Mailbox ini sangat bersih secara desain, ada satu risiko performa serius yang dikenal sebagai Mailbox Bloat (Pembengkakan Kotak Surat).

  • Jika pesan masuk ke Mailbox suatu proses jauh lebih cepat daripada kemampuan pemrosesan proses tersebut, atau jika proses tersebut terhambat karena operasi disk I/O yang lambat, Mailbox akan terus menimbun pesan tak terproses.
  • Karena Mailbox hidup sepenuhnya di dalam memori RAM, pembengkakan ini akan menyebabkan penggunaan memori RAM pada node RabbitMQ meningkat secara eksponensial dalam waktu singkat.
  • Berbeda dengan antrean RabbitMQ yang memiliki fitur memindahkan pesan ke disk (paging to disk) saat memori penuh, Mailbox Erlang tidak memiliki fitur paging ke disk. Jika Mailbox membengkak melewati batas RAM fisik, sistem operasi akan memicu OOM Killer untuk membunuh proses RabbitMQ secara paksa.
  • Penyebab Umum: Channel Multiplexing yang berlebihan pada satu koneksi TCP tunggal, di mana satu proses Connection Erlang harus menyalurkan lalu lintas data dari ribuan channel konkuren secara bersamaan, membuat proses tersebut kewalahan membaca data.

Anti-Pattern vs Solusi: Channel Leakage akibat Penggunaan Thread Lokal Tanpa Close #

Mari kita pelajari salah satu kesalahan pemrograman yang paling sering memicu kegagalan operasional server RabbitMQ akibat kehabisan memori Erlang VM.

Kasus Anti-Pattern: Kebocoran Proses Channel (Channel Leakage) #

Di dalam protokol AMQP, Channel digunakan untuk membagi koneksi TCP utama menjadi beberapa jalur logis. Namun, beberapa pengembang membuat channel baru secara dinamis setiap kali ingin mempublikasikan pesan, tetapi lupa untuk menutup (close) channel tersebut setelah pesan terkirim.

// ANTI-PATTERN: Membuat channel tanpa menutupnya memicu kebocoran proses Erlang
func PublishBad(conn *amqp.Connection, message []byte) {
    // ✗ JANGAN: Membuat channel baru terus-menerus tanpa penutupan
    ch, _ := conn.Channel()
    
    _ = ch.Publish("my-exchange", "routing-key", false, false, amqp.Publishing{
        ContentType: "text/plain",
        Body:        message,
    })
    
    // Masalah: Proses Erlang untuk channel ini tetap hidup di memori server RabbitMQ.
    // Jika fungsi ini dipanggil 10.000 kali, akan terbentuk 10.000 proses Erlang yang menganggur.
    // Server RabbitMQ akan melambat secara perlahan dan akhirnya crash akibat OOM.
}

Solusi Praktis: Menggunakan Penutupan Otomatis atau Melakukan Reuse Channel #

Kita harus selalu memastikan bahwa setiap channel ditutup secara aman menggunakan blok pembersihan resource (seperti defer di Go, try-with-resources di Java, atau blok finally di Node.js), atau mendesain aplikasi agar menggunakan kembali (reuse) satu channel yang sama untuk pengiriman berulang.

// BENAR: Menjamin channel ditutup setelah digunakan
func PublishGood(conn *amqp.Connection, message []byte) {
    ch, err := conn.Channel()
    if err != nil {
        log.Printf("Gagal membuka channel: %v", err)
        return
    }
    // ✓ SOLUSI: Selalu gunakan defer untuk menjamin penutupan channel
    defer ch.Close()
    
    _ = ch.Publish("my-exchange", "routing-key", false, false, amqp.Publishing{
        ContentType: "text/plain",
        Body:        message,
    })
}

Ringkasan #

  • Lightweight Processes — Desain proses ringan Erlang (2-4KB per proses) yang memungkinkan RabbitMQ mengelola ratusan ribu koneksi konkuren tanpa kelelahan thread sistem operasi.
  • Per-Process Garbage Collection — Mekanisme pembersihan memori lokal pada masing-masing proses Erlang yang mengeliminasi latensi jeda global (Stop-The-World GC).
  • Let It Crash Philosophy — Filosofi penanganan kesalahan Erlang yang mengandalkan supervisi pemulihan otomatis ketimbang kode penulisan try-catch defensif yang rumit.
  • Single-Core Queue Limit — Satu proses antrean RabbitMQ terikat pada satu core CPU fisik, mengharuskan kita menggunakan banyak antrean untuk skalabilitas horizontal optimal.

← Sebelumnya: Single vs Cluster   Berikutnya: Metadata & State →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact