# バッチ設計書 2-handleSchedules

## 概要

本ドキュメントは、Payloadのジョブキューシステムにおけるスケジュール管理バッチ処理「handleSchedules」の設計仕様を定義する。このバッチは、タスクとワークフローのスケジュール設定に基づきジョブをキューに追加する責務を担う。

### 本バッチの処理概要

handleSchedulesバッチは、cron式で定義されたスケジュールに基づいて、タスクやワークフローのジョブを自動的にキューに追加するバッチ処理である。

**業務上の目的・背景**：定期的なデータ処理、レポート生成、通知送信などの業務要件に対応するため、あらかじめ設定されたスケジュールに従ってジョブを自動的にキューイングする必要がある。handleSchedulesバッチは、この定期実行の仕組みを提供し、運用の自動化と効率化を実現する。

**バッチの実行タイミング**：autoRun設定のcronに従って定期実行される。Vercelなどのサーバーレス環境では、Vercel Cron経由で呼び出すことも可能。デフォルトでは、autoRunが有効な場合にジョブ実行前に自動的に呼び出される。

**主要な処理内容**：
1. スケジュール設定を持つタスク・ワークフローの抽出
2. payload-job-statsグローバルから最終スケジュール実行情報を取得
3. cron式に基づく次回実行時刻の算出（cronerライブラリ使用）
4. beforeScheduleフックによるスケジュール実行可否判定
5. ジョブのキューイング（waitUntilに次回実行時刻を設定）
6. afterScheduleフックによる後処理

**前後の処理との関連**：本バッチでキューイングされたジョブは、runJobsバッチによって実際に実行される。autoRunが有効な場合、handleSchedulesはrunJobsの前に自動的に呼び出される。

**影響範囲**：payload-jobsコレクション（ジョブの追加）、payload-job-statsグローバル（スケジュール統計の更新）。

## バッチ種別

スケジュール管理処理（定期ジョブキューイング）

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | autoRun設定のcronに依存 |
| 実行時刻 | cron式による（デフォルト: `* * * * *`） |
| 実行曜日 | cron式による |
| 実行日 | cron式による |
| トリガー | cron/APIエンドポイント/autoRun前処理 |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| スケジュール設定存在 | タスクまたはワークフローにschedule設定が定義されていること |
| ジョブキュー有効化 | Payload設定でjobs設定が有効であること |
| stats有効化 | payload-job-statsグローバルが存在すること |

### 実行可否判定

スケジュール設定を持つタスク・ワークフローに対して、以下の条件を確認：
- cron式に基づく次回実行時刻が到来していること
- beforeScheduleフック（デフォルト：defaultBeforeSchedule）がshouldSchedule=trueを返すこと

defaultBeforeScheduleのデフォルト条件：
- 同一タスク/ワークフローの実行中ジョブが存在しないこと
- 同一タスク/ワークフローのスケジュール済み（未実行）ジョブが存在しないこと
- 同一タスク/ワークフローのリトライ可能な失敗ジョブが存在しないこと

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| allQueues | boolean | No | false | trueの場合、全キューのスケジュールを処理 |
| queue | string | No | 'default' | 対象キュー名 |
| req | PayloadRequest | Yes | - | Payloadリクエストオブジェクト |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| jobsConfig.tasks | 設定 | スケジュール設定を持つタスク一覧 |
| jobsConfig.workflows | 設定 | スケジュール設定を持つワークフロー一覧 |
| payload-job-stats | DB | スケジュール実行統計グローバル |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| payload-jobs | DB | キューイングされたジョブレコード |
| HandleSchedulesResult | オブジェクト | 処理結果（queued、skipped、errored） |

### 出力ファイル仕様

ファイル出力なし（データベース更新のみ）

## 処理フロー

### 処理シーケンス

