Как вы делаете schema evolution для метаданных документов в RAG?

Краткий тезис

Schema evolution (эволюция схемы) в RAG — это процесс изменения структуры метаданных документов без остановки системы и потери данных. Ключевой подход — использование сериализационных форматов с поддержкой backward compatibility (обратной совместимости), таких как Avro или Protobuf. При изменении схемы создаётся новая версия, а старые документы читаются с default значениями для новых полей. Все поля должны быть optional (необязательными) для обеспечения гибкости.


1. Термин: Schema evolution (эволюция схемы)

Что это: Способность системы изменять структуру данных (добавлять, удалять, переименовывать поля) без необходимости переписывать все существующие данные и останавливать сервис.

Почему это важно в RAG

  • Метаданные — это не только текст документа, но и дата, автор, категория, версия, теги, source URL, permissions и т.д.
  • Со временем требования меняются: нужно добавить поле language, confidence_score, embedding_model_version.
  • Если нет эволюции схемы, каждое изменение требует полной переиндексации всех документов — дорого и долго.

Термин «Backward compatibility» (обратная совместимость): Новый код может читать данные, записанные старой схемой. Старый код может читать данные, записанные новой схемой (forward compatibility — прямая совместимость).


2. Основные подходы к сериализации метаданных

ФорматПоддержка эволюцииПроизводительностьРазмер данныхТипизация
JSONНет (ломкая)НизкаяБольшойСлабая
AvroОтличная (backward + forward)ВысокаяКомпактный (бинарный)Строгая
ProtobufХорошая (backward)ВысокаяКомпактныйСтрогая
ParquetХорошая (columnar)СредняяСжатыйСтрогая

Рекомендация Для метаданных в RAG чаще используют Avro или Protobuf, так как они нативно поддерживают эволюцию схемы и компактны для хранения в векторной БД.


3. Практика: Как реализовать schema evolution с Avro

3.1 Определение схемы (пример на Python)

# Версия 1 схемы (avro schema)
schema_v1 = {
    "type": "record",
    "name": "DocumentMetadata",
    "fields": [
        {"name": "doc_id", "type": "string"},
        {"name": "title", "type": "string"},
        {"name": "created_at", "type": "long", "logicalType": "timestamp-millis"},
        {"name": "category", "type": ["null", "string"], "default": None}
    ]
}

3.2 Добавление нового поля (версия 2)

schema_v2 = {
    "type": "record",
    "name": "DocumentMetadata",
    "fields": [
        {"name": "doc_id", "type": "string"},
        {"name": "title", "type": "string"},
        {"name": "created_at", "type": "long", "logicalType": "timestamp-millis"},
        {"name": "category", "type": ["null", "string"], "default": None},
        {"name": "language", "type": ["null", "string"], "default": "en"}  # новое поле с default
    ]
}

Ключевые правила

  • Новое поле должно иметь default value (значение по умолчанию]]).
  • Тип поля — union с null (например, ["null", "string"]), чтобы старые записи могли быть прочитаны.
  • Не удалять старые поля, только добавлять (или помечать deprecated).

3.3 Чтение старых документов новой схемой

import fastavro

# Старый документ (версия 1) без поля language
old_record = {"doc_id": "123", "title": "Old Doc", "created_at": 1700000000000, "category": "tech"}

# Сериализуем старой схемой
with open("old_doc.avro", "wb") as f:
    fastavro.writer(f, schema_v1, [old_record])

# Читаем новой схемой — поле language получит default "en"
with open("old_doc.avro", "rb") as f:
    reader = fastavro.reader(f, reader_schema=schema_v2)
    for record in reader:
        print(record["language"])  # Выведет "en"

4. Стратегии эволюции схемы в RAG-системе

4.1 Backward compatibility (обратная совместимость)

  • Что даёт Новый код может читать старые данные.
  • Как реализовать Новые поля с default, старые поля не удалять.
  • Пример: Добавили поле embedding_model с default "text-embedding-ada-002". Все старые документы получат это значение при чтении.

4.2 Forward compatibility (прямая совместимость)

  • Что даёт Старый код может читать новые данные (если не знает о новых полях — игнорирует их).
  • Как реализовать В Avro — автоматически (новые поля игнорируются). В Protobuf — использовать unknown fields.
  • Пример: Старый сервис, который не знает о поле language, просто пропустит его.

4.3 Full compatibility (полная совместимость)

  • Что даёт Оба направления работают.
  • Как реализовать Комбинация backward + forward. Все поля optional, default везде.

