SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor

NATS, RabbitMQ czy Kafka na jednym serwerze VPS?

Porównanie wydajności i kosztów utrzymania kolejek komunikatów na pojedynczym VPS. Analiza gwarancji dostarczenia, zachowania po restarcie oraz kiedy Postgres zastępuje brokera.

Krótka odpowiedź dla jednego serwera

Kolejka komunikatów na jednym VPS to decyzja dotycząca gwarancji dostarczenia, a nie szybkości. Na pojedynczej maszynie broker rzadko stanowi wąskie gardło, ponieważ kod aplikacji, baza danych i dysk osiągają swoje limity znacznie wcześniej. Należy wybrać narzędzie, którego sposób zachowania w przypadku awarii jest akceptowalny, a następnie przeprowadzić pomiary na posiadanej infrastrukturze.

Cztery opcje, w kolejności, w jakiej większość czytelników powinna je rozważyć:

  • Wykorzystanie bazy danych, która już działa. Postgres z SELECT ... FOR UPDATE SKIP LOCKED stanowi funkcjonalną kolejkę zadań i nie dodaje nowego procesu do monitorowania.
  • Wykorzystanie RabbitMQ, gdy każdy komunikat jest jednostką pracy, która musi zostać potwierdzona, ponowiona określoną liczbę razy, a następnie umieszczona w miejscu dostępnym do weryfikacji przez administratora.
  • Wykorzystanie NATS, gdy komunikaty są zdarzeniami, na które reaguje kilka części systemu. Należy włączyć JetStream dla zdarzeń, które muszą przetrwać restart.
  • Wykorzystanie Kafka, gdy narzędzie końcowe obsługuje wyłącznie protokół Kafka. Na jednym serwerze jest to niemal jedyny uzasadniony powód.

Dalsza część tego przewodnika przedstawia uzasadnienie: jaki jest koszt każdej opcji w zakresie pamięci i dysku na małym VPS, jak zachowuje się ona podczas restartu maszyny oraz dokładne polecenie, które pozwala sprawdzić zaległości w kolejce, zanim odczują to użytkownicy.

Co faktycznie oznacza gwarancja dostarczenia

At most once oznacza, że broker przekazuje wiadomość i usuwa ją z pamięci. Jeśli żaden konsument nie jest podłączony lub konsument ulegnie awarii w trakcie przetwarzania, wiadomość przepada bez żadnego powiadomienia.

At least once oznacza, że konsument wysyła potwierdzenie (ack) po pomyślnym zakończeniu pracy. Do momentu otrzymania tego potwierdzenia broker przechowuje wiadomość i dostarczy ją ponownie. Ponowne dostarczenie jest powodem, dla którego handlery muszą być idempotentne: przetworzenie tej samej wiadomości dwukrotnie nie może skutkować dwukrotnym obciążeniem karty. Gwarancja "exactly once" w ujęciu end-to-end nie jest zapewniana przez brokera. Wynika ona z zastosowania unikalnego klucza wewnątrz własnej bazy danych.

Replay to odrębna właściwość. Kolejka usuwa wiadomość po jej potwierdzeniu. Log przechowuje ją przez określony czas retencji, dzięki czemu nowy konsument może rozpocząć pracę od początku i odczytać całą historię. Kafka oraz NATS JetStream to logi. RabbitMQ to kolejka. Ta różnica determinuje architekturę systemów w większym stopniu niż przepustowość.

Dead lettering to mechanizm obsługi wiadomości, które wielokrotnie kończą się błędem. Bez niego tzw. poison message krąży w pętli w nieskończoność, a pętla ta wygląda jak obciążony pracą proces, a nie jak uszkodzony element systemu.

Rozpocznij od Postgres i wymuś weryfikację brokera

Większość obciążeń pojedynczych aplikacji to kilka tysięcy zadań w tle dziennie. Taka ilość mieści się w jednej tabeli.

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);

Proces roboczy pobiera jedno zadanie wewnątrz transakcji.

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 to cały sekret. Blokuje on zwracany wiersz i pomija każdy wiersz już zablokowany przez inną transakcję, dzięki czemu dwa procesy robocze nigdy nie pobiorą tego samego zadania. Jeśli proces roboczy ulegnie awarii, Postgres wycofa transakcję, blokada zostanie zwolniona, a wiersz stanie się widoczny dla kolejnego procesu. Uzyskuje się dostarczanie co najmniej raz (at-least-once), ponawianie prób poprzez zwiększanie attempts oraz tabelę zadań nieudanych (dead letter table), a wszystko to przy zachowaniu trwałości danych, za którą już płacisz. Zaległości sprawdza się jednym zapytaniem: SELECT count(*) FROM job WHERE run_after <= now();

