# バッチ設計書 14-Internal::RemoveOrphanConversationsJob

## 概要

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

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

本バッチは、コンタクトまたはインボックスが存在しない孤立した会話（orphan conversations）を検出し削除する処理を実行します。

**業務上の目的・背景**：会話データは通常、コンタクトとインボックスに紐づいて管理されます。しかし、コンタクトやインボックスが削除された際に、関連する会話が残存してしまうケースがあります。これらの孤立した会話は、UIからアクセスできず、データベースの容量を無駄に消費します。本バッチにより、こうした不整合データを定期的に検出・削除し、データベースの健全性を維持します。

**バッチの実行タイミング**：12時間毎（0:00 UTCと12:00 UTC）に実行されます。

**主要な処理内容**：
1. RemoveOrphanConversationsServiceを呼び出し
2. 過去1日以内に作成された会話を対象にクエリを構築
3. LEFT OUTER JOINでcontactsとinboxesをJOIN
4. contact_id IS NULLまたはinbox_id IS NULLの会話を検出
5. バッチ単位（1000件）で検出した孤立会話を削除

**前後の処理との関連**：本バッチは独立して動作します。コンタクト削除やインボックス削除の後処理として機能しますが、直接的な依存関係はありません。

**影響範囲**：conversationsテーブルの孤立レコードが削除されます。削除時にdestroy_allを使用するため、関連するmessagesなどの子レコードも削除されます。

## バッチ種別

データクレンジング / ハウスキーピング

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 12時間毎 |
| 実行時刻 | 00:00 UTC, 12:00 UTC |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | cron（sidekiq-cron） |

## 実行条件

### 前提条件

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

### 実行可否判定

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | パラメータは不要（Serviceクラスでデフォルト値使用） |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| conversations | DB | 会話レコード |
| contacts | DB | コンタクトレコード（LEFT JOIN） |
| inboxes | DB | インボックスレコード（LEFT JOIN） |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| conversations | DB | 孤立した会話が削除される |
| Railsログ | ログ | 削除件数がINFOレベルで出力される |

### 出力ファイル仕様

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

## 処理フロー

### 処理シーケンス

```
1. バッチ開始
   └─ housekeepingキューから起動
2. RemoveOrphanConversationsService初期化
   └─ デフォルト: account=nil, days=1
3. 孤立会話クエリ構築（build_orphan_conversations_query）
   └─ 過去1日以内の会話が対象
4. LEFT OUTER JOINでcontacts, inboxesを結合
   └─ left_outer_joins(:contact, :inbox)
5. 孤立条件でフィルタ
   └─ contacts.id IS NULL OR inboxes.id IS NULL
6. バッチ単位で削除処理
   └─ find_in_batches(batch_size: 1000)
7. 各バッチをdestroy_all
   └─ Conversation.where(id: conversation_ids).destroy_all
8. 削除件数をログ出力
9. バッチ終了
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[Service初期化]
    B --> C[孤立会話クエリ構築]
    C --> D[LEFT OUTER JOIN contacts, inboxes]
    D --> E[孤立条件でフィルタ]
    E --> F[find_in_batches batch_size:1000]
    F --> G{バッチあり?}
    G -->|No| I[完了ログ出力]
    G -->|Yes| H[destroy_all]
    H --> J[削除件数ログ出力]
    J --> F
    I --> K[バッチ終了]
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 孤立会話検出 | conversations, contacts, inboxes | SELECT | LEFT OUTER JOINで孤立会話を検出 |
| 会話削除 | conversations | DELETE | 検出された孤立会話を削除 |
| 関連レコード削除 | messages等 | DELETE | destroy_allによるカスケード削除 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | created_at > 1.day.ago, contacts.id IS NULL OR inboxes.id IS NULL | LEFT OUTER JOIN使用 |
| DELETE | - | id IN (検出されたID) | destroy_allで削除 |

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

### 障害時対応

バッチ失敗時は次回の定期実行（12時間後）で自動的にリカバリされます。緊急時はRailsコンソールから手動実行可能です：
```ruby
Internal::RemoveOrphanConversationsJob.perform_now
```

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | バッチ単位（1000件ごと） |
| コミットタイミング | 各バッチのdestroy_all完了時 |
| ロールバック条件 | 個別バッチの削除操作失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 通常は少数（孤立会話は稀なケース） |
| 目標処理時間 | 数秒〜数分 |
| メモリ使用量上限 | find_in_batches(batch_size: 1000)により一定に制御 |

## 排他制御

- 同時実行制御なし
- destroy_allは個別のDELETE文として実行されるため、他の操作との競合は発生しにくい
- 処理対象は過去1日以内に作成された会話に限定されるため、影響範囲は限定的

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | 処理開始時 | "[RemoveOrphanConversationsService] Starting removal of orphan conversations" |
| 進捗ログ | 各バッチ完了時 | "[RemoveOrphanConversationsService] Deleted #{batch.size} orphan conversations (#{total_deleted} total)" |
| 終了ログ | 処理完了時 | "[RemoveOrphanConversationsService] Completed. Total deleted: #{total_deleted}" |
| エラーログ | エラー発生時 | Sidekiq標準エラーログ |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 30分以上 | Sidekiq監視システム |
| エラー件数 | 1件以上 | Sidekiq監視システム |
| 削除件数 | 異常に多い場合（例：1000件以上） | 運用チーム |

## 備考

- 処理対象は過去1日以内（days=1）に作成された会話に限定
- destroy_allを使用しているため、ActiveRecordコールバックが発動し、関連レコード（messages等）も削除される
- アカウント指定も可能（ServiceクラスのaccountパラメータでNULL以外を指定）
- 孤立会話の発生原因：コンタクト削除時やインボックス削除時のカスケード削除失敗など
