3 points par GN⁺ 2026-01-30 | 1 commentaires | Partager sur WhatsApp
  • Oban.py est une version portée en Python, basée sur PostgreSQL, du framework de traitement de jobs Oban d’Elixir, permettant d’insérer et de traiter des jobs uniquement avec la base de données
  • Les jobs sont créés et annulés dans des transactions de base de données, avec la prise en charge de nombreuses fonctions comme la gestion des files, le stockage des résultats et la planification cron
  • La version open source comporte des limites, comme une exécution asyncio monothread et des insertions et confirmations individuelles, tandis que la version Pro offre le traitement parallèle, les workflows et une concurrence intelligente
  • Son fonctionnement interne se compose de cinq étapes : Insert → Notify → Fetch → Execute → Ack, et s’appuie sur FOR UPDATE SKIP LOCKED de PostgreSQL pour éviter les conflits de concurrence
  • L’élection du leader, la récupération des jobs orphelins et les nouvelles tentatives avec backoff sont également gérées via la base de données, ce qui permet un traitement distribué fiable sans broker externe

Présentation d’Oban.py

  • Oban.py est un framework de traitement de jobs basé sur une base de données, issu d’un portage en Python d’Oban pour Elixir
    • Il insère et traite les jobs au sein de transactions de base de données, et en cas d’échec, l’ensemble de la transaction est annulé
    • Il inclut diverses fonctions de contrôle comme les limites de file, la conservation des jobs terminés, la rétention des résultats et la planification cron
  • Deux versions sont proposées
    • Open source (OSS) : exécution asyncio monothread, insertion et confirmation individuelles, récupération simple
    • Version Pro : traitement parallèle basé sur un pool de processus, avec prise en charge des workflows, relais, jobs uniques et concurrence intelligente
  • La version OSS convient aux projets personnels ou à l’évaluation, tandis que la version Pro est recommandée pour les environnements à grande échelle

Chemin de traitement des jobs

  • Après insertion, les jobs sont enregistrés dans la table oban_jobs avec state='available', puis une notification est envoyée à chaque nœud via NOTIFY de PostgreSQL
  • Le Stager de chaque nœud détecte la file concernée, réveille le Producer, puis le Producer récupère et exécute les jobs
  • Lors de la sélection des jobs, l’instruction SQL FOR UPDATE SKIP LOCKED permet un traitement parallèle sans exécution en double
    • Les lignes déjà verrouillées sont ignorées, ce qui permet à d’autres producteurs de récupérer immédiatement d’autres jobs
  • Les jobs sont dispatchés comme async tasks et, une fois terminés, un callback gère l’acknowledgement
  • La version Pro utilise un dispatcher avec pool de processus à la place d’asyncio pour permettre une exécution parallèle sur plusieurs cœurs

Processus d’arrière-plan

  • Élection du leader (Leader Election)
    • Le leader est déterminé via INSERT ... ON CONFLICT de PostgreSQL et un bail (lease) basé sur un TTL
    • Sans protocole de consensus séparé, un leader unique se charge du nettoyage et de la récupération des jobs
  • Lifeline (récupération des jobs orphelins)
    • Si un job en cours d’exécution dépasse une certaine durée (rescue_after, 5 minutes par défaut), il est restauré à l’état available
    • La version Pro vérifie si le Producer est toujours vivant, tandis que l’OSS ne se base que sur le temps écoulé
  • Pruner (nettoyage des jobs)
    • Supprime les jobs terminés, annulés ou abandonnés datant de plus de max_age (1 jour par défaut)
    • Limite la portée de suppression avec LIMIT afin d’éviter une surcharge de la base de données

Nouvelles tentatives et backoff

  • Lorsqu’un job lève une exception, l’Executor décide s’il faut le relancer
    • Si le nombre maximal de tentatives (max_attempts) n’est pas atteint, le job est relancé, sinon il est abandonné
  • Le backoff par défaut suit une croissance exponentielle avec jitter
    • Cela évite les nouvelles tentatives simultanées lors de défaillances massives et atténue les pics de charge (Thundering Herd)
    • Exemple : environ 17 secondes à la 1re tentative, 47 secondes à la 5e, 17 minutes à la 10e
  • Les classes de workers peuvent implémenter une logique de backoff personnalisée via la méthode backoff()

