中文翻译暂不可用,显示俄语原文。
Как вы делаете cache invalidation для semantic cache при обновлении знаний?
Краткий тезис
Инвалидация semantic cache (семантического кэша) — одна из самых сложных задач в RAG-системах, особенно при частом обновлении базы знаний. Идеального решения нет: простой TTL (Time‑to‑Live) может выдавать устаревшие ответы, а точное версионирование документов требует отслеживания зависимостей между кэшированными ответами и исходными чанками. На практике часто используют гибридные стратегии: TTL для быстро меняющихся данных и версионирование для стабильных документов, дополняя их event‑driven invalidation (инвалидацией по событиям) при обновлении.
1. Термин: Semantic Cache (семантический кэш)
cache|Semantic cache — это кэш, который хранит не точные совпадения запросов, а их семантические эмбеддинги. При новом запросе система ищет в кэше наиболее похожий (по косинусной близости) запрос и, если сходство выше порога, возвращает сохранённый ответ без обращения к LLM и retrieval.
Зачем нужен
- Снижение latency (задержки) — ответ из кэша приходит за миллисекунды вместо секунд.
- Экономия токенов и стоимости LLM.
- Разгрузка векторной БД и пайплайна retrieval.
Где хранится обычно в in‑memory store (Redis, Memcached) или в быстрой векторной БД (FAISS, Milvus).
2. Проблема: stale cache при обновлении знаний
Если документы в базе знаний обновляются (добавлены новые факты, исправлены ошибки, удалены устаревшие данные), кэшированные ответы могут стать stale (устаревшими). Пользователь получит неверный или неполный ответ, что подрывает доверие к системе.
Пример:
В базе знаний был документ «Столица Франции — Париж». Кэш сохранил ответ на вопрос «Какая столица Франции?». Затем документ исправляют на «Столица Франции — Лион» (гипотетически). Кэш продолжает выдавать «Париж» до инвалидации.
Ключевой вызов semantic cache хранит ответы, а не исходные документы. Нужно понять, какие кэшированные ответы зависят от обновлённого документа, и удалить или пересчитать их.
3. Подход 1: TTL (Time‑to‑Live)
TTL — время жизни кэш-записи. По истечении TTL запись автоматически удаляется или помечается как устаревшая.
Как работает
- При сохранении ответа в кэш устанавливается TTL (например, 1 час, 1 день).
- По истечении срока запись исчезает, следующий запрос пойдёт в полный пайплайн.
Плюсы
- Простота реализации.
- Не требует отслеживания зависимостей.
- Подходит для данных, которые меняются предсказуемо (например, новости обновляются раз в час).
Минусы
- Не гарантирует актуальность: если документ изменился через 5 минут после кэширования, ответ будет неверным ещё 55 минут.
- Избыточная инвалидация: если документ не менялся, кэш всё равно сбрасывается.
Когда использовать
- Данные обновляются редко и по расписанию (например, ежедневные отчёты).
- Допустима небольшая задержка в актуализации.
4. Подход 2: Versioned Cache (версионирование документов)
Versioned cache — каждый документ в базе знаний имеет версию (монотонно возрастающее число или хеш). Кэшированный ответ хранит список версий документов, на основе которых он был сгенерирован.
Как работает
- При генерации ответа система фиксирует версии всех чанков, попавших в контекст.
- В кэш сохраняется кортеж (эмбеддинг запроса, ответ, список версий).
- При попадании в кэш проверяется, совпадают ли текущие версии документов с сохранёнными.
- Если хотя бы одна версия изменилась → кэш считается невалидным, ответ перегенерируется.
Плюсы
- Высокая точность: кэш инвалидируется только при реальном изменении зависимых документов.
- Минимум ложных срабатываний.
Минусы
- Сложность: нужно хранить версии для каждого документа, обновлять их при изменениях.
- Дополнительное хранилище для маппинга
(документ → версия). - При каждом запросе из кэша нужно проверять версии (несколько чтений).
Реализация версий
- Использовать hash содержимого документа (SHA256) — автоматически меняется при любом изменении.
- Или sequence number (инкремент) — проще, но требует ручного управления.
5. Подход 3: Invalidation by Dependency (отслеживание зависимостей)
Это развитие versioned cache: система хранит граф зависимостей между кэшированными ответами и документами (чанками). При обновлении документа инвалидируются все ответы, которые ссылаются на этот документ.
Как работает
- В отдельной таблице (или графовой БД) хранятся связи:
(id_ответа, id_документа). - При обновлении документа система находит все ответы, зависящие от него, и удаляет их из кэша.
- Можно делать lazy invalidation (помечать ответ как «возможно устаревший» и проверять при следующем запросе) или eager (немедленно удалять).
Плюсы
- Минимальное время неактуальности: ответы инвалидируются сразу после обновления документа.
- Не нужно хранить версии в самом кэше.
Минусы
- Высокая сложность поддержки графа зависимостей.
- При большом количестве ответов инвалидация может быть массовой (каскадное удаление).
- Требует транзакционной согласованности между обновлением документа и инвалидацией.
Когда использовать
- Системы с высокой частотой обновлений (например, вики-подобные базы знаний).
- Когда критична актуальность ответов (финансовые, медицинские данные).
6. Подход 4: Event‑driven invalidation (инвалидация по событиям)
Система подписывается на события обновления документов (например, через message queue — Kafka, RabbitMQ). При получении события о изменении документа запускается процедура инвалидации.
Как работает
- При обновлении документа публикуется событие document_updated(document_id, new_version).
- Consumer (слушатель) получает событие и инвалидирует все кэшированные ответы, связанные с этим документом.
- Инвалидация может быть асинхронной — кэш очищается в фоне.
Плюсы
- Слабая связанность: обновление документа и инвалидация кэша разделены.
- Масштабируемость: можно добавлять новые обработчики событий.
Минусы
- Задержка между событием и фактической инвалидацией (eventual consistency).
- Необходимость в инфраструктуре очередей.
Когда использовать
- Микросервисная архитектура, где обновление документа и кэш — разные сервисы.
- Высоконагруженные системы, где синхронная инвалидация заблокирует запись.
7. Сравнительная таблица подходов
| Подход | Точность | Сложность | Задержка инвалидации | Нагрузка на хранилище | Пример использования |
|---|---|---|---|---|---|
| TTL | Низкая | Очень низкая | До TTL | Низкая | Новостная лента, прогноз погоды |
| Versioned cache | Высокая | Средняя | Мгновенно (при проверке) | Средняя (версии) | Корпоративная база знаний, FAQ |
| Dependency graph | Очень высокая | Высокая | Мгновенно (eager) или лениво | Высокая (граф) | Медицинские справочники, юридические документы |
| Event‑driven | Средняя | Средняя | Несколько секунд | Низкая (очередь) | Микросервисные RAG-системы |
8. Практические рекомендации (гибридные стратегии)
В реальных проектах редко используют один подход. Часто комбинируют:
- TTL + versioned cache для «горячих» данных (часто запрашиваемых) — TTL короткий (5–10 минут), для остальных — версионирование.
- Lazy invalidation при попадании в кэш проверять не только TTL, но и версию документа (если версия устарела — перегенерировать). Это снижает нагрузку на фоновую инвалидацию.
- Probabilistic invalidation использовать Bloom filter для быстрой проверки, изменился ли документ, без хранения полного списка версий.
Пример гибрида в коде (псевдокод):
class SemanticCache:
def __init__(self, ttl_seconds=3600):
self.cache = {} # key: embedding_hash, value: (response, doc_versions, timestamp)
self.doc_versions = {} # doc_id -> version
def get(self, query_embedding):
key = self._hash(query_embedding)
if key not in self.cache:
return None
response, versions, ts = self.cache[key]
# 1. Check TTL
if time.time() - ts > self.ttl_seconds:
del self.cache[key]
return None
# 2. Check versions
for doc_id, cached_version in versions.items():
current_version = self.doc_versions.get(doc_id)
if current_version != cached_version:
del self.cache[key]
return None
return response
def set(self, query_embedding, response, doc_ids):
versions = {doc_id: self.doc_versions[doc_id] for doc_id in doc_ids}
key = self._hash(query_embedding)
self.cache[key] = (response, versions, time.time())
9. Связь с Agentic RAG
В Agentic RAG агенты могут кэшировать не только ответы LLM, но и результаты выполнения инструментов (например, вызов API, результаты поиска). Инвалидация становится ещё сложнее:
- Агент может выполнить цепочку действий, и кэшированный результат одного шага может зависеть от нескольких документов.
- Нужно хранить трассировку зависимостей (trace) для каждого кэшированного вывода.
- Рекомендуется использовать versioned cache с графом зависимостей, где узлы — не только документы, но и результаты вызовов инструментов.
10. Пет-проект для закрепления
Задача Реализовать semantic cache с versioned invalidation для простой RAG-системы на основе Wikipedia.
Инструменты
- Python, Flask/FastAPI для API.
- Sentence‑Transformers для эмбеддингов.
- Redis (in‑memory store) для кэша.
- SQLite для хранения версий документов.
Шаги:
- Загрузите несколько статей Wikipedia (например, 10) в локальную векторную БД (FAISS).
- Реализуйте простой RAG: retrieval + LLM (можно использовать OpenAI API или локальную модель).
- Добавьте semantic cache: при запросе ищите похожий запрос в Redis (по косинусной близости эмбеддингов).
- Внедрите версионирование: при добавлении/обновлении статьи увеличивайте её версию в SQLite.
- При попадании в кэш проверяйте версии документов, на которые ссылается ответ.
- Напишите скрипт, который обновляет одну из статей и проверяет, что кэш для связанных запросов стал невалидным.
Ожидаемый результат
Работающий сервис, который возвращает актуальные ответы после обновления базы знаний, при этом для неизменённых документов продолжает использовать кэш.
11. Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 244 | Проектирование semantic cache |
| 246 | Версионирование документов |
| 247 | Согласованность данных |
| 248 | Оценка актуальности |
| 249 | Тестирование инвалидации |
| 250 | Интеграция с внешними источниками |
12. Навигация
- Предыдущий: 244
- Следующий: 246
- Индекс: 00. Индекс разборов
Навигация
- Предыдущий: 244
- Следующий: 246
- Индекс: 00. Индекс разборов