# バッチ設計書 1-email-analytics-fetch-latest

## 概要

本ドキュメントは、Ghostプラットフォームにおけるメール分析イベント取得バッチ「email-analytics-fetch-latest」の設計仕様を記載したものです。このバッチは、メール配信サービス（Mailgun）から配信・開封・失敗などのイベントを定期的に取得し、統計情報を集計・更新する役割を担います。

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

**業務上の目的・背景**：Ghostはニュースレター配信プラットフォームとして、メールの配信状況をリアルタイムで把握することが重要です。このバッチは、Mailgun APIから最新のメールイベント（配信成功、開封、一時的/恒久的失敗、購読解除、スパム報告）を取得し、メンバーごとの統計情報（開封率、配信数など）を集計することで、サイト運営者がニュースレターのパフォーマンスを把握できるようにします。

**バッチの実行タイミング**：5分ごとにcronスケジュールで自動実行されます（`${s} ${m}/5 * * * *`形式で、秒と分はランダム値を使用してスパイクを防止）。

**主要な処理内容**：
1. メインスレッドからワーカースレッドを起動し、StartEmailAnalyticsJobEventを発火
2. Mailgun APIから最新のopened（開封）イベントを優先的に取得（最大10,000件）
3. 配信・失敗・購読解除・スパム報告などのnon-openedイベントを取得
4. 過去30分以上前の取りこぼしイベント（missing）を取得
5. イベントをemail_recipientsテーブルに反映し、emails/membersテーブルの統計を集計

**前後の処理との関連**：batch-sending-service-jobによるメール送信後に発生したイベントを収集します。収集されたデータは管理画面のメール統計表示や、メンバーのエンゲージメント分析に使用されます。

**影響範囲**：email_recipients、emails、members、jobsテーブルが更新されます。メール統計のダッシュボード表示に直接影響します。

## バッチ種別

データ連携 / 集計処理

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 5分ごと |
| 実行時刻 | ランダムな秒・分オフセット付きで5分間隔 |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | cron（`${s} ${m}/5 * * * *`） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| メール送信実績 | emailsテーブルにレコードが存在すること |
| Mailgun設定 | Mailgun APIキーとドメインが設定されていること |
| 前回ジョブ完了 | 同一ジョブが実行中でないこと（fetching フラグで制御） |

### 実行可否判定

- `shouldFetchStats()`関数でemailsテーブルのレコード数をチェック
- 0件の場合はスキップ
- 既に`fetching`フラグがtrueの場合は「already running」でスキップ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| maxEvents | number | No | Infinity | 取得するイベントの最大数 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| Mailgun API | REST API | メールイベント（delivered, opened, failed等）の取得元 |
| jobs テーブル | DB | 前回実行時のタイムスタンプ取得 |
| email_recipients テーブル | DB | 既存の配信状況データ |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| email_recipients テーブル | DB | 各受信者の配信・開封・失敗状況を更新 |
| emails テーブル | DB | メールごとの配信数・開封数・失敗数を集計更新 |
| members テーブル | DB | メンバーごとのメール統計（email_count, email_opened_count, email_open_rate）を更新 |
| jobs テーブル | DB | ジョブの実行状態・完了タイムスタンプを記録 |

### 出力ファイル仕様

ファイル出力なし（データベースのみ更新）

## 処理フロー

### 処理シーケンス

```
1. ワーカースレッド起動
   └─ parentPort経由でStartEmailAnalyticsJobEventを発火

2. メインスレッドでstartFetch()実行
   └─ fetchingフラグをtrueに設定

3. fetchLatestOpenedEvents（最大10,000件）
   └─ 開封イベントを優先的に取得・処理
   └─ 10,000件以上の場合は即座に再起動

4. fetchLatestNonOpenedEvents
   └─ delivered, failed, unsubscribed, complained イベントを取得

5. fetchMissing
   └─ 30分以上前の取りこぼしイベントを取得

6. fetchScheduled
   └─ スケジュールされたバックフィル処理

7. 各イベントをprocessEventBatch()で処理
   └─ バッチ処理モード or シーケンシャル処理モードで実行

8. aggregateStats()で統計を集計
   └─ メールごと、メンバーごとの統計を更新

9. fetchingフラグをfalseに設定
```

### フローチャート

```mermaid
flowchart TD
    A[ワーカースレッド起動] --> B[StartEmailAnalyticsJobEvent発火]
    B --> C{fetching中?}
    C -->|Yes| D[スキップ]
    C -->|No| E[fetching=true]
    E --> F[fetchLatestOpenedEvents]
    F --> G{10000件以上?}
    G -->|Yes| H[即座に再起動]
    G -->|No| I[fetchLatestNonOpenedEvents]
    I --> J[fetchMissing]
    J --> K[fetchScheduled]
    K --> L[統計集計]
    L --> M[fetching=false]
    H --> M
    M --> N[バッチ終了]
    D --> N
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| イベント取得位置確認 | jobs | SELECT | 前回処理完了位置の取得 |
| イベント処理 | email_recipients | UPDATE | delivered_at, opened_at, failed_at等を更新 |
| メール統計集計 | emails | UPDATE | delivered_count, opened_count, failed_countを更新 |
| メンバー統計集計 | members | UPDATE | email_count, email_opened_count, email_open_rateを更新 |
| ジョブ状態記録 | jobs | INSERT/UPDATE | 実行状態・完了タイムスタンプを記録 |

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

#### email_recipients

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | delivered_at | イベントのタイムスタンプ | 配信成功時 |
| UPDATE | opened_at | イベントのタイムスタンプ | 開封時 |
| UPDATE | failed_at | イベントのタイムスタンプ | 配信失敗時 |

#### emails

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | delivered_count | COUNT(delivered_at IS NOT NULL) | 集計値 |
| UPDATE | opened_count | COUNT(opened_at IS NOT NULL) | 集計値 |
| UPDATE | failed_count | COUNT(failed_at IS NOT NULL) | 集計値 |

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | email_count | 総受信メール数 | 集計値 |
| UPDATE | email_opened_count | 開封メール数 | 集計値 |
| UPDATE | email_open_rate | 開封率（%） | tracked_email >= 5件の場合のみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | API通信エラー | Mailgun APIへの接続失敗 | エラーログ出力後、次回実行時に再取得 |
| - | キャンセル | ワーカースレッドがキャンセルされた | 「Fetching canceled」をログ出力して終了 |
| - | 集計エラー | aggregateStats失敗 | エラーログ出力後、処理続行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（次回のcron実行で再取得） |
| リトライ間隔 | 5分（次回cron実行時） |
| リトライ対象エラー | API通信エラー、一時的なDB接続エラー |

### 障害時対応

- エラー発生時はfetchingフラグをfalseにリセット
- 次回のcron実行時に前回の続きから処理を再開（lastEventTimestampから取得）
- 重大なエラーはlogging.errorで記録

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | イベントバッチ単位 |
| コミットタイミング | 5分ごと、または5000メンバー処理ごとに中間集計 |
| ロールバック条件 | 個別イベント処理の失敗（全体はロールバックしない） |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 最大10,000イベント/実行 |
| 目標処理時間 | 約2,500イベント/分（大規模DBの場合） |
| メモリ使用量上限 | バッチ処理モードで100件ずつメンバー統計を更新 |

## 排他制御

- `fetching`フラグによる同時実行防止
- 同一ジョブが実行中の場合はスキップ

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | バッチ開始時 | 処理モード（BATCHED/SEQUENTIAL） |
| 進捗ログ | 各フェーズ完了時 | イベント数、処理時間、スループット |
| 終了ログ | バッチ終了時 | 総イベント数、所要時間、イベント種別内訳 |
| エラーログ | エラー発生時 | エラー詳細、スタックトレース |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理遅延 | openedJobLagWarningMinutes設定値 | logging.warn |
| 処理時間 | 設定なし | メトリクス記録（Prometheus） |

## 備考

- バッチ処理モード（`emailAnalytics:batchProcessing`）が有効な場合、100件単位でメンバー統計を更新
- 開封イベントは最も重要なため、優先的に処理される
- TRUST_THRESHOLD_MS（30分）以上前のイベントは「missing」として別途取得
- FETCH_LATEST_END_MARGIN_MS（1分）のマージンを設けて、Mailgunのストレージ安定化を待つ
