Discourse 2026.10.0-latest.1 / Sidekiq 7.3.10 / Ruby 4.0.9
Résumé
Depuis la mise à niveau d’un forum auto-hébergé de Discourse 2026.2.0-latest (Ruby 3.3.8) vers 2026.10.0-latest.1 (Ruby 4.0.9, Rails 8.1.4, Sidekiq 7.3.10) le 2026-10-26, les travaux mis en file d’attente via Jobs.enqueue_in sont perdus de manière intermittente et silencieuse : le travail disparaît du jeu trié schedule à son échéance, mais perform ne s’exécute jamais. Il n’y a aucune tentative de nouvelle exécution, aucun travail mort, aucune exception consignée dans les journaux (nous utilisons Logster::RedisStore, donc les exceptions avalées sont visibles — il n’y en a aucune), et stat:failed reste à 0. Taux de perte observé sur 9 occurrences contrôlées/réelles : 4 perdues / 5 exécutées (~40 %).
Environnement
- Installation officielle via Docker (
./launcher rebuild), site unique - Discourse
2026.10.0-latest.1, imagediscourse/base:2.0.20261026-0138 - Ruby 4.0.9 (2026-10-23), Rails 8.1.4, Sidekiq 7.3.10
UNICORN_SIDEKIQS=1(Sidekiq supervisé parDemon::Sidekiq, lancé par pitchfork)- L’état de Sidekiq se trouve dans une base de données Redis dédiée (
Discourse.sidekiq_redis_config), séparée de la Redis de Discourse - Le processus Sidekiq est sain pendant toutes les observations : 0 redémarrages,
[0 of 5 busy], files d’attente vides,retry = 0,dead = 0,stat:failed = 0 - Aucune erreur OOM, aucun événement dmesg, aucun redémarrage de conteneur
Observations (2026-10-30, cas réel + expériences contrôlées)
Le travail affecté est Jobs::NotifyMailingListSubscribers, mis en file d’attente via Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes, ...) (600 s) → jeu trié schedule avec score = échéance.
| Échéance (UTC) | Travail | Résultat |
|---|---|---|
| 15:33:05 | réel (post 282) | perdu — a disparu de schedule, jamais exécuté |
| 15:50:10 | contrôle (post_id 999999 → retour silencieux précoce) | exécuté (+1 stat:processed ~8 s après l’échéance) |
| 16:04:18 | réel (post 283) | exécuté (erreurs SMTP écrites, voir ci-dessous) |
| 16:21:44 | réel ×2 + contrôle | tous exécutés |
| 17:34:39 | réel ×2 | perdus ×2 |
| 17:42:39 | contrôle | exécuté (+1, 11 s après l’échéance) |
| 17:55:59 | réel | perdu |
Preuves recueillies pour chaque travail « perdu » :
zrange schedulene contient plus la charge utile (lezrema eu lieu), et elle n’est présente ni dansretry, ni dansdead, ni dans aucune file d’attente (queue:low, etc. sont toutes à 0), ni dans l’attribution destat:processed.- Un outil de surveillance
pg_stat_activityéchantillonnant toutes les 300 ms pendant la fenêtre d’échéance n’a enregistré aucune requête SQL provenant de la connexion Sidekiq — pas même leSELECT ... FROM postsinitial queexecuteeffectue en premier. Logster::RedisStore(__LOGSTER__MAP) ne contient aucune nouvelle entrée pour ces travaux, tandis que des entrées sont écrites pour les exécutions réussies (échecs SMTP liés à un problème différent et sans rapport, corrigé en parallèle).- Le titre du processus Sidekiq reste
[0 of 5 busy]; aucun redémarrage de Sidekiq (même PID depuis le démarrage), aucune erreur OOM. - Le nombre d’entrées
SkippedEmailLogest inchangé pour les travaux perdus (les exécutions échouant y écrivent bien des lignes).
Les exécutions « réussies » sont confirmées : elles ont généré des entrées d’erreur SMTP par utilisateur (capturées dans SkippedEmailLog.custom_reason), donc la classe de travail et la structure de la charge utile sont identiques entre les occurrences perdues et exécutées (même classe, même file d’attente low, seul le post_id diffère).
L’installation précédente (Discourse 2026.2.0-latest, Ruby 3.3.8) ne présentait pas ce comportement : les mêmes travaux retardés de liste de diffusion ont été livrés de manière fiable le 2026-10-01 et le 2026-10-21 (vérifié via EmailLog + files d’attente MTA en aval).
Mesure d’atténuation appliquée
Nous avons défini email_time_window_mins = 0 pour ce site afin qu’enqueue_in(0) ignore la clé at et pousse le travail directement dans sa file d’attente (client_push sans at), contournant entièrement le poller planifié. Après ce changement, les 6 lignes EmailLog attendues ont été créées immédiatement et les courriels ont été acceptés par le MTA en aval.
Arbitrage accepté : les notifications de liste de diffusion sont désormais envoyées immédiatement au lieu d’être regroupées avec un décalage de +10 min.
Questions
- S’agit-il d’une régression connue dans la combinaison 2026.10 / Sidekiq 7.3.10 (ou dans
Demon::Sidekiq/ le poller planifié sous Ruby 4.0) ? - Quels diagnostics supplémentaires souhaitez-vous que nous recueillions lors de la prochaine occurrence (par exemple, l’activation de l’instrumentation de travail
DISCOURSE_LOG_SIDEKIQ=1, les backtraces d’exceptions Sidekiq, les dumps des threads du poller) ?