LLM Frontline
開発・エージェント

RAGのインデックス更新・鮮度管理|「作ったら終わり」にしないための変更検知と削除反映の設計

ミナト・開発・API担当
・ 約36分で読めます
RAGのインデックス更新・鮮度管理|「作ったら終わり」にしないための変更検知と削除反映の設計

RAGを立ち上げるとき、いちばん盛り上がるのは初回の一括インデックスです。社内のファイルを読み込み、チャンクに割り、埋め込みを取り、ベクトルDBに入れる。検索が効いて回答が返る瞬間は、たしかに気持ちがいい。問題はその翌月から始まります。規程が改訂され、価格表が差し替わり、退職者のフォルダが消え、誤って公開された資料が削除される。それらの変更がインデックスに追いつかないと、RAGは「古い情報を、出典つきで、自信満々に」答えるようになります。この記事では、初回構築のあとに必要になる継続運用の設計を、変更検知・チャンクIDの安定性・削除の反映・更新中の一貫性・埋め込みモデルの移行・鮮度の可観測性という順で整理します。仕様の記述は各製品の公式ドキュメントで裏を取りました。

初回構築と継続運用は別の仕事

初回の一括インデックスは、規模こそ大きいものの性質は単純なバッチ処理です。全部読んで、全部入れて、失敗したらやり直す。途中の状態を誰も見ていないので、冪等性も一貫性も深刻な問題になりません。

継続運用はそうはいきません。検索は常時走り、元文書は非同期に変わり、変更の通知は取りこぼしうる。ここで必要になるのは「差分をどう知るか」「同じ更新を二度流しても壊れないか」「消えたものをどう消すか」という、データ同期の問題です。RAGの解説はチャンク分割や検索精度で終わりがちですが、本番の運用を引き継いだ人が最初に困るのはこちら側です。

鮮度切れはハルシネーションより見つけにくい

運用の観点では、作り話より鮮度切れのほうが厄介です。理由は3つあります。第一に、回答には出典が付きます。利用者は出典を見て信頼しますが、その中身は半年前のインデックスに入った旧版のテキストです。第二に、旧版の規程は旧版として筋が通った文章なので、内容の整合性から誤りを疑えません。第三に、誰も指摘しません。作り話は「そんな制度はない」と返ってきますが、旧版の説明は「前はそうだったな」で流されます。

「精度の改善要望は来るのですが、鮮度の苦情はほとんど来ないんです。来るときは『この案内、去年の金額で説明されていました』という形で、しかも社外に出たあとでした。」
社内RAGを引き継いだエンジニア

利用者からの報告は「ファイルは直したのに古いことを言う」という形で来ます。そこに至る経路は、差分検知のジョブが止まっている、変更ありと判定されなかった、新チャンクは入ったが旧チャンクが消えていない、取り込みが失敗してログに埋もれている、のいずれかです。どれも二値ではなくパイプラインのどこかの段で止まっているので、文書単位の記録がないと切り分けられません。

あわせて読みたい

RAGとは何か|仕組み・向き不向き・導入判断の考え方

変更検知の方式を選ぶ

差分をどう知るかには、いくつかの定番があります。

方式やることコスト取りこぼしのリスク
フルリビルド(全件洗い替え)毎回すべての文書を読み直し、作り直す文書数に比例。埋め込み費用が毎回かかる最小。ただし実行間隔がそのまま鮮度の上限になる
更新日時(ハイウォーターマーク)前回実行時刻より新しいものだけ処理する低い更新日時が変わらない編集、同一秒内の複数更新などで漏れる
ETag・条件付きリクエスト前回のETagを保存し、If-None-Match付きで取得して304なら未変更として飛ばす低〜中。全件にリクエストは要るが、未変更なら304で本文の転送は発生しないサーバのETag生成規則が弱いと同値とみなされて漏れる
内容ハッシュ本文のハッシュを保存し、変化したものだけ再処理する中。本文の取得自体は全件必要漏れにくい
変更追跡・チェンジフィード元システムの変更ログを順に消化する低い。削除も取れることが多い保持期間を超えて放置するとフィードが切れる
Webhook・プッシュ通知通知を受けて即時に処理する低い。受け口の運用は必要通知の欠落・重複・順序入れ替えが前提。単独では使えない

フルリビルドは「負け」ではない

差分更新は正しい設計ですが、フルリビルドが常に劣るわけではありません。文書数が数千程度で埋め込みのコストが許容範囲なら、毎晩の洗い替えは「取りこぼしが構造的に起きない」という強い性質を持ち、削除の反映も自動的に解決します。差分ロジックのバグを運用で抱え続けるより総コストが低い場面は普通にあります。判断の目安は1回の所要時間と費用で、夜間に収まらなくなった時点が差分更新へ移る潮時です。

更新日時の落とし穴

いちばん手軽なのは、前回実行時刻より新しい更新日時の文書だけを処理する方式です。Azure AI SearchはこれをHigh Water Mark Change Detection Policyとして提供しており、公式ドキュメントは対象列に、すべての挿入で値が設定されること、すべての更新で値が変わること、値が増加し続けること、その列での絞り込みと並び替えが効率よく実行できることを求めています。

