# バッチ設計書 77-PruneOldEventsWorker

## 概要

本ドキュメントは、古いイベントレコードを削除するバッチ `PruneOldEventsWorker` の設計仕様を記載する。

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

このバッチは、3年以上前に作成されたイベントレコードを定期的に削除し、データベースのサイズを最適化するワーカーである。

**業務上の目的・背景**：GitLabでは、ユーザーのアクティビティ（コミット、マージリクエストの作成、コメントなど）がイベントとして `events` テーブルに記録される。これらのイベントはユーザーのコントリビューションカレンダー（最大12ヶ月分を表示）や活動履歴の表示に使用される。しかし、長期間運用されたインスタンスではイベントレコードが蓄積し、データベースの肥大化やクエリパフォーマンスの低下を引き起こす。このバッチは、表示に必要な期間（12ヶ月）を大幅に超える3年以上前のレコードを定期的に削除することで、データの整合性を保ちながらデータベースを最適化する。

**バッチの実行タイミング**：6時間ごと（`0 */6 * * *`）にCronジョブとして実行される。

**主要な処理内容**：
1. フィーチャーフラグ `ops_prune_old_events` の確認
2. フラグ有効時: 3年1日以上前のイベントを最大10,000件削除
3. フラグ無効時: ログ出力のみで処理をスキップ

**前後の処理との関連**：単独で動作し、他のバッチとの依存関係はない。

**影響範囲**：`events` テーブルからのレコード削除

## バッチ種別

データクレンジング / データ保持ポリシー実行

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 6時間ごと |
| 実行時刻 | 0, 6, 12, 18時 |
| 実行曜日 | 全曜日 |
| 実行日 | 毎日 |
| トリガー | cron（`0 */6 * * *`） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| フィーチャーフラグ | `ops_prune_old_events` が有効であること |
| データベース接続 | 読み書き可能な接続 |

### 実行可否判定

- `Feature.enabled?(:ops_prune_old_events, type: :ops)` が `true` の場合のみ削除処理を実行
- フラグ無効時はログ出力のみで終了

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | このワーカーはパラメータを受け取らない |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| events | DB | 削除対象のイベントレコード |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| events | DB | レコード削除 |
| Gitlab::AppLogger | ログ | フラグ無効時のログ出力 |

### 出力ファイル仕様

ファイル出力なし

## 処理フロー

### 処理シーケンス

```
1. フィーチャーフラグ確認
   └─ pruning_enabled? メソッドで確認
2a. フラグ有効時:
   └─ カットオフ日付計算（3年1日前）
   └─ Event.created_before(cutoff_date) でレコード取得
   └─ delete_with_limit(10000) で最大1万件削除
2b. フラグ無効時:
   └─ ログ出力: ":ops_prune_old_events is disabled, skipping."
   └─ 処理終了
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{フラグ有効?}
    B -->|No| C[ログ出力]
    B -->|Yes| D[カットオフ日付計算]
    D --> E[3年1日以上前のイベント取得]
    E --> F[最大10000件削除]
    F --> G[処理終了]
    C --> G
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| created_before | events | SELECT | 削除対象レコードの特定 |
| delete_with_limit | events | DELETE | レコード削除 |

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

#### events

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | created_at < (3年1日前) | LIMIT 10000 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | データベースエラー | DB接続失敗時 | Sidekiqによる自動リトライ |
| - | タイムアウト | 削除処理が長時間化 | 次回実行で継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト |
| リトライ間隔 | Sidekiqデフォルト |
| リトライ対象エラー | 標準例外 |

### 障害時対応

- 削除処理失敗時は次回の6時間後の実行で再試行
- 1万件制限により、大量のレコードがあっても徐々に削除される

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | DELETE文単位 |
| コミットタイミング | DELETE完了時 |
| ロールバック条件 | DELETE失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 最大10,000件/回 |
| 目標処理時間 | 6時間以内（次回実行まで） |
| メモリ使用量上限 | DELETE_LIMITで制御 |

## 排他制御

- 特別な排他制御なし
- 削除上限（10,000件）により自然な負荷制御

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| INFOログ | フラグ無効時 | ":ops_prune_old_events is disabled, skipping." |
| 開始ログ | ジョブ開始時 | ジョブID、クラス名 |
| 終了ログ | ジョブ完了時 | 処理結果 |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| ジョブ失敗率 | 継続的な失敗 | Sidekiq監視システム |
| eventsテーブルサイズ | 異常な増加 | 管理者通知 |

## 備考

- このワーカーは `idempotent!` として宣言されていない
- feature_category は `user_profile`
- data_consistency は `sticky` に設定
- フィーチャーフラグ `ops_prune_old_events` は `:ops` タイプ
- カットオフ期間は3年1日（CUTOFF_DATE = 3.years + 1.day）
- コントリビューションカレンダーは最大12ヶ月表示のため、3年分は十分な余裕がある
- 6時間ごとに最大1万件削除で、大量レコードも徐々に削減
- `Event.unscoped` を使用して default_scope を無視
