- Oban.py ist eine auf PostgreSQL basierende Portierung von Elixirs Job-Verarbeitungs-Framework Oban nach Python, mit der Jobs allein über die Datenbank eingefügt und verarbeitet werden können
- Jobs werden innerhalb von Datenbanktransaktionen erstellt und zurückgerollt; unterstützt werden unter anderem Queue-Management, Ergebnisspeicherung und Cron-Scheduling
- Die Open-Source-Version hat Einschränkungen wie Single-Thread-
asyncio-Ausführung und individuelles Einfügen und Bestätigen, während die Pro-Version Parallelverarbeitung, Workflows und Smart Concurrency bietet - Der interne Ablauf besteht aus fünf Schritten:
Insert → Notify → Fetch → Execute → Ack; dabei wird PostgreSQLsFOR UPDATE SKIP LOCKEDgenutzt, um Konflikte bei der Parallelität zu vermeiden - Leader Election, Wiederherstellung verwaister Jobs und Backoff-Retries werden ebenfalls datenbankbasiert ausgeführt und ermöglichen so eine stabile verteilte Verarbeitung ohne externen Broker
Überblick über Oban.py
- Oban.py ist eine nach Python portierte, datenbankbasierte Job-Verarbeitungs-Framework-Variante von Elixirs Oban
- Jobs werden innerhalb von Datenbanktransaktionen eingefügt und verarbeitet; bei Fehlern wird die gesamte Transaktion zurückgerollt
- Enthält verschiedene Steuerungsfunktionen wie Queue-Limits, Speicherung abgeschlossener Jobs, Ergebnisaufbewahrung und Cron-Scheduling
- Es gibt zwei Versionen
- Open Source (OSS): Single-Thread-
asyncio-Ausführung, individuelles Einfügen und Bestätigen, einfache Wiederherstellung - Pro-Version: Parallelverarbeitung auf Basis eines Prozesspools, Unterstützung für Workflows, Relays, Unique Jobs und Smart Concurrency
- Open Source (OSS): Single-Thread-
- OSS eignet sich für persönliche Projekte oder Evaluierungen, für große Umgebungen wird die Pro-Version empfohlen
Pfad der Job-Verarbeitung
- Nach dem Einfügen werden Jobs in der Tabelle
oban_jobsmitstate='available'gespeichert, und per PostgreSQL-NOTIFY wird eine Benachrichtigung an jeden Knoten gesendet - Der Stager jedes Knotens erkennt die betreffende Queue und weckt den Producer auf, der die Jobs abholt und ausführt
- Bei der Job-Auswahl wird SQLs
FOR UPDATE SKIP LOCKEDverwendet, um parallele Verarbeitung ohne doppelte Ausführung zu ermöglichen- Bereits gesperrte Zeilen werden übersprungen, sodass andere Producer sofort andere Jobs holen können
- Jobs werden als Async-Tasks dispatcht; nach Abschluss erfolgt die Acknowledgement-Verarbeitung per Callback
- Die Pro-Version verwendet statt
asyncioeinen Prozesspool-Dispatcher und unterstützt so parallele Ausführung über mehrere CPU-Kerne
Hintergrundprozesse
- Leader Election
- Der Leader wird über PostgreSQLs
INSERT ... ON CONFLICTund ein lease-basiertes TTL-Verfahren bestimmt - Ohne separates Konsensprotokoll übernimmt ein einzelner Leader das Aufräumen und Wiederherstellen von Jobs
- Der Leader wird über PostgreSQLs
- Lifeline (Wiederherstellung verwaister Jobs)
- Läuft ein Job länger als eine bestimmte Zeit (
rescue_after, standardmäßig 5 Minuten), wird er in den Status available zurückversetzt - Die Pro-Version prüft, ob der Producer noch lebt, OSS entscheidet nur zeitbasiert
- Läuft ein Job länger als eine bestimmte Zeit (
- Pruner (Job-Bereinigung)
- Löscht abgeschlossene, abgebrochene oder verworfene Jobs, deren
max_age(standardmäßig 1 Tag) überschritten ist - Begrenzt den Löschumfang mit
LIMIT, um Datenbanklast zu vermeiden
- Löscht abgeschlossene, abgebrochene oder verworfene Jobs, deren
Retries und Backoff
- Wenn ein Job eine Ausnahme auslöst, entscheidet der Executor, ob ein Retry erfolgt
- Liegt die Zahl unter der maximalen Anzahl an Versuchen (
max_attempts), wird erneut versucht, andernfalls wird der Job verworfen
- Liegt die Zahl unter der maximalen Anzahl an Versuchen (
- Der Standard-Backoff ist ein exponentieller Anstieg mit Jitter
- Verhindert bei vielen Fehlschlägen gleichzeitige Retries und mildert so Lastspitzen durch den Thundering Herd
- Beispiel: ca. 17 Sekunden beim 1. Versuch, ca. 47 Sekunden beim 5. Versuch, ca. 17 Minuten beim 10. Versuch
- Worker-Klassen können über die Methode
backoff()eine benutzerdefinierte Backoff-Logik implementieren
Wichtige Merkmale und Bewertung
- PostgreSQL übernimmt die Schlüsselrolle
- Über
FOR UPDATE SKIP LOCKED,LISTEN/NOTIFYundON CONFLICTwerden Parallelitätskontrolle, Signalisierung und Leader Election vollständig umgesetzt - Statt Redis oder eines externen Brokers wird eine einzelne Datenbank als Koordinationsschicht genutzt
- Über
- Nicht parallel, aber nebenläufig
- Auf
asynciobasierend und damit gut für I/O-bound Workloads geeignet; für CPU-bound Aufgaben wird die Pro-Version empfohlen
- Auf
- Klare Code-Struktur
- Konsistente Benennung und sauber getrennte Verantwortlichkeiten sorgen für eine gut lesbare Codebasis
- Klare Rollentrennung zwischen OSS und Pro
- OSS für Experimente und kleinere Einsätze, Pro für große Umgebungen und hohe Performance
- Fazit: Eine saubere und strukturiert umgesetzte Python-Portierung, die allein mit PostgreSQL eine vollständige Job-Queue implementiert, und geeignet ist für Elixir-Nutzer oder Entwickler, die ein Job-System ohne externe Infrastruktur suchen
1 Kommentare
Hacker-News-Kommentare
Ich bin der Entwickler von Sidekiq und gratuliere Shannon und Parker zu ihrem Release.
Früher stand ich vor derselben Frage — mich auf Ruby konzentrieren oder Sidekiq auf andere Sprachen ausweiten. Ich habe erkannt, dass ich unmöglich Experte für jede Sprache sein kann, und stattdessen Faktory gebaut. Dabei verwaltet ein zentraler Server den Lebenszyklus der Queue, während die Clients pro Sprache einfach bleiben. Es gibt zum Beispiel Clients wie faktory-rs. Der Nachteil ist, dass man sich nicht auf eine bestimmte Sprach-Community konzentriert und deshalb schwerer sprachspezifische Beispiele liefern kann.
Ein fokussierter Ansatz auf eine einzelne Community könnte am Ende bessere Ergebnisse liefern. Die Zeit wird es zeigen
Der Kern von Oban ist, dass Jobs nur mit der Datenbank eingefügt und verarbeitet werden können. Man kann innerhalb einer Transaktion zur Benutzererstellung auch gleich einen E-Mail-Versand-Job einfügen, und wenn etwas fehlschlägt, wird alles zusammen zurückgerollt.
Viele sagen, man solle keine relationale DB als Job-Queue verwenden, übersehen dabei aber die Bedeutung von Transaktionen. Brandur Leachs Artikel Job Drain erklärt dieses Konzept ebenfalls sehr gut
Inzwischen erinnert sich aber niemand mehr an diesen Schmerz. Das „transactional outbox pattern“ ist unverzichtbar, und ich bevorzuge einen Ansatz mit denselben ACID-Garantien wie meine Daten.
Selbst ohne tiefes DB-Interna-Wissen spart eine Woche, die man in Isolation Levels und Commit-Reihenfolge investiert, einem ein Jahr Debugging in verteilten Systemen
In einer Zeit mit vielen langen KI-Prozessen ist solche Durability unverzichtbar. In anderen Sprachökosystemen zahlt man für so etwas, bei Oban ist es standardmäßig dabei
Das Oban-Team ist im Elixir-Ökosystem für ausgereifte Engineering-Arbeit bekannt. Es ist aber verwirrend, dass sie im Pro-Modell den Prozess-Pool eingeschränkt haben.
Der Tarif für 135 $/Monat enthält zum Beispiel Multi-Process-Ausführung, Workflows, globale Limits, Unique Jobs, Bulk-Jobs, verschlüsselte Quellen und dedizierten Support.
Mein Projekt Chancy ist komplett kostenlos, und man kann asyncio, Prozesse, Threads und Sub-Interpreter frei kombinieren.
Ich frage mich, ob es nicht besser wäre, solche Funktionen ins OSS zu übernehmen und das kostenpflichtige Angebot stärker auf Enterprise-Support auszurichten. Im Python-Ökosystem gibt es deutlich mehr Konkurrenz
Ein Modell, das nur Support verkauft, hat bei uns nicht besonders gut funktioniert, aber in Python könnte das anders sein.
Im Python-Ökosystem gibt es wirklich alles in Hülle und Fülle
Wenn du Support für Django Tasks in Chancy ergänzen oder ein Paket
django-chancybauen würdest, könnte das schnell Akzeptanz findenOSS Oban unterstützt nur single-threaded asyncio execution, daher blockieren CPU-bound Jobs den Event Loop.
Deshalb hatte ich das Gefühl, dass es einen Versuch nicht wert ist. Das Celery-Interface gefällt mir zwar nicht besonders, aber es ist vertraut und lässt sich vertikal wie horizontal praktisch unbegrenzt skalieren.
Allerdings hat sich meine Meinung etwas geändert, seit ich weiß, dass man mehrere Worker-Nodes starten kann
Die Trennung zwischen OSS- und Pro-Funktionen ist okay, aber „Die Pro-Version verfolgt das Überleben des Producers mit intelligenteren Heartbeats“ ist enttäuschend.
Wenn zuverlässigkeitsrelevante Funktionen kostenpflichtig sind, wird das die Einführung in OSS-Projekten erschweren
Die Basisversion sollte die beste sein, und Zusatzfunktionen sollten kostenpflichtig sein. Die Grenze wirkt an einer etwas seltsamen Stelle gezogen
Der zitierte Satz ist leicht ungenau — das Tracking des Producer-Lebenszyklus ist identisch, der Unterschied liegt nur in der Wiederherstellung verwaister Jobs
Ich wünschte, Python-Workflows für BI/ML/DS würden nach Elixir wechseln.
Ich halte Elixir mit seiner funktionalen, fehlertoleranten und hochgradig nebenläufigen Natur für eine viel natürlichere Grundlage für solche Aufgaben
Dieses Video und der Elixir-Genius-Guide sind gute Referenzen
Wir verwenden bei uns ebenfalls Celery, und so toll ist es nicht. Temporal ist zu schwergewichtig, Oban wirkt dagegen leicht und sympathisch.
Mich würde ein Vergleich von jemandem interessieren, der beides verwendet hat
Temporal passt zu Organisationen, die Workflow-Garantien brauchen und die Komplexität akzeptieren können, etwa Banken.
Oban ist eine DB-basierte Queue, deren Zuverlässigkeit man selbst weiter absichern muss.
Ich finde, beide haben ihren Platz, sogar im selben System
Wir nutzen eine Kombination aus einfachem ProcessWorker und ECS-Workern
Ich frage mich, ob Celery in letzter Zeit weniger stabil oder schwieriger zu handhaben geworden ist
Interessantes Projekt. Auffällig ist nur, dass einige Kernfunktionen nur in Pro enthalten sind.
Frühere Projekte, die Postgres-basierte Durable Workflows als OSS umgesetzt haben, sind DBOS und Absurd.
Es ist erfreulich, mehr datenbankzentrierte Ansätze zu sehen
Ein vollständig Open-Source-basiertes Modell, das sich allein über Support-Verkauf trägt, ist ein Traummodell. Hoffentlich ist es irgendwann möglich
Ich frage mich, ob Postgres genug Performance hat, um Hunderte Millionen Jobs zu verarbeiten. Früher habe ich beim Wechsel auf Redis + Sidekiq große Leistungsgewinne gesehen
Bei der OSS-Version heißt es, lange laufende Jobs könnten fälschlich wiederhergestellt werden, selbst wenn der Producer noch lebt.
Bedeutet das, dass man nur kurze Jobs verwenden sollte?
Das Timing der Wiederherstellung unterscheidet sich nur in Fällen, in denen nicht auf ein normales Herunterfahren gewartet werden konnte