同ドキュメントはさらに、この列にはrowversion型を強く推奨し、それ以外の型では「インデクサのクエリと同時に実行されるトランザクションがある状況で、すべての変更を捕捉できる保証はない」と明記しています。FAQでも、rowversion以外の使用は推奨しないとしつつ、アプリケーションのロジック次第では安全になりうる条件として、インデクサ実行時に対象テーブルに未解決のトランザクションがないことを保証できる場合(更新をスケジュールされたバッチにまとめ、インデクサの実行時刻と重ならないようにするなど)と、定期的に全件再インデックスして漏れを拾う場合の2つを挙げています。更新日時方式は、定期的なフルリビルドとセットで初めて完結する設計だということです。

HTTP経由で取得する文書なら、条件付きリクエストが使えます。RFC 9110はETagを条件付きリクエストのための不透明な検証子と定義し、W/接頭辞の弱い検証子と、バイト単位の同一性まで保証する強い検証子を区別しています。Last-Modifiedについては、強い検証子だと判断できる条件を満たさないかぎり暗黙に弱い検証子として扱われるとしています。その条件として挙げられているのは、オリジンサーバが「その表現は当該の1秒間に2回変わっていない」と確実に分かる場合などです。裏を返せば、1秒の粒度しか持たない日時は、同一秒内の連続更新を取りこぼしうるということです。同RFCは、更新日時の1秒解像度では不十分な場面ではETagのほうが検証手段として信頼できる、とも書いています。

内容ハッシュは素直で強い

もっとも取りこぼしにくいのは、本文そのもののハッシュを持つ方式です。主要なフレームワークが標準で備えています。LlamaIndexのIngestion Pipelineは、ドキュメントストアにdoc_idからdocument_hashへのマップを保持し、同じdoc_idでハッシュが変わっていれば再処理してアップサート、変わっていなければそのノードをスキップすると説明しています。ベクトルストアを接続していない場合は重複入力の検出と除去しかできない、という注記も付いています。

LangChainも同じ発想です。公式リポジトリのlangchain_core/indexing/api.pyでは、レコードマネージャがどの文書がベクトルストアに書き込まれたかを追跡し、文書はハッシュを使ってインデックスされるとindex()のドキュメント文字列に書かれています。ハッシュ関数はkey_encoderで選べ、既定のSHA-1については「衝突耐性がなく、動機を持った攻撃者は同じキーになる2つの異なるテキストを作れる」ため新しいアプリケーションは他のエンコーダを使うべきだとされ、さらに「キーエンコーダを変更したら、重複を避けるためにインデックス側も変更しなければならない」と明記されています。ハッシュ関数自体がインデックスのバージョンの一部になる、ということです。

メモ

内容ハッシュの対象は「埋め込みの入力になるテキスト」と揃えてください。ファイルのバイト列を対象にすると、PDFのメタデータだけが変わった再保存で全チャンクが再埋め込みになります。逆に抽出後の本文だけを対象にすると、抽出器のバージョンアップで出力が変わったことに気づけません。抽出器の版をハッシュの材料に混ぜておくと両立できます。

変更追跡とWebhook

元システムが変更ログを提供しているなら、それが最も素直です。Azure AI SearchのSQLインデクサはSQL Serverのネイティブな変更追跡を使うSqlIntegratedChangeTrackingPolicyを推奨しており、効率性に加えて削除された行を識別できることを理由に挙げています。ただし制約も具体的で、データベース側で変更追跡が有効になっていること、テーブルのみ(ビュー不可)、テーブル自体がクラスター化されていないこと(既存のクラスター化インデックスは非クラスター化で作り直す必要があり、公式ドキュメントはこの回避策がしばしば性能を劣化させると注記しています)、主キーは単一列、削除検知には主キーが非クラスター化であること、テーブルが空でないこと、が並びます。「TRUNCATE TABLEで行を消した場合、インデクサをリセットして再実行しても対応する検索ドキュメントは削除されない」とまで書かれています。さらに、削除された行が自動で検知されるには、検索インデックス側のドキュメントキーがSQLテーブルの主キーと同じであることも求められます。ドキュメントキーをURLやハッシュから作る設計にすると、この条件を満たさず削除が反映されません。

Google Driveのように変更一覧をページトークンで消化する方式もあります。公式ドキュメントによれば、changes.getStartPageTokenで現在時点のトークンを取り、changes.listで古い順に変更を取得し、レスポンスのnewStartPageTokenを次回のために保存します。削除や共有解除を含めるかはincludeRemovedで決まります。プッシュ通知(changes.watch)もありますが、同ドキュメントは「通知には変更の詳細が含まれない。新しい変更があることを示すだけだ」と明記しています。

