# バッチ設計書 42-Members::ExpiringWorker

## 概要

本ドキュメントは、GitLabにおけるメンバーシップ期限切れ通知バッチ（Members::ExpiringWorker）の設計仕様を定義したものである。

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

グループやプロジェクトのメンバーシップが間もなく期限切れになるユーザーに対して、事前に通知メールを送信するバッチ処理である。

**業務上の目的・背景**：GitLabではメンバーシップに有効期限を設定できる。期限切れによりアクセス権が失われると、ユーザーは突然プロジェクトやグループにアクセスできなくなり、業務に支障が生じる可能性がある。本バッチは、メンバーシップ期限切れの7日前にユーザーに通知することで、事前に対処（期限延長の依頼等）を可能にし、業務の継続性を確保することを目的としている。

**バッチの実行タイミング**：日次実行（毎日1時0分 UTC）

**主要な処理内容**：
1. 7日以内に期限切れとなり、まだ通知されていないメンバーを取得
2. 各メンバーに対してMembers::ExpiringEmailNotificationWorkerをキューイング
3. 子ワーカーがメール送信と通知フラグの更新を実行

**前後の処理との関連**：本バッチは親ワーカーとして動作し、Members::ExpiringEmailNotificationWorkerを子ワーカーとして呼び出す。RemoveExpiredMembersWorkerによる期限切れメンバーの削除処理の前に実行される。

**影響範囲**：Memberテーブル（expiry_notified_atカラム更新）、メール送信システム

## バッチ種別

通知配信

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 日次 |
| 実行時刻 | 01:00 (UTC) |
| 実行曜日 | 毎日 |
| 実行日 | - |
| トリガー | cron |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| データベース接続 | membersテーブルへの読み取りアクセスが可能であること |
| Sidekiq | 子ワーカーをキューイングするためにSidekiqが稼働していること |

### 実行可否判定

特別な実行可否判定ロジックはなく、cronスケジュールに基づいて常に実行される。対象となるメンバーが存在しない場合は、処理をスキップして正常終了する。

## 入力仕様

### 入力パラメータ

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

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| members | DB | 7日以内に期限切れかつ未通知のメンバーレコード |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| Sidekiq Queue | Sidekiq Job | ExpiringEmailNotificationWorkerジョブ |

### 出力ファイル仕様

ファイル出力はなし

## 処理フロー

### 処理シーケンス

```
1. バッチ開始
   └─ CronjobQueueからperformメソッドが呼び出される
2. 期限日計算
   └─ DAYS_TO_EXPIRE（7日）を基に期限日を算出
3. 対象メンバーの取得
   └─ non_invite.non_request.non_minimal_access.expiring_and_not_notifiedで対象を取得
4. バッチ処理ループ
   └─ each_batch(of: 500)でメンバーを分割して処理
5. 子ワーカーキューイング
   └─ 各メンバーIDに対してExpiringEmailNotificationWorker.perform_asyncを実行
6. バッチ終了
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[期限日を計算 DAYS_TO_EXPIRE.days.from_now]
    B --> C[対象メンバーを取得]
    C --> D{メンバー存在?}
    D -->|なし| H[バッチ終了]
    D -->|あり| E[each_batch of:500 で処理]
    E --> F[member_idを抽出]
    F --> G[ExpiringEmailNotificationWorker.perform_async]
    G --> I{次のバッチ?}
    I -->|あり| E
    I -->|なし| H
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| メンバー取得 | members | SELECT | 期限切れ間近かつ未通知のメンバーを取得 |
| ID抽出 | members | SELECT | pluck_primary_keyでメンバーIDを取得 |

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

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | invite_token | NULL | 招待ではない |
| SELECT | requested_at | NULL | リクエストではない |
| SELECT | access_level | != MINIMAL_ACCESS | 最小アクセスレベルではない |
| SELECT | expires_at | <= limit_date AND > current_date | 期限切れ間近 |
| SELECT | expiry_notified_at | NULL | まだ通知されていない |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ActiveRecord::StatementInvalid | DB接続エラー | Sidekiqリトライ |
| - | Redis::ConnectionError | Sidekiqキューイング失敗 | Sidekiqリトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なDB/Redis接続エラー |

### 障害時対応

バッチ失敗時は次回のcron実行時に再度処理が実行される。expiry_notified_atフラグにより、既に通知済みのメンバーには重複送信されない。

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | バッチ単位（each_batch） |
| コミットタイミング | 各バッチ処理完了時 |
| ロールバック条件 | バッチ処理でのエラー発生時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 数百〜数千件 |
| 目標処理時間 | バッチサイズ500件 x バッチ数 x 0.5秒程度 |
| メモリ使用量上限 | BATCH_LIMIT: 500件によるメモリ制御 |

## 排他制御

本バッチはidempotentではなく（rubocop:disable Scalability/IdempotentWorker）、同時実行の厳密な排他制御は行っていない。ただし、expiry_notified_atによる状態管理により、重複処理のリスクは低い。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | バッチ開始時 | ワーカー名、実行開始時刻 |
| 進捗ログ | 各バッチ処理時 | キューイングされたジョブ数 |
| 終了ログ | バッチ終了時 | 実行完了、総処理件数 |
| エラーログ | エラー発生時 | エラーメッセージ、スタックトレース |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 1800秒 | システム管理者 |
| エラー件数 | 5件 | システム管理者 |

## 備考

- DAYS_TO_EXPIREはExpirableモジュールで7日と定義されている
- BATCH_LIMITは500件で定義されており、メモリ効率を考慮した設計
- feature_categoryは「system_access」に分類される
- data_consistencyは「sticky」で、レプリカDBを使用可能
- 子ワーカー（ExpiringEmailNotificationWorker）は実際のメール送信とexpiry_notified_atの更新を担当
