# バッチ設計書 1-ドキュメント埋め込みインジェスト

## 概要

本ドキュメントは、ベクトルストアにアップロードされたドキュメントのテキスト抽出・チャンキング・埋め込みベクトル生成を行う定期実行バッチ処理の設計を記載する。

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

本バッチは、ユーザーがベクトルストアにアップロードしたドキュメントファイル（TXT、MD、PDF）に対して、テキスト抽出・チャンキング・埋め込みベクトル生成を行い、RAG（Retrieval Augmented Generation）機能で検索可能な状態にするための処理を担う。

**業務上の目的・背景**：ドキュメントアップロード時のリアルタイム処理は、ファイルサイズやサーバー負荷により失敗する場合がある。また、Vercelのサーバーレス関数のタイムアウト（30秒程度）により中断される可能性もある。このバッチは、そうした処理漏れのドキュメントを定期的に検出し、確実にインジェスト処理を完了させることで、ユーザーがアップロードしたドキュメントがRAG検索で利用可能になることを保証する。

**バッチの実行タイミング**：3分ごとにcronジョブとして自動実行される（`*/3 * * * *`）。Vercel Cronを使用してスケジュール実行される。

**主要な処理内容**：
1. インジェスト対象ドキュメントの抽出（idle状態、または15分以上running状態のもの）
2. 各ドキュメントに対するatomicなロック取得（楽観的排他制御）
3. Supabase Storageからファイルをダウンロード
4. ファイル形式に応じたテキスト抽出（TXT/MD/PDF対応）
5. テキストのチャンキング処理
6. 設定された埋め込みプロファイルごとの埋め込みベクトル生成
7. 埋め込みベクトルのデータベース登録
8. 処理結果に応じたステータス更新（completed/failed）

**前後の処理との関連**：ドキュメントアップロードAPI（`/api/vector-stores/document/[documentVectorStoreId]/documents`）でアップロードされたファイルが前提。本バッチの完了後、RAGクエリ処理でベクトル検索が可能になる。

**影響範囲**：
- `document_vector_store_sources`テーブル（ステータス更新）
- `document_embeddings`テーブル（埋め込みベクトル登録）
- Supabase Storage（ファイル読み取り）
- 外部埋め込みAPI（OpenAI等）

## バッチ種別

データ連携 / ベクトル埋め込み生成

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 3分ごと |
| 実行時刻 | 毎時0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57分 |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | Vercel Cron（`*/3 * * * *`） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| CRON_SECRET環境変数 | 認証用シークレットが設定されていること |
| SUPABASE_URL環境変数 | Supabase接続URLが設定されていること |
| SUPABASE_SERVICE_KEY環境変数 | Supabaseサービスキーが設定されていること |
| 対象ドキュメント存在 | インジェスト対象のドキュメントが1件以上存在すること |

### 実行可否判定

認証ヘッダーに含まれるBearerトークンが`CRON_SECRET`と一致する場合のみ実行する。SHA-256ハッシュによるタイミングセーフ比較を行い、タイミング攻撃を防止する。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| Authorization | string | Yes | - | Bearerトークン形式の認証ヘッダー |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| document_vector_store_sources | DB | インジェスト対象ドキュメント情報 |
| document_vector_stores | DB | ベクトルストア情報 |
| document_embedding_profiles | DB | 埋め込みプロファイル設定 |
| Supabase Storage | ファイル | アップロードされたドキュメントファイル |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| document_vector_store_sources | DB | ステータス更新（completed/failed/idle） |
| document_embeddings | DB | 生成された埋め込みベクトル |

### 出力ファイル仕様

ファイル出力はない（データベースへの登録のみ）。

| 項目 | 内容 |
|-----|------|
| ファイル名 | 該当なし |
| 出力先 | 該当なし |
| 文字コード | 該当なし |
| 区切り文字 | 該当なし |

## 処理フロー

### 処理シーケンス

```
1. 認証チェック
   └─ Authorizationヘッダーのトークンを検証

2. インジェスト対象取得
   └─ idle状態、または15分以上running状態のドキュメントを抽出
   └─ 各ドキュメントに紐づく埋め込みプロファイルIDを取得

3. ドキュメントごとの処理（順次実行）
   └─ 3.1 ドキュメントのロック取得（atomic UPDATE）
   └─ 3.2 ファイルダウンロード（Supabase Storage）
   └─ 3.3 テキスト抽出（TXT/MD/PDF対応）
   └─ 3.4 テキストチャンキング
   └─ 3.5 埋め込みプロファイルごとの埋め込み生成
   └─ 3.6 埋め込みベクトルのDB登録
   └─ 3.7 ステータス更新（completed）

4. エラー発生時
   └─ 部分挿入された埋め込みのロールバック削除
   └─ ステータスをfailedに更新

5. レスポンス返却
   └─ 処理結果（processed, success, failure, skipped）を返却
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D[インジェスト対象取得]
    D --> E{対象あり?}
    E -->|なし| F[空結果返却]
    E -->|あり| G[次のドキュメント取得]
    G --> H{ロック取得}
    H -->|失敗| I[スキップ]
    H -->|成功| J[ファイルダウンロード]
    J --> K[テキスト抽出]
    K --> L[チャンキング]
    L --> M[埋め込み生成]
    M --> N[DB登録]
    N --> O{成功?}
    O -->|はい| P[completed更新]
    O -->|いいえ| Q[failed更新]
    P --> R{残りあり?}
    Q --> R
    I --> R
    R -->|あり| G
    R -->|なし| S[結果集計]
    S --> T[レスポンス返却]
    F --> T
    T --> U[バッチ終了]
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 対象取得 | document_vector_store_sources | SELECT | インジェスト対象のドキュメント取得 |
| 対象取得 | document_vector_stores | SELECT | ベクトルストア情報取得 |
| 対象取得 | document_embedding_profiles | SELECT | 埋め込みプロファイル取得 |
| ロック取得 | document_vector_store_sources | UPDATE | ステータスをrunningに更新 |
| 埋め込み登録 | document_embeddings | INSERT | 生成した埋め込みベクトルを登録 |
| ステータス更新 | document_vector_store_sources | UPDATE | 処理結果に応じたステータス更新 |
| ロールバック | document_embeddings | DELETE | エラー時の部分挿入削除 |

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

#### document_vector_store_sources

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, dbId, documentVectorStoreDbId, ingestStatus, updatedAt | ingestStatus = 'idle' OR (ingestStatus = 'running' AND updatedAt < 15分前) | 対象抽出 |
| UPDATE | ingestStatus | 'running' | ロック取得時 |
| UPDATE | ingestErrorCode | null | ロック取得時にクリア |
| UPDATE | updatedAt | 現在日時 | タイムスタンプ更新 |
| UPDATE | ingestStatus | 'completed' / 'failed' / 'idle' | 処理結果に応じて |
| UPDATE | ingestErrorCode | エラーコード文字列 | 失敗時のみ |
| UPDATE | ingestedAt | 現在日時 | 完了時のみ |

#### document_embeddings

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | documentVectorStoreDbId | ストアのdbId | 外部キー |
| INSERT | documentVectorStoreSourceDbId | ソースのdbId | 外部キー |
| INSERT | embeddingProfileId | プロファイルID | 埋め込み設定 |
| INSERT | embeddingDimensions | 1536 / 3072 | 次元数 |
| INSERT | documentKey | ファイル名 | ドキュメント識別子 |
| INSERT | chunkIndex | 0〜N | チャンク番号 |
| INSERT | chunkContent | テキスト内容 | チャンクテキスト |
| INSERT | embedding | ベクトル配列 | 埋め込みベクトル |
| DELETE | documentVectorStoreSourceDbId, embeddingProfileId | ソースDBIDとプロファイルIDで絞り込み | ロールバック時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| source-not-found | 業務エラー | 対象ソースが存在しない | ステータスをfailedに更新 |
| file-not-found | 業務エラー | Supabase Storageにファイルが存在しない | ステータスをfailedに更新 |
| extraction-failed | 業務エラー | テキスト抽出に失敗 | ステータスをfailedに更新 |
| chunking-failed | 業務エラー | チャンキング処理に失敗 | ステータスをfailedに更新 |
| embedding-failed | 業務エラー | 埋め込み生成に失敗 | 部分挿入をロールバック後、ステータスをfailedに更新 |
| unsupported-type | 業務エラー | サポートされていないファイル形式 | ステータスをfailedに更新 |
| invalid-state | 業務エラー | 不正な状態遷移 | ステータスをfailedに更新 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 自動リトライなし |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし（failed状態はcronでリトライされない） |

### 障害時対応

- `failed`ステータスのドキュメントは自動リトライされない
- ユーザーによる再アップロード、または設定修正（API Key設定等）後に手動対応が必要
- running状態で15分以上経過したドキュメントは次回cron実行時に再処理される

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | ドキュメント単位 |
| コミットタイミング | 各ドキュメントの処理完了時 |
| ロールバック条件 | 埋め込み生成・登録エラー時に部分挿入を削除 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 1回の実行で数件〜数十件 |
| 目標処理時間 | 800秒以内（maxDuration設定） |
| メモリ使用量上限 | Vercel Serverless関数の制限内（1GB程度） |

## 排他制御

- 楽観的排他制御（atomic UPDATE）により、複数cronインスタンス間での競合を防止
- ロック取得時にingestStatusとupdatedAtの条件でUPDATEを実行し、rowCount = 0の場合はスキップ
- 15分以上経過したrunning状態は「スティール」により別インスタンスが処理可能

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | バッチ開始時 | 暗黙的（Vercel Cronログ） |
| 進捗ログ | 処理中 | スキップ時「Document {sourceId} is already being processed, skipping」 |
| 終了ログ | バッチ終了時 | 暗黙的（HTTPレスポンス） |
| エラーログ | エラー発生時 | 「Failed to ingest document {sourceId}:」+ エラーメッセージ |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 800秒（タイムアウト） | Vercel Functions監視 |
| エラー件数 | 異常な連続失敗 | アプリケーションログ監視 |

## 備考

- ドキュメントは順次処理される（並列処理なし）。これは埋め込みAPIへの負荷を軽減するため
- failed状態のドキュメントは意図的に自動リトライ対象から除外されている（永続的エラーの無限ループ防止）
- アボートシグナル対応により、処理中断時はidle状態に戻される
