# バッチ設計書 6-Internal::RemoveStaleRedisKeysJob

## 概要

本ドキュメントは、Chatwootアプリケーションにおける`Internal::RemoveStaleRedisKeysJob`バッチの設計仕様を記載したものである。

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

このバッチは、古いオンラインプレゼンスキーなど、Redisの不要キーを削除しメモリ使用量を削減するハウスキーピングジョブである。

**業務上の目的・背景**：Chatwootでは、コンタクトやユーザーのオンライン状態をRedisのSorted Setで管理している。コンタクトがシステムにアクセスするたびにプレゼンス情報がRedisに追加されるが、古いプレゼンス情報は不要となる。長期間運用するとこれらの不要データがRedisのメモリを圧迫するため、定期的にクリーンアップする必要がある。このバッチにより、Redisのメモリ使用量を約50%削減できる。

**バッチの実行タイミング**：日次で22:30 UTCに実行される。これはトラフィックが最も少ない時間帯として選択されている。

**主要な処理内容**：
1. 全アカウントをバッチサイズ100で分割取得
2. 各アカウントに対してProcessStaleRedisKeysJobをキューイング
3. 子ジョブがアカウント単位で古いRedisキーを削除

**前後の処理との関連**：本バッチは親ジョブとして機能し、Internal::ProcessStaleRedisKeysJobを子ジョブとしてトリガーする。子ジョブがRemoveStaleRedisKeysServiceを使用して実際の削除処理を行う。

**影響範囲**：Redis内のオンラインプレゼンスデータ（ONLINE_PRESENCE_CONTACTS）

## バッチ種別

データクリーンアップ / ハウスキーピング

## 実行スケジュール

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

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| なし | 特別な前提条件なく常に実行される |

### 実行可否判定

常に実行される。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | 本バッチは引数を取らない |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| accounts | DB | 全アカウントの一覧 |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| Sidekiqキュー | ジョブ | ProcessStaleRedisKeysJobをキューイング |
| ログ | テキスト | 子ジョブのキューイング情報 |

### 出力ファイル仕様

ファイル出力なし

## 処理フロー

### 処理シーケンス

```
1. 全アカウントの取得
   └─ Account.find_in_batches(batch_size: 100)
2. 各アカウントに対してジョブキューイング
   └─ ログ出力: "Enqueuing ProcessStaleRedisKeysJob for account {id}"
   └─ Internal::ProcessStaleRedisKeysJob.perform_later(account)
3. 子ジョブの処理（ProcessStaleRedisKeysJob）
   └─ RemoveStaleRedisKeysServiceを呼び出し
   └─ コンタクトのプレゼンスキーから古いエントリを削除
   └─ zremrangebyscoreでPRESENCE_DURATION以前のスコアを削除
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[全アカウント取得]
    B --> C[各アカウントをループ処理]
    C --> D[ログ出力]
    D --> E[ProcessStaleRedisKeysJobをキューイング]
    E --> F{次のアカウントあり?}
    F -->|Yes| C
    F -->|No| G[バッチ終了]

    subgraph 子ジョブ処理
    H[ProcessStaleRedisKeysJob] --> I[RemoveStaleRedisKeysService]
    I --> J[Redisから古いキー削除]
    J --> K[削除件数ログ出力]
    end
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| アカウント取得 | accounts | SELECT | 全アカウントの取得 |

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

#### accounts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | 全レコード | batch_size: 100で分割取得 |

## Redis操作仕様

### 操作対象キー

| キー形式 | 説明 |
|---------|------|
| `ONLINE_PRESENCE_CONTACTS:{account_id}` | コンタクトのオンラインプレゼンス（Sorted Set） |

### 操作詳細

| 操作 | コマンド | パラメータ | 説明 |
|-----|---------|----------|------|
| 削除 | ZREMRANGEBYSCORE | キー, '-inf', "(#{range_start}" | PRESENCE_DURATION以前のスコアを持つエントリを削除 |

- range_start: `(Time.zone.now - OnlineStatusTracker::PRESENCE_DURATION).to_i`
- PRESENCE_DURATION: 環境変数で設定可能（デフォルト20秒）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Redis::ConnectionError | Redis接続エラー | Sidekiqリトライ機構に委譲 |
| - | StandardError | 予期しないエラー | Sidekiqリトライ機構に委譲 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqのデフォルト設定に依存 |
| リトライ間隔 | Sidekiqのデフォルト設定に依存 |
| リトライ対象エラー | ジョブ実行時の例外全般 |

### 障害時対応

親ジョブまたは子ジョブの失敗時は、翌日の定期実行で再度処理される。Redisキーの削除は冪等な操作であり、再実行しても問題ない。

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

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

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | アカウント数分のジョブキューイング |
| 目標処理時間 | 数秒（キューイングのみ） |
| メモリ使用量上限 | 最小限（find_in_batchesで分割処理） |

## 排他制御

同時実行の制御は特になし。日次1回のみの実行であり、通常は競合しない。Redis操作はアトミックであり、並行実行しても問題ない。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 情報ログ | 子ジョブキューイング時 | `Enqueuing ProcessStaleRedisKeysJob for account {id}` |
| 情報ログ | 子ジョブ完了時 | `Successfully cleaned up Redis keys for account {id} (removed {count} keys)` |
| 情報ログ | 子ジョブ処理時 | `Removing redis stale keys for account {id}` |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| ジョブ失敗 | 連続失敗 | Sidekiq監視 |
| Redisメモリ使用量 | 閾値超過 | インフラ監視 |

## 備考

- Redisメモリ使用量を約50%削減できると期待される（コメントより）
- PRESENCE_DURATIONは環境変数で設定可能（デフォルト20秒）
- 子ジョブ（ProcessStaleRedisKeysJob）は`low`キューで実行され、優先度が低い
- コンタクトのプレゼンスキーのみが対象（ユーザーのプレゼンスキーは対象外）
- Sorted Setのスコアはタイムスタンプ（Unix時間）で、古いエントリほどスコアが小さい