これは設計上の重要な示唆です。Webhookは「いま取りに行け」という合図であって、変更内容そのものではありません。通知が落ちても、重複しても、順序が入れ替わっても、最終的な状態が正しくなる作りにしておく必要があります。実務的には次の4層になります。

  1. 1

    Webhookは合図として受ける

    通知を受けたら文書IDを更新キューに積むだけにします。通知のペイロードを信じて本文を再構成しないでください。
  2. 2

    ワーカーが元システムから取り直す

    最新の本文とメタデータを取得し、ハッシュを比較して、変わっていれば再チャンク・再埋め込み・置換します。
  3. 3

    定期の差分スキャンを併走させる

    通知の取りこぼしを拾うため、更新日時や変更追跡による巡回を並行して回します。頻度は通知より粗くて構いません。
  4. 4

    定期のフルリビルドまたは全件ハッシュ照合を置く

    月次でも四半期でも構いません。差分ロジックのバグが積み上がっていないかを、いちばん確実に確かめられる手段です。

注意

チェンジフィードや変更追跡には保持期間があります。取り込みを数日止めたあとに再開すると、フィードが期限切れで読めず全件からやり直しになることがあります。停止時に「保持期間内に復旧できなければフルリビルドへ切り替える」という判断基準を決めておいてください。止まったことに気づかないまま保持期間を過ぎるのが最悪のパターンです。

あわせて読みたい

ベクトルデータベースの選び方|pgvector・Qdrant・Milvus・Weaviate・Chromaを用途と規模と運用体制で決める

冪等性と安定ID

差分検知ができても、書き込み側が冪等でなければ運用は破綻します。同じ更新が二度流れても、途中で失敗して再実行しても、最終状態が同じになること。その鍵がチャンクIDです。

チャンクIDの決め方には2つの流儀があります。1つ目は「文書ID+チャンク位置」。位置が同じなら同じIDになるので、更新のたびに同じIDへ上書きできます。Qdrantの公式ドキュメントは「同じidの点は再アップロード時に上書きされる」と明記し、IDとして64ビットの符号なし整数とUUIDが使えるとしています。文字列をそのままIDにできない実装もあるため、安定した文字列キーをUUIDv5などで決定的に変換する設計がよく使われます。

2つ目は「内容ハッシュ」。同じ本文なら常に同じIDになるので重複が構造的に起きませんが、本文が1文字変わるとIDが変わるため、旧IDを別途消さないと孤児チャンクが残ります。実務では両方を持つのが扱いやすく、IDは位置ベースで安定させ、内容ハッシュはメタデータに置いて再埋め込みの要否判定に使います。

# 位置ベースの安定IDと、内容ハッシュによる再埋め込みの抑制を併用する例(擬似コード)
import hashlib, uuid

NAMESPACE = uuid.UUID("00000000-0000-0000-0000-000000000000")  # 自組織で固定した名前空間

def chunk_id(doc_id: str, chunker_version: str, position: int) -> str:
    # チャンカの版をIDの材料に入れると、再チャンク時に新旧が衝突しない
    return str(uuid.uuid5(NAMESPACE, f"{doc_id}|{chunker_version}|{position:05d}"))

def content_hash(text: str, extractor_version: str, embed_model: str) -> str:
    h = hashlib.sha256()
    for part in (extractor_version, embed_model, text):
        h.update(part.encode())
        h.update(b"\x00")
    return h.hexdigest()

再チャンクするとチャンクIDの対応が崩れる

チャンクサイズやオーバーラップ、見出しの扱いを変えると、同じ文書から生成されるチャンクの数と境界が変わります。内容ハッシュをIDにしている場合や、上の擬似コードのようにチャンカの版をIDの材料に入れている場合は、旧チャンクのIDは新チャンク側に一切存在しなくなります。版を含めない素朴な位置ベースでも、チャンク数が減った分の旧IDは上書きされずに孤児として残ります。いずれにせよ更新は、チャンク単位のアップサートではなく文書単位の置換として設計します。

1. doc_idでフィルタして既存チャンクのIDを列挙する
2. 新チャンクを生成し、アップサートする
3. 手順1で列挙したIDのうち、新チャンクに含まれないものを削除する

この順序(先に入れて、あとで消す)なら、処理中に検索が走っても「新旧が一時的に重複して出る」で済みます。逆順にすると、その文書は削除から投入までの間、検索結果から消えます。多くの社内RAGでは重複のほうが害は小さいはずです。

LangChainのindex()も同じ問題をcleanup引数で扱います。公式リポジトリのドキュメント文字列によれば、incrementalは「更新されておらず、かつ今回のインデックス作成で見かけたソースIDに紐づく文書をすべて掃除する」もので、掃除がインデックス作成中に継続的に行われるため利用者が重複した内容を見る可能性を最小化できます。fullは「今回のローダーが返さなかった文書をすべて削除する」もので、掃除が全文書の処理後に走るため「インデックス作成中は利用者が重複した内容を見る可能性がある」とされています。あわせて、fullモードではローダーがデータセットの一部ではなく全体を返す必要があり、そうでなければ消してはいけない文書まで消えてしまう、という警告も書かれています。

ヒント

