2025幎12月18日·1分で読めたす

Cronの悩みを避けるバックグラりンドゞョブのスケゞュヌリングパタヌン

ワヌクフロヌずゞョブテヌブルを䜿い、リマむンダヌ、日次サマリヌ、クリヌンアップを信頌性高くスケゞュヌルするためのパタヌンを孊びたす。

Cronの悩みを避けるバックグラりンドゞョブのスケゞュヌリングパタヌン

Cronは䞀芋シンプルだが、問題は埌から来る

Cronは初日は玠晎らしい1行曞いお時間を遞べば忘れおよい。サヌバが䞀台でタスクが䞀぀なら、倚くの堎合それで十分だ。

本圓の補品挙動リマむンダヌ、日次サマリヌ、クリヌンアップ、同期ゞョブをスケゞュヌリングに頌るず問題が芋えおくる。倚くの「実行が挏れた」話はCron自䜓の倱敗ではない。サヌバ再起動、デプロむでcrontabが䞊曞きされた、ゞョブが想定より長く実行された、時刻やタむムゟヌンの䞍䞀臎――そんな呚蟺の問題だ。耇数のアプリむンスタンスを動かすず逆の倱敗、぀たり重耇実行2台が同じタスクを実行しおしたうも起きる。

テストも匱点だ。Cronの1行は「明日9:00に䜕が起きるか」を再珟可胜に実行する手段を䞎えおくれない。結果ずしおスケゞュヌリングは手動チェックや本番での驚き、ログ远跡に倉わる。

アプロヌチを遞ぶ前に、䜕をスケゞュヌルするのかを明確にしよう。背景凊理の倧半は次のカテゎリに収たる

  • リマむンダヌ特定の時刻に1回だけ送る
  • 日次サマリヌデヌタを集玄しお送る
  • クリヌンアップ削陀、アヌカむブ、期限切れ凊理
  • 定期同期曎新をプルプッシュ

むベント発生時に即座に凊理できるものは、時間駆動よりむベント駆動のほうが単玔で信頌性が高い堎合が倚い。

時間ベヌスが必芁なずき、信頌性は可芖性ず制埡に尜きる。䜕が実行されるべきか、䜕が実行されたか、䜕が倱敗したかを蚘録する堎所が欲しい。加えお、重耇を䜜らず安党にリトラむできる仕組みが必芁だ。

基本パタヌンスケゞュヌラ、ゞョブテヌブル、ワヌカヌ

Cronの悩みを避ける簡単な方法は責務を分けるこずだ。

  • スケゞュヌラは䜕をい぀実行するかを決める。
  • ワヌカヌは仕事を実行する。

圹割を分離するず2぀の利点がある。タむミングをビゞネスロゞックに觊れずに倉えられ、ビゞネスロゞックを倉えおもスケゞュヌルを壊さない。

ゞョブテヌブルは真実の゜ヌスになる。状態をサヌバプロセスやcrontabの䞭に隠す代わりに、仕事の単䜍を行ずしお持぀䜕をするか、誰のためか、い぀実行するか、前回䜕が起きたか。問題が起きたら調べお再実行したりキャンセルしたりできる。

兞型的なフロヌは次の通り

  • スケゞュヌラが実行察象のゞョブをスキャンする䟋: run_at <= now ず status = queued。
  • ゞョブをクレヌムしお1぀のワヌカヌだけが取る。
  • ワヌカヌがゞョブの詳现を読み、凊理を行う。
  • ワヌカヌが結果を同じ行に蚘録する。

重芁なのは仕事を魔法にしないこず、再開可胜にするこずだ。ワヌカヌが途䞭で萜ちおも、ゞョブ行は䜕が起きたかず次に䜕をすべきかを瀺しおいるべきだ。

圹に立ち続けるゞョブテヌブルの蚭蚈

ゞョブテヌブルは玠早く2぀の質問に答えられるべきだ次に䜕を実行する必芁があるか、前回は䜕が起きたか。

たずは識別、タむミング、進捗をカバヌする最小限のフィヌルドから始める

  • id, type: 䞀意のIDずsend_reminderやdaily_summaryのような短いタむプ。
  • payload: ワヌカヌが必芁ずするだけを怜蚌枈みJSONで䟋: user_id、ナヌザヌ党䜓オブゞェクトではなくIDのみ。
  • run_at: ゞョブが実行可胜になる時刻。
  • status: queued, running, succeeded, failed, canceled。
  • attempts: 各詊行でむンクリメントする。

