はじめに
RAGシステムでは、データをベクトル化(Embedding)する必要があります。
しかし、数千件のデータを毎回処理すると、以下の問題が発生します。
- 時間がかかる:全件処理に数十分〜数時間
- コストがかかる:Embedding APIは従量課金
この記事では、**変更があったデータだけを処理する「差分同期」**の仕組みを解説します。
日常的な運用コストを抑えながら、データを最新に保つ方法です。
毎回フル処理する問題点
処理時間の問題
毎回フル処理すると、「ちょっとマニュアルを1件追加しただけ」でも長時間待つことになります。
コストの問題
OpenAIのEmbedding APIは従量課金です。
text-embedding-3-smallの場合、100万トークンあたり約$0.02です。
一見安そうに見えますが、変更がないのに毎回お金を払うのは無駄です。
このムダを減らすだけでも、運用はかなり楽になりますよね。
差分同期の考え方
基本的なアイデア
差分同期の基本的なアイデアは、**「変更があったものだけ処理する」**ことです。
各データソースから最新データを取得
各データの「指紋」を計算
DBに保存されているハッシュと比較
ハッシュが異なるもののみEmbedding
コンテンツハッシュとは
コンテンツハッシュとは、データの内容を短い文字列(ハッシュ値)に変換したものです。同じ内容なら同じハッシュ値になります。
データ内容 → ハッシュ関数 → ハッシュ値
「返金の手続きについて...」 → MD5 → "a1b2c3d4e5f6..."
「返金の手続きについて...」 → MD5 → "a1b2c3d4e5f6..."(同じ)
「返金の手順について...」 → MD5 → "x7y8z9w0..."(異なる)
差分検知の仕組み
データベースに保存する際、ハッシュ値も一緒に保存しておきます。
同期時に、新しいデータのハッシュと保存済みのハッシュを比較します。
実装のポイント
ハッシュ計算の対象
ハッシュ計算には、Embeddingに影響する要素のみを含めます。
メタ情報が変わっただけで再Embeddingしないよう、検索に関わる部分だけをハッシュ対象にします。
バルク処理
差分が見つかった場合、1件ずつAPIを呼ぶのではなく、まとめて処理します。
変更あり: 10件
↓
10件をまとめてEmbedding APIに送信
↓
10件まとめてDBに保存
これにより、API呼び出し回数を減らし、処理効率を上げています。
トランザクション管理
データの整合性を保つため、トランザクションで処理します。
BEGIN
元データにないものを削除
変更があったものを更新
新規データを追加
COMMIT(途中でエラーなら ROLLBACK)
途中でエラーが発生しても、中途半端な状態にならないようにしています。
コスト削減効果
比較シナリオ
実際のプロジェクトでの比較です。
日常的な運用
通常の運用では、大きな変更がないケースがほとんどです。
- マニュアルの軽微な修正:数件
- 新しいFAQの追加:週に数件
- 対応履歴の追加:月に数十〜数百件
差分同期により、これらの日常的な更新は数秒〜数分で完了します。
運用フロー
推奨される更新サイクル
同期コマンド
同期は、シンプルなコマンド1つで実行できます。
npm run sync-all
このコマンドを実行すると、以下の処理が自動で行われます。
- 各データソースからデータを取得
- ハッシュを比較して差分を検出
- 差分のみをEmbedding
- DBを更新
- 処理結果をレポート表示
処理結果のレポート
同期完了後、以下のような結果が表示されます。
=== 同期完了 ===
処理時間: 45秒
変更検出: 15件
- 追加: 8件
- 更新: 5件
- 削除: 2件
スキップ: 5,235件
API費用: 約$0.0002
どのくらいの処理が行われたか、一目で確認できます。
フォールバック:フル同期
いつフル同期が必要か
差分同期が基本ですが、以下の場合はフル同期が必要になることがあります。
フル同期コマンド
必要な場合は、フル同期も可能です。
npm run sync-all --force
--force オプションを付けると、ハッシュ比較をスキップして全件処理します。
まとめ
差分同期により、以下を実現しました。
- 処理時間の短縮:日常的な更新は数秒〜数分で完了
- コストの削減:変更がない場合はAPI費用ゼロ
- 運用のしやすさ:コマンド1つで完了、レポートで結果確認
データ量が増えても、差分同期なら日常的な運用コストを抑えたままデータを最新に保てます。