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