Gdzie to przestaje działać. Każde pobranie i usunięcie to operacja zapisu, więc wysoka częstotliwość zadań pozostawia martwe wersje wierszy, a tabela kolejki jest klasycznym przykładem sytuacji, w której rozrost (bloat) wyprzedza działanie autovacuum. Długotrwałe zadania pogarszają ten stan, ponieważ transakcja otwarta na czas trwania pracy wstrzymuje również horyzont vacuum dla całej bazy danych. Odpytywanie (polling) zwiększa opóźnienia, a LISTEN wraz z NOTIFY eliminuje odpytywanie, ale nie operacje zapisu. Gdy tabela zadań staje się najbardziej obciążoną tabelą lub gdy druga usługa potrzebuje tych samych zdarzeń, należy przenieść obsługę zadań na zewnątrz. Ten wybór zależy od sposobu wdrożenia samej bazy danych, dlatego ustal czy baza danych działa w Docker czy na hoście przed dodaniem obok niej brokera.

Redis to drugie rozwiązanie, które być może już wykorzystujesz. Redis Streams oferuje grupy konsumentów z XADD oraz XREADGROUP, listę oczekujących dla każdej grupy oraz XAUTOCLAIM, aby przejąć pracę od konsumenta, który przestał działać. Jest to rozwiązanie lekkie i szybkie. Uczciwe zastrzeżenie dla pojedynczej maszyny: przy standardowym ustawieniu appendfsync everysec, nagła utrata zasilania może spowodować utratę około sekundy zapisów. Jest to akceptowalne w przypadku unieważniania pamięci podręcznej, ale niedopuszczalne przy obsłudze płatności. Jeśli aplikacja jest pojedynczym procesem zbudowanym wokół SQLite w środowisku produkcyjnym na VPS, ten sam wzorzec pobierania i usuwania zadziała, choć SQLite nie posiada odpowiednika SKIP LOCKED i każdy proces roboczy musi czekać w kolejce na jedną blokadę zapisu.

NATS core: routing tematów bez pamięci

docker run -d --name nats \
  -p 4222:4222 -p 127.0.0.1:8222:8222 \
  nats:2.14 -m 8222

Według stanu na sierpień 2026 r. aktualna linia serwera to 2.14. Flaga -m 8222 włącza port monitorowania HTTP, który domyślnie jest wyłączony i nie posiada uwierzytelniania, dlatego należy powiązać go z localhost, jak wskazano powyżej.

NATS core działa w modelu "co najwyżej raz" (at most once) i nie przechowuje żadnych danych. Wydawca wysyła komunikat na temat, taki jak orders.created, a każdy subskrybent, którego filtr pasuje, otrzymuje kopię. Jeśli nikt nie jest zasubskrybowany, komunikat jest odrzucany, a wydawca nie otrzymuje błędu, ponieważ zadanie wydawcy kończy się w momencie przyjęcia bajtów przez serwer. Grupa kolejkowa (kilku subskrybentów współdzielących jedną nazwę grupy) sprawia, że serwer wybiera jednego członka dla każdego komunikatu, co pozwala rozdzielić pracę bez tworzenia kolejki na dysku.

Zapotrzebowanie na zasoby zależy od stanu subskrypcji oraz bufora zapisu dla każdego połączenia, więc serwer śledzi liczbę połączeń, a nie wolumen komunikatów; na dysku nie gromadzą się żadne dane. Zachowanie po restarcie wynika z powyższego: komunikaty w locie przepadają, klienci łączą się ponownie samodzielnie i nie ma żadnego etapu odzyskiwania, na który trzeba czekać.

Nie ma żadnego zaległego bufora do monitorowania, należy więc monitorować utratę danych. Gdy subskrybent odczytuje dane z gniazda wolniej, niż serwer zapisuje je do niego, bufor serwera dla tego klienta zapełnia się. Jeśli klient nie nadrobi zaległości przed upływem terminu zapisu, serwer zamyka całe połączenie i zwiększa licznik.

curl -s http://localhost:8222/varz | jq '.slow_consumers, .connections, .in_msgs, .out_msgs'