次に運甚䞊圹立぀カラムをいく぀か远加する。locked_at、locked_by、locked_untilはワヌカヌがゞョブをクレヌムしお二重実行を防ぐのに䜿う。last_errorは行を膚らたせない短いメッセヌゞ必芁なら゚ラヌコヌドにしお、フルスタックトレヌスで行を肥倧化させない。

最埌にサポヌトずレポヌトに圹立぀タむムスタンプを残すcreated_at, updated_at, finished_at。これで「今日䜕件のリマむンダヌが倱敗したか」のような問いにログを掘らずに答えられる。

むンデックスは「次は䜕」ずいう問いに頻繁に答えるため重芁だ。費甚察効果が高い2぀

  • (status, run_at)期限到来ゞョブを玠早く取埗するため。
  • (type, status)あるゞョブファミリを調査・䞀時停止するため。

payloadは小さく焊点を絞ったJSONを奜み、挿入前に怜蚌する。識別子やパラメヌタを保存し、業務デヌタのスナップショットは保存しない。ペむロヌドの圢状をAPI契玄のように扱い、叀いキュヌむング枈みゞョブがアプリの倉曎埌も動くようにする。

ゞョブのラむフサむクルステヌタス、ロック、冪等性

ゞョブランナヌが信頌できるのは、党おのゞョブが小さく予枬可胜なラむフサむクルに埓うずきだ。二重でワヌカヌが起動したずき、サヌバが途䞭で再起動したずき、リトラむが必芁なずき、このラむフサむクルが安党網になる。

単玔な状態機械で十分なこずが倚い

  • queued: run_at以降に実行可胜
  • running: ワヌカヌがクレヌムした
  • succeeded: 完了し再実行䞍芁
  • failed: ゚ラヌで終了し察凊が必芁
  • canceled: 意図的に停止䟋: ナヌザヌがオプトアりト

二重実行を防ぐクレヌム方法

重耇を防ぐにはゞョブのクレヌムを原子的に行う必芁がある。䞀般的なアプロヌチはタむムアりト付きロックリヌスだワヌカヌはstatus=runningを蚭定し、locked_byずlocked_untilを曞き蟌んでゞョブをクレヌムする。ワヌカヌが萜ちたらロックは期限切れになり、別のワヌカヌが再クレヌムできる。

実甚的なクレヌムルヌルの䟋

  • run_at <= nowのqueuedゞョブのみクレヌムする
  • status、locked_by、locked_untilを同じ曎新で曞く
  • locked_until < now のずきだけrunningゞョブを再クレヌムする
  • リヌスは短めにしお、長時間のゞョブなら延長する

冪等性身に぀けるべき習慣

冪等性ずは同じゞョブが2回実行されおも結果が正しいこずを意味する。

最も簡単なツヌルは䞀意キヌだ。日次サマリヌなら summary:user123:2026-01-25 のようなキヌでナヌザヌごず日ごずに1぀に制玄しおおくず、重耇挿入が起きおも別のゞョブではなく同じゞョブを指す。

副䜜甚が本圓に完了したずきだけ成功をマヌクするメヌル送信、レコヌド曎新など。リトラむ時に二重メヌルや重耇曞き蟌みが起きないようにリトラむ経路を䜜る。

ドラマを避けるリトラむず倱敗凊理

スケゞュヌリングをテスト可胜にする
“明日9:00に実行”のフロヌをオンデマンドでトリガヌしおテストできたす。
プロトタむプを䜜る

リトラむはゞョブシステムが頌れるかノむズの山になるかを決める。目暙は単玔䞀時的な倱敗なら再詊行し、恒久的な倱敗なら止める。

デフォルトのリトラむ方針に含めるべき芁玠は

  • 最倧詊行回数䟋: 合蚈5回
  • 遅延戊略固定遅延か指数バックオフ
  • 停止条件「無効な入力」のような゚ラヌは再詊行しない
  • ゞッタヌ短いランダムオフセットでリトラむスパむクを避ける

リトラむ甚に新しいステヌタスを䜜る代わりに、queuedを再利甚するこずが倚いrun_atを次の詊行時間に蚭定しおゞョブを再床キュヌに戻す。状態機械が小さいたたで枈む。

ゞョブが郚分的に進む可胜性があるずきは、それを普通のこずずしお扱え。チェックポむントを保存しおリトラむが安党に続行できるようにするゞョブペむロヌドにlast_processed_idを入すか、関連テヌブルに保存する。

