# バッチ設計書 5-WorkspaceCleanupThread

## 概要

本ドキュメントは、JenkinsのWorkspaceCleanupThreadバッチ処理の設計について記載する。このバッチはエージェント上の古いワークスペースをスキャンし、保持期間を過ぎたものを削除するクリーンアップ機能を提供する。

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

WorkspaceCleanupThreadは、Jenkins全体のジョブのワークスペースをスキャンし、最後に使用されてから保持期間（デフォルト30日）を過ぎた古いワークスペースディレクトリを削除することで、ディスク容量を回収するバッチ処理である。

**業務上の目的・背景**：Jenkinsのビルドでは、各ジョブがワークスペースディレクトリを使用してソースコードのチェックアウトやビルド処理を行う。ジョブの削除や設定変更により不要になったワークスペース、または長期間使用されていないワークスペースが残り続けると、ディスク容量を無駄に消費する。特に複数のエージェントを使用している環境では、同一ジョブのワークスペースが複数のエージェントに残存することがある。本バッチは、これらの古いワークスペースを定期的に削除することで、ストレージ容量を効率的に管理する。

**バッチの実行タイミング**：デフォルト24時間間隔で定期実行される。実行間隔はシステムプロパティ`hudson.model.WorkspaceCleanupThread.recurrencePeriodHours`で変更可能。

**主要な処理内容**：
1. 無効化設定のチェック
2. 全ノード（コントローラー＋エージェント）のリストを取得
3. 全TopLevelItemをイテレート
4. 各アイテムについて、各ノードのワークスペースをチェック
5. 削除条件を満たすワークスペースを削除
6. ワークスペースサフィックス（@2, @3等の並列ビルド用）も含めて削除

**前後の処理との関連**：独立したバッチ処理だが、SCMプラグインとの連携がある。削除前にSCM.processWorkspaceBeforeDeletion()を呼び出し、SCMが削除を拒否できる仕組みがある（例：ロックされたワークスペース）。

**影響範囲**：エージェント上のワークスペースディレクトリを削除する。削除されたワークスペースの内容は復元不可能だが、次回ビルド時に再作成される。

## バッチ種別

クリーンアップ / ディスク容量管理

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 24時間ごと（recurrencePeriodHours設定値） |
| 実行時刻 | 不定（Jenkins起動時からの経過時間に依存） |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | AsyncPeriodicWorkによる定期実行 |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Jenkinsが起動していること | AsyncPeriodicWorkの初期化後に自動スケジュール |
| disabledフラグがfalse | システムプロパティで無効化されていないこと |

### 実行可否判定

- `WorkspaceCleanupThread.disabled`がtrueの場合、処理をスキップ
- ワークスペースが最後のビルド先ノードにある場合は削除しない
- ジョブがビルド中の場合は削除しない
- SCMが削除を拒否した場合は削除しない

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| disabled | boolean | No | false | trueの場合、クリーンアップを実行しない。システムプロパティ `hudson.model.WorkspaceCleanupThread.disabled` で設定 |
| recurrencePeriodHours | int | No | 24 | 実行間隔（時間）。システムプロパティ `hudson.model.WorkspaceCleanupThread.recurrencePeriodHours` で設定 |
| retainForDays | int | No | 30 | ワークスペース保持日数。システムプロパティ `hudson.model.WorkspaceCleanupThread.retainForDays` で設定 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| Jenkins.get().allItems(TopLevelItem.class) | Iterable<TopLevelItem> | 全トップレベルアイテム |
| Jenkins.get().getNodes() | List<Node> | 全エージェントノード |
| Node.getWorkspaceFor(item) | FilePath | 各ノードでのワークスペースパス |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| ファイルシステム | ディレクトリ削除 | 古いワークスペースの削除 |
| ログファイル | テキスト | 処理結果（$JENKINS_HOME/logs/tasks/Workspace clean-up.log） |

### 出力ファイル仕様

| 項目 | 内容 |
|-----|------|
| ファイル名 | Workspace clean-up.log |
| 出力先 | $JENKINS_HOME/logs/tasks/ |
| 文字コード | UTF-8 |
| 区切り文字 | 改行 |

## 処理フロー

### 処理シーケンス