文書単位の置換を成立させるには、チャンクのメタデータに必ずdoc_idを持たせ、そのフィールドにインデックスを張っておく必要があります。Qdrantの公式ドキュメントはIDによる削除に加えてフィルタによる削除も用意していると説明しています。doc_idでフィルタできない設計にすると、古いチャンクを消す手段が全件走査しか残りません。

あわせて読みたい

RAGのチャンク分割(chunking)とは|検索品質を左右する文書の区切り方

削除の反映がいちばん漏れる

追加と更新は「新しい情報が入っていない」という分かりやすい苦情になります。削除は違います。消したはずの文書が検索され続けても、利用者は元文書が消えたことを知らないので異常だと思いません。そして削除漏れは、しばしば情報漏えいそのものです。

元システムから物理的に消えた場合、差分検知の側からは「存在しないもの」を観測できません。Azure AI Searchの公式ドキュメントはこの非対称性をはっきり書いています。Azure Storageが相手なら変更検知は組み込みのタイムスタンプで自動的に行われるが、削除検知はそうではない。インデクサはデータソース側のオブジェクト削除を追跡しない、と。

だから各サービスは論理削除(ソフトデリート)を要求します。同ドキュメントが挙げるのは、Blob Storageのネイティブなソフトデリート機能を使う方法と、カスタムメタデータで削除フラグを立てる方法の2つです。後者はIsDeletedのようなフラグを用意し、データソース定義にSoftDeleteColumnDeletionDetectionPolicyとsoftDeleteColumnName・softDeleteMarkerValueを設定します。Azure SQLインデクサでも、High Water Mark方式は削除行を扱えないため、ソフトデリート列と削除検知ポリシーの併用が案内されています。

ここで見落としやすい注意が2つあります。

1つ目。削除検知の戦略は「最初のインデクサ実行の時点から」適用されていなければなりません。公式ドキュメントは、初回実行より前に削除された文書は、ポリシーを後から追加してリセットしてもインデックスに残ると明記し、そうなった場合は新しいインデクサで新しいインデックスを作り直すことを勧めています。

2つ目。ネイティブのソフトデリートでは、インデクサがそのBlobを「ソフトデリート状態のときに処理した場合にのみ」対応するドキュメントを削除します。だから保持期間はインデクサの実行間隔よりずっと長く設定せよ、と書かれています。削除の猶予期間が実行間隔より短いと、消えたことを誰も観測しないまま物理削除が完了します。

注意

マネージドサービスでも削除の反映は自動では完結しません。Amazon Bedrock Knowledge Basesの公式ドキュメントは、同期は増分であり前回同期以降に追加・変更・削除されたドキュメントだけを処理するとしたうえで、同期シナリオの表で「ドキュメントが削除された場合、そのドキュメントはベクトルストアから削除される」と説明しています。削除は反映されますが、それは同期を実行したときの話です。同期そのものが止まっていれば、消したはずの文書は残り続けます。

定期同期とは別に、「いますぐ消す」経路も要ります。誤って公開された資料、退職者の個人フォルダ、契約解除した取引先の資料は次のスケジュール実行を待てません。Bedrock Knowledge Basesには同期を経ずにインデックスを直接操作するKnowledgeBaseDocuments系のAPIがあり、公式ドキュメントはS3とカスタムのデータソースで「同期の必要なく、単一の操作でファイルの追加・更新・削除ができる」と説明しています。ただしS3については、直接入れた変更はS3側に反映されないため後で同じ変更をS3にも入れないと次の同期で上書きされること、IngestKnowledgeBaseDocumentsとStartIngestionJobを同時に投げてはいけないことが併記されています。緊急経路は正規の同期と競合しない形で用意してください。

削除反映の確認項目

  • 元システムから物理削除されたものを検知する手段がある(論理削除、変更追跡、または定期の全件突き合わせ)
  • 削除検知の仕組みが初回インデックスの時点から有効だった(後付けなら、それ以前の削除分は残っている前提で棚卸しした)
  • 文書IDを指定して全チャンクを1操作で消せる緊急経路がある
  • 削除したはずの文書IDで検索して0件になることを、削除処理の直後に自動で確認している
  • インデックスだけでなく、キャッシュ・評価用データセット・ログ・バックアップからも消す手順がある
  • 権限剥奪(異動・退職・契約終了)が、削除ではなく権限メタデータの更新として反映される経路がある
  • 削除操作が監査ログに残る(誰が・いつ・どの文書IDを・なぜ)
  • 1回の同期で削除される件数に上限アラートを設定している

最後の項目は見落とされがちです。差分検知にバグがあると、「ローダーが返さなかった文書をすべて削除する」という掃除モードは一瞬でインデックスを空にします。削除件数に上限を設け、超えたら停止して人間に投げてください。なお、文書は残すが特定の人に見せてはいけない、というケースは削除ではなく権限メタデータの問題です。

あわせて読みたい

RAGのアクセス制御設計|社内文書の閲覧権限をベクトル検索にどう持ち込むか

更新中の検索一貫性

