NATS vs RabbitMQ vs Kafka untuk Satu VPS
Bandingkan jaminan pengiriman, biaya memori dan disk, perilaku saat restart, cara memeriksa backlog, serta kapan Postgres lebih tepat untuk satu VPS.
Jawaban singkat untuk satu server
Message queue pada satu VPS merupakan keputusan tentang jaminan pengiriman, bukan tentang kecepatan. Pada satu server, broker jarang menjadi bottleneck karena kode aplikasi, database, dan satu disk Anda biasanya mencapai batasnya lebih dahulu. Pilih tool dengan perilaku saat terjadi kegagalan yang dapat Anda terima, lalu ukur server yang benar-benar Anda gunakan.
Empat opsi berikut disusun berdasarkan urutan yang sebaiknya dipertimbangkan oleh sebagian besar pembaca.
- Gunakan database yang sudah Anda jalankan. Postgres dengan
SELECT ... FOR UPDATE SKIP LOCKEDmerupakan job queue yang dapat digunakan dan tidak menambahkan proses baru yang harus dipantau. - Gunakan RabbitMQ jika setiap message merupakan unit kerja yang harus diakui, dicoba ulang dalam jumlah terbatas, lalu dipindahkan ke tempat yang dapat diperiksa oleh manusia.
- Gunakan NATS jika message merupakan event yang ditangani oleh beberapa bagian sistem Anda. Aktifkan JetStream untuk event yang harus tetap tersedia setelah restart.
- Gunakan Kafka jika tool downstream hanya mendukung protokol Kafka. Pada satu server, hal ini hampir menjadi satu-satunya alasan untuk memilihnya.
Bagian selanjutnya menjelaskan alasannya: biaya setiap opsi dalam penggunaan memory dan disk pada VPS kecil, perilakunya saat server reboot, serta command yang tepat untuk menampilkan backlog sebelum pengguna Anda merasakannya.
Apa arti jaminan pengiriman sebenarnya
Paling banyak sekali berarti broker menyerahkan pesan lalu melupakannya. Jika tidak ada consumer yang terhubung, atau consumer berhenti di tengah proses, pesan hilang dan tidak ada yang melaporkannya.
Setidaknya sekali berarti consumer mengirim acknowledgement (ack) setelah proses berhasil. Sebelum ack tersebut diterima, broker menyimpan pesan dan akan mengirimkannya lagi. Pengiriman ulang adalah alasan handler harus idempotent: memproses pesan yang sama dua kali tidak boleh membebankan biaya ke kartu dua kali. Pemrosesan tepat satu kali dari ujung ke ujung bukan sesuatu yang diberikan oleh broker. Jaminan tersebut berasal dari kunci unik di database Anda sendiri.
Replay adalah properti yang berbeda. Queue membuang pesan setelah pesan tersebut di-acknowledge. Log menyimpan pesan selama periode retensi, sehingga consumer baru dapat memulai dari awal dan membaca seluruh riwayat. Kafka dan NATS JetStream adalah log. RabbitMQ adalah queue. Perbedaan ini memengaruhi lebih banyak arsitektur daripada throughput.
Dead lettering terjadi pada pesan yang terus gagal diproses. Tanpa mekanisme ini, poison message akan berulang tanpa batas, dan perulangan tersebut terlihat seperti worker yang sibuk, bukan worker yang rusak.
Mulai dengan Postgres dan biarkan broker membuktikan keandalannya
Sebagian besar workload satu aplikasi hanya memproses beberapa ribu job latar belakang per hari. Jumlah itu dapat ditangani dalam satu tabel.
CREATE TABLE job (
id bigserial PRIMARY KEY,
payload jsonb NOT NULL,
run_after timestamptz NOT NULL DEFAULT now(),
attempts int NOT NULL DEFAULT 0
);
CREATE INDEX job_ready_idx ON job (run_after, id);Worker mengambil satu job di dalam transaksi.
BEGIN;
SELECT id, payload
FROM job
WHERE run_after <= now()
ORDER BY id
FOR UPDATE SKIP LOCKED
LIMIT 1;
-- run the work, then remove the row
DELETE FROM job WHERE id = $1;
COMMIT;FOR UPDATE SKIP LOCKED adalah inti mekanismenya. Perintah ini mengunci baris yang dikembalikannya dan melewati baris yang sudah dikunci oleh transaksi lain. Dengan demikian, dua worker tidak akan mengambil job yang sama. Jika worker mengalami crash, Postgres membatalkan transaksinya, kunci dilepaskan, dan baris tersebut dapat dilihat oleh worker berikutnya. Anda mendapatkan pengiriman setidaknya sekali, percobaan ulang dengan menaikkan attempts, serta tabel dead letter, semuanya dengan durabilitas yang sudah Anda bayar. Backlog dapat diperiksa melalui satu query: SELECT count(*) FROM job WHERE run_after <= now();
Batasannya. Setiap pengambilan dan penghapusan job adalah operasi tulis. Karena itu, laju job yang tinggi meninggalkan versi baris yang sudah tidak berlaku, dan tabel queue adalah kasus klasik ketika bloat tumbuh lebih cepat daripada kemampuan autovacuum membersihkannya. Job yang berjalan lama memperburuk keadaan karena transaksi yang tetap terbuka selama proses berlangsung juga menahan vacuum horizon untuk seluruh database. Polling menambah latensi, sedangkan LISTEN dengan NOTIFY menghilangkan polling, tetapi tidak menghilangkan operasi tulis. Jika tabel job menjadi tabel tersibuk yang Anda miliki, atau service kedua memerlukan event yang sama, pindahkan pekerjaan tersebut ke luar database. Pilihan ini berkaitan dengan cara database itu sendiri di-deploy. Karena itu, tentukan apakah database berjalan di Docker atau pada host sebelum menambahkan broker di sebelahnya.
Redis adalah opsi lain yang mungkin sudah Anda jalankan. Redis Streams menyediakan consumer group dengan XADD dan XREADGROUP, daftar pending untuk setiap group, serta XAUTOCLAIM untuk mengambil kembali pekerjaan dari consumer yang mati. Redis berukuran kecil dan cepat. Namun, ada batasan penting pada satu mesin: dengan pengaturan appendfsync everysec yang umum digunakan, kehilangan daya dapat menyebabkan sekitar satu detik operasi tulis hilang. Hal itu sesuai untuk invalidasi cache, tetapi tidak sesuai untuk pembayaran. Jika aplikasi Anda adalah satu proses yang dibangun berdasarkan SQLite di production pada VPS, pola pengambilan dan penghapusan yang sama tetap dapat digunakan. Namun, SQLite tidak memiliki padanan SKIP LOCKED, dan setiap worker harus menunggu secara bergantian pada satu-satunya write lock.
NATS core: routing berdasarkan subject tanpa penyimpanan
docker run -d --name nats \
-p 4222:4222 -p 127.0.0.1:8222:8222 \
nats:2.15 -m 8222Per Oktober 2026, lini server saat ini adalah 2.15. -m 8222 mengaktifkan port pemantauan HTTP, yang secara default nonaktif dan tidak memiliki autentikasi. Karena itu, bind port tersebut ke localhost seperti di atas.
Core NATS menerapkan pengiriman paling banyak satu kali dan tidak menyimpan pesan. Publisher mengirim pesan ke subject seperti orders.created, lalu setiap subscriber yang filternya cocok menerima salinan pesan. Jika tidak ada subscriber, pesan akan dibuang dan publisher tidak menerima error, karena tugas publisher selesai saat server menerima byte tersebut. Queue group, yaitu beberapa subscriber yang menggunakan nama group yang sama, membuat server memilih satu anggota untuk setiap pesan. Mekanisme ini membagi pekerjaan tanpa menyimpan antrean.
Penggunaan resource terdiri atas state subscription dan write buffer untuk setiap koneksi. Karena itu, penggunaan resource mengikuti jumlah koneksi, bukan volume pesan, dan tidak ada data yang terakumulasi di disk. Perilaku setelah restart mengikuti karakteristik tersebut: pesan yang sedang diproses akan hilang, client terhubung kembali secara otomatis, dan tidak ada proses pemulihan yang perlu ditunggu.
Tidak ada backlog yang dapat dipantau. Karena itu, pantau kehilangan pesan. Jika subscriber membaca socket lebih lambat daripada server menulis data ke socket tersebut, buffer server untuk client itu akan penuh. Jika client belum mengejar ketertinggalannya sebelum batas waktu penulisan tercapai, server menutup seluruh koneksi dan menambah nilai counter.
curl -s http://localhost:8222/varz | jq '.slow_consumers, .connections, .in_msgs, .out_msgs'Nilai slow_consumers yang terus meningkat berarti pesan sedang dibuang. Karena itu, buat alert berdasarkan nilai tersebut, bukan hanya membacanya sekali. Core NATS cocok untuk pesan yang nilainya cepat kedaluwarsa, seperti metrik, pembaruan status kehadiran, atau invalidasi cache yang akan digantikan oleh event berikutnya.
NATS JetStream: stream durable dan replay dalam proses yang sama
JetStream bukan produk kedua. JetStream adalah subsistem dalam binary yang sama dan diaktifkan dengan satu flag.
docker run -d --name nats \
-p 4222:4222 -p 127.0.0.1:8222:8222 \
-v nats-data:/data \
nats:2.15 -js -sd /data -m 8222-sd /data menentukan direktori penyimpanan. Jika opsi ini dihilangkan, JetStream menyimpan datanya di bawah /tmp, yang tingkat ketahanannya sesuai dengan namanya. Buat stream menggunakan CLI yang tersedia dalam image nats-box.
docker run --rm -it --network host natsio/nats-box:latest \
nats stream add ORDERS \
--subjects 'orders.>' \
--storage file \
--retention limits \
--max-age 72h \
--max-bytes=1073741824 \
--discard old \
--defaultsSetiap batas tersebut diperlukan pada server kecil. --storage file menentukan data yang tetap tersedia setelah crash, karena memory stream tidak memiliki ketahanan tersebut. --max-bytes=1073741824 membatasi stream hingga 1 GiB dalam satuan byte, sedangkan --discard old menghapus pesan paling lama saat batas tercapai, bukan menolak penulisan baru. Jika batas ini dihilangkan, satu publisher yang tidak terkendali dapat memenuhi disk. Akibatnya, database juga berhenti karena menggunakan disk yang sama.
Consumer durable menyimpan posisinya sendiri dalam stream dan mempertahankannya setelah restart. Atur --max-deliver pada consumer agar pesan yang selalu gagal tidak terus-menerus dikirim ulang. Saat jumlah pengiriman pesan habis, JetStream menerbitkan advisory pada $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>. Dengan berlangganan ke subject tersebut, Anda dapat membangun jalur dead letter yang tersedia sebagai fitur di RabbitMQ. Jalur ini harus Anda implementasikan sendiri.
Untuk melihat backlog, jalankan nats stream report untuk melihat jumlah pesan yang tersimpan dan nats consumer report ORDERS untuk melihat acknowledgment yang masih menunggu serta pesan yang belum diproses pada setiap consumer. Jumlah pesan yang belum diproses adalah nilai yang perlu dipantau sebagai alarm. Penggunaan disk dapat dilihat dengan du -sh pada direktori penyimpanan, dan nilainya terus bertambah hingga batas retention menghapus data.
RabbitMQ: akui setiap pesan, simpan kegagalan secara terpisah
docker run -d --name rabbitmq \
-p 5672:5672 -p 127.0.0.1:15672:15672 \
-v rabbitmq-data:/var/lib/rabbitmq \
rabbitmq:4-managementPer Oktober 2026, seri saat ini adalah 4.3. Port 5672 digunakan oleh AMQP (advanced message queuing protocol), sedangkan port 15672 digunakan oleh antarmuka manajemen. Pertahankan antarmuka tersebut pada localhost dan akses melalui SSH tunnel.
Deklarasikan queue dengan set argumen x-queue-type ke quorum; nilai defaultnya tetap classic. Quorum queue selalu durable dan menulis data ke disk sebelum melakukan hal lain. Dengan demikian, pada satu node, perilakunya jelas dan tidak bergantung pada kombinasi opsi durable dan transient. Tetapkan target dead letter dengan policy.
docker exec rabbitmq rabbitmqctl set_policy DLX ".*" \
'{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' \
--apply-to queues --priority 7Pesan menjadi dead letter karena empat alasan: consumer menolaknya dengan basic.reject atau basic.nack dan requeue disetel ke false, TTL (time to live) per pesan berakhir, panjang queue melampaui batas, atau pesan melampaui batas pengiriman quorum queue. Batas tersebut defaultnya adalah 20 sejak RabbitMQ 4.0. Karena itu, handler yang melempar exception lalu melakukan nack akan mencoba ulang sebanyak 20 kali, kemudian menyerahkan pesan ke dead letter exchange, bukan terus melakukan loop.
Memori sering menimbulkan masalah yang tidak terduga pada VPS kecil. High watermark default adalah 0.6 dari RAM yang tersedia. Ketika node melampaui batas ini, RabbitMQ memblokir setiap koneksi yang sedang melakukan publish. Aplikasi Anda tidak menerima error. Aplikasi menerima operasi publish yang tidak pernah selesai, sehingga kode Anda sendiri tampak macet. Log startup mencetak angka yang dihitung oleh node:
Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) totalDisk alarm juga memblokir publisher dengan cara yang sama ketika ruang kosong turun di bawah 50 MB secara default. Quorum queue memiliki perhitungan tambahan: dokumentasi mengalokasikan setidaknya 32 byte metadata dalam memori untuk setiap pesan, sekitar 1 MB untuk setiap 30,000 pesan, dan merekomendasikan RAM setidaknya tiga kali ukuran write-ahead log efektif. Batas WAL default adalah 512 MiB. Jadi, rekomendasi tersebut saja memerlukan 1.5 GB. Pada server 2 GB, turunkan nilainya di rabbitmq.conf, bukan berharap nilai default sesuai.
raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5Backlog terdiri dari dua angka. Pasangan angka tersebut menunjukkan jenis kegagalan yang terjadi.
docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledgedmessages_ready sedang menunggu consumer. messages_unacknowledged sudah dikirim tetapi belum pernah diakui. Jumlah pesan yang belum diakui yang terus meningkat, sementara jumlah pesan siap tetap, berarti worker Anda sudah mengambil pekerjaan tetapi berhenti menyelesaikannya. Ini merupakan bug yang berbeda dari queue yang sekadar tertinggal.
Kafka pada satu server, dan kapan penggunaannya tidak lagi masuk akal
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
bin/kafka-storage.sh format --standalone -t $KAFKA_CLUSTER_ID -c config/server.properties
bin/kafka-server-start.sh config/server.propertiesItulah quickstart untuk Kafka 4.3.1, yang berlaku hingga Oktober 2026, berjalan dalam mode KRaft (Kafka Raft, controller bawaan yang menggantikan ZooKeeper pada Kafka 4.0). Padanan dalam container adalah apache/kafka:4.3.1.
Skrip start menetapkan export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" jika Anda belum menetapkannya sendiri. Akibatnya, broker mencadangkan heap Java sebesar 1 GB sebelum menyimpan satu pesan pun, dan mengharapkan RAM bebas di luar jumlah tersebut untuk page cache yang digunakan saat membaca data. Pada VPS 2 GB, aplikasi Anda harus berbagi sisa RAM dengan JVM.
Retention adalah kejutan berikutnya. log.retention.hours secara default bernilai 168, yaitu tujuh hari, sedangkan log.retention.bytes secara default bernilai -1, yang berarti tidak ada batas ukuran. Kafka menyimpan pesan selama seluruh jendela tersebut, terlepas dari apakah setiap consumer sudah membacanya. Itulah fitur yang Anda perlukan, tetapi pada satu disk kecil fitur ini juga menjadi penyebab kegagalan. Karena itu, tetapkan batas dalam byte per topic sebelum Anda mengetahuinya melalui masalah.
Sekarang bagian yang perlu disampaikan secara terus terang. Satu broker berarti replication factor 1, sehingga acks=all menghasilkan satu fsync pada satu disk. Anda mendapatkan daya tahan data satu mesin, dengan biaya operasional broker JVM dan satu controller. Partition memberikan paralelisme antarbroker yang tidak Anda miliki. Replication, rack awareness, dan fitur lain untuk cluster tidak digunakan. JetStream memberikan replay persisten yang sama pada server yang sama dengan penggunaan memori yang jauh lebih kecil. Ada dua alasan yang tetap membenarkan penggunaan Kafka di sini: tool downstream hanya mendukung protokol Kafka (misalnya change data capture dengan Debezium atau pemuat analitik), atau Anda sedang mereproduksi topologi produksi dalam skala kecil. Rencana untuk berkembang menjadi cluster berarti Anda memang berencana membeli lebih banyak mesin. Sampai saat itu, pertimbangannya sama seperti menjalankan k3s pada satu node, yaitu Anda menanggung kompleksitas cluster untuk mendapatkan keandalan satu node.
Backlog dalam Kafka disebut consumer lag.
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-groupBaca kolom LAG, yaitu LOG-END-OFFSET dikurangi CURRENT-OFFSET untuk setiap partition. Jika lag meningkat pada satu partition sementara partition lain tetap datar, kemungkinan penyebabnya adalah distribusi key yang tidak merata. Semua pesan dengan key yang sama masuk ke partition yang sama, dan hanya satu consumer yang menanganinya.
Yang terjadi saat server dimulai ulang
Core NATS kehilangan semua data yang sedang diproses dan kembali aktif seketika karena tidak ada data yang perlu dipulihkan. JetStream memuat ulang stream dan posisi consumer dari direktori penyimpanan, sehingga consumer melanjutkan dari offset sebelumnya. RabbitMQ memulihkan quorum queue dari disk, sedangkan transient queue klasik dan pesan apa pun yang diterbitkan tanpa delivery mode persistent akan hilang. Kafka memutar ulang segmen log saat startup. Setelah shutdown yang tidak bersih, pemindaian pemulihan tersebut dapat memerlukan waktu beberapa menit pada disk kecil sebelum broker menerima koneksi.
Ada dua hal yang sebaiknya ditetapkan sekali. Berikan container restart policy (restart: unless-stopped) atau aktifkan unit systemd agar broker kembali aktif setelah reboot akibat upgrade kernel tanpa perlu intervensi manual. Selanjutnya, atur urutannya. Broker yang siap dua puluh detik setelah aplikasi akan menolak koneksi pertama, dan beberapa client library akan keluar alih-alih mencoba lagi. Tahan startup aplikasi hingga broker siap dengan Compose healthcheck yang menunda service dependen hingga broker siap.
Biaya pada VPS Anda, diukur sendiri, bukan berdasarkan angka yang dikutip
Angka throughput yang dipublikasikan diukur pada perangkat keras yang tidak Anda miliki, biasanya server multi-core dengan NVMe lokal. Anggap angka tersebut sebagai batas atas, lalu ukur VPS Anda sendiri.
docker stats --no-stream
free -m
sudo du -sh /var/lib/docker/volumes/*/_dataJalankan pengujian tersebut saat broker tidak sibuk, lalu ulangi saat menangani trafik aktual Anda. Selisih antara kedua hasil tersebut menentukan apakah broker dapat berjalan berdampingan dengan aplikasi Anda. Untuk perkiraan batas bawah throughput, gunakan load generator bawaan setiap proyek, bukan artikel blog orang lain: nats bench pub test --msgs 100000 --clients 2 untuk NATS, bin/kafka-producer-perf-test.sh untuk Kafka, dan PerfTest untuk RabbitMQ. Menjalankan generator pada VPS yang sama mengukur broker dan generator secara bersamaan. Hal ini dapat diterima selama Anda menyatakannya saat melaporkan hasil pengukuran.
Satu batas berlaku untuk semuanya. Setiap opsi durable di sini menunggu fsync. Karena itu, pada VPS dengan storage yang terhubung melalui jaringan, disk menjadi faktor pembatas dan mengganti broker tidak akan mengubahnya.
Tiga workload dan message queue yang dibutuhkan masing-masing
- Background job untuk satu aplikasi web, seperti mengirim email, mengubah ukuran gambar, atau mengirim webhook. Mulai dengan Postgres dan
SKIP LOCKED. Gunakan RabbitMQ dengan quorum queue jika Anda memerlukan acknowledgment per pesan, batas pengiriman, dan dead-letter queue yang dapat diperiksa tanpa menulis logika tersebut sendiri, atau jika tabel job telah menjadi tabel yang paling sibuk dalam database. - Event yang ditanggapi oleh beberapa service internal, ketika pesan yang hilang segera digantikan oleh pesan yang lebih baru. Gunakan Core NATS, dengan subject sebagai skema routing dan queue group saat Anda memerlukan pembagian pekerjaan. Tambahkan stream JetStream untuk sejumlah kecil subject yang harus tetap ada setelah restart, dan simpan sisanya di memory.
- Log event yang dibaca consumer sejak awal, untuk audit trail, membangun ulang read model, atau memasok data ke analytics di kemudian hari. Gunakan JetStream dengan file storage dan batas byte yang ditetapkan secara eksplisit. Pilih Kafka hanya jika tool downstream memerlukan protokol Kafka, dan terima penggunaan heap JVM sebagai biaya kompatibilitas tersebut.
Biaya dari pilihan yang salah pada satu server bukan throughput. Biayanya adalah proses recovery pada pukul tiga pagi, ketika Anda perlu mengetahui apakah pesan masih ada. Pilih berdasarkan hal itu.
FAQ
Dapatkah saya menjalankan Kafka pada VPS 2 GB?
Kafka dapat dijalankan, tetapi kapasitasnya akan sangat terbatas. bin/kafka-server-start.sh menetapkan KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" jika Anda belum menggantinya, sehingga JVM menggunakan 1 GB sebelum menyimpan pesan apa pun. Kafka juga memerlukan memori bebas di luar kapasitas tersebut untuk page cache. Jika aplikasi dan database dijalankan pada server yang sama, sistem akan mulai menggunakan swap. Anda juga mendapatkan replication factor 1. Artinya, acks=all hanya berupa satu fsync pada satu disk. Dengan demikian, Anda menanggung biaya operasional Kafka tanpa model durability-nya. NATS JetStream menyediakan pemutaran ulang yang durable pada perangkat keras yang sama dengan penggunaan memori yang jauh lebih rendah.
Apakah saya memerlukan message queue jika sudah menjalankan Postgres?
Sering kali tidak. Tabel job yang dibaca dengan SELECT ... FOR UPDATE SKIP LOCKED di dalam transaksi menyediakan pengiriman at-least-once, worker konkuren yang aman, retry, dan tabel dead letter, tanpa service tambahan yang harus dimonitor serta dengan backup yang memang sudah Anda lakukan. Indikasi untuk memindahkan beban kerja ke sistem lain bersifat spesifik: tabel queue menjadi beban penulisan terberat dan autovacuum tertinggal, job yang berjalan lama menahan transaksi tetap terbuka dan menghambat vacuum untuk seluruh database, atau service kedua perlu menggunakan event yang sama secara independen.
Sebaiknya saya menggunakan NATS JetStream atau RabbitMQ untuk job latar belakang?
Gunakan RabbitMQ jika Anda memerlukan acknowledgement per pesan, batas pengiriman, dan dead letter routing sebagai perilaku bawaan. Quorum queue selalu durable. Batas pengiriman secara default adalah 20 sejak RabbitMQ 4.0. Policy dapat mengirim pesan yang telah mencapai batas tersebut ke dead letter exchange yang dapat Anda kosongkan dan periksa. Gunakan JetStream jika event yang sama juga perlu diputar ulang oleh consumer lain di kemudian hari, karena stream tetap menyimpan pesan setelah acknowledgement, sedangkan queue tidak. Dengan JetStream, Anda menetapkan --max-deliver dan membangun jalur dead letter sendiri dari advisory $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>.
Bagaimana cara mengetahui seberapa jauh consumer saya tertinggal?
Setiap broker memiliki satu command untuk pemeriksaan ini. Pada RabbitMQ, rabbitmqctl list_queues name messages messages_ready messages_unacknowledged memisahkan pekerjaan yang masih menunggu consumer dari pekerjaan yang sudah dikirim tetapi belum pernah di-acknowledge. Pada JetStream, nats consumer report <stream> menampilkan pesan yang belum diproses dan acknowledgement yang masih tertunda untuk setiap consumer. Pada Kafka, kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group> menampilkan kolom LAG untuk setiap partition. Core NATS tidak memiliki backlog untuk dibaca karena tidak menyimpan apa pun. Karena itu, pantau counter slow_consumers pada http://localhost:8222/varz. Counter tersebut menghitung koneksi yang ditutup server karena tertinggal, yang berarti terjadi kehilangan pesan.