# バッチ設計書 3-Ci_ArchiveTracesCronWorker

## 概要

本ドキュメントは、古いCIトレースをアーカイブするバッチ処理「Ci::ArchiveTracesCronWorker」の設計仕様を記載する。

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

Ci::ArchiveTracesCronWorkerは、Redis/データベースに残存する古いCIジョブトレース（ログ）をオブジェクトストレージにアーカイブする定期バッチ処理である。通常、トレースは`Ci::ArchiveTraceWorker`によってジョブ完了時にアーカイブされるが、SIGKILLなどでワーカーが強制終了された場合にアーカイブされずに残ることがある。本バッチはそれらの「取り残された」トレースを回収する。

**業務上の目的・背景**：CI/CDジョブのトレース（実行ログ）は、初期段階ではRedisやデータベースに「ライブトレース」として保存される。ジョブ完了後、これらはオブジェクトストレージにアーカイブされ、元のライブトレースは削除されるのが正常なフローである。しかし、Sidekiqワーカーの異常終了（SIGKILL等）により、アーカイブ処理がスキップされることがある。これらの「古いライブトレース」はRedis/DBのリソースを消費し続け、データ損失のリスクもある。本バッチは1週間以上放置されるとデータ損失の可能性があるため、定期的なクリーンアップが必要である。

**バッチの実行タイミング**：毎時17分に実行（cron: `17 * * * *`）。1時間に1回のペースで古いライブトレースを検出・アーカイブする。

**主要な処理内容**：
1. 排他ロック（ExclusiveLease）を取得し、同時実行を防止
2. `Ci::Build.with_stale_live_trace`で古いライブトレースを持つビルドを検索
3. バッチサイズ100件ずつ、最大2000件まで処理
4. 各ビルドのトレースを`job.trace.archive!`でオブジェクトストレージにアーカイブ
5. アーカイブ成功時、関連するインテグレーションにイベントを通知
6. 55分のループタイムアウトで処理を終了

**前後の処理との関連**：通常のトレースアーカイブは`Ci::ArchiveTraceWorker`が担当する。本ワーカーは異常終了によりアーカイブされなかったトレースを回収するフォールバック処理である。アーカイブ後のトレースは`job_artifacts_trace`として保存される。

**影響範囲**：全プロジェクトのCI/CDジョブトレースに影響。アーカイブ処理によりRedis/DBからデータが削除され、オブジェクトストレージに移動する。

## バッチ種別

データアーカイブ / システムメンテナンス

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 60分間隔 |
| 実行時刻 | 毎時17分 |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | cron (`17 * * * *`) |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| 排他ロック取得 | ExclusiveLeaseを取得できること（TTL: 56分） |
| 古いライブトレース存在 | `with_stale_live_trace`スコープに該当するレコードが存在すること |

### 実行可否判定

排他ロック（ExclusiveLease）を取得できた場合のみ処理を実行する。TTLは56分、リトライは1回。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | cronジョブとして引数なしで実行 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| ci_builds | DB | CIビルドテーブル（with_stale_live_traceスコープ） |
| Redis | ライブトレース | インクリメンタルログデータ |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| オブジェクトストレージ | ファイル | アーカイブされたトレースファイル |
| ci_job_artifacts | DB | トレースアーティファクトレコード |

### 出力ファイル仕様

| 項目 | 内容 |
|-----|------|
| ファイル名 | job_artifacts_traceとして保存 |
| 出力先 | オブジェクトストレージ |
| 文字コード | UTF-8 |
| 形式 | テキストログ |

## 処理フロー

### 処理シーケンス

```
1. 排他ロック取得
   └─ ExclusiveLeaseHelpers.in_lockでロック取得（TTL: 56分、リトライ: 1回）
2. ループ処理開始
   └─ 55分のタイムアウトと2000件のリミットを設定
3. 古いライブトレースを持つビルド取得
   └─ Ci::Build.with_stale_live_trace.find_each(batch_size: 100)
4. アーカイブ試行可否チェック
   └─ archival_attempts_available?とcan_attempt_archival_now?を確認
5. トレースアーカイブ実行
   └─ job.trace.archive!でオブジェクトストレージに保存
6. ペンディング状態削除
   └─ job.remove_pending_state!
7. インテグレーション通知
   └─ archive_trace_hooksイベントを実行
8. エラー処理
   └─ 失敗時はarchival_attemptsをインクリメント、エラートラッキング
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{排他ロック取得}
    B -->|成功| C[古いライブトレース検索]
    B -->|失敗| J[バッチ終了]
    C --> D{ビルド存在?}
    D -->|なし| J
    D -->|あり| E{アーカイブ試行可能?}
    E -->|いいえ| F[クリーンアップ試行]
    E -->|はい| G[トレースアーカイブ]
    F --> H{次のビルド?}
    G --> I[インテグレーション通知]
    I --> H
    H -->|あり| D
    H -->|なし| J
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 古いライブトレース取得 | ci_builds | SELECT | with_stale_live_traceスコープ |
| アーティファクト作成 | ci_job_artifacts | INSERT | トレースアーティファクト作成 |
| ペンディング状態削除 | ci_build_pending_states | DELETE | remove_pending_state! |
| アーカイブ試行回数更新 | ci_builds | UPDATE | archival_attemptsインクリメント |

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

#### ci_builds

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全カラム | with_stale_live_traceスコープ | バッチサイズ100件 |

#### ci_job_artifacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | job_id, file_type, file | トレースデータ | archive!で作成 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ExclusiveLease取得失敗 | 別プロセスがロック保持中 | スキップ |
| - | AlreadyArchivedError | 既にアーカイブ済み | 無視して続行 |
| - | StandardError | アーカイブ処理中のエラー | archival_attempts++、エラートラッキング |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 1回（ExclusiveLease取得） |
| リトライ間隔 | - |
| リトライ対象エラー | archival_attempts_available?で管理 |

### 障害時対応

1. ロック取得失敗の場合：次回実行時に自動復旧
2. アーカイブ失敗の場合：archival_attemptsをインクリメント、次回実行時にリトライ
3. アーカイブ試行回数超過の場合：attempt_archive_cleanup!でクリーンアップ

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | レコード単位 |
| コミットタイミング | 各ビルドのarchive!完了時 |
| ロールバック条件 | archive!処理中のエラー発生時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 最大2000件（LOOP_LIMIT） |
| 目標処理時間 | 55分以内（LOOP_TIMEOUT） |
| メモリ使用量上限 | バッチサイズ100件による制御 |

## 排他制御

- ExclusiveLease（TTL: 56分）による排他制御
- 同時に1プロセスのみ実行可能
- ロックキー: `archive_trace_service:batch_execute:lock`

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 警告ログ | アーカイブ試行不可時 | "The job is out of archival attempts." |
| 警告ログ | アーカイブ待機時 | "The job can not be archived right now." |
| 警告ログ | ループ上限到達時 | "Loop limit reached." |
| エラーログ | アーカイブ失敗時 | "Failed to archive trace. message: ..." |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 55分超過 | 運用チーム |
| アーカイブ失敗数 | job_trace_archive_failed_totalメトリクス | 運用チーム |

## 備考

- `data_consistency :always`が設定されており、プライマリDBを使用
- `deduplicate :until_executed, including_scheduled: true`で重複実行を防止
- 関連Issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/36791
- 関連Issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/51502
- 1週間以上アーカイブされないとデータ損失の可能性があるため、重要なメンテナンスバッチ