1000文書を再インデックスする間も検索は止まりません。そのとき利用者が見るのは「半分だけ新しいインデックス」です。

多くの社内RAGでは、これは許容できます。文書単位で置換していれば、見えるのは主に「ある文書は新版・別の文書は旧版」という状態です。同じ文書についても、先に入れてあとで消す手順を取る以上、置換の窓の間は新旧のチャンクが一時的に併存しますが、これは短時間で収束します。問題になるのは、複数の文書をまたいだ整合性が要る場合です。価格表と料金規程が同時に改訂され、片方だけ反映された状態で検索されると、矛盾した根拠から回答が組み立てられます。この種のデータは文書単位ではなく「改訂セット単位」で切り替える必要があります。

そこで使うのがインデックスのエイリアス(別名)です。アプリは常に別名に対して検索し、裏で新しいインデックスを作り終えたら別名の指す先を切り替えます。Elasticの公式ドキュメントは、エイリアスAPIについて「単一のアトミックな操作で複数のエイリアス操作を実行できる」とし、入れ替えの間エイリアスにはダウンタイムがなく両方を同時に指すこともないと説明しています。Qdrantも、バックグラウンドで2つ目のコレクションを構築してエイリアスを切り替えられるとし、「エイリアスの変更はすべてアトミックに行われるため、切り替え中に同時実行のリクエストが影響を受けることはない」としています。

Azure AI Searchのエイリアスには実務的な制約が明記されています。エイリアスの更新が伝播するまで最大10秒かかることがあるため、以前のインデックスを削除する前に最低10秒待つこと。indexes配列に指定できるインデックス名は1つだけであること。そしてエイリアスはインデクサのtargetIndexNameには指定できないこと。最後の制約は切り替え手順に効きます。インデクサ側は実インデックス名を見ているので、切り替えのたびにインデクサ定義も更新する必要があります。

メモ

エイリアス切り替えは常用するものではありません。全件を作り直すコストがかかるため、日常の更新は文書単位の置換、節目の変更(チャンク戦略の変更、埋め込みモデルの交換、スキーマ変更)はエイリアス切り替え、と使い分けるのが現実的です。切り替え後も旧インデックスはすぐ消さず、一定期間残してロールバック経路にしてください。

埋め込みモデルを変えるときの全件再インデックス

埋め込みモデルの差し替えは、部分更新ができない代表例です。理由は2つあります。

1つは次元です。Qdrantの公式ドキュメントは「同じコレクション内の各点のベクトルは、同じ次元数を持ち、単一の距離尺度で比較されなければならない」と明記しています。pgvectorでもvector(1536)のように列定義で次元を固定するのが一般的です。公式リポジトリのFAQは次元指定なしのvector型なら異なる次元を同じ列に置けるとしていますが、インデックスは同じ次元数の行にしか張れません。もう1つは、次元が同じでも意味が違うことです。異なるモデルの出力は別々の空間に属するため、次元数がたまたま一致していても混ぜれば距離の比較が意味を失います。エラーにならないぶん、こちらのほうが危険です。

インデックス側の上限にも注意が要ります。pgvectorの公式リポジトリによれば、vector型は最大16,000次元まで扱えますが、HNSWとIVFFlatのインデックスは(通常のvectorで)最大2,000次元までです。高次元のモデルを選ぶ場合、インデックスを張れるかを先に確認してください。

Qdrantの公式チュートリアルは、移行手順をブルーグリーン方式として整理しています。新しい次元のコレクションを作り、新旧への二重書き込みを有効にし、バックグラウンドで旧コレクションを走査して新モデルで埋め込みを取り直し、検索トラフィックを新へ切り替え、二重書き込みを止めて旧を削除する流れです。名前付きベクトルを使う構成では、新モデル用のベクトルを追加のスキーマとして足す方法も案内されています。

同チュートリアルには重要な注意があります。ブルーグリーンの手順は「アップサート操作についてのみそのまま機能する。削除や部分更新を使っている場合は、移行中それらの操作を止めるか、追加のロジックを実装する必要がある」という点です。二重書き込みで削除が片方にしか届かないと、切り替えた瞬間に「消したはずの文書が復活する」事故になります。

  1. 1

    移行前に評価セットを凍結する

    現行モデルでの検索品質を先に測ります。基準値がなければ移行後の良し悪しを判断できません。
  2. 2

    新しいインデックスを作る

    新モデルの次元と距離尺度で作成します。スキーマやメタデータの変更もここでまとめると、再インデックスの回数を減らせます。
  3. 3

    二重書き込みを有効にする

    移行中の新規・更新を新旧両方へ書き込みます。削除と部分更新の扱い(止めるか、両方に適用するか)を先に決めます。
  4. 4

    既存文書を背後で再埋め込みする

    レート制限とコストを見ながら流します。元本文をペイロードに保持していれば元システムへ取りに行かずに済みます。保持していなければ、ここで大量アクセスが発生します。
  5. 5

    新旧で検索品質を比較する

    凍結した評価セットで比較し、劣化があれば切り替えずにチャンク設定などを見直します。
  6. 6

    エイリアスを切り替え、旧を一定期間残す

    切り替えはアトミックに行い、問題があれば戻せる状態を保ちます。二重書き込みの停止と旧インデックスの削除は、明示的なタスクとして登録してください。

