SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor

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 LOCKED ist 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 Postgres beginnen und den Broker erst beweisen lassen

Die meisten Workloads einzelner Anwendungen umfassen einige tausend Hintergrundjobs 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 einen Job 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. Die Anweisung sperrt die zurückgegebene Zeile und überspringt jede Zeile, die bereits von einer anderen Transaktion gesperrt wurde. Dadurch übernehmen niemals zwei Worker denselben Job. Wenn ein Worker abstürzt, bricht Postgres seine Transaktion ab, gibt die Sperre frei und die Zeile wird für den nächsten Worker sichtbar. Sie erhalten Zustellung mindestens einmal, Wiederholungsversuche durch Erhöhen von attempts und eine Dead-Letter-Tabelle. Dafür nutzen Sie die Persistenz, für die Sie bereits bezahlen. Der Rückstand lässt sich mit einer Abfrage ermitteln: SELECT count(*) FROM job WHERE run_after <= now();

Wo dieser Ansatz nicht mehr funktioniert. Jeder Abruf und jedes Löschen ist ein Schreibvorgang. Bei einer hohen Jobrate bleiben dadurch veraltete Zeilenversionen zurück. Eine Warteschlangentabelle ist der klassische Fall, in dem die Aufblähung schneller zunimmt, als Autovacuum sie beseitigen kann. Lange Jobs verschärfen das Problem. Eine Transaktion, die für die gesamte Dauer der Arbeit offen bleibt, hält auch den Vacuum-Horizont für die gesamte Datenbank zurück. Polling verursacht zusätzliche Latenz. LISTEN mit NOTIFY entfernt das Polling, nicht aber die Schreibvorgänge. Wenn die Jobtabelle Ihre meistgenutzte Tabelle ist oder ein zweiter Dienst dieselben Ereignisse benötigt, sollten Sie die Verarbeitung auslagern. Diese Entscheidung hängt auch 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 daneben einen Broker hinzufügen.

Redis ist möglicherweise der andere Dienst, den Sie bereits betreiben. Redis Streams bieten Consumer-Gruppen mit XADD und XREADGROUP, eine Liste ausstehender Einträge pro Gruppe und XAUTOCLAIM, um Arbeit von einem ausgefallenen Consumer zurückzuholen. Redis ist klein und schnell. Der ehrliche Nachteil auf einem einzelnen Rechner: Mit der üblichen Einstellung appendfsync everysec kann ein Stromausfall ungefähr eine Sekunde an Schreibvorgängen verlieren. Das ist für die Cache-Invalidierung in Ordnung, für Zahlungen jedoch nicht. Wenn Ihre Anwendung aus einem einzelnen Prozess besteht und auf SQLite in der Produktion auf einem VPS aufbaut, funktioniert dasselbe Muster aus Übernehmen und Löschen. SQLite verfügt jedoch über kein Äquivalent zu SKIP LOCKED, und jeder Worker wird an der einen Schreibsperre serialisiert.

NATS Core: Subject-Routing ohne Speicherung

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

Stand Oktober 2026 ist die aktuelle Server-Reihe 2.15. -m 8222 aktiviert den HTTP-Monitoring-Port. Dieser ist standardmäßig deaktiviert und bietet keine Authentifizierung. Binden Sie ihn daher wie oben an localhost.

Core NATS arbeitet nach dem At-most-once-Prinzip und speichert keine Nachrichten. Ein Publisher sendet an ein Subject wie orders.created. Jeder Subscriber, dessen Filter übereinstimmt, erhält eine Kopie. Wenn niemand abonniert ist, wird die Nachricht verworfen. Der Publisher erhält keinen Fehler, weil seine Aufgabe endet, sobald der Server die Bytes angenommen hat. Eine Queue Group, in der mehrere Subscriber denselben Gruppennamen verwenden, lässt den Server pro Nachricht ein Mitglied auswählen. Dadurch wird die Arbeit verteilt, ohne eine Queue zu speichern.

Der Speicherbedarf besteht aus dem Subscription-Status und einem Schreibpuffer pro Verbindung. Er hängt daher von der Anzahl der Verbindungen ab, nicht vom Nachrichtenvolumen. Auf der Festplatte sammelt sich nichts an. Daraus ergibt sich auch das Verhalten beim Neustart: Nachrichten, deren Übertragung noch läuft, gehen verloren, Clients verbinden sich selbstständig neu, und es gibt keinen Wiederherstellungsschritt, auf den Sie warten müssen.

