# バッチ設計書 12-IndicesRequestCacheCleaner

## 概要

本ドキュメントは、OpenSearchのインデックスリクエストキャッシュを定期的にクリーンアップするバッチ処理「IndicesRequestCacheCleaner」の設計仕様を記載する。

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

IndicesRequestCacheCleanerは、IndicesRequestCache内部のIndicesRequestCacheCleanupManagerが管理するクリーンアップタスクであり、期限切れのキャッシュエントリやクローズされたシャードに関連するキャッシュエントリを定期的に削除するバッチ処理である。

**業務上の目的・背景**：インデックスリクエストキャッシュは、シャードレベルのリクエスト応答（特に集約処理など高コストな検索結果）をキャッシュする機能である。NRT（Near Real-Time）セマンティクスに完全準拠しており、インデックスリーダーのキャッシュキーをキーの一部として使用する。しかし、リーダーがクローズされたりシャードが削除された場合、対応するキャッシュエントリが残留する。本バッチは、これらの陳腐化したエントリを検出・削除し、メモリ使用量を適正に保つために必要である。また、陳腐化率（staleness threshold）に基づくクリーンアップスキップ機能により、不要なキャッシュスキャンを回避してパフォーマンスを最適化する。

**バッチの実行タイミング**：`indices.requests.cache.cleanup.interval`設定に基づく定期実行（デフォルトは`indices.cache.cleanup_interval`と同じ1分間隔）。ノード起動時にIndicesRequestCacheCleanupManagerのコンストラクタでスケジューリングされる。

**主要な処理内容**：
1. IndicesRequestCacheCleanupManagerのcleanCache()メソッドを呼び出す
2. 陳腐化率（staleKeysCount / totalKeysInCache）が閾値を超えているか判定
3. 閾値超過時、keysToCleanセットからクリーンアップ対象キーを分類（フルクリーンアップ、クローズ済みシャード、古いリーダー）
4. キャッシュのイテレーターを走査し、該当するエントリを削除
5. クローズされたシャードのスタッツ情報をドロップ
6. cache.refresh()で変更を反映

**前後の処理との関連**：IndicesRequestCacheのgetOrCompute()でキャッシュミス時にCleanupKeyが登録される。リーダーがクローズされるとClosedListenerが発火し、キーがクリーンアップキューに追加される。本バッチはそれらのキューを定期的に処理する。

**影響範囲**：各ノードのインデックスリクエストキャッシュ（ヒープメモリまたはディスク）に影響する。キャッシュエントリの削除により、次回同等クエリ実行時はキャッシュミスとなるが、陳腐化したエントリのみが対象であるため検索精度への影響はない。

## バッチ種別

キャッシュ管理 / メモリクリーンアップ

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 定期実行（デフォルト1分間隔） |
| 実行時刻 | ノード起動後、設定間隔ごとに繰り返し実行 |
| 実行曜日 | 該当なし（常時） |
| 実行日 | 該当なし（常時） |
| トリガー | ThreadPoolによるスケジューリング（scheduleUnlessShuttingDown） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| ノードが起動中であること | ノードシャットダウン時はスケジュールされない |
| IndicesRequestCacheが初期化済みであること | キャッシュインスタンスが利用可能であること |

### 実行可否判定

1. IndicesRequestCacheCleanerのclosedフラグがfalseであること
2. cleanCache()内で陳腐化率チェック：`staleKeysInCachePercentage < stalenessThreshold`の場合はスキップ（ただしthresholdが0の場合は常に実行）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| indices.requests.cache.cleanup.interval | TimeValue | No | indices.cache.cleanup_intervalと同値（1m） | クリーンアップ実行間隔 |
| indices.requests.cache.cleanup.staleness_threshold | String | No | 0% | 陳腐化率の閾値。0%の場合は毎回クリーンアップを実行 |
| indices.requests.cache.size | ByteSizeValue | No | 1%（ヒープの1%） | キャッシュサイズ上限 |
| indices.requests.cache.expire | TimeValue | No | 0（無期限） | キャッシュエントリの有効期限 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| keysToClean | ConcurrentSet | クリーンアップ対象として登録されたCleanupKeyのセット |
| cleanupKeyToCountMap | ConcurrentHashMap | ShardId別のリーダーキャッシュキーIDとカウントのマップ |
| staleKeysCount | AtomicInteger | 陳腐化したキーの数 |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| ICache | キャッシュ更新 | 陳腐化エントリの削除 |
| ヒープメモリ | メモリ解放 | 不要キャッシュエントリの解放 |

### 出力ファイル仕様

ファイル出力はなし。

## 処理フロー

### 処理シーケンス

```
1. IndicesRequestCacheCleaner.run()実行
   └─ cacheCleanupManager.cleanCache()を呼び出し
2. canSkipCacheCleanup()で陳腐化率チェック
   └─ staleKeysInCachePercentage < stalenessThresholdならスキップ
3. keysToCleanからCleanupKeyを分類
   ├─ readerCacheKeyId==null → cleanupKeysFromFullClean（フルクリーンアップ）
   ├─ entity.isOpen()==false → cleanupKeysFromClosedShards（クローズ済みシャード）
   └─ それ以外 → cleanupKeysFromOutdatedReaders（古いリーダー）
4. キャッシュのキーをイテレーション
   ├─ フルクリーンアップ対象またはクローズ済みシャード対象 → エントリ削除
   ├─ cacheEntityがnull → エントリ削除＋ディメンション情報記録
   └─ 古いリーダー対象 → エントリ削除
5. クローズ済みシャードのスタッツ情報をドロップ
6. cache.refresh()で変更反映
7. 再スケジュール
   └─ closedフラグがfalseの場合、次回実行をスケジュール
```

### フローチャート

```mermaid
flowchart TD
    A[IndicesRequestCacheCleaner.run 開始] --> B[cleanCache 呼び出し]
    B --> C{canSkipCacheCleanup?}
    C -->|スキップ| D[デバッグログ出力]
    C -->|実行| E[keysToCleanから対象キーを分類]
    E --> F{対象キーあり?}
    F -->|なし| G[処理終了]
    F -->|あり| H[キャッシュキーをイテレーション]
    H --> I[該当エントリを削除]
    I --> J[クローズ済みシャードのスタッツをドロップ]
    J --> K[cache.refresh 実行]
    D --> L{closed == false?}
    G --> L
    K --> L
    L -->|Yes| M[次回実行をスケジュール]
    L -->|No| N[終了]
    M --> N
```

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

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

本バッチはデータベース操作を行わない。インメモリキャッシュの操作のみを行う。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| なし | Exception | cleanCache()実行中の例外 | 警告ログ"Exception during periodic indices request cache cleanup:"を出力し処理継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 明示的なリトライなし（定期実行により自動再試行） |
| リトライ間隔 | 次回定期実行間隔 |
| リトライ対象エラー | すべてのException |

### 障害時対応

例外発生時は警告ログが出力されるが、再スケジュールは継続される。陳腐化エントリが蓄積する場合は`indices.requests.cache.cleanup.staleness_threshold`を下げる（0%にすると毎回クリーンアップ）か、`indices.requests.cache.cleanup.interval`を短くすることで対応可能。

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | synchronizedブロックでcleanCache全体を排他制御 |
| コミットタイミング | cache.refresh()呼び出し時 |
| ロールバック条件 | なし（部分的なクリーンアップは許容される） |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | キャッシュ内のエントリ数に依存 |
| 目標処理時間 | キャッシュサイズに応じて変動。陳腐化率チェックによりスキップ可能 |
| メモリ使用量上限 | クリーンアップ処理中、一時的にSet（cleanupKeysFromOutdatedReaders等）を使用 |

## 排他制御

cleanCache()メソッドはsynchronizedで排他制御されており、同時に複数のクリーンアップ処理が実行されることはない。また、IndicesRequestCacheCleanerはScheduleUnlessShuttingDownで逐次実行されるため、前回の実行完了後に次回がスケジュールされる。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| デバッグログ | cleanCache開始時 | "Cleaning Indices Request Cache with threshold : {threshold}" |
| デバッグログ | スキップ時 | "Skipping Indices Request cache cleanup since the percentage of stale keys : {X} is less than the threshold : {Y}" |
| 警告ログ | 例外発生時 | "Exception during periodic indices request cache cleanup:" + 例外詳細 |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| リクエストキャッシュサイズ | indices.requests.cache.sizeで設定（デフォルト1%） | OpenSearchログ |
| 陳腐化率 | indices.requests.cache.cleanup.staleness_thresholdで設定 | OpenSearchログ |

## 備考

- IndicesRequestCacheCleanerはIndicesRequestCache内部のIndicesRequestCacheCleanupManager内のprivate finalクラスとして実装されている
- 実装ファイル: `server/src/main/java/org/opensearch/indices/IndicesRequestCache.java`（875行目付近）
- 陳腐化率の追跡機能により、キャッシュサイズが大きい場合でも不要なフルスキャンを回避できる
- 動的設定`indices.requests.cache.cleanup.staleness_threshold`で陳腐化閾値を変更可能
- CleanupKeyはIndexReader.ClosedListenerを実装しており、リーダークローズ時に自動的にキューに追加される
