关于 rails-jobs
rails-jobs 是用于设计和审查 Rails 后台任务的技能,涵盖安全重试、队列安排、周期任务、错误处理与测试规范。
主要功能
- 01浅层且幂等的任务设计
- 02队列与负载建议
- 03重试和错误处理模式
- 04周期任务与批量处理模式
- 05异步行为测试建议
适用场景
- 01审查任务的重试安全性
- 02设计队列和租户上下文处理
- 03规划长时间运行或周期性工作
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marckohlbrugge/ 37signals-skills --skill rails-jobs安装后在 Claude Code 中输入/rails-jobs确认已加载。
详细介绍
Rails 任务
用于后台任务设计和评审。模式来自 Campfire(Resque、4 个轻量任务)和 Fizzy(Solid Queue、多队列)。
默认设置
- 保持任务精简;任务主体只写一行,调用模型方法:
def perform(card) = card.notify_recipients。 - 让任务具备幂等性并可安全重试(例如,在领域操作中捕获
RecordNotUnique)。 - 遇到真正的错误时明确失败;避免静默捕获错误的模式。
- 在应用默认设置中设置
ActiveJob::Base.enqueue_after_transaction_commit = true——从根本上解决任务先于数据提交的竞态问题。 - 新应用优先使用 Solid Queue(基于数据库),而非基于 Redis 的队列;在小型部署中与 Puma 同机运行(
SOLID_QUEUE_IN_PUMA)。
命名约定
_later用于入队,普通名称用于执行工作,需要显式同步版本时使用_now:
def notify_recipients # the work — public API the job calls
def notify_recipients_later # enqueues NotifyRecipientsJob (often private, called from callbacks)
- 将入队时的上下文捕获为关键字参数(
Mention::CreateJob.perform_later(self, mentioner: Current.user))——不要依赖执行任务时的Current。 - 在多租户应用中,将租户上下文序列化到每个任务中:为 ApplicationJob 添加扩展,在入队时捕获
Current.account(作为 GlobalID),并在perform_now中使用Current.with_account包装执行过程。
良好模式
- 使用精简的任务负载;传递记录(GlobalID)或 ID,绝不传递庞大的对象图。
- 按重要性拆分队列(
default、backend、webhooks),并在队列配置中明确优先级顺序。 - 将编排任务与繁重的处理任务分开。
- 将定时任务错开安排在不规则的分钟数(12、27、50),避免负载峰值同步出现。
- 批量入队:
due.in_batches { |batch| ActiveJob.perform_all_later(batch.map { DeliverJob.new(it) }) }。 - 使用
ActiveJob::Continuable实现崩溃安全的长时间迭代:step :dispatch+find_each(start: step.cursor)+step.advance!——对于扇出任务(webhook、广播、回填)至关重要。 - 使用
limits_concurrency to: 1, key: ->(owner) { owner }按所有者串行处理工作(Solid Queue)。 - 对于高扇出 HTTP 请求(Web Push),采用两级异步处理:先用一个任务处理领域事件,再由进程内线程池处理 HTTP 调用。将所有 AR 数据解析完毕后再提交到线程池;遇到
RejectedExecutionError时丢弃请求,以实现背压。 - 对于
after_destroy_commit异步工作,在before_destroy中快照所需关联数据——任务运行时父级记录可能已被删除。
定时与维护
- 定时任务调用普通模型类方法(
command: "MagicLink.cleanup"),而非专用任务类。 - 清理过期数据时,对作用域使用
delete_all(stale.delete_all)——跳过过期记录的回调。 - 安排清理已完成的队列任务、过期令牌和旧投递记录。
- 优先采用使用时重置,而非定时重置:在领域方法中检查并重置(
spend调用reset_if_due),而不是安排定时任务。 - 在 beta/staging 环境中,将定时任务精简到必要项目。
错误处理策略
- 使用
retry_on ..., wait: :polynomially_longer重试暂时性故障(超时、DNS、Net::SMTPServerBusy)。 - 不要重试永久性故障:捕获错误,按错误类或消息分类,以
:info严重级别记录日志(这是预期情况——地址无效、邮箱已满),然后继续处理。将队列资源留给能够成功的工作。 - 区分“目标端失败”(记录结果并完成任务)和“我们的代码引发异常”(标记为错误,并重新引发以便重试)——参见 rails-webhooks。
- 将错误分类封装为 concern,并引入框架任务(
ActionMailer::MailDeliveryJob.include SmtpDeliveryErrorHandling)。
测试
- 不要对简单的任务类进行单元测试;在模型测试中使用
perform_enqueued_jobs(only: Mention::CreateJob) { ... }和assert_enqueued_with(job: ...)测试异步行为。 - 验证可观察到的结果(已创建提及、已发送邮件),而非任务内部实现。
危险信号
- 作业包含复杂的分支逻辑和直接嵌入的业务规则。
- 未设置保护措施的非幂等副作用。
- 重试永久无效的输入。
- 在必需记录提交前就将作业加入队列。
- 在
perform中读取Current.user/Current.account,却未在入队时对其进行序列化。 - 在循环中逐项调用
perform_later,而使用perform_all_later或 Continuable 迭代会更合适。 - 使用 Cron 风格的重置作业,而按需重置逻辑更简单、更安全。
- 后台作业触发多余的 Turbo 广播(请用
suppressing_turbo_broadcasts包裹)。
评论
支持 Markdown