# バッチ設計書 13-Notification::RemoveOldNotificationJob

## 概要

本ドキュメントは、Chatwootシステムにおける古い通知データのクリーンアップ処理を実行するバッチ処理の設計を記述するものです。

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

本バッチは、1ヶ月以上前に作成された通知を削除し、各ユーザーの通知数を300件以下に制限する処理を実行します。

**業務上の目的・背景**：通知テーブルは日々の運用で大量のレコードが蓄積されます。古い通知は業務上の価値が低下する一方、データベースの容量を圧迫し、クエリパフォーマンスに悪影響を与えます。本バッチにより、古い通知を定期的に削除し、ユーザーごとの通知数を適切に制限することで、システムパフォーマンスの維持とストレージコストの最適化を実現します。

**バッチの実行タイミング**：毎日22:30 UTC（日本時間7:30）に実行されます。これはシステムのトラフィックが最も低い時間帯に設定されています。

**主要な処理内容**：
1. 1ヶ月以上前に作成された通知を一括削除（remove_old_notifications）
2. 300件を超える通知を持つユーザーを特定（user_ids_exceeding_limit）
3. 該当ユーザーの古い通知をトリミング（trim_notifications_for_user）
4. 各ユーザーの通知を最新300件に制限

**前後の処理との関連**：本バッチは独立して動作し、他のバッチとの依存関係はありません。ただし、同時刻に実行される他のクリーンアップジョブ（RemoveStaleContactInboxesJob、RemoveStaleRedisKeysJob）と同じ低トラフィック時間帯に設定されています。

**影響範囲**：notificationsテーブルの古いレコードが削除されます。ユーザーの通知一覧表示に影響がありますが、業務上重要な最新の通知は保持されます。

## バッチ種別

データクレンジング / データ削除

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 日次 |
| 実行時刻 | 22:30 UTC |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | cron（sidekiq-cron） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| sidekiq-cronが稼働していること | スケジュールジョブの起動に必要 |
| データベース接続が可能であること | notificationsテーブルへのアクセスに必要 |

### 実行可否判定

本バッチは無条件で実行されます。削除対象データが存在しない場合も正常終了します。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | パラメータは不要 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| notifications | DB | 通知レコード |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| notifications | DB | 古い通知が削除される |

### 出力ファイル仕様

ファイル出力はありません。

## 処理フロー

### 処理シーケンス

```
1. バッチ開始
   └─ purgableキューから起動
2. 古い通知の一括削除（remove_old_notifications）
   └─ created_at < 1.month.ago のレコードをdelete_all
3. 通知数超過ユーザーの特定（user_ids_exceeding_limit）
   └─ GROUP BY user_id HAVING COUNT(*) > 300
4. 各ユーザーの通知トリミング（trim_notifications_for_user）
   └─ ユーザーごとにループ処理
5. カットオフポイントの決定
   └─ 301番目の通知の(created_at, id)を取得
6. カットオフより古い通知を削除
   └─ created_at < cutoff_time OR (created_at = cutoff_time AND id <= cutoff_id)
7. バッチ終了
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[remove_old_notifications]
    B --> C[1ヶ月以上前の通知をdelete_all]
    C --> D[user_ids_exceeding_limit取得]
    D --> E{超過ユーザーあり?}
    E -->|No| G[バッチ終了]
    E -->|Yes| F[trim_notifications_for_user]
    F --> H[カットオフポイント取得]
    H --> I{カットオフあり?}
    I -->|No| J{次のユーザーあり?}
    I -->|Yes| K[カットオフより古い通知を削除]
    K --> J
    J -->|Yes| F
    J -->|No| G
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 古い通知削除 | notifications | DELETE | 1ヶ月以上前の全通知を削除 |
| 超過ユーザー取得 | notifications | SELECT | 300件超のユーザーIDを取得 |
| カットオフ取得 | notifications | SELECT | 301番目の通知を取得 |
| トリミング削除 | notifications | DELETE | カットオフより古い通知を削除 |

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

#### notifications

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | created_at < 1.month.ago | 一括削除（delete_all） |
| SELECT | user_id | GROUP BY user_id HAVING COUNT(*) > 300 | 超過ユーザー特定 |
| SELECT | created_at, id | user_id = ? ORDER BY created_at DESC, id DESC OFFSET 300 LIMIT 1 | カットオフ取得 |
| DELETE | - | created_at < cutoff_time OR (created_at = cutoff_time AND id <= cutoff_id) | ユーザー単位削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | StandardError | DB操作失敗時 | Sidekiqの標準リトライ機構により再実行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqのデフォルト設定 |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | StandardError全般 |

### 障害時対応

バッチ失敗時は次回の定期実行（翌日22:30 UTC）で自動的にリカバリされます。緊急時はRailsコンソールから手動実行可能です：
```ruby
Notification::RemoveOldNotificationJob.perform_now
```

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | 各delete_all操作は個別トランザクション |
| コミットタイミング | 各delete_all完了時 |
| ロールバック条件 | 個別delete_all操作の失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 通知数に依存（大規模環境では数十万〜数百万件） |
| 目標処理時間 | 数分〜数十分 |
| メモリ使用量上限 | delete_allにより一定に制御 |

## 排他制御

- 同時実行制御なし
- delete_allは個別のDELETE文として実行されるため、他の操作との競合は発生しにくい
- カットオフ決定後に新規通知が作成されても、created_atとidの複合条件により誤削除を防止

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | バッチ開始時 | Sidekiq標準ログ |
| 進捗ログ | - | 明示的なログ出力なし |
| 終了ログ | バッチ終了時 | Sidekiq標準ログ |
| エラーログ | エラー発生時 | Sidekiq標準エラーログ |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 1時間以上 | Sidekiq監視システム |
| エラー件数 | 1件以上 | Sidekiq監視システム |

## 備考

- NOTIFICATION_LIMIT = 300（ユーザーあたりの保持上限）
- OLD_NOTIFICATION_THRESHOLD = 1.month（古い通知の閾値）
- delete_allを使用しているため、ActiveRecordコールバックは発動しない
- カットオフ決定時のソート順：created_at DESC, id DESC（決定論的な順序保証）
- レースコンディション対策：created_atとidの複合条件により、カットオフ決定後に作成された通知は誤削除されない
