# Sidekiq 调度 zset 与执行之间间歇性丢失延迟任务（无执行、无重试、无记录）

**URL:** <https://meta.discourse.org/t/delayed-jobs-are-intermittently-lost-between-the-sidekiq-schedule-zset-and-execution-no-perform-no-retry-no-trace/416214>\
**Category:** Bug\
**Created:** [2026 年10 月 30 日 19:42 UTC](https://meta.discourse.org/t/delayed-jobs-are-intermittently-lost-between-the-sidekiq-schedule-zset-and-execution-no-perform-no-retry-no-trace/416214 "2026-10-30T19:42:07Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![StephaneBRT](https://avatars.discourse-cdn.com/v4/letter/s/9f8e36/32.png) [@StephaneBRT](https://meta.discourse.org/u/StephaneBRT)\
发布日期： [2026 年10 月 30 日 19:42 UTC](https://meta.discourse.org/t/delayed-jobs-are-intermittently-lost-between-the-sidekiq-schedule-zset-and-execution-no-perform-no-retry-no-trace/416214/1 "2026-10-30T19:42:07Z")

</div>

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

### 概述

自2026年10月26日将自建论坛从 Discourse 2026.2.0-latest（Ruby 3.3.8）升级至 2026.10.0-latest.1（Ruby 4.0.9、Rails 8.1.4、Sidekiq 7.3.10）以来，通过 `Jobs.enqueue_in` 入队的任务偶尔会静默丢失：任务在到期时从 `schedule` zset 中消失，但 `perform` 从未执行。没有重试，没有死信任务，没有记录的异常（我们使用 `Logster::RedisStore`，因此被吞没的异常是可见的——但确实没有），并且 `stat:failed` 保持为 0。在9次受控/真实案例中，观察到的丢失率为：4次丢失 / 5次执行（~40%）。

### 环境

- 官方 docker 安装（`./launcher rebuild`），单站点
- Discourse `2026.10.0-latest.1`，镜像 `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 由 `Demon::Sidekiq` 监督，由 pitchfork fork）
- Sidekiq 状态保存在专用的 Redis 数据库中（`Discourse.sidekiq_redis_config`），与 Discourse redis 分离
- 在所有观察期间 Sidekiq 进程健康：0 次重启，`[0 of 5 busy]`，队列空，`retry = 0`，`dead = 0`，`stat:failed = 0`
- 无 OOM，无 dmesg 事件，无容器重启

### 观察（2026年10月30日，真实案例 + 受控实验）

受影响的任务是 `Jobs::NotifyMailingListSubscribers`，通过 `Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes, ...)` 入队（600秒）→ `schedule` zset，score = 到期时间。

| 到期时间 (UTC) | 任务 | 结果 |
| --- | --- | --- |
| 15:33:05 | 真实 (帖子 282) | 丢失 — 从 `schedule` 消失，从未执行 |
| 15:50:10 | 控制 (post\_id 999999 → 静默提前返回) | 执行（在到期后 ~8 秒增加 1 个 `stat:processed`） |
| 16:04:18 | 真实 (帖子 283) | 执行（SMTP 错误已写入，见下文） |
| 16:21:44 | 真实 ×2 + 控制 | 全部执行 |
| 17:34:39 | 真实 ×2 | 丢失 ×2 |
| 17:42:39 | 控制 | 执行（+1，在到期后 11 秒） |
| 17:55:59 | 真实 | 丢失 |

对于每个“丢失”的任务收集的证据：

- `zrange schedule` 不再包含该载荷（`zrem` 已发生），且它不在 `retry`、`dead` 或任何队列中（`queue:low` 等均为 0），也不在 `stat:processed` 归属中。
- `pg_stat_activity` 监视器在到期窗口期间每 300 ms 采样一次，记录到 Sidekiq 连接的零条 SQL——甚至没有 `execute` 首先执行的初始 `SELECT ... FROM posts`。
- `Logster::RedisStore`（` __LOGSTER__ MAP`）中没有这些任务的新条目，但 **已执行** 的运行确实写入了条目（SMTP 失败来自我们并行修复的另一个无关问题）。
- Sidekiq proctitle 保持 `[0 of 5 busy]`；没有 sidekiq 重启（自启动以来 PID 相同），无 OOM。
- 对于丢失的任务，`SkippedEmailLog` 计数未变（已执行且失败的运行确实会在其中写入行）。

“已执行”的运行得到正面确认：它们生成了每用户 SMTP 错误条目（捕获在 `SkippedEmailLog.custom_reason` 中），因此任务类和载荷形状在丢失和执行的实例之间是相同的（类相同，队列 `low` 相同，仅 `post_id` 不同）。

先前的安装（Discourse 2026.2.0-latest，Ruby 3.3.8）未表现出此行为：相同的延迟邮件列表任务在 2026年10月1日和 2026年10月21日可靠投递（通过 `EmailLog` + 下游 MTA 队列验证）。

### 已应用的缓解措施

我们将该站点的 `email_time_window_mins = 0`，以便 `enqueue_in(0)` 跳过 `at` 键并直接将任务推送到其队列（不带 `at` 的 `client_push`），从而完全绕过调度轮询器。在此更改后，所有 6 个预期的 `EmailLog` 行均立即创建，并且邮件被下游 MTA 接受。

接受的权衡：邮件列表通知现在立即发送，而不是批处理 +10 分钟。

### 问题

1. 这是 2026.10 / Sidekiq 7.3.10 组合（或 Ruby 4.0 下的 `Demon::Sidekiq` / 调度轮询器）中已知的回归吗？
2. 您希望我们在下一次发生时捕获哪些额外的诊断信息（例如启用 `DISCOURSE_LOG_SIDEKIQ=1` 任务插桩、Sidekiq 异常回溯、轮询器线程转储）？