Wartość slow_consumers, która stale rośnie, oznacza, że komunikaty są odrzucane, dlatego należy ustawić alert dla tego parametru, zamiast sprawdzać go jednorazowo. NATS core sprawdza się w przypadku komunikatów, których wartość szybko wygasa: metryk, aktualizacji statusu obecności czy unieważnienia pamięci podręcznej, które i tak zostaną zastąpione przez kolejne zdarzenie.

NATS JetStream: trwałe strumienie i odtwarzanie w tym samym procesie

JetStream nie jest osobnym produktem. To podsystem wewnątrz tego samego pliku binarnego, aktywowany jedną flagą.

docker run -d --name nats \
  -p 4222:4222 -p 127.0.0.1:8222:8222 \
  -v nats-data:/data \
  nats:2.14 -js -sd /data -m 8222

-sd /data określa katalog przechowywania danych. Pominięcie tej opcji sprawi, że JetStream zapisze dane w /tmp, co jest tak trwałe, jak sugeruje nazwa. Strumień tworzy się za pomocą CLI, który jest dostępny w obrazie 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 \
    --defaults

Każdy limit w tym miejscu jest istotny na małym serwerze. --storage file decyduje o tym, co przetrwa awarię, ponieważ strumień w pamięci RAM tego nie robi. --max-bytes=1073741824 ogranicza strumień do 1 GiB (wartość w bajtach), a --discard old usuwa najstarsze wiadomości po osiągnięciu limitu, zamiast odrzucać nowe zapisy. Brak limitu może doprowadzić do sytuacji, w której niekontrolowany proces publikujący zapełni dysk, co spowoduje zatrzymanie bazy danych, jeśli współdzielą one ten sam nośnik.

Trwały konsument (durable consumer) utrzymuje własną pozycję w strumieniu i zachowuje ją po restarcie. Ustaw --max-deliver dla konsumenta, aby wiadomość, która stale powoduje błąd, przestała być w nieskończoność dostarczana ponownie. Gdy wyczerpie się limit prób dostarczenia wiadomości, JetStream publikuje powiadomienie na $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>. Subskrypcja tego tematu pozwala na zbudowanie mechanizmu dead letter, który w RabbitMQ jest dostępny jako wbudowana funkcja. W tym przypadku jest to zadanie do samodzielnej implementacji.

Aby sprawdzić zaległości, użyj nats stream report dla liczby zapisanych wiadomości oraz nats consumer report ORDERS dla oczekujących potwierdzeń i nieprzetworzonych wiadomości na konsumenta. Wartość nieprzetworzonych wiadomości jest kluczowa dla monitoringu i alarmowania. Wykorzystanie dysku można sprawdzić za pomocą du -sh w katalogu przechowywania danych; rozmiar ten rośnie, dopóki limit retencji go nie ograniczy.

RabbitMQ: potwierdzanie każdej wiadomości i obsługa błędów

docker run -d --name rabbitmq \
  -p 5672:5672 -p 127.0.0.1:15672:15672 \
  -v rabbitmq-data:/var/lib/rabbitmq \
  rabbitmq:4-management

Od sierpnia 2026 roku aktualną serią jest 4.3. Port 5672 obsługuje AMQP (advanced message queuing protocol), a port 15672 służy do interfejsu zarządzania. Interfejs należy pozostawić dostępnym wyłącznie na localhost i uzyskiwać do niego dostęp przez tunel SSH.

Kolejki należy deklarować z argumentem x-queue-type ustawionym na quorum; wartością domyślną pozostaje classic. Kolejki typu quorum są zawsze trwałe i zapisują dane na dysku przed wykonaniem jakiejkolwiek innej operacji. Dzięki temu na pojedynczym węźle uzyskuje się przewidywalne zachowanie zamiast złożonej macierzy opcji trwałych i tymczasowych. Cel dla wiadomości nieobsłużonych (dead letter) należy skonfigurować za pomocą polityki.

docker exec rabbitmq rabbitmqctl set_policy DLX ".*" \
  '{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' \
  --apply-to queues --priority 7

Wiadomość trafia do kolejki dead letter z czterech powodów: konsument odrzuca ją za pomocą basic.reject lub basic.nack przy ustawieniu requeue na false, wygasa jej czas życia (TTL) dla pojedynczej wiadomości, kolejka przekracza limit długości lub przekroczony zostaje limit dostarczeń dla kolejki quorum. Od wersji RabbitMQ 4.0 limit ten wynosi domyślnie 20. Dzięki temu procedura obsługi, która zgłasza błąd i wykonuje nack, ponawia próbę dwadzieścia razy, a następnie przekazuje wiadomość do wymiany dead letter zamiast wpadać w nieskończoną pętlę.