Es gibt keinen Rückstand, den Sie überwachen können. Überwachen Sie stattdessen Nachrichtenverluste. Wenn ein Subscriber seinen Socket langsamer liest, als der Server in ihn schreibt, füllt sich der Puffer für diesen Client. Hat der Client bis zum Ablauf des Schreibzeitlimits nicht aufgeholt, 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. Lösen Sie daher bei diesem Wert einen Alarm aus, statt ihn nur einmal auszulesen. Core NATS eignet sich für Nachrichten, deren Wert schnell verfällt: eine Metrik, eine Statusaktualisierung 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.15 -js -sd /data -m 8222

-sd /data legt das Speicherverzeichnis fest. Lassen Sie es weg, speichert JetStream seine Daten unter /tmp. Das ist genau so dauerhaft, wie es klingt. 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 \
    --defaults

Jedes Limit erfüllt auf einem kleinen Server einen Zweck. --storage file bestimmt, was einen Absturz übersteht, da ein Memory-Stream das nicht tut. --max-bytes=1073741824 begrenzt den Stream auf 1 GiB, angegeben als Byte-Anzahl. --discard old verwirft die ältesten Nachrichten, sobald das Limit erreicht ist, statt neue Schreibvorgänge abzulehnen. Lassen Sie das Limit weg, füllt ein außer Kontrolle geratener Publisher die Festplatte. 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, die ständig fehlschlägt, nicht unbegrenzt erneut zugestellt wird. Wenn eine Nachricht die maximale Anzahl an Zustellungen erreicht, veröffentlicht JetStream unter $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.> eine Meldung. Wenn Sie dieses Subject abonnieren, können Sie den Dead-Letter-Pfad selbst implementieren, den RabbitMQ als Funktion bereitstellt. Diese Arbeit müssen Sie selbst erledigen.

Um einen Rückstand anzuzeigen, verwenden Sie nats stream report für die Anzahl gespeicherter Nachrichten und nats consumer report ORDERS für ausstehende Bestätigungen und nicht verarbeitete Nachrichten pro Consumer. Die Anzahl der nicht verarbeiteten Nachrichten ist der Wert, für den Sie einen Alarm auslösen sollten. Den Speicherbedarf sehen Sie mit du -sh für das Speicherverzeichnis. Er wächst, bis ein Aufbewahrungslimit ihn reduziert.

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-management

Stand Oktober 2026 ist die aktuelle Serie 4.3. Port 5672 verwendet AMQP (Advanced Message Queuing Protocol), Port 15672 ist die Management-Schnittstelle. Lassen Sie die Schnittstelle auf localhost und greifen Sie über einen SSH-Tunnel darauf zu.

Deklarieren Sie Queues mit dem Argument x-queue-type auf quorum gesetzt; 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 eindeutiges Verhalten statt einer Kombination aus dauerhaften und flüchtigen 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 7

Eine Nachricht wird aus vier Gründen als Dead Letter behandelt: Ein Consumer weist sie mit basic.reject oder basic.nack zurück und requeue ist auf false gesetzt, ihre per-message TTL (Time to Live) läuft ab, die Queue überschreitet ein Längenlimit oder die Nachricht überschreitet das Zustelllimit der Quorum Queue. Dieses Limit ist ab RabbitMQ 4.0 standardmäßig auf 20 gesetzt. Ein Handler, der eine Exception auslöst und die Nachricht per nack ablehnt, versucht es daher zwanzigmal und übergibt die Nachricht anschließend an den Dead-Letter-Exchange, statt in einer Schleife weiterzumachen.

Der Arbeitsspeicher sorgt bei RabbitMQ auf kleinen VPS häufig für unerwartetes Verhalten. Der standardmäßige High-Watermark-Wert beträgt 0.6 des verfügbaren RAM. Sobald der Knoten diesen Wert überschreitet, blockiert RabbitMQ jede Verbindung, über die Nachrichten veröffentlicht werden. Ihre Anwendung erhält keinen Fehler. Sie erhält einen Publish-Vorgang, der nie zurückkehrt. In Ihrem eigenen Code wirkt das wie ein Hänger. Das Startprotokoll gibt den vom Knoten berechneten Wert aus:

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