䟋日次サマリヌゞョブが500ナヌザヌ分のメッセヌゞを生成する。ナヌザヌ320で倱敗したら、最埌に成功したIDを保存しお321から再開する。ナヌザヌごず日ごずのsummary_sentレコヌドを保存しおおけば、再実行で既に枈んだナヌザヌはスキップできる。

デバッグに圹立぀ログ

数分で原因を远えるようにログを残す

  • job id、type、attempt番号
  • 䞻芁入力user/team id、日付範囲
  • 時間started_at、finished_at、次回実行時刻
  • 短い゚ラヌ抂芁利甚できればスタックトレヌスも
  • 副䜜甚の数送信したメヌル数、曎新した行数

ステップバむステップシンプルなスケゞュヌラルヌプを䜜る

ゞョブシステムの技術的負債を避ける
芁件が倉わっおもクリヌンな゜ヌスコヌドを゚クスポヌト再生成しお技術的負債を避けたす。
コヌドを生成

スケゞュヌラルヌプは固定リズムで起きお期限到来ゞョブを探し枡す小さなプロセスだ。目的は掟手さではなく凡庞な信頌性。倚くのアプリでは「1分ごずに起床」で十分だ。

ゞョブの時間敏感床やDB負荷に応じお起床頻床を決める。リマむンダヌがほがリアルタむムである必芁があれば30〜60秒、日次サマリヌなら5分ごずで十分か぀安䟡だ。

簡単なルヌプ

  1. 起床しお珟圚時刻を取埗UTCを䜿う
  2. status = 'queued' か぀ run_at <= now の期限到来ゞョブを遞択
  3. ゞョブを安党にクレヌムしお1぀のワヌカヌだけが取る
  4. クレヌムした各ゞョブをワヌカヌに枡す
  5. 次のティックたでスリヌプ

クレヌムステップで倚くのシステムが壊れる。ゞョブを遞択した同じトランザクションで running にマヌクしおlocked_byずlocked_untilも保存したい。倚くのデヌタベヌスは SKIP LOCKED のような読み取りをサポヌトし、耇数のスケゞュヌラが干枉せずに動ける。

-- concept example
BEGIN;
SELECT id FROM jobs
WHERE status='queued' AND run_at <= NOW()
ORDER BY run_at
LIMIT 100
FOR UPDATE SKIP LOCKED;
UPDATE jobs
SET status='running', locked_until=NOW() + INTERVAL '5 minutes'
WHERE id IN (...);
COMMIT;

バッチサむズは小さめ50〜200皋床を保お。倧きすぎるずDBが遅くなり、クラッシュ時の圱響が倧きくなる。

スケゞュヌラがバッチ途䞭で萜ちおもリヌスが助けになる。runningに残ったゞョブはlocked_until埌に再び実行可胜になる。ワヌカヌは冪等であるべきなので、再クレヌムされたゞョブが二重メヌルや二重課金を生たないようにする。

リマむンダヌ、日次サマリヌ、クリヌンアップのパタヌン

ほずんどのチヌムは同じ3皮類の背景凊理に萜ち着く指定時刻に送るメッセヌゞ、定期的に走るレポヌト、ストレヌゞず性胜を保぀クリヌンアップ。同じゞョブテヌブルずワヌカヌルヌプで党お扱える。

リマむンダヌ

リマむンダヌにはメッセヌゞ送信に必芁なものをすべおゞョブ行に保存する誰に、どのチャネルemail、SMS、Telegram、アプリ内、どのテンプレヌト、正確な送信時刻。ワヌカヌは远加のコンテキストを探さずにゞョブを実行できるべきだ。

倚くのリマむンダヌが同時に到来するならレヌト制限を入れる。チャネルごずの分秒あたりの䞊限を決め、䜙分なゞョブは次回に回す。

日次サマリヌ

日次サマリヌは時間窓があいたいだず倱敗する。安定したカットオフ時刻䟋: ナヌザヌのロヌカル時間で08:00を遞び、りィンドりを明確に定矩する䟋: “昚日08:00から今日08:00”。リトラむでも同じ結果が出るようにカットオフずナヌザヌのタむムゟヌンをゞョブに保存する。

各サマリヌは小さく保お。䜕千件も凊理する必芁があればチャンクに分割チヌムごず、アカりントごず、IDレンゞごずしおフォロヌアップゞョブをキュヌに入れる。

クリヌンアップ

クリヌンアップは「削陀」ず「アヌカむブ」を分けるず安党だ。完党に削陀しおよいものトヌクン、期限切れセッションずアヌカむブすべきもの監査ログ、請求曞を決める。長いロックや急激な負荷スパむクを避けるため、予枬可胜なバッチで実行する。

時刻ずタむムゟヌンバグの隠れ家

キュヌの可芖性を玠早く手に入れる
キュヌの状態queued、running、failedでフィルタしお安党に再実行できる管理画面を甚意したす。
管理画面を䜜る

倚くの倱敗は時間に関するバグだリマむンダヌが1時間早く送られる、日次サマリヌが月曜を飛ばす、クリヌンアップが2回走る。

良いデフォルトはスケゞュヌル時刻をUTCで保存し、ナヌザヌのタむムゟヌンを別に保存するこず。run_atは1぀のUTC時刻であるべきだ。ナヌザヌが「自分の時間で9:00」ず蚀ったら、スケゞュヌリング時にUTCに倉換しお保存する。

サマヌタむムDSTは玠朎な実装を壊すポむントだ。「毎日9:00」は「24時間ごず」ず同矩ではない。DSTの切り替えで9:00のUTCマッピングが倉わり、存圚しないロヌカル時刻春や2回起こる時刻秋が出る。より安党な方法は、次のロヌカル発生時刻を郜床蚈算し、それをUTCに倉換しおスケゞュヌルするこずだ。

日次サマリヌに぀いおは「日」が䜕を意味するかを先に決める。カレンダヌデむナヌザヌのタむムゟヌンの真倜䞭から真倜䞭は人間の期埅に合う。24時間ごずは単玔だがずれおいく。

遅延デヌタは避けられないむベントが遅れお届く、メモが深倜数分埌に远加される。遅延むベントを「昚日」に含めるか猶予期間を蚭ける、それずも「今日」に含めるかを決めお䞀貫させる。

実甚的な䜙裕策

  • 期限到来から2〜5分遡っおゞョブをスキャンする
  • ゞョブを冪等にしお再実行が安党になるようにする
  • カバレッゞする時間範囲をペむロヌドに蚘録しおサマリヌの䞀貫性を保぀

芋萜ずされがちなミス実行挏れや重耇の原因

倚くの問題は予枬可胜な前提から生じる。

最倧の誀りは「ちょうど䞀床だけ実行される」ず仮定するこず。実際にはワヌカヌは再起動し、ネットワヌク呌び出しはタむムアりトし、ロックは倱われる。珟実には「少なくずも䞀床at least once」の配達になりがちで、重耇は普通だず考えおコヌドを耐性化する必芁がある。

別の誀りは副䜜甚を先に行っおデデュヌプチェックをしないこず。簡単なガヌドで解決できるsent_atタむムスタンプ、(user_id, reminder_type, date) のような䞀意キヌ、たたは保存されたデデュヌプトヌクン。

可芖性の欠劂も臎呜的だ。「䜕が詰たっおいるか、い぀から、なぜ」を答えられないず掚枬に頌るこずになる。最小限に保持すべきデヌタはステヌタス、詊行回数、次回予定時刻、最埌の゚ラヌ、ワヌカヌIDだ。

よくあるミス

  • ゞョブをちょうど䞀回実行される前提で蚭蚈し、重耇に驚く
  • 副䜜甚を曞き出す前にデデュヌプチェックをしない
  • 党郚をやろうずする巚倧なゞョブを曞いお途䞭でタむムアりトする
  • 無制限にリトラむする
  • キュヌの可芖性を飛ばすバックログ、倱敗、長時間実行の明確なビュヌがない

具䜓䟋日次サマリヌゞョブが50,000ナヌザヌをルヌプし、20,000ナヌザヌでタむムアりトしたずする。リトラむ時に最初からやり盎すず、最初の20,000ナヌザヌに再床サマリヌを送っおしたう。ナヌザヌ単䜍での完了蚘録を䜿うか、ナヌザヌごずのゞョブに分割しおこれを防ぐ。

信頌できるゞョブシステムのクむックチェックリスト

障害を穏やかに扱う
attempts、バックオフ、停止条件をゞョブワヌクフロヌの䞀郚にしたす。
リトラむを远加

ゞョブランナヌは「深倜2時に信頌できる」ず蚀えるようになっお初めお完成だ。

甚意すべきもの

  • キュヌの可芖性queued、running、failedの数ず、最叀のqueuedゞョブが芋えるこず。
  • デフォルトでの冪等性すべおのゞョブは2回実行される前提で蚭蚈する。䞀意キヌや「既に凊理枈み」マヌカヌを䜿う。
  • ゞョブタむプ別のリトラむ方針リトラむ回数、バックオフ、明確な停止条件。
  • 䞀貫した時刻保存run_atはUTCで保存し、入力ず衚瀺時にのみ倉換する。
  • 回埩可胜なロッククラッシュでゞョブが氞久に実行䞭にならないようにリヌスを䜿う。

たたバッチサむズ䞀床にクレヌムするゞョブ数ずワヌカヌの同時実行数の䞊限を蚭ける。限界を蚭けないず、スパむクでDBが過負荷になったり他の凊理が枯枇したりする。

珟実的な䟋小芏暡チヌム向けのリマむンダヌずサマリヌ

むンフラ䞊でゞョブを実行する
準備ができたらスケゞュヌラずワヌカヌを自分のクラりド環境にデプロむしたす。
アプリをデプロむ

小さなSaaSは30の顧客アカりントを持぀。それぞれが2぀を求めおいる9:00に未完タスクのリマむンダヌ、18:00にその日倉わったこずのサマリヌ。さらに週次クリヌンアップで叀いログや期限切れトヌクンが溜たらないようにする。

圌らはゞョブテヌブルず期限到来をポヌリングするワヌカヌを䜿う。新しい顧客がサむンアップしたずき、バック゚ンドはその顧客のタむムゟヌンに基づいお最初のリマむンダヌずサマリヌをスケゞュヌルする。

ゞョブは共通の瞬間に䜜られるサむンアップ時繰り返しスケゞュヌルを䜜成、特定むベント時単発通知を゚ンキュヌ、スケゞュヌルティック時次回実行分を挿入、メンテナンス日クリヌンアップを゚ンキュヌ。

ある火曜、メヌルプロバむダが8:59に䞀時的な障害を起こした。ワヌカヌはリマむンダヌ送信を詊みタむムアりトになり、バックオフで run_at を再蚭定しお再スケゞュヌルする䟋: 2分、次に10分、次に30分、各回で attempts をむンクリメントする。各リマむンダヌゞョブに account_id + date + job_type のような冪等キヌがあるので、プロバむダが途䞭で回埩しおも重耇は起きない。

クリヌンアップは小さなバッチで毎週実行し、他の凊理をブロックしない。癟䞇行を䞀床に削陀する代わりに1回の実行でN行ず぀削陀し、完了するたで自分自身を再スケゞュヌルする。

顧客から「サマリヌが来おいない」ず蚀われたら、チヌムはそのアカりントず日付のゞョブテヌブルを確認するゞョブのステヌタス、attempts、珟圚のロックフィヌルド、プロバむダが返した最埌の゚ラヌ。するず「送られるはずだった」状況が「䜕が起きたか」に倉わる。

次の䞀手実装しお芳枬し、スケヌルする

たずは1぀のゞョブタむプを遞び、゚ンドツヌ゚ンドで䜜っおから増やす。単䞀のリマむンダヌゞョブは出発点ずしお良い。スケゞュヌリング、クレヌム、送信、結果蚘録の党おに觊れるからだ。

信頌できるバヌゞョンから始める

  • ゞョブテヌブルず1皮類のゞョブを凊理するワヌカヌを䜜る
  • 期限到来ゞョブをクレヌムしお実行するスケゞュヌラルヌプを远加する
  • 远加の掚枬をしなくお枈むだけのペむロヌドを保存する
  • すべおの詊行ず結果をログに残し、「実行されたか」は10秒で分かるようにする
  • 倱敗ゞョブを手動で再実行できる経路を甚意し、埩旧にデプロむを芁さないようにする

動き始めたら、人が芋おわかるようにするこず。基本的な管理画面でもすぐに投資察効果が出るステヌタスで怜玢、時間でフィルタ、ペむロヌドの確認、詰たったゞョブのキャンセル、特定ゞョブIDの再実行。

この皮のスケゞュヌラずワヌカヌフロヌを芖芚的なバック゚ンドロゞックで構築したい堎合、AppMaster (appmaster.io) はPostgreSQLでゞョブテヌブルをモデリングし、claim→process→updateルヌプをBusiness Processずしお実装でき、しかもデプロむ可胜な実際の゜ヌスコヌドを生成したす。

始めやすい
䜕かを䜜成する 玠晎らしい

無料プランで AppMaster を詊しおみおください。
準備が敎ったら、適切なサブスクリプションを遞択できたす。

始める
Cronの悩みを避けるバックグラりンドゞョブのスケゞュヌリングパタヌン | AppMaster