# バッチ設計書 85-Namespaces::ProcessOutdatedNamespaceDescendantsCronWorker

## 概要

本ドキュメントは、古い名前空間子孫キャッシュを更新するバッチ処理「Namespaces::ProcessOutdatedNamespaceDescendantsCronWorker」の設計仕様を定義するものである。

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

このバッチは、Namespaces::Descendantsテーブルで古くなった（outdated_at が設定された）レコードを処理し、子孫情報のキャッシュを更新するCronジョブである。

**業務上の目的・背景**：GitLabでは、名前空間の階層構造変更（サブグループの追加・削除、プロジェクトの移動など）が頻繁に発生する。これらの変更が発生すると、子孫キャッシュが古くなる（outdated）としてマークされる。本バッチは、これらの古くなったキャッシュを検出し、UpdateDenormalizedDescendantsServiceを使用して最新の状態に更新する。

**バッチの実行タイミング**：Cronジョブとして定期的に実行される。最大実行時間は45秒に制限されている。

**主要な処理内容**：
1. Namespaces::Descendantsテーブルから古くなったレコードをバッチ（50件）で取得
2. 各namespace_idに対してUpdateDenormalizedDescendantsServiceを実行
3. 処理結果（:processed または その他）を集計
4. 時間超過または未処理レコードがある場合は処理を中断
5. 処理結果をログに出力

**前後の処理との関連**：EnableDescendantsCacheCronWorker（No.84）と連携して動作する。No.84がキャッシュ対象を特定し、本バッチが実際のキャッシュ更新を行う。

**影響範囲**：Namespaces::Descendantsテーブル、名前空間の子孫カウント情報。

## バッチ種別

キャッシュ更新処理

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 定期実行（Cron） |
| 実行時刻 | 設定により異なる |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | cron |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| 古いレコード存在 | Namespaces::Descendantsに古くなったレコードが存在すること |
| feature_category | groups_and_projects機能が有効であること |

### 実行可否判定

古くなったレコードが存在しない場合は何も処理せずに終了。45秒の最大実行時間制限あり。

## 入力仕様

### 入力パラメータ

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

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| Namespaces::Descendants | DB | 古くなったキャッシュレコード |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| namespaces_descendants | DB | 更新されたキャッシュ情報 |
| Sidekiqログ | Metadata | 処理結果（processed, その他）のカウント |

### 出力ファイル仕様

ファイル出力なし。

## 処理フロー

### 処理シーケンス

```
1. ワーカー起動
   └─ Sidekiqによりジョブがキューから取得される
2. 実行時間リミッター初期化
   └─ LoopWithRuntimeLimitで45秒のリミット設定
3. 古いレコード取得ループ
   ├─ load_outdated_batch(50)で古いレコードのnamespace_idsを取得
   ├─ 空の場合はループ終了
   ├─ 各namespace_idに対してUpdateDenormalizedDescendantsServiceを実行
   ├─ 結果を集計（:processed または その他）
   ├─ 時間超過の場合はループ終了
   └─ 未処理（:processed以外）がある場合もループ終了
4. 処理完了
   └─ 結果をメタデータとしてログ出力
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[結果ハッシュ初期化]
    B --> C[loop_with_runtime_limit 45秒]
    C --> D[load_outdated_batch 50件]
    D --> E{namespace_ids存在?}
    E -->|なし| F[ループ終了]
    E -->|あり| G[各namespace_idを処理]
    G --> H[UpdateDenormalizedDescendantsService実行]
    H --> I[結果を集計]
    I --> J{時間超過?}
    J -->|Yes| F
    J -->|No| K{全て:processed?}
    K -->|No| F
    K -->|Yes| D
    F --> L[結果ログ出力]
    L --> M[バッチ終了]
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 古いレコード取得 | namespaces_descendants | SELECT | outdated_atが設定されたレコードを取得 |
| キャッシュ更新 | namespaces_descendants | UPDATE | キャッシュ情報を更新 |

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

#### namespaces_descendants

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | namespace_id | outdated_atがNULLでないレコード | 50件ずつバッチ取得 |
| UPDATE | 子孫情報 | UpdateDenormalizedDescendantsServiceによる更新 | サービス経由 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 時間超過 | 45秒経過 | 処理中断、次回継続 |
| - | 処理失敗 | サービス実行失敗 | 処理中断、次回リトライ |
| - | DBエラー | データベース接続失敗 | 自動リトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト |
| リトライ間隔 | Sidekiqデフォルト（指数バックオフ） |
| リトライ対象エラー | 一般的な例外 |

### 障害時対応

idempotent!が設定されているため、再実行しても安全。処理に失敗したレコードは次回実行時に再処理される。

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | サービス単位 |
| コミットタイミング | 各サービス実行後 |
| ロールバック条件 | サービス実行失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 可変（古いレコード数に依存） |
| 目標処理時間 | 最大45秒（MAX_RUNTIME） |
| メモリ使用量上限 | バッチ処理により制限 |

## 排他制御

特別な排他制御なし。LoopWithRuntimeLimitによる時間制限で長時間実行を防止。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 結果ログ | バッチ終了時 | 処理結果のカウント（:processed: N, :error: M など） |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 45秒（MAX_RUNTIME） | 運用チーム |
| エラー件数 | 連続3回以上 | 運用チーム |

## 備考

- feature_category: :groups_and_projects
- data_consistency: :always
- idempotent: true
- LoopWithRuntimeLimitをinclude
- MAX_RUNTIME: 45秒
- BATCH_SIZE: 50
- 処理結果が全て:processedでない場合は処理を中断し、次回実行時に再試行
