Tarefas agendadas desaparecem intermitentemente entre o zset de agendamento do Sidekiq e a execução (sem execução, sem nova tentativa, sem rastreamento)

Discourse 2026.10.0-latest.1 / Sidekiq 7.3.10 / Ruby 4.0.9

Resumo

Desde a atualização de um fórum auto-hospedado do Discourse 2026.2.0-latest (Ruby 3.3.8) para o 2026.10.0-latest.1 (Ruby 4.0.9, Rails 8.1.4, Sidekiq 7.3.10) em 26/10/2026, trabalhos enfileirados com Jobs.enqueue_in estão sendo perdidos intermitentemente e silenciosamente: o trabalho desaparece do conjunto ordenado schedule no momento de sua execução programada, mas o método perform nunca é executado. Não há nova tentativa, não há trabalho morto, nenhuma exceção registrada (usamos Logster::RedisStore, portanto exceções engolidas são visíveis — não há nenhuma) e stat:failed permanece em 0. Taxa de perda observada em 9 ocorrências controladas/reais: 4 perdidos / 5 executados (~40 %).

Ambiente

  • Instalação oficial via docker (./launcher rebuild), único site
  • Discourse 2026.10.0-latest.1, imagem 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 supervisionado por Demon::Sidekiq, bifurcado pelo pitchfork)
  • Estado do Sidekiq em um banco de dados Redis dedicado (Discourse.sidekiq_redis_config), separado do Redis do Discourse
  • Processo do Sidekiq saudável durante todas as observações: 0 reinicializações, [0 de 5 ocupados], filas vazias, retry = 0, dead = 0, stat:failed = 0
  • Sem OOM, sem eventos no dmesg, sem reinicializações do contêiner

Observações (30/10/2026, caso real + experimentos controlados)

O trabalho afetado é Jobs::NotifyMailingListSubscribers, enfileirado com Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes, ...) (600 s) → conjunto ordenado schedule com score = momento da execução.

Momento da execução (UTC) Trabalho Resultado
15:33:05 real (post 282) perdido — desapareceu de schedule, nunca executado
15:50:10 controle (post_id 999999 → retorno silencioso antecipado) executado (+1 stat:processed ~8 s após o momento programado)
16:04:18 real (post 283) executado (erros SMTP gravados, ver abaixo)
16:21:44 real ×2 + controle todos executados
17:34:39 real ×2 perdidos ×2
17:42:39 controle executado (+1, 11 s após o momento programado)
17:55:59 real perdido

Evidências reunidas para cada trabalho “perdido”:

  • zrange schedule não contém mais a carga útil (o zrem ocorreu), e ela não está presente nem em retry, nem em dead, nem em qualquer fila (queue:low etc. todas com 0), nem atribuída a stat:processed.
  • Um observador pg_stat_activity amostrando a cada 300 ms durante a janela de execução programada não registrou nenhuma consulta SQL vinda da conexão do Sidekiq — nem mesmo a consulta inicial SELECT ... FROM posts que execute realiza primeiro.
  • O Logster::RedisStore (__LOGSTER__MAP) não contém novas entradas para esses trabalhos, enquanto as entradas SÃO gravadas para as execuções executadas (falhas SMTP de um problema diferente e não relacionado que corrigimos em paralelo).
  • O proctitle do Sidekiq permanece [0 de 5 ocupados]; sem reinicialização do Sidekiq (mesmo PID desde a inicialização), sem OOM.
  • A contagem de SkippedEmailLog permaneceu inalterada para os trabalhos perdidos (as execuções executadas e com falha realmente gravam linhas lá).

As execuções “executadas” são positivamente confirmadas: elas produziram entradas de erro SMTP por usuário (capturadas em SkippedEmailLog.custom_reason), portanto, a classe do trabalho e a forma da carga útil são idênticas entre as ocorrências perdidas e executadas (mesma classe, mesma fila low, apenas post_id difere).

A instalação anterior (Discourse 2026.2.0-latest, Ruby 3.3.8) não exibiu esse comportamento: os mesmos trabalhos de lista de e-mail agendados foram entregues de forma confiável em 01/10/2026 e 21/10/2026 (verificado via EmailLog + filas do MTA a jusante).

Mitigação aplicada

Definimos email_time_window_mins = 0 para este site para que enqueue_in(0) ignore a chave at e envie o trabalho diretamente para sua fila (client_push sem at), evitando completamente o poller agendado. Após essa alteração, todas as 6 linhas esperadas de EmailLog foram criadas imediatamente e os e-mails foram aceitos pelo MTA a jusante.

Concessão aceita: as notificações de listas de discussão são agora enviadas imediatamente em vez de serem agrupadas com +10 min.

Perguntas

  1. Esta é uma regressão conhecida na combinação 2026.10 / Sidekiq 7.3.10 (ou em Demon::Sidekiq / o poller agendado sob Ruby 4.0)?
  2. Quais diagnósticos adicionais vocês gostariam que capturássemos na próxima ocorrência (por exemplo, habilitar a instrumentação de trabalhos DISCOURSE_LOG_SIDEKIQ=1, backtraces de exceções do Sidekiq, dumps de threads do poller)?
2 Curtiram