NATS vs RabbitMQ vs Kafka auf einem VPS
Vergleichen Sie Zustellgarantien, Speicher- und Festplattenkosten, Neustarts und Backlogs. Erfahren Sie, wann Postgres auf einem einzelnen VPS die bessere Wahl ist.
Die kurze Antwort für einen Server
Eine Nachrichtenwarteschlange auf einem einzelnen VPS ist eine Entscheidung über Zustellgarantien, nicht über Geschwindigkeit. Auf einem einzelnen Server ist der Broker selten der Engpass, weil zuerst Ihr Anwendungscode, Ihre Datenbank und Ihre einzelne Festplatte an ihre Grenzen kommen. Wählen Sie das Tool, mit dessen Fehlerverhalten Sie leben können, und messen Sie anschließend den Server, den Sie tatsächlich betreiben.
Vier Optionen in der Reihenfolge, in der die meisten Leser sie prüfen sollten.
- Verwenden Sie die Datenbank, die bereits läuft. Postgres mit
SELECT ... FOR UPDATE SKIP LOCKEDist eine funktionierende Jobwarteschlange und fügt keinen zusätzlichen Prozess hinzu, den Sie überwachen müssen. - Verwenden Sie RabbitMQ, wenn jede Nachricht eine Arbeitseinheit ist, die bestätigt, eine begrenzte Anzahl von Malen wiederholt und anschließend an einem Ort abgelegt werden muss, an dem ein Mensch sie prüfen kann.
- Verwenden Sie NATS, wenn Nachrichten Ereignisse sind, auf die mehrere Teile Ihres Systems reagieren. Aktivieren Sie JetStream für Ereignisse, die einen Neustart überstehen müssen.
- Verwenden Sie Kafka, wenn ein nachgelagertes Tool ausschließlich das Kafka-Protokoll unterstützt. Auf einem einzelnen Server ist das beinahe der einzige verbleibende Grund.
Der restliche Leitfaden erläutert die Gründe: Welche Kosten jede Option auf einem kleinen VPS in Bezug auf Arbeitsspeicher und Datenträger verursacht, wie sie sich bei einem Neustart des Servers verhält und welcher genaue Befehl einen Rückstand anzeigt, bevor Ihre Benutzer ihn bemerken.
Was eine Zustellgarantie tatsächlich bedeutet
Höchstens einmal bedeutet, dass der Broker die Nachricht übergibt und anschließend verwirft. Wenn kein Consumer verbunden ist oder ein Consumer während der Verarbeitung ausfällt, ist die Nachricht verloren. Es wird nichts gemeldet.
Mindestens einmal bedeutet, dass der Consumer nach erfolgreicher Verarbeitung eine Bestätigung (ein Ack) sendet. Bis diese Bestätigung eintrifft, behält der Broker die Nachricht und stellt sie erneut zu. Deshalb müssen Ihre Handler idempotent sein: Die zweimalige Verarbeitung derselben Nachricht darf die Karte nicht zweimal belasten. Eine Ende-zu-Ende-Garantie für genau einmal gibt Ihnen ein Broker nicht. Sie entsteht durch einen eindeutigen Schlüssel in Ihrer eigenen Datenbank.
Replay ist eine separate Eigenschaft. Eine Queue verwirft eine Nachricht, sobald sie bestätigt wurde. Ein Log bewahrt sie für ein Aufbewahrungsintervall auf. Ein neuer Consumer kann daher am Anfang beginnen und die gesamte Historie lesen. Kafka und NATS JetStream sind Logs. RabbitMQ ist eine Queue. Dieser Unterschied prägt mehr Architekturen als der Durchsatz.
Dead Lettering bezeichnet den Umgang mit einer Nachricht, deren Verarbeitung wiederholt fehlschlägt. Ohne Dead Lettering läuft eine Poison Message endlos weiter. Die Schleife wirkt dann wie ein ausgelasteter Worker und nicht wie ein fehlerhafter.
Mit PostgreSQL beginnen und den Broker sich bewähren lassen
Die meisten Workloads mit einer einzelnen Anwendung umfassen einige tausend Hintergrundaufgaben pro Tag. Das passt in eine Tabelle.
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);Ein Worker übernimmt eine Aufgabe innerhalb einer Transaktion.
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 ist der gesamte entscheidende Mechanismus. Es sperrt die zurückgegebene Zeile und überspringt jede Zeile, die bereits von einer anderen Transaktion gesperrt wurde. Dadurch übernehmen zwei Worker nie dieselbe Aufgabe. Wenn ein Worker abstürzt, bricht PostgreSQL seine Transaktion ab, die Sperre wird freigegeben und die Zeile wird für den nächsten Worker sichtbar. Sie erhalten eine Zustellung mindestens einmal, Wiederholungen durch Erhöhen von attempts und eine Dead-Letter-Tabelle. Dafür nutzen Sie ausschließlich die bereits bezahlbare Datenbankpersistenz. Der Rückstand lässt sich mit einer Abfrage ermitteln: SELECT count(*) FROM job WHERE run_after <= now();
Wo dieser Ansatz nicht mehr funktioniert. Jede Übernahme und jedes Löschen ist ein Schreibvorgang. Eine hohe Aufgabenrate hinterlässt daher alte, nicht mehr benötigte Zeilenversionen. Eine Warteschlangentabelle ist der klassische Fall, in dem die Aufblähung schneller wächst als autovacuum sie beseitigt. Lange Aufgaben verschärfen das Problem. Eine Transaktion, die über die gesamte Ausführungsdauer offen bleibt, hält auch den Vacuum-Horizont für die gesamte Datenbank zurück. Polling erhöht die Latenz. LISTEN zusammen mit NOTIFY beseitigt zwar das Polling, nicht aber die Schreibvorgänge. Wenn die Aufgabentabelle die am stärksten belastete Tabelle ist oder ein zweiter Dienst dieselben Ereignisse benötigt, sollten Sie die Verarbeitung aus der Datenbank herausnehmen. Diese Entscheidung hängt davon ab, wie die Datenbank selbst bereitgestellt wird. Klären Sie daher ob die Datenbank in Docker oder auf dem Host läuft, bevor Sie einen Broker daneben betreiben.
Redis ist die andere Komponente, die Sie möglicherweise bereits betreiben. Redis Streams bieten Consumer-Gruppen mit XADD und XREADGROUP, eine Liste ausstehenden Aufgaben pro Gruppe sowie XAUTOCLAIM, um Aufgaben von einem ausgefallenen Consumer zurückzuholen. Redis ist klein und schnell. Der wichtige Vorbehalt auf einem einzelnen System: Mit der üblichen Einstellung appendfsync everysec kann ein Stromausfall ungefähr eine Sekunde an Schreibvorgängen verlieren. Das ist für die Cache-Invalidierung akzeptabel, aber für Zahlungen falsch. Wenn Ihre Anwendung ein einzelner Prozess ist und auf SQLite in der Produktion auf einem VPS basiert, funktioniert dasselbe Muster aus Übernehmen und Löschen ebenfalls. SQLite verfügt jedoch über kein Äquivalent zu SKIP LOCKED, und jeder Worker wird an der einen Schreibsperre serialisiert.
NATS Core: Subject-Routing ohne Speicher
docker run -d --name nats \
-p 4222:4222 -p 127.0.0.1:8222:8222 \
nats:2.14 -m 8222Im August 2026 ist die aktuelle Server-Linie 2.14. -m 8222 aktiviert den HTTP-Monitoring-Port. Dieser ist standardmäßig deaktiviert und verfügt über keine Authentifizierung. Binden Sie ihn daher wie oben an localhost.
Core NATS bietet höchstens einmalige Zustellung und speichert keine Nachrichten. Ein Publisher sendet an ein Subject wie orders.created. Jeder Subscriber, dessen Filter übereinstimmt, erhält eine Kopie. Wenn niemand das Subject abonniert hat, wird die Nachricht verworfen. Der Publisher erhält keinen Fehler, weil seine Aufgabe beendet war, sobald der Server die Bytes angenommen hatte. Eine Queue Group, in der mehrere Subscriber denselben Gruppennamen verwenden, lässt den Server für jede Nachricht ein Mitglied auswählen. Dadurch wird die Arbeit verteilt, ohne eine Warteschlange zu speichern.
Der Speicherbedarf besteht aus dem Subscription-Status und einem Schreibpuffer pro Verbindung. Er hängt daher von der Anzahl der Verbindungen und nicht vom Nachrichtenvolumen ab. Auf der Festplatte sammelt sich nichts an. Daraus ergibt sich auch das Verhalten bei einem Neustart: Nachrichten, deren Übertragung noch läuft, gehen verloren, Clients verbinden sich selbstständig erneut, und es gibt keinen Wiederherstellungsschritt, auf den gewartet werden muss.
Es gibt keinen Rückstand, den Sie überwachen können. Überwachen Sie stattdessen Nachrichtenverluste. Wenn ein Subscriber seinen Socket langsamer liest, als der Server Daten dorthin schreibt, füllt sich der Puffer für diesen Client. Hat der Client die Daten bis zum Ablauf des Schreibzeitlimits nicht verarbeitet, schließt der Server die gesamte Verbindung und erhöht einen Zähler.
curl -s http://localhost:8222/varz | jq '.slow_consumers, .connections, .in_msgs, .out_msgs'Ein slow_consumers-Wert, der kontinuierlich steigt, bedeutet, dass Nachrichten verworfen werden. Richten Sie daher einen Alarm darauf ein, statt den Wert nur einmal auszulesen. Core NATS eignet sich für Nachrichten, deren Wert schnell verfällt: eine Metrik, eine Presence-Aktualisierung oder eine Cache-Invalidierung, die ohnehin durch das nächste Ereignis ersetzt wird.
NATS JetStream: dauerhafte Streams und Replay im selben Prozess
JetStream ist kein zweites Produkt. Es ist ein Subsystem in derselben Binärdatei und wird mit einem Flag aktiviert.
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 legt das Speicherverzeichnis fest. Lassen Sie es weg, speichert JetStream seine Daten unter /tmp. Diese Speicherung ist genau so dauerhaft, wie der Name vermuten lässt. Erstellen Sie den Stream mit der CLI, die im Image nats-box enthalten ist.
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 \
--defaultsJedes dieser Limits ist auf einem kleinen Server sinnvoll. --storage file bestimmt, welche Daten einen Absturz überstehen, da ein Memory-Stream dies nicht tut. --max-bytes=1073741824 begrenzt den Stream auf 1 GiB, angegeben als Byte-Anzahl. --discard old verwirft beim Erreichen dieses Limits die ältesten Nachrichten, statt neue Schreibvorgänge abzulehnen. Lassen Sie das Limit weg, kann ein außer Kontrolle geratener Publisher die Festplatte füllen. Dann stoppt auch Ihre Datenbank, weil beide denselben Datenträger verwenden.
Ein dauerhafter Consumer speichert seine eigene Position im Stream und behält sie über einen Neustart hinweg. Setzen Sie --max-deliver für den Consumer, damit eine Nachricht, deren Verarbeitung immer fehlschlägt, nicht dauerhaft erneut zugestellt wird. Wenn für eine Nachricht keine Zustellversuche mehr verbleiben, veröffentlicht JetStream auf $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.> eine Advisory-Nachricht. Durch das Abonnieren dieses Subjects bauen Sie den Dead-Letter-Pfad selbst auf, den RabbitMQ als Funktion bereitstellt. Diese Arbeit müssen Sie selbst implementieren.
Um einen Rückstand anzuzeigen, verwenden Sie nats stream report für die Anzahl gespeicherter Nachrichten und nats consumer report ORDERS für ausstehende Acknowledgements sowie unverarbeitete Nachrichten pro Consumer. Die Anzahl unverarbeiteter Nachrichten ist der Wert, für den Sie einen Alarm einrichten sollten. Den Speicherverbrauch sehen Sie mit du -sh für das Speicherverzeichnis. Er wächst, bis ein Retention-Limit Daten entfernt.
RabbitMQ: Jede Nachricht bestätigen, Fehler zurückstellen
docker run -d --name rabbitmq \
-p 5672:5672 -p 127.0.0.1:15672:15672 \
-v rabbitmq-data:/var/lib/rabbitmq \
rabbitmq:4-managementIm August 2026 ist die aktuelle Serie 4.3. Port 5672 verwendet AMQP (Advanced Message Queuing Protocol), und Port 15672 ist die Verwaltungsschnittstelle. Binden Sie die Schnittstelle an localhost und greifen Sie über einen SSH-Tunnel darauf zu.
Deklarieren Sie Queues mit dem Argument x-queue-type auf quorum; der Standardwert ist weiterhin classic. Quorum Queues sind immer dauerhaft und schreiben Daten zuerst auf die Festplatte. Auf einem einzelnen Knoten erhalten Sie dadurch ein eindeutig definiertes Verhalten statt einer Kombination aus dauerhaften und temporären Optionen. Legen Sie das Dead-Letter-Ziel mit einer Policy fest.
docker exec rabbitmq rabbitmqctl set_policy DLX ".*" \
'{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' \
--apply-to queues --priority 7Eine Nachricht wird aus vier Gründen als Dead Letter behandelt: Ein Consumer weist sie mit basic.reject oder basic.nack zurück, während requeue auf false gesetzt ist; ihre TTL (Time to Live) pro Nachricht läuft ab; die Queue überschreitet ein Längenlimit; oder sie überschreitet das Zustelllimit der Quorum Queue. Dieses Limit ist ab RabbitMQ 4.0 standardmäßig auf 20 gesetzt. Ein Handler, der einen Fehler auslöst und die Nachricht per Nack zurückweist, versucht sie daher zwanzigmal erneut und übergibt sie anschließend an den Dead-Letter-Exchange, statt endlos weiterzulaufen.
Der Speicherbedarf ist der Punkt, an dem RabbitMQ auf einem kleinen VPS häufig überrascht. Der standardmäßige High-Watermark liegt bei 0.6 des verfügbaren RAM. Sobald der Knoten diesen Wert überschreitet, blockiert RabbitMQ jede Verbindung, die Nachrichten veröffentlicht. Ihre Anwendung erhält dabei keinen Fehler. Sie führt eine Veröffentlichung aus, die nie zurückkehrt. Im eigenen Code erscheint das als Hängen. Das Startprotokoll gibt den vom Knoten berechneten Wert aus:
Memory high watermark set to 1024 MiB (1073741824 bytes) of 8192 MiB (8589934592 bytes) totalDer Disk Alarm blockiert Publisher auf dieselbe Weise, wenn der freie Speicher standardmäßig unter 50 MB fällt. Quorum Queues benötigen zusätzlich eigenen Speicher: Die Dokumentation rechnet mit mindestens 32 Bytes In-Memory-Metadaten pro Nachricht, also etwa 1 MB pro 30,000 Nachrichten, und empfiehlt mindestens das Dreifache der effektiven Write-Ahead-Log-Größe als RAM. Das WAL-Limit beträgt standardmäßig 512 MiB. Allein diese Empfehlung erfordert daher 1.5 GB. Setzen Sie den Wert auf einem 2-GB-Server in rabbitmq.conf herab, statt darauf zu vertrauen, dass der Standardwert ausreicht.
raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5Der Rückstand besteht aus zwei Zahlen. Zusammen zeigen sie, welcher Fehler vorliegt.
docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledgedmessages_ready wartet auf einen Consumer. messages_unacknowledged wurde zugestellt, aber nie bestätigt. Eine steigende Anzahl unbestätigter Nachrichten neben einer konstanten Anzahl bereiter Nachrichten bedeutet, dass Ihre Worker die Aufgaben übernommen haben, ihre Verarbeitung aber nicht abgeschlossen haben. Das ist ein anderer Fehler als bei einer Queue, die lediglich hinterherhinkt.
Kafka auf einem Server und ab wann es nicht mehr sinnvoll ist
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.propertiesDas ist der Quickstart für Kafka 4.3.1, Stand August 2026, im KRaft-Modus (Kafka Raft, der integrierte Controller, der ZooKeeper in Kafka 4.0 ersetzt hat). Das Container-Pendant ist apache/kafka:4.3.1.
Das Startskript setzt export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G", wenn Sie die Variable nicht selbst gesetzt haben. Der Broker reserviert daher 1 GB Java-Heap, bevor er eine einzige Nachricht speichert. Zusätzlich benötigt er freien RAM für den Page Cache, aus dem er liest. Auf einem VPS mit 2 GB konkurriert Ihre Anwendung dann mit der JVM um den verbleibenden Speicher.
Die Aufbewahrung ist die nächste Überraschung. log.retention.hours hat standardmäßig den Wert 168, also sieben Tage. log.retention.bytes hat standardmäßig den Wert -1, also überhaupt keine Größenbegrenzung. Kafka behält Nachrichten während des gesamten Zeitfensters, unabhängig davon, ob jeder Consumer sie gelesen hat. Das ist die Funktion, die Sie benötigen. Auf einer kleinen Festplatte ist sie zugleich die Fehlerursache. Legen Sie daher ein Byte-Limit pro Topic fest, bevor Sie es selbst feststellen.
Nun zum entscheidenden Punkt. Ein einzelner Broker bedeutet Replikationsfaktor 1. Daher entspricht acks=all einem fsync auf einer einzigen Festplatte. Sie erhalten die Haltbarkeit eines einzelnen Servers, müssen aber den Betriebsaufwand eines JVM-Brokers plus eines Controllers tragen. Partitionen ermöglichen Parallelität über mehrere Broker, die Sie nicht haben. Replikation, Rack Awareness und die übrigen Funktionen für größere Umgebungen bleiben ungenutzt. JetStream bietet auf demselben Server dauerhaftes Replay mit einem Bruchteil des Speicherbedarfs. Zwei Gründe sprechen dennoch für Kafka: Ein nachgelagertes Tool unterstützt ausschließlich das Kafka-Protokoll, etwa Change Data Capture mit Debezium oder ein Analytics-Loader, oder Sie bilden eine Produktions-Topologie im Kleinformat nach. Der geplante Ausbau zu einem Cluster bedeutet, weitere Server anzuschaffen. Bis dahin ist der Kompromiss derselbe wie bei k3s auf einem einzelnen Knoten, wo Sie für die Zuverlässigkeit eines Knotens die Komplexität eines Clusters bezahlen.
Der Rückstand in Kafka wird als Consumer Lag bezeichnet.
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-groupLesen Sie die Spalte LAG. Sie entspricht für jede Partition LOG-END-OFFSET minus CURRENT-OFFSET. Steigt der Lag auf einer Partition, während die übrigen unverändert bleiben, deutet das auf eine unausgewogene Verteilung der Schlüssel hin. Alle Nachrichten mit demselben Schlüssel landen auf derselben Partition, die dann von einem einzelnen Consumer verarbeitet wird.
Was beim Neustart des Servers passiert
Core NATS verliert alle Nachrichten, die sich gerade in Bearbeitung befinden, und ist sofort wieder verfügbar, weil keine Wiederherstellung erforderlich ist. JetStream lädt Streams und Consumer-Positionen aus dem Verzeichnis store neu. Die Consumer setzen ihre Verarbeitung an dem Offset fort, den sie zuvor erreicht hatten. RabbitMQ stellt Quorum Queues von der Festplatte wieder her. Klassische transiente Queues sowie alle Nachrichten, die ohne den persistenten Zustellmodus veröffentlicht wurden, sind dagegen verloren. Kafka liest seine Logsegmente beim Start erneut ein. Nach einem nicht ordnungsgemäßen Herunterfahren kann dieser Wiederherstellungsscan auf einer kleinen Festplatte mehrere Minuten dauern, bevor der Broker Verbindungen annimmt.
Zwei Einstellungen sollten Sie einmalig vornehmen. Geben Sie dem Container eine Restart-Policy (restart: unless-stopped) oder aktivieren Sie die systemd-Unit. Dadurch startet der Broker nach einem Neustart wegen eines Kernel-Upgrades automatisch wieder. Regeln Sie anschließend die Startreihenfolge. Wenn der Broker erst zwanzig Sekunden nach Ihrer Anwendung bereit ist, weist er die ersten Verbindungen zurück. Einige Client-Bibliotheken beenden sich dann, statt es erneut zu versuchen. Halten Sie die Anwendung mit Compose-Healthchecks, die einen abhängigen Dienst zurückhalten, bis der Broker bereit ist vom Broker abhängig.
Kosten auf dem eigenen VPS, gemessen statt aus Quellen übernommen
Veröffentlichte Durchsatzwerte werden auf Hardware gemessen, die Ihnen nicht zur Verfügung steht, meist auf einem Mehrkernserver mit lokalem NVMe-Speicher. Betrachten Sie diese Werte als Obergrenze und messen Sie Ihr eigenes System.
docker stats --no-stream
free -m
sudo du -sh /var/lib/docker/volumes/*/_dataFühren Sie diese Tests zunächst bei inaktivem Broker und anschließend unter Ihrer realen Netzwerklast aus. Die Differenz zwischen beiden Messungen entscheidet, ob der Broker neben Ihrer Anwendung auf dem System betrieben werden kann. Als grobe Untergrenze für den Durchsatz sollten Sie den Lastgenerator des jeweiligen Projekts verwenden und nicht die Angaben aus einem Blogbeitrag: nats bench pub test --msgs 100000 --clients 2 für NATS, bin/kafka-producer-perf-test.sh für Kafka und PerfTest für RabbitMQ. Wenn Sie den Generator auf demselben VPS ausführen, messen Sie Broker und Generator gemeinsam. Das ist in Ordnung, solange Sie dies bei der Angabe des Messwerts ausdrücklich erwähnen.
Für alle drei Systeme gilt dieselbe Obergrenze. Jede hier verwendete dauerhafte Option wartet auf fsync. Bei einem VPS mit netzwerkgebundenem Speicher begrenzt daher der Datenträger den Durchsatz. Ein Wechsel des Brokers ändert daran nichts.
Drei Workloads und die jeweils benötigte Message Queue
- Hintergrundaufgaben für eine Webanwendung, etwa das Versenden von E-Mails, die Größenänderung von Bildern oder die Zustellung von Webhooks. Beginnen Sie mit Postgres und
SKIP LOCKED. Wechseln Sie zu RabbitMQ mit Quorum Queues, wenn Sie Bestätigungen pro Nachricht, ein Zustelllimit und eine Dead-Letter-Queue benötigen, die Sie prüfen können, ohne diese Logik selbst zu implementieren, oder wenn die Jobtabelle zur am stärksten ausgelasteten Tabelle in der Datenbank geworden ist. - Ereignisse, auf die mehrere interne Dienste reagieren und bei denen eine verlorene Nachricht schnell durch eine neuere ersetzt wird. Core NATS mit Subjects als Routing-Schema und Queue Groups, wenn Sie eine Arbeitsverteilung benötigen. Fügen Sie einen JetStream-Stream für die begrenzte Menge an Subjects hinzu, die einen Neustart überstehen müssen, und lassen Sie den Rest im Speicher.
- Ein Ereignisprotokoll, das Consumer vom Anfang an lesen, etwa für einen Audit-Trail, zum erneuten Aufbau eines Read Models oder zur späteren Bereitstellung für Analysen. JetStream mit Dateispeicher und einer expliziten Byte-Begrenzung. Wählen Sie Kafka nur, wenn ein nachgelagertes Tool das Kafka-Protokoll benötigt, und akzeptieren Sie den JVM-Heap als Preis für diese Kompatibilität.
Die Kosten der falschen Wahl auf einem einzelnen Server bestehen nicht im Durchsatz. Sie bestehen in der Wiederherstellung um drei Uhr morgens, wenn Sie wissen müssen, ob die Nachrichten noch vorhanden sind. Treffen Sie Ihre Auswahl danach.
FAQ
Kann ich Kafka auf einem 2 GB VPS betreiben?
Kafka startet, aber die Ressourcen werden knapp. bin/kafka-server-start.sh setzt KAFKA_HEAP_OPTS="-Xmx1G -Xms1G", wenn Sie diesen Wert nicht überschrieben haben. Dadurch beansprucht die JVM 1 GB, bevor sie eine Nachricht speichert. Kafka benötigt darüber hinaus freien Arbeitsspeicher für den Page Cache. Wenn auf demselben System zusätzlich Ihre Anwendung und die Datenbank laufen, wird bereits Swap verwendet. Außerdem erhalten Sie den Replikationsfaktor 1. Das bedeutet, dass acks=all aus einem fsync auf einer einzelnen Festplatte besteht. Sie tragen damit den Betriebsaufwand von Kafka, ohne dessen Datenhaltbarkeitsmodell zu nutzen. NATS JetStream ermöglicht auf derselben Hardware dauerhaftes Replay bei deutlich geringerem Speicherbedarf.
Benötige ich eine Message Queue, wenn ich bereits Postgres betreibe?
Oft nicht. Eine job-Tabelle mit SELECT ... FOR UPDATE SKIP LOCKED innerhalb einer Transaktion ermöglicht Zustellung mindestens einmal, sichere parallele Worker, Wiederholungen und eine Dead-Letter-Tabelle. Dafür benötigen Sie keinen zusätzlichen Dienst, den Sie überwachen müssen, und können die bereits vorhandenen Backups verwenden. Die Gründe für eine Auslagerung sind konkret: Die Queue-Tabelle verursacht die höchste Schreiblast, und autovacuum kommt nicht hinterher. Lang laufende Jobs halten Transaktionen offen und blockieren vacuum für die gesamte Datenbank. Oder ein zweiter Dienst muss dieselben Ereignisse unabhängig konsumieren.
Sollte ich für Hintergrundjobs NATS JetStream oder RabbitMQ verwenden?
RabbitMQ, wenn Sie Quittierungen pro Nachricht, ein Zustellungslimit und Dead-Letter-Routing als integriertes Verhalten benötigen. Quorum Queues sind immer dauerhaft persistent. Das Zustellungslimit ist ab RabbitMQ 4.0 standardmäßig auf 20 gesetzt. Eine Policy leitet Nachrichten, die dieses Limit erreicht haben, an einen Dead-Letter-Exchange weiter, den Sie leeren und untersuchen können. JetStream, wenn dieselben Ereignisse später auch von anderen Consumern erneut gelesen werden müssen. Ein Stream behält Nachrichten nach der Quittierung, eine Queue dagegen nicht. Bei JetStream setzen Sie --max-deliver und erstellen den Dead-Letter-Pfad selbst anhand des $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>-Advisorys.
Wie erkenne ich, wie weit meine Consumer zurückliegen?
Jeder Broker verfügt über einen eigenen Befehl. Bei RabbitMQ trennt rabbitmqctl list_queues name messages messages_ready messages_unacknowledged die Arbeit, die auf einen Consumer wartet, von der Arbeit, die zugestellt, aber nie quittiert wurde. Bei JetStream zeigt nats consumer report <stream> die nicht verarbeiteten Nachrichten und die ausstehenden Quittierungen pro Consumer. Bei Kafka gibt kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group> pro Partition eine LAG-Spalte aus. Core NATS hat keinen Rückstand, den Sie auslesen können, weil es nichts speichert. Überwachen Sie stattdessen den Zähler slow_consumers auf http://localhost:8222/varz. Er zählt Verbindungen, die der Server geschlossen hat, weil sie hinterherhinken. Das bedeutet Nachrichtenverlust.