Auswahl der richtigen Postgres-Queue-Technologie
(adriano.fyi)- Bevor man ein separates Queue-System einführt, kann man mit dem bereits genutzten Postgres oft eine Queue für Hintergrundjobs aufbauen und so stark von operativer Einfachheit und der Vertrautheit des Teams profitieren
- Seit Postgres 9.5 lösen
NOTIFY/LISTENundFOR UPDATE SKIP LOCKEDgemeinsam die Benachrichtigung über neue Jobs und die Vermeidung doppelter Verarbeitung zwischen Workern - Redis, Kafka, RabbitMQ und Amazon SQS sind leistungsfähige Optionen, aber jede neue Abhängigkeit bringt Fehlermodi und Lernaufwand in Entwicklungs-, Test- und Betriebsumgebungen
- Auch eine Postgres-Queue ist kein Allheilmittel: Push/Pop kann langsamer sein als bei Redis, und es können betriebliche Kosten entstehen, etwa durch eine separate Queue-Datenbank oder einen eigenen Server sowie häufigeres
VACUUM - Der Code für Hintergrundjobs sollte so abstrahiert werden, dass er nicht an ein bestimmtes Queue-Backend gebunden ist; andere Queue-Technologien sollte man erst dann prüfen, wenn die vertraute Technik die Anforderungen nicht erfüllt
Wie eine Postgres-Queue funktioniert
- Eine Postgres-Queue-Technik besteht aus zwei Elementen
- Pub/Sub zum Ankündigen und Empfangen neuer Jobs
- Zeilensperren, damit nicht mehrere Worker denselben Job verarbeiten
- Beide Funktionen sind seit Postgres 9.5, erschienen 2016, standardmäßig verfügbar
- Mit
NOTIFYundLISTENzusammen lässt sich Pub/Sub an die Anwendung anbinden FOR UPDATE SKIP LOCKEDsetzt Zeilensperren auf passende Datensätze und überspringt bereits gesperrte Datensätze- Auf Job-Datensätze angewandt lassen sich damit Queue-Abfragen wie
SELECT * FROM jobs ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1erstellen
- Auf Job-Datensätze angewandt lassen sich damit Queue-Abfragen wie
SKIP LOCKEDliefert eine „inkonsistente“ Sicht auf die Daten, aber genau diese Eigenschaft ist für Queues nützlich- Bereits in Bearbeitung befindliche Jobs sind wegen der Zeilensperre für andere Worker nicht sichtbar
- Dieses Verhalten ermöglicht verteilte gegenseitige Ausschließung
- Wenn
NOTIFYneue Jobs an Worker sendet, dieLISTENverwenden, lassen sich sowohl periodische Batch-Verarbeitung als auch Echtzeitverarbeitung aufbauen
Warum Redis zum Standard für Hintergrundjobs wurde
- Die Frage, wie lang laufende Jobs verarbeitet werden sollen, ist eine Technologieentscheidung mit vielen Trade-offs
- Häufig genutzte Optionen für Queue- und Message-Broker sind:
- Redis: ein In-Memory-Datenspeicher und Backend vieler Bibliotheken für Hintergrundjobs
- Apache Kafka: eine von der Apache Foundation betreute verteilte Event-Streaming-Plattform
- RabbitMQ: ein weit verbreiteter Message-Broker
- Amazon SQS: ein skalierbarer Queue-SaaS-Dienst von Amazon
- Unter dem GitHub-Thema background jobs topic basieren die fünf populärsten Bibliotheken alle auf Redis
- Redis speichert Daten im Arbeitsspeicher, ist dadurch schnell bei Einfügen und Abrufen und eignet sich mit seiner Pub/Sub-API sowie den Datentypen
listundsetgut für Queue-Implementierungen - Die Skalierbarkeit von Redis macht es für viele Entwickler zur Standardwahl, und Standards haben bei Technologieentscheidungen große Wirkung
Kriterien, die wichtiger sein können als Skalierbarkeit
- Die Fixierung der Tech-Branche auf „Scale“ kann Einfachheit, Wartbarkeit und eine geringere kognitive Last für Entwickler in den Hintergrund drängen
- Viele Anwendungen brauchen keine Skalierung auf dem Niveau von Google, Facebook oder Uber
- Beim Start eines Projekts oder Unternehmens sollte man daher nicht von Anfang an nur auf Skalierbarkeit optimieren, sondern zuerst auf Folgendes achten
- Technologien, die das Team gut kennt
- Technologien, die gut genug sind, um die Anforderungen der Nutzer zu erfüllen
- die Lösung mit dem geringsten Aufwand, passend zu den Fähigkeiten des Teams
- Auch Postgres lässt sich in der Praxis gut skalieren, ist aber für Queue-Anwendungsfälle nicht das am stärksten skalierende spezialisierte Queue-System
- Als Generalist unter den Softwaresystemen erledigt Postgres viele Aufgaben ziemlich gut, und Queues sind eine davon
Entscheidungen mit „langweiliger Technologie“ treffen
- Die wichtigste Frage bei der Wahl einer Queue-Technologie lautet, welche Technik bereits im Einsatz ist und wirklich gut verstanden wird
- Technik, die man schon nutzt und gut kennt, belastet den Software-Stack deutlich weniger
- Wenn bereits eine relationale Datenbank im Einsatz ist und es sich dabei um Postgres handelt, kann man eine Postgres-Queue vor anderer Software in Betracht ziehen
- „Langweilige Technologie“ ist keine feste Liste, sondern hängt davon ab, was gerade schon genutzt wird
- Für nachrichtenorientierte Anwendungen kann RabbitMQ die langweilige Technologie sein
- Für cache-orientierte Anwendungen kann Redis die langweilige Technologie sein
- Für Anwendungen mit vielen relationalen Daten kann Postgres die langweilige Technologie sein
- Wenn Redis, Kafka, RabbitMQ oder SQS nur für Hintergrundjobs neu eingeführt werden, steigen die Kosten deutlich
- Neue Systemabhängigkeiten kommen in Entwicklungs-, Test- und Produktionsumgebungen hinzu
- Entwickler, DBAs und SREs müssen die Fehlermodi und Konfiguration des neuen Systems später beherrschen
- Wissen über Disaster Recovery, Fehlerdiagnose und Performance-Monitoring wird nötig
- Unknown unknowns, die das Team noch nicht kennt, bleiben als Risiko bestehen
Kosten und Auswahlkriterien für eine Postgres-Queue
- Auch langweilige Technologie ist kein Allheilmittel, und Postgres bildet keine Ausnahme
- Wer sich für eine Postgres-Queue entscheidet, gewinnt Vertrautheit, bekannte Fehlermodi und verteilte Kosten, zahlt dafür aber möglicherweise einen Preis
- Push/Pop ist bei einer Postgres-Queue deutlich langsamer als bei Redis
- Statt einer einzelnen relationalen Datenbank können eine Anwendungsdatenbank und eine separate Queue-Datenbank nötig sein
- Um Hintergrundjobs unabhängig zu skalieren, kann ein eigener Datenbankserver erforderlich sein
- Häufigeres
VACUUMkann notwendig werden und zusätzliche Performance-Kosten verursachen
- Keines von Postgres, Redis, Kafka, RabbitMQ oder SQS sollte ein automatischer Standard sein
- Technologieentscheidungen bleiben fortlaufende Trade-offs und müssen anhand der Anforderungen von Team und Anwendung getroffen werden
- Im Zweifel kann man die Regel anwenden: „Alternativen erst dann in Betracht ziehen, wenn nachgewiesen ist, dass die langweilige Technologie die Anforderungen nicht erfüllt“
Eine Architektur für Hintergrundjobs, bei der die Queue austauschbar bleibt
- Der Verarbeitungscode für Hintergrundjobs sollte nicht von einer bestimmten Queue-Technologie abhängen
- Mit dem Wachstum einer Anwendung können bei Bedarf Technologien wie memcached oder Redis hinzukommen; mit der Zeit können auch sie für das Team zur vertrauten „langweiligen Technologie“ werden
- Wenn die Queue-Technologie abstrahiert wird, können Nutzer die passende Queue für ihre Aufgabe auswählen
- Von den populärsten Bibliotheken für Hintergrundjobs auf GitHub bietet mit Ausnahme von Hangfire keine einen Ausstiegspfad weg von Redis zu anderen Queue-Technologien
- In einer solchen Struktur muss beim Wechsel der Queue der Anwendungscode neu geschrieben werden
- Neoq ist eine Go-Bibliothek, die nach dieser Philosophie entwickelt wurde
- Als Queue-Backend können In-Memory, Postgres und Redis verwendet werden
- Nutzer können mit einem anderen Queue-Backend initialisieren, ohne den Anwendungscode zu ändern
- Die Implementierungen für In-Memory und Postgres stammen aus erster Hand, die Redis-Implementierung nutzt asynq
- Redis, Kafka, RabbitMQ und SQS können hervorragende Technologien sein, sind aber nicht immer die passende Queue oder die richtige Komplexität für die jeweilige Aufgabe
1 Kommentare
Meinungen auf Hacker News
Ich habe bisher dreimal ein verteiltes Job-System gebaut, und die Faustregel, die ich seit Jahren empfehle, lautet: „Entwirf für das Zehnfache der aktuellen Größenordnung.“
Wenn es 70 Requests pro Sekunde sind, plane für 700; wenn du 20 Server für Batch-Jobs brauchst, rechne mit 200. Selbst ein Startup, das jedes Jahr um 100 % wächst, ist nach drei Jahren erst bei der achtfachen Größe, also bleibt beim Wachsen noch Zeit, Dinge neu zu schreiben.
Beim ersten System habe ich wegen „Skalierbarkeit“ SQL vermieden, bin aber über viele Randfälle gestolpert, in denen transaktionale Integrität nötig war.
Die beiden danach gebauten verteilten Job-Systeme nutzten PostgreSQL als Koordinator und waren im Grunde rund um
SELECT FOR UPDATE SKIP LOCKEDaufgebaut. Eines steuert normalerweise 350 Worker, das andere wendet komplexe Prioritäten auf Tausende von Jobs an.Beide werden problemlos durchhalten, bis sie mehrere Millionen Dollar Jahresumsatz tragen; das System mit 350 Workern ließe sich zum Beispiel mit etwas Feinschliff auf etwa 2.000 CPUs skalieren.
Hyperscale-Technologie wirkt ziemlich billig, bis man merkt, dass man Transaktionen braucht. Ab diesem Moment wird es zum Engineering-Albtraum, Transaktionssemantik auf einem eventual-consistency Store nachzubauen.
Also sollte man sich hinsetzen und durchrechnen: Wie groß müsste das verteilte System werden, wenn das Unternehmen 100 Mio. Dollar Jahresumsatz macht? Lässt sich leicht eine PostgreSQL-Instanz bekommen, die diese Last bewältigt, oder reicht Sharding pro Client? Wenn ja, sollte man PostgreSQL ernsthaft in Betracht ziehen. Hundert kleine Dinge werden dadurch einfacher.
SQL-Lösungen sind meist einfacher, verbrauchen weniger Rechenressourcen und sind leichter betrieblich zu betreuen.
Damit sie funktionieren, muss man die Datenbank allerdings gut genug kennen, um überhaupt zu wissen, dass Funktionen wie
SELECT FOR UPDATE SKIP LOCKEDexistieren. Heutzutage wachsen viele Engineers in Umgebungen auf, in denen schwergewichtige ORMs die tatsächlichen Fähigkeiten des DBMS verdecken, sodass dieses Wissen ziemlich selten wird.Systeme müssen Spitzenlast aushalten, und wenn man die Spitze nicht kennt, braucht man entweder Sicherheitsmarge oder einen Weg, Jobs bei Bedarf zu verwerfen oder nach hinten zu schieben.
Alles ist ein Trade-off; optimiert werden sollte nur, was Optimierung braucht, und zu erkennen, was das ist, zeichnet gute Engineers aus.
Falls das neu für dich ist, hast du gerade den Gegenwert einer Woche HN-Lesen während der Arbeitszeit bekommen; es lohnt sich, ihn auf Papier zu schreiben und auf deine Geldkarte zu kleben.
In mehreren Projekten habe ich einen einfacheren Ansatz gewählt: normale Tabellen und
SELECT FOR UPDATE SKIP LOCKED, was direkt mit den ORMs/Query-DSL-Frameworks aller Sprachen funktioniert.https://www.pgcasts.com/episodes/the-skip-locked-feature-in-...
Es ist kein „Web Scale“, aber meiner Erfahrung nach skaliert es problemlos bis zu Tausenden von Hintergrundjobs.
Ich habe diesen Ansatz auch für Jobs in großen Organisationen ohne Probleme genutzt. Man braucht kein besonderes Deployment und keine neue Infrastruktur, sondern startet einfach ein paar Worker-Threads in der App. Man kann auch einen Thread haben, der aufgegebene Jobs zurücksetzt.
In drei Jahren ist diese Situation allerdings nie tatsächlich eingetreten; alles lag in
try/catch, bei einem Fehler wurde es wieder in die Queue gelegt, und auch die Java-App war sehr stabil.delete from taskwhere task_id in( select task_idfrom taskorder by random() -- use tablesample for better performancefor updateskip lockedlimit 1)returning task_id, task_type, params::jsonb as params[1] https://taylor.town/pg-task
Es hieß „Tausende von Hintergrundjobs“, aber Queues sollte man in der Regel anhand von Littles Gesetz betrachten und Größen wie die durchschnittliche Job-Zuflussrate pro Sekunde und die durchschnittliche Job-Dauer angeben. Eine bloße Rohanzahl sagt nicht viel aus.
Am Anfang reicht auch ein naives
UPDATE ... SET, aber das hält zu viele Locks. Man kann mit einerSELECT-Subquery imUPDATEarbeiten oder mitSELECT FOR UPDATE SKIP LOCKEDdas Sperren beim Herausnehmen effizienter machen, doch am Ende können die Pull-Queries sich gegenseitig durch Locks blockieren und die Queue zum Stillstand bringen.Dann kann man zwar den Zufluss abschalten, um der DB Luft zu verschaffen, aber wegen des verlorenen Zuflusses entsteht Datenverlust, und meist ist das Kernproblem, dass sich die Pulls gegenseitig blockieren.
Man kann die Job-Tabelle hastig sharden, um Locks zu vermeiden, aber das ist bei der Verteilung über mehrere Worker fragil und kann zu Datenverlust führen. Man kann auch einen Teil zufälliger Jobs verwerfen, aber das ist natürlich ebenfalls Datenverlust.
Solche Optionen sind im laufenden Betrieb extrem stressig, und ohne eine Neuentwicklung von Grund auf ist eine Wiederherstellung sehr schwierig. Wenn man nur ein paar Kunden hat und sicher ist, für immer bei ein paar Dutzend Jobs pro Sekunde zu bleiben, mag das anders sein; seit ich diese Situation selbst erlebt habe, wähle ich nach Möglichkeit echte Queue-Technologie statt einer Datenbank.
Kritik nach dem Muster „Ingenieure kopieren FAANG-Infrastruktur, um cool auszusehen, obwohl sie völlig andere Anforderungen haben“ sieht man oft, aber ich halte das zu einem großen Teil für ein Problem von Wissen und Dokumentation.
Wenn man FAANG oder anderen Startups nacheifern und eine unbegrenzt skalierbare, Queue-basierte Architektur bauen will, gibt es Dutzende hochwertiger Guides, Tutorials und Whitepaper, mit denen man skalierbare Lösungen wie Redis oder SQS in wenigen Stunden einrichten kann.
Die Wartungskosten sind höher, aber man kann mit Befehlen, Code und Konfigurationen aus angesehenen Quellen per Copy-and-paste loslegen.
Wenn man dagegen PostgreSQLs
NOTIFYverwenden möchte und nach „SQLALchemy notify listen postgres“ sucht, findet man nur ein paar unbeantwortete Stack-Overflow-Fragen und GitHub-Gists ohne Kontext.Ich würde diesen Ansatz in einem Nebenprojekt wirklich gern ausprobieren, habe aber nicht die Zeit, allein 2–3 Tage herauszufinden, wie es geht.
Oberflächlich sieht es aus wie die Wahl zwischen „einfach, aber nicht skalierend, also nur PostgreSQL“ und „komplex, aber skalierend, also Redis/SQS usw.“; dann wirkt es so, als würde ich mich von cooler Technik und FAANG blenden lassen und unnötige Skalierbarkeit wählen.
Berücksichtigt man aber auch Guides und Materialien, liegen die tatsächlichen Optionen eher bei „komplex und Skalierbarkeit unvorhersehbar, weil ich die Implementierung und Fallstricke nicht kenne“ und „einfach und skalierbar, so wie es alle in der Praxis machen“. Dann erscheint die Entscheidung eines Ingenieurs, FAANG zu folgen, deutlich rationaler.
NOTIFYverwenden will, liest man einfach https://www.postgresql.org/docs/current/sql-notify.html; besondere Suchfähigkeiten braucht man nicht.Nutzt man zum Beispiel PostgreSQL bei geringer Last, werden Schema-Migrationen, neue Constraints, Analysen usw. fast trivial.
Mit SQS, Cassandra usw. gewinnt man Skalierbarkeit/Verfügbarkeit, aber wenn man feststellt, dass das ursprüngliche Design nicht passt, dauert eine Änderung deutlich länger. Etwa wenn das Business verlangt, einen Constraint wie „Nutzer vom Typ foo dürfen nicht gleichzeitig mit Wert bar kombiniert werden“ hinzuzufügen.
Das lässt sich auch ohne PostgreSQL umsetzen, ist aber besonders bei Änderungsbedarf weder leicht noch einfach.
Deshalb halte ich es für besser, PostgreSQL zu verwenden, um flexibel zu bleiben, und erst dann etwas zusätzlich auf PostgreSQL zu setzen, wenn man weiß, dass es sich nicht ändern wird. Natürlich kommen in diesem Fall Infrastruktur- und Wartungs-Overhead hinzu.
Am Ende ist es immer ein Trade-off, und man muss wissen, was man wann gegen was eintauscht.
Ich halte es nicht für ideal, wenn jeder in seiner Anwendung eigene, maßgeschneiderte Background-Job-Worker implementiert, egal ob mit PostgreSQL-Backend oder einer anderen Queue.
Bei Background-Jobs gibt es viele subtile Punkte und Implementierungsdetails, bei denen man leicht Fehler macht; normalerweise sollte das besser eine umfassendere dedizierte Library oder ein Framework übernehmen.
Wenn nicht jede Rails-Anwendung Sidekiq/Active Jobs hätte und stattdessen jede ihre eigenen Background-Worker implementieren würde, hätten Rails-Apps in Sachen Zuverlässigkeit vermutlich einen deutlich schlechteren Ruf als heute.
Man sollte die Lösung wählen, die den größten Nutzen bringt, ohne sich zu weit vom Hauptziel zu entfernen.
Ich habe einmal ein Startup gesehen, in dem Entwickler etwa 80 % ihrer Zeit damit verbrachten, mit Tools und Infrastruktur zu kämpfen. Die Runway betrug drei Monate, und heute ist am Ende nur noch ein großes Loch übrig. Allein der Gedanke daran lässt mich immer noch schaudern.
Mir scheint, dass es nicht genug Respekt vor Komplexität gibt.
Der größte Nachteil, wenn man PostgreSQL mit
LISTEN/NOTIFYals Publish/Subscribe-Bus verwendet, ist, dassLISTENeine Session-Funktion ist und daher nicht zu Connection-Pooling auf Statement-Ebene passt.Wenn man diesen Ansatz nutzt, sollte man Advisory Locks verwenden. Andere explizite Locks belasten die Datenbank stärker, aber Advisory Locks sind bewusst sehr leichtgewichtig gestaltet.
Mein Lieblingsbeispiel für eine Implementierung ist que, das in mehrere Sprachen portiert wurde.
NOTIFY/LISTENnicht mag, ist, dass es bei Problemen schwer zu diagnostizieren ist.Erst kürzlich hatten wir ein Problem, bei dem nach einiger Zeit alle
NOTIFY/LISTEN-Vorgänge stoppten und sich das nur durch einen Neustart der Datenbank beheben ließ; deshalb mussten wir die Nutzung einstellen. https://dba.stackexchange.com/questions/325104/error-could-n...Skype nutzte vor Jahren PostgreSQL als Queue zusammen mit einem kleinen Plugin, um alle CDRs zu verarbeiten. Ich weiß nicht, ob sie es heute noch nutzen, aber gemessen an vor 10 Jahren war das „Web Scale“, und während im Internet darüber gestritten wurde, dass eine Datenbank als Queue ein Anti-Pattern sei, lief es einfach gut
Transaktionen zu haben ist ziemlich praktisch
https://wiki.postgresql.org/wiki/SkyTools
Da ich es beruflich ziemlich viel genutzt habe, habe ich bei Sydpy auch ein paar Vorträge zu dem Thema gehalten. Wenn man PostgreSQL ohnehin schon gut betreibt und unterstützt, ist es nützlich
Allerdings würde ich heutzutage wohl eine dedizierte Queue verwenden und RabbitMQ auf jeden Fall vermeiden
Selbst wenn man Vendor Lock-in befürchtet, sind die grundlegenden Operationen einer Queue im Grunde nur push/pop und damit so klein, dass es relativ leicht ist, den Code bei Bedarf portierbar zu schreiben
Wenn man die Queue nur deshalb in die Datenbank gelegt hat, weil man die Datenbank ohnehin schon betreibt, ist diese Logik ein zweischneidiges Schwert. Es ist kein Grund zum Feiern, aber das Queue-Chaos kann jetzt auch den primären Datenspeicher mit herunterziehen
Einer der größten Vorteile, PostgreSQL als Anwendungs-Queue zu verwenden, ist, dass alle eingeplanten asynchronen Jobs von Transaktionalität profitieren
Wenn es zum Beispiel nach einer Registrierung eine komplexe Backend-Änderung gibt, die eine E-Mail versendet, und nach dem Einreihen des E-Mail-Jobs ein späterer Teil derselben Transaktion fehlschlägt und zurückgerollt wird, landet dieser E-Mail-Job überhaupt nicht erst in der Queue
Bei E-Mail muss man, wenn das Warten auf die Versandbestätigung fehlschlägt, entscheiden, ob man die Transaktion scheitern lässt und die Möglichkeit doppelter E-Mails in Kauf nimmt oder ob man fortfährt und die Möglichkeit verlorener E-Mails in Kauf nimmt
Der große Vorteil entsteht bei Codepfaden, die die DB asynchron ändern. Da Job-Verbrauch und DB-Update in derselben Transaktion liegen, kann Exactly-once-Semantik vollständig transaktional umgesetzt werden
In PostgreSQL gibt es eine Transaktionslog-Tabelle, die ausgelöste Events und die
pg_current_xact_id()der jeweiligen Transaktion enthält. Man könnte auch das eingebautexminder Zeile verwenden, muss dann aber auf Transaction-Wraparound achten. Beim Einfügen in diese Zeile wird einNOTIFYausgelöstEin Hintergrundprozess läuft wiederholt und wählt aus der Transaktionstabelle alle Zeilen mit Transaktions-IDs zwischen dem
xmindes letzten Laufs und dem aktuellenpg_snapshot_xmin(pg_current_snapshot())aus. Diese Events werden auf Jobs gemappt und an SQS übermittelt; anschließend wirdxmingespeichert und perLISTENauf das nächsteNOTIFYgewartetBei einer PostgreSQL-Queue stimmt es: Wenn nach dem Einreihen des E-Mail-Versandjobs die Transaktion später fehlschlägt, landet die E-Mail nicht in der Queue
Datenbankcode, der wirklich getrennt sein muss, sollte in separate Transaktionen aufgeteilt werden. Umgekehrt möchte man vielleicht keine Registrierungs-E-Mail senden, wenn der Benutzer nicht in der DB angelegt wurde; es hängt also von der Situation ab
Bei Redis-basierten Queues oder praktisch jeder anderen Queue gerät man schnell in Situationen, in denen ein Job ausgeführt wird, der davon ausgeht, dass ein Datenbankdatensatz existiert, bevor die Transaktion committet wurde. Der Code, der das behebt, wird meist unbeholfen und komplex
Was ich an Kafka mag, ist, dass es einfach ein Append-only-Log ist und Clients im Wesentlichen nur Offsets halten
Konzeptionell ist das sehr einfach zu verstehen, persistent und ziemlich robust gegenüber Ausfällen, weil man zu jedem Offset zurückgehen und erneut lesen kann
Leider bringt Kafka aufgrund seines verteilten Charakters genug Komplexität mit, dass es sich für die meisten Anwendungsfälle am Ende nicht lohnt
Persönlich fände ich etwas Ähnliches schön, das einfacher zu betreiben ist. Auf einem einzelnen Node könnte es Hunderte, vielleicht Tausende Events pro Sekunde verarbeiten, und ohne verteilte Komplexität wäre das ziemlich gut
Theoretisch kann man das auch mit PostgreSQL machen, man darf nur niemals Zeilen löschen. Vielleicht ist das ja die Antwort
Wenn man nicht den vollständigen Funktionsumfang wie Consumer Groups und Partitionen braucht, dürfte auch die Consumer-Konfiguration deutlich einfacher werden
Bei Windmill verwenden wir genau diesen Ansatz. Windmill ist eine Open-Source-Alternative zu Retool und eher ein modernes Airflow-ähnliches Tool, und wir lassen täglich Benchmarks laufen
Wenn man auf einer ordentlichen GitHub-CI-Instanz einen Windmill-Worker und PostgreSQL in Containern ausführt, verarbeitet der Benchmark 1.200 Jobs pro Sekunde. Mit zusätzlichen Workern skaliert es stabil bis auf 5.000 Jobs pro Sekunde
Wir prüfen den Einsatz von Citus, um auf einer Multi-Tenant-Instanz die Marke von 5.000 Jobs pro Sekunde zu überschreiten
https://github.com/windmill-labs/windmill/tree/benchmarks
Zu der Zeit, als es um etwa 10 Nachrichten pro Sekunde ging, haben wir für einige Queues PostgreSQL verwendet, und es ließ sich durchaus skalieren. Aber ehrlich gesagt ist es in AWS, GCP und Azure extrem einfach, SQS oder einen anderen Queue-Stack einzurichten; diese Dienste sind genau für diesen Zweck gebaut und bringen Dinge wie DLQs bereits mit.
Ich sehe keinen Grund, diesen Weg zu wählen und sich Sorgen zu machen, dass dieses System zusammenbricht und dadurch auch der Zustand der restlichen DB beeinträchtigt wird.
Ich mag das Prinzip „verwende das einfachste Werkzeug“, aber manchmal gehen Engineers dabei zu weit und lassen am Ende nur noch das dümmste Werkzeug mit fragwürdigen Einschränkungen übrig, obwohl die etablierten Alternativen vergleichsweise günstig und einfach sind.
Wenn der Job-Zustand in der DB liegt, kann man ihn gut abfragen. Auch wenn das nicht unbedingt den exakten tatsächlichen Zustand zeigt, hilft es bei der Analyse von Betriebsstörungen. Vor allem löschen die meisten Job-Queues verarbeitete Records einfach.
Und wenn man alle Background-Jobs idempotent macht, ist ein Ansatz wie „eine Nachricht an die Job-Queue senden, damit dieser Job verarbeitet wird“ fast immer sicher.
Wenn man sich nur auf eine Message Queue verlässt, ist es bei Performance-Problemen oft schwer zu erkennen, was passiert. RabbitMQ kann zum Beispiel zwar die Queue-Größe melden, bietet aber kaum oder nur sehr eingeschränkte Möglichkeiten, die internen Daten zu inspizieren.
Manche Software wird voraussichtlich nie über die Fähigkeiten von PostgreSQL hinauswachsen; und selbst wenn doch, kann man sie so bauen, dass ein Wechsel zu einem anderen Service einfach bleibt.
Außerdem kann man ein transaktionales Job-System schlicht innerhalb von PostgreSQL aufbauen wollen.
Wir verwenden Oban für Elixir und verarbeiten damit innerhalb von PostgreSQL täglich Hunderttausende bis Millionen von Jobs.
Transaktionssemantik rund um Background-Jobs zu haben – etwa E-Mails nur dann einzuplanen, wenn eine Transaktion erfolgreich war – ist enorm praktisch.
Man muss
autovacuumein wenig tunen, aber nachdem wir das bereinigt hatten, hat es für uns sehr gut gepasst.