> **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
Эта спецификация рассматривает проблемы и недостатки, выявленные в процессе первоначальной реализации интеграции структурированных данных TrustGraph, как описано в `structured-data.md`.
## Описание проблем
### 1. Несоответствие в наименованиях: "Объект" против "Строка"
В текущей реализации используется терминология "объект" во всем коде (например, `ExtractedObject`, извлечение объектов, векторные представления объектов). Это наименование слишком общее и вызывает путаницу:
Термин "объект" является перегруженным в программном обеспечении (объекты Python, объекты JSON и т.д.)
Данные, которые обрабатываются, по сути являются табличными - строки в таблицах с определенными схемами.
"Строка" более точно описывает модель данных и соответствует терминологии баз данных.
Это несоответствие проявляется в названиях модулей, названиях классов, типах сообщений и документации.
### 2. Ограничения запросов к хранилищу строк
Текущая реализация хранилища строк имеет значительные ограничения по запросам:
**Несоответствие с естественным языком**: Запросы испытывают трудности с вариациями реальных данных. Например:
Сложно найти базу данных улиц, содержащую `"CHESTNUT ST"`, при запросе информации о`"Chestnut Street"`.
Аббревиатуры, различия в регистре и вариации форматирования нарушают запросы точного соответствия.
Пользователи ожидают семантического понимания, но хранилище обеспечивает только буквальное сопоставление.
**Проблемы с эволюцией схемы**: Изменение схем вызывает проблемы:
Существующие данные могут не соответствовать обновленным схемам.
Изменения структуры таблицы могут нарушить запросы и целостность данных.
Отсутствует четкий путь миграции для обновлений схемы.
### 3. Требуются векторные представления строк
В связи с проблемой 2, системе требуются векторные представления данных строк для обеспечения:
Семантического поиска по структурированным данным (поиск "Chestnut Street", когда данные содержат "CHESTNUT ST").
Сопоставления по сходству для нечетких запросов.
Гибридного поиска, сочетающего структурированные фильтры с семантическим сходством.
Лучшей поддержки запросов на естественном языке.
Сервис создания векторных представлений был определен, но не реализован.
### 4. Неполная загрузка данных строк
Конвейер загрузки структурированных данных не полностью функционирует:
Существуют диагностические подсказки для классификации форматов входных данных (CSV, JSON и т.д.).
Сервис загрузки, использующий эти подсказки, не подключен к системе.
Отсутствует сквозной путь для загрузки предварительно структурированных данных в хранилище строк.
## Цели
**Гибкость схемы**: Обеспечить эволюцию схемы без нарушения существующих данных или необходимости миграций.
**Согласованное наименование**: Стандартизировать использование термина "строка" во всем коде.
**Семантическая запросаемость**: Поддержка нечеткого/семантического сопоставления с помощью векторных представлений строк.
**Полный конвейер загрузки**: Предоставить сквозной путь для загрузки структурированных данных.
## Технический дизайн
### Унифицированная схема хранения строк
В предыдущей реализации для каждой схемы создавалась отдельная таблица Cassandra. Это вызывало проблемы при эволюции схем, поскольку изменения структуры таблицы требовали миграций.
В новой разработке используется единая унифицированная таблица для всех данных строк:
| `collection` | `text` | Идентификатор сбора/импорта данных (из метаданных) |
| `schema_name` | `text` | Название схемы, которой соответствует эта строка |
| `index_name` | `text` | Название индексируемого поля (полей), объединенное запятыми для составных индексов |
| `index_value` | `frozen<list<text>>` | Значение(я) индекса в виде списка |
| `data` | `map<text, text>` | Данные строки в виде пар ключ-значение |
| `source` | `text` | Необязательный URI, ссылающийся на информацию об источнике в графе знаний. Пустая строка или NULL указывает на отсутствие источника. |
#### Обработка индексов
Каждая строка хранится несколько раз - один раз для каждого индексируемого поля, определенного в схеме. Первичные ключи рассматриваются как индекс без специального маркера, что обеспечивает гибкость в будущем.
**Пример индекса для одного поля:**
Схема определяет `email` как индексируемое
`index_name = "email"`
`index_value = ['foo@bar.com']`
**Пример составного индекса:**
Схема определяет составной индекс для `region` и `status`
`index_name = "region,status"` (названия полей отсортированы и объединены запятыми)
`index_value = ['US', 'active']` (значения в том же порядке, что и названия полей)
**Пример первичного ключа:**
Схема определяет `customer_id` как первичный ключ
`index_name = "customer_id"`
`index_value = ['CUST001']`
#### Шаблоны запросов
Все запросы следуют одной и той же схеме, независимо от того, какой индекс используется:
```sql
SELECT * FROM rows
WHERE collection = 'import_2024'
AND schema_name = 'customers'
AND index_name = 'email'
AND index_value = ['foo@bar.com']
```
#### Компромиссы в проектировании
**Преимущества:**
Изменения схемы не требуют изменений структуры таблицы
Данные строк не видны Cassandra - добавление/удаление полей прозрачно
Единый шаблон запросов для всех методов доступа
Отсутствуют вторичные индексы Cassandra (которые могут быть медленными при больших масштабах)
Использование нативных типов Cassandra (`map`, `frozen<list>`)
**Компромиссы:**
Увеличение количества операций записи: каждая вставка строки = N вставок (по одной на каждое индексированное поле)
Дополнительные затраты памяти из-за дублирования данных строк
Информация о типах хранится в конфигурации схемы, преобразование происходит на уровне приложения
#### Модель согласованности
В проекте приняты определенные упрощения:
1.**Отсутствие обновлений строк**: Система работает только на добавление данных. Это устраняет проблемы согласованности, связанные с обновлением нескольких копий одной и той же строки.
2.**Толерантность к изменениям схемы**: При изменении схем (например, при добавлении/удалении индексов) существующие строки сохраняют свою первоначальную индексацию. Старые строки не будут доступны через новые индексы. Пользователи могут удалить и пересоздать схему для обеспечения согласованности, если это необходимо.
### Отслеживание и удаление разделов
#### Проблема
При использовании ключа раздела `(collection, schema_name, index_name)`, для эффективного удаления необходимо знать все ключи разделов, которые нужно удалить. Удаление только по `collection` или `collection + schema_name` требует знания всех значений `index_name`, которые содержат данные.
Это обеспечивает эффективное обнаружение разделов для операций удаления.
#### Поведение модуля записи строк
Модуль записи строк поддерживает кэш в памяти, содержащий зарегистрированные пары `(collection, schema_name)`. При обработке строки:
1. Проверьте, есть ли `(collection, schema_name)` в кэше.
2. Если ⟦CODE_0⟧ не в кэше (первая строка для этой пары):
Найдите конфигурацию схемы, чтобы получить все имена индексов.
Вставьте записи в `row_partitions` для каждого `(collection, schema_name, index_name)`.
Добавьте пару в кэш.
3. Перейдите к записи данных строки.
Модуль записи строк также отслеживает события изменения конфигурации схемы. При изменении схемы соответствующие записи кэша очищаются, чтобы следующая строка вызвала повторную регистрацию с обновленными именами индексов.
Этот подход обеспечивает:
Записи в таблицу поиска происходят только один раз для каждой пары `(collection, schema_name)`, а не для каждой строки.
Таблица поиска отражает индексы, которые были активны во время записи данных.
Изменения схемы во время импорта обрабатываются правильно.
#### Операции удаления
**Удаление коллекции:**
```sql
-- 1. Discover all partitions
SELECT schema_name, index_name FROM row_partitions WHERE collection = 'X';
-- 2. Delete each partition from rows table
DELETE FROM rows WHERE collection = 'X' AND schema_name = '...' AND index_name = '...';
-- (repeat for each discovered partition)
-- 3. Clean up the lookup table
DELETE FROM row_partitions WHERE collection = 'X';
```
**Удалить коллекцию и схему:**
```sql
-- 1. Discover partitions for this schema
SELECT index_name FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y';
-- 2. Delete each partition from rows table
DELETE FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '...';
-- (repeat for each discovered partition)
-- 3. Clean up the lookup table entries
DELETE FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y';
```
### Встраивания строк
Встраивания строк обеспечивают семантическое/приближенное сопоставление проиндексированных значений, решая проблему несовместимости естественного языка (например, поиск "CHESTNUT ST", когда выполняется запрос "Chestnut Street").
#### Обзор архитектуры
Каждое проиндексированное значение преобразуется во встраивание и хранится в векторной базе данных (Qdrant). Во время запроса запрос также преобразуется во встраивание, находятся похожие векторы, и связанная метаинформация используется для поиска фактических строк в Cassandra.
#### Структура коллекции Qdrant
Одна коллекция Qdrant для каждой кортежи `(user, collection, schema_name, dimension)`:
Имена очищаются (небуквенно-цифровые символы заменяются на `_`, приводятся к нижнему регистру, числовые префиксы получают префикс `r_`)
**Обоснование:** Позволяет чисто удалить экземпляр `(user, collection, schema_name)` путем удаления соответствующих коллекций Qdrant; суффикс измерения позволяет различным моделям встраивания сосуществовать.
#### Что подвергается встраиванию
Текстовое представление значений индекса:
| Тип индекса | Пример `index_value` | Текст для встраивания |
| `index_name` | Индексированные поля, которые представляет эта вставка. |
| `index_value` | Исходный список значений (для поиска в Cassandra). |
| `text` | Текст, который был вставлен (для отладки/отображения). |
Обратите внимание: `user`, `collection` и `schema_name` определяются неявно из имени коллекции Qdrant.
#### Поток запросов
1. Пользователь запрашивает "Chestnut Street" для пользователя U, коллекции X, схемы Y.
2. Вставьте текст запроса.
3. Определите имя(и) коллекции Qdrant, соответствующие префиксу `rows_U_X_Y_`.
4. Выполните поиск в соответствующих коллекциях Qdrant для поиска ближайших векторов.
5. Получите соответствующие точки с полезными нагрузками, содержащими `index_name` и `index_value`.
6. Запрос к Cassandra:
```sql
SELECT * FROM rows
WHERE collection = 'X'
AND schema_name = 'Y'
AND index_name = '<frompayload>'
AND index_value = <frompayload>
```
7. Возврат соответствующих строк.
#### Необязательно: Фильтрация по имени индекса.
Запросы могут опционально фильтровать данные по `index_name` в Qdrant, чтобы искать только определенные поля:
**"Найти любое поле, соответствующее 'Chestnut'"** → поиск по всем векторам в коллекции.
**"Найти поле street_name, соответствующее 'Chestnut'"** → фильтрация по `payload.index_name = 'street_name'`.
#### Архитектура.
Векторные представления строк следуют **двухступенчатой схеме**, используемой в GraphRAG (graph-embeddings, document-embeddings):
**Этап 1: Вычисление векторных представлений** (`trustgraph-flow/trustgraph/embeddings/row_embeddings/`) - Использует `ExtractedObject`, вычисляет векторные представления через сервис векторных представлений, выдает `RowEmbeddings`.
**Этап 2: Хранение векторных представлений** (`trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/`) - Использует `RowEmbeddings`, записывает векторы в Qdrant.
Модуль записи строк в Cassandra является отдельным параллельным компонентом:
**Модуль записи строк в Cassandra** (`trustgraph-flow/trustgraph/storage/rows/cassandra`) - Использует `ExtractedObject`, записывает строки в Cassandra.
Все три компонента используют один и тот же поток данных, что обеспечивает их независимость. Это позволяет:
Независимое масштабирование записи в Cassandra по сравнению с генерацией векторных представлений и хранением векторов.
Отключение сервисов генерации векторных представлений, если они не требуются.
Отказы в одном компоненте не влияют на другие.
Согласованная архитектура с конвейерами GraphRAG.
#### Путь записи.
**Этап 1 (процессор векторных представлений строк):** При получении `ExtractedObject`:
1. Получение схемы для поиска индексированных полей.
2. Для каждого индексированного поля:
Создание текстового представления значения индекса.
Вычисление векторного представления через сервис векторных представлений.
3. Вывод сообщения `RowEmbeddings`, содержащего все вычисленные векторы.
**Этап 2 (запись векторных представлений в Qdrant):** При получении `RowEmbeddings`:
1. Для каждого векторного представления в сообщении:
Определение коллекции Qdrant из `(user, collection, schema_name, dimension)`.
Создание коллекции, если это необходимо (создание при первой записи).
Это разделение позволяет четко разделить функциональность:
Сервис GraphQL фокусируется на точных, структурированных запросах
API векторных представлений обрабатывает семантическую схожесть
Рабочий процесс пользователя: нечеткий поиск с помощью векторных представлений для поиска кандидатов, затем точный запрос для получения полных данных строки
| Записывающий модуль | `trustgraph-flow/trustgraph/storage/objects/cassandra/write.py` | ~540 | Создание таблиц для каждой схемы, вторичные индексы, вставка/удаление |
**Текущая схема запросов:**
```sql
SELECT * FROM {keyspace}.o_{schema_name}
WHERE collection = 'X' AND email = 'foo@bar.com'
ALLOW FILTERING
```
**Новая схема запроса:**
```sql
SELECT * FROM {keyspace}.rows
WHERE collection = 'X' AND schema_name = 'customers'
AND index_name = 'email' AND index_value = ['foo@bar.com']
```
### Ключевые изменения
1.**Упрощение семантики запросов**: Новая схема поддерживает только точное соответствие для `index_value`. Текущие фильтры GraphQL (`gt`, `lt`, `contains` и т.д.) либо:
Становятся постобработкой возвращаемых данных (если это все еще необходимо)
Удаляются в пользу использования API для работы с эмбеддингами для нечеткого сопоставления.
2.**Код GraphQL тесно связан**: Текущие сборки `service.py` включают генерацию типов Strawberry, разбор фильтров и запросы, специфичные для Cassandra. Добавление еще одного бэкенда для хранения данных потребует дублирования примерно 400 строк кода GraphQL.
### Предлагаемая реструктуризация
Реструктуризация состоит из двух частей:
#### 1. Выделение кода GraphQL
Извлеките многократно используемые компоненты GraphQL в общий модуль: