Skip to content
Published on

Cron とスケジュールタスク 2026 — systemd timers / Vercel Cron / AWS EventBridge Scheduler / k8s CronJobs / BullMQ / Sidekiq / Temporal 徹底ガイド

Authors

プロローグ — 「cron 回しとけばいいでしょ?」が危うくなった時代

2026 年のあるチームの設計レビュー。

新人「毎日 9 時にレポートメールを送りたいんです。cron でいいですよね?」 シニア「どのホストで?監視は?失敗したら?二回走ったら?マシンが落ちたら?」 新人「……あ。」

この短いやり取りに 2026 年のスケジューリングがすべて詰まっている。cron は 50 年間動き続けてきたが、クラウドネイティブ時代に入ってからは「決まった時間にコマンドを実行する」だけでは到底足りない。ホストの停止、二重実行の競合、失敗通知、リトライ、分散ロック、冪等性、タイムゾーン、サマータイム(DST)——すべてが運用上の罠になった。

2026 年現在、スケジュールタスクは大きく 4 つのカテゴリ に分かれる。システムレベル(cron、systemd timers、fcron、anacron)、クラウドマネージド(Vercel Cron、EventBridge Scheduler、Cloudflare Workers、GitHub Actions schedule)、分散バックグラウンドキュー(BullMQ、Sidekiq、Celery Beat)、ワークフローエンジン(Airflow、Dagster、Prefect、Temporal、Inngest、Trigger.dev、Hatchet)。そしてその上に監視(Healthchecks.io、Cronitor.io)が乗る。

本記事ではこの地形を一気に整理する。古典的な cron の 5 フィールドから、systemd timers、Vercel Cron、AWS EventBridge Scheduler(2022 年 11 月に CloudWatch Events Rules を置き換え)、k8s CronJobs、BullMQ、Sidekiq、Celery Beat、Temporal Schedules、Hatchet、Quartz、Hangfire、fcron、anacron、Nomad periodic jobs、監視 SaaS まで。韓国のトス・カカオ、日本のメルカリが何をどう使っているかも見る。


1. 2026 年の cron 地図 — システム / クラウド / キュー / ワークフロー

まず全体像。「スケジュールタスク」という一語の下に、まったく異なる道具が並んでいる。

カテゴリ代表的なツール適したケース
システム croncrond、systemd timers、fcron、anacron単一ホストのバックグラウンド整理、ログローテーション
クラウドマネージドVercel Cron、AWS EventBridge Scheduler、Cloudflare Workers Cron Triggers、GitHub Actions schedule、GitLab schedule pipelinesサーバーレス、CI/CD、インフラ自動化
分散バックグラウンドキューBullMQ、Sidekiq、Celery Beat、RQ、dramatiq、APScheduler、Quartz、Spring @Scheduled、Hangfireアプリ内のジョブキュー + スケジュール
ワークフローエンジンAirflow、Dagster、Prefect、Temporal Schedules、Inngest Crons、Trigger.dev v3、Hatchet多段階データパイプライン、信頼性重視のワークフロー
コンテナk8s CronJobs、Nomad periodic jobsコンテナベースのバッチ
監視Healthchecks.io、Cronitor.ioデッドマンスイッチ、未受信アラート

2026 年に共有されているベストプラクティスはこうだ。

  1. 単一ホストの一コマンド なら systemd timers(cron ではなく)。
  2. サーバーレス環境 なら Vercel Cron、EventBridge Scheduler、Cloudflare Cron のいずれかをインフラに合わせて。
  3. アプリ内のバックグラウンド + 定期作業 なら BullMQ / Sidekiq / Celery Beat。
  4. 多段階ワークフロー なら Temporal / Inngest / Trigger.dev / Hatchet(モダン)か Airflow / Dagster / Prefect(伝統)。
  5. 何を選んでも Healthchecks.io / Cronitor.io で未受信アラート を仕掛ける。

このガイドに従えば、2026 年のケースの 99% は片付く。


2. 古典的な cron — 5 フィールド表記、/etc/crontab

cron は 1975 年に Brian Kernighan が書いた Unix のツールだ。50 年間生き残り、ほぼすべての Linux ホストに今もインストールされている。中心の 5 フィールド から。

# ┌───────── 分 (0-59)
# │ ┌─────── 時 (0-23)
# │ │ ┌───── 日 (1-31)
# │ │ │ ┌─── 月 (1-12)
# │ │ │ │ ┌─ 曜日 (0-7、0 と 7 は日曜)
# │ │ │ │ │
# * * * * *  command-to-execute

よく使うパターン。

# 毎日 0 時
0 0 * * * /usr/local/bin/backup.sh

# 毎時 0 分
0 * * * * /usr/local/bin/sync.sh

# 5 分おき
*/5 * * * * /usr/local/bin/healthcheck.sh

# 平日 9 時
0 9 * * 1-5 /usr/local/bin/report.sh

# 毎月 1 日 3 時
0 3 1 * * /usr/local/bin/monthly-billing.sh

cron には 2 種類の crontab がある。

  • ユーザー crontab: crontab -e で編集、/var/spool/cron/ に保存、ユーザー権限で実行。
  • システム crontab: /etc/crontab/etc/cron.d/* に置き、実行ユーザーを 6 番目のフィールドとして明示する。
# /etc/crontab — システム用。user フィールドが追加される。
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts /etc/cron.daily )

5 フィールド以外に、一部の cron 実装(Vixie cron、anacron、cronie)は 秒の 6 フィールド を扱える。ただし標準的な POSIX cron は 5 フィールドしか理解しないことを忘れずに。BullMQ・Quartz・Spring などのライブラリは 6 フィールドや 7 フィールド(年)まで受け付ける。

cron の限界。

  1. 監視がない: 失敗しても誰も気付かない。出力はメールで送られるが、2026 年に root のメールを誰が読んでいるのか。
  2. キャッチアップなし: ホストが落ちている間に予定された実行は素直に欠落(anacron がこの問題を解く)。
  3. 単一ホスト: 分散ロックがない → 同じ cron を 2 つのホストに入れると 2 回走る。
  4. タイムゾーン: システムの TZ で動く。UTC でなければ DST(サマータイム)で 1 時間消えるか 2 回走る事故が起きる。
  5. 表現力不足: 「毎月最終平日」のような表現が難しい。

それでも cron は生きている。シンプルさという価値があるからだ。1 行のジョブ、1 つのホスト、監視が明らかに不要なケース(ログローテーション、一時ファイルの掃除)であれば、cron は今でも最適だ。


3. systemd timers — Linux のモダンな代替

systemd が init システムを統一して以降(2010 年代半ば)、systemd timers が cron のモダンな代替として定着した。RHEL/CentOS Stream、Ubuntu、Debian、Fedora はすべて systemd がデフォルトだ。

systemd timer は 2 ファイル構成。

# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=10min

[Install]
WantedBy=timers.target

有効化。

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
sudo systemctl list-timers

systemd timers が cron に勝る点。

  1. ジャーナルロギング統合: journalctl -u backup.service 一発で実行ログを確認。cron はメールか syslog。
  2. Persistent: ホストが落ちて欠落した実行を、立ち上がった直後に 1 回だけ実行。anacron 相当が組み込み。
  3. RandomizedDelaySec: 同じ時刻に全ホストが一斉に cron を走らせて負荷が跳ねる問題を自動で分散。
  4. OnCalendar の表現力: Mon..Fri 09:00*-*-1..7 09:00(毎月第 1 週の平日)、quarterlyyearly など人間に読める形式。
  5. リソース制限: Service unit が CPUQuotaMemoryMaxIOSchedulingClass といった cgroup 制限を直接持つ。
  6. 依存関係: Requires=After= で「別サービスが起きているときだけ実行」のような条件を表現できる。

OnCalendar の例。

# 毎日 03:00
OnCalendar=daily

# 毎週日曜 04:00
OnCalendar=Sun 04:00

# 毎月 1 日と 15 日
OnCalendar=*-*-01,15 02:00

# 平日 10 分おき
OnCalendar=Mon..Fri *:0/10

# 6 時間おき (0, 6, 12, 18)
OnCalendar=*-*-* 0/6:00:00

systemd-analyze calendar 'Mon..Fri *:0/10' で表記を検証できる。次の発火時刻も出力される。

cron から systemd timer への移行は、2026 年の標準推奨と言ってよい。Ubuntu 24.04 LTS と RHEL 10 のいずれも cron はオプションパッケージで、systemd timer が標準メカニズムになっている。


4. Vercel Cron — サーバーレススケジュール

Vercel Cron は 2022 年に GA となり、2026 年現在は Next.js・SvelteKit などのフレームワークと統合された標準スケジューラだ。

設定は vercel.json 1 ファイル。

{
  "crons": [
    {
      "path": "/api/cron/daily-report",
      "schedule": "0 9 * * *"
    },
    {
      "path": "/api/cron/cleanup",
      "schedule": "*/15 * * * *"
    }
  ]
}

ハンドラ(Next.js App Router)。

// app/api/cron/daily-report/route.ts
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  // Vercel は Authorization ヘッダで自前のシークレットを送る
  const authHeader = request.headers.get('authorization')
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return new NextResponse('Unauthorized', { status: 401 })
  }

  // 実処理
  await sendDailyReport()

  return NextResponse.json({ ok: true })
}

Vercel Cron の特徴。

  • Hobby/Free プラン: 1 日 cron 最大 2 個、毎分 1 回。学習・個人プロジェクト向け。
  • Pro プラン: 40 個の cron、毎分複数回可。実運用に耐える。
  • Enterprise: 事実上無制限。
  • タイムアウト: 関数自体のタイムアウト(Pro 60 秒、Enterprise 900 秒)内に終える必要あり。
  • HMAC 認証: CRON_SECRET 環境変数で外部からの任意呼び出しを防止。
  • 監視: Vercel ダッシュボードで最後の実行とステータスを確認。

制約は明確だ。関数タイムアウト内に終わるものに限られるため、長時間処理(バッチ、大量データ処理)には不向き。長くなるなら ジョブキュー(Inngest、Trigger.dev、QStash)に投げて即座に終わるパターンが定番。Vercel Cron 自体は「適時に起こすアラーム」だ。


5. AWS EventBridge Scheduler(2022 年 11 月) — CloudWatch Events Rules の置き換え

AWS の cron は 2022 年 11 月に EventBridge Scheduler に世代交代した。以前の標準は CloudWatch Events Rules(CloudWatch Events rate / cron expression)だったが、EventBridge Scheduler は 1 アカウントあたり 1 億スケジュールまで扱えると発表されてからは推奨オプションとなった。

差分の核心。

項目CloudWatch Events Rules(旧)EventBridge Scheduler(新)
提供開始20162022 年 11 月
スケジュール数上限アカウントあたり 100~300(サービスクォータ)アカウントあたり 1 億
一回限りスケジュールなしat(...) 表現対応
タイムゾーンUTC 固定IANA TZ 指定可
フレキシブル時間ウィンドウなしFlexibleTimeWindowMinutes
ターゲット統合EventBus 経由の間接Lambda、SQS、ECS、SageMaker、Step Functions など 200+ に直接
推奨非推奨ガイダンスAWS 公式推奨

EventBridge Scheduler を作る(Terraform)。

resource "aws_scheduler_schedule" "daily_report" {
  name       = "daily-report"
  group_name = "default"

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = "cron(0 9 ? * MON-FRI *)"
  schedule_expression_timezone = "Asia/Tokyo"

  target {
    arn      = aws_lambda_function.report.arn
    role_arn = aws_iam_role.scheduler.arn

    retry_policy {
      maximum_retry_attempts = 3
      maximum_event_age_in_seconds = 3600
    }
  }
}

AWS の cron 表記は 6 フィールド(分 時 日 月 曜日 年)で、? は「気にしない」を意味する。POSIX cron と微妙に異なるので要注意。

もうひとつの選択肢が Step Functions の Wait + EventBridge の組み合わせだ。ワークフロー内の「5 分待ってから次へ」のようなパターンは Step Functions 自身の Wait state で表現し、外部から起こすところを EventBridge Scheduler が担う。

2026 年の推奨は 新規プロジェクトは EventBridge Scheduler、既存の CloudWatch Events Rules は段階的に移行。AWS 自身がコンソールでマイグレーションツールを提供している。


6. Cloudflare Workers Cron Triggers

Cloudflare Workers はエッジ関数プラットフォームで、Cron Triggers という名前で cron 表記ベースのスケジューリングを提供する。2026 年現在、Workers の無料プランでも cron を 3 つまで持てる。

wrangler.toml の設定。

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2026-05-01"

[triggers]
crons = [
  "0 */6 * * *",
  "0 0 * * 0"
]

ハンドラ。

// src/index.ts
export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    console.log(`cron fired: ${event.cron} at ${event.scheduledTime}`)
    // 実処理
    await runCleanup(env)
  },

  async fetch(req: Request): Promise<Response> {
    return new Response('Worker alive')
  }
}

Cloudflare の差別化ポイント。

  • エッジ全球実行: cron がどこで起きるかは Cloudflare が決める(ルーティング最適化)。ワークロードにタイムゾーン依存があるならコード側で処理する。
  • 無料枠込み: 10 万リクエスト / 日の中に cron も含まれる。
  • 30 秒 CPU 上限: 標準 Worker は 30 秒 CPU。長時間処理は Durable Objects か Queues と組み合わせる。
  • イベントメタデータ: event.cron でどの式が発火したかが分かるので、1 関数で複数スケジュールを分岐できる。

Workers Cron Triggers は Vercel Cron と似ているが、エッジ分散手厚い無料枠 が強みだ。


7. GitHub Actions schedule / GitLab schedule pipelines

CI/CD プラットフォーム自体が cron になるパターンは 2020 年以降に爆発的に増えた。インフラ運用、データ同期、定期点検などを ワークフローファイル で管理できる。

GitHub Actions schedule の例。

# .github/workflows/nightly.yml
name: Nightly Maintenance

on:
  schedule:
    - cron: '0 17 * * *'   # UTC 17:00 = JST 02:00
  workflow_dispatch:        # 手動実行も許可

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run cleanup
        run: ./scripts/cleanup.sh

GitHub Actions schedule の注意点。

  1. UTC 固定: タイムゾーンオプションなし。自前で変換する必要がある。
  2. 低活動リポジトリの自動停止: 60 日 push がないと、schedule ワークフローは自動的に無効化される。あえてコミットする必要あり。
  3. 遅延あり: GitHub の負荷次第で、約束時刻から数分~数十分遅れて発火することはよくある。
  4. 無料枠: パブリックリポジトリは無料、プライベートは分単位の quota から消費。

GitLab schedule pipelines。

# .gitlab-ci.yml
nightly:
  script:
    - ./scripts/cleanup.sh
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

GitLab はコード以外に、CI/CD > Schedules の UI から時刻・タイムゾーン・変数を設定する。GitHub Actions と違ってタイムゾーン設定が効くのが強み。

CI スケジュールが向いているケース。

  • 依存関係のセキュリティスキャン / SBOM 更新。
  • 静的サイトの再生成(為替・天気など)。
  • 毎日 / 毎週のレポートメール送信。
  • Git リポジトリのミラーリング。

不向きなケース。

  • 分単位の精度が必要な処理(CI は約束時刻より遅れがち)。
  • リアルタイムなインフラ操作(EventBridge Scheduler か Vercel Cron 推奨)。

8. Kubernetes CronJobs — kind: CronJob

Kubernetes には 1.21 で CronJob が安定化した。一度きりのコンテナ実行リソースである Job の上に cron 表記を載せたもの。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-cleanup
spec:
  schedule: "0 2 * * *"
  timeZone: "Asia/Tokyo"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 600
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: cleanup
              image: registry.example.com/cleanup:1.4.2
              command: ["/app/cleanup.sh"]
              resources:
                requests: { cpu: "100m", memory: "128Mi" }
                limits:   { cpu: "1",    memory: "512Mi" }

Kubernetes CronJob の主なフィールド。

  • schedule: 5 フィールドの cron 表現。
  • timeZone: 1.25 から正式。IANA TZ(例: Asia/Tokyo)。
  • concurrencyPolicy: Allow(既定) / Forbid(前のジョブが終わっていなければ起動しない) / Replace(新しいジョブが前のジョブを殺す)。
  • startingDeadlineSeconds: この秒数以内に起こせなければ諦める(コントローラが少し遅れたとき向け)。
  • successfulJobsHistoryLimit / failedJobsHistoryLimit: Job オブジェクトを何件残すか。
  • backoffLimit: Pod 失敗時の再試行回数。

CronJob の落とし穴。

  1. clock drift: ノード間で時刻がずれると発火時刻が揺れる。NTP は必須。
  2. コントローラ遅延: kube-controller-manager に負荷がかかると発火が遅れる。平均で 1 秒〜数十秒。
  3. タイムアウト未設定: Job に activeDeadlineSeconds を入れないと無限実行が起こり得る。
  4. タイムゾーン罠: 1.25 以前のクラスタは常に UTC。DST をまたぐ時刻で 2 回または 0 回発火する古典的バグがある。

運用 Tips。

  • CronJob ごとに PodDisruptionBudgetPriorityClass を定義してノード障害に強くする。
  • ロギング: stdout/stderr をコンテナログ → Loki / OpenSearch へ収集。
  • 監視: Prometheus の kube_cronjob_* メトリクス + Alertmanager で「最後に成功してから X 時間経過したらアラート」。

9. BullMQ(Node) / Sidekiq(Ruby) / Celery Beat(Python)

アプリ内のバックグラウンドジョブ + 定期作業のパターンは、Redis ベースのジョブキュー + スケジューラ が定番だ。2026 年の三大選択肢。

BullMQ — Node.js

BullMQ は OptimalBits の Bull の後継で、2026 年の Node.js 界の標準キュー。

import { Queue, Worker, QueueEvents } from 'bullmq'

const connection = { host: '127.0.0.1', port: 6379 }

// キュー
const reportsQueue = new Queue('reports', { connection })

// 反復ジョブ(cron)登録
await reportsQueue.add(
  'daily-report',
  { type: 'pdf' },
  {
    repeat: { pattern: '0 9 * * 1-5', tz: 'Asia/Tokyo' },
    jobId: 'daily-report',
  }
)

// ワーカー
new Worker('reports', async (job) => {
  console.log(`processing ${job.name}`)
  await generateReport(job.data)
}, { connection })

BullMQ の強み。

  • TypeScript first: 型安全。
  • Repeatable jobs: cron か every(ミリ秒)。tz 指定可。
  • Flow producer: 親子ジョブの依存関係(ワークフロー風)。
  • Rate limiting / priority / retries: 1 行で。

BullMQ Pro は有料。グループ、一時停止、優先キューグループなどエンタープライズ機能を追加。BullMQ を運用する会社(Taskforce.sh)がメンテナで、無料 OSS も活発。

Sidekiq — Ruby

Sidekiq は Ruby 界で圧倒的 1 位。2012 年に Mike Perham が開始、2026 年も活発に開発が続いている。

# Gemfile
gem 'sidekiq'
gem 'sidekiq-scheduler'   # または sidekiq-cron

# config/sidekiq.yml
:schedule:
  daily_report:
    cron: '0 9 * * 1-5'
    class: DailyReportJob
    queue: reports

# app/jobs/daily_report_job.rb
class DailyReportJob
  include Sidekiq::Job
  sidekiq_options queue: :reports, retry: 3

  def perform
    ReportMailer.daily.deliver_now
  end
end

Sidekiq の派生。

  • Sidekiq(OSS): 無料。Redis。Web UI 標準。
  • Sidekiq Pro: 有料。信頼性強化(reliable fetch、batches)。
  • Sidekiq Enterprise: さらに高価。ユニークジョブ、定期ジョブ内蔵(別 gem 不要)、マルチプロセス、マルチ DC。

Mike Perham のビジネスモデルは、OSS と有料版を単一メンテナが運営する好例としてよく引用される。

Celery Beat — Python

Celery は Python のバックグラウンドキューの標準。Celery Beat がそのスケジューラコンポーネント。

# celery_app.py
from celery import Celery
from celery.schedules import crontab

app = Celery('myapp', broker='redis://localhost:6379/0')

app.conf.beat_schedule = {
    'daily-report': {
        'task': 'tasks.generate_report',
        'schedule': crontab(hour=9, minute=0, day_of_week='1-5'),
    },
    'every-15-min-cleanup': {
        'task': 'tasks.cleanup',
        'schedule': 900.0,  # 秒単位
    },
}
app.conf.timezone = 'Asia/Tokyo'

# tasks.py
@app.task
def generate_report():
    ...

実行。

# ワーカー
celery -A celery_app worker -l info

# Beat スケジューラ(単一インスタンスのみ)
celery -A celery_app beat -l info

Celery Beat の落とし穴 — Beat は単一ノード だけにすること。2 つのノードで立ち上げると、すべてのジョブがキューに 2 回入る。分散ロックが欲しければ celery-redbeat というバックエンドを使う(Redis 分散ロックで Beat が 2 つ生きていてもキューに 1 回しか入らない)。


10. RQ / dramatiq / APScheduler — Python の選択肢

Celery 以外にも、Python にはより軽い代替がいくつかある。

RQ(Redis Queue)

最もシンプルな Python ジョブキュー。Celery より圧倒的に簡単だが、その分機能も少ない。

from rq import Queue
from rq_scheduler import Scheduler
from redis import Redis
from datetime import datetime

scheduler = Scheduler(connection=Redis())

scheduler.cron(
    '0 9 * * 1-5',
    func=generate_report,
    queue_name='reports',
)

rq-scheduler パッケージが cron 表記をサポート。RQ が向く場面: 単一ホスト、単一ワーカー、軽量なバックグラウンド処理。

dramatiq

RQ より頑健、Celery より単純な中間。RabbitMQ 推奨だが Redis も使える。

import dramatiq
from dramatiq.brokers.rabbitmq import RabbitmqBroker
from dramatiq_crontab import cron

dramatiq.set_broker(RabbitmqBroker())

@cron('0 9 * * 1-5')
@dramatiq.actor
def daily_report():
    ...

dramatiq の強み: リトライバックオフ、デッドレター、ミドルウェアシステムが綺麗。Celery が重すぎ、RQ が軽すぎると感じるチームの選択肢。

APScheduler

ワーカーが別途要らない アプリ内インプロセススケジューラ。Flask・FastAPI から直接呼ぶ。

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

scheduler = AsyncIOScheduler(timezone='Asia/Tokyo')
scheduler.add_job(
    daily_report,
    CronTrigger.from_crontab('0 9 * * 1-5')
)
scheduler.start()

APScheduler が向く場面: 1〜2 プロセスの小規模アプリで、外部 Redis/RabbitMQ を置きたくない場合。限界: 分散ロックが弱い、プロセスが死ぬと欠落。SQLAlchemy / MongoDB / Redis のジョブストアがあるが、いずれも強整合性ではない。

選択ガイド。

ツールバックエンド分散複雑度適合
Celery + BeatRedis / RabbitMQYes(Beat は 1 個のみ)大規模アプリ、多様なワークロード
RQ + rq-schedulerRedis単一ホスト、軽量ジョブ
dramatiqRabbitMQ / RedisYes信頼性 + 単純さ
APSchedulerインプロセスNo非常に低1〜2 プロセスの小アプリ

11. Airflow / Dagster / Prefect / Temporal / Inngest / Trigger.dev — ワークフロー

ワークフローエンジンは cron の自然な進化形。多段階 + リトライ + 状態追跡 + 可視化 が中心。このカテゴリは別記事で深掘りしたので、ここでは cron 視点に絞って整理する。

Apache Airflow

データエンジニアリングの標準。DAG(Directed Acyclic Graph)の中に schedule_interval を置く。

from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime

with DAG(
    'daily_etl',
    schedule_interval='0 2 * * *',
    start_date=datetime(2026, 1, 1),
    catchup=False,
    tags=['etl'],
) as dag:
    extract = PythonOperator(task_id='extract', python_callable=do_extract)
    transform = PythonOperator(task_id='transform', python_callable=do_transform)
    load = PythonOperator(task_id='load', python_callable=do_load)

    extract >> transform >> load

Airflow の強みは可視化と catchup(欠落時刻のバックフィル)。弱みは運用の複雑さと重いスケジューラ。

Dagster

アセットベースのモデル。cron だけでなく、データアセットが古くなったとき に発火させるパターンが強力。

from dagster import asset, ScheduleDefinition, define_asset_job

@asset
def daily_metrics():
    ...

daily_job = define_asset_job('daily_metrics_job', selection=[daily_metrics])
daily_schedule = ScheduleDefinition(
    job=daily_job,
    cron_schedule='0 2 * * *',
    execution_timezone='Asia/Tokyo',
)

Prefect

Python の関数デコレータでワークフローを定義。cron 以外に interval、rrule(反復)も対応。

from prefect import flow, task
from prefect.deployments import Deployment
from prefect.client.schemas.schedules import CronSchedule

@task
def fetch(): ...

@flow
def daily_pipeline():
    fetch()

Deployment.build_from_flow(
    flow=daily_pipeline,
    name='daily',
    schedule=CronSchedule(cron='0 2 * * *', timezone='Asia/Tokyo'),
).apply()

Temporal Schedules

Temporal はワークフローエンジン界の新星。2023 年に Schedules API が正式に出て、cron + ワークフローの信頼性を統合した。

import { Client } from '@temporalio/client'

const client = new Client()

await client.schedule.create({
  scheduleId: 'daily-report',
  spec: {
    cronExpressions: ['0 9 * * 1-5'],
    timezoneName: 'Asia/Tokyo',
  },
  action: {
    type: 'startWorkflow',
    workflowType: 'DailyReportWorkflow',
    workflowId: 'daily-report',
    taskQueue: 'reports',
  },
  policies: {
    overlap: 'SKIP',  // 前のワークフローが終わっていなければスキップ
    catchupWindow: '1h',
  },
})

Temporal Schedules の強みは ワークフロー自体が永続的 であること。cron が発火してワークフローが始まれば、そのワークフローはホストが死んでも別のホストで継続実行される。本物の「fire and forget」。

Inngest Crons

サーバーレス関数 / イベント駆動ワークフロー。cron 表記のほかイベント駆動トリガーも持つ。

import { inngest } from './client'

export const dailyReport = inngest.createFunction(
  { id: 'daily-report' },
  { cron: 'TZ=Asia/Tokyo 0 9 * * 1-5' },
  async ({ step }) => {
    const data = await step.run('fetch', fetchData)
    await step.run('send', () => sendReport(data))
  }
)

Trigger.dev v3

React 風のワークフローコード。cron 以外に cron.listschedules.create() のような動的スケジュール API も提供。

import { schedules } from '@trigger.dev/sdk/v3'

export const dailyReport = schedules.task({
  id: 'daily-report',
  cron: { pattern: '0 9 * * 1-5', timezone: 'Asia/Tokyo' },
  run: async (payload) => {
    // payload.timestamp で発火時刻を受け取る
  }
})

12. Hatchet — 新星ワークフロー

Hatchet は 2024 年に登場したワークフローエンジン。PostgreSQL 上にジョブキュー + ワークフロー + スケジュールを統合したのが特徴。

import { Hatchet } from '@hatchet-dev/typescript-sdk'

const hatchet = Hatchet.init()

hatchet.workflow({
  id: 'daily-report',
  on: { cron: '0 9 * * 1-5' },
  steps: [
    {
      name: 'fetch-data',
      run: async (ctx) => fetchData(),
    },
    {
      name: 'send-email',
      parents: ['fetch-data'],
      run: async (ctx) => {
        const data = ctx.stepOutput('fetch-data')
        await sendEmail(data)
      },
    }
  ]
})

Hatchet の差別化ポイント。

  • PostgreSQL 単一依存: Redis・Kafka・NATS のような別依存は不要。Postgres 1 インスタンスでキュー + ジョブ保存 + ワークフロー状態のすべて。
  • セルフホスト向き: 単一 Docker イメージでフルスタックを動かせる。
  • OpenTelemetry 内蔵: すべてのジョブ / ワークフローがトレースとして出る。
  • 有料 / 無料: OSS は無料、Hatchet Cloud がマネージド版。

2026 年で Hatchet が向くのは: PostgreSQL を既に運用している小〜中規模チームが Temporal より軽い選択肢を探すとき。


13. Quartz(Java) / Spring @Scheduled / Hangfire(.NET)

JVM と .NET 界の cron。

Quartz Scheduler

JVM 界の古典的スケジューリングライブラリ。1999 年からあり、2026 年も活発。

import org.quartz.*;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;

public class DailyReportJob implements Job {
    public void execute(JobExecutionContext ctx) {
        // 処理
    }
}

JobDetail job = newJob(DailyReportJob.class)
    .withIdentity("dailyReport", "reports")
    .build();

CronTrigger trigger = (CronTrigger) newTrigger()
    .withIdentity("dailyReportTrigger", "reports")
    .withSchedule(cronSchedule("0 0 9 ? * MON-FRI").inTimeZone(TimeZone.getTimeZone("Asia/Tokyo")))
    .build();

scheduler.scheduleJob(job, trigger);

Quartz は JDBC JobStore でジョブ状態を RDB に永続化し、クラスタモード(複数のスケジューラインスタンスが同じ DB を共有)にも対応。AWS EventBridge や BullMQ のようなマネージドより精緻な制御が必要なときに。

Spring @Scheduled

Spring Boot ならアノテーション 1 つで cron ジョブを作れる。

@Component
public class ScheduledTasks {

    @Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Asia/Tokyo")
    public void dailyReport() {
        // 処理
    }

    @Scheduled(fixedRate = 60000)
    public void healthcheck() {
        // 60 秒おき
    }
}

main クラスに @EnableScheduling を付ければ有効になる。Spring の cron 表記は 6 フィールド(秒 分 時 日 月 曜日) に注意。POSIX cron とは違う。

分散環境では ShedLock ライブラリで分散ロックを足し、複数インスタンスで同じジョブが 2 回回らないようにする。

Hangfire — .NET

.NET 界のバックグラウンドジョブ + スケジュールライブラリ。

// Startup.cs
services.AddHangfire(c => c.UseSqlServerStorage(connStr));
services.AddHangfireServer();

// Program / Controller
RecurringJob.AddOrUpdate<IDailyReportService>(
    "daily-report",
    svc => svc.SendReport(),
    "0 9 * * 1-5",
    TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time")
);

Hangfire の強み。

  • SQL Server / PostgreSQL / Redis バックエンドが選べる。
  • ダッシュボード: ジョブ状態、失敗、リトライをブラウザで確認。
  • 自動リトライ: 失敗時の指数バックオフ。
  • OSS 無料 + Hangfire Pro 有料

Hangfire は .NET 界の事実上の標準だ。

Castle Windsor のような IoC コンテナとの統合も綺麗で、依存性注入が整理しやすい。


14. fcron / anacron — Linux の代替

cron の代替が 2 つ、Linux 界には残っている。

anacron

ホストが常時起動とは限らない環境(ノート PC、デスクトップ)向けの cron 補完。ジョブの予定時刻にホストが落ちていれば、次に起きたときに 1 回だけ実行してくれる。

# /etc/anacrontab
# period delay job-identifier command
1   5   cron.daily   run-parts /etc/cron.daily
7   25  cron.weekly  run-parts /etc/cron.weekly
@monthly 45 cron.monthly run-parts /etc/cron.monthly

period は日数、delay は起動後の待機分数、job-identifier は最終実行を追跡する名前。systemd timers の Persistent=true が同じ機能をより綺麗にこなす。

fcron

cron と anacron の統合を目指したもの。1999 年から開発されており、「多様な環境を 1 ツールで」が目標。

# fcrontab の例
@daily,mailto=ops backup.sh
@reboot,nice=10 startup-checks.sh
%every 10 minutes after boot * * * * /usr/local/bin/poll.sh

fcron の差別化ポイント。

  • @daily%hours%mins など人間に読みやすい独自表記。
  • 起動後 N 分のような相対時刻指定。
  • 落ちていた間のジョブの追いつき。
  • ジョブの同時実行制限のようなリソース管理。

2026 年現在、fcron はニッチなツール。systemd timers が実質同じ機能を提供しつつ、より深いシステム統合を持つから。それでも一部の Debian / Gentoo 環境ではまだ使われている。


15. Healthchecks.io / Cronitor.io — 監視

cron の「黙って失敗する」問題を解くデッドマンスイッチサービス。ジョブが「完了した」と ping を送らねばならず、送らないとアラート。別記事でより深く触れているので、ここでは使用パターンに絞る。

Healthchecks.io

OSS + マネージド。無料プランが手厚い(チェック 20 個まで)。

# crontab
0 9 * * 1-5 /usr/local/bin/report.sh && curl -fsS --retry 3 https://hc-ping.com/PROJECT_ID/daily-report > /dev/null

または開始 / 終了 / 失敗の 3 つに ping を分ける。

#!/bin/bash
URL="https://hc-ping.com/PROJECT_ID/daily-report"
curl -fsS --retry 3 "$URL/start"
if /usr/local/bin/report.sh; then
  curl -fsS --retry 3 "$URL"
else
  curl -fsS --retry 3 "$URL/fail"
fi

/start と終了 ping の間の時間で実行時間を測る。/fail で失敗アラート。Slack・メール・PagerDuty 統合あり。

Cronitor.io

商用 SaaS。より精緻なメトリクス、アラートルーティング。Healthchecks.io のマネージド競合。

# Cronitor も同じパターン
0 9 * * 1-5 cronitor exec daily-report /usr/local/bin/report.sh

cronitor CLI が開始 / 終了 / 失敗 ping を自動で送る。

運用推奨: 重要な cron すべてに監視を仕掛ける。2026 年の SRE 合意は「ping のない cron は運用負債」。


16. Nomad periodic jobs

HashiCorp Nomad の cron 相当。コンテナ / バイナリ / Java / Docker / QEMU など多様なワークロードタイプに対応するのが差別化。

job "daily-cleanup" {
  type = "batch"
  periodic {
    cron             = "0 2 * * *"
    time_zone        = "Asia/Tokyo"
    prohibit_overlap = true
  }

  group "cleanup" {
    task "run" {
      driver = "docker"
      config {
        image = "registry.example.com/cleanup:1.4.2"
        command = "/app/cleanup.sh"
      }
      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}

Nomad の強み。

  • 単一バイナリ: HashiCorp 標準の運用モデル。
  • 多様な driver: Docker だけでなく raw_exec、java、qemu、podman など。
  • Consul / Vault 統合: サービスディスカバリとシークレット。
  • k8s より単純: 小規模チームでも運用しやすい。

Kubernetes CronJob と比べると、Nomad は 小〜中規模環境 に向く。大規模 SaaS は k8s が優勢。


17. 韓国 / 日本 — トス、カカオ、メルカリ

トス — cron インフラ

トスは SLASH カンファレンスで「分散 cron インフラ」のセッションを何度か出している。要点。

  • 自前 ScheduleJob プラットフォーム: トス内部の cron マネージャ。数千個の cron ジョブを中央管理。
  • 分散ロック: 同一ジョブが 2 ホストで走らないように Redis または ZooKeeper の分散ロック。
  • k8s CronJob の上の抽象化レイヤ: 開発者は YAML ではなく UI で登録。
  • 監視自動化: ジョブ登録時に自動でデッドマンスイッチまで作る。
  • タイムゾーン統合: すべてのジョブが KST。UTC 変換はプラットフォームが担当。

トスの核心: 「cron ジョブが数百を超えたら、ツールではなく プラットフォーム が要る」。

カカオ — タスクスケジューリング

カカオは if(kakao) カンファレンスでタスクスケジューリングのインフラを共有している。

  • カカオトークプッシュ: 定時送信プッシュは独立スケジューラ。Quartz の上に自前で構築。
  • データパイプライン: Airflow + 自前ラッパー。
  • 広告精算: Sidekiq 風のジョブキュー + cron の組み合わせ。

カカオの教訓: 「1 つのツールではすべてをカバーできない。ワークロードごとに最も適したツールをモザイクのように組み合わせる」が現実解。

メルカリ — Scheduled Jobs

メルカリ日本は Microservices 運用の代表例として有名。

  • Google Cloud Tasks + Cloud Scheduler: GCP 中心のインフラ。
  • k8s CronJob: インフラ整理タスク。
  • 各マイクロサービス内のインプロセススケジュール: 軽量なジョブはサービス自身が処理。

メルカリのエンジニアリングブログ(英語)が定期的に運用事例を公開している。


18. 誰が何を選ぶべきか — シンプル / 分散 / バックグラウンド / 信頼性

2026 年の cron 選定をデシジョンツリーにまとめると。

ジョブは 1 ホスト・1 コマンドで完結?
├── YES → systemd timers(cron ではなく)
│        + Healthchecks.io ping
└── NO →
    インフラはサーバーレス?
    ├── Vercel ベース → Vercel Cron
    ├── AWS ベース   → EventBridge Scheduler
    ├── Cloudflare   → Workers Cron Triggers
    └── コンテナ     → k8s CronJob または Nomad periodic

ジョブはアプリ内バックグラウンドの一部?
├── Node       → BullMQ
├── Ruby       → Sidekiq
├── Python     → Celery Beat(大)/ RQ(小)/ dramatiq(中)
├── Java       → Quartz / Spring @Scheduled
└── .NET       → Hangfire

ジョブは多段階ワークフロー?
├── データ     → Airflow / Dagster / Prefect
├── 信頼性     → Temporal
├── サーバーレス → Inngest / Trigger.dev v3
└── Postgres のみ → Hatchet

CI 活動に紐づく?
└── GitHub Actions schedule / GitLab schedule pipelines

ルール化

  1. 単純なジョブ + 1 ホスト → systemd timers。
  2. クラウドマネージド → Vercel / EventBridge / Cloudflare のうちインフラ依存に合うもの。
  3. アプリ統合 → 言語別の標準(BullMQ / Sidekiq / Celery Beat / Quartz / Hangfire)。
  4. ワークフロー信頼性 → Temporal / Hatchet。
  5. データパイプライン → Airflow / Dagster / Prefect。
  6. 何を使っても → Healthchecks.io か Cronitor.io で監視。

2026 年の cron 運用は「1 ツールですべて」ではなく、「ワークロードごとに最適なツールを選び、すべてに監視を付ける」 が標準だ。


参考 / References