I job ritardati vengono persi occasionalmente tra lo zset della pianificazione di Sidekiq e l'esecuzione (nessuna esecuzione, nessun ritentativo, nessuna traccia)

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, immagine discourse/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 da Demon::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 schedule non contiene più il payload (lo zrem è avvenuto), e non è presente né in retry, né in dead, né in alcuna coda (queue:low ecc. tutti a 0), né nell’attribuzione stat:processed.
  • Un watcher pg_stat_activity che campiona ogni 300 ms nella finestra di scadenza ha registrato zero query SQL dalla connessione Sidekiq — nemmeno la SELECT ... FROM posts iniziale che execute esegue 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 SkippedEmailLog rimane 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

  1. 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)?
  2. 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)?
3 Mi Piace