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、複数キュー)のパターンです。
デフォルト
- ジョブは浅く保ちます。ジョブ本体はモデルメソッドを呼び出す1行にします:
def perform(card) = card.notify_recipients。 - ジョブを冪等にし、安全に再試行できるようにします(例:ドメイン操作内で
RecordNotUniqueを rescue する)。 - 実際のエラーは明確に失敗させ、エラーを握りつぶすパターンは避けます。
- アプリのデフォルトで
ActiveJob::Base.enqueue_after_transaction_commit = trueを設定します。これにより、ジョブがデータより先に実行される競合を根本から解消できます。 - 新規アプリでは、Redis ベースのキューより Solid Queue(データベースベース)を優先します。小規模なデプロイでは 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に依存しないでください。 - マルチテナントアプリでは、すべてのジョブにテナントのコンテキストをシリアライズします。エンキュー時に
Current.accountを(GlobalID として)取得し、perform_nowをCurrent.with_accountでラップする ApplicationJob 拡張を先頭に追加します。
推奨パターン
- ジョブのペイロードは小さく保ちます。レコード(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 処理(ウェブプッシュ)では、2段階の非同期処理にします。ドメインイベント用のジョブを実行し、その後、インプロセスのスレッドプールで HTTP 呼び出しを行います。プールに投入する前にすべての AR データを解決し、バックプレッシャーが発生した場合は
RejectedExecutionErrorで破棄します。 after_destroy_commitの非同期処理では、before_destroyで必要な関連データのスナップショットを取得します。ジョブの実行時には親レコードが削除されている可能性があります。
定期処理とメンテナンス
- 定期タスクでは専用のジョブクラスではなく、モデルクラスの通常のメソッドを呼び出します(
command: "MagicLink.cleanup")。 - 保持期間を過ぎたデータの一括削除には、スコープに対する
delete_all(stale.delete_all)を使います。古いレコードのコールバックはスキップします。 - 完了済みのキュージョブ、期限切れトークン、古い配信レコードをクリーンアップする処理をスケジュールします。
- cron によるリセットより、使用時のリセットを優先します。スケジュールされたジョブではなく、ドメインメソッド内で確認してリセットします(
spendがreset_if_dueを呼び出す)。 - ベータ環境やステージング環境では、定期スケジュールを必須のものに絞ります。
エラー処理の方針
- 一時的な失敗(タイムアウト、DNS、
Net::SMTPServerBusy)はretry_on ..., wait: :polynomially_longerで再試行します。 - 恒久的な失敗は再試行しません。rescue して、エラークラスやメッセージで分類し、
:infoレベルでログに記録します(想定内のエラーです。宛先不明、メールボックスが満杯など)。成功する可能性のある処理のために、ジョブキューのリソースを確保します。 - 「宛先側で失敗した」(結果を記録してジョブを完了する)場合と、「コード側で例外が発生した」(エラーとしてマークし、再試行のために再度 raise する)場合を区別します。rails-webhooks を参照してください。
- エラー分類を Concern としてパッケージ化し、フレームワークのジョブに include します(
ActionMailer::MailDeliveryJob.include SmtpDeliveryErrorHandling)。
テスト
- 単純なジョブクラスはユニットテストしません。モデルのテストで
perform_enqueued_jobs(only: Mention::CreateJob) { ... }とassert_enqueued_with(job: ...)を使い、非同期動作をテストします。 - ジョブの内部ではなく、観測可能な結果(メンションが作成された、メールが送信された)を検証します。
危険な兆候
- 複雑な分岐やビジネスルールが直接埋め込まれたジョブ。
- ガードのない、冪等でない副作用。
- 恒久的に無効な入力の再試行。
- 必要なレコードがコミットされる前のジョブのエンキュー。
- エンキュー時にシリアライズせず、
perform内でCurrent.user/Current.accountを読み取ること。 perform_all_laterや Continuable による反復処理が適している場面で、ループ内で項目ごとにperform_laterを呼び出すこと。- 使用時にリセットするロジックのほうがシンプルで安全なのに、Cron 形式のリセットジョブを使うこと。
- バックグラウンドジョブによって不要な Turbo ブロードキャストが発生すること(
suppressing_turbo_broadcastsで囲みます)。
コメント
Markdown 対応