5. Интеграция с векторной БД

5.1 Хранение метаданных

Векторные БД (Pinecone, Weaviate, Qdrant, Milvus) хранят метаданные как JSON-подобные структуры. Но при сериализации в Avro/Protobuf вы можете:

  1. Хранить метаданные в бинарном виде (Avro) как одно поле metadata_blob.
  2. При чтении десериализовать с актуальной схемой.
# Пример записи в Qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
import fastavro

client = QdrantClient(host="localhost")

# Сериализуем метаданные
metadata = {"doc_id": "123", "title": "Doc", "created_at": 1700000000000}
with io.BytesIO() as buf:
    fastavro.writer(buf, schema_v2, [metadata])
    metadata_bytes = buf.getvalue()

# Сохраняем как бинарное поле
client.upsert(
    collection_name="my_collection",
    points=[PointStruct(id=1, vector=[0.1, 0.2], payload={"metadata": metadata_bytes})]
)

5.2 Фильтрация по метаданным

Если нужно фильтровать по новым полям (например, language), нужно либо:

  • Переиндексировать старые документы с новыми полями (если фильтрация критична).
  • Использовать default значения для фильтрации (например, все старые документы считаются language="en").

6. Версионирование схемы

6.1 Хранение версий

Храните все версии схемы в реестре (например, Schema Registry от Confluent для Avro).

# Пример регистрации схемы
from confluent_kafka.schema_registry import SchemaRegistryClient
from confluent_kafka.schema_registry.avro import AvroSchema

schema_registry = SchemaRegistryClient({"url": "http://localhost:8081"})
schema = AvroSchema(schema_v2)
schema_id = schema_registry.register("document-metadata-value", schema)

6.2 Идентификация версии

Добавьте в метаданные поле schema_version:

schema_v3 = {
    "type": "record",
    "name": "DocumentMetadata",
    "fields": [
        {"name": "doc_id", "type": "string"},
        {"name": "title", "type": "string"},
        {"name": "created_at", "type": "long", "logicalType": "timestamp-millis"},
        {"name": "category", "type": ["null", "string"], "default": None},
        {"name": "language", "type": ["null", "string"], "default": "en"},
        {"name": "schema_version", "type": "int", "default": 3}
    ]
}

7. Обработка breaking changes (критических изменений)

Иногда нужно удалить поле или изменить его тип. Это breaking change (ломающее изменение). Стратегии:

СитуацияРешение
Удаление поляПометить deprecated, не удалять физически. Через N версий — удалить, но с миграцией данных.
Изменение типаСоздать новое поле с новым именем, старое оставить. Например, category_id вместо category.
ПереименованиеДобавить новое поле, старое оставить с default.

Пример миграции

# Старое поле category (string) -> новое category_id (int)
schema_v4 = {
    "type": "record",
    "name": "DocumentMetadata",
    "fields": [
        # ... старые поля
        {"name": "category", "type": ["null", "string"], "default": None},  # deprecated
        {"name": "category_id", "type": ["null", "int"], "default": None}   # новое поле
    ]
}

8. Инструменты для schema evolution

ИнструментНазначение
Apache AvroСериализация с поддержкой эволюции
Protocol Buffers (Protobuf)Альтернатива Avro, популярна в микросервисах
Confluent Schema RegistryРеестр схем для Kafka/Avro
Great ExpectationsВалидация данных при миграции
Apache ParquetХранение больших объёмов метаданных с эволюцией

Пет-проект для закрепления

Задача Создать RAG-систему для хранения статей, где метаданные эволюционируют со временем.

Инструменты Python, FastAPI, Qdrant (векторная БД), fastavro, Schema Registry (можно эмулировать через файл).

Шаги:

  1. Определите схему метаданных v1 (doc_id, title, created_at).
  2. Реализуйте endpoint для добавления документа с сериализацией в Avro.
  3. Через неделю добавьте поле language с default "en" (v2).
  4. Реализуйте чтение старых документов — они должны корректно читаться.
  5. Добавьте поле category_id (v3) с миграцией.
  6. Напишите тест, проверяющий backward compatibility.

Ожидаемый результат Система, которая может читать документы, записанные любой версией схемы, без ошибок.


Связь с другими вопросами

ВопросТема
5Оценка качества retrieval
9Обновление документов в RAG
10Self-RAG
15Версионирование эмбеддингов
20Обработка дубликатов документов
25Стратегии индексации

Навигация