# バッチ設計書 5-Ci_StuckBuilds_DropScheduledWorker

## 概要

本ドキュメントは、スケジュール状態で停滞したビルドを削除するバッチ処理「Ci::StuckBuilds::DropScheduledWorker」の設計仕様を記載する。

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

Ci::StuckBuilds::DropScheduledWorkerは、スケジュール（scheduled）状態のまま1時間以上経過したCIビルドを検出し、失敗（failed）状態に変更するバッチ処理である。遅延実行が設定されたジョブが、スケジュール時刻を過ぎても開始されない異常状態をクリーンアップする。

**業務上の目的・背景**：CI/CDパイプラインでは、`when: delayed`ディレクティブにより、ジョブの実行を一定時間遅延させることができる。これらのジョブは「scheduled」状態で待機し、指定時刻になると「pending」状態に移行して実行される。しかし、システムの異常やタイミングの問題で、スケジュール時刻を1時間以上過ぎてもスケジュール状態のまま残るジョブが発生することがある。本バッチは、このような「古くなったスケジュールジョブ」を検出し、失敗させることでパイプラインの健全性を維持する。

**バッチの実行タイミング**：StuckCiJobsWorkerから呼び出され、30分後に実行される。親ワーカーは毎時0分に実行されるため、本ワーカーは毎時30分頃に実行される。

**主要な処理内容**：
1. 排他ロック（ExclusiveLeaseGuard）を取得し、同時実行を防止
2. scheduled_at（スケジュール実行予定時刻）が1時間以上前のビルドを検索
3. 検出されたビルドを100件ずつバッチ処理で失敗状態に更新
4. failure_reasonとして`stale_schedule`を設定

**前後の処理との関連**：本ワーカーはStuckCiJobsWorkerから呼び出される。StuckCiJobsWorkerは親ワーカーとして、DropRunningWorker、本ワーカー、DropCancelingWorkerを時間をずらして実行する。

**影響範囲**：遅延実行が設定されたCI/CDジョブに影響。ジョブが失敗状態に変更されることで、パイプラインの状態が更新され、関連する通知やステータス表示に影響する。

## バッチ種別

データクレンジング / システムメンテナンス

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 60分間隔（親ワーカー経由） |
| 実行時刻 | 毎時30分頃（StuckCiJobsWorkerから30分後） |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | StuckCiJobsWorkerからのperform_in |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| 排他ロック取得 | ExclusiveLeaseGuardによるロック取得が可能であること |
| 停滞ビルド存在 | scheduled_atが1時間以上前のスケジュール状態ビルドが存在すること |

### 実行可否判定

排他ロック（ExclusiveLeaseGuard）を取得できた場合のみ処理を実行する。ロックのTTLは30分。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | 親ワーカーから引数なしで呼び出し |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| ci_builds | DB | CIビルドテーブル |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| ci_builds | DB | ステータス更新（failed）、failure_reason設定 |

### 出力ファイル仕様

ファイル出力なし。

## 処理フロー

### 処理シーケンス

```
1. 排他ロック取得
   └─ ExclusiveLeaseGuard.try_obtain_leaseでロック取得（TTL: 30分）
2. ログ出力
   └─ "Cleaning scheduled, timed-out builds"をAppLoggerに出力
3. 停滞ビルド取得
   └─ Ci::Build.scheduled.scheduled_at_before(1.hour.ago)
4. バッチドロップ処理
   └─ drop(builds, failure_reason: :stale_schedule)
5. ロック解放
   └─ 処理完了後自動解放
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{排他ロック取得}
    B -->|成功| C[停滞ビルド検索]
    B -->|失敗| G[バッチ終了]
    C --> D{ビルド存在?}
    D -->|なし| G
    D -->|あり| E[ビルドをdrop処理]
    E --> F{次のビルド?}
    F -->|あり| E
    F -->|なし| G
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 停滞ビルド取得 | ci_builds | SELECT | scheduled状態でscheduled_atが1時間以上前のビルドを取得 |
| ビルドステータス更新 | ci_builds | UPDATE | statusをfailedに更新 |

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

#### ci_builds

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全カラム | status='scheduled', scheduled_at < 1.hour.ago | バッチサイズ100件 |
| UPDATE | status, failure_reason | failed, stale_schedule | drop!メソッドで更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ExclusiveLease取得失敗 | 別プロセスがロック保持中 | スキップ |
| - | StandardError | ビルドdrop処理中のエラー | doom!で代替処理、エラートラッキング |
| - | OptimisticLockingエラー | 同時更新の競合 | 3回リトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 3回（OptimisticLocking） |
| リトライ間隔 | 即時 |
| リトライ対象エラー | OptimisticLockingエラー |

### 障害時対応

1. ロック取得失敗の場合：次回実行時に自動復旧
2. ビルドdrop失敗の場合：doom!メソッドで強制終了状態にマーク
3. エラー発生の場合：Gitlab::ErrorTrackingでSentryに報告

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | レコード単位 |
| コミットタイミング | 各ビルドのdrop!完了時 |
| ロールバック条件 | drop!処理中のエラー発生時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | バッチサイズ100件ずつ処理 |
| 目標処理時間 | 30分以内（ロックTTL） |
| メモリ使用量上限 | バッチサイズ100件による制御 |

## 排他制御

- ExclusiveLeaseGuard（TTL: 30分）による排他制御
- 同時に1プロセスのみ実行可能
- 親ワーカー（StuckCiJobsWorker）とは別のロックを使用

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | サービス開始時 | "Cleaning scheduled, timed-out builds" |
| 進捗ログ | ビルドdrop時 | "Dropping outdated build", build_id, runner_id, status, failure_reason |
| エラーログ | エラー発生時 | 例外情報、build詳細 |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 30分超過 | 運用チーム |
| エラー件数 | 多発時 | 運用チーム |
| 削除ジョブ数 | 異常増加時 | 運用チーム |

## 備考

- `idempotent!`が設定されており、冪等性が保証されている
- `data_consistency :always`が設定されており、プライマリDBを使用
- インスタンス全体のクリーンアップクエリのため、CronWorkerContextは無効化
- BUILD_SCHEDULED_OUTDATED_TIMEOUT = 1.hour で停滞判定
- failure_reasonは`stale_schedule`（古いスケジュール）を使用
- DropRunningWorkerとは異なり、パーティションを考慮しない処理