Pamięć RAM jest obszarem, w którym RabbitMQ często zaskakuje użytkowników na małych serwerach VPS. Domyślny próg ostrzegawczy (high watermark) wynosi 0.6 dostępnej pamięci RAM. Gdy węzeł go przekroczy, RabbitMQ blokuje wszystkie połączenia publikujące dane. Aplikacja nie otrzymuje błędu. Publikacja po prostu nie kończy się, co w kodzie aplikacji wygląda jak zawieszenie procesu. Dziennik startowy zawiera wartość obliczoną przez węzeł:

Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) total

Alarm dyskowy blokuje wydawców w ten sam sposób, gdy ilość wolnego miejsca spadnie domyślnie poniżej 50 MB. Kolejki quorum dodają do tego własne wymagania: dokumentacja zakłada co najmniej 32 bajty metadanych w pamięci RAM na wiadomość (około 1 MB na 30 000 wiadomości) i zaleca, aby ilość pamięci RAM była co najmniej trzykrotnie większa od efektywnego rozmiaru dziennika zapisu (write-ahead log). Limit WAL wynosi domyślnie 512 MiB, więc samo to zalecenie wymaga 1.5 GB pamięci. Na serwerze z 2 GB RAM należy obniżyć tę wartość w rabbitmq.conf, zamiast zakładać, że ustawienia domyślne będą odpowiednie.

raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5

Zaległości (backlog) składają się z dwóch liczb, a ich zestawienie wskazuje na rodzaj problemu.

docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledged

messages_ready oznacza oczekiwanie na konsumenta. messages_unacknowledged oznacza wiadomość dostarczoną, która nigdy nie została potwierdzona (ack). Rosnąca liczba niepotwierdzonych wiadomości przy stałej liczbie wiadomości gotowych oznacza, że procesy robocze pobrały zadania, ale przestały je kończyć. Jest to błąd innego typu niż sytuacja, w której kolejka po prostu nie nadąża z przetwarzaniem.

Kafka na jednej maszynie i moment, w którym przestaje to mieć sens

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.properties

To jest przewodnik szybkiego startu dla Kafka 4.3.1, aktualny na sierpień 2026 roku, działający w trybie KRaft (Kafka Raft, wbudowany kontroler, który zastąpił ZooKeeper w Kafka 4.0). Odpowiednik kontenerowy to apache/kafka:4.3.1.

Skrypt startowy ustawia export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G", jeśli nie zrobisz tego samodzielnie, więc broker rezerwuje 1 GB sterty Java (heap) zanim zapisze choćby jedną wiadomość, a dodatkowo oczekuje wolnej pamięci RAM na cache strony, z której korzysta. Na VPS z 2 GB pamięci RAM Twoja aplikacja rywalizuje z JVM o pozostałe zasoby.

Retencja to kolejne zaskoczenie. log.retention.hours domyślnie wynosi 168, co oznacza siedem dni, a log.retention.bytes domyślnie wynosi -1, co oznacza brak limitu rozmiaru. Kafka przechowuje wiadomości przez cały ten czas, niezależnie od tego, czy każdy konsument je odczytał. To funkcja, dla której wybiera się to rozwiązanie, ale na małym dysku jest to również przyczyna awarii, więc ustaw limit bajtów na temat (topic), zanim przekonasz się o tym w praktyce.

Teraz część szczera. Pojedynczy broker oznacza współczynnik replikacji 1, więc acks=all sprowadza się do jednego fsync na jednym dysku. Uzyskujesz trwałość jednej maszyny przy koszcie operacyjnym brokera JVM oraz kontrolera. Partycje zapewniają równoległość między brokerami, których nie posiadasz. Replikacja, świadomość szaf (rack awareness) i reszta funkcji klastrowych pozostają nieaktywne. JetStream oferuje taką samą trwałość i możliwość odtworzenia danych na tej samej maszynie przy ułamku zużycia pamięci. Dwa powody wciąż uzasadniają użycie Kafka w tym scenariuszu: narzędzie końcowe obsługuje wyłącznie protokół Kafka (zmiana przechwytywania danych za pomocą Debezium lub ładowarka analityczna) albo odtwarzasz topologię produkcyjną w miniaturze. Planowanie rozwoju do klastra to plan zakupu większej liczby maszyn, a do tego czasu bilans jest taki sam jak w przypadku uruchamiania k3s na pojedynczym węźle, gdzie płacisz złożonością klastra za niezawodność jednego węzła.

