English translation is not available yet. Showing Russian content.
Как вы делаете schema evolution для метаданных документов в RAG?
Краткий тезис
Schema evolution (эволюция схемы) — это процесс изменения структуры метаданных документов в RAG-системе без потери совместимости с уже проиндексированными данными. Ключевая идея — использовать сериализационные форматы с поддержкой эволюции (Avro, Protobuf), делать все поля optional с default values, версионировать схемы и выполнять backfill (обратную миграцию) старых документов в новую схему. Это позволяет добавлять новые поля метаданных, не ломая существующие индексы и запросы.
1. Термин: Schema Evolution (эволюция схемы)
Schema evolution — это способность системы обрабатывать данные, записанные по старой схеме, после того как схема была изменена. В контексте RAG метаданные документов (например, дата создания, автор, категория, версия) хранятся вместе с векторными эмбеддингами в векторной БД или отдельном индексе. Когда бизнес-требования меняются, приходится добавлять, удалять или изменять поля метаданных. Без продуманной эволюции это приведёт к ошибкам десериализации, потере данных или необходимости полной переиндексации.
Почему это важно в RAG
- Метаданные используются для фильтрации (например, «только документы за 2024 год») и ранжирования (например, по рейтингу).
- Векторные БД (Pinecone, Weaviate, Qdrant) позволяют хранить метаданные как payload или filter attributes.
- Если схема меняется, старые документы с отсутствующими полями должны корректно обрабатываться, а новые запросы — учитывать новые поля.
2. Проблема: метаданные в RAG
Метаданные — это структурированная информация о документе: заголовок, дата, автор, теги, источник, версия, права доступа и т.д. В RAG они обычно хранятся в двух местах:
- Векторная БД — как часть записи (point) вместе с вектором.
- Отдельный индекс (например, Elasticsearch) для гибридного поиска.
При изменении схемы возникают проблемы:
- Несовместимость типов: поле было строкой, стало числом.
- Отсутствие поля: старые документы не имеют нового поля.
- Удаление поля: старые документы могут содержать поле, которого больше нет в схеме.
Пример изменения
- Изначально метаданные: { "title": str, "date": str }.
- Через год нужно добавить "author": str и изменить
"date"на "timestamp": int.
3. Подходы к schema evolution
Сравним популярные форматы сериализации:
| Формат | Поддержка эволюции | Типизация | Производительность | Использование в RAG |
|---|---|---|---|---|
| Avro | Отличная (backward/forward совместимость) | Сильная (схема в JSON) | Высокая (бинарный) | Часто в Kafka, Hadoop |
| Protobuf | Хорошая (правила эволюции) | Сильная (.proto файлы) | Очень высокая | gRPC, микросервисы |
| JSON Schema | Средняя (ручная валидация) | Слабая (динамическая) | Низкая (текстовый) | REST API, простые случаи |
| Thrift | Хорошая | Сильная | Высокая | Устаревает, редко |
Рекомендация для RAG Avro или Protobuf — они обеспечивают строгую типизацию и встроенные механизмы эволюции. JSON Schema подходит для прототипов, но не для продакшена из-за отсутствия бинарной сериализации и риска ошибок.
4. Принципы backward compatibility (обратная совместимость)
Backward compatibility означает, что новый код может читать данные, записанные по старой схеме. Основные правила:
- Все новые поля — optional (с default value, например
nullили0). - Не удаляйте поля — помечайте их как deprecated и оставляйте в схеме.
- Не меняйте тип существующего поля — вместо этого добавляйте новое поле с другим именем.
- Используйте default values для полей, которые могут отсутствовать.
Пример в Avro
{
"type": "record",
"name": "DocumentMetadata",
"fields": [
{"name": "title", "type": "string"},
{"name": "date", "type": ["null", "string"], "default": null},
{"name": "author", "type": ["null", "string"], "default": null}
]
}
Здесь date и author — optional (union с null). Старые документы без author будут читаться с null.
5. Версионирование схем
Каждая версия схемы получает уникальный идентификатор (например, v1, v2). В RAG-системе можно хранить версию схемы в самом документе или в отдельном реестре.
Практика
- Schema Registry (Confluent, Apicurio) — централизованное хранение схем, проверка совместимости при регистрации новой версии.
- Векторная БД может хранить версию как одно из полей метаданных.
Пример версионирования
v1: { title, date }
v2: { title, date, author } // backward compatible (author optional)
v3: { title, timestamp, author } // несовместимо с v1 (date удалено, timestamp новый тип)
В v3 нужно либо сохранить date как deprecated, либо выполнить миграцию.
6. Хранение метаданных в векторной БД
Большинство векторных БД (Pinecone, Weaviate, Qdrant, Milvus) позволяют хранить метаданные как payload или filter attributes. При эволюции схемы:
- Все поля делаем optional — в БД они могут отсутствовать.
- Индексы по метаданным (например, для фильтрации) должны поддерживать
nullзначения. - При добавлении нового поля — оно автоматически становится доступным для фильтрации, но старые документы его не имеют.
Пример в Qdrant (Python):
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Filter, FieldCondition, MatchValue
client = QdrantClient("localhost")
# Добавляем точку с метаданными v2
client.upsert(
collection_name="docs",
points=[
PointStruct(
id=1,
vector=[0.1, 0.2],
payload={"title": "Doc1", "date": "2023-01-01", "author": "Alice"}
)
]
)
# Старая точка (v1) без author
client.upsert(
collection_name="docs",
points=[
PointStruct(
id=2,
vector=[0.3, 0.4],
payload={"title": "Doc2", "date": "2022-05-10"}
)
]
)
7. Query с учётом эволюции
При поиске по новым полям (например, author == "Alice") старые документы, у которых author отсутствует (null), не будут соответствовать условию. Это ожидаемое поведение.
Пример фильтрации
# Ищем документы, где author = "Alice"
filter_condition = Filter(
must=[
FieldCondition(
key="author",
match=MatchValue(value="Alice")
)
]
)
results = client.search(
collection_name="docs",
query_vector=[0.1, 0.2],
query_filter=filter_condition
)
Документ с id=2 не попадёт в результаты, так как у него author == null.
Важно Если нужно, чтобы старые документы тоже участвовали в поиске по новому полю, можно задать default value (например, пустая строка) и при backfill заполнить его.
8. Миграция (backfill) — оффлайн конвертация
Backfill — это процесс преобразования старых документов в новую схему. Выполняется оффлайн, обычно в фоновом режиме.
Шаги:
- Определить новую схему (v2).
- Считать все старые документы из векторной БД.
- Для каждого документа:
- Добавить новые поля с default значениями.
- Преобразовать типы, если необходимо (например,
date→ timestamp).
- Перезаписать документы в БД (или создать новую коллекцию).
- Переключить приложение на новую коллекцию.
Пример backfill на Python (псевдокод):
def backfill_v1_to_v2(old_points):
new_points = []
for point in old_points:
payload = point.payload
# Добавляем author с default
if "author" not in payload:
payload["author"] = ""
# Преобразуем date в timestamp (если нужно)
if "date" in payload:
from datetime import datetime
payload["timestamp"] = int(datetime.strptime(payload["date"], "%Y-%m-%d").timestamp())
# Можно удалить старое поле, но лучше оставить для совместимости
new_points.append(PointStruct(id=point.id, vector=point.vector, payload=payload))
return new_points
Инструменты Spark, Apache Beam, простые Python-скрипты с параллельной обработкой.
9. Инструменты и практики
- Schema Registry (Confluent, Apicurio) — для управления версиями схем и проверки совместимости.
- CI/CD — автоматическая проверка совместимости при каждом изменении схемы.
- Тестирование — написать unit-тесты, которые проверяют, что старые документы корректно десериализуются новой схемой.
- Мониторинг — логировать ошибки десериализации, чтобы быстро обнаружить несовместимость.
Пример проверки совместимости в Avro
from avro.schema import parse
from avro.datafile import DataFileReader, DataFileWriter
# Проверка backward compatibility
old_schema = parse(open("v1.avsc").read())
new_schema = parse(open("v2.avsc").read())
# Используем библиотеку avro-compatibility или встроенные проверки
10. Пример кода: эволюция схемы с Protobuf
Файл metadata.proto
syntax = "proto3";
message DocumentMetadata {
string title = 1;
optional string date = 2; // v1
optional string author = 3; // v2 (new)
optional int64 timestamp = 4; // v3 (new, replaces date)
}
Генерация Python-классов protoc --python_out=. metadata.proto
Использование
from metadata_pb2 import DocumentMetadata
# Старый документ (v1)
old = DocumentMetadata(title="Doc1", date="2023-01-01")
# Новый код читает — author будет None
print(old.author) # None
# Новый документ (v2)
new = DocumentMetadata(title="Doc2", date="2024-05-10", author="Bob")
Сериализация в векторную БД можно хранить protobuf как bytes в payload, но удобнее использовать встроенные поля БД. Protobuf полезен для передачи между микросервисами.
11. Плюсы и минусы разных подходов
| Подход | Плюсы | Минусы |
|---|---|---|
| Avro + Schema Registry | Полная совместимость, интеграция с Kafka, бинарный формат | Дополнительная инфраструктура, сложность |
| Protobuf + gRPC | Высокая производительность, строгая типизация, поддержка optional | Необходимость компиляции, сложнее для ad-hoc запросов |
| JSON Schema + NoSQL | Простота, человекочитаемость, гибкость | Нет бинарной сериализации, риск ошибок при ручной валидации |
| Встроенные механизмы БД (например, Qdrant payload) | Минимум кода, автоматическая обработка null | Зависимость от вендора, ограниченные возможности эволюции |
Рекомендация для продакшн RAG используйте Avro или Protobuf с Schema Registry и backfill. Для прототипов — JSON Schema.
Пет-проект для закрепления
Задача Создать RAG-систему для хранения документов с метаданными, которая поддерживает эволюцию схемы. Реализовать добавление нового поля rating (float) и миграцию старых документов.
Инструменты
- Python 3.10+
- Qdrant (локально через Docker)
- Apache Avro (библиотека
avro-python3) - Schema Registry (Confluent, можно поднять в Docker)
Шаги:
- Определить схему v1:
{ "title": str, "date": str }. - Загрузить 100 документов с этой схемой в Qdrant.
- Определить схему v2: добавить
"rating": floatс default0.0. - Написать скрипт backfill, который читает все точки, добавляет
rating=0.0и перезаписывает. - Проверить, что новые документы с
ratingкорректно фильтруются, а старые — нет (или участвуют с default). - Добавить тест на backward compatibility: прочитать старый документ новой схемой.
Ожидаемый результат Работающая RAG-система, где можно добавлять новые поля метаданных без остановки сервиса и потери данных. Код с комментариями и тестами.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 525 | Как вы обновляете документы в существующей RAG-системе? |
| 527 | Как вы обрабатываете дубликаты документов в RAG? |
| 510 | Как вы выбираете векторную БД для RAG? |
| 515 | Как вы храните метаданные в векторной БД? |
| 530 | Как вы делаете гибридный поиск (векторный + ключевой)? |
| 505 | Как вы оцениваете качество retrieval'а в RAG-системе? |
Навигация
- Предыдущий: 525
- Следующий: 527
- Индекс: 00. Индекс разборов