🍪🥷 sidekiq-tasks : La gem qu'on utilise pour piloter nos Rake tasks en production
Temps de lecture : 6 minutes
Bienvenue sur la 45ème édition de Ruby Biscuit.
Vous êtes maintenant 610 abonnés 🥳
Bonne lecture.
Vous lisez Ruby Biscuit, la newsletter Rails de Capsens. S’abonner gratuitement
Chez Capsens, on développe des plateformes Rails pour des clients. En production, personne ne touche aux bases de données directement, ni les devs ni les chefs de projet. Mais il y a toujours des opérations à faire à la main : corriger des incohérences après un bug, lancer un backfill suite à un refactor, déclencher une synchro CRM ponctuelle. Pour ça, on passe par des rake tasks.
Rake, c’est l’outil de scripting de l’écosystème Ruby : on déclare une tâche dans un fichier .rake, on l’exécute en ligne de commande avec rake namespace:ma_task. Rails l’utilise lui-même pour ses commandes d’administration (db:migrate, db:seed...) et nous laisse la même mécanique pour nos propres scripts.
Particularité de notre setup, ce ne sont jamais les devs qui les déclenchent : ce sont les chefs de projet, parfois après le go d’un client, parfois pour traiter un ticket en cours. Ils ont besoin de pouvoir lancer une rake task en autonomie, sans dépendre d’un dev pour le déclenchement.
On avait déjà un système qui leur permettait ça, mais il nous manquait trois choses : savoir quelles tasks existent, savoir comment les lancer correctement (le nom plus les arguments avec les bonnes valeurs concrètes), et comprendre ce qui s’est passé en cas d’échec.
“C’est quoi le nom de la task déjà ?”
Nous avons conçu un intranet qui permet entre autres aux chefs de projet de déclencher une rake task sur n’importe quel environnement. C’est fonctionnel, mais l’architecture est lourde : l’intranet appelle l’API CircleCI, qui provisionne via Terraform un Kubernetes Job éphémère, qui pull l’image, boot Rails, puis exécute la task. Trente à soixante secondes avant que la task commence vraiment à tourner.
Et il y a plusieurs frictions sur l’usage. D’abord, le chef de projet doit connaître le nom exact de la task : reports:generate_monthly, pas report:generate_monthly. Aucune liste, aucune description visible. Avant chaque lancement, même rituel sur Slack :
Et la réponse n’est pas toujours un nom à copier-coller : on transmet souvent une signature avec des placeholders, comme project:repair_broken[project_id, date]. Le piège, c’est que ces placeholders peuvent être pris pour des valeurs littérales et soumis tels quels. Quand la précision manque côté dev, c’est aller-retour Slack pour ajuster, et si la task finit par planter en cours d’exécution, la stack trace est à aller chercher sur CircleCI.
Le vrai pain point n’a jamais été d’avoir accès au lancement. C’était de savoir laquelle, comment la lancer, et ce qui s’est passé quand elle a échoué.
sidekiq-tasks, en pratique
C’est pour ces raisons qu’on a décidé d’en faire une gem, et plus précisément une extension Sidekiq, qui ajoute un onglet “Tasks” dédié au lancement à la demande dans la Web UI.
L’installation :
C’est tout. Un onglet “Tasks” apparaît dans Sidekiq.
Par défaut, il est vide. Aucune task n’est exposée tant qu’on ne l’a pas demandé. On ajoute un magic comment # sidekiq-tasks:enable au-dessus d’une task ou d’un namespace :
Le magic comment au-dessus du namespace active toutes les tasks à l’intérieur. C’est opt-in par défaut : seules les tasks annotées et définies dans /lib apparaissent. Les db:migrate, db:drop et compagnie ne risquent pas de se retrouver dans l’interface.
L’onglet Tasks affiche la liste des tâches avec leurs descriptions et la date de leur dernier lancement, avec un champ de recherche pour les retrouver rapidement :
Les descriptions jouent un rôle important ici. Quand on sait que la desc apparaît dans une interface que les chefs de projet consultent, on fait un effort. On a vu des devs ajouter des desc claires sur des tasks qui n’en avaient jamais eu, préciser le format attendu des arguments, et même utiliser la description comme étiquette : [TEMPORARY] Backfill monthly reports for Q4 2025. Quand c’est sous les yeux de toute l’équipe, le ménage se fait tout seul.
Chaque task est cliquable. On accède à sa fiche avec le formulaire de lancement et l’historique d’exécution complet : qui a lancé, quand, combien de temps, succès ou échec, et le message d’erreur si ça a planté. Plus besoin d’aller fouiller dans CircleCI pour comprendre pourquoi une task n’est pas passée : la stack trace est juste là, au-dessus du formulaire.
Côté arguments, les paramètres déclarés dans la signature Rake ([:month]) sont détectés automatiquement et génèrent des champs dans le formulaire. Le chef de projet voit “month” comme label, lit le format attendu directement dans la description (month: YYYY-MM), il remplit, il lance. Plus besoin de transmettre une signature avec des placeholders sur Slack.
Bonus : comme la task est exécutée par les workers Sidekiq déjà up, le lancement est immédiat. On passe de trente secondes de cold-start à quelques millisecondes.
Un champ de confirmation oblige à retaper le nom de l’environnement courant avant de valider, ce qui évite les accidents.
Sous le capot
L’architecture repose sur trois concepts qui rendent la gem extensible.
Une Strategy est un adapter qui définit comment les tâches sont découvertes et exécutées. Par défaut c’est RakeTask : on récupère la liste des tâches Rake chargées, on extrait leurs descriptions et leurs arguments, et au moment du lancement on enqueue un job Sidekiq qui exécute la tâche dans un worker. Concrètement, on hérite de tout l’écosystème Sidekiq : la queue, les workers, les retries, le monitoring, et le fait qu’on ne bloque jamais le process web pendant qu’une rake task tourne.
Si Rake ne suffit pas, on écrit sa propre Strategy. Imaginons par exemple qu’on veuille exposer dans l’onglet Tasks des scripts SQL versionnés dans db/sql_scripts/*.sql :
Trois méthodes, et n’importe quel fichier .sql dans db/sql_scripts/ apparaît dans Sidekiq, exécutable en un clic. La stratégie RakeTask par défaut suit exactement le même contrat, en s’appuyant sur l’API de Rake (code source).
Une Strategy s’instancie avec une liste de Rules qu’on lui injecte. Une Rule, c’est un filtre booléen : sa méthode respected?(task) répond true ou false, et la Strategy ne garde que les tasks pour lesquelles toutes les Rules répondent true (elles sont cumulables). Par défaut, deux règles s’appliquent : TaskFromLib (seules les tasks définies dans /lib, pas celles de Rails ou des gems) et EnableWithComment (seules celles annotées avec le magic comment). On peut remplacer la seconde par DisableWithComment pour un mode “tout sauf”, ou injecter ses propres rules depuis l’initializer :
Le Storage gère la persistance de l’historique. Redis par défaut, parce que Sidekiq tourne déjà avec, donc rien à configurer. Mais swappable avec ActiveRecord ou autre, si on a besoin d’un historique long terme avec des requêtes plus riches.
Pour la config avancée (authorization, current_user, custom strategies), voir le README.
Le petit caillou dans la chaussure
Un mot sur un aspect invisible côté utilisateur : la compatibilité. L’API d’enregistrement d’extensions Web a changé trois fois entre Sidekiq 6.5 et 8.0 :
S’ajoutent la séparation url_params / route_params en 8.0, et une CSP stricte qui bloque les scripts inline depuis la 7.3. Chaque variation est isolée derrière un helper if Sidekiq::Tasks::Web::SIDEKIQ_GTE_X_Y, parce qu’on veut que la gem s’installe sans condition sur n’importe quelle app Sidekiq de l’écosystème, et qu’on s’engage à la maintenir au fil des futures évolutions de l’API.
Ce que ça a changé
Les chefs de projet sont autonomes. Plus de Slack pour demander un nom de task, deviner les bonnes valeurs d’arguments, ou aller fouiller dans les logs CircleCI quand ça plante. Et avec l’historique dans l’interface, on sait toujours qui a lancé quoi, avec quels arguments. Cerise sur le gâteau, le lancement est immédiat au lieu de prendre une minute.
Pour finir
La gem est open source, compatible Ruby 3.0+ et Sidekiq 6.5 à 8.x. bundle add sidekiq-tasks, deux lignes dans les routes, cinq minutes. Le code est sur GitHub.
Prenez soin de vos chefs de projet (et de vos rake tasks) !
— Victor, CTO chez Capsens








