# バッチ設計書 24-model:prune

## 概要

本ドキュメントは、Laravelフレームワークのモデルプルーニングコマンド `model:prune` の設計仕様を定義します。

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

`model:prune` コマンドは、Eloquentモデルに定義されたプルーニング条件に基づいて、不要になったレコードを自動的に削除するバッチ処理です。`Prunable` または `MassPrunable` トレイトを実装したモデルを対象に、アプリケーションで不要となった古いデータを効率的にクリーンアップします。

**業務上の目的・背景**：アプリケーションの運用が長期化すると、ログデータ、一時データ、期限切れデータなど、不要なレコードがデータベースに蓄積されます。これらのデータはストレージコストの増大、クエリパフォーマンスの低下、バックアップ時間の増加などの問題を引き起こします。本バッチは、モデルごとに定義されたプルーニング条件（例：30日以上前のログ）に基づいて、これらの不要データを定期的に削除することで、データベースの健全性を維持します。

**バッチの実行タイミング**：主に以下のタイミングで実行されます。
- 日次の定期メンテナンスジョブとして（Scheduler経由）
- データクリーンアップが必要な運用メンテナンス時
- ストレージ使用量の監視アラート発生時

**主要な処理内容**：
1. プルーニング対象モデルの検出（`Prunable`/`MassPrunable` トレイト実装モデル）
2. 各モデルに対してプルーニング処理を実行
3. `--pretend` オプション時は削除対象件数のみ表示
4. 削除結果をコンソールに出力
5. `ModelPruningStarting`、`ModelsPruned`、`ModelPruningFinished` イベントを発火

**前後の処理との関連**：通常、日次バッチ処理の一部としてSchedulerで定期実行されます。データベースバックアップ処理の前後に実行することで、バックアップサイズの最適化が可能です。

**影響範囲**：`Prunable` または `MassPrunable` トレイトを実装し、`prunable()` メソッドで条件を定義したすべてのモデルのレコードが削除対象となります。

## バッチ種別

データクレンジング / 定期メンテナンス

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 日次（推奨） |
| 実行時刻 | 深夜帯（低負荷時間帯推奨） |
| 実行曜日 | 毎日 |
| 実行日 | - |
| トリガー | cron / 手動 |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Prunableモデル | `Prunable` または `MassPrunable` トレイトを実装したモデルが存在すること |
| prunable()実装 | 対象モデルに `prunable()` メソッドが実装されていること |
| DELETE権限 | 対象テーブルへのDELETE権限を持つこと |

### 実行可否判定

1. 対象モデルが1つ以上存在すること
2. `--model` と `--except` オプションは同時指定不可

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| --model | array | No | [] | プルーニング対象のモデルクラス名（複数指定可） |
| --except | array | No | [] | プルーニング対象から除外するモデルクラス名（複数指定可） |
| --path | array | No | app/Models | モデルが配置されているディレクトリパス |
| --chunk | int | No | 1000 | 1回のチャンクで処理するモデル数 |
| --pretend | bool | No | false | 実際には削除せず、削除対象件数のみ表示 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| モデルクラス | PHP Class | Prunable/MassPrunableトレイトを実装したEloquentモデル |
| データベーステーブル | DB | 各モデルに対応するテーブル |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| データベース | DB DELETE | 対象レコードの削除 |
| コンソール | 標準出力 | 削除件数の出力 |

### 出力ファイル仕様

ファイル出力なし

## 処理フロー

### 処理シーケンス

```
1. モデル検出
   └─ Finder/オプションから対象モデルを特定
2. オプション検証
   └─ --modelと--exceptの同時指定チェック
3. Prunableモデルフィルタリング
   └─ isPrunable()でフィルタリング
4. プルーニング実行前イベント
   └─ ModelPruningStartingイベント発火
5. 各モデルのプルーニング
   └─ pretend: 件数表示のみ
   └─ 実行: pruneAll()で削除
6. プルーニング完了イベント
   └─ ModelPruningFinishedイベント発火
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[対象モデル検出]
    B --> C{モデルあり?}
    C -->|なし| D[No prunable models found.]
    C -->|あり| E{--pretend?}
    E -->|Yes| F[各モデルの削除対象件数表示]
    F --> G[終了]
    E -->|No| H[ModelPruningStartingイベント]
    H --> I[ModelsPrunedイベントリスナー登録]
    I --> J[各モデルでpruneModel実行]
    J --> K[ModelPruningFinishedイベント]
    K --> G
    D --> G
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| プルーニング | Prunableモデルテーブル | DELETE | prunable()条件に合致するレコード削除 |
| ソフトデリート | SoftDeletesモデル | DELETE (force) | forceDelete()による完全削除 |

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

#### Prunableトレイト使用モデル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | prunable()メソッドで定義されたクエリ条件 | chunkById()で分割処理 |

#### MassPrunableトレイト使用モデル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | prunable()メソッドで定義されたクエリ条件 | バルクDELETEで効率的に処理 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| InvalidArgumentException | オプション競合 | --modelと--exceptを同時指定 | どちらか一方のみ指定 |
| LogicException | prunable未実装 | prunable()メソッドが未実装 | モデルにprunable()を実装 |
| Throwable | 削除エラー | レコード削除時のエラー | ExceptionHandlerでレポート後、次レコードへ継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | レコード単位でエラー発生時は次レコードへ継続 |
| リトライ間隔 | - |
| リトライ対象エラー | Prunable使用時の個別レコード削除エラー |

### 障害時対応

1. エラーログを確認し、原因を特定
2. Prunableトレイトの場合、個別エラーはExceptionHandlerで報告され処理継続
3. MassPrunableの場合、バルク削除のためエラー時は処理中断
4. モデルの `prunable()` メソッドの条件を確認

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | トレイト実装に依存 |
| コミットタイミング | Prunable: 各レコード削除後 / MassPrunable: チャンク単位 |
| ロールバック条件 | トレイト実装に依存 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | モデル・データ量に依存 |
| 目標処理時間 | データ量・チャンクサイズに依存 |
| メモリ使用量上限 | チャンクサイズで制御（デフォルト1000） |

## 排他制御

- 同時実行：可能（ただし同一モデルへの同時実行は競合の可能性）
- ロック機構：コマンドレベルでのロックなし
- 注意：Prunableはレコード単位、MassPrunableはバルク処理のため特性が異なる

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 情報ログ | モデル検出なし | "No prunable models found." |
| 情報ログ | --pretend時 | "{count} [{model}] records will be pruned." |
| 進捗ログ | 各モデル処理時 | "Pruning [{model}] records." |
| 進捗ログ | 各チャンク処理後 | "{model}: {count} records" |
| 情報ログ | 対象なし | "No prunable [{model}] records found." |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 削除件数 | 異常に多い場合 | 運用担当者 |
| 処理時間 | アプリケーション設定に依存 | - |
| エラー件数 | 1件以上 | 開発者 |

## 備考

- `Prunable` トレイトはレコードを1件ずつ削除し、各レコードで `prune()` メソッドと `pruning()` フックが呼ばれます
- `MassPrunable` トレイトはバルクDELETEを使用するため高速ですが、個別のフックは呼ばれません
- ソフトデリートが有効なモデルでは、`withTrashed()` を含めて取得し、`forceDelete()` で完全削除します
- モデルに `$prunableChunkSize` プロパティを定義することで、モデル固有のチャンクサイズを設定できます
- `ModelPruningStarting`、`ModelsPruned`、`ModelPruningFinished` イベントを活用して、プルーニングの監視や後処理を実装できます
