mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 16:36:21 +02:00
688 lines
38 KiB
Markdown
688 lines
38 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.
|
|||
|
|
|
|||
|
|
**Статус:** Черновик
|
|||
|
|
**Автор:** Ассистент
|
|||
|
|
**Дата:** 2025-09-18
|
|||
|
|
|
|||
|
|
## Обзор
|
|||
|
|
|
|||
|
|
Данная спецификация рассматривает проблемы производительности в реализации базы знаний TrustGraph на Cassandra и предлагает оптимизации для хранения и запросов RDF-тройных.
|
|||
|
|
|
|||
|
|
## Текущая реализация
|
|||
|
|
|
|||
|
|
### Структура данных
|
|||
|
|
|
|||
|
|
Текущая реализация использует структуру данных с одной таблицей в `trustgraph-flow/trustgraph/direct/cassandra_kg.py`:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE triples (
|
|||
|
|
collection text,
|
|||
|
|
s text,
|
|||
|
|
p text,
|
|||
|
|
o text,
|
|||
|
|
PRIMARY KEY (collection, s, p, o)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вторичные индексы:**
|
|||
|
|
`triples_s` по `s` (субъект)
|
|||
|
|
`triples_p` по `p` (предикат)
|
|||
|
|
`triples_o` по `o` (объект)
|
|||
|
|
|
|||
|
|
### Шаблоны запросов
|
|||
|
|
|
|||
|
|
Текущая реализация поддерживает 8 различных шаблонов запросов:
|
|||
|
|
|
|||
|
|
1. **get_all(collection, limit=50)** - Получить все тройки для коллекции
|
|||
|
|
```sql
|
|||
|
|
SELECT s, p, o FROM triples WHERE collection = ? LIMIT 50
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **get_s(collection, s, limit=10)** - Запрос по теме.
|
|||
|
|
```sql
|
|||
|
|
SELECT p, o FROM triples WHERE collection = ? AND s = ? LIMIT 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **get_p(collection, p, limit=10)** - Запрос по предикату.
|
|||
|
|
```sql
|
|||
|
|
SELECT s, o FROM triples WHERE collection = ? AND p = ? LIMIT 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. **get_o(collection, o, limit=10)** - Запрос по объекту.
|
|||
|
|
```sql
|
|||
|
|
SELECT s, p FROM triples WHERE collection = ? AND o = ? LIMIT 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
5. **get_sp(collection, s, p, limit=10)** - Запрос по субъекту + предикату.
|
|||
|
|
```sql
|
|||
|
|
SELECT o FROM triples WHERE collection = ? AND s = ? AND p = ? LIMIT 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
6. **get_po(collection, p, o, limit=10)** - Запрос по предикату + объекту ⚠️
|
|||
|
|
```sql
|
|||
|
|
SELECT s FROM triples WHERE collection = ? AND p = ? AND o = ? LIMIT 10 ALLOW FILTERING
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
7. **get_os(collection, o, s, limit=10)** - Запрос по объекту + субъекту ⚠️
|
|||
|
|
```sql
|
|||
|
|
SELECT p FROM triples WHERE collection = ? AND o = ? AND s = ? LIMIT 10 ALLOW FILTERING
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
8. **get_spo(collection, s, p, o, limit=10)** - Точное соответствие тройке.
|
|||
|
|
```sql
|
|||
|
|
SELECT s as x FROM triples WHERE collection = ? AND s = ? AND p = ? AND o = ? LIMIT 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Текущая архитектура
|
|||
|
|
|
|||
|
|
**Файл: `trustgraph-flow/trustgraph/direct/cassandra_kg.py`**
|
|||
|
|
Один класс `KnowledgeGraph`, обрабатывающий все операции
|
|||
|
|
Объединение подключений через глобальный список `_active_clusters`
|
|||
|
|
Фиксированное имя таблицы: `"triples"`
|
|||
|
|
Пространство ключей для каждой модели пользователя
|
|||
|
|
Репликация SimpleStrategy с коэффициентом 1
|
|||
|
|
|
|||
|
|
**Точки интеграции:**
|
|||
|
|
**Путь записи:** `trustgraph-flow/trustgraph/storage/triples/cassandra/write.py`
|
|||
|
|
**Путь запроса:** `trustgraph-flow/trustgraph/query/triples/cassandra/service.py`
|
|||
|
|
**Хранилище знаний:** `trustgraph-flow/trustgraph/tables/knowledge.py`
|
|||
|
|
|
|||
|
|
## Выявленные проблемы с производительностью
|
|||
|
|
|
|||
|
|
### Проблемы на уровне схемы
|
|||
|
|
|
|||
|
|
1. **Неэффективный дизайн первичного ключа**
|
|||
|
|
Текущий: `PRIMARY KEY (collection, s, p, o)`
|
|||
|
|
Приводит к плохой кластеризации для распространенных шаблонов доступа
|
|||
|
|
Заставляет использовать дорогостоящие вторичные индексы
|
|||
|
|
|
|||
|
|
2. **Чрезмерное использование вторичных индексов** ⚠️
|
|||
|
|
Три вторичных индекса на столбцах с высокой кардинальностью (s, p, o)
|
|||
|
|
Вторичные индексы в Cassandra дороги и плохо масштабируются
|
|||
|
|
Запросы 6 и 7 требуют `ALLOW FILTERING`, что указывает на плохое моделирование данных
|
|||
|
|
|
|||
|
|
3. **Риск "горячих" разделов**
|
|||
|
|
Один ключ раздела `collection` может создавать "горячие" разделы
|
|||
|
|
Большие коллекции будут концентрироваться на отдельных узлах
|
|||
|
|
Отсутствует стратегия распределения для балансировки нагрузки
|
|||
|
|
|
|||
|
|
### Проблемы на уровне запросов
|
|||
|
|
|
|||
|
|
1. **Использование ALLOW FILTERING** ⚠️
|
|||
|
|
Два типа запросов (get_po, get_os) требуют `ALLOW FILTERING`
|
|||
|
|
Эти запросы сканируют несколько разделов и чрезвычайно дороги
|
|||
|
|
Производительность ухудшается линейно с увеличением объема данных
|
|||
|
|
|
|||
|
|
2. **Неэффективные шаблоны доступа**
|
|||
|
|
Отсутствует оптимизация для распространенных шаблонов запросов RDF
|
|||
|
|
Отсутствуют составные индексы для часто используемых комбинаций запросов
|
|||
|
|
Не учитываются шаблоны обхода графов
|
|||
|
|
|
|||
|
|
3. **Отсутствие оптимизации запросов**
|
|||
|
|
Отсутствует кэширование подготовленных выражений
|
|||
|
|
Отсутствуют подсказки или стратегии оптимизации запросов
|
|||
|
|
Не учитывается постраничная навигация, кроме простого LIMIT
|
|||
|
|
|
|||
|
|
## Описание проблемы
|
|||
|
|
|
|||
|
|
Текущая реализация базы знаний Cassandra имеет два критических узких места в производительности:
|
|||
|
|
|
|||
|
|
### 1. Неэффективная производительность запроса get_po
|
|||
|
|
|
|||
|
|
Запрос `get_po(collection, p, o)` крайне неэффективен, поскольку требует `ALLOW FILTERING`:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT s FROM triples WHERE collection = ? AND p = ? AND o = ? LIMIT 10 ALLOW FILTERING
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Почему это является проблемой:**
|
|||
|
|
`ALLOW FILTERING` заставляет Cassandra сканировать все разделы внутри коллекции.
|
|||
|
|
Производительность ухудшается линейно с увеличением размера данных.
|
|||
|
|
Это распространенный шаблон запросов RDF (поиск объектов, имеющих определенную связь "субъект-предикат-объект").
|
|||
|
|
Это создает значительную нагрузку на кластер по мере роста данных.
|
|||
|
|
|
|||
|
|
### 2. Неоптимальная стратегия кластеризации
|
|||
|
|
|
|||
|
|
Текущий первичный ключ `PRIMARY KEY (collection, s, p, o)` обеспечивает минимальные преимущества кластеризации:
|
|||
|
|
|
|||
|
|
**Проблемы с текущей кластеризацией:**
|
|||
|
|
`collection` в качестве ключа раздела не обеспечивает эффективное распределение данных.
|
|||
|
|
Большинство коллекций содержат разнообразные данные, что делает кластеризацию неэффективной.
|
|||
|
|
Отсутствует учет типичных шаблонов доступа в запросах RDF.
|
|||
|
|
Большие коллекции создают "горячие" разделы на отдельных узлах.
|
|||
|
|
Столбцы кластеризации (s, p, o) не оптимизированы для типичных шаблонов обхода графа.
|
|||
|
|
|
|||
|
|
**Влияние:**
|
|||
|
|
Запросы не получают выгоды от локальности данных.
|
|||
|
|
Плохое использование кэша.
|
|||
|
|
Неравномерное распределение нагрузки между узлами кластера.
|
|||
|
|
"Узкие места" масштабируемости по мере роста коллекций.
|
|||
|
|
|
|||
|
|
## Предлагаемое решение: Стратегия денормализации с использованием 4 таблиц
|
|||
|
|
|
|||
|
|
### Обзор
|
|||
|
|
|
|||
|
|
Замените одну таблицу `triples` на четыре специализированные таблицы, каждая из которых оптимизирована для конкретных шаблонов запросов. Это устраняет необходимость во вторичных индексах и использовании ALLOW FILTERING, обеспечивая при этом оптимальную производительность для всех типов запросов. Четвертая таблица обеспечивает эффективное удаление коллекций, несмотря на составные ключи разделов.
|
|||
|
|
|
|||
|
|
### Новая структура схемы
|
|||
|
|
|
|||
|
|
**Таблица 1: Запросы, ориентированные на субъекты (triples_s)**
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE triples_s (
|
|||
|
|
collection text,
|
|||
|
|
s text,
|
|||
|
|
p text,
|
|||
|
|
o text,
|
|||
|
|
PRIMARY KEY ((collection, s), p, o)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
**Оптимизирует:** get_s, get_sp, get_os
|
|||
|
|
**Ключ разделения:** (collection, s) - Обеспечивает лучшую распределенность, чем только collection.
|
|||
|
|
**Кластеризация:** (p, o) - Обеспечивает эффективный поиск по предикатам/объектам для субъекта.
|
|||
|
|
|
|||
|
|
**Таблица 2: Запросы предикат-объект (triples_p)**
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE triples_p (
|
|||
|
|
collection text,
|
|||
|
|
p text,
|
|||
|
|
o text,
|
|||
|
|
s text,
|
|||
|
|
PRIMARY KEY ((collection, p), o, s)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
**Оптимизирует:** get_p, get_po (исключает ALLOW FILTERING!)
|
|||
|
|
**Ключ партиции:** (collection, p) - Прямой доступ по предикату
|
|||
|
|
**Кластеризация:** (o, s) - Эффективный обход объектов и субъектов
|
|||
|
|
|
|||
|
|
**Таблица 3: Объектно-ориентированные запросы (triples_o)**
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE triples_o (
|
|||
|
|
collection text,
|
|||
|
|
o text,
|
|||
|
|
s text,
|
|||
|
|
p text,
|
|||
|
|
PRIMARY KEY ((collection, o), s, p)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
**Оптимизирует:** get_o
|
|||
|
|
**Ключ разделения:** (collection, o) - Прямой доступ по объекту
|
|||
|
|
**Кластеризация:** (s, p) - Эффективный обход по субъекту-предикату
|
|||
|
|
|
|||
|
|
**Таблица 4: Управление коллекциями и запросы SPO (triples_collection)**
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE triples_collection (
|
|||
|
|
collection text,
|
|||
|
|
s text,
|
|||
|
|
p text,
|
|||
|
|
o text,
|
|||
|
|
PRIMARY KEY (collection, s, p, o)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
**Оптимизирует:** get_spo, delete_collection
|
|||
|
|
**Разделительный ключ:** только коллекция - Обеспечивает эффективные операции на уровне коллекций.
|
|||
|
|
**Кластеризация:** (s, p, o) - Стандартный порядок троек.
|
|||
|
|
**Назначение:** Двойное использование для точного поиска SPO и в качестве индекса удаления.
|
|||
|
|
|
|||
|
|
### Отображение запросов
|
|||
|
|
|
|||
|
|
| Исходный запрос | Целевая таблица | Улучшение производительности |
|
|||
|
|
|----------------|-------------|------------------------|
|
|||
|
|
| get_all(collection) | triples_s | ALLOW FILTERING (приемлемо для сканирования) |
|
|||
|
|
| get_s(collection, s) | triples_s | Прямой доступ к разделу |
|
|||
|
|
| get_p(collection, p) | triples_p | Прямой доступ к разделу |
|
|||
|
|
| get_o(collection, o) | triples_o | Прямой доступ к разделу |
|
|||
|
|
| get_sp(collection, s, p) | triples_s | Раздел + кластеризация |
|
|||
|
|
| get_po(collection, p, o) | triples_p | **Больше не требуется ALLOW FILTERING!** |
|
|||
|
|
| get_os(collection, o, s) | triples_o | Раздел + кластеризация |
|
|||
|
|
| get_spo(collection, s, p, o) | triples_collection | Точный поиск по ключу |
|
|||
|
|
| delete_collection(collection) | triples_collection | Чтение индекса, пакетное удаление всех |
|
|||
|
|
|
|||
|
|
### Стратегия удаления коллекций
|
|||
|
|
|
|||
|
|
С составными раздельными ключами мы не можем просто выполнить `DELETE FROM table WHERE collection = ?`. Вместо этого:
|
|||
|
|
|
|||
|
|
1. **Фаза чтения:** Запрос `triples_collection` для перечисления всех троек:
|
|||
|
|
```sql
|
|||
|
|
SELECT s, p, o FROM triples_collection WHERE collection = ?
|
|||
|
|
```
|
|||
|
|
Это эффективно, поскольку `collection` является ключом секции для этой таблицы.
|
|||
|
|
|
|||
|
|
2. **Фаза удаления:** Для каждой тройки (s, p, o) удалите из всех 4 таблиц, используя полные ключи секций:
|
|||
|
|
```sql
|
|||
|
|
DELETE FROM triples_s WHERE collection = ? AND s = ? AND p = ? AND o = ?
|
|||
|
|
DELETE FROM triples_p WHERE collection = ? AND p = ? AND o = ? AND s = ?
|
|||
|
|
DELETE FROM triples_o WHERE collection = ? AND o = ? AND s = ? AND p = ?
|
|||
|
|
DELETE FROM triples_collection WHERE collection = ? AND s = ? AND p = ? AND o = ?
|
|||
|
|
```
|
|||
|
|
Обрабатывается пакетами по 100 элементов для повышения эффективности.
|
|||
|
|
|
|||
|
|
**Анализ компромиссов:**
|
|||
|
|
✅ Поддерживает оптимальную производительность запросов с использованием распределенных разделов.
|
|||
|
|
✅ Отсутствуют "горячие" разделы для больших коллекций.
|
|||
|
|
❌ Более сложная логика удаления (чтение, а затем удаление).
|
|||
|
|
❌ Время удаления пропорционально размеру коллекции.
|
|||
|
|
|
|||
|
|
### Преимущества
|
|||
|
|
|
|||
|
|
1. **Устраняет ALLOW FILTERING** - Каждый запрос имеет оптимальный путь доступа (за исключением полного сканирования).
|
|||
|
|
2. **Не требуются вторичные индексы** - Каждая таблица ЯВЛЯЕТСЯ индексом для своего шаблона запросов.
|
|||
|
|
3. **Более равномерное распределение данных** - Композитные ключи разделов эффективно распределяют нагрузку.
|
|||
|
|
4. **Предсказуемая производительность** - Время выполнения запроса пропорционально размеру результата, а не общему объему данных.
|
|||
|
|
5. **Использует сильные стороны Cassandra** - Разработана с учетом архитектуры Cassandra.
|
|||
|
|
6. **Позволяет удалять коллекции** - `triples_collection` служит индексом для удаления.
|
|||
|
|
|
|||
|
|
## План реализации
|
|||
|
|
|
|||
|
|
### Файлы, требующие изменений
|
|||
|
|
|
|||
|
|
#### Основной файл реализации
|
|||
|
|
|
|||
|
|
**`trustgraph-flow/trustgraph/direct/cassandra_kg.py`** - Требуется полная переработка.
|
|||
|
|
|
|||
|
|
**Методы, требующие рефакторинга:**
|
|||
|
|
```python
|
|||
|
|
# Schema initialization
|
|||
|
|
def init(self) -> None # Replace single table with three tables
|
|||
|
|
|
|||
|
|
# Insert operations
|
|||
|
|
def insert(self, collection, s, p, o) -> None # Write to all three tables
|
|||
|
|
|
|||
|
|
# Query operations (API unchanged, implementation optimized)
|
|||
|
|
def get_all(self, collection, limit=50) # Use triples_by_subject
|
|||
|
|
def get_s(self, collection, s, limit=10) # Use triples_by_subject
|
|||
|
|
def get_p(self, collection, p, limit=10) # Use triples_by_po
|
|||
|
|
def get_o(self, collection, o, limit=10) # Use triples_by_object
|
|||
|
|
def get_sp(self, collection, s, p, limit=10) # Use triples_by_subject
|
|||
|
|
def get_po(self, collection, p, o, limit=10) # Use triples_by_po (NO ALLOW FILTERING!)
|
|||
|
|
def get_os(self, collection, o, s, limit=10) # Use triples_by_subject
|
|||
|
|
def get_spo(self, collection, s, p, o, limit=10) # Use triples_by_subject
|
|||
|
|
|
|||
|
|
# Collection management
|
|||
|
|
def delete_collection(self, collection) -> None # Delete from all three tables
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Интеграционные файлы (изменения в логике не требуются)
|
|||
|
|
|
|||
|
|
**`trustgraph-flow/trustgraph/storage/triples/cassandra/write.py`**
|
|||
|
|
Изменения не требуются - используется существующий API KnowledgeGraph.
|
|||
|
|
Автоматически получает преимущества от улучшений производительности.
|
|||
|
|
|
|||
|
|
**`trustgraph-flow/trustgraph/query/triples/cassandra/service.py`**
|
|||
|
|
Изменения не требуются - используется существующий API KnowledgeGraph.
|
|||
|
|
Автоматически получает преимущества от улучшений производительности.
|
|||
|
|
|
|||
|
|
### Файлы для тестирования, требующие обновления
|
|||
|
|
|
|||
|
|
#### Юнит-тесты
|
|||
|
|
**`tests/unit/test_storage/test_triples_cassandra_storage.py`**
|
|||
|
|
Обновить ожидаемые результаты тестов в связи с изменениями схемы.
|
|||
|
|
Добавить тесты для обеспечения согласованности между несколькими таблицами.
|
|||
|
|
Проверить отсутствие использования ALLOW FILTERING в планах запросов.
|
|||
|
|
|
|||
|
|
**`tests/unit/test_query/test_triples_cassandra_query.py`**
|
|||
|
|
Обновить утверждения о производительности.
|
|||
|
|
Протестировать все 8 шаблонов запросов для новых таблиц.
|
|||
|
|
Проверить маршрутизацию запросов к правильным таблицам.
|
|||
|
|
|
|||
|
|
#### Интеграционные тесты
|
|||
|
|
**`tests/integration/test_cassandra_integration.py`**
|
|||
|
|
Комплексное тестирование с новой схемой.
|
|||
|
|
Сравнение результатов бенчмаркинга производительности.
|
|||
|
|
Проверка согласованности данных между таблицами.
|
|||
|
|
|
|||
|
|
**`tests/unit/test_storage/test_cassandra_config_integration.py`**
|
|||
|
|
Обновить тесты проверки схемы.
|
|||
|
|
Протестировать сценарии миграции.
|
|||
|
|
|
|||
|
|
### Стратегия реализации
|
|||
|
|
|
|||
|
|
#### Фаза 1: Схема и основные методы
|
|||
|
|
1. **Переписать метод `init()`** - Создать четыре таблицы вместо одной.
|
|||
|
|
2. **Переписать метод `insert()`** - Выполнять пакетные записи во все четыре таблицы.
|
|||
|
|
3. **Реализовать подготовленные запросы** - Для оптимальной производительности.
|
|||
|
|
4. **Добавить логику маршрутизации таблиц** - Направлять запросы к оптимальным таблицам.
|
|||
|
|
5. **Реализовать удаление коллекций** - Читать из triples_collection, пакетно удалять из всех таблиц.
|
|||
|
|
|
|||
|
|
#### Фаза 2: Оптимизация методов запросов
|
|||
|
|
1. **Переписать каждый метод get_*** для использования оптимальной таблицы.
|
|||
|
|
2. **Удалить все случаи использования ALLOW FILTERING**.
|
|||
|
|
3. **Реализовать эффективное использование ключей кластеризации**.
|
|||
|
|
4. **Добавить логирование производительности запросов**.
|
|||
|
|
|
|||
|
|
#### Фаза 3: Управление коллекциями
|
|||
|
|
1. **Обновить `delete_collection()`** - Удалить из всех трех таблиц.
|
|||
|
|
2. **Добавить проверку согласованности** - Обеспечить синхронизацию всех таблиц.
|
|||
|
|
3. **Реализовать пакетные операции** - Для атомарных операций с несколькими таблицами.
|
|||
|
|
|
|||
|
|
### Ключевые детали реализации
|
|||
|
|
|
|||
|
|
#### Стратегия пакетной записи
|
|||
|
|
```python
|
|||
|
|
def insert(self, collection, s, p, o):
|
|||
|
|
batch = BatchStatement()
|
|||
|
|
|
|||
|
|
# Insert into all four tables
|
|||
|
|
batch.add(self.insert_subject_stmt, (collection, s, p, o))
|
|||
|
|
batch.add(self.insert_po_stmt, (collection, p, o, s))
|
|||
|
|
batch.add(self.insert_object_stmt, (collection, o, s, p))
|
|||
|
|
batch.add(self.insert_collection_stmt, (collection, s, p, o))
|
|||
|
|
|
|||
|
|
self.session.execute(batch)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Логика маршрутизации запросов
|
|||
|
|
```python
|
|||
|
|
def get_po(self, collection, p, o, limit=10):
|
|||
|
|
# Route to triples_p table - NO ALLOW FILTERING!
|
|||
|
|
return self.session.execute(
|
|||
|
|
self.get_po_stmt,
|
|||
|
|
(collection, p, o, limit)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def get_spo(self, collection, s, p, o, limit=10):
|
|||
|
|
# Route to triples_collection table for exact SPO lookup
|
|||
|
|
return self.session.execute(
|
|||
|
|
self.get_spo_stmt,
|
|||
|
|
(collection, s, p, o, limit)
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Логика удаления коллекции
|
|||
|
|
```python
|
|||
|
|
def delete_collection(self, collection):
|
|||
|
|
# Step 1: Read all triples from collection table
|
|||
|
|
rows = self.session.execute(
|
|||
|
|
f"SELECT s, p, o FROM {self.collection_table} WHERE collection = %s",
|
|||
|
|
(collection,)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Step 2: Batch delete from all 4 tables
|
|||
|
|
batch = BatchStatement()
|
|||
|
|
count = 0
|
|||
|
|
|
|||
|
|
for row in rows:
|
|||
|
|
s, p, o = row.s, row.p, row.o
|
|||
|
|
|
|||
|
|
# Delete using full partition keys for each table
|
|||
|
|
batch.add(SimpleStatement(
|
|||
|
|
f"DELETE FROM {self.subject_table} WHERE collection = ? AND s = ? AND p = ? AND o = ?"
|
|||
|
|
), (collection, s, p, o))
|
|||
|
|
|
|||
|
|
batch.add(SimpleStatement(
|
|||
|
|
f"DELETE FROM {self.po_table} WHERE collection = ? AND p = ? AND o = ? AND s = ?"
|
|||
|
|
), (collection, p, o, s))
|
|||
|
|
|
|||
|
|
batch.add(SimpleStatement(
|
|||
|
|
f"DELETE FROM {self.object_table} WHERE collection = ? AND o = ? AND s = ? AND p = ?"
|
|||
|
|
), (collection, o, s, p))
|
|||
|
|
|
|||
|
|
batch.add(SimpleStatement(
|
|||
|
|
f"DELETE FROM {self.collection_table} WHERE collection = ? AND s = ? AND p = ? AND o = ?"
|
|||
|
|
), (collection, s, p, o))
|
|||
|
|
|
|||
|
|
count += 1
|
|||
|
|
|
|||
|
|
# Execute every 100 triples to avoid oversized batches
|
|||
|
|
if count % 100 == 0:
|
|||
|
|
self.session.execute(batch)
|
|||
|
|
batch = BatchStatement()
|
|||
|
|
|
|||
|
|
# Execute remaining deletions
|
|||
|
|
if count % 100 != 0:
|
|||
|
|
self.session.execute(batch)
|
|||
|
|
|
|||
|
|
logger.info(f"Deleted {count} triples from collection {collection}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Оптимизация подготовленных выражений
|
|||
|
|
```python
|
|||
|
|
def prepare_statements(self):
|
|||
|
|
# Cache prepared statements for better performance
|
|||
|
|
self.insert_subject_stmt = self.session.prepare(
|
|||
|
|
f"INSERT INTO {self.subject_table} (collection, s, p, o) VALUES (?, ?, ?, ?)"
|
|||
|
|
)
|
|||
|
|
self.insert_po_stmt = self.session.prepare(
|
|||
|
|
f"INSERT INTO {self.po_table} (collection, p, o, s) VALUES (?, ?, ?, ?)"
|
|||
|
|
)
|
|||
|
|
self.insert_object_stmt = self.session.prepare(
|
|||
|
|
f"INSERT INTO {self.object_table} (collection, o, s, p) VALUES (?, ?, ?, ?)"
|
|||
|
|
)
|
|||
|
|
self.insert_collection_stmt = self.session.prepare(
|
|||
|
|
f"INSERT INTO {self.collection_table} (collection, s, p, o) VALUES (?, ?, ?, ?)"
|
|||
|
|
)
|
|||
|
|
# ... query statements
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Стратегия миграции
|
|||
|
|
|
|||
|
|
### Подход к миграции данных
|
|||
|
|
|
|||
|
|
#### Вариант 1: Развертывание Blue-Green (Рекомендуется)
|
|||
|
|
1. **Разверните новую схему параллельно с существующей** - Временно используйте разные имена таблиц.
|
|||
|
|
2. **Период двойной записи** - Записывайте данные как в старую, так и в новую схемы во время перехода.
|
|||
|
|
3. **Фоновая миграция** - Скопируйте существующие данные в новые таблицы.
|
|||
|
|
4. **Перенаправление операций чтения** - Направляйте запросы к новым таблицам после миграции данных.
|
|||
|
|
5. **Удаление старых таблиц** - После периода проверки.
|
|||
|
|
|
|||
|
|
#### Вариант 2: Миграция на месте
|
|||
|
|
1. **Добавление схемы** - Создайте новые таблицы в существующем пространстве ключей.
|
|||
|
|
2. **Скрипт миграции данных** - Пакетная копия из старой таблицы в новые таблицы.
|
|||
|
|
3. **Обновление приложения** - Разверните новый код после завершения миграции.
|
|||
|
|
4. **Очистка старой таблицы** - Удалите старую таблицу и индексы.
|
|||
|
|
|
|||
|
|
### Обратная совместимость
|
|||
|
|
|
|||
|
|
#### Стратегия развертывания
|
|||
|
|
```python
|
|||
|
|
# Environment variable to control table usage during migration
|
|||
|
|
USE_LEGACY_TABLES = os.getenv('CASSANDRA_USE_LEGACY', 'false').lower() == 'true'
|
|||
|
|
|
|||
|
|
class KnowledgeGraph:
|
|||
|
|
def __init__(self, ...):
|
|||
|
|
if USE_LEGACY_TABLES:
|
|||
|
|
self.init_legacy_schema()
|
|||
|
|
else:
|
|||
|
|
self.init_optimized_schema()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Скрипт миграции
|
|||
|
|
```python
|
|||
|
|
def migrate_data():
|
|||
|
|
# Read from old table
|
|||
|
|
old_triples = session.execute("SELECT collection, s, p, o FROM triples")
|
|||
|
|
|
|||
|
|
# Batch write to new tables
|
|||
|
|
for batch in batched(old_triples, 100):
|
|||
|
|
batch_stmt = BatchStatement()
|
|||
|
|
for row in batch:
|
|||
|
|
# Add to all three new tables
|
|||
|
|
batch_stmt.add(insert_subject_stmt, row)
|
|||
|
|
batch_stmt.add(insert_po_stmt, (row.collection, row.p, row.o, row.s))
|
|||
|
|
batch_stmt.add(insert_object_stmt, (row.collection, row.o, row.s, row.p))
|
|||
|
|
session.execute(batch_stmt)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Стратегия проверки
|
|||
|
|
|
|||
|
|
#### Проверки согласованности данных
|
|||
|
|
```python
|
|||
|
|
def validate_migration():
|
|||
|
|
# Count total records in old vs new tables
|
|||
|
|
old_count = session.execute("SELECT COUNT(*) FROM triples WHERE collection = ?", (collection,))
|
|||
|
|
new_count = session.execute("SELECT COUNT(*) FROM triples_by_subject WHERE collection = ?", (collection,))
|
|||
|
|
|
|||
|
|
assert old_count == new_count, f"Record count mismatch: {old_count} vs {new_count}"
|
|||
|
|
|
|||
|
|
# Spot check random samples
|
|||
|
|
sample_queries = generate_test_queries()
|
|||
|
|
for query in sample_queries:
|
|||
|
|
old_result = execute_legacy_query(query)
|
|||
|
|
new_result = execute_optimized_query(query)
|
|||
|
|
assert old_result == new_result, f"Query results differ for {query}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Стратегия тестирования
|
|||
|
|
|
|||
|
|
### Тестирование производительности
|
|||
|
|
|
|||
|
|
#### Сценарии для бенчмаркинга
|
|||
|
|
1. **Сравнение производительности запросов**
|
|||
|
|
Показатели производительности до и после для всех 8 типов запросов
|
|||
|
|
Акцент на улучшении производительности запроса `get_po` (удаление `ALLOW FILTERING`)
|
|||
|
|
Измерение задержки запросов при различных объемах данных
|
|||
|
|
|
|||
|
|
2. **Тестирование нагрузки**
|
|||
|
|
Одновременное выполнение запросов
|
|||
|
|
Скорость записи с использованием пакетных операций
|
|||
|
|
Использование памяти и ЦП
|
|||
|
|
|
|||
|
|
3. **Тестирование масштабируемости**
|
|||
|
|
Производительность при увеличении размеров коллекций
|
|||
|
|
Распределение запросов по нескольким коллекциям
|
|||
|
|
Использование узлов кластера
|
|||
|
|
|
|||
|
|
#### Наборы тестовых данных
|
|||
|
|
**Маленький:** 10 тысяч троек на коллекцию
|
|||
|
|
**Средний:** 100 тысяч троек на коллекцию
|
|||
|
|
**Большой:** 1 миллион и более троек на коллекцию
|
|||
|
|
**Несколько коллекций:** Тестирование распределения партиций
|
|||
|
|
|
|||
|
|
### Функциональное тестирование
|
|||
|
|
|
|||
|
|
#### Обновления модульных тестов
|
|||
|
|
```python
|
|||
|
|
# Example test structure for new implementation
|
|||
|
|
class TestCassandraKGPerformance:
|
|||
|
|
def test_get_po_no_allow_filtering(self):
|
|||
|
|
# Verify get_po queries don't use ALLOW FILTERING
|
|||
|
|
with patch('cassandra.cluster.Session.execute') as mock_execute:
|
|||
|
|
kg.get_po('test_collection', 'predicate', 'object')
|
|||
|
|
executed_query = mock_execute.call_args[0][0]
|
|||
|
|
assert 'ALLOW FILTERING' not in executed_query
|
|||
|
|
|
|||
|
|
def test_multi_table_consistency(self):
|
|||
|
|
# Verify all tables stay in sync
|
|||
|
|
kg.insert('test', 's1', 'p1', 'o1')
|
|||
|
|
|
|||
|
|
# Check all tables contain the triple
|
|||
|
|
assert_triple_exists('triples_by_subject', 'test', 's1', 'p1', 'o1')
|
|||
|
|
assert_triple_exists('triples_by_po', 'test', 'p1', 'o1', 's1')
|
|||
|
|
assert_triple_exists('triples_by_object', 'test', 'o1', 's1', 'p1')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Обновления результатов интеграционного тестирования
|
|||
|
|
```python
|
|||
|
|
class TestCassandraIntegration:
|
|||
|
|
def test_query_performance_regression(self):
|
|||
|
|
# Ensure new implementation is faster than old
|
|||
|
|
old_time = benchmark_legacy_get_po()
|
|||
|
|
new_time = benchmark_optimized_get_po()
|
|||
|
|
assert new_time < old_time * 0.5 # At least 50% improvement
|
|||
|
|
|
|||
|
|
def test_end_to_end_workflow(self):
|
|||
|
|
# Test complete write -> query -> delete cycle
|
|||
|
|
# Verify no performance degradation in integration
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### План отката
|
|||
|
|
|
|||
|
|
#### Быстрая стратегия отката
|
|||
|
|
1. **Переключение переменной окружения** - Немедленный возврат к устаревшим таблицам.
|
|||
|
|
2. **Сохранение устаревших таблиц** - Не удалять до тех пор, пока производительность не будет подтверждена.
|
|||
|
|
3. **Сигналы мониторинга** - Автоматический запуск отката на основе показателей ошибок/задержек.
|
|||
|
|
|
|||
|
|
#### Проверка отката
|
|||
|
|
```python
|
|||
|
|
def rollback_to_legacy():
|
|||
|
|
# Set environment variable
|
|||
|
|
os.environ['CASSANDRA_USE_LEGACY'] = 'true'
|
|||
|
|
|
|||
|
|
# Restart services to pick up change
|
|||
|
|
restart_cassandra_services()
|
|||
|
|
|
|||
|
|
# Validate functionality
|
|||
|
|
run_smoke_tests()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Риски и соображения
|
|||
|
|
|
|||
|
|
### Риски производительности
|
|||
|
|
**Увеличение времени записи** - 4 операции записи на каждую вставку (на 33% больше, чем при использовании 3-х таблиц)
|
|||
|
|
**Избыточность хранения** - В 4 раза больше места для хранения (на 33% больше, чем при использовании 3-х таблиц)
|
|||
|
|
**Сбои пакетной записи** - Требуется правильная обработка ошибок
|
|||
|
|
**Сложность удаления** - Удаление коллекции требует цикла "чтение-удаление"
|
|||
|
|
|
|||
|
|
### Операционные риски
|
|||
|
|
**Сложность миграции** - Миграция данных для больших наборов данных
|
|||
|
|
**Проблемы с согласованностью** - Обеспечение синхронизации всех таблиц
|
|||
|
|
**Недостаточность мониторинга** - Необходимы новые метрики для операций с несколькими таблицами
|
|||
|
|
|
|||
|
|
### Стратегии смягчения
|
|||
|
|
1. **Постепенное внедрение** - Начните с небольших коллекций
|
|||
|
|
2. **Комплексный мониторинг** - Отслеживайте все метрики производительности
|
|||
|
|
3. **Автоматизированная проверка** - Постоянная проверка согласованности
|
|||
|
|
4. **Возможность быстрого отката** - Выбор таблицы на основе среды
|
|||
|
|
|
|||
|
|
## Критерии успеха
|
|||
|
|
|
|||
|
|
### Улучшения производительности
|
|||
|
|
[ ] **Устранение ALLOW FILTERING** - Запросы get_po и get_os выполняются без фильтрации
|
|||
|
|
[ ] **Сокращение задержки запросов** - Улучшение времени отклика запросов на 50% или более
|
|||
|
|
[ ] **Более равномерное распределение нагрузки** - Отсутствие "горячих" партиций, равномерная нагрузка на узлы кластера
|
|||
|
|
[ ] **Масштабируемая производительность** - Время запроса пропорционально размеру результата, а не общему объему данных
|
|||
|
|
|
|||
|
|
### Функциональные требования
|
|||
|
|
[ ] **Совместимость API** - Весь существующий код продолжает работать без изменений
|
|||
|
|
[ ] **Согласованность данных** - Все три таблицы остаются синхронизированными
|
|||
|
|
[ ] **Отсутствие потери данных** - Миграция сохраняет все существующие тройки
|
|||
|
|
[ ] **Обратная совместимость** - Возможность отката к устаревшей схеме
|
|||
|
|
|
|||
|
|
### Операционные требования
|
|||
|
|
[ ] **Безопасная миграция** - Развертывание по принципу "синий-зеленый" с возможностью отката
|
|||
|
|
[ ] **Покрытие мониторингом** - Комплексные метрики для операций с несколькими таблицами
|
|||
|
|
[ ] **Покрытие тестами** - Все шаблоны запросов протестированы с использованием эталонных показателей производительности
|
|||
|
|
[ ] **Документация** - Обновленные процедуры развертывания и эксплуатации
|
|||
|
|
|
|||
|
|
## План
|
|||
|
|
|
|||
|
|
### Фаза 1: Реализация
|
|||
|
|
[ ] Переписать `cassandra_kg.py` с использованием схемы нескольких таблиц
|
|||
|
|
[ ] Реализовать пакетные операции записи
|
|||
|
|
[ ] Добавить оптимизацию с использованием подготовленных выражений
|
|||
|
|
[ ] Обновить модульные тесты
|
|||
|
|
|
|||
|
|
### Фаза 2: Интеграционное тестирование
|
|||
|
|
[ ] Обновить интеграционные тесты
|
|||
|
|
[ ] Эталонное тестирование производительности
|
|||
|
|
[ ] Тестирование нагрузки с использованием реалистичных объемов данных
|
|||
|
|
[ ] Скрипты проверки согласованности данных
|
|||
|
|
|
|||
|
|
### Фаза 3: Планирование миграции
|
|||
|
|
[ ] Скрипты развертывания по принципу "синий-зеленый"
|
|||
|
|
[ ] Инструменты миграции данных
|
|||
|
|
[ ] Обновления панели мониторинга
|
|||
|
|
[ ] Процедуры отката
|
|||
|
|
|
|||
|
|
### Фаза 4: Развертывание в производственной среде
|
|||
|
|
[ ] Поэтапное развертывание в производственной среде
|
|||
|
|
[ ] Мониторинг и проверка производительности
|
|||
|
|
[ ] Очистка устаревших таблиц
|
|||
|
|
[ ] Обновление документации
|
|||
|
|
|
|||
|
|
## Заключение
|
|||
|
|
|
|||
|
|
Эта стратегия денормализации с использованием нескольких таблиц напрямую решает две критические проблемы производительности:
|
|||
|
|
|
|||
|
|
1. **Устраняет дорогостоящий ALLOW FILTERING**, предоставляя оптимальные структуры таблиц для каждого шаблона запроса
|
|||
|
|
2. **Улучшает эффективность кластеризации** за счет составных ключей партиций, которые правильно распределяют нагрузку
|
|||
|
|
|
|||
|
|
Этот подход использует сильные стороны Cassandra, сохраняя при этом полную совместимость API, что обеспечивает автоматическое получение преимуществ от улучшений производительности для существующего кода.
|