NATS vs RabbitMQ vs Kafka na jednym serwerze VPS
Analiza wyboru brokera komunikatów dla pojedynczej instancji VPS. Porównanie zużycia pamięci, zachowania po restarcie, gwarancji dostarczenia oraz zasadności użycia Postgres.
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 LOCKEDstanowi 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.15 -m 8222Według stanu na październik 2026, aktualna linia serwera to 2.15. 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 pokazano powyżej.
NATS core działa w modelu at-most-once i nie przechowuje żadnych danych. Wydawca wysyła wiadomość do tematu, takiego jak orders.created, a każdy subskrybent, którego filtr pasuje, otrzymuje kopię. Jeśli nikt nie jest zasubskrybowany, wiadomość jest odrzucana, a wydawca nie otrzymuje błędu, ponieważ jego zadanie kończy się w momencie przyjęcia bajtów przez serwer. Grupa kolejkowa (kilku subskrybentów dzielących tę samą nazwę grupy) sprawia, że serwer wybiera jednego członka dla każdej wiadomości, co pozwala na rozdzielenie pracy 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 wiadomości; na dysku nie gromadzą się żadne dane. Zachowanie po restarcie wynika z powyższego: wiadomości w trakcie przesyłania przepadają, klienci łączą się ponownie samodzielnie, a proces odzyskiwania nie istnieje.
Nie ma zaległości do monitorowania, więc należy monitorować utratę danych. Gdy subskrybent odczytuje dane z gniazda wolniej, niż serwer je zapisuje, 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 wiadomości są odrzucane, dlatego należy ustawić alert na ten parametr, zamiast sprawdzać go jednorazowo. NATS core sprawdza się w przypadku wiadomości, których wartość szybko wygasa: metryk, aktualizacji statusu obecności lub 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.15 -js -sd /data -m 8222-sd /data określa katalog przechowywania danych. Pozostawienie tego pola pustego spowoduje, że JetStream zapisze dane w /tmp, co jest tak trwałe, jak sugeruje nazwa. Strumień tworzy się za pomocą CLI, który znajduje się 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 \
--defaultsKażdy limit w tym miejscu jest uzasadniony na małym serwerze. --storage file to dane, które przetrwają awarię, w przeciwieństwie do strumienia w pamięci RAM. --max-bytes=1073741824 ogranicza strumień do 1 GiB, wyrażonego jako liczba bajtów, a --discard old usuwa najstarsze wiadomości po osiągnięciu limitu, zamiast odrzucać nowe zapisy. Brak limitu sprawi, że niekontrolowany wydawca zapełni dysk, co spowoduje zatrzymanie bazy danych, ponieważ 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 wiadomość wyczerpie limit prób dostarczenia, 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, które należy zaimplementować samodzielnie.
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 monitorowania i alarmowania. Koszt zajętości dysku można sprawdzić za pomocą du -sh w katalogu przechowywania danych; wartość ta rośnie, dopóki limit retencji jej 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-managementOd października 2026 roku aktualną serią jest 4.3. Port 5672 służy do obsługi protokołu AMQP (advanced message queuing protocol), a port 15672 udostępnia interfejs 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ę jednoznaczne zachowanie, zamiast złożonej macierzy opcji trwałych i tymczasowych. Cel dla wiadomości niedostarczonych (dead letter) należy ustawić 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 7Wiadomość 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) określony dla danej wiadomości, kolejka przekracza limit długości lub przekroczony zostaje limit dostarczeń dla kolejki quorum. Od wersji RabbitMQ 4.0 limit ten domyślnie wynosi 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 to obszar, w którym RabbitMQ często zaskakuje użytkowników małych serwerów VPS. Domyślny wysoki próg (high watermark) wynosi 0.6 dostępnej pamięci RAM. Gdy węzeł go przekroczy, RabbitMQ blokuje wszystkie połączenia publikujące wiadomości. Aplikacja nie otrzymuje błędu. Operacja publikacji zawiesza się, co w kodzie aplikacji jest interpretowane jako brak odpowiedzi. Dziennik startowy zawiera wartość obliczoną przez węzeł:
Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) totalAlarm dyskowy blokuje wydawców w ten sam sposób, gdy ilość wolnego miejsca spadnie poniżej domyślnych 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 wynosiła co najmniej trzykrotność efektywnego rozmiaru dziennika zapisu (write-ahead log). Limit WAL domyślnie wynosi 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.5Zaległości (backlog) to dwie liczby, których zestawienie wskazuje na rodzaj problemu.
docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledgedmessages_ready oznacza oczekiwanie na konsumenta. messages_unacknowledged oznacza wiadomość dostarczoną, która nie została potwierdzona (ack). Wzrost liczby 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 pojedynczej maszynie i granice sensowności tego rozwiązania
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.propertiesPowyżej znajduje się instrukcja szybkiego startu dla Kafka 4.3.1, aktualna na październik 2026 roku, działająca 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 zrobiono tego samodzielnie. Broker rezerwuje 1 GB sterty Java przed zapisaniem pierwszej wiadomości i wymaga dodatkowej wolnej pamięci RAM na cache strony, z którego korzysta. Na serwerze VPS z 2 GB pamięci RAM aplikacja konkuruje 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 okres, niezależnie od tego, czy wszyscy konsumenci je odczytali. Jest to funkcja, dla której wybiera się to rozwiązanie, ale na małym dysku stanowi również przyczynę awarii, dlatego należy ustawić limit bajtów dla każdego topic przed wystąpieniem problemów.
Teraz kwestia uczciwości. Pojedynczy broker oznacza współczynnik replikacji 1, więc acks=all sprowadza się do jednego fsync na jednym dysku. Uzyskuje się trwałość pojedynczej maszyny przy koszcie operacyjnym brokera JVM oraz kontrolera. Partycje zapewniają równoległość w obrębie brokerów, których tutaj nie ma. Replikacja, świadomość racków i pozostałe funkcje klastrowe pozostają nieaktywne. JetStream oferuje taką samą trwałość i możliwość odtwarzania 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 loader analityczny) albo odtwarzana jest miniaturowa topologia produkcyjna. Planowanie rozbudowy do klastra to plan zakupu kolejnych maszyn, a do tego czasu bilans jest taki sam jak w przypadku uruchamiania k3s na pojedynczym węźle, gdzie płaci się 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-groupNależy monitorować kolumnę LAG, która stanowi różnicę LOG-END-OFFSET minus CURRENT-OFFSET dla każdej partycji. Wzrost opóźnienia na jednej partycji przy stabilnych pozostałych wskazuje na nierównomierny klucz, ponieważ wszystkie wiadomości z tym samym kluczem trafiają do tej samej partycji i są obsługiwane przez jednego konsumenta.
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/*/_dataUruchom 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
- 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. - 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.
- 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.