あわせて読みたい

エンベディングとは何か|ベクトル検索の仕組みをRAGの内側から理解する

鮮度の可観測性

「更新されているはず」を「更新されていることが確認できる」に変えるのが、この節の目的です。最低限、各チャンクのメタデータに次を持たせます。

メタデータ用途
source_updated_at元文書側の最終更新時刻。回答に時点を出す材料になる
last_indexed_atこのチャンクが書かれた時刻。更新ラグの計測に使う
content_hash再埋め込みの要否判定と、取り込みの重複検知
chunker_version / embed_model再チャンク・再埋め込みの対象を絞り込む版情報
doc_id文書単位の置換・削除の軸

source_updated_atとlast_indexed_atの差が、その文書の更新ラグです。全文書のこの差の分布を見れば、鮮度が保てているかが一目で分かります。監視項目としては、更新ラグのp50とp95、元文書数とインデックス上の文書数の差分、1回の同期での追加・更新・削除の件数、失敗文書の件数と滞留時間、ジョブの最終成功時刻、埋め込みAPIの呼び出し回数とコストを並べておけば十分です。最後の項目は、変更検知が壊れて全件再埋め込みになったときに跳ねます。

注意

鮮度の監視でいちばん多い失敗は、ジョブの成功・失敗だけを見ていることです。「同期は成功、処理件数は0件」という状態は、変更がなかったのか、変更検知が壊れて何も拾えていないのかを区別できません。処理件数が長期間0のまま続いたら警告を上げてください。

取り込みは必ず一部が失敗します。壊れたPDF、パスワード保護されたファイル、レート制限、巨大すぎる文書。これらを「ログに出して次へ」で流すと、その文書は永久にインデックスに入りません。一時的な失敗は指数バックオフで再試行し、規定回数を超えたらデッドレターキューへ送って文書ID・エラー種別・最終試行時刻を残し、その件数を監視して週次で中身を見る担当を決めます。恒久的に取り込めない文書は除外リストに明示的に登録し、黙って失敗し続ける状態をなくしてください。

あわせて読みたい

LLM APIのレート制限とリトライ設計|429・タイムアウト・二重実行を本番で捌く

回答に「いつ時点の情報か」を出す

どれだけ頑張ってもラグはゼロになりません。だからこそ、回答側で時点を示す設計が効きます。やることは単純で、取得したチャンクのメタデータを回答の生成に引き回し、出典表示に含めるだけです。

参照した文書:
- 就業規則 第3版(最終更新: 2026-04-01 / インデックス反映: 2026-04-02 03:12)
- 育児休業取扱規程(最終更新: 2025-11-15 / インデックス反映: 2025-11-16 03:10)

この表示があると、利用者は「4月改訂のはずなのに参照が去年のものだ」と自分で気づけます。運用側が鮮度を全部保証するのではなく、利用者に検証の手がかりを渡す設計です。さらに「最終更新から一定期間を過ぎた文書だけが根拠になった場合は注意書きを添える」というルールも有効ですが、この判定をモデルに任せると安定しません。メタデータを見てアプリ側で決定的に付与してください。

あわせて読みたい

LLMアプリのオブザーバビリティ|トレーシングとログ設計で「なぜこの出力になったか」を後から追えるようにする

更新頻度とコストをどう決めるか

全文書を毎日回す必要はありません。文書種別ごとに頻度を分けるのが基本です。

文書種別の例更新の性質妥当な同期頻度
価格表・料金規程改訂が業務に直結し、誤りの影響が大きい通知ベースで即時。加えて日次で突き合わせ
社内規程・手順書改訂は頻繁ではないが、旧版の回答が実害になる日次の差分同期
製品ドキュメント・FAQ更新は多いが多少の遅れは許容できる日次から週次
議事録・報告書追加が中心で更新はまれ追加時のみ。週次の巡回でも足りる
過去のアーカイブほぼ変わらない月次または四半期の全件突き合わせのみ

コストの内訳も押さえておきます。差分更新は「変更のあった文書数 × チャンク数 × 埋め込み単価」、フルリビルドは「全文書 × チャンク数 × 埋め込み単価」です。内容ハッシュ方式では埋め込みこそ差分で済みますが、本文の取得とテキスト抽出は全件必要になる点を見落とさないでください。また、元データが月次でしか更新されないなら、1時間ごとに同期しても鮮度は改善しません。元データの更新リズムに頻度を合わせるのが原則です。

あわせて読みたい

LLMのコスト管理|トークン課金の考え方と削減の定石

RAGに載せない判断

更新頻度が極端に高いデータは、RAGに向きません。在庫数・残席・配送状況のように分単位で変わる値、利用者ごとに異なる値(注文履歴、契約内容、ポイント残高)、集計して求める値がこれにあたります。ベクトルインデックスに載せると更新が追いつかないうえ、そもそも意味の近さで検索する対象ではないため、たまたま近いものが返るという挙動になります。

