# バッチ設計書 40-RemoveUnacceptedMemberInvitesWorker

## 概要

本ドキュメントは、未承諾のメンバー招待を削除するバッチ処理「RemoveUnacceptedMemberInvitesWorker」の設計書です。

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

**業務上の目的・背景**：GitLabではプロジェクトやグループにユーザーを招待する機能があります。招待を受けたユーザーが一定期間（90日）以内に招待を承諾しない場合、その招待は無効とみなされます。本バッチは、90日以上経過した未承諾の招待レコードを自動的に削除することで、データベースの肥大化を防ぎ、招待管理の清潔性を維持します。また、承諾されていない古い招待はセキュリティリスクとなる可能性があるため、定期的なクリーンアップが重要です。

**バッチの実行タイミング**：日次で午後3時10分に実行されます。cronスケジュールは`10 15 * * *`で設定されています。

**主要な処理内容**：
1. 90日以上前に作成され、まだ承諾されていない招待をバッチ（10,000件ずつ）で取得
2. user_idがNULLの招待（未承諾）のみを対象
3. delete_allで一括削除
4. 削除対象がなくなるまでループ処理

**前後の処理との関連**：MemberInvitationReminderEmailsWorkerが招待リマインダーを送信し、本バッチがその後の未承諾招待のクリーンアップを担当します。

**影響範囲**：membersテーブル（招待レコード）

## バッチ種別

データクレンジング

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 日次 |
| 実行時刻 | 15:10 |
| 実行曜日 | 毎日 |
| 実行日 | - |
| トリガー | cron |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Sidekiq稼働 | Sidekiqワーカーが正常に稼働していること |
| データベース接続 | メインデータベースへの接続が確立されていること |

### 実行可否判定

- 特別な実行可否判定ロジックは実装されていません
- 削除対象がない場合は即座に終了

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | パラメータなしで実行 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| members | DB | 未承諾の招待レコード（invite_token IS NOT NULL かつ user_id IS NULL かつ created_at < 90日前） |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| members | DB | 古い未承諾招待を削除 |

### 出力ファイル仕様

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

## 処理フロー

### 処理シーケンス

```
1. バッチ開始
   └─ CronjobQueueによって定期実行
2. 削除ループ開始
   └─ 無限ループで処理
3. 削除対象クエリ構築
   └─ invite / created_before(90日前) / user_id IS NULL
4. 内部クエリで10,000件取得
   └─ SELECT id LIMIT 10,000
5. 一括削除実行
   └─ WHERE id IN (内部クエリ) で delete_all
6. 削除件数チェック
   └─ 0件の場合はループ終了
7. 次のバッチへ
   └─ 削除対象がある限りループ継続
8. バッチ終了
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[削除ループ開始]
    B --> C[未承諾招待クエリ構築]
    C --> D[10,000件のIDを取得]
    D --> E[delete_allで一括削除]
    E --> F{削除件数 = 0?}
    F -->|はい| G[バッチ終了]
    F -->|いいえ| B
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 招待取得 | members | SELECT | 削除対象の招待IDを取得 |
| 招待削除 | members | DELETE | 古い未承諾招待を一括削除 |

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

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | invite（invite_tokenがNOT NULL）かつ created_at < 90日前 かつ user_id IS NULL | LIMIT 10,000 |
| DELETE | - | WHERE id IN (内部クエリ) | delete_all使用 |

**削除条件**:
- `invite_token IS NOT NULL`（招待レコードである）
- `created_at < 90.days.ago`（90日以上前に作成）
- `user_id IS NULL`（まだ承諾されていない）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | DB接続エラー | データベースへの接続失敗 | Sidekiqの標準リトライで再実行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | すべての例外 |

### 障害時対応

- idempotent!が設定されているため、再実行しても問題なし
- 削除対象が減るため、再実行時は処理量が減少

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | バッチ単位（10,000件） |
| コミットタイミング | delete_all実行時（暗黙コミット） |
| ロールバック条件 | delete_all失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 数百〜数千件/日 |
| 目標処理時間 | 特に制限なし |
| メモリ使用量上限 | 制限なし（バッチ処理により制御） |

## 排他制御

- CronjobQueueにより同時実行は自動的に防止される
- idempotent!により冪等性が保証される

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | バッチ開始時 | Sidekiq標準ログ |
| 進捗ログ | 処理中 | なし |
| 終了ログ | バッチ終了時 | Sidekiq標準ログ |
| エラーログ | エラー発生時 | 例外情報 |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| ジョブ失敗 | 1件以上 | Sidekiqダッシュボード |
| 処理時間 | 設定なし | - |

## 備考

- feature_category: system_access
- data_consistency: always
- urgency: low
- idempotent!: 設定あり
- EXPIRATION_THRESHOLD: 90日
- BATCH_SIZE: 10,000件
- user_id IS NULLのチェックは重要：承諾済みでinvite_tokenが残っているケースを除外するため
- loopで削除が0件になるまで継続処理
- 内部クエリ + WHERE id IN パターンで効率的に削除