Der Festplattenalarm blockiert Publisher auf dieselbe Weise, wenn der freie Speicher standardmäßig unter 50 MB fällt. Quorum Queues verursachen zusätzliche Anforderungen: Die Dokumentation rechnet mit mindestens 32 Bytes Metadaten im Arbeitsspeicher pro Nachricht, also etwa 1 MB pro 30,000 Nachrichten, und empfiehlt mindestens das Dreifache der effektiven Größe des Write-Ahead-Logs im RAM. Das WAL-Limit beträgt standardmäßig 512 MiB. Allein diese Empfehlung erfordert daher 1.5 GB. Setzen Sie den Wert auf einem Server mit 2 GB in rabbitmq.conf herab, statt darauf zu hoffen, dass der Standardwert passt.

raft.wal_max_size_bytes = 64000000
vm_memory_high_watermark.relative = 0.5

Der Rückstand besteht aus zwei Zahlen. Zusammen zeigen sie, welcher Fehler vorliegt.

docker exec rabbitmq rabbitmqctl list_queues name messages messages_ready messages_unacknowledged

messages_ready wartet auf einen Consumer. messages_unacknowledged wurde zugestellt und nie bestätigt. Ein steigender Wert für nicht bestätigte Nachrichten neben einem konstanten Wert für bereite Nachrichten bedeutet, dass Ihre Worker die Aufträge übernommen haben und nicht fertigstellen. Das ist ein anderer Fehler als bei einer Queue, die lediglich hinterherhinkt.

Kafka auf einem Host 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.properties

Das ist die Kurzanleitung für Kafka 4.3.1, Stand Oktober 2026, im KRaft-Modus (Kafka Raft, der integrierte Controller, der in Kafka 4.0 ZooKeeper ersetzt hat). Das Gegenstück für Container ist apache/kafka:4.3.1.

Das Startskript setzt export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G", wenn Sie die Variable nicht selbst gesetzt haben. Dadurch reserviert der Broker 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 einer 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 keine Größenbegrenzung. Kafka behält Nachrichten während des gesamten Zeitraums, unabhängig davon, ob jeder Consumer sie gelesen hat. Das ist die gewünschte Funktion. Auf einer kleinen Festplatte wird sie zugleich zum Ausfallgrund. Setzen Sie deshalb ein Byte-Limit pro Topic, bevor Sie das Problem feststellen.

Nun zum entscheidenden Punkt. Ein einzelner Broker bedeutet einen Replikationsfaktor von 1. acks=all führt daher zu einem fsync auf einer einzigen Festplatte. Sie erhalten die Dauerhaftigkeit eines einzelnen Systems und tragen gleichzeitig den Betriebsaufwand eines JVM-Brokers mit Controller. Partitionen schaffen Parallelität über Broker, die Sie nicht haben. Replikation, Rack-Awareness und die übrigen Funktionen für größere Umgebungen bleiben ungenutzt. JetStream bietet auf demselben Host für einen Bruchteil des Speichers dieselbe dauerhafte Möglichkeit zum erneuten Lesen. Zwei Gründe sprechen trotzdem für Kafka: Ein nachgelagertes Tool unterstützt ausschließlich das Kafka-Protokoll, etwa bei Change Data Capture mit Debezium oder beim Laden in ein Analytics-System, oder Sie bilden eine Produktivtopologie im Kleinen nach. Wenn Sie in einen Cluster hineinwachsen möchten, benötigen Sie dafür weitere Maschinen. Bis dahin entspricht der Kompromiss dem Betrieb von k3s auf einem einzelnen Knoten, bei dem Sie die Komplexität eines Clusters für die Zuverlässigkeit eines einzelnen Knotens übernehmen.

Der Rückstand in Kafka wird als Consumer-Lag bezeichnet.

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

Lesen Sie die Spalte LAG. Sie entspricht für jede Partition LOG-END-OFFSET minus CURRENT-OFFSET. Steigt der Lag auf einer Partition, während er auf den anderen unverändert bleibt, deutet das auf eine ungleichmäßige Verteilung der Schlüssel hin. Alle Nachrichten mit demselben Schlüssel landen auf derselben Partition, und ein einzelner Consumer verarbeitet sie allein.

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/*/_data

Fü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

  1. 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.
  2. 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.
  3. 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.

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