性質向く方式
文章として書かれた知識で、更新が日から月単位RAG(ベクトル検索)
構造化された値で、更新が分から時間単位ツール呼び出し(API直参照)
利用者ごとに異なる値ツール呼び出し(認証済みセッションのIDで問い合わせる)
集計・計算が必要ツール呼び出し、またはSQL生成

「返品ポリシー(RAG)を説明しつつ、この注文の返品期限(API)を答える」のように両方を持つ構成は一般的です。ただし同じ情報を両方に置くのは避けてください。片方が古くなったとき、どちらが正なのか分からなくなります。

あわせて読みたい

LLMのTool Use(関数呼び出し)とは|仕組みと精度を上げるツール設計

既存RAGの棚卸し手順

すでに動いているRAGを引き継いだ場合の順序です。

  1. 1

    いま入っているものを数える

    インデックス上の文書数・チャンク数と、元システム側の文書数を突き合わせます。合わないなら、追加と削除のどちらが壊れているかを先に特定します。
  2. 2

    最終更新時刻の分布を見る

    last_indexed_atがなければ、まずそれを持たせるところから始めます。分布を見れば「半年前から一度も更新されていない塊」がすぐ見つかります。
  3. 3

    削除の経路を実際に試す

    テスト文書を1件作り、インデックスに入ったことを確認してから元システムで消し、次の同期後に検索結果から消えるかを見ます。消えなければ削除検知が存在しません。
  4. 4

    定期ジョブの実行履歴を確認する

    最後に成功したのはいつか、失敗が黙殺されていないか、通知先が生きているか。ここが止まっていることは珍しくありません。
  5. 5

    不要な文書を削ってから設計に入る

    機密や権限の観点でそもそも入れるべきでなかった文書を減らします。対象が減れば更新頻度もコストも同時に下がります。
  6. 6

    文書種別ごとに更新頻度の目標を決める

    一律にせず分けます。ここで決めた目標がそのまま監視項目になります。
  7. 7

    安定IDと内容ハッシュを導入する

    IDの決め方を直すには一度のフルリビルドが必要です。スキーマ変更やチャンク設定の見直しがあるなら同じタイミングでまとめます。
  8. 8

    監視を入れてから自動化を広げる

    監視のないまま同期頻度を上げると、壊れたときの被害範囲も比例して広がります。順序を逆にしないでください。

この記事に出てくる機能名・既定値・制約は、執筆時点(2026年9月)の各社公式ドキュメントおよび公式リポジトリの記載に基づきます(Microsoft Learn、Elastic、Qdrant、pgvector、LangChain、LlamaIndex、AWS、Google Workspace、RFC 9110)。各製品の仕様は更新されるため、実装時は必ず最新の一次情報を確認してください。本記事は一般的な設計方針の整理であり、特定製品の優劣を判断するものではありません。

よくある質問