```
1. execute()メソッドが呼び出される
   └─ AsyncPeriodicWorkのタイマーにより定期実行
2. 無効化チェック
   └─ disabled == true の場合、ログ出力してreturn
3. ノードリストを構築
   └─ Jenkins自身 + 全エージェントノード
4. 全TopLevelItemをイテレート
   └─ ModifiableTopLevelItemGroupはスキップ（子アイテムが別途処理される）
5. 各アイテムについて全ノードをチェック
   └─ 5.1 ノードのワークスペースパス取得
   └─ 5.2 オフラインノードはスキップ
   └─ 5.3 shouldBeDeleted()で削除判定
   └─ 5.4 削除条件を満たす場合、CleanupOldWorkspaces実行
6. CleanupOldWorkspaces処理
   └─ 6.1 親ディレクトリ内の該当ワークスペースをフィルタリング
   └─ 6.2 保持期間超過＋名前パターン一致のディレクトリを削除
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{disabled?}
    B -->|Yes| C[ログ出力: Disabled]
    C --> D[バッチ終了]
    B -->|No| E[ノードリスト構築]
    E --> F[全TopLevelItemをイテレート]
    F --> G{ModifiableTopLevelItemGroup?}
    G -->|Yes| F
    G -->|No| H[各ノードをチェック]
    H --> I{ワークスペース取得可能?}
    I -->|No| H
    I -->|Yes| J{shouldBeDeleted?}
    J -->|No| H
    J -->|Yes| K[ログ出力: Deleting]
    K --> L[CleanupOldWorkspaces実行]
    L --> H
    H --> M{全ノード処理完了?}
    M -->|No| H
    M -->|Yes| N{全アイテム処理完了?}
    N -->|No| F
    N -->|Yes| D
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| なし | なし | なし | データベースへのアクセスは行わない |

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

データベース操作は存在しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| なし | IOException | ワークスペースチェック失敗 | ログ出力、次のアイテムへ続行 |
| なし | InterruptedException | 処理中断 | ログ出力、次のアイテムへ続行 |
| なし | IOException | ディレクトリ削除失敗 | ログ出力、次のアイテムへ続行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（次回の定期実行で再試行） |
| リトライ間隔 | 24時間（recurrencePeriodHours） |
| リトライ対象エラー | なし |

### 障害時対応

個別のワークスペース処理でエラーが発生しても、他のワークスペースの処理は継続される。エラーはログファイルに記録される。

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | なし（ファイルシステム操作） |
| コミットタイミング | なし |
| ロールバック条件 | なし |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | ジョブ数 × ノード数 |
| 目標処理時間 | 制限なし（AsyncPeriodicWorkで非同期実行） |
| メモリ使用量上限 | イテレーション処理のため最小限 |

## 排他制御

AsyncPeriodicWorkを継承しているため、前回の実行が完了する前に次回実行がトリガーされた場合、実行はスキップされる。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | execute開始時 | Started at {timestamp} |
| 進捗ログ | 各アイテムチェック時 | Checking {item.getFullDisplayName()} |
| 削除ログ | 削除実行時 | Deleting {ws} on {node.getDisplayName()} |
| 終了ログ | execute終了時 | Finished at {timestamp}. {duration}ms |
| エラーログ | エラー発生時 | Failed to check/delete {path} on {node} |
| スキップログ | 無効化時 | "Disabled. Skipping execution" (FINE) |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| なし | なし | なし |

## 備考

- シンボル名: `workspaceCleanup`（@Symbolアノテーション）
- ワークスペースサフィックス（例: job@2, job@3）はWorkspaceList.COMBINATORを使用して検出
- 削除判定の条件:
  1. 最後にビルドしたノードでない
  2. SCMが削除を拒否していない（processWorkspaceBeforeDeletion）
  3. ジョブがビルド中でない
  4. lastModifiedが保持期間を超過
- disabled, retainForDaysはScript Consoleから変更可能（@SuppressFBWarnings注釈）
- recurrencePeriodHoursはfinalで起動時に固定
- MasterToSlaveFileCallableを使用してエージェント上でファイル操作を実行
- DAY定数（86400000ミリ秒）を保持日数計算に使用
