# 機能設計書 27-Explain API

## 概要

本ドキュメントは、OpenSearchのExplain API機能に関する機能設計書である。特定ドキュメントのスコア計算根拠を説明する機能を定義する。

### 本機能の処理概要

**業務上の目的・背景**：検索結果のランキング（スコアリング）がなぜそのようになったかを理解するために、特定のドキュメントがクエリに対してどのようにスコアリングされたかの詳細な説明が必要である。Explain APIは、検索クエリのチューニング、関連性の改善、スコアリングの問題診断に不可欠なデバッグツールである。

**機能の利用シーン**：検索結果のランキング理由の調査、BM25スコアリングパラメータの調整、カスタムスコアリングロジックのデバッグ、検索品質改善のためのスコア分析などで利用される。

**主要な処理内容**：
1. 対象インデックスとドキュメントIDを指定してExplainリクエストを構築する
2. クエリをリライトしてから、該当シャードでドキュメントを取得する
3. Luceneのsearcher.explain()を使用してスコア計算の詳細な説明を生成する
4. リスコアコンテキストがある場合は、リスコアラーの説明も含める
5. オプションで_sourceやストアドフィールドも返却する

**関連システム・外部連携**：SearchServiceを利用してSearchContextを作成し、Lucene IndexSearcherのexplain()メソッドを呼び出す。

**権限による制御**：`indices:data/read/explain`アクション名で権限が制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | 検索説明 | 主画面 | 特定ドキュメントがクエリに一致する理由とスコア計算根拠を返す処理 |

## 機能種別

データ検索（スコア説明・デバッグ）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| index | String | Yes | 対象インデックス名 | null不可 |
| id | String | Yes | 対象ドキュメントID | 空文字不可 |
| query | QueryBuilder | Yes | 説明対象のクエリ | null不可 |
| routing | String | No | ルーティング値 | - |
| preference | String | No | シャード優先指定 | - |
| stored_fields | String[] | No | 取得するストアドフィールド | - |
| _source | FetchSourceContext | No | ソースフィールドの取得制御 | - |

### 入力データソース

REST APIエンドポイント（`GET /{index}/_explain/{id}`, `POST /{index}/_explain/{id}`）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| _index | String | インデックス名 |
| _id | String | ドキュメントID |
| matched | boolean | ドキュメントがクエリに一致したか |
| explanation | Explanation | スコア計算の詳細説明 |
| explanation.value | float | スコア値 |
| explanation.description | String | スコア計算の説明テキスト |
| explanation.details | Explanation[] | 下位レベルの説明（再帰構造） |
| get | GetResult | ドキュメントのソース/ストアドフィールド（オプション） |

### 出力先

REST APIレスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. クエリのリライト
   └─ Rewriteable.rewriteAndFetchでクエリを最適化
2. シャードルーティング解決
   └─ operationRouting.getShardsでドキュメントIDから対象シャードを特定
3. エイリアスフィルタの構築
   └─ searchService.buildAliasFilterでエイリアスフィルタを適用
4. ルーティング必須チェック
   └─ ルーティング必須なのに未指定の場合はRoutingMissingException
5. SearchContextの作成
   └─ searchService.createSearchContextで検索コンテキストを構築
6. ドキュメントの取得
   └─ Engine.Getでドキュメント存在確認
7. スコア計算の説明
   └─ context.searcher().explain()でLuceneのExplanationを取得
8. リスコアの説明追加
   └─ rescorer.explain()でリスコア結果の説明を追加
9. ソース/ストアドフィールドの取得（オプション）
   └─ GetResultとして_sourceやストアドフィールドを返却
```

### フローチャート

```mermaid
flowchart TD
    A[ExplainRequest] --> B[クエリリライト]
    B --> C[シャードルーティング解決]
    C --> D[エイリアスフィルタ構築]
    D --> E[SearchContext作成]
    E --> F[Engine.Get でドキュメント取得]
    F --> G{ドキュメント存在?}
    G -->|No| H[matched=false で返却]
    G -->|Yes| I[parsedQuery構築]
    I --> J[searcher.explain でスコア説明取得]
    J --> K{リスコアあり?}
    K -->|Yes| L[rescorer.explain追加]
    K -->|No| M{ソース取得要求?}
    L --> M
    M -->|Yes| N[GetResult取得]
    M -->|No| O[ExplainResponse返却]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-27-01 | ドキュメント不存在 | ドキュメントが存在しない場合、matched=falseのレスポンスを返却 | ドキュメント不存在時 |
| BR-27-02 | ルーティング必須 | ルーティング必須インデックスでルーティング未指定の場合はRoutingMissingException | ルーティング必須設定時 |
| BR-27-03 | リスコア対応 | リスコアコンテキストが定義されている場合、リスコア後の説明も含める | リスコア定義時 |
| BR-27-04 | IDF差異注意 | Explain APIのIDFは通常検索と異なる可能性がある（単一シャードでの計算） | 常時（TODO注記） |

### 計算ロジック

Luceneのsearcher.explain()がBM25等のスコアリングアルゴリズムに基づいてスコアの詳細分解を生成する。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ドキュメント取得 | 対象インデックスの対象シャード | SELECT | ドキュメントの存在確認とデータ取得 |
| スコア説明 | 対象インデックスの対象シャード | SELECT | Luceneインデックスに対するスコア説明 |

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

読み取り専用操作。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | ActionRequestValidationException | index、id、queryが未指定 | 必須パラメータを指定 |
| 400 | RoutingMissingException | ルーティング必須なのに未指定 | routingを指定する |
| 500 | OpenSearchException | Luceneのexplain実行中にIOException | インデックスの状態を確認 |

### リトライ仕様

TransportSingleShardActionのリトライ仕様に準拠（シャードレプリカへのフォールバック）。

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

読み取り専用操作であり、トランザクション管理は不要。Engine.GetResultとSearchContextは明示的にクローズされる。

## パフォーマンス要件

- GETスレッドプール（ThreadPool.Names.GET）で実行される
- 検索スロットル設定のインデックスでは、SEARCH_THROTTLEDスレッドプールで実行される
- awaitShardSearchActiveにより、シャードがアクティブになるまで待機

## セキュリティ考慮事項

- アクション名`indices:data/read/explain`による権限制御
- ドキュメントの_sourceを返却する場合は、フィールドレベルセキュリティに注意

## 備考

- TransportSingleShardActionを継承しており、単一シャードへのリクエストとして処理される
- コード内のTODOコメントにより、AggregatedDfsの未対応が記されている（IDFが通常検索と異なる可能性）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ExplainRequest.java | `server/src/main/java/org/opensearch/action/explain/ExplainRequest.java` | id, query, routing, preference, storedFields, fetchSourceContext |
| 1-2 | ExplainResponse.java | `server/src/main/java/org/opensearch/action/explain/ExplainResponse.java` | matched, explanation, getResult |

**読解のコツ**: ExplainRequestはSingleShardRequest<ExplainRequest>を継承しており、単一シャードへのリクエストとして設計されている。idフィールド（65行目）、queryフィールド（68行目）が中核。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ExplainAction.java | `server/src/main/java/org/opensearch/action/explain/ExplainAction.java` | アクション定義 |
| 2-2 | RestExplainAction.java | `server/src/main/java/org/opensearch/rest/action/search/RestExplainAction.java` | REST APIエンドポイント |

#### Step 3: トランスポート層の処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | TransportExplainAction.java | `server/src/main/java/org/opensearch/action/explain/TransportExplainAction.java` | スコア説明の中核ロジック |

**主要処理フロー**:
- **104-116行目**: doExecute - クエリリライトと実行開始
- **124-132行目**: resolveRequest - エイリアスフィルタ構築、ルーティング必須チェック
- **149-184行目**: shardOperation - 検索コンテキスト作成、ドキュメント取得、explain実行
- **155-156行目**: Engine.Getでドキュメント取得
- **163行目**: searcher.explain()でLuceneのExplanation取得
- **164-167行目**: リスコアラーのexplain追加
- **192-201行目**: shards - シャードルーティング解決

### プログラム呼び出し階層図

```
RestExplainAction (REST)
    |
    +-- TransportExplainAction.doExecute()
            |
            +-- Rewriteable.rewriteAndFetch() [クエリリライト]
            |
            +-- resolveRequest() [エイリアスフィルタ/ルーティング]
            |
            +-- shardOperation()
                    |
                    +-- searchService.createSearchContext()
                    |
                    +-- indexShard.get() [Engine.Get]
                    |
                    +-- context.searcher().explain() [Lucene Explanation]
                    |
                    +-- rescorer.explain() [リスコア説明]
                    |
                    +-- indexShard.getService().get() [_source取得]
```

### データフロー図

```
[入力]                    [処理]                              [出力]

ExplainRequest ──────> クエリリライト ──────> ExplainResponse
  (index, id,           |                    (matched,
   query)               v                    explanation,
                   シャードルーティング        get)
                        |
                   Engine.Get
                        |
                   searcher.explain()
                        |
                   Explanation
                        |
                   rescorer.explain()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ExplainAction.java | `server/src/main/java/org/opensearch/action/explain/ExplainAction.java` | ソース | アクション型定義 |
| ExplainRequest.java | `server/src/main/java/org/opensearch/action/explain/ExplainRequest.java` | ソース | リクエストモデル |
| ExplainResponse.java | `server/src/main/java/org/opensearch/action/explain/ExplainResponse.java` | ソース | レスポンスモデル |
| TransportExplainAction.java | `server/src/main/java/org/opensearch/action/explain/TransportExplainAction.java` | ソース | スコア説明ロジック |
| RestExplainAction.java | `server/src/main/java/org/opensearch/rest/action/search/RestExplainAction.java` | ソース | REST APIエンドポイント |