Zaległości w Kafka to opóźnienie konsumenta (consumer lag).

bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group

Sprawdź kolumnę LAG, która jest różnicą LOG-END-OFFSET minus CURRENT-OFFSET dla każdej partycji. Wzrost opóźnienia na jednej partycji, podczas gdy inne pozostają stabilne, wskazuje na nierównomierny klucz, ponieważ wszystkie wiadomości z tym samym kluczem trafiają do tej samej partycji, a jeden konsument obsługuje je samodzielnie.

Co dzieje się po restarcie serwera

Core NATS traci wszystkie dane w trakcie przesyłania i uruchamia się natychmiast, ponieważ nie ma nic do odzyskania. JetStream wczytuje strumienie i pozycje konsumentów z katalogu magazynu, dzięki czemu konsumenci wznawiają pracę od ostatniego offsetu. RabbitMQ odzyskuje kolejki typu quorum z dysku, podczas gdy klasyczne kolejki nietrwałe oraz wszelkie wiadomości opublikowane bez trybu trwałego dostarczania (persistent delivery mode) zostają utracone. Kafka odtwarza swoje segmenty logów podczas uruchamiania, a po nieprawidłowym zamknięciu (unclean shutdown) skanowanie odzyskiwania może zająć minuty na małym dysku, zanim broker zaakceptuje połączenie.

Warto skonfigurować dwie rzeczy. Nadaj kontenerowi politykę restartu (restart: unless-stopped) lub włącz jednostkę systemd, aby broker powrócił po restarcie systemu po aktualizacji jądra bez ingerencji użytkownika. Następnie zadbaj o kolejność: broker, który staje się gotowy dwadzieścia sekund po aplikacji, odrzuci pierwsze połączenia, a niektóre biblioteki klienckie zakończą działanie zamiast ponowić próbę. Zależność aplikacji od brokera można kontrolować za pomocą sprawdzeń stanu w Compose, które wstrzymują zależną usługę do momentu gotowości brokera.

Koszty na własnym VPS, mierzone, a nie deklarowane

Publikowane wartości przepustowości są mierzone na sprzęcie, którego nie posiadasz, zazwyczaj na serwerze wielordzeniowym z lokalnym dyskiem NVMe. Traktuj je jako górną granicę i przeprowadź pomiary na własnej maszynie.

