- 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
Aucun commentaire pour le moment.