Discourse 2026.10.0-latest.1 / Sidekiq 7.3.10 / Ruby 4.0.9
Riepilogo
Dopo l’aggiornamento di un forum self-hosted da Discourse 2026.2.0-latest (Ruby 3.3.8) a 2026.10.0-latest.1 (Ruby 4.0.9, Rails 8.1.4, Sidekiq 7.3.10) avvenuto il 2026-10-26, i job messi in coda con Jobs.enqueue_in vengono persi in modo intermittente e silenzioso: il job scompare dalla zset schedule alla sua ora di scadenza ma perform non viene mai eseguito. Non ci sono tentativi di riprova, job nella dead queue o eccezioni registrate (usiamo Logster::RedisStore, quindi le eccezioni silenziate sono visibili — non ce ne sono), e stat:failed rimane a 0. Il tasso di perdita osservato su 9 casi controllati/reali: 4 persi / 5 eseguiti (~40%).
Ambiente
- Installazione docker ufficiale (
./launcher rebuild), sito singolo - Discourse
2026.10.0-latest.1, immaginediscourse/base:2.0.20261026-0138 - Ruby 4.0.9 (2026-10-23), Rails 8.1.4, Sidekiq 7.3.10
UNICORN_SIDEKIQS=1(sidekiq supervisionato daDemon::Sidekiq, forked da pitchfork)- Lo stato di Sidekiq si trova in un database Redis dedicato (
Discourse.sidekiq_redis_config), separato dal redis di Discourse - Il processo Sidekiq è rimasto sano durante tutte le osservazioni: 0 riavvii,
[0 of 5 busy], code vuote,retry = 0,dead = 0,stat:failed = 0 - Nessun OOM, nessun evento dmesg, nessun riavvio del container
Osservazioni (2026-10-30, caso reale + esperimenti controllati)
Il job interessato è Jobs::NotifyMailingListSubscribers, messo in coda con Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes, ...) (600 s) → zset schedule con punteggio = ora di scadenza.
| Ora di scadenza (UTC) | Job | Esito |
|---|---|---|
| 15:33:05 | reale (post 282) | perso — sparito da schedule, mai eseguito |
| 15:50:10 | di controllo (post_id 999999 → ritorno silenzioso anticipato) | eseguito (+1 stat:processed ~8 s dopo la scadenza) |
| 16:04:18 | reale (post 283) | eseguito (errori SMTP scritti, vedi sotto) |
| 16:21:44 | reale ×2 + di controllo | tutti eseguiti |
| 17:34:39 | reale ×2 | persi ×2 |
| 17:42:39 | di controllo | eseguito (+1, 11 s dopo la scadenza) |
| 17:55:59 | reale | perso |
Evidenze raccolte per ogni job “perso”:
zrange schedulenon contiene più il payload (lozremè avvenuto), e non è presente né inretry, né indead, né in alcuna coda (queue:lowecc. tutti a 0), né nell’attribuzionestat:processed.- Un watcher
pg_stat_activityche campiona ogni 300 ms nella finestra di scadenza ha registrato zero query SQL dalla connessione Sidekiq — nemmeno laSELECT ... FROM postsiniziale cheexecuteesegue per prima. Logster::RedisStore(__LOGSTER__MAP) non contiene nuove voci per questi job, mentre le voci SONO scritte per le esecuzioni riuscite (fallimenti SMTP di un altro problema non correlato che abbiamo risolto in parallelo).- Il proctitle di Sidekiq rimane
[0 of 5 busy]; nessun riavvio di sidekiq (stesso PID dall’avvio), nessun OOM. - Il conteggio
SkippedEmailLogrimane invariato per i job persi (le esecuzioni riuscite ma fallimentari scrivono righe lì).
Le esecuzioni “riuscite” sono confermate positivamente: hanno prodotto voci di errore SMTP per utente (catturate in SkippedEmailLog.custom_reason), quindi la classe del job e la forma del payload sono identiche tra i casi persi e quelli riusciti (stessa classe, stessa coda low, cambia solo post_id).
L’installazione precedente (Discourse 2026.2.0-latest, Ruby 3.3.8) non mostrava questo comportamento: gli stessi job ritardati per la mailing list sono stati consegnati in modo affidabile il 2026-10-01 e il 2026-10-21 (verificato tramite EmailLog + code MTA downstream).
Mitigazione applicata
Abbiamo impostato email_time_window_mins = 0 per questo sito in modo che enqueue_in(0) salti la chiave at e spinga il job direttamente nella sua coda (client_push senza at), bypassando completamente il poller schedulato. Dopo questa modifica, tutte le 6 righe EmailLog previste sono state create immediatamente e le mail sono state accettate dall’MTA downstream.
Compromesso accettato: le notifiche per la mailing list vengono ora inviate immediatamente invece di essere raggruppate dopo +10 minuti.
Domande
- Si tratta di una regressione nota nella combinazione 2026.10 / Sidekiq 7.3.10 (o in
Demon::Sidekiq/ il poller schedulato sotto Ruby 4.0)? - Quali ulteriori diagnosi vorreste che catturassimo alla prossima occorrenza (es. abilitare lo strumento di misurazione job
DISCOURSE_LOG_SIDEKIQ=1, backtrace delle eccezioni Sidekiq, dump dei thread del poller)?