Principales caractéristiques et évaluation

  • PostgreSQL joue un rôle central
    • FOR UPDATE SKIP LOCKED, LISTEN/NOTIFY et ON CONFLICT gèrent ensemble le contrôle de la concurrence, la signalisation et l’élection du leader
    • Une seule base de données suffit à constituer la couche de coordination, sans Redis ni broker externe
  • Pas parallèle, mais compatible avec la concurrence
    • La base asyncio convient bien aux tâches I/O-bound, tandis que la version Pro est recommandée pour les tâches CPU-bound
  • Clarté de la structure du code
    • Une nomenclature cohérente et une séparation nette des responsabilités donnent une base de code facile à lire
  • Répartition claire des rôles entre OSS et Pro
    • L’OSS vise l’expérimentation et les petits déploiements, la version Pro les environnements à grande échelle et à hautes performances
  • Conclusion : un portage Python propre et bien structuré qui implémente une file de jobs complète uniquement avec PostgreSQL, adapté aux utilisateurs d’Elixir comme aux développeurs recherchant un système de jobs sans infrastructure externe

1 commentaires

 
GN⁺ 2026-01-30
Avis sur Hacker News
  • Je suis la personne qui a créé Sidekiq, et je félicite Shannon et Parker pour ce lancement.
    J’ai déjà eu la même réflexion — me concentrer sur Ruby, ou étendre Sidekiq à d’autres langages. J’ai réalisé qu’il était impossible d’être expert de tous les langages, alors j’ai créé Faktory à la place. C’est une architecture où un serveur central gère le cycle de vie de la file, tandis que les clients propres à chaque langage restent simples. Par exemple, il existe un client comme faktory-rs. L’inconvénient, c’est qu’en ne se concentrant pas sur une communauté de langage en particulier, il est difficile de fournir des exemples adaptés à ce langage.
    Il est possible qu’une approche centrée sur une seule communauté donne de meilleurs résultats. Le temps le dira.

    • Merci, Mike ! Tu es une vraie source d’inspiration. Parker et moi avons des points forts différents, et nous croyons à la synergie qui naît de l’interopérabilité entre Python et Elixir.
    • Faktory a été une grande source d’inspiration pour Ocypod, mon système de file de tâches indépendant du langage. Merci de l’avoir publié en open source.
    • En réalité, ne serait-il pas plus exact de dire que les deux sont basés sur Resque ?
    • Ce n’était sans doute pas ton intention, mais ton commentaire donne l’impression de faire la promo de ton projet. Ce n’est généralement pas très bien vu ici.
    • L’expression “based on” me semble un peu exagérée. Sidekiq est bien plus simple si on le compare aux workflows, au cron, au partitionnement, aux tâches dépendantes, à la gestion des échecs, etc. pris en charge par Oban.
  • Le cœur d’Oban, c’est qu’on peut insérer et traiter des tâches uniquement avec la base de données. On peut ajouter une tâche d’envoi d’e-mail dans une transaction de création d’utilisateur, avec rollback complet en cas d’échec.
    Beaucoup de gens disent qu’il ne faut pas utiliser une base relationnelle comme file de tâches, mais ils négligent l’importance des transactions. L’article de Brandur Leach, Job Drain, explique très bien ce concept.

    • Je suis profondément d’accord avec ça. Pendant des années, j’ai vu des événements disparaître à cause du Dual Write Problem. Au final, revenir à une approche fondée sur SQL a résolu le problème en une journée.
      Mais aujourd’hui, plus personne ne se souvient de cette douleur. Le pattern Transactional Outbox est indispensable, et je préfère une approche qui bénéficie des mêmes garanties ACID que mes données.
      Même sans connaître les entrailles d’une base, consacrer une semaine à apprendre les niveaux d’isolation et l’ordre des commits peut faire économiser un an de debug de systèmes distribués.
    • C’est exactement ce qu’on appelle le pattern Transactional Outbox.
    • Moi aussi, je trouve cette fonctionnalité vraiment géniale. Pour ma part, j’utilise pg_timetable.
    • Nous construisons un AI app builder, et nous utilisons Elixir, Phoenix, et bien sûr OBAN.
      À une époque où les longs processus IA sont nombreux, cette durabilité est indispensable. Dans d’autres écosystèmes, il faut payer pour avoir ce genre de fonctionnalités, alors qu’avec Oban c’est inclus par défaut.
    • Je n’avais jamais envisagé les transactions de cette manière, c’est vraiment impressionnant.
  • L’équipe d’Oban est connue dans l’écosystème Elixir pour son ingénierie raffinée. En revanche, le fait d’avoir verrouillé le pool de processus dans la version Pro est déroutant.
    Par exemple, le forfait à 135 $/mois inclut l’exécution multiprocessus, les workflows, les limites globales, les tâches uniques, les opérations en masse, les sources chiffrées et un support dédié.
    Mon projet Chancy est entièrement gratuit, et permet de mélanger librement asyncio, processus, threads et sous-interpréteurs.
    Je me demande s’il ne vaudrait pas mieux faire passer ces fonctionnalités en OSS, et réserver le payant surtout au support entreprise. L’écosystème Python a bien plus de concurrents.

    • Selon l’intérêt et l’usage, certaines fonctionnalités pourraient passer en OSS. Nous l’avons déjà fait dans Elixir.
      Un modèle consistant à ne vendre que du support ne nous a pas vraiment réussi, mais en Python ce sera peut-être différent.
      Dans l’écosystème Python, il y a vraiment de tout en abondance.
    • Pour information, Django 6.0 ajoute la Django Tasks API, qui permet de changer de backend, mais il n’existe pas encore de backend réellement prêt pour la production.
      Ajouter le support Django Tasks à Chancy, ou créer un package django-chancy, permettrait probablement une adoption rapide.
    • Merci d’avoir partagé Chancy. Ça a l’air intéressant. Mis à part les changements d’API, est-ce que ça peut déjà être utilisé de façon stable en production ?
  • La version OSS d’Oban ne prend en charge que l’exécution asyncio mono-thread, donc les tâches CPU-bound bloquent la boucle d’événements.
    C’est pour ça que j’ai eu l’impression que ça ne valait pas la peine d’essayer. L’interface de Celery ne me plaît pas vraiment, mais j’y suis habitué, et on peut faire du scale vertical comme horizontal sans limite.
    Cela dit, savoir qu’on peut lancer plusieurs nœuds worker m’a fait un peu changer d’avis.

  • La distinction OSS/Pro ne me dérange pas, mais le fait de dire que « la version Pro suit la survie des producteurs avec des heartbeats plus intelligents » est regrettable.
    Le fait que des fonctionnalités liées à la fiabilité soient payantes rend l’adoption plus difficile pour les projets OSS.

    • J’ai la même impression. Oban est super, mais c’est dommage que la version payante soit une « meilleure version de la même fonctionnalité ».
      La version de base devrait être la meilleure, et les fonctionnalités additionnelles devraient être payantes. La frontière semble placée à un endroit un peu étrange.
    • Les files basées sur Redis ou RabbitMQ peuvent aussi perdre des tâches après un arrêt inattendu.
      La phrase citée est légèrement inexacte — le suivi de la survie des producteurs est identique, la différence ne porte que sur la méthode de récupération des tâches orphelines.
  • J’aimerais que les workflows BI/ML/DS en Python basculent vers Elixir.
    Je pense qu’Elixir, avec son côté fonctionnel, tolérant aux pannes et fortement concurrent, constitue une base bien plus naturelle pour ce type de travail.

  • Dans notre entreprise, nous utilisons aussi Celery, et ce n’est pas formidable. Temporal est trop lourd, alors qu’Oban est léger et me plaît bien.
    J’aimerais avoir un comparatif de quelqu’un qui a utilisé les deux.

    • Les deux outils ont une philosophie complètement différente.
      Temporal convient aux organisations qui ont besoin de garanties sur les workflows et peuvent assumer cette complexité, comme les banques.
      Oban est une file basée sur une base de données, dont il faut soi-même renforcer la fiabilité.
      À mon avis, il est très bien d’avoir les deux dans un même système.
    • Nous sommes passés de Celery à Prefect et nous en sommes satisfaits. C’est parfait pour traiter des tâches par milliers.
      Nous utilisons une combinaison simple de ProcessWorker et de workers ECS.
    • Je suis revenu récemment au développement web en Python, et j’ai été surpris par le nombre de plaintes à propos de Celery.
      Je me demande si Celery est devenu moins stable ou plus difficile à gérer ces derniers temps.
  • Projet intéressant. Cela dit, le fait qu’une partie des fonctionnalités centrales soit réservée à la version Pro saute aux yeux.
    Parmi les projets antérieurs qui implémentent des workflows durables basés sur Postgres en OSS, on peut citer DBOS et Absurd.
    C’est une bonne chose de voir les approches centrées sur la base de données se multiplier.

    • Il existait aussi un projet OSS similaire dans l’écosystème Elixir, et l’implémentation des workflows durables d’Oban a précédé DBOS de plusieurs années.
      Un modèle entièrement open source, financé uniquement par la vente de support, c’est un modèle de rêve. J’espère qu’un jour ce sera possible.
  • Je me demande si Postgres offre des performances suffisantes pour traiter des centaines de millions de tâches. À une époque, j’ai vu un gros gain de performances en migrant vers Redis + Sidekiq.

    • Je serais curieux de connaître la période concernée. De mon côté, je traite 20 millions de tâches par jour avec Rails/Solid Queue + Postgres sur une VM à 45 $, avec encore beaucoup de marge.
  • Il est dit que dans la version OSS, si une tâche est longue, elle peut être récupérée à tort même si le producteur est toujours vivant.
    Cela veut-il dire qu’il faut se limiter à des tâches courtes ?

    • Il n’y a pas de limite sur la durée des tâches. La différence ne concerne que la vitesse de récupération après un crash.
      Le timing de récupération ne change que lorsqu’on n’a pas pu attendre un arrêt propre.