docker stats --no-stream
free -m
sudo du -sh /var/lib/docker/volumes/*/_data

Uruchom testy przy bezczynnym brokerze, a następnie pod rzeczywistym obciążeniem. Różnica między tymi wynikami decyduje o tym, czy broker zmieści się na serwerze obok Twojej aplikacji. Aby uzyskać przybliżoną dolną granicę przepustowości, użyj generatora obciążenia dostarczonego przez twórców danego projektu zamiast polegać na wpisach blogowych: nats bench pub test --msgs 100000 --clients 2 dla NATS, bin/kafka-producer-perf-test.sh dla Kafka oraz PerfTest dla RabbitMQ. Uruchomienie generatora na tym samym VPS mierzy wydajność brokera i generatora łącznie, co jest dopuszczalne, o ile zaznaczysz to w raporcie z wynikami.

Wszystkie te rozwiązania mają jedno wspólne ograniczenie. Każda opcja trwałego zapisu (durable) oczekuje na operację fsync, więc na VPS z sieciową pamięcią masową to dysk wyznacza limit, a zmiana brokera nie przyniesie poprawy wydajności.

Trzy typy obciążeń i odpowiadające im kolejki komunikatów

  1. Zadania w tle dla aplikacji webowej, takie jak wysyłka e-maili, skalowanie obrazów czy dostarczanie webhooków. Rozpocznij od Postgres i SKIP LOCKED. Przejdź na RabbitMQ z kolejkami typu quorum, gdy wymagane są potwierdzenia odbioru dla każdego komunikatu, limit prób dostarczenia oraz kolejka typu dead letter, którą można sprawdzić bez samodzielnego implementowania tej logiki, lub gdy tabela zadań stała się najbardziej obciążoną tabelą w bazie danych.
  2. Zdarzenia, na które reaguje kilka usług wewnętrznych, gdzie utracony komunikat jest szybko zastępowany nowszym. Wykorzystaj podstawowy NATS, stosując tematy (subjects) jako schemat routingu oraz grupy kolejek (queue groups) tam, gdzie potrzebny jest podział pracy. Dodaj strumień JetStream dla ograniczonego zestawu tematów, które muszą przetrwać restart, a resztę pozostaw w pamięci operacyjnej.
  3. Dziennik zdarzeń, z którego odbiorcy czytają od początku, w celu prowadzenia ścieżki audytu, odbudowy modelu odczytu lub późniejszego zasilania analityki. Wykorzystaj JetStream z magazynem plikowym i jawnym limitem bajtów. Wybierz Kafka tylko wtedy, gdy narzędzie końcowe wymaga protokołu Kafka i zaakceptuj stertę JVM jako cenę za tę kompatybilność.

Koszt błędnego wyboru na pojedynczym serwerze nie jest związany z przepustowością. Jest nim odzyskiwanie systemu o trzeciej nad ranem, gdy musisz wiedzieć, czy komunikaty nadal istnieją. Kieruj się tym przy wyborze.

FAQ

Czy mogę uruchomić Kafka na VPS z 2 GB RAM?

Uruchomi się, ale będzie to konfiguracja bardzo ograniczona. bin/kafka-server-start.sh ustawia KAFKA_HEAP_OPTS="-Xmx1G -Xms1G", jeśli nie zostanie to nadpisane, więc JVM rezerwuje 1 GB przed zapisaniem jakiejkolwiek wiadomości, a Kafka polega na wolnej pamięci powyżej tego limitu w celu obsługi page cache. Dodanie aplikacji i bazy danych na tym samym serwerze spowoduje użycie swap. Otrzymasz również współczynnik replikacji 1, co oznacza, że acks=all to jeden fsync na jednym dysku, więc ponosisz koszty operacyjne Kafka bez zapewnienia modelu trwałości danych. NATS JetStream zapewnia trwałe odtwarzanie wiadomości na tym samym sprzęcie przy znacznie mniejszym zużyciu pamięci.

Czy potrzebuję kolejki wiadomości, jeśli używam już Postgres?

Często nie. Tabela job z odczytem SELECT ... FOR UPDATE SKIP LOCKED wewnątrz transakcji zapewnia dostarczenie co najmniej raz (at-least-once), bezpieczną pracę współbieżnych procesów, ponawianie prób oraz tabelę dla błędnych wiadomości (dead letter table), bez konieczności monitorowania dodatkowej usługi i przy zachowaniu istniejących procedur backupu. Sygnały do zmiany rozwiązania są konkretne: tabela kolejki staje się najbardziej obciążonym elementem zapisu i autovacuum nie nadąża, długotrwałe zadania utrzymują otwarte transakcje i blokują vacuum dla całej bazy danych, lub druga usługa musi niezależnie konsumować te same zdarzenia.

Czy do zadań w tle powinienem użyć NATS JetStream czy RabbitMQ?

RabbitMQ, jeśli wymagane jest potwierdzenie dla każdej wiadomości, limit dostarczeń oraz wbudowane kierowanie błędnych wiadomości (dead letter routing). Kolejki typu quorum są zawsze trwałe, limit dostarczeń w RabbitMQ 4.0 domyślnie wynosi 20, a polityka wysyła wyczerpane wiadomości do dead letter exchange, który można opróżnić i sprawdzić. JetStream, jeśli te same zdarzenia muszą być później odtworzone przez innych konsumentów, ponieważ strumień przechowuje wiadomości po potwierdzeniu, a kolejka nie. W JetStream ustawiasz --max-deliver i samodzielnie budujesz ścieżkę dla błędnych wiadomości na podstawie powiadomienia $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>.

Jak sprawdzić, jak duże opóźnienie mają moi konsumenci?

Każdy broker posiada odpowiednie polecenie. W przypadku RabbitMQ, rabbitmqctl list_queues name messages messages_ready messages_unacknowledged rozdziela pracę oczekującą na konsumenta od pracy dostarczonej, ale niepotwierdzonej. W przypadku JetStream, nats consumer report <stream> pokazuje nieprzetworzone wiadomości oraz oczekujące potwierdzenia dla każdego konsumenta. W przypadku Kafka, kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group> wyświetla kolumnę LAG dla każdej partycji. Core NATS nie posiada kolejki do odczytu, ponieważ nie przechowuje danych, więc należy monitorować licznik slow_consumers w http://localhost:8222/varz: zlicza on połączenia zamknięte przez serwer z powodu zbyt wolnego odbioru danych, co oznacza utratę wiadomości.

#nats#rabbitmq#kafka#message-queue#architecture