mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 08:26:21 +02:00
Native CLI i18n: The TrustGraph CLI has built-in translation support that dynamically loads language strings. You can test and use different languages by simply passing the --lang flag (e.g., --lang es for Spanish, --lang ru for Russian) or by configuring your environment's LANG variable. Automated Docs Translations: This PR introduces autonomously translated Markdown documentation into several target languages, including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew, Arabic, Simplified Chinese, and Russian.
268 lines
21 KiB
Markdown
268 lines
21 KiB
Markdown
---
|
||
layout: default
|
||
title: "Хранение графов знаний, ориентированных на сущности, в Cassandra"
|
||
parent: "Russian (Beta)"
|
||
---
|
||
|
||
# Хранение графов знаний, ориентированных на сущности, в Cassandra
|
||
|
||
> **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.
|
||
|
||
## Обзор
|
||
|
||
Этот документ описывает модель хранения графов знаний в стиле RDF в Apache Cassandra. Модель использует подход, ориентированный на сущности, при котором каждая сущность знает о каждой квадрупле, в которой она участвует, и о роли, которую она играет. Это заменяет традиционный подход с использованием нескольких таблиц и различных перестановок SPO (Subject, Predicate, Object) всего двумя таблицами.
|
||
|
||
## Предыстория и мотивация
|
||
|
||
### Традиционный подход
|
||
|
||
Стандартное хранилище квадруплов RDF в Cassandra требует нескольких денормализованных таблиц для охвата шаблонов запросов — обычно 6 или более таблиц, представляющих различные перестановки Subject, Predicate, Object и Dataset (SPOD). Каждый квадруплет записывается в каждую таблицу, что приводит к значительному увеличению количества операций записи, оперативным издержкам и сложности схемы.
|
||
|
||
Кроме того, разрешение меток (получение удобочитаемых имен для сущностей) требует отдельных запросов, что особенно дорого в сценариях использования AI и GraphRAG, где метки необходимы для контекста LLM.
|
||
|
||
### Инсайт, ориентированный на сущности
|
||
|
||
Каждый квадруплет `(D, S, P, O)` включает до 4 сущностей. Записывая строку для участия каждой сущности в квадруплете, мы гарантируем, что **любой запрос, содержащий хотя бы один известный элемент, попадет в первичный ключ**. Это охватывает все 16 шаблонов запросов с помощью одной таблицы данных.
|
||
|
||
Основные преимущества:
|
||
|
||
**2 таблицы** вместо 7+
|
||
**4 записи на квадруплет** вместо 6+
|
||
**Разрешение меток бесплатно** — метки сущности находятся рядом с ее отношениями, что естественным образом подогревает кэш приложения.
|
||
**Все 16 шаблонов запросов** обслуживаются с помощью чтения из одной секции.
|
||
**Более простые операции** — одна таблица данных для настройки, уплотнения и восстановления.
|
||
|
||
## Схема
|
||
|
||
### Таблица 1: quads_by_entity
|
||
|
||
Основная таблица данных. Каждая сущность имеет секцию, содержащую все квадруплеты, в которых она участвует. Названа в соответствии с шаблоном запроса (поиск по сущности).
|
||
|
||
```sql
|
||
CREATE TABLE quads_by_entity (
|
||
collection text, -- Collection/tenant scope (always specified)
|
||
entity text, -- The entity this row is about
|
||
role text, -- 'S', 'P', 'O', 'G' — how this entity participates
|
||
p text, -- Predicate of the quad
|
||
otype text, -- 'U' (URI), 'L' (literal), 'T' (triple/reification)
|
||
s text, -- Subject of the quad
|
||
o text, -- Object of the quad
|
||
d text, -- Dataset/graph of the quad
|
||
dtype text, -- XSD datatype (when otype = 'L'), e.g. 'xsd:string'
|
||
lang text, -- Language tag (when otype = 'L'), e.g. 'en', 'fr'
|
||
PRIMARY KEY ((collection, entity), role, p, otype, s, o, d, dtype, lang)
|
||
);
|
||
```
|
||
|
||
**Ключ секции (Partition key)**: `(collection, entity)` — ограничен областью действия коллекции, одна секция на сущность.
|
||
|
||
**Обоснование порядка столбцов кластеризации**:
|
||
|
||
1. **role** — большинство запросов начинаются с "где эта сущность является субъектом/объектом"
|
||
2. **p** — следующий по частоте фильтр, "верните все `knows` отношения"
|
||
3. **otype** — позволяет фильтровать по отношениям со значениями URI по сравнению с литеральными значениями
|
||
4. **s, o, d** — оставшиеся столбцы для обеспечения уникальности
|
||
5. **dtype, lang** — различают литералы с одинаковым значением, но с разными метаданными типа (например, `"thing"` vs `"thing"@en` vs `"thing"^^xsd:string`)
|
||
|
||
### Таблица 2: quads_by_collection
|
||
|
||
Поддерживает запросы и удаление на уровне коллекции. Предоставляет список всех квадруплетов, принадлежащих коллекции. Название отражает шаблон запроса (поиск по коллекции).
|
||
|
||
```sql
|
||
CREATE TABLE quads_by_collection (
|
||
collection text,
|
||
d text, -- Dataset/graph of the quad
|
||
s text, -- Subject of the quad
|
||
p text, -- Predicate of the quad
|
||
o text, -- Object of the quad
|
||
otype text, -- 'U' (URI), 'L' (literal), 'T' (triple/reification)
|
||
dtype text, -- XSD datatype (when otype = 'L')
|
||
lang text, -- Language tag (when otype = 'L')
|
||
PRIMARY KEY (collection, d, s, p, o, otype, dtype, lang)
|
||
);
|
||
```
|
||
|
||
Группировка сначала по набору данных, что позволяет удалять данные как на уровне коллекции, так и на уровне набора данных. Столбцы `otype`, `dtype` и `lang` включены в ключ группировки для различения литералов с одинаковым значением, но с разными метаданными типа — в RDF, `"thing"`, `"thing"@en` и `"thing"^^xsd:string` являются семантически различными значениями.
|
||
|
||
## Путь записи
|
||
|
||
Для каждой входящей квадрупли `(D, S, P, O)` в коллекции `C`, записывайте **4 строки** в `quads_by_entity` и **1 строку** в `quads_by_collection`.
|
||
|
||
### Пример
|
||
|
||
Для квадрупли в коллекции `tenant1`:
|
||
|
||
```
|
||
Dataset: https://example.org/graph1
|
||
Subject: https://example.org/Alice
|
||
Predicate: https://example.org/knows
|
||
Object: https://example.org/Bob
|
||
```
|
||
|
||
Запишите 4 строки в `quads_by_entity`:
|
||
|
||
| collection | entity | role | p | otype | s | o | d |
|
||
|---|---|---|---|---|---|---|---|
|
||
| tenant1 | https://example.org/graph1 | G | https://example.org/knows | U | https://example.org/Alice | https://example.org/Bob | https://example.org/graph1 |
|
||
| tenant1 | https://example.org/Alice | S | https://example.org/knows | U | https://example.org/Alice | https://example.org/Bob | https://example.org/graph1 |
|
||
| tenant1 | https://example.org/knows | P | https://example.org/knows | U | https://example.org/Alice | https://example.org/Bob | https://example.org/graph1 |
|
||
| tenant1 | https://example.org/Bob | O | https://example.org/knows | U | https://example.org/Alice | https://example.org/Bob | https://example.org/graph1 |
|
||
|
||
Запишите 1 строку в `quads_by_collection`:
|
||
|
||
| collection | d | s | p | o | otype | dtype | lang |
|
||
|---|---|---|---|---|---|---|---|
|
||
| tenant1 | https://example.org/graph1 | https://example.org/Alice | https://example.org/knows | https://example.org/Bob | U | | |
|
||
|
||
### Пример с буквальным текстом
|
||
|
||
Для тройки меток:
|
||
|
||
```
|
||
Dataset: https://example.org/graph1
|
||
Subject: https://example.org/Alice
|
||
Predicate: http://www.w3.org/2000/01/rdf-schema#label
|
||
Object: "Alice Smith" (lang: en)
|
||
```
|
||
|
||
`otype` — это `'L'`, `dtype` — это `'xsd:string'`, а `lang` — это `'en'`. Литеральное значение `"Alice Smith"` хранится в `o`. В `quads_by_entity` требуется всего 3 строки — строка для литерала как сущности не записывается, поскольку литералы не являются независимыми сущностями, к которым можно обращаться.
|
||
|
||
## Шаблоны запросов
|
||
|
||
### Все 16 шаблонов DSPO
|
||
|
||
В таблице ниже "Идеальный префикс" означает, что запрос использует непрерывный префикс столбцов кластеризации. "Сканирование раздела + фильтрация" означает, что Cassandra считывает часть одного раздела и фильтрует в памяти — это эффективно, но не является чистым совпадением префикса.
|
||
|
||
| # | Известно | Поиск сущности | Префикс кластеризации | Эффективность |
|
||
|---|---|---|---|---|
|
||
| 1 | D,S,P,O | entity=S, role='S', p=P | Полное совпадение | Идеальный префикс |
|
||
| 2 | D,S,P,? | entity=S, role='S', p=P | Фильтрация по D | Сканирование раздела + фильтрация |
|
||
| 3 | D,S,?,O | entity=S, role='S' | Фильтрация по D, O | Сканирование раздела + фильтрация |
|
||
| 4 | D,?,P,O | entity=O, role='O', p=P | Фильтрация по D | Сканирование раздела + фильтрация |
|
||
| 5 | ?,S,P,O | entity=S, role='S', p=P | Фильтрация по O | Сканирование раздела + фильтрация |
|
||
| 6 | D,S,?,? | entity=S, role='S' | Фильтрация по D | Сканирование раздела + фильтрация |
|
||
| 7 | D,?,P,? | entity=P, role='P' | Фильтрация по D | Сканирование раздела + фильтрация |
|
||
| 8 | D,?,?,O | entity=O, role='O' | Фильтрация по D | Сканирование раздела + фильтрация |
|
||
| 9 | ?,S,P,? | entity=S, role='S', p=P | — | **Идеальный префикс** |
|
||
| 10 | ?,S,?,O | entity=S, role='S' | Фильтрация по O | Сканирование раздела + фильтрация |
|
||
| 11 | ?,?,P,O | entity=O, role='O', p=P | — | **Идеальный префикс** |
|
||
| 12 | D,?,?,? | entity=D, role='G' | — | **Идеальный префикс** |
|
||
| 13 | ?,S,?,? | entity=S, role='S' | — | **Идеальный префикс** |
|
||
| 14 | ?,?,P,? | entity=P, role='P' | — | **Идеальный префикс** |
|
||
| 15 | ?,?,?,O | entity=O, role='O' | — | **Идеальный префикс** |
|
||
| 16 | ?,?,?,? | — | Полное сканирование | Только исследование |
|
||
|
||
**Ключевой результат**: 7 из 15 нетривиальных шаблонов являются идеальными совпадениями префикса кластеризации. Оставшиеся 8 — это чтение одного раздела с фильтрацией внутри раздела. Каждый запрос, содержащий хотя бы один известный элемент, соответствует ключу раздела.
|
||
|
||
Шаблон 16 (?,?,?,?) не встречается на практике, поскольку коллекция всегда указана, что сводит его к шаблону 12.
|
||
|
||
### Примеры распространенных запросов
|
||
|
||
**Вся информация об сущности:**
|
||
|
||
```sql
|
||
SELECT * FROM quads_by_entity
|
||
WHERE collection = 'tenant1' AND entity = 'https://example.org/Alice';
|
||
```
|
||
|
||
**Все исходящие связи для сущности:**
|
||
|
||
```sql
|
||
SELECT * FROM quads_by_entity
|
||
WHERE collection = 'tenant1' AND entity = 'https://example.org/Alice'
|
||
AND role = 'S';
|
||
```
|
||
|
||
**Конкретный предикат для сущности:**
|
||
|
||
```sql
|
||
SELECT * FROM quads_by_entity
|
||
WHERE collection = 'tenant1' AND entity = 'https://example.org/Alice'
|
||
AND role = 'S' AND p = 'https://example.org/knows';
|
||
```
|
||
|
||
**Метка для сущности (на конкретном языке):**
|
||
|
||
```sql
|
||
SELECT * FROM quads_by_entity
|
||
WHERE collection = 'tenant1' AND entity = 'https://example.org/Alice'
|
||
AND role = 'S' AND p = 'http://www.w3.org/2000/01/rdf-schema#label'
|
||
AND otype = 'L';
|
||
```
|
||
|
||
Затем, при необходимости, выполните фильтрацию на стороне приложения с использованием `lang = 'en'`.
|
||
|
||
**Только связи, имеющие значение URI (ссылки между сущностями):**
|
||
|
||
```sql
|
||
SELECT * FROM quads_by_entity
|
||
WHERE collection = 'tenant1' AND entity = 'https://example.org/Alice'
|
||
AND role = 'S' AND p = 'https://example.org/knows' AND otype = 'U';
|
||
```
|
||
|
||
**Обратный поиск — что указывает на эту сущность:**
|
||
|
||
```sql
|
||
SELECT * FROM quads_by_entity
|
||
WHERE collection = 'tenant1' AND entity = 'https://example.org/Bob'
|
||
AND role = 'O';
|
||
```
|
||
|
||
## Разрешение меток и предварительная загрузка кэша
|
||
|
||
Одним из наиболее значительных преимуществ модели, ориентированной на сущности, является то, что **разрешение меток становится побочным эффектом**.
|
||
|
||
В традиционной многотабличной модели получение меток требует отдельных запросов: извлечение троек, определение URI сущностей в результатах, а затем получение `rdfs:label` для каждого. Этот шаблон N+1 дорог.
|
||
|
||
В модели, ориентированной на сущности, запрос сущности возвращает **все** ее квадруплеты, включая ее метки, типы и другие свойства. Когда приложение кэширует результаты запросов, метки предварительно загружаются в кэш до того, как кто-либо запросит их.
|
||
|
||
Два режима использования подтверждают, что это хорошо работает на практике:
|
||
|
||
**Запросы, предназначенные для пользователей**: обычно небольшие наборы результатов, метки необходимы. Чтение сущностей предварительно загружает кэш.
|
||
**Запросы для ИИ/пакетной обработки**: большие наборы результатов с жесткими ограничениями. Метки либо не нужны, либо необходимы только для курированного подмножества сущностей, которые уже находятся в кэше.
|
||
|
||
Теоретическое опасение по поводу разрешения меток для огромных наборов результатов (например, 30 000 сущностей) нивелируется практическим наблюдением, что ни один пользователь или ИИ не обрабатывает такое большое количество меток. Ограничения на запросы на уровне приложения обеспечивают, чтобы нагрузка на кэш оставалась управляемой.
|
||
|
||
## Широкие разделы и реификация
|
||
|
||
Реификация (утверждения RDF-star об утверждениях) создает центральные сущности, например, документ-источник, который поддерживает тысячи извлеченных фактов. Это может привести к широким разделам.
|
||
|
||
Факторы, смягчающие ситуацию:
|
||
|
||
**Ограничения на запросы на уровне приложения**: все запросы GraphRAG и предназначенные для пользователей применяют жесткие ограничения, поэтому широкие разделы никогда не сканируются полностью на пути горячего чтения.
|
||
**Cassandra эффективно обрабатывает частичные чтения**: сканирование кластерной колонки с ранней остановкой происходит быстро даже на больших разделах.
|
||
**Удаление коллекции** (единственная операция, которая может просматривать полные разделы) является приемлемым фоновым процессом.
|
||
|
||
## Удаление коллекции
|
||
|
||
Запускается по API-вызову, выполняется в фоновом режиме (с конечной согласованностью).
|
||
|
||
1. Чтение `quads_by_collection` для целевой коллекции, чтобы получить все квадруплеты.
|
||
2. Извлечение уникальных сущностей из квадруплетов (значений s, p, o, d).
|
||
3. Для каждой уникальной сущности удалите раздел из `quads_by_entity`.
|
||
4. Удалите строки из `quads_by_collection`.
|
||
|
||
Таблица `quads_by_collection` предоставляет индекс, необходимый для поиска всех разделов сущностей без полного сканирования таблицы. Удаление на уровне раздела эффективно, поскольку `(collection, entity)` является ключом раздела.
|
||
|
||
## Путь миграции из многотабличной модели
|
||
|
||
Модель, ориентированная на сущности, может сосуществовать с существующей многотабличной моделью во время миграции:
|
||
|
||
1. Разверните таблицы `quads_by_entity` и `quads_by_collection` рядом с существующими таблицами.
|
||
2. Записывайте новые квадруплеты одновременно в старые и новые таблицы.
|
||
3. Заполните существующие данные в новые таблицы.
|
||
4. Мигрируйте пути запросов по одному шаблону за раз.
|
||
5. Отключите старые таблицы после миграции всех запросов.
|
||
|
||
## Краткое описание
|
||
|
||
| Аспект | Традиционная (6 таблиц) | Ориентированная на сущности (2 таблицы) |
|
||
|---|---|---|
|
||
| Таблицы | 7+ | 2 |
|
||
| Записи на квадруплет | 6+ | 5 (4 данных + 1 манифест) |
|
||
| Разрешение меток | Отдельные запросы | Бесплатно благодаря предварительной загрузке кэша |
|
||
| Шаблоны запросов | 16 по 6 таблицам | 16 на 1 таблицу |
|
||
| Сложность схемы | Высокая | Низкая |
|
||
| Операционные накладные расходы | 6 таблиц для настройки/восстановления | 1 таблица данных |
|
||
| Поддержка реификации | Дополнительная сложность | Естественная совместимость |
|
||
| Фильтрация по типу объекта | Недоступно | Нативно (через кластеризацию по типу объекта) |
|