task-scheduling について
ジョブの実行時刻と担当ワーカーを決めるためのアーキテクチャ設計スキルです。スケジュール、遅延実行、リース、優先度、公平性、リトライ、タスク単位の冪等性を扱い、信頼性の高いバックグラウンド処理を支援します。
主な機能
- 01Cron、遅延、定期実行のトリガー
- 02プル型ワーカーレーシング
- 03優先度と公平なスケジューリング
- 04リース、ハートビート、重複排除の指針
- 05滞留と障害の監視
活用シーン
- 01リマインダーや定期ジョブのスケジュール
- 02キュー内タスクのワーカー間割り当て
- 03ワーカー障害後の重複処理防止
- 04優先度とテナント間の公平性管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill task-schedulingインストール後、Claude Code で/task-schedulingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.mdtemporal.md詳細
タスクスケジューリング
作業を「いつ」実行し、「どのワーカー」が実行するかを決めます。スケジュール(cron/遅延/定期実行)でジョブを起動し、リースを通じて各ジョブを必ず1つのワーカーに割り当て、クラッシュやリトライがあっても一度だけ完了するようにします。これはmessaging-streamingのキューの上に成り立ちます。キューはトランスポートであり、このスキルではスケジューリング、リース、優先度、タスク単位の冪等性を追加します。誤ると、ジョブが実行されない、二度実行される(二重請求、二重メール)、またはワーカー群が恒常的に処理しきれなくなるまでジョブが滞留するといった問題が発生します。
どんな場合に使うか
作業を後で実行する(24時間後にリマインダーを送る)、スケジュールに従って実行する(毎晩の集計、毎時のcron)、または繰り返し実行する(5分ごとにポーリング)必要がある場合。時間のかかる処理をすでにリクエスト処理から切り離してあり(→ messaging-streaming)、今度はワーカープールに確実に割り当てる必要がある場合。ジョブに優先度(有料ユーザーを無料ユーザーより優先)や公平性(単一テナントが他のテナントを締め出さない)が必要な場合。または、ジョブを保持するワーカーが処理中にクラッシュする可能性があっても、ジョブを一度だけ完了させる必要がある場合。
使わない場合
呼び出し元が結果をその場で必要とする場合、これは同期呼び出しであり、スケジュールされたジョブではありません。スケジュール、優先度、一度だけの実行が不要な、単発のファイア・アンド・フォーゲット型の非同期ステップには、通常のキューと冪等なコンシューマー(messaging-streaming)のほうがシンプルです。スケジューラーを追加しないでください。1台のマシンで1つの定期ジョブを実行するだけなら、複数のスケジューラーが必要になったり、履歴やリトライが必要になったりするまでは、OSのcronで十分です。ロールバックを伴う長時間の複数ステップのサガには、ジョブ間で状態を手作業で管理するのではなく、永続的なワークフローエンジンを使ってください。「いずれバッチジョブが必要になるから」とAirflow/Celeryを導入しないでください(YAGNI)。運用と監視が必要なステートフルなコントロールプレーンだからです。
まず確認すること
- トリガーの種類 — スケジュール実行(cron/指定時刻)、遅延実行(N秒後に実行)、定期実行(Nごと)、イベント駆動型(キューメッセージの到着)のどれですか?これにより、そもそもスケジューラーが対象となるかが決まります。
- 一度だけの実行か、少なくとも1回の実行か — 重複実行は有害ですか(金銭、メール)、それとも問題ありませんか(冪等な再計算)?リースと重複排除の設計に影響します。
- レイテンシの許容範囲とスループット — 遅延ジョブは指定時刻から数秒以内に起動する必要がありますか、それとも「数分以内」で十分ですか?タイミングを厳密にするとコストが高くなります。
- 優先度/公平性 — 一部のジョブを優先し、特定のテナントやジョブクラスが他を締め出さないようにする必要がありますか?(→
back-of-the-envelopeで到着率と処理率を確認してください。) - ジョブの所要時間とばらつき — 数秒ですか、それとも数時間ですか?これにより、可視性タイムアウト/リース期間や、長時間ジョブでハートビートが必要かどうかが決まります。
- 冪等性キー — リトライ時に、あるタスクを同一のタスクとして識別するものは何ですか?(キーの契約は
api-designが担います。)
選択肢
スケジューリングのトリガー
- OS cron / 単一スケジューラー — 1 つのプロセスが crontab のジョブを実行します。次の場合に使用します:ノードが 1 台で、ジョブが少数であり、高可用性が不要な場合。
- 分散スケジューラー(HA cron) — リーダー選出されたスケジューラーが、実行時刻になったジョブをキューに登録し、フォロワーは待機します。次の場合に使用します:ノード障害に耐え、ジョブが二重に実行されないようにする必要がある場合。
- 遅延キュー / タイマー — ジョブに「この時刻より前には実行しない」という時刻を設定し、実行時刻までキューに保持します(配信遅延、ソート済みセットのスコア、またはタイマーホイールを使用)。次の場合に使用します:ジョブごとに遅延時間が異なり、cron の実行間隔を設けたくない場合。
- ワークフロー / オーケストレーション DAG — バックフィルと履歴を備えた、宣言的なタスク依存関係(Airflow 形式)です。次の場合に使用します:バッチパイプラインに依存関係があり、実行履歴や再実行が必要な場合。
ワーカーの割り当て
- プル(ワーカーリース) — ワーカーがキューをポーリングし、ジョブを可視性タイムアウト付きでリースし、成功時に ACK / 削除します。次の場合に使用します:バックプレッシャーを追加コストなしで実現し、伸縮性があり、自律的に負荷分散するワーカーが必要な場合。デフォルトの方式です。
- プッシュ(ディスパッチャーによる割り当て) — コーディネーターが特定のワーカーにジョブを振り分けます。次の場合に使用します:アフィニティ / データ局所性が重要な場合(ジョブを、そのデータがある場所で実行する必要がある場合)。
優先度と公平性
- 優先度キュー — 高優先度キューと低優先度キューを分け、順番に処理します。次の場合に使用します:一部のクラスを先に実行する必要がある場合。
- 重み付き / 公平スケジューリング — テナントごとのキューからラウンドロビンまたは重み付きで選択します。次の場合に使用します:あるテナントの急増によって、ほかのテナントが処理されなくなるのを防ぐ必要がある場合。
トレードオフ
| 選択肢 | 解決できること | 悪化すること | 切り替えのタイミング |
|---|---|---|---|
| OS cron / 単一スケジューラー | シンプルで、インフラが不要 | 単一障害点 — ノードが停止するとスケジュールも停止し、再試行 / 履歴もない | 高可用性や実行漏れからの復旧が必要になったら → 分散スケジューラー |
| 分散スケジューラー(HA cron) | ノード障害に耐え、二重実行を防止(リーダー選出) | リーダー選出が必要(→ consistency-coordination)。構成要素も増える | マシン 1 台とジョブ 1 つで十分なら → OS cron |
| 遅延キュー / タイマー | cron の実行間隔なしでジョブごとの遅延を設定でき、タイミングも比較的正確 | かなり先のジョブがキューに滞留し、タイマーの精度はポーリング間隔に制限される | 遅延が一定 / 周期的なら → cron、依存関係があるなら → DAG |
| ワークフロー DAG(Airflow 形式) | 依存関係、バックフィル、実行履歴、再実行 | 制御プレーンが重く、スケジューラーの遅延があり、単一ジョブには過剰 | ジョブが互いに独立した単発処理なら → 通常のキュー + スケジューラー |
| プル(ワーカーリース) | 自律的な負荷分散、伸縮性、自然なバックプレッシャー | 少なくとも 1 回の配信:遅いジョブのリースが切れると再実行される(冪等性が必要) | 特定のノードでジョブを実行する必要があるなら(データ局所性) → プッシュ |
| プッシュ(ディスパッチャー) | アフィニティ / データ局所性、中央制御 | ディスパッチャーがボトルネック / 単一障害点になり、ワーカーの健全性を追跡する必要がある | データ局所性が不要なら → プルのほうがシンプル |
| 優先度キュー | 重要な処理を先に実行 | 負荷が継続すると低優先度の処理が滞留する | テナント間の公平性が重要なら → 重み付き / 公平スケジューリング |
| 重み付き / 公平スケジューリング | あるテナントがほかのテナントを処理できなくするのを防止 | 複雑さが増し、テナントごとの使用状況の記録が必要 | ワークロードクラスが 1 つだけなら → 単一キュー |
高負荷時の動作
スケジューラーは気付かないうちに遅延することもあれば、処理に苦戦しているフリートでは完了できない作業を再ディスパッチし、障害を増幅させることもあります。
- バックログの増加: 到着率がワーカーの処理能力を上回ると、期限到来済みのジョブがキューにたまり、「09:00 にスケジュール済み」のジョブが 09:40 に実行されます。CPU 使用率に問題がなくても、エンドツーエンドの遅延は増加します。緩和策: 到着率だけでなく、最も古い期限到来済みジョブの経過時間とキューの深さにアラームを設定します。ワーカーをスケールし、優先度の低いジョブを切り捨てるか、延期します。
- リースの期限切れ/再実行の嵐(典型例): ジョブの実行時間が可視性タイムアウトを超えると、リースが期限切れになり、キューがそのジョブを 2 台目のワーカーに再配信します。その結果、2 台のワーカーが同じジョブを実行し、容量を浪費します。冪等性がなければ、副作用も二重に適用されます。緩和策: タイムアウトをジョブ実行時間の p99 より長く設定し、長時間のジョブではハートビートでリースを延長し、タスクを冪等にするか、重複排除します。
- 毒性タスク: 常に失敗するジョブが無限に再試行され、ワーカーを浪費し、障害のあるダウンストリームに再び負荷をかけます。緩和策: バックオフとジッターを適用して再試行回数を制限し、その後デッドレターキューに送ります(再試行/バックオフ/DLQ は
resilience-failureが担当します)。 - ティック時のサンダリングハード: 毎時ちょうどにスケジュールされた数千の cron ジョブが一斉に起動し、ダウンストリームに殺到します。緩和策: スケジュールにジッターを加えるか、トリガーを分散させるか、ディスパッチをレート制限します。
- スケジューラーのスプリットブレイン: 2 台のスケジューラーがどちらも自分をリーダーだと判断し、定期ジョブをすべて二重にキューへ投入します。緩和策: リーダー選出とフェンシングによってリーダーを 1 台にします(→
consistency-coordination)。(job, scheduled_time) をキーにして、冪等にキューへ投入します。 - 飢餓状態: 高優先度ジョブの大量発生や、あるテナントの大量のジョブによって、ほかのすべてのジョブが飢餓状態になります。緩和策: 公平/重み付きスケジューリングと、テナントごとの並行実行数の上限を適用します。
監視項目: 最も古い期限到来済みジョブの経過時間(遅延を示す最良のシグナル)、優先度別のキューの深さ、リース期限切れ/再配信率、再試行率と DLQ 率、ワーカー使用率、テナントごとの処理割合。
適用方法
- 入力条件を明確にします。 トリガーの種類、厳密に 1 回か少なくとも 1 回か、遅延予算、優先度/公平性、ジョブの実行時間を確定します(まず明確にすることを参照)。作業がスケジュールや優先度のない単一の非同期ステップであれば、ここで終了します。通常のキューと冪等なコンシューマー(
messaging-streaming)で十分です。 - トレードオフの表から選びます。 トリガー(cron → 分散スケジューラー → 遅延キュー → DAG の順に、要件を満たす最も低コストなもの)、割り当てモデル(プル方式のリースをデフォルトとし、プッシュ方式はローカリティが必要な場合のみ)、複数のクラスがある場合に限り、優先度/公平性モデルを選択します。
- 重要な設定値を決めます。 可視性タイムアウトをジョブ実行時間の p99 より長くし、DLQ に送るまでの再試行上限とバックオフ、タスクごとの重複排除/冪等性キー、長時間ジョブ向けのリースハートビート間隔、テナントごとの並行実行数の上限を設定します。
- 選択肢のストレステストを行います。 バックログの増加、リース期限切れによる再実行、毒性タスク、ティック時の殺到、スケジューラーのスプリットブレイン、飢餓状態を検証します。ワークロードによって発生しうる各問題に、緩和策があることを確認します。
- 数値で規模を見積もります。 必要なワーカー数 = 到着率 × ジョブの平均実行秒数 ÷ 目標並行実行数(リトルの法則)。継続的な処理能力でピーク時の到着を処理できること、遠い将来に実行する遅延ジョブがストレージに収まることを確認します(→ 重要な数値)。
- プロバイダーを選びます。 汎用のレシピをデフォルトとし、ユーザーがクラウドを指定した場合にのみプロバイダーファイルを開きます(プロバイダーの選択を参照)。
- 回答でトレードオフを示します — ここで行った選択ごとに 1 行ずつ、
Decision | Solves | Worsens | Change it when…の表を示します(例:選択肢、そのストア、障害時のポリシー)。最後の列には具体的なトリガー(数値または観測可能なシグナル)を記載します。その後、最初に破綻する箇所を示します。
すべきこと、すべきでないこと
すべきこと
- 可視性タイムアウトを設けたプル型のワーカーリースをデフォルトにします。負荷が自動的に分散されます。
- 可視性タイムアウトはジョブ実行時間の p99 を超える値に設定し、長時間のジョブではハートビートで延長します。
- すべてのタスクを冪等にします(タスクキーで重複排除)。再実行しても問題が起きないようにします。
- スケジューラーのリーダーを 1 つだけ選出し、定期的なエンキューには (job, time) をキーとして使います。
- リトライ回数に上限を設け、バックオフとジッターを適用した後、デッドレターキューに送ります。期限超過ジョブの最長経過時間を監視し、アラートを設定します。
すべきでないこと
- リースされたジョブが 1 回だけ実行されると思い込まないでください。少なくとも 1 回の配信では重複が発生するため、それを考慮して設計します。
- すべての cron ジョブを :00 にスケジュールしないでください。ティックの集中を避けるため、ジッターを加えます。
- リーダー選出なしで 2 つのスケジューラーを実行しないでください(スプリットブレインによる二重実行が起こります)。
- 有害なタスクを際限なくリトライしないでください。上限を設けてデッドレターキューに送ります。
- スケジュール、優先度、または HA のニーズが現実のものになる前に、Airflow/Celery を導入しないでください(YAGNI)。
重要な数値
リトルの法則を使ってワーカープールの規模を決めます。処理中のジョブ数 = 到着率 × 平均ジョブ実行時間なので、ワーカー数 ≈ ピーク到着率 × 1 ジョブあたりの平均秒数 / ワーカーあたりの同時実行数となります。継続的な処理能力がピーク到着率を上回らなければ、バックログは解消されません。可視性タイムアウトはジョブ実行時間の p99 を超える値に設定します(短すぎるタイムアウトは重複実行の最大の原因です)。遠い未来の遅延ジョブの保存容量は、遅延ジョブ率 × 最大遅延時間 × ジョブサイズで設定します。レイテンシー/QPS の表を再掲せず、back-of-the-envelope からレートと実行時間を取得してください。
インターフェースの概要
スケジュールされたタスクは契約です。「ジョブ」ではなく、タスクとして定義します。
- タスクエンベロープ: 安定した
task_id/ 冪等性キー(リトライ時の重複排除。キーの契約はapi-designが管理)、task_type/バージョン、payload、priority、scheduled_for(実行してよい最早時刻)、attempt、trace_id。 - スケジュール仕様: 定期ジョブの場合、cron/間隔の式に加え、二重エンキューを無操作にする
dedup_key = (job_name, scheduled_time)。 - リース契約: デキュー時にワーカーはタスクを
visibility_timeoutの間不可視にします。成功時には ack/delete を行うか、ハートビートで延長する必要があります。リースの期限切れ → 自動的に再配信されます。リトライ上限後の失敗 → 試行回数と最後のエラーを添えて DLQ に送られます。
プロバイダーの選択
上記の汎用レシピをデフォルトにします。ユーザーがクラウドを指定した場合は、マネージドサービスへの対応付け、クォータ/制限、プロバイダー固有のトレードオフについて references/providers/<provider>.md を参照してください。そのプロバイダーのファイルが存在しない場合は、汎用レシピが回答となります。
図
スケジューラー → キュー → リースされたワーカーの経路、またはリース期限切れによる再配信ループを可視化するには、プラグイン内の architecture-diagram スキルを使用します。簡単なインライン図:[scheduler] → [delay/priority queue] → workers (lease) ─expire→ requeue ─fail×N→ [DLQ]。メイン経路は実線、期限切れ/DLQ の分岐は破線にします。
関連する構成要素
messaging-streaming— 依存先:キューはこのスキルがスケジュールしてリースする対象のトランスポートです。配信保証、順序、DLQ はこの構成要素が担い、このスキルでは再実装しません。resilience-failure— リトライポリシーで組み合わせて使用します。バックオフ、ジッター、封じ込め策としての DLQ はそこで調整し、このスキルではその要素を指定します。api-design— 冪等性キーの契約に依存します。この契約によって、リース期限切れ後の再実行が安全になります。consistency-coordination— リーダー選出(およびフェンシング)に依存します。アクティブなスケジューラーを 1 つだけにし、定期ジョブの二重実行を防ぎます。back-of-the-envelope— 規模の算定に情報を提供します。到着率とジョブ実行時間によって、ワーカー数とバックログの解消速度が決まります。system-design— オーケストレーターの推論ループに情報を提供します。作業を後で実行する場合、スケジュールに従って実行する場合、またはワーカーに確実に割り当てる場合に、ここへ処理を振り分けます。
参考資料
references/deep-dive.md— リース/可視性タイムアウトの仕組みとハートビート、分散 cron とリーダー選出、遅延キューの実装 (ソート済みセット、タイマーホイール)、優先度/公平性のアルゴリズム、重複排除による厳密に 1 回の効果、および Celery/Sidekiq/Airflow の内部動作を説明しています。スケジューラーの詳細設計時に参照してください。references/providers/{generic,aws,azure,gcp,temporal}.md— 環境ごとのサービス対応、選択を左右する制限事項、および落とし穴を説明しています。