An open API service indexing awesome lists of open source software.

https://github.com/ydb-platform/ydb-materializer


https://github.com/ydb-platform/ydb-materializer

Last synced: 4 months ago
JSON representation

Awesome Lists containing this project

README

          

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ydb-platform/ydb-materializer/blob/master/LICENSE)
[![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Ftech%2Fydb%2Fapps%2Fydb-materializer%2Fmaven-metadata.xml)](https://mvnrepository.com/artifact/tech.ydb.apps/ydb-materializer)
[![Publish](https://img.shields.io/github/actions/workflow/status/ydb-platform/ydb-materializer/publish.yaml)](https://github.com/ydb-platform/ydb-materializer/actions/workflows/publish.yaml)

# Процессор материализованных представлений YDB

YDB Materializer — это Java-приложение, которое обеспечивает наполнение данными управляемых пользователем материализованных представлений в YDB.

Каждое «материализованное представление» (MV) технически представляет собой обычную таблицу YDB, которая обновляется с помощью этого приложения. Исходная информация для наполнения MV извлекается из набора других таблиц, связанных друг с другом с помощью JOIN в стиле SQL. Для поддержки онлайн-синхронизации изменений исходных таблиц в MV используются потоки YDB Change Data Capture.

Таблицы назначения для MV, исходные таблицы, необходимые индексы и CDC-потоки должны быть созданы до использования приложения в режиме синхронизации MV. Приложение может помочь сгенерировать части DDL для некоторых объектов — например, оно сообщает о недостающих индексах и может сгенерировать предлагаемую структуру таблиц назначения.

[Скачать приложение можно на странице релизов](https://github.com/ydb-platform/ydb-materializer/releases).

## Минимальный исполняемый пример
Файлы для простого примера на одной таблице:
- `scripts/example-tables.sql` (создание таблиц и changefeed)
- `scripts/example-job1.sql` (определение MV и задания обработки)
- `scripts/example-job1.xml` (настройки приложения)

## Системные требования и порядок сборки

- Java 17 или выше.
- Кластер YDB 24.4+ с соответствующими разрешениями.
- Сетевой доступ к кластеру YDB.
- Необходимые системные таблицы, созданные в базе данных.
- Для сборки из исходных кодов - [Maven](https://maven.apache.org/)

Сборка:

```bash
export JAVA_HOME=/Library/Java/JavaVirtualMachines/openjdk-17.jdk/Contents/Home
mvn clean package -DskipTests=true
```

## Использование

YDB Materializer может быть встроен как библиотека в пользовательской приложение, либо применён как автономное приложение.

Описание материализованных представлений и заданий по их обработке необходимо подготовить с использованием специального SQL-подобного языка. Соответствующие описания могут быть поданы в виде текстового файла, либо в виде таблицы БД. Настройки подключения к БД и различные технические параметры подаются в виде набора свойств (программно либо в виде конфигурационного файла Java Properties).

В режиме автономного приложения YDB Materializer реализует:
- проверку корректности описаний материализованных представлений и заданий, включая соответствие их структуре исходных таблиц БД, и вывод для анализа пользователем соответствующих сообщений об ошибках и предупреждений;
- формирование и вывод для анализа пользователем различных SQL-операторов, используемых при работе средств материализации;
- работу в режиме сервиса, выполняющего синхронизацию изменений из исходных таблиц в таблицы материализованных представлений.

В режиме встраиваемой библиотеки YDB Materializer реализует все перечисленные функции, предоставляя возможность их программного вызова через методы соответствующих классов.

Зависимость Maven для встраивания YDB Materializer в приложение:

```xml

tech.ydb.apps
ydb-materializer
1.13

```

## Синтаксис языка материализованных представлений

YDB Materializer использует специальный SQL-подобный язык для определения материализованных представлений и заданий их обработки (обработчиков). Этот язык основан на подмножестве SQL с особыми расширениями для поддержки пользовательских материализованных представлений YDB.

### Обзор языка

Язык поддерживает два основных типа инструкций:
1. **Определение материализованного представления** — определяет структуру и логику материализованного представления.
2. **Определение обработчика** — определяет, как обрабатывать потоки изменений для обновления материализованного представления.

### Определение материализованного представления

Простое материализованное представление:

```sql
CREATE ASYNC MATERIALIZED VIEW
[DESTINATION ]
AS
SELECT
FROM AS
[]
[WHERE ];
```

- Выражение `SELECT` определяет набор таблиц, используемых как источники для MV, а также связи между исходными таблицами в виде ограниченного варианта SQL‑запроса.
- Необязательное выражение `DESTINATION` позволяет поместить таблицу MV в отдельную базу данных (см. отдельный раздел ниже).

Составное материализованное представление:

```sql
CREATE ASYNC MATERIALIZED VIEW
[DESTINATION ]
AS
(SELECT
FROM AS
[]
[WHERE ]) AS
UNION ALL
(SELECT
FROM AS
[]
[WHERE ]) AS
UNION ALL
...
;
```

Определение составного материализованного представления состоит из двух или более подзапросов, соответствующих по синтаксису запросу для простого материализованного представления, и объединённых оператором `UNION ALL`. Каждый из подзапросов дополнительно должен содержать псевдоним, уникальный в рамках составного материализованного представления, и используемый для идентификации подзапроса при его обработке.

#### Базы данных назначения

По умолчанию таблицы материализованных представлений создаются и обновляются в той же базе данных YDB, из которой читаются исходные данные и которая задаётся параметром подключения `ydb.url`.

При необходимости материализованное представление можно связать с **отдельной базой данных назначения**, используя выражение `DESTINATION`:

```sql
CREATE ASYNC MATERIALIZED VIEW
DESTINATION
AS
SELECT ...
```

- **``** — логическое имя дополнительного подключения к YDB.
- Исходные таблицы для представления по‑прежнему читаются из **основной** базы данных.
- Все операции записи (`UPSERT` / `DELETE`) для материализованного представления выполняются в **базе назначения**.

Чтобы использовать отдельную базу назначения, опишите дополнительное подключение в конфигурации, добавив префикс `.` к обычным параметрам подключения к YDB, например:

```xml

grpcs://ydb01.localdomain:2135/cluster1/testdb

grpcs://ydb01.localdomain:2135/cluster1/testdb_mv
/path/to/ca.crt
STATIC
root
your_password
```

В определении материализованного представления укажите это имя подключения:

```sql
CREATE ASYNC MATERIALIZED VIEW `pk_test/mv1`
DESTINATION `altdest1`
AS
SELECT ...
```

Если предложение `DESTINATION` не указано, материализованное представление пишется в основную базу данных, настроенную через параметр `ydb.url`.

#### Определение столбцов

Каждый столбец в предложении SELECT может быть:
- **Прямой ссылкой на столбец**: `table_alias.column_name AS column_alias`
- **Вычисляемым выражением**: `#[]# AS column_alias`
- **Вычисляемым выражением со ссылками на колонки**: `COMPUTE ON table_alias.column_name, ... #[]# AS column_alias`

#### Условия JOIN

```sql
[INNER | LEFT] JOIN AS
ON [AND ]*
```

Условия JOIN поддерживают:
- Равенство столбцов: `table1.col1 = table2.col2`
- Равенство констант: `table1.col1 = 'value'` или `table1.col1 = 123`

#### Фильтрующие выражения

Предложение WHERE поддерживает непрозрачные (для приложения) YQL-выражения, подставляемые без изменений непосредственно в формируемые запросы:
```sql
WHERE COMPUTE ON table_alias.column_name, ... #[]#
```

Наличие ссылок на конкретные имена таблиц и колонки позволяет корректно формировать производные SQL-операторы с использованием непрозрачных выражений, опирающихся на конкретные колонки исходных таблиц.

### Определение обработчика

```sql
CREATE ASYNC HANDLER
[CONSUMER ]
PROCESS ,
[PROCESS ,]
INPUT CHANGEFEED AS [STREAM|BATCH],
[INPUT CHANGEFEED AS [STREAM|BATCH], ...];
```

#### Компоненты обработчика

- **PROCESS**: указывает, какие материализованные представления обновляет этот обработчик.
- **INPUT**: определяет входные таблицы и их потоки changefeed.
- `STREAM`: обработка отдельных изменений в режиме реального времени.
- `BATCH`: пакетная обработка накопленных изменений.
- **CONSUMER**: необязательное имя потребителя для changefeed (если не указано, то используется имя обработчика).

### Непрозрачные выражения

Язык поддерживает непрозрачные выражения, заключённые в разделители `#[` и `]#`. Они содержат код YQL (Yandex Query Language), который передаётся в базу данных без разбора:

```sql
-- В предложении SELECT
SELECT #[Substring(main.c20, 3, 5)]# AS c11,
#[CAST(NULL AS Int32?)]# AS c12

-- В предложении WHERE
WHERE #[main.c6=7 AND (sub2.c7 IS NULL OR sub2.c7='val2'u)]#

-- С предложением COMPUTE ON
COMPUTE ON main, sub2 #[main.c6=7 AND (sub2.c7 IS NULL OR sub2.c7='val2'u)]#
```

### Полный пример

```sql
-- Определение материализованного представления
CREATE ASYNC MATERIALIZED VIEW `test1/mv1` AS
SELECT main.id AS id,
main.c1 AS c1,
main.c2 AS c2,
main.c3 AS c3,
sub1.c8 AS c8,
sub2.c9 AS c9,
sub3.c10 AS c10,
#[Substring(main.c20, 3, 5)]# AS c11,
#[CAST(NULL AS Int32?)]# AS c12
FROM `test1/main_table` AS main
INNER JOIN `test1/sub_table1` AS sub1
ON main.c1 = sub1.c1 AND main.c2 = sub1.c2
LEFT JOIN `test1/sub_table2` AS sub2
ON main.c3 = sub2.c3 AND 'val1'u = sub2.c4
INNER JOIN `test1/sub_table3` AS sub3
ON sub3.c5 = 58
WHERE #[main.c6=7 AND (sub2.c7 IS NULL OR sub2.c7='val2'u)]#;

-- Определение обработчика для обработки изменений
CREATE ASYNC HANDLER h1 CONSUMER h1_consumer
PROCESS `test1/mv1`,
INPUT `test1/main_table` CHANGEFEED cf1 AS STREAM,
INPUT `test1/sub_table1` CHANGEFEED cf2 AS STREAM,
INPUT `test1/sub_table2` CHANGEFEED cf3 AS STREAM,
INPUT `test1/sub_table3` CHANGEFEED cf4 AS BATCH;
```

### Особенности языка

- **Нечувствительность к регистру ключевых слов**: все ключевые слова SQL не чувствительны к регистру.
- **Идентификаторы в кавычках**: используйте обратные кавычки для идентификаторов со специальными символами: `` `table/name` ``.
- **Строковые литералы**: строки в одинарных кавычках с необязательными суффиксами типа (`'value'u` для Utf8).
- **Комментарии**: как однострочные комментарии (`--`), так и блочные (`/* */`).
- **Завершение точкой с запятой**: инструкции должны заканчиваться точкой с запятой.

### Поддерживаемые типы данных

Язык работает со стандартными типами данных YDB:
- **Текстовые**: строковые данные (используйте `'value'u` для строк Utf8).
- **Числовые**: Int32, Int64, Decimal и т. д.
- **Временные**: Timestamp, Date и т. д.
- **Сложные**: JsonDocument и т. д.

## Синтаксис командной строки

```bash
java -jar ydb-materializer-*.jar
```

**Параметры:**
- `` — путь к файлу XML-конфигурации.
- `` — режим работы, в соответствии с приведённым ниже перечнем.

Приложение поддерживает три режима работы:
- CHECK: проверка конфигурации;
- SQL: генерация SQL-инструкций, представляющих логику материализации;
- STREAMS: генерация операторов для создания CDC-потоков и CDC-консьюмеров;
- LOCAL: фактическая синхронизация MV в режиме отдельного приложения;
- JOB: фактическая синхронизация MV под управлением встроенного менеджера распределённых заданий.

### Режимы работы

#### Режим CHECK
Проверяет определения материализованных представлений и сообщает о проблемах:
```bash
java -jar ydb-materializer-*.jar config.xml CHECK
```

#### Режим SQL
Генерирует и выводит SQL-инструкции для материализованных представлений:
```bash
java -jar ydb-materializer-*.jar config.xml SQL
```

#### Режим STREAMS
Генерирует и (опционально) применяет к рабочей базе данных SQL-инструкции для создания потоков CDC:
```bash
java -jar ydb-materializer-*.jar config.xml STREAMS
```

Создание потоков CDC осуществляется при наличии в конфигурации установленного параметра `job.streams.create=true`.

#### Режим LOCAL
Запускает локальную одноузловую службу обработки материализованных представлений:
```bash
java -jar ydb-materializer-*.jar config.xml LOCAL
```

#### Режим JOB
Запускает распределенную службу обработки материализованных представлений:
```bash
java -jar ydb-materializer-*.jar config.xml JOB
```

## Файл конфигурации

Файл конфигурации — это файл свойств XML, в котором определены параметры подключения и настройки задания. Вот пример конфигурации:

```xml

Пример конфигурации YDB Materializer

grpcs://ydb01.localdomain:2135/cluster1/testdb
/path/to/ca.crt
1000
false

STATIC
root
your_password

FILE
example-job1.sql
mv/statements
false

h1,h2,h3
10000
mv/scans_state
mv/coordination
10

dictionary
mv/dict_hist
28800

HASH
4
4
10000
1000
500
100000
30

mv/jobs
mv/job_scans
mv/runners
mv/runner_jobs
mv/commands
5000
10000
30000
90000
1

```

### Справочная информация о параметрах конфигурации

#### Подключение к базе данных
- `ydb.url` — строка подключения к YDB (обязательно).
- `ydb.cafile` — путь к файлу TLS-сертификата (опционально).
- `ydb.poolSize` — размер пула подключений (по умолчанию: 2 × количество ядер ЦП).
- `ydb.preferLocalDc` — предпочтительный локальный центр обработки данных (по умолчанию: false).

#### Аутентификация
- `ydb.auth.mode` — режим аутентификации:
- `NONE` — без аутентификации.
- `ENV` — переменные окружения.
- `STATIC` — имя пользователя и пароль.
- `METADATA` — метаданные виртуальной машины.
- `SAKEY` — файл ключа сервисного аккаунта.
- `ydb.auth.username` — имя пользователя (для режима STATIC).
- `ydb.auth.password` — пароль (для режима STATIC).
- `ydb.auth.sakey` — путь к файлу ключа сервисного аккаунта (для режима SAKEY).

#### Настройка задания
- `job.input.mode` — источник ввода: `FILE` или `TABLE`.
- `job.input.file` — путь к SQL-файлу (для режима FILE).
- `job.input.table` — имя таблицы для инструкций (для режима TABLE).
- `job.streams.create` - если установлено в `true`, создать недостающие объекты потоков CDC.
- `job.handlers` — список имён обработчиков для активации, разделённый запятыми.
- `job.scan.table` — имя таблицы для ведения позиций сканирования
- `job.dict.hist.table` - имя таблицы для ведения истории изменения справочников
- `job.coordination.path` — путь к узлу службы координации
- `job.coordination.timeout` - таймаут распределенной блокировки, секунд

#### Настройки сканера справочников
- `job.dict.consumer` - имя консьюмера для сбора информации об изменениях справочников
- `job.dict.hist.table` - альтернативное имя таблицы `mv/dict_hist`
- `job.dict.scan.seconds` - период между проверками изменений справочников

#### Настройка производительности
- `job.apply.partitioning` - HASH (по умолчанию) или RANGE стратегия партиционирования задач
- `job.cdc.threads` — количество потоков чтения CDC
- `job.apply.threads` — количество рабочих потоков apply
- `job.apply.queue` — максимальное количество элементов в очереди apply на поток
- `job.batch.select` — размер пакета для операций SELECT
- `job.batch.upsert` — размер пакета для операций UPSERT или DELETE
- `job.max.row.changes` — максимальное количество изменений по отдельной таблице, обрабатываемых за одну итерацию
- `job.query.seconds` — максимальное время выполнения запроса на выборку, вставку или удаление данных, секунд
- `job.scan.rate` - Ограничение скорости операций сканирования, строк в секунду

#### Настройки системы управления заданиями
- `mv.jobs.table` - Альтернативное имя таблицы `mv/jobs`
- `mv.scans.table` - Альтернативное имя таблицы `mv/job_scans`
- `mv.runners.table` - Альтернативное имя таблицы `mv/runners`
- `mv.runner.jobs.table` - Альтернативное имя таблицы `mv/runner_jobs`
- `mv.commands.table` - Альтернативное имя таблицы `mv/commands`
- `mv.scan.period.ms` - Период сканирования Исполнителя и Координатора, миллисекунды
- `mv.report.period.ms` - Период обновления состояния Исполнителя, миллисекунды
- `mv.runner.timeout.ms` - Таймаут отсутствия обновлений Координатора и Исполнителя, миллисекунды
- `mv.coord.startup.ms` - Пауза между стартом Координатора и началом распределения заданий, миллисекунды
- `mv.coord.runners.count` - Минимальное количество Исполнителей для распределения заданий

#### Метрики
- `metrics.enabled` - Включить endpoint метрик Prometheus (по умолчанию: false)
- `metrics.port` - Порт endpoint метрик Prometheus (по умолчанию: 7311)
- `metrics.host` - Адрес/интерфейс для endpoint метрик (по умолчанию: 0.0.0.0)

Готовый стенд Prometheus + Grafana описан в `monitoring/README.md`.

### Собираемые метрики

При включённых метриках (`metrics.enabled=true`) приложение отдаёт следующие метрики Prometheus. Все имена метрик имеют префикс `ydbmv_`.

#### Метрики обработчика (задания)

Описание метрик см. в таблице ниже.

| Метрика | Тип | Описание |
|--------|-----|----------|
| `ydbmv_handler_active` | Gauge | 1, если обработчик (задание) активен, 0 иначе, для каждого обработчика |
| `ydbmv_handler_locked` | Gauge | 1, если активный обработчик не может продолжать работу из-за проблемы обработки, 0 иначе |
| `ydbmv_handler_threads` | Gauge | Количество рабочих потоков обработчика |
| `ydbmv_handler_queue_size` | Gauge | Текущий размер входной очереди обработчика |
| `ydbmv_handler_queue_limit` | Gauge | Максимально допустимый размер входной очереди |
| `ydbmv_handler_queue_wait` | Counter | Количество ожиданий на вставке данных в очередь из-за её переполнения |

Описание меток приведено ниже.

| Метка | Описание |
|-------|----------|
| `handler` | Имя обработчика |

#### Метрики CDC

Описание метрик см. в таблице ниже.

| Метрика | Тип | Описание |
|--------|-----|----------|
| `ydbmv_cdc_records_read` | Counter | Количество записей CDC, прочитанных из топиков |
| `ydbmv_cdc_records_submitted` | Counter | Количество разобранных записей CDC, переданных в обработку |
| `ydbmv_cdc_parse_errors` | Counter | Количество ошибок разбора сообщений CDC |
| `ydbmv_cdc_parse_seconds` | Histogram | Время разбора сообщений CDC |
| `ydbmv_cdc_submit_seconds` | Histogram | Время постановки сообщений CDC в очередь, включая ожидание освобождения места в очереди |

Описание меток приведено ниже.

| Метка | Описание |
|-------|----------|
| `handler` | Имя обработчика |
| `consumer` | Имя консьюмера CDC |
| `topic` | Полный путь топика CDC |

#### Метрики операций сканирования

Описание метрик см. в таблице ниже.

| Метрика | Тип | Описание |
|--------|-----|----------|
| `ydbmv_scan_records_submitted` | Counter | Количество записей, переданных на обработку при начальном/фоновом сканировании |
| `ydbmv_scan_delay_millis` | Counter | Суммарная задержка сканирования в миллисекундах из-за ограничителя скорости |

Описание меток приведено ниже.

| Метка | Описание |
|-------|----------|
| `handler` | Имя обработчика |
| `target` | Имя обрабатываемого MV |
| `alias` | Имя компонента MV для MV в стиле `UNION ALL` |

#### Метрики обработки

Описание метрик см. в таблице ниже.

| Метрика | Тип | Описание |
|--------|-----|----------|
| `ydbmv_processing_records` | Counter | Успешно обработано записей по действию |
| `ydbmv_processing_errors` | Counter | Ошибки обработки по действию |
| `ydbmv_processing_seconds` | Histogram | Полное время обработки по действию |
| `ydbmv_sql_seconds` | Histogram | Время выполнения SQL по действию |

Описание меток приведено ниже.

| Метка | Описание |
|-------|----------|
| `type` | Этап обработки (например, `filter`, `grabKeys`, `transform`, `sync`) |
| `handler` | Имя обработчика |
| `target` | Имя обрабатываемого MV |
| `alias` | Имя компонента MV для MV в стиле `UNION ALL` |
| `source` | Имя входной таблицы для этапа обработки |
| `action` | Имя действия (`select`, `upsert`, `delete` для времени SQL и `all` для остальных метрик) |

#### Метрики JVM

При использовании встроенного сервера Prometheus дополнительно автоматически регистрируются стандартные метрики JVM (память, GC, потоки и т.д.).

## Управление распределёнными задачами (режим JOB)

Режим JOB предоставляет возможности для управления распределёнными задачами, позволяя управлять задачами обработки материализованных представлений на нескольких экземплярах. Этот режим запускается с помощью команды:

```bash
java -jar ydb-materializer.jar config.xml JOB
```

### Обзор архитектуры

Система управления распределёнными задачами состоит из двух основных компонентов:

- MvRunner — выполняет задачи локально на каждом экземпляре.
- MvCoordinator — управляет распределением и координацией задач между исполнителями.

Каждая задача — это работающий экземпляр «обработчика», определённого в конфигурации.

При запуске задачи приложение заново считывает и проверяет конфигурацию, после чего использует её в конкретной задаче.

### Управляющие таблицы

Система использует несколько таблиц YDB для управления распределёнными операциями:

#### Таблицы конфигурации

**`mv/jobs`** — определения задач и желаемое состояние:

```sql
CREATE TABLE `mv/jobs` (
job_name Text NOT NULL, -- Имя обработчика
job_settings JsonDocument, -- Конфигурация обработчика
should_run Bool, -- Должна ли задача выполняться
PRIMARY KEY(job_name)
);
```

**`mv/job_scans`** - запросы на сканирование определённых целей:

```sql
CREATE TABLE `mv/job_scans` (
job_name Text NOT NULL, -- Имя обработчика
target_name Text NOT NULL, -- Имя целевой таблицы
scan_settings JsonDocument, -- Конфигурация сканирования
requested_at Timestamp, -- Когда был запрошен скан
accepted_at Timestamp, -- Когда скан был принят
runner_id Text, -- Назначенный идентификатор исполнителя
command_no Uint64, -- Номер команды
PRIMARY KEY(job_name, target_name)
);
```

#### Рабочие таблицы

**`mv/runners`** - активные экземпляры исполнителей:

```sql
CREATE TABLE `mv/runners` (
runner_id Text NOT NULL, -- Уникальный идентификатор исполнителя
runner_identity Text, -- Информация о хосте, PID, времени запуска
updated_at Timestamp, -- Последнее обновление статуса
PRIMARY KEY(runner_id)
);
```

**`mv/runner_jobs`** - задачи, выполняемые в данный момент каждым исполнителем:

```sql
CREATE TABLE `mv/runner_jobs` (
runner_id Text NOT NULL, -- Идентификатор исполнителя
job_name Text NOT NULL, -- Имя задачи
job_settings JsonDocument, -- Конфигурация задачи
started_at Timestamp, -- Когда задача началась
INDEX ix_job_name GLOBAL SYNC ON (job_name),
PRIMARY KEY(runner_id, job_name)
);
```

**`mv/commands`** - очередь команд для исполнителей:

```sql
CREATE TABLE `mv/commands` (
runner_id Text NOT NULL, -- Целевой исполнитель
command_no Uint64 NOT NULL, -- Номер последовательности команды
created_at Timestamp, -- Время создания команды
command_type Text, -- START/STOP/SCAN/NOSCAN
job_name Text, -- Имя целевой задачи
target_name Text, -- Целевая таблица (для сканирования)
job_settings JsonDocument, -- Конфигурация задачи
command_status Text, -- CREATED/TAKEN/SUCCESS/ERROR
command_diag Text, -- Диагностика ошибок
INDEX ix_command_no GLOBAL SYNC ON (command_no),
INDEX ix_command_status GLOBAL SYNC ON (command_status, runner_id),
PRIMARY KEY(runner_id, command_no)
);
```

### Операции управления задачами

#### Добавление задач

Чтобы добавить новую задачу, вставьте запись в таблицу `mv/jobs`:

```sql
INSERT INTO `mv/jobs` (job_name, job_settings, should_run)
VALUES ('my_handler', NULL, true);
```

Координатор автоматически обнаружит новую задачу и назначит её доступному исполнителю.

Параметры в поле `job_settings` можно не указывать (будут использованы параметры по умолчанию, загружаемые из глобальных настроек) или указать в виде JSON-документа следующего формата:

```json
{ # в комментарии указана соответствующая глобальная настройка
"cdcReaderThreads": 4, # job.cdc.threads
"applyThreads": 4, # job.apply.threads
"applyQueueSize": 10000, # job.apply.queue
"selectBatchSize": 1000, # job.batch.select
"upsertBatchSize": 500, # job.batch.upsert
"dictionaryScanSeconds": 28800, # job.dict.scan.seconds
"queryTimeoutSeconds": 30 # job.query.seconds
}
```

В примере выше показаны параметры по умолчанию для обычных задач. Для специальной задачи сканера словаря можно указать следующие параметры:

```json
{
"upsertBatchSize": 500, # job.batch.upsert
"cdcReaderThreads": 4, # job.cdc.threads
"rowsPerSecondLimit": 10000, # job.scan.rate
"maxChangeRowsScanned": 100000 # job.max.row.changes
}
```

#### Удаление задач

Чтобы остановить и удалить задачу:

```sql
UPDATE `mv/jobs` SET should_run = false WHERE job_name = 'my_handler';
-- или
DELETE FROM `mv/jobs` WHERE job_name = 'my_handler';
```

#### Запрос на сканирование

Чтобы запросить сканирование определённой целевой таблицы:

```sql
INSERT INTO `mv/job_scans` (job_name, target_name, scan_settings, requested_at)
VALUES ('my_handler', 'target_table', '{"rowsPerSecondLimit": 5000}', CurrentUtcTimestamp());
```

### Мониторинг операций

#### Проверка выполняемых задач

```sql
SELECT rj.runner_id, rj.job_name, rj.started_at, r.runner_identity
FROM `mv/runner_jobs` rj
JOIN `mv/runners` r ON rj.runner_id = r.runner_id;
```

#### Проверка состояния задачи

```sql
SELECT j.job_name, j.should_run,
CASE WHEN rj.job_name IS NOT NULL THEN 'RUNNING'u ELSE 'STOPPED'u END as status
FROM `mv/jobs` j
LEFT JOIN `mv/runner_jobs` rj ON j.job_name = rj.job_name;
```

#### Проверка очереди команд:

```sql
SELECT runner_id, command_no, command_type, job_name, command_status, created_at
FROM `mv/commands`
WHERE command_status IN ('CREATED'u, 'TAKEN'u)
ORDER BY created_at;
```

#### Проверка состояния исполнителя

```sql
SELECT runner_id, runner_identity, updated_at
FROM `mv/runners`
ORDER BY updated_at DESC;
```

### Типы команд

Система поддерживает четыре типа команд:

- `START` — запустить задачу на исполнителе.
- `STOP` — остановить задачу на исполнителе.
- `SCAN` — начать сканирование определённой целевой таблицы.
- `NOSCAN` — остановить уже запущенное сканирование для определённой целевой таблицы.

### Имена задач

Имя задачи для обычных задач соответствует имени обработчика. Есть два специальных имени задач:

- `ydbmv$dictionary` — сканер словаря (управляется вручную)
- `ydbmv$coordinator` — задача координатора (управляется автоматически)

Эти специальные имена нельзя использовать для обычных обработчиков (на самом деле имя обработчика не может начинаться с префикса «ydbmv»).

### Отказоустойчивость

Система обеспечивает автоматическую отказоустойчивость:

- Обнаружение сбоя исполнителя — исполнители периодически сообщают о своём состоянии; неактивные исполнители автоматически удаляются.
- Перераспределение задач — при сбое исполнителей их задачи автоматически переназначаются доступным исполнителям.
- Повтор команд — неудачные команды остаются в очереди для повторной попытки.
- Выбор лидера — одновременно активен только один экземпляр координатора.

### Развёртывание

1. **Создание управляющих таблиц** — используйте предоставленный скрипт `scripts/example-tables.sql`. Имена таблиц можно настроить по мере необходимости.
1. **Развёртывание исполнителей** — запустите несколько экземпляров в режиме JOB.
1. **Настройка задач** — вставьте определения задач в таблицу `mv/jobs`. Добавьте определения сканирования в таблицу mv/job_scans.
1. **Мониторинг операций** — используйте запросы для мониторинга, перечисленные выше.

Система автоматически распределит задачи между доступными исполнителями и поддержит желаемое состояние.

## Настройка производительности

На практике общая производительность в первую очередь зависит от сложности запросов, определяющих материализованное представление, а не только от количества рабочих потоков. Сложные JOIN-операции, большие промежуточные результирующие наборы, сложные условия `WHERE` (включая непрозрачные YQL-выражения `#[ ... ]#`), а также наличие нескольких веток `UNION ALL` напрямую увеличивают стоимость каждого оператора `SELECT` / `UPSERT` / `DELETE`, выполняемого YDB Materializer.

Параметры задания обработчика, перечисленные ниже, контролируют, насколько агрессивно YDB Materializer читает потоки изменений CDC и применяет обновления как для обработчиков стиля **STREAM**, так и для обработчиков стиля **BATCH** (включая операции сканирования и обновления MV на основе изменений справочников):

- **`job.cdc.threads` / `cdcReaderThreads`**
- Задаёт количество параллельных потоков, читающих из CDC изменения.
- **Режим STREAM**: больше потоков увеличивает скорость чтения новых событий из YDB, что может улучшить сквозную задержку, но также увеличивает нагрузку на нижестоящие рабочие потоки apply и на базу данных в целом.
- **Режим BATCH**: больше потоков ускоряют чтение и сохранение накопленных изменений в промежуточную таблицу, но не влияют напрямую на сквозную задержку (которая в основном зависит от настройки `job.dict.scan.seconds`). Обратите внимание, что используется настройка, определённая для задачи сканера словаря (или глобальная настройка, если конкретная настройка для сканера словаря не определена), а не настройка, сконфигурированная для соответствующей обычной задачи.
- **Сканирования**: эта настройка не влияет на сканирования, в том числе на работу сканирований при обнаружении изменений справочников.

- **`job.apply.threads` / `applyThreads`**
- Задаёт количество рабочих потоков, выполняющих операторы `SELECT` / `UPSERT` / `DELETE` для обновления таблиц MV. Обрабатываемые ключи отправляются конкретному рабочему потоку в зависимости от значения ключа, поэтому конфликт по одному ключу никогда не происходит.
- **Режим STREAM**: больше потоков позволяют обрабатывать больше пакетов изменений параллельно, улучшая пропускную способность; слишком много потоков может вызвать конкуренцию за ресурсы и перегрузить кластер YDB.
- **Режим BATCH**: то же, что и для режима STREAM. Использование значительного количества потоков в сочетании с большим количеством строк, затронутых изменениями справочников, может привести к большому всплеску использования ресурсов при обработке накопленных изменений справочников.
- **Сканирования**: то же, что и для режима STREAM. Общее время, необходимое для выполнения сканирования, зависит от объёма работы (размера MV) и скорости сканирования, которая ограничивается как настройкой `job.scan.rate`, так и производительностью процесса apply, а последняя зависит от настроенного количества задействованных потоков.

- **`job.apply.queue` / `applyQueueSize`**
- Максимальное количество поставленных в очередь изменений, передаваемых на обработку (размер буфера между читателями CDC и рабочими потоками apply, а также между рабочими потоками apply, выполняющими операцию выборки ключей, и рабочими потоками apply, выполняющими обновление MV). Каждая задача использует счётчик (один на задачу) для подсчёта общего количества элементов в очереди, и использует его значение для приостановки чтения дополнительных входных данных из топиков CDC, когда очередь становится слишком большой. Это ограничивает оперативную память, используемую промежуточными данными внутри экземпляра YDB Materializer, выполняющего конкретную задачу.
- **Режим STREAM**: большая очередь сглаживает кратковременные всплески входящего трафика CDC; если она слишком мала, читатели CDC ограничиваются чаще, и сквозная задержка увеличивается. Если очередь слишком велика, задание может накапливать много ожидающих изменений в памяти, увеличивая использование памяти и время, необходимое для освобождения очереди.
- **Режим BATCH и сканирования**: определяет, сколько подготовленных данных может ждать выполнения. Большие очереди помогают держать рабочие потоки apply занятыми, но также увеличивают использование памяти.

- **`job.batch.select` / `selectBatchSize`**
- Ограничивает, сколько исходных строк читается в одном операторе `SELECT` при сборе данных для пакета изменений.
- **Режим STREAM**: небольшие значения помогают группировать операции чтения и при этом сохранять их задержки на низком уровне; увеличение этого значения может уменьшить накладные расходы SQL на строку, но может повысить задержку для отдельных изменений, поскольку большие пакеты дольше обрабатываются.
- **Режим BATCH и сканирования**: напрямую влияет на размер операций чтения во время полной или частичной повторной синхронизации. Большие пакеты улучшают пропускную способность (меньше запросов, лучше утилизация сетевых взаимодействий), но создают более тяжёлые запросы для YDB.

- **`job.batch.upsert` / `upsertBatchSize`**
- Контролирует, сколько строк записывается в одной операции `UPSERT` или `DELETE` в целевые таблицы MV.
- **Режим STREAM**: небольшие значения помогают эффективно группировать небольшие обновления, сохраняя приемлемую задержку записи; очень маленькие значения увеличивают накладные расходы на строку; очень большие значения могут вызвать длительно выполняющиеся операторы записи, которые с большей вероятностью превысят таймауты или увеличат общую задержку обновлений MV.
- **Режим BATCH и сканирования**: большие пакеты значительно увеличивают пропускную способность записи во время операций массового обновления, но также усиливают влияние каждого отдельного оператора на потребление ресурсов (блокировки, использование ресурсов и потенциальные повторные попытки). Рекомендуется постепенно увеличивать это значение и отслеживать задержку YDB, частоту ошибок и использование ресурсов.

- **`job.query.seconds` / `queryTimeoutSeconds`**
- Максимально допустимое время для выполнения запроса. При достижении таймаута возвращается ошибка `CLIENT_DEADLINE_EXCEEDED`, и запрос перезапускается. Этот параметр является защитой от зависших запросов, которые могли бы замедлить общую обработку, если бы им было разрешено выполняться произвольное количество времени. Значение должно быть выбрано таким образом, чтобы позволить выполнение всех используемых потенциально сложных запросов, но при этом ограничить максимальное время выполнения в худшем случае, например, при выполнении на перегруженном узле YDB.

- **`job.dict.scan.seconds` / `dictionaryScanSeconds`**
- Интервал между проверками изменений словаря, потенциально влияющих на MV в рамках конкретной задачи.
- **Режим STREAM и сканирования**: не влияет.
- **Режим BATCH**: больше времени между проверками означает более редкие проверки изменений, что позволяет этим изменениям накапливаться. Накопление большего количества изменений позволяет обработать эти изменения в одном сканировании вместо запуска нескольких сканирований для каждой меньшей порции изменений. Связанная настройка `job.max.row.changes` ограничивает общее количество изменений, разрешённых к обработке в одном пакете, что помогает гарантировать, что слишком много обновлений словаря не переполнят память текущего экземпляра YDB Materializer.

При настройке этих параметров начните со значений по умолчанию, наблюдайте за метриками YDB (задержка, пропускная способность, CPU, память, таймауты запросов), затем настраивайте один параметр за раз. Для большинства рабочих нагрузок безопаснее сохранять размеры пакетов и количество потоков умеренными для обработчиков STREAM (предпочитая предсказуемую задержку), и использовать более агрессивные значения только для запланированных операций BATCH или сканирований, где допустима более высокая кратковременная нагрузка.