```
1. スケジュール設定の抽出
   └─ getQueuesWithSchedules()でスケジュール付きタスク/ワークフローを取得
2. スケジュール設定が存在しない場合は早期リターン
   └─ 空のresultを返却
3. payload-job-statsグローバルの取得
   └─ 最終スケジュール実行情報を取得
4. 時間制約チェック（checkQueueableTimeConstraints）
   └─ cron式と最終実行時刻から次回実行時刻を算出
5. キューイング対象の絞り込み
   └─ 次回実行時刻が存在するもののみを抽出
6. 各キューイング対象に対してscheduleQueueableを実行
   └─ beforeScheduleフック実行、ジョブキューイング、afterScheduleフック実行
7. 結果の集計と返却
   └─ queued、skipped、erroredの配列を返却
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[スケジュール設定抽出]
    B --> C{スケジュール存在?}
    C -->|No| D[空の結果を返却]
    C -->|Yes| E[job-stats取得]
    E --> F[各スケジュールをループ]
    F --> G[時間制約チェック]
    G --> H{次回実行時刻到来?}
    H -->|No| F
    H -->|Yes| I[beforeScheduleフック]
    I --> J{shouldSchedule?}
    J -->|No| K[skippedに追加]
    J -->|Yes| L[ジョブキューイング]
    L --> M{成功?}
    M -->|No| N[erroredに追加]
    M -->|Yes| O[queuedに追加]
    O --> P[afterScheduleフック]
    K --> P
    N --> P
    P --> F
    F --> Q{全スケジュール処理完了?}
    Q -->|No| F
    Q -->|Yes| R[結果返却]
    R --> S[バッチ終了]
```

## データベース操作仕様

### 操作別データベース影響一覧

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 統計取得 | payload-job-stats | SELECT | 最終スケジュール実行情報の取得 |
| ジョブ追加 | payload-jobs | INSERT | スケジュールされたジョブの追加 |
| 統計更新 | payload-job-stats | UPDATE | afterScheduleフックで更新（デフォルト動作） |

### テーブル別操作詳細

#### payload-job-stats

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | stats.scheduledRuns.queues | キュー別の最終スケジュール実行情報 | |
| UPDATE | stats.scheduledRuns.queues[queue].tasks[slug].lastScheduledRun | 現在時刻 | afterScheduleで更新 |
| UPDATE | stats.scheduledRuns.queues[queue].workflows[slug].lastScheduledRun | 現在時刻 | afterScheduleで更新 |

#### payload-jobs

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | taskSlug/workflowSlug | タスク/ワークフローのslug | |
| INSERT | queue | スケジュール設定のqueue値 | |
| INSERT | waitUntil | 次回実行時刻 | cronerで算出 |
| INSERT | input | beforeScheduleで指定されたinput | |
| INSERT | meta.scheduled | true | スケジュールによるキューイングを示す |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | スケジュールエラー | scheduleQueueable内でエラー発生 | erroredに追加、afterScheduleでエラー通知 |
| - | 設定エラー | タスク/ワークフロー設定が不正 | 設定ファイルの修正 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（スケジュール処理自体のリトライはなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

### 障害時対応

1. erroredに追加されたスケジュールを確認
2. afterScheduleフックでエラー情報を確認
3. 必要に応じて手動でジョブをキューイング

## トランザクション仕様

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | 個別スケジュール単位 |
| コミットタイミング | 各ジョブキューイング完了時 |
| ロールバック条件 | scheduleQueueable内でエラー発生時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | スケジュール設定数に依存 |
| 目標処理時間 | 数秒以内（設定数に比例） |
| メモリ使用量上限 | 制限なし |

## 排他制御

- defaultBeforeScheduleによる重複防止：同一タスク/ワークフローの実行中・スケジュール済みジョブが存在する場合はスキップ
- カスタムbeforeScheduleフックで独自の排他制御ロジックを実装可能

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| - | - | デフォルトではログ出力なし |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| erroredスケジュール数 | 設定による | 運用チーム |
| スケジュール実行間隔 | 設定による | 運用チーム |

## 備考

- cronerライブラリを使用してcron式を解析
- cron式の形式：`秒 分 時 日 月 曜日`（秒はオプション）
- Vercel等のサーバーレス環境では、autoRunではなくVercel Cron経由での呼び出しを推奨
- beforeScheduleフックでshouldSchedule=falseを返すことで、条件付きスケジュールを実現可能
- afterScheduleフックで統計更新やカスタム通知を実装可能
