RAGセキュリティ監視パイプライン — 本番ゼロからの設計ガイド
RAGセキュリティ監視パイプラインを本番環境でゼロから設計する場合、「何をどのタイミングでログに出力し、どのルールで検知するか」が最大の課題になる。 一般的なAPMツールはRAG特有のイベント構造(Ingestion・Retrieval・Generation)を把握しておらず、そのまま流用すると権限外参照やガードレール違反を見落とす。
本記事では、RAG監査ログ設計の具体的なJSONスキーマ、セキュリティイベントスキーマの必須フィールド定義、検知ルール20選、Pythonによる簡易パイプライン実装、そして品質KPIとセキュリティKPIを統合したRAGモニタリングダッシュボード構成案を解説する。
この記事で分かること
- RAG専用の監査ログスキーマ(必須フィールド・機密度ラベル)の設計方法
- 権限外参照・異常頻度・ガードレール違反を検知する20のルール定義
- 品質監視(RAGASメトリクス)とセキュリティ監視を1つのパイプラインに統合する設計パターン
対象読者 / 前提知識
対象読者:RAGを本番運用するSRE・セキュリティ監視担当
前提知識:RAGの基本アーキテクチャ(Embedding・Vector Store・LLM呼び出し)の概要、PythonでのJSON操作、SIEM/ログ集約ツールの基礎知識
目次
なぜRAGには専用の監視パイプラインが必要なのか
一般的なAPM(Datadog・New Relic等)はレイテンシ・エラーレート・スループットの3指標を中心に設計されており、誰がどのドキュメントを取得したかというRAG固有のアクセス制御イベントをキャプチャできない。 RAGはドキュメントをチャンク化してVector Storeに格納する時点で、元ドキュメントのアクセス権限がチャンク単位に引き継がれないケースが多く、これが本番でのコンプライアンス違反につながる。 OWASP RAG Security Cheat Sheetは「RAGはリスクを削減するのではなく、データパイプライン全体に再分配する」と明示している。
一般APMとRAG専用監視の差異を整理すると以下のとおりだ。
| 観点 | 一般APM | RAG専用監視 |
|---|---|---|
| イベント粒度 | HTTP req/res単位 | Ingestion・Retrieval・Generationの3フェーズ |
| アクセス制御 | APIキー・JWT認証のみ | チャンク単位のACL・テナント隔離の検証 |
| 品質KPI | 非対応 | Faithfulness・Answer Relevancy等RAGASメトリクス |
| 検知対象 | レイテンシ異常・5xxエラー | 権限外取得・ガードレール違反・異常クエリ頻度 |
| ログ保全 | 任意 | 取得ドキュメントIDをトレーサブルに保存(監査要件) |
| インシデント対応 | 汎用プレイブック | 毒入りドキュメント隔離・キャッシュ無効化専用手順 |
品質監視とセキュリティ監視は別のシステムで運用されがちだが、RAGASメトリクス(Faithfulness・Context Recall等)の悪化はドキュメント汚染攻撃の早期シグナルになりうるため、両者を同一パイプラインに統合するアーキテクチャが本番運用では有効だ。
ここまでのまとめ
- 一般APMはRAGのアクセス制御・品質KPIイベントをカバーしない
- OWASPはIngestionからGenerationまでの全フェーズにわたる可観測性を要求している
- 品質劣化シグナルとセキュリティアラートを統合することで、攻撃の早期検知が可能になる
RAGパイプラインのイベントモデル
RAGの監視設計で最初に行うべきは、パイプライン上のどの地点でどのイベントが発生するかを明確にすることだ。 以下のASCII図でイベント発生点を俯瞰する。
[ユーザー/エージェント]
│ query
▼
┌──────────────────────────────────────────────────────────┐
│ [E1] QueryReceived │
│ │ │
│ ┌──────▼──────┐ ┌───────────────┐ ┌───────────┐ │
│ │ Retrieval │───▶│ Vector Store │───▶│ LLM (Gen) │ │
│ │ Engine │ │ ACL Check │ │ │ │
│ └─────────────┘ └───────────────┘ └───────────┘ │
│ [E2] QueryNormalized │
│ [E3] RetrievalHit / [E4] RetrievalMiss │
│ [E5] ACLCheckResult (permit/deny) │
│ [E6] ChunkScoreRecorded │
│ [E7] PromptAssembled │
│ [E8] GenerationCompleted │
│ [E9] GuardrailEvaluated │
└──────────────────────────────────────────────────────────┘
▲
[Ingestion Pipeline]
[E0-A] DocumentIngested
[E0-B] DocumentUpdated
[E0-C] DocumentDeleted
[E0-D] HashVerificationFailed
Ingestionイベント(追加・更新・削除)
Ingestionフェーズでは、ドキュメントの追加・更新・削除の3種類のイベントに加え、ハッシュ検証失敗(E0-D)を独立したセキュリティイベントとして扱う必要がある。
OWASP RAG Security Cheat Sheet Section 1は「すべてのドキュメントをIngestion時点でSHA-256でハッシュし、Retrieval前に検証すること」を要求している。
ドキュメントの出所(アップロード者・ソースURL・承認フロー通過有無)をprovenanceフィールドに記録することで、汚染ドキュメントのフォレンジックが可能になる。
Retrievalイベント(クエリ・ヒット・スコア・権限チェック)
RetrievalフェーズはRAGセキュリティで最もインシデント頻度が高い領域だ。 クエリ正規化後のテキスト(E2)、取得されたチャンクのIDとコサイン類似度スコア(E6)、ACLチェック結果(E5)の3点が最低限の記録項目になる。 OWASP Section 4が強調するとおり、権限チェックはIngestion時ではなくRetrieval時にも毎回実施する必要があり、その結果を監査ログに残すことが前提条件となる。
Generationイベント(プロンプト・応答・ガードレール判定結果)
Generationフェーズでは、LLMに渡したプロンプト全体(または安全なハッシュ)、生成された応答、そしてガードレール違反検知の結果(pass/block/redact)を記録する。 ガードレール結果(E9)はセキュリティイベントとして扱い、連続違反のカウントをリアルタイムで追跡することが重要だ。 応答にPII・機密ラベル付きコンテンツが含まれていないかの出力バリデーション結果もこのイベントに統合する。
ここまでのまとめ
- イベントはIngestion(E0系)・Retrieval(E2〜E6)・Generation(E7〜E9)の3グループに分類する
- ハッシュ検証失敗とACLチェック結果は独立したセキュリティイベントとして扱う
- ガードレール判定結果はGenerationイベントに統合し、連続違反を追跡可能にする
セキュリティ監査ログスキーマの設計
RAG品質管理とセキュリティ監視を統合するには、単一のJSONスキーマで両者のフィールドを収容することが最も実装コストが低い。
スキーマはイベントタイプによってフィールドの必須/任意を切り替えられるようevent_typeを起点とした条件分岐で定義する。
必須フィールド一覧
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "RAGAuditEvent",
"type": "object",
"required": [
"event_id", "event_type", "timestamp", "trace_id",
"user_id", "role", "tenant",
"query_text_hash", "retrieved_doc_ids",
"guardrail_result", "sensitivity_label"
],
"properties": {
"event_id": { "type": "string", "format": "uuid" },
"event_type": {
"type": "string",
"enum": [
"ingestion.added", "ingestion.updated", "ingestion.deleted",
"ingestion.hash_failed",
"retrieval.query", "retrieval.hit", "retrieval.miss",
"retrieval.acl_deny",
"generation.prompt_assembled", "generation.completed",
"generation.guardrail_block", "generation.guardrail_redact"
]
},
"timestamp": { "type": "string", "format": "date-time" },
"trace_id": { "type": "string", "description": "end-to-end request correlation ID" },
"user_id": { "type": "string" },
"role": { "type": "string", "description": "RBAC role at request time" },
"tenant": { "type": "string", "description": "tenant/org identifier" },
"query_text_hash": { "type": "string", "description": "SHA-256 of normalized query" },
"retrieved_doc_ids": { "type": "array", "items": { "type": "string" } },
"chunk_scores": { "type": "array", "items": { "type": "number" } },
"acl_check_result": { "type": "string", "enum": ["permit", "deny", "partial_deny"] },
"guardrail_result": {
"type": "object",
"properties": {
"status": { "type": "string", "enum": ["pass", "block", "redact"] },
"rule_ids": { "type": "array", "items": { "type": "string" } },
"pii_detected": { "type": "boolean" }
}
},
"sensitivity_label": {
"type": "string",
"enum": ["public", "internal", "confidential", "restricted"]
},
"doc_provenance": {
"type": "object",
"properties": {
"source_url": { "type": "string" },
"uploader_id": { "type": "string" },
"sha256_hash": { "type": "string" },
"approved": { "type": "boolean" }
}
},
"ragas_metrics": {
"type": "object",
"description": "RAG品質管理メトリクス(generation eventのみ)",
"properties": {
"faithfulness": { "type": "number", "minimum": 0, "maximum": 1 },
"answer_relevancy": { "type": "number", "minimum": 0, "maximum": 1 },
"context_recall": { "type": "number", "minimum": 0, "maximum": 1 }
}
},
"response_text_hash": { "type": "string" },
"latency_ms": { "type": "integer" }
}
}
query_text_hashにはSHA-256ハッシュを格納し、生クエリテキストは別の暗号化ストレージに保存する。
これにより、ログ基盤にアクセスできるオペレーターがクエリ内容を直接参照できない構造を実現し、プライバシーとフォレンジック要件を両立できる。
ragas_metricsを同一スキーマに含めることで、FaithfulnessスコアとGuardrail違反数を同じダッシュボードで相関分析できるのがこの設計の最大の差別化ポイントだ。
機密度ラベルと取り扱いポリシー
| sensitivity_label | ログ保存期間 | アクセス可能ロール | 暗号化要件 | 削除ポリシー |
|---|---|---|---|---|
public | 90日 | 全SRE | 転送時TLS | TTL後自動削除 |
internal | 1年 | SRE・セキュリティ担当 | 転送時TLS+保存時AES-256 | 保持期限後削除 |
confidential | 3年 | セキュリティ担当のみ | テナント別鍵で暗号化 | 手動承認後削除 |
restricted | 7年(法的要件依存) | Security Lead+法務 | HSM管理鍵+WORM | 法的保持期間まで削除不可 |
OWASP RAG Security Cheat Sheet Section 4に従い、ソースドキュメントが削除または権限変更された場合はVector Store内の関連チャンクも連鎖削除し、その削除イベントをログに記録する必要がある。
restrictedラベルを持つドキュメントへの照会が集中した場合は、後述の検知ルール(R-18)が自動でアラートを発火する。
ここまでのまとめ
- 単一JSONスキーマにRAGAS品質メトリクスとセキュリティフィールドを統合することで、両者の相関分析が可能になる
- 生クエリテキストはハッシュのみをログに記録し、原文は暗号化ストアに分離保管する
- 機密度ラベルごとに保存期間・暗号化要件・削除ポリシーを定義し、コンプライアンスを担保する
検知ルール20選
以下のルール一覧は、権限外参照・異常クエリ頻度・ガードレール連続違反・大量取得・特定ラベル集中照会を網羅する。 優先度Highは即時アラート、Mediumはダッシュボードでのトレンド監視、Lowは定期バッチレポートを推奨する。
| # | ルール名 | 対象イベント | 検知条件 | アクション | 優先度 |
|---|---|---|---|---|---|
| R-01 | ACL権限外参照 | retrieval.acl_deny | 同一user_idで5分以内に3回以上deny | セッション一時停止・Slackアラート | High |
| R-02 | テナント境界越え | retrieval.hit | retrieved_doc_idsにリクエスト元tenant外のchunk_idが含まれる | 即時ブロック・インシデント起票 | High |
| R-03 | クエリ異常頻度 | retrieval.query | 同一user_idで60秒以内に30クエリ超 | レート制限・アラート | High |
| R-04 | ガードレール連続違反 | generation.guardrail_block | 同一user_idで10分以内に5回以上block | ユーザーロック・エスカレーション | High |
| R-05 | ハッシュ検証失敗 | ingestion.hash_failed | 1件でも発生 | 当該ドキュメント隔離・セキュリティ通知 | High |
| R-06 | 大量チャンク取得 | retrieval.hit | 1リクエストでretrieved_doc_ids件数が閾値(デフォルト10)超 | アラート・リクエスト記録 | High |
| R-07 | Restricted集中照会 | retrieval.hit | sensitivity_label=restrictedのchunkが10分以内に50件超取得 | 自動ブロック・インシデント起票 | High |
| R-08 | プロンプトインジェクション試行 | generation.prompt_assembled | プロンプトに"ignore previous"・"SYSTEM:"等のパターンを検出 | Generationブロック・ログ記録 | High |
| R-09 | PII出力検出 | generation.completed | guardrail_result.pii_detected=true | 応答を返さずredact・アラート | High |
| R-10 | 削除済みドキュメント参照 | retrieval.hit | retrieved_doc_idsに削除済みchunk_idが含まれる | 即時ブロック・Vector Store整合性チェック起動 | High |
| R-11 | 夜間異常クエリ | retrieval.query | 業務時間外(0:00〜5:00 JST)にクエリ頻度が通常の300%超 | アラート・ユーザー確認 | Medium |
| R-12 | 新規ロールによるConfidential参照 | retrieval.hit | roleが過去30日以内に初めてconfidentialドキュメントに触れた | 管理者通知・監査ログフラグ | Medium |
| R-13 | Faithfulness急落 | generation.completed | ragas_metrics.faithfulness が直近100件の平均から0.2以上低下 | 品質アラート・ドキュメント汚染調査トリガー | Medium |
| R-14 | スコア分布異常 | retrieval.hit | chunk_scoresの最大値が0.98超(過適合の可能性) | 埋め込み分布チェック起動 | Medium |
| R-15 | クエリパターン偵察 | retrieval.query | 同一user_idで類似クエリを系統的にバリエーション展開(Levenshtein距離≦5) | Corpus偵察疑いフラグ・アラート | Medium |
| R-16 | Ingestion権限外実行 | ingestion.added | ingestionを実行したuser_idが承認済みパイプラインロール外 | Ingestionロールバック・通知 | Medium |
| R-17 | キャッシュ境界違反 | retrieval.hit | キャッシュhit時のsensitivity_labelがリクエスタのクリアランスを超過 | キャッシュ無効化・アラート | Medium |
| R-18 | Restricted集中照会(緩) | retrieval.hit | sensitivity_label=restrictedのchunkが1時間以内に200件超 | ダッシュボードフラグ・次営業日レポート | Low |
| R-19 | ガードレール迂回試行 | generation.guardrail_block | blockされた直後の再クエリが5秒以内(自動化ツール疑い) | レート制限強化・フラグ | Medium |
| R-20 | Context Recall長期低下 | generation.completed | ragas_metrics.context_recall が7日移動平均で0.15以上低下 | Vector Store再インデックス提案・品質アラート | Low |
R-13(Faithfulness急落)とR-20(Context Recall低下)はRAGパイプライン評価のRAGAS的な品質指標をセキュリティアラートのトリガーとして使用する点で、従来の監視設計にない視点だ。 ドキュメント汚染攻撃はまず品質メトリクスの悪化として観測されることが多く、これらのルールが早期警戒システムとして機能する。
ここまでのまとめ
- High優先度10件は即時アラート・自動ブロックと連動させる
- RAGASメトリクスの急落をセキュリティルールに組み込むことで品質劣化と攻撃を同時検知できる
- R-15(偵察パターン)のようにクエリ意図レベルの検知を行うにはLevenshtein距離等の類似度計算が必要になる
Pythonでの簡易監視パイプライン実装例
以下は「ログ出力 → ルール評価 → アラート送信」の3ステップを最小限のコードで実現した実装例だ。 本番では各ステップをマイクロサービス化または既存のログ基盤(OpenTelemetry Collector等)に差し込む形で拡張する。
Step 1: 監査ログ出力
# filename: rag_audit_logger.py
import json
import hashlib
import uuid
from datetime import datetime, timezone
def make_audit_event(
event_type: str,
user_id: str,
role: str,
tenant: str,
query_text: str,
retrieved_doc_ids: list[str],
chunk_scores: list[float],
acl_check_result: str,
guardrail_result: dict,
sensitivity_label: str,
ragas_metrics: dict | None = None,
trace_id: str | None = None,
) -> dict:
return {
"event_id": str(uuid.uuid4()),
"event_type": event_type,
"timestamp": datetime.now(timezone.utc).isoformat(),
"trace_id": trace_id or str(uuid.uuid4()),
"user_id": user_id,
"role": role,
"tenant": tenant,
# Store hash only; raw text goes to encrypted store
"query_text_hash": hashlib.sha256(query_text.encode()).hexdigest(),
"retrieved_doc_ids": retrieved_doc_ids,
"chunk_scores": chunk_scores,
"acl_check_result": acl_check_result,
"guardrail_result": guardrail_result,
"sensitivity_label": sensitivity_label,
"ragas_metrics": ragas_metrics or {},
}
def emit_event(event: dict) -> None:
# Output as structured JSON log; shipped to SIEM by log agent
print(json.dumps(event, ensure_ascii=False))
出力例(retrieval.hitイベント):
{
"event_id": "a3f2c1d4-...",
"event_type": "retrieval.hit",
"timestamp": "2026-06-27T12:00:00+00:00",
"trace_id": "9b8e7f6a-...",
"user_id": "user_042",
"role": "analyst",
"tenant": "org_acme",
"query_text_hash": "e3b0c44298fc1c...",
"retrieved_doc_ids": ["doc_101_chunk_3", "doc_205_chunk_1"],
"chunk_scores": [0.91, 0.87],
"acl_check_result": "permit",
"guardrail_result": {"status": "pass", "rule_ids": [], "pii_detected": false},
"sensitivity_label": "confidential",
"ragas_metrics": {}
}
Step 2: ルール評価エンジン
# filename: rag_rule_engine.py
from collections import defaultdict, deque
from datetime import datetime, timezone
import time
class RuleEngine:
def __init__(self):
# Sliding window: user_id -> deque of timestamps
self._acl_deny_windows: dict[str, deque] = defaultdict(deque)
self._query_windows: dict[str, deque] = defaultdict(deque)
self._guardrail_windows: dict[str, deque] = defaultdict(deque)
self.DELETED_CHUNK_IDS: set[str] = set() # populated from document lifecycle events
def _sliding_window_count(self, window: deque, window_sec: int) -> int:
now = time.time()
while window and window[0] < now - window_sec:
window.popleft()
return len(window)
def evaluate(self, event: dict) -> list[dict]:
alerts = []
uid = event.get("user_id", "")
etype = event.get("event_type", "")
now = time.time()
# R-01: ACL deny 3+ times in 5 min
if etype == "retrieval.acl_deny":
self._acl_deny_windows[uid].append(now)
if self._sliding_window_count(self._acl_deny_windows[uid], 300) >= 3:
alerts.append({"rule": "R-01", "severity": "high", "user_id": uid,
"event_id": event["event_id"]})
# R-03: Query rate > 30 in 60 sec
if etype == "retrieval.query":
self._query_windows[uid].append(now)
if self._sliding_window_count(self._query_windows[uid], 60) > 30:
alerts.append({"rule": "R-03", "severity": "high", "user_id": uid,
"event_id": event["event_id"]})
# R-04: Guardrail block 5+ times in 10 min
if etype == "generation.guardrail_block":
self._guardrail_windows[uid].append(now)
if self._sliding_window_count(self._guardrail_windows[uid], 600) >= 5:
alerts.append({"rule": "R-04", "severity": "high", "user_id": uid,
"event_id": event["event_id"]})
# R-06: Bulk chunk retrieval (>10 chunks)
if etype == "retrieval.hit":
doc_ids = event.get("retrieved_doc_ids", [])
if len(doc_ids) > 10:
alerts.append({"rule": "R-06", "severity": "high", "user_id": uid,
"chunk_count": len(doc_ids), "event_id": event["event_id"]})
# R-09: PII detected in output
if etype == "generation.completed":
gr = event.get("guardrail_result", {})
if gr.get("pii_detected"):
alerts.append({"rule": "R-09", "severity": "high", "user_id": uid,
"event_id": event["event_id"]})
# R-13: Faithfulness drop (simplified check)
if etype == "generation.completed":
metrics = event.get("ragas_metrics", {})
if metrics.get("faithfulness", 1.0) < 0.5:
alerts.append({"rule": "R-13", "severity": "medium", "user_id": uid,
"faithfulness": metrics.get("faithfulness"),
"event_id": event["event_id"]})
return alerts
Step 3: アラート送信
# filename: rag_alert_dispatcher.py
import json
import urllib.request
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
def dispatch_alert(alert: dict) -> None:
severity_emoji = {"high": ":red_circle:", "medium": ":large_yellow_circle:", "low": ":large_blue_circle:"}
emoji = severity_emoji.get(alert.get("severity", "low"), ":white_circle:")
payload = {
"text": f"{emoji} *RAG Security Alert* [{alert['rule']}]\n"
f"User: `{alert.get('user_id', 'unknown')}` | "
f"Event: `{alert.get('event_id', '-')}`\n"
f"```{json.dumps(alert, ensure_ascii=False, indent=2)}```"
}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
SLACK_WEBHOOK_URL,
data=data,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as resp:
_ = resp.read()
# --- Wiring all 3 steps together ---
def process_rag_event(raw_event: dict) -> None:
engine = RuleEngine()
alerts = engine.evaluate(raw_event)
emit_event(raw_event) # Step 1: structured log
for alert in alerts: # Step 2+3: rule match -> alert
dispatch_alert(alert)
ここまでのまとめ
- 3ステップ(emit → evaluate → dispatch)を分離することで、各層の差し替えが容易になる
- スライディングウィンドウカウンタをメモリ上で管理するだけでR-01/03/04の大半は実装可能
- RAGASメトリクスはgeneration.completedイベント内に埋め込み、R-13等の品質検知ルールと統合する
ダッシュボード構成案
品質KPIとセキュリティKPIを1画面に収めることで、RAGの異常を多角的に把握できる。 以下はGrafanaやDatadogのカスタムダッシュボードを想定したレイアウト案だ。
┌──────────────────────────────────────────────────────────────────────────────┐
│ RAG Unified Monitoring Dashboard [tenant: all] [1h ▼] │
├───────────────────────────┬──────────────────────────┬───────────────────────┤
│ 🟢 QUALITY KPIs │ 🔴 SECURITY KPIs │ 🔔 ACTIVE ALERTS │
│ │ │ │
│ Faithfulness 0.87 ▲ │ ACL Denies (5min) 2 │ R-03 [HIGH] user042 │
│ Answer Relevancy 0.91 ─ │ Guardrail Blocks 14 │ R-04 [HIGH] user007 │
│ Context Recall 0.78 ▼ │ Hash Failures 0 │ R-13 [MED] trace9f │
│ │ Tenant Violations 0 │ │
├───────────────────────────┴──────────────────────────┴───────────────────────┤
│ 📈 Query Volume & Anomaly (time series, 1h) │
│ ████░░░░░█████████░░░░████████████░░░░░░░░░░░░░░░░░░ [threshold ---] │
│ 00:00 15:00 30:00 45:00 60:00 │
├──────────────────────────────────────────────────────────────────────────────┤
│ 📊 Sensitivity Label Distribution │ 🥧 Event Type Breakdown │
│ │ │
│ public ██████████████████ 68% │ retrieval.hit 45% │
│ internal ████████ 24% │ retrieval.query 30% │
│ confidential ████ 7% │ generation.* 20% │
│ restricted █ 1% │ ingestion.* 5% │
├──────────────────────────────────────────────────────────────────────────────┤
│ 🔎 Top Retrieved Doc IDs (1h) │ ⚠️ Rule Trigger Heatmap │
│ doc_205 ████████████████ 342 │ R01 R03 R04 R06 │
│ doc_101 ████████████ 218 │ u042 · ● · · │
│ doc_089 ████████ 145 │ u007 · · ● · │
│ doc_334 ████ 78 │ u019 · · · ● │
└──────────────────────────────────────────────────────────────────────────────┘
左上ブロックにRAGASメトリクス(Faithfulness・Answer Relevancy・Context Recall)を配置し、右上にセキュリティKPI(ACL Deny件数・Guardrail Block数・Hash失敗数)を並べることで、品質低下とセキュリティ事象を同一の視野で確認できる。 右下のRule Trigger HeatmapはユーザーIDとルールIDのクロス集計で、特定ユーザーが複数ルールに同時抵触しているかを即座に把握するために有効だ。 ダッシュボードのデータソースはStep 1で出力した構造化JSONログをElasticsearch/OpenSearchに取り込むか、OpenTelemetry Collector経由でPrometheusにエクスポートする構成が一般的だ。
ここまでのまとめ
- 品質KPIとセキュリティKPIを左右に並べる構成で、異常の相関を即座に読み取れる
- Rule Trigger Heatmapにより、複数ルールに同時抵触するユーザーを視覚的に特定できる
- データソースは構造化JSONログを集約ストア(Elasticsearch等)に取り込むだけで実現可能
監視の運用コスト見積もり観点
RAG監視パイプラインの運用コストは主に「ログ量」と「アラート疲れ」の2軸で決まる。 設計段階でこの2点を定量的に見積もらないと、本番稼働後に収集コストの増大やアラート無視(Alert Fatigue)が発生する。
| 見積もり項目 | 計算式の目安 | チューニング方針 |
|---|---|---|
| 1日あたりのログ量 | クエリ数/日 × 平均イベント数/クエリ(3〜5件) × 1イベント平均2KB | sensitivity_label=publicはサンプリング率10%に落として保存コスト削減 |
| アラート発火率 | ルール数 × 誤検知率 × クエリ数/日 | High優先度ルールは閾値を2週間のベースラインから設定(固定値禁止) |
| ルール評価CPU | スライディングウィンドウはO(1)。Redis利用でステートレスに水平スケール | RedisのkeyはユーザーID:rule_id、TTLをウィンドウ幅に設定して自動削除 |
| RAGASメトリクス計算コスト | LLMベースのRAGAS評価は高コスト。全件評価は不要 | 全クエリの5〜10%をランダムサンプリングして評価(重要ユーザーは100%) |
| アラート疲れ防止 | Mediumアラートの重複抑制:同一rule+user_idで30分以内の再発火は抑制 | 週次でFP/TP比を集計し、FP率30%超のルールは閾値を自動引き上げ |
アラート疲れを防ぐ最も効果的な対策は、閾値を固定値ではなくベースライン相対値で設定することだ。 たとえばR-03(クエリ異常頻度)の閾値「60秒に30クエリ超」は業務システムの種類によって最適値が大きく異なるため、2週間の移動平均の3σを閾値として動的に更新する設計を採用する。 またRAGASメトリクスの計算にはLLM APIコストが発生するため、全リクエストの5〜10%をランダムサンプリングし、suspicious_flagが立ったリクエストのみ100%評価するハイブリッド戦略が現実的だ。
ここまでのまとめ
- ログコストはsensitivity_labelごとのサンプリング率で制御し、restrictedのみ全量保存する
- 閾値は固定値ではなくベースライン相対値(移動平均±3σ)で動的設定する
- RAGASメトリクスの計算はサンプリング+フラグ付きリクエスト全件の2段階戦略で費用を最適化する
シリーズ内リンク