毎晩フルリビルドするのは筋が悪いですか
規模次第で、十分に妥当な選択です。フルリビルドは差分検知の取りこぼしが構造的に起きず、削除の反映も自動的に解決します。判断材料は1回にかかる時間と埋め込みのコスト、そして許容できる鮮度の遅れです。夜間バッチに収まり費用も許容範囲なら、複雑な差分ロジックを運用で抱えるより総コストが低くなります。夜間に収まらなくなった時点が差分更新へ移る目安です。なお差分更新に移っても、定期的なフルリビルドまたは全件ハッシュ照合は残してください。差分ロジックのバグが積み上がっていないかを、もっとも確実に確かめられるのがこの方法です。
更新日時だけで差分を取るのは危険ですか
単独では取りこぼします。Azure AI Searchの公式ドキュメントは、高ウォーターマーク列にrowversion型を強く推奨したうえで、それ以外のデータ型では、インデクサのクエリと同時に実行されるトランザクションがある状況ですべての変更を捕捉できる保証はないと明記しています。FAQでも、rowversion以外の使用は推奨しないとしつつ、アプリケーションのロジック次第では安全になりうる条件として、インデクサ実行時に対象テーブルに未解決のトランザクションがないことを保証できる場合(更新をスケジュールされたバッチにまとめ、インデクサの実行時刻と重ならないようにするなど)と、定期的に全件再インデックスして漏れを拾う場合の2つを挙げています。HTTP経由で取得する文書については、RFC 9110がLast-Modifiedを、強い検証子だと判断できる条件(同一秒内に2回変更していないとオリジンサーバが確実に分かる場合など)を満たさないかぎり暗黙に弱い検証子として扱うとしており、1秒の粒度では同一秒内の連続更新を区別できません。更新日時による巡回に、内容ハッシュの照合と定期のフルリビルドを組み合わせるのが安全です。
チャンクIDは内容ハッシュと位置ベースのどちらにすべきですか
併用が扱いやすくなります。位置ベース(文書ID+チャンク位置)は同じ位置なら同じIDになるので上書き更新が素直です。Qdrantの公式ドキュメントも、同じidの点は再アップロード時に上書きされると明記しています。内容ハッシュをIDにすると同じ本文の重複登録は起きませんが、本文が1文字変わるとIDが変わるため、旧IDを別途削除しないと孤児チャンクが残ります。実務的には、IDは位置ベースで安定させ、内容ハッシュはメタデータに持たせて再埋め込みの要否判定に使う構成が扱いやすいです。いずれの場合も、更新はチャンク単位ではなく文書単位の置換(新チャンクを入れてから、残った旧チャンクを消す)で考えてください。
削除の反映が漏れていないか、どう確認すればよいですか
テスト用の文書を1件消して試すのが確実です。インデックスに入ったことを確認してから元システムで削除し、次の同期後に検索結果から消えるかを見ます。あわせて、元文書数とインデックス上の文書数の差分を継続的に監視してください。なお、Azure AI Searchの公式ドキュメントは、削除検知の戦略は最初のインデクサ実行の時点から適用されている必要があり、初回実行より前に削除された文書はポリシーを後から追加してリセットしてもインデックスに残る、と明記しています。後付けした場合は、それ以前の削除分が残っている前提で棚卸ししてください。
埋め込みモデルを変えるとき、古いベクトルと混ぜて使えますか
使えません。Qdrantの公式ドキュメントは、同じコレクション内の各点のベクトルは同じ次元数を持ち単一の距離尺度で比較されなければならないとしています。pgvectorも`vector(1536)`のように列定義で次元を固定するのが一般的で、次元指定なしなら格納自体は混在できますが、インデックスは同じ次元数の行にしか張れません。次元が同じであっても、異なるモデルのベクトルは別の空間に属するため距離の比較が意味を失います。次元が一致しているとエラーにならず静かに品質が落ちるので、こちらのほうが危険です。移行はブルーグリーン方式(新しいコレクションを作り、二重書き込みしながら再埋め込みし、エイリアスを切り替える)で行います。Qdrantの移行チュートリアルは、この手順はアップサート操作についてのみそのまま機能し、削除や部分更新を使う場合は移行中それらを止めるか追加のロジックが必要だ、と注意しています。

まとめ

RAGのインデックス更新は、検索精度の話ではなくデータ同期の話です。そう捉え直すと必要なものははっきりします。差分をどう知るか、同じ更新を二度流しても壊れないか、消えたものをどう消すか、更新中に何が見えるか、そしてそれが本当に動いていることをどう確認するか。

原則を3つに絞るなら、次のとおりです。第一に、変更検知は単独の方式に頼らないこと。更新日時にせよ通知にせよ取りこぼしが前提で、定期の全件突き合わせが最後の砦になります。第二に、削除は追加より優先度が高いこと。削除漏れは苦情として表面化せず、情報漏えいになりうるからです。第三に、鮮度は測れる形にすること。last_indexed_atと更新ラグの分布がなければ、壊れていることにさえ気づけません。

そして、すべてをRAGで解こうとしないこと。更新が速いデータと利用者ごとに異なるデータはツール呼び出しに逃がすほうが、鮮度でもコストでも素直に解決します。インデックスに入れないという判断も、鮮度管理の一部です。

インデックス鮮度の運用チェックリスト

  • 初回インデックスとは別に、継続同期のジョブと死活監視が存在する
  • 変更検知の方式が取りこぼす条件を把握し、補完手段を用意した
  • 定期のフルリビルドまたは全件ハッシュ照合を、頻度を決めて実行している
  • チャンクIDが安定しており、更新が文書単位の置換として冪等に実行できる
  • 元システムからの削除を検知する手段があり、実際に1件消して反映を確認した
  • スケジュールを待たない緊急削除の経路があり、監査ログに残る
  • 1回の同期で削除される件数に上限アラートを設定している
  • source_updated_atとlast_indexed_atを持ち、更新ラグを監視している
  • 失敗文書の再試行キューとデッドレターがあり、滞留を定期的に確認する担当がいる
  • 回答の出典表示に、参照文書の最終更新時点を出している
  • 文書種別ごとに更新頻度の目標を決め、一律の同期をやめた
  • 更新が速いデータ・利用者ごとのデータをRAGから外し、ツール呼び出しに寄せた
  • 埋め込みモデルやチャンク設定を変える手順(ブルーグリーンとエイリアス切り替え)を文書化した

あわせて読みたい

Agentic RAG(エージェンティックRAG)とは|1回検索で終わる従来RAGを、計画・再検索・自己反省で賢くする

出典・参考

この記事をシェア

関連する記事

開発・エージェント

RAGのアクセス制御設計|社内文書の閲覧権限をベクトル検索にどう持ち込むか

社内文書RAGで「権限のない人が要約経由で機密を読めてしまう」事故を防ぐ設計。チャンクが元ファイルのACLを失う理由、事前フィルタ・事後フィルタ・インデックス分割・ACL同期の4パターン比較、権限フィルタとANNインデックスの再現率トレードオフを実装例とともに整理します。