{"id":31785552,"url":"https://github.com/ydb-platform/ydb-materializer","last_synced_at":"2026-02-14T22:08:02.688Z","repository":{"id":306104846,"uuid":"1024792442","full_name":"ydb-platform/ydb-materializer","owner":"ydb-platform","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-05T17:20:21.000Z","size":1033,"stargazers_count":2,"open_issues_count":2,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-06T00:41:46.523Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ydb-platform.png","metadata":{"files":{"readme":"README-ru.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-23T08:55:19.000Z","updated_at":"2026-02-05T17:26:56.000Z","dependencies_parsed_at":"2025-07-23T18:17:01.413Z","dependency_job_id":"6563e717-2d1f-4397-a74e-76d49d56ebb0","html_url":"https://github.com/ydb-platform/ydb-materializer","commit_stats":null,"previous_names":["zinal/ydb-materializer","ydb-platform/ydb-materializer"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/ydb-platform/ydb-materializer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ydb-platform%2Fydb-materializer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ydb-platform%2Fydb-materializer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ydb-platform%2Fydb-materializer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ydb-platform%2Fydb-materializer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ydb-platform","download_url":"https://codeload.github.com/ydb-platform/ydb-materializer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ydb-platform%2Fydb-materializer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29457941,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T21:29:27.764Z","status":"ssl_error","status_checked_at":"2026-02-14T21:28:11.111Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-10T11:58:10.862Z","updated_at":"2026-02-14T22:08:02.678Z","avatar_url":"https://github.com/ydb-platform.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ydb-platform/ydb-materializer/blob/master/LICENSE)\n[![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)\n[![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)\n\n# Процессор материализованных представлений YDB\n\nYDB Materializer — это Java-приложение, которое обеспечивает наполнение данными управляемых пользователем материализованных представлений в YDB.\n\nКаждое «материализованное представление» (MV) технически представляет собой обычную таблицу YDB, которая обновляется с помощью этого приложения. Исходная информация для наполнения MV извлекается из набора других таблиц, связанных друг с другом с помощью JOIN в стиле SQL. Для поддержки онлайн-синхронизации изменений исходных таблиц в MV используются потоки YDB Change Data Capture.\n\nТаблицы назначения для MV, исходные таблицы, необходимые индексы и CDC-потоки должны быть созданы до использования приложения в режиме синхронизации MV. Приложение может помочь сгенерировать части DDL для некоторых объектов — например, оно сообщает о недостающих индексах и может сгенерировать предлагаемую структуру таблиц назначения.\n\n[Скачать приложение можно на странице релизов](https://github.com/ydb-platform/ydb-materializer/releases).\n\n## Минимальный исполняемый пример\nФайлы для простого примера на одной таблице:\n- `scripts/example-tables.sql` (создание таблиц и changefeed)\n- `scripts/example-job1.sql` (определение MV и задания обработки)\n- `scripts/example-job1.xml` (настройки приложения)\n\n## Системные требования и порядок сборки\n\n- Java 17 или выше.\n- Кластер YDB 24.4+ с соответствующими разрешениями.\n- Сетевой доступ к кластеру YDB.\n- Необходимые системные таблицы, созданные в базе данных.\n- Для сборки из исходных кодов - [Maven](https://maven.apache.org/)\n\nСборка:\n\n```bash\nexport JAVA_HOME=/Library/Java/JavaVirtualMachines/openjdk-17.jdk/Contents/Home\nmvn clean package -DskipTests=true\n```\n\n## Использование\n\nYDB Materializer может быть встроен как библиотека в пользовательской приложение, либо применён как автономное приложение.\n\nОписание материализованных представлений и заданий по их обработке необходимо подготовить с использованием специального SQL-подобного языка. Соответствующие описания могут быть поданы в виде текстового файла, либо в виде таблицы БД. Настройки подключения к БД и различные технические параметры подаются в виде набора свойств (программно либо в виде конфигурационного файла Java Properties).\n\nВ режиме автономного приложения YDB Materializer реализует:\n- проверку корректности описаний материализованных представлений и заданий, включая соответствие их структуре исходных таблиц БД, и вывод для анализа пользователем соответствующих сообщений об ошибках и предупреждений;\n- формирование и вывод для анализа пользователем различных SQL-операторов, используемых при работе средств материализации;\n- работу в режиме сервиса, выполняющего синхронизацию изменений из исходных таблиц в таблицы материализованных представлений.\n\nВ режиме встраиваемой библиотеки YDB Materializer реализует все перечисленные функции, предоставляя возможность их программного вызова через методы соответствующих классов.\n\nЗависимость Maven для встраивания YDB Materializer в приложение:\n\n```xml\n        \u003cdependency\u003e\n            \u003cgroupId\u003etech.ydb.apps\u003c/groupId\u003e\n            \u003cartifactId\u003eydb-materializer\u003c/artifactId\u003e\n            \u003cversion\u003e1.13\u003c/version\u003e\n        \u003c/dependency\u003e\n```\n\n## Синтаксис языка материализованных представлений\n\nYDB Materializer использует специальный SQL-подобный язык для определения материализованных представлений и заданий их обработки (обработчиков). Этот язык основан на подмножестве SQL с особыми расширениями для поддержки пользовательских материализованных представлений YDB.\n\n### Обзор языка\n\nЯзык поддерживает два основных типа инструкций:\n1. **Определение материализованного представления** — определяет структуру и логику материализованного представления.\n2. **Определение обработчика** — определяет, как обрабатывать потоки изменений для обновления материализованного представления.\n\n### Определение материализованного представления\n\nПростое материализованное представление:\n\n```sql\nCREATE ASYNC MATERIALIZED VIEW \u003cview_name\u003e\n  [DESTINATION \u003cconnection_name\u003e]\nAS\n  SELECT \u003ccolumn_definitions\u003e\n  FROM \u003cmain_table\u003e AS \u003calias\u003e\n  [\u003cjoin_clauses\u003e]\n  [WHERE \u003cfilter_expression\u003e];\n```\n\n- Выражение `SELECT` определяет набор таблиц, используемых как источники для MV, а также связи между исходными таблицами в виде ограниченного варианта SQL‑запроса.\n- Необязательное выражение `DESTINATION` позволяет поместить таблицу MV в отдельную базу данных (см. отдельный раздел ниже).\n\nСоставное материализованное представление:\n\n```sql\nCREATE ASYNC MATERIALIZED VIEW \u003cview_name\u003e\n  [DESTINATION \u003cconnection_name\u003e]\nAS\n  (SELECT \u003ccolumn_definitions\u003e\n   FROM \u003cmain_table1\u003e AS \u003calias\u003e\n   [\u003cjoin_clauses\u003e]\n   [WHERE \u003cfilter_expression\u003e]) AS \u003cselect_alias1\u003e\nUNION ALL\n  (SELECT \u003ccolumn_definitions\u003e\n   FROM \u003cmain_table2\u003e AS \u003calias\u003e\n   [\u003cjoin_clauses\u003e]\n   [WHERE \u003cfilter_expression\u003e]) AS \u003cselect_alias2\u003e\nUNION ALL\n   ...\n   ;\n```\n\nОпределение составного материализованного представления состоит из двух или более подзапросов, соответствующих по синтаксису запросу для простого материализованного представления, и объединённых оператором `UNION ALL`. Каждый из подзапросов дополнительно должен содержать псевдоним, уникальный в рамках составного материализованного представления, и используемый для идентификации подзапроса при его обработке.\n\n#### Базы данных назначения\n\nПо умолчанию таблицы материализованных представлений создаются и обновляются в той же базе данных YDB, из которой читаются исходные данные и которая задаётся параметром подключения `ydb.url`.\n\nПри необходимости материализованное представление можно связать с **отдельной базой данных назначения**, используя выражение `DESTINATION`:\n\n```sql\nCREATE ASYNC MATERIALIZED VIEW \u003cview_name\u003e\n  DESTINATION \u003cconnection_name\u003e\nAS\n  SELECT ...\n```\n\n- **`\u003cconnection_name\u003e`** — логическое имя дополнительного подключения к YDB.\n- Исходные таблицы для представления по‑прежнему читаются из **основной** базы данных.\n- Все операции записи (`UPSERT` / `DELETE`) для материализованного представления выполняются в **базе назначения**.\n\nЧтобы использовать отдельную базу назначения, опишите дополнительное подключение в конфигурации, добавив префикс `\u003cconnection_name\u003e.` к обычным параметрам подключения к YDB, например:\n\n```xml\n\u003c!-- Основная база данных (исходные таблицы) --\u003e\n\u003centry key=\"ydb.url\"\u003egrpcs://ydb01.localdomain:2135/cluster1/testdb\u003c/entry\u003e\n\n\u003c!-- Отдельная база данных назначения для MV `pk_test/mv1` --\u003e\n\u003centry key=\"altdest1.ydb.url\"\u003egrpcs://ydb01.localdomain:2135/cluster1/testdb_mv\u003c/entry\u003e\n\u003centry key=\"altdest1.ydb.cafile\"\u003e/path/to/ca.crt\u003c/entry\u003e\n\u003centry key=\"altdest1.ydb.auth.mode\"\u003eSTATIC\u003c/entry\u003e\n\u003centry key=\"altdest1.ydb.auth.username\"\u003eroot\u003c/entry\u003e\n\u003centry key=\"altdest1.ydb.auth.password\"\u003eyour_password\u003c/entry\u003e\n```\n\nВ определении материализованного представления укажите это имя подключения:\n\n```sql\nCREATE ASYNC MATERIALIZED VIEW `pk_test/mv1`\n  DESTINATION `altdest1`\nAS\n  SELECT ...\n```\n\nЕсли предложение `DESTINATION` не указано, материализованное представление пишется в основную базу данных, настроенную через параметр `ydb.url`.\n\n#### Определение столбцов\n\nКаждый столбец в предложении SELECT может быть:\n- **Прямой ссылкой на столбец**: `table_alias.column_name AS column_alias`\n- **Вычисляемым выражением**: `#[\u003cyql_expression\u003e]# AS column_alias`\n- **Вычисляемым выражением со ссылками на колонки**: `COMPUTE ON table_alias.column_name, ... #[\u003cyql_expression\u003e]# AS column_alias`\n\n#### Условия JOIN\n\n```sql\n[INNER | LEFT] JOIN \u003ctable_name\u003e AS \u003calias\u003e\n  ON \u003cjoin_condition\u003e [AND \u003cjoin_condition\u003e]*\n```\n\nУсловия JOIN поддерживают:\n- Равенство столбцов: `table1.col1 = table2.col2`\n- Равенство констант: `table1.col1 = 'value'` или `table1.col1 = 123`\n\n#### Фильтрующие выражения\n\nПредложение WHERE поддерживает непрозрачные (для приложения) YQL-выражения, подставляемые без изменений непосредственно в формируемые запросы:\n```sql\nWHERE COMPUTE ON table_alias.column_name, ... #[\u003cyql_expression\u003e]#\n```\n\nНаличие ссылок на конкретные имена таблиц и колонки позволяет корректно формировать производные SQL-операторы с использованием непрозрачных выражений, опирающихся на конкретные колонки исходных таблиц.\n\n### Определение обработчика\n\n```sql\nCREATE ASYNC HANDLER \u003chandler_name\u003e\n  [CONSUMER \u003cconsumer_name\u003e]\n  PROCESS \u003cmaterialized_view_name\u003e,\n  [PROCESS \u003cmaterialized_view_name\u003e,]\n  INPUT \u003ctable_name\u003e CHANGEFEED \u003cchangefeed_name\u003e AS [STREAM|BATCH],\n  [INPUT \u003ctable_name\u003e CHANGEFEED \u003cchangefeed_name\u003e AS [STREAM|BATCH], ...];\n```\n\n#### Компоненты обработчика\n\n- **PROCESS**: указывает, какие материализованные представления обновляет этот обработчик.\n- **INPUT**: определяет входные таблицы и их потоки changefeed.\n  - `STREAM`: обработка отдельных изменений в режиме реального времени.\n  - `BATCH`: пакетная обработка накопленных изменений.\n- **CONSUMER**: необязательное имя потребителя для changefeed (если не указано, то используется имя обработчика).\n\n### Непрозрачные выражения\n\nЯзык поддерживает непрозрачные выражения, заключённые в разделители `#[` и `]#`. Они содержат код YQL (Yandex Query Language), который передаётся в базу данных без разбора:\n\n```sql\n-- В предложении SELECT\nSELECT #[Substring(main.c20, 3, 5)]# AS c11,\n       #[CAST(NULL AS Int32?)]# AS c12\n\n-- В предложении WHERE\nWHERE #[main.c6=7 AND (sub2.c7 IS NULL OR sub2.c7='val2'u)]#\n\n-- С предложением COMPUTE ON\nCOMPUTE ON main, sub2 #[main.c6=7 AND (sub2.c7 IS NULL OR sub2.c7='val2'u)]#\n```\n\n### Полный пример\n\n```sql\n-- Определение материализованного представления\nCREATE ASYNC MATERIALIZED VIEW `test1/mv1` AS\n  SELECT main.id AS id,\n         main.c1 AS c1,\n         main.c2 AS c2,\n         main.c3 AS c3,\n         sub1.c8 AS c8,\n         sub2.c9 AS c9,\n         sub3.c10 AS c10,\n         #[Substring(main.c20, 3, 5)]# AS c11,\n         #[CAST(NULL AS Int32?)]# AS c12\n  FROM `test1/main_table` AS main\n  INNER JOIN `test1/sub_table1` AS sub1\n    ON main.c1 = sub1.c1 AND main.c2 = sub1.c2\n  LEFT JOIN `test1/sub_table2` AS sub2\n    ON main.c3 = sub2.c3 AND 'val1'u = sub2.c4\n  INNER JOIN `test1/sub_table3` AS sub3\n    ON sub3.c5 = 58\n  WHERE #[main.c6=7 AND (sub2.c7 IS NULL OR sub2.c7='val2'u)]#;\n\n-- Определение обработчика для обработки изменений\nCREATE ASYNC HANDLER h1 CONSUMER h1_consumer\n  PROCESS `test1/mv1`,\n  INPUT `test1/main_table` CHANGEFEED cf1 AS STREAM,\n  INPUT `test1/sub_table1` CHANGEFEED cf2 AS STREAM,\n  INPUT `test1/sub_table2` CHANGEFEED cf3 AS STREAM,\n  INPUT `test1/sub_table3` CHANGEFEED cf4 AS BATCH;\n```\n\n### Особенности языка\n\n- **Нечувствительность к регистру ключевых слов**: все ключевые слова SQL не чувствительны к регистру.\n- **Идентификаторы в кавычках**: используйте обратные кавычки для идентификаторов со специальными символами: `` `table/name` ``.\n- **Строковые литералы**: строки в одинарных кавычках с необязательными суффиксами типа (`'value'u` для Utf8).\n- **Комментарии**: как однострочные комментарии (`--`), так и блочные (`/* */`).\n- **Завершение точкой с запятой**: инструкции должны заканчиваться точкой с запятой.\n\n### Поддерживаемые типы данных\n\nЯзык работает со стандартными типами данных YDB:\n- **Текстовые**: строковые данные (используйте `'value'u` для строк Utf8).\n- **Числовые**: Int32, Int64, Decimal и т. д.\n- **Временные**: Timestamp, Date и т. д.\n- **Сложные**: JsonDocument и т. д.\n\n## Синтаксис командной строки\n\n```bash\njava -jar ydb-materializer-*.jar \u003cconfig.xml\u003e \u003cMODE\u003e\n```\n\n**Параметры:**\n- `\u003cconfig.xml\u003e` — путь к файлу XML-конфигурации.\n- `\u003cMODE\u003e` — режим работы, в соответствии с приведённым ниже перечнем.\n\nПриложение поддерживает три режима работы:\n- CHECK: проверка конфигурации;\n- SQL: генерация SQL-инструкций, представляющих логику материализации;\n- STREAMS: генерация операторов для создания CDC-потоков и CDC-консьюмеров;\n- LOCAL: фактическая синхронизация MV в режиме отдельного приложения;\n- JOB: фактическая синхронизация MV под управлением встроенного менеджера распределённых заданий.\n\n### Режимы работы\n\n#### Режим CHECK\nПроверяет определения материализованных представлений и сообщает о проблемах:\n```bash\njava -jar ydb-materializer-*.jar config.xml CHECK\n```\n\n#### Режим SQL\nГенерирует и выводит SQL-инструкции для материализованных представлений:\n```bash\njava -jar ydb-materializer-*.jar config.xml SQL\n```\n\n#### Режим STREAMS\nГенерирует и (опционально) применяет к рабочей базе данных SQL-инструкции для создания потоков CDC:\n```bash\njava -jar ydb-materializer-*.jar config.xml STREAMS\n```\n\nСоздание потоков CDC осуществляется при наличии в конфигурации установленного параметра `job.streams.create=true`.\n\n#### Режим LOCAL\nЗапускает локальную одноузловую службу обработки материализованных представлений:\n```bash\njava -jar ydb-materializer-*.jar config.xml LOCAL\n```\n\n#### Режим JOB\nЗапускает распределенную службу обработки материализованных представлений:\n```bash\njava -jar ydb-materializer-*.jar config.xml JOB\n```\n\n## Файл конфигурации\n\nФайл конфигурации — это файл свойств XML, в котором определены параметры подключения и настройки задания. Вот пример конфигурации:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\"\u003e\n\u003cproperties\u003e\n\u003ccomment\u003eПример конфигурации YDB Materializer\u003c/comment\u003e\n\n\u003c!-- *** Параметры подключения *** --\u003e\n\u003centry key=\"ydb.url\"\u003egrpcs://ydb01.localdomain:2135/cluster1/testdb\u003c/entry\u003e\n\u003centry key=\"ydb.cafile\"\u003e/path/to/ca.crt\u003c/entry\u003e\n\u003centry key=\"ydb.poolSize\"\u003e1000\u003c/entry\u003e\n\u003centry key=\"ydb.preferLocalDc\"\u003efalse\u003c/entry\u003e\n\n\u003c!-- Режим аутентификации: NONE, ENV, STATIC, METADATA, SAKEY --\u003e\n\u003centry key=\"ydb.auth.mode\"\u003eSTATIC\u003c/entry\u003e\n\u003centry key=\"ydb.auth.username\"\u003eroot\u003c/entry\u003e\n\u003centry key=\"ydb.auth.password\"\u003eyour_password\u003c/entry\u003e\n\n\u003c!-- Режим ввода: FILE или TABLE --\u003e\n\u003centry key=\"job.input.mode\"\u003eFILE\u003c/entry\u003e\n\u003centry key=\"job.input.file\"\u003eexample-job1.sql\u003c/entry\u003e\n\u003centry key=\"job.input.table\"\u003emv/statements\u003c/entry\u003e\n\u003centry key=\"job.streams.create\"\u003efalse\u003c/entry\u003e\n\n\u003c!-- Конфигурация обработчика --\u003e\n\u003centry key=\"job.handlers\"\u003eh1,h2,h3\u003c/entry\u003e\n\u003centry key=\"job.scan.rate\"\u003e10000\u003c/entry\u003e\n\u003centry key=\"job.scan.table\"\u003emv/scans_state\u003c/entry\u003e\n\u003centry key=\"job.coordination.path\"\u003emv/coordination\u003c/entry\u003e\n\u003centry key=\"job.coordination.timeout\"\u003e10\u003c/entry\u003e\n\n\u003c!-- Настройки сканера справочников --\u003e\n\u003centry key=\"job.dict.consumer\"\u003edictionary\u003c/entry\u003e\n\u003centry key=\"job.dict.hist.table\"\u003emv/dict_hist\u003c/entry\u003e\n\u003centry key=\"job.dict.scan.seconds\"\u003e28800\u003c/entry\u003e\n\n\u003c!-- Настройка производительности --\u003e\n\u003centry key=\"job.apply.partitioning\"\u003eHASH\u003c/entry\u003e\n\u003centry key=\"job.cdc.threads\"\u003e4\u003c/entry\u003e\n\u003centry key=\"job.apply.threads\"\u003e4\u003c/entry\u003e\n\u003centry key=\"job.apply.queue\"\u003e10000\u003c/entry\u003e\n\u003centry key=\"job.batch.select\"\u003e1000\u003c/entry\u003e\n\u003centry key=\"job.batch.upsert\"\u003e500\u003c/entry\u003e\n\u003centry key=\"job.max.row.changes\"\u003e100000\u003c/entry\u003e\n\u003centry key=\"job.query.seconds\"\u003e30\u003c/entry\u003e\n\n\u003c!-- Настройки средств управления --\u003e\n\u003centry key=\"mv.jobs.table\"\u003emv/jobs\u003c/entry\u003e\n\u003centry key=\"mv.scans.table\"\u003emv/job_scans\u003c/entry\u003e\n\u003centry key=\"mv.runners.table\"\u003emv/runners\u003c/entry\u003e\n\u003centry key=\"mv.runner.jobs.table\"\u003emv/runner_jobs\u003c/entry\u003e\n\u003centry key=\"mv.commands.table\"\u003emv/commands\u003c/entry\u003e\n\u003centry key=\"mv.scan.period.ms\"\u003e5000\u003c/entry\u003e\n\u003centry key=\"mv.report.period.ms\"\u003e10000\u003c/entry\u003e\n\u003centry key=\"mv.runner.timeout.ms\"\u003e30000\u003c/entry\u003e\n\u003centry key=\"mv.coord.startup.ms\"\u003e90000\u003c/entry\u003e\n\u003centry key=\"mv.coord.runners.count\"\u003e1\u003c/entry\u003e\n\n\u003c/properties\u003e\n```\n\n### Справочная информация о параметрах конфигурации\n\n#### Подключение к базе данных\n- `ydb.url` — строка подключения к YDB (обязательно).\n- `ydb.cafile` — путь к файлу TLS-сертификата (опционально).\n- `ydb.poolSize` — размер пула подключений (по умолчанию: 2 × количество ядер ЦП).\n- `ydb.preferLocalDc` — предпочтительный локальный центр обработки данных (по умолчанию: false).\n\n#### Аутентификация\n- `ydb.auth.mode` — режим аутентификации:\n  - `NONE` — без аутентификации.\n  - `ENV` — переменные окружения.\n  - `STATIC` — имя пользователя и пароль.\n  - `METADATA` — метаданные виртуальной машины.\n  - `SAKEY` — файл ключа сервисного аккаунта.\n- `ydb.auth.username` — имя пользователя (для режима STATIC).\n- `ydb.auth.password` — пароль (для режима STATIC).\n- `ydb.auth.sakey` — путь к файлу ключа сервисного аккаунта (для режима SAKEY).\n\n#### Настройка задания\n- `job.input.mode` — источник ввода: `FILE` или `TABLE`.\n- `job.input.file` — путь к SQL-файлу (для режима FILE).\n- `job.input.table` — имя таблицы для инструкций (для режима TABLE).\n- `job.streams.create` - если установлено в `true`, создать недостающие объекты потоков CDC.\n- `job.handlers` — список имён обработчиков для активации, разделённый запятыми.\n- `job.scan.table` — имя таблицы для ведения позиций сканирования\n- `job.dict.hist.table` - имя таблицы для ведения истории изменения справочников\n- `job.coordination.path` — путь к узлу службы координации\n- `job.coordination.timeout` - таймаут распределенной блокировки, секунд\n\n#### Настройки сканера справочников\n- `job.dict.consumer` - имя консьюмера для сбора информации об изменениях справочников\n- `job.dict.hist.table` - альтернативное имя таблицы `mv/dict_hist`\n- `job.dict.scan.seconds` - период между проверками изменений справочников\n\n#### Настройка производительности\n- `job.apply.partitioning` - HASH (по умолчанию) или RANGE стратегия партиционирования задач\n- `job.cdc.threads` — количество потоков чтения CDC\n- `job.apply.threads` — количество рабочих потоков apply\n- `job.apply.queue` — максимальное количество элементов в очереди apply на поток\n- `job.batch.select` — размер пакета для операций SELECT\n- `job.batch.upsert` — размер пакета для операций UPSERT или DELETE\n- `job.max.row.changes` — максимальное количество изменений по отдельной таблице, обрабатываемых за одну итерацию\n- `job.query.seconds` — максимальное время выполнения запроса на выборку, вставку или удаление данных, секунд\n- `job.scan.rate` - Ограничение скорости операций сканирования, строк в секунду\n\n#### Настройки системы управления заданиями\n- `mv.jobs.table` - Альтернативное имя таблицы `mv/jobs`\n- `mv.scans.table` - Альтернативное имя таблицы `mv/job_scans`\n- `mv.runners.table` - Альтернативное имя таблицы `mv/runners`\n- `mv.runner.jobs.table` - Альтернативное имя таблицы `mv/runner_jobs`\n- `mv.commands.table` - Альтернативное имя таблицы `mv/commands`\n- `mv.scan.period.ms` - Период сканирования Исполнителя и Координатора, миллисекунды\n- `mv.report.period.ms` - Период обновления состояния Исполнителя, миллисекунды\n- `mv.runner.timeout.ms` - Таймаут отсутствия обновлений Координатора и Исполнителя, миллисекунды\n- `mv.coord.startup.ms` - Пауза между стартом Координатора и началом распределения заданий, миллисекунды\n- `mv.coord.runners.count` - Минимальное количество Исполнителей для распределения заданий\n\n#### Метрики\n- `metrics.enabled` - Включить endpoint метрик Prometheus (по умолчанию: false)\n- `metrics.port` - Порт endpoint метрик Prometheus (по умолчанию: 7311)\n- `metrics.host` - Адрес/интерфейс для endpoint метрик (по умолчанию: 0.0.0.0)\n\nГотовый стенд Prometheus + Grafana описан в `monitoring/README.md`.\n\n### Собираемые метрики\n\nПри включённых метриках (`metrics.enabled=true`) приложение отдаёт следующие метрики Prometheus. Все имена метрик имеют префикс `ydbmv_`.\n\n#### Метрики обработчика (задания)\n\nОписание метрик см. в таблице ниже.\n\n| Метрика | Тип | Описание |\n|--------|-----|----------|\n| `ydbmv_handler_active` | Gauge | 1, если обработчик (задание) активен, 0 иначе, для каждого обработчика |\n| `ydbmv_handler_locked` | Gauge | 1, если активный обработчик не может продолжать работу из-за проблемы обработки, 0 иначе |\n| `ydbmv_handler_threads` | Gauge | Количество рабочих потоков обработчика |\n| `ydbmv_handler_queue_size` | Gauge | Текущий размер входной очереди обработчика |\n| `ydbmv_handler_queue_limit` | Gauge | Максимально допустимый размер входной очереди |\n| `ydbmv_handler_queue_wait` | Counter | Количество ожиданий на вставке данных в очередь из-за её переполнения |\n\nОписание меток приведено ниже.\n\n| Метка | Описание |\n|-------|----------|\n| `handler` | Имя обработчика |\n\n#### Метрики CDC\n\nОписание метрик см. в таблице ниже.\n\n| Метрика | Тип | Описание |\n|--------|-----|----------|\n| `ydbmv_cdc_records_read` | Counter | Количество записей CDC, прочитанных из топиков |\n| `ydbmv_cdc_records_submitted` | Counter | Количество разобранных записей CDC, переданных в обработку |\n| `ydbmv_cdc_parse_errors` | Counter | Количество ошибок разбора сообщений CDC |\n| `ydbmv_cdc_parse_seconds` | Histogram | Время разбора сообщений CDC |\n| `ydbmv_cdc_submit_seconds` | Histogram | Время постановки сообщений CDC в очередь, включая ожидание освобождения места в очереди |\n\nОписание меток приведено ниже.\n\n| Метка | Описание |\n|-------|----------|\n| `handler` | Имя обработчика |\n| `consumer` | Имя консьюмера CDC |\n| `topic` | Полный путь топика CDC |\n\n#### Метрики операций сканирования\n\nОписание метрик см. в таблице ниже.\n\n| Метрика | Тип | Описание |\n|--------|-----|----------|\n| `ydbmv_scan_records_submitted` | Counter | Количество записей, переданных на обработку при начальном/фоновом сканировании |\n| `ydbmv_scan_delay_millis` | Counter | Суммарная задержка сканирования в миллисекундах из-за ограничителя скорости |\n\nОписание меток приведено ниже.\n\n| Метка | Описание |\n|-------|----------|\n| `handler` | Имя обработчика |\n| `target` | Имя обрабатываемого MV |\n| `alias` | Имя компонента MV для MV в стиле `UNION ALL` |\n\n#### Метрики обработки\n\nОписание метрик см. в таблице ниже.\n\n| Метрика | Тип | Описание |\n|--------|-----|----------|\n| `ydbmv_processing_records` | Counter | Успешно обработано записей по действию |\n| `ydbmv_processing_errors` | Counter | Ошибки обработки по действию |\n| `ydbmv_processing_seconds` | Histogram | Полное время обработки по действию |\n| `ydbmv_sql_seconds` | Histogram | Время выполнения SQL по действию |\n\nОписание меток приведено ниже.\n\n| Метка | Описание |\n|-------|----------|\n| `type` | Этап обработки (например, `filter`, `grabKeys`, `transform`, `sync`) |\n| `handler` | Имя обработчика |\n| `target` | Имя обрабатываемого MV |\n| `alias` | Имя компонента MV для MV в стиле `UNION ALL` |\n| `source` | Имя входной таблицы для этапа обработки |\n| `action` | Имя действия (`select`, `upsert`, `delete` для времени SQL и `all` для остальных метрик) |\n\n#### Метрики JVM\n\nПри использовании встроенного сервера Prometheus дополнительно автоматически регистрируются стандартные метрики JVM (память, GC, потоки и т.д.).\n\n## Управление распределёнными задачами (режим JOB)\n\nРежим JOB предоставляет возможности для управления распределёнными задачами, позволяя управлять задачами обработки материализованных представлений на нескольких экземплярах. Этот режим запускается с помощью команды:\n\n```bash\njava -jar ydb-materializer.jar config.xml JOB\n```\n\n### Обзор архитектуры\n\nСистема управления распределёнными задачами состоит из двух основных компонентов:\n\n- MvRunner — выполняет задачи локально на каждом экземпляре.\n- MvCoordinator — управляет распределением и координацией задач между исполнителями.\n\nКаждая задача — это работающий экземпляр «обработчика», определённого в конфигурации.\n\nПри запуске задачи приложение заново считывает и проверяет конфигурацию, после чего использует её в конкретной задаче.\n\n### Управляющие таблицы\n\nСистема использует несколько таблиц YDB для управления распределёнными операциями:\n\n#### Таблицы конфигурации\n\n**`mv/jobs`**  — определения задач и желаемое состояние:\n\n```sql\nCREATE TABLE `mv/jobs` (\n    job_name Text NOT NULL,           -- Имя обработчика\n    job_settings JsonDocument,        -- Конфигурация обработчика\n    should_run Bool,                  -- Должна ли задача выполняться\n    PRIMARY KEY(job_name)\n);\n```\n\n**`mv/job_scans`** - запросы на сканирование определённых целей:\n\n```sql\nCREATE TABLE `mv/job_scans` (\n    job_name Text NOT NULL,           -- Имя обработчика\n    target_name Text NOT NULL,        -- Имя целевой таблицы\n    scan_settings JsonDocument,       -- Конфигурация сканирования\n    requested_at Timestamp,           -- Когда был запрошен скан\n    accepted_at Timestamp,            -- Когда скан был принят\n    runner_id Text,                   -- Назначенный идентификатор исполнителя\n    command_no Uint64,                -- Номер команды\n    PRIMARY KEY(job_name, target_name)\n);\n```\n\n#### Рабочие таблицы\n\n**`mv/runners`** - активные экземпляры исполнителей:\n\n```sql\nCREATE TABLE `mv/runners` (\n    runner_id Text NOT NULL,          -- Уникальный идентификатор исполнителя\n    runner_identity Text,             -- Информация о хосте, PID, времени запуска\n    updated_at Timestamp,             -- Последнее обновление статуса\n    PRIMARY KEY(runner_id)\n);\n```\n\n**`mv/runner_jobs`** - задачи, выполняемые в данный момент каждым исполнителем:\n\n```sql\nCREATE TABLE `mv/runner_jobs` (\n    runner_id Text NOT NULL,          -- Идентификатор исполнителя\n    job_name Text NOT NULL,           -- Имя задачи\n    job_settings JsonDocument,        -- Конфигурация задачи\n    started_at Timestamp,             -- Когда задача началась\n    INDEX ix_job_name GLOBAL SYNC ON (job_name),\n    PRIMARY KEY(runner_id, job_name)\n);\n```\n\n**`mv/commands`** - очередь команд для исполнителей:\n\n```sql\nCREATE TABLE `mv/commands` (\n    runner_id Text NOT NULL,          -- Целевой исполнитель\n    command_no Uint64 NOT NULL,       -- Номер последовательности команды\n    created_at Timestamp,             -- Время создания команды\n    command_type Text,                -- START/STOP/SCAN/NOSCAN\n    job_name Text,                    -- Имя целевой задачи\n    target_name Text,                 -- Целевая таблица (для сканирования)\n    job_settings JsonDocument,        -- Конфигурация задачи\n    command_status Text,              -- CREATED/TAKEN/SUCCESS/ERROR\n    command_diag Text,                -- Диагностика ошибок\n    INDEX ix_command_no GLOBAL SYNC ON (command_no),\n    INDEX ix_command_status GLOBAL SYNC ON (command_status, runner_id),\n    PRIMARY KEY(runner_id, command_no)\n);\n```\n\n### Операции управления задачами\n\n#### Добавление задач\n\nЧтобы добавить новую задачу, вставьте запись в таблицу `mv/jobs`:\n\n```sql\nINSERT INTO `mv/jobs` (job_name, job_settings, should_run)\nVALUES ('my_handler', NULL, true);\n```\n\nКоординатор автоматически обнаружит новую задачу и назначит её доступному исполнителю.\n\nПараметры в поле `job_settings` можно не указывать (будут использованы параметры по умолчанию, загружаемые из глобальных настроек) или указать в виде JSON-документа следующего формата:\n\n```json\n{ # в комментарии указана соответствующая глобальная настройка\n    \"cdcReaderThreads\": 4,                # job.cdc.threads\n    \"applyThreads\": 4,                    # job.apply.threads\n    \"applyQueueSize\": 10000,              # job.apply.queue\n    \"selectBatchSize\": 1000,              # job.batch.select\n    \"upsertBatchSize\": 500,               # job.batch.upsert\n    \"dictionaryScanSeconds\": 28800,       # job.dict.scan.seconds\n    \"queryTimeoutSeconds\": 30             # job.query.seconds\n}\n```\n\nВ примере выше показаны параметры по умолчанию для обычных задач. Для специальной задачи сканера словаря можно указать следующие параметры:\n\n```json\n{\n    \"upsertBatchSize\": 500,               # job.batch.upsert\n    \"cdcReaderThreads\": 4,                # job.cdc.threads\n    \"rowsPerSecondLimit\": 10000,          # job.scan.rate\n    \"maxChangeRowsScanned\": 100000        # job.max.row.changes\n}\n```\n\n#### Удаление задач\n\nЧтобы остановить и удалить задачу:\n\n```sql\nUPDATE `mv/jobs` SET should_run = false WHERE job_name = 'my_handler';\n-- или\nDELETE FROM `mv/jobs` WHERE job_name = 'my_handler';\n```\n\n#### Запрос на сканирование\n\nЧтобы запросить сканирование определённой целевой таблицы:\n\n```sql\nINSERT INTO `mv/job_scans` (job_name, target_name, scan_settings, requested_at)\nVALUES ('my_handler', 'target_table', '{\"rowsPerSecondLimit\": 5000}', CurrentUtcTimestamp());\n```\n\n### Мониторинг операций\n\n#### Проверка выполняемых задач\n\n```sql\nSELECT rj.runner_id, rj.job_name, rj.started_at, r.runner_identity\nFROM `mv/runner_jobs` rj\nJOIN `mv/runners` r ON rj.runner_id = r.runner_id;\n```\n\n#### Проверка состояния задачи\n\n```sql\nSELECT j.job_name, j.should_run,\n       CASE WHEN rj.job_name IS NOT NULL THEN 'RUNNING'u ELSE 'STOPPED'u END as status\nFROM `mv/jobs` j\nLEFT JOIN `mv/runner_jobs` rj ON j.job_name = rj.job_name;\n```\n\n#### Проверка очереди команд:\n\n```sql\nSELECT runner_id, command_no, command_type, job_name, command_status, created_at\nFROM `mv/commands`\nWHERE command_status IN ('CREATED'u, 'TAKEN'u)\nORDER BY created_at;\n```\n\n#### Проверка состояния исполнителя\n\n```sql\nSELECT runner_id, runner_identity, updated_at\nFROM `mv/runners`\nORDER BY updated_at DESC;\n```\n\n### Типы команд\n\nСистема поддерживает четыре типа команд:\n\n- `START` — запустить задачу на исполнителе.\n- `STOP` — остановить задачу на исполнителе.\n- `SCAN` — начать сканирование определённой целевой таблицы.\n- `NOSCAN` — остановить уже запущенное сканирование для определённой целевой таблицы.\n\n### Имена задач\n\nИмя задачи для обычных задач соответствует имени обработчика. Есть два специальных имени задач:\n\n- `ydbmv$dictionary` — сканер словаря (управляется вручную)\n- `ydbmv$coordinator` — задача координатора (управляется автоматически)\n\nЭти специальные имена нельзя использовать для обычных обработчиков (на самом деле имя обработчика не может начинаться с префикса «ydbmv»).\n\n### Отказоустойчивость\n\nСистема обеспечивает автоматическую отказоустойчивость:\n\n- Обнаружение сбоя исполнителя — исполнители периодически сообщают о своём состоянии; неактивные исполнители автоматически удаляются.\n- Перераспределение задач — при сбое исполнителей их задачи автоматически переназначаются доступным исполнителям.\n- Повтор команд — неудачные команды остаются в очереди для повторной попытки.\n- Выбор лидера — одновременно активен только один экземпляр координатора.\n\n### Развёртывание\n\n1. **Создание управляющих таблиц** — используйте предоставленный скрипт `scripts/example-tables.sql`. Имена таблиц можно настроить по мере необходимости.\n1. **Развёртывание исполнителей** — запустите несколько экземпляров в режиме JOB.\n1. **Настройка задач** — вставьте определения задач в таблицу `mv/jobs`. Добавьте определения сканирования в таблицу mv/job_scans.\n1. **Мониторинг операций** — используйте запросы для мониторинга, перечисленные выше.\n\nСистема автоматически распределит задачи между доступными исполнителями и поддержит желаемое состояние.\n\n## Настройка производительности\n\nНа практике общая производительность в первую очередь зависит от сложности запросов, определяющих материализованное представление, а не только от количества рабочих потоков. Сложные JOIN-операции, большие промежуточные результирующие наборы, сложные условия `WHERE` (включая непрозрачные YQL-выражения `#[ ... ]#`), а также наличие нескольких веток `UNION ALL` напрямую увеличивают стоимость каждого оператора `SELECT` / `UPSERT` / `DELETE`, выполняемого YDB Materializer.\n\nПараметры задания обработчика, перечисленные ниже, контролируют, насколько агрессивно YDB Materializer читает потоки изменений CDC и применяет обновления как для обработчиков стиля **STREAM**, так и для обработчиков стиля **BATCH** (включая операции сканирования и обновления MV на основе изменений справочников):\n\n- **`job.cdc.threads` / `cdcReaderThreads`**\n  - Задаёт количество параллельных потоков, читающих из CDC изменения.\n  - **Режим STREAM**: больше потоков увеличивает скорость чтения новых событий из YDB, что может улучшить сквозную задержку, но также увеличивает нагрузку на нижестоящие рабочие потоки apply и на базу данных в целом.\n  - **Режим BATCH**: больше потоков ускоряют чтение и сохранение накопленных изменений в промежуточную таблицу, но не влияют напрямую на сквозную задержку (которая в основном зависит от настройки `job.dict.scan.seconds`). Обратите внимание, что используется настройка, определённая для задачи сканера словаря (или глобальная настройка, если конкретная настройка для сканера словаря не определена), а не настройка, сконфигурированная для соответствующей обычной задачи.\n  - **Сканирования**: эта настройка не влияет на сканирования, в том числе на работу сканирований при обнаружении изменений справочников.\n\n- **`job.apply.threads` / `applyThreads`**\n  - Задаёт количество рабочих потоков, выполняющих операторы `SELECT` / `UPSERT` / `DELETE` для обновления таблиц MV. Обрабатываемые ключи отправляются конкретному рабочему потоку в зависимости от значения ключа, поэтому конфликт по одному ключу никогда не происходит.\n  - **Режим STREAM**: больше потоков позволяют обрабатывать больше пакетов изменений параллельно, улучшая пропускную способность; слишком много потоков может вызвать конкуренцию за ресурсы и перегрузить кластер YDB.\n  - **Режим BATCH**: то же, что и для режима STREAM. Использование значительного количества потоков в сочетании с большим количеством строк, затронутых изменениями справочников, может привести к большому всплеску использования ресурсов при обработке накопленных изменений справочников.\n  - **Сканирования**: то же, что и для режима STREAM. Общее время, необходимое для выполнения сканирования, зависит от объёма работы (размера MV) и скорости сканирования, которая ограничивается как настройкой `job.scan.rate`, так и производительностью процесса apply, а последняя зависит от настроенного количества задействованных потоков.\n\n- **`job.apply.queue` / `applyQueueSize`**\n  - Максимальное количество поставленных в очередь изменений, передаваемых на обработку (размер буфера между читателями CDC и рабочими потоками apply, а также между рабочими потоками apply, выполняющими операцию выборки ключей, и рабочими потоками apply, выполняющими обновление MV). Каждая задача использует счётчик (один на задачу) для подсчёта общего количества элементов в очереди, и использует его значение для приостановки чтения дополнительных входных данных из топиков CDC, когда очередь становится слишком большой. Это ограничивает оперативную память, используемую промежуточными данными внутри экземпляра YDB Materializer, выполняющего конкретную задачу.\n  - **Режим STREAM**: большая очередь сглаживает кратковременные всплески входящего трафика CDC; если она слишком мала, читатели CDC ограничиваются чаще, и сквозная задержка увеличивается. Если очередь слишком велика, задание может накапливать много ожидающих изменений в памяти, увеличивая использование памяти и время, необходимое для освобождения очереди.\n  - **Режим BATCH и сканирования**: определяет, сколько подготовленных данных может ждать выполнения. Большие очереди помогают держать рабочие потоки apply занятыми, но также увеличивают использование памяти.\n\n- **`job.batch.select` / `selectBatchSize`**\n  - Ограничивает, сколько исходных строк читается в одном операторе `SELECT` при сборе данных для пакета изменений.\n  - **Режим STREAM**: небольшие значения помогают группировать операции чтения и при этом сохранять их задержки на низком уровне; увеличение этого значения может уменьшить накладные расходы SQL на строку, но может повысить задержку для отдельных изменений, поскольку большие пакеты дольше обрабатываются.\n  - **Режим BATCH и сканирования**: напрямую влияет на размер операций чтения во время полной или частичной повторной синхронизации. Большие пакеты улучшают пропускную способность (меньше запросов, лучше утилизация сетевых взаимодействий), но создают более тяжёлые запросы для YDB.\n\n- **`job.batch.upsert` / `upsertBatchSize`**\n  - Контролирует, сколько строк записывается в одной операции `UPSERT` или `DELETE` в целевые таблицы MV.\n  - **Режим STREAM**: небольшие значения помогают эффективно группировать небольшие обновления, сохраняя приемлемую задержку записи; очень маленькие значения увеличивают накладные расходы на строку; очень большие значения могут вызвать длительно выполняющиеся операторы записи, которые с большей вероятностью превысят таймауты или увеличат общую задержку обновлений MV.\n  - **Режим BATCH и сканирования**: большие пакеты значительно увеличивают пропускную способность записи во время операций массового обновления, но также усиливают влияние каждого отдельного оператора на потребление ресурсов (блокировки, использование ресурсов и потенциальные повторные попытки). Рекомендуется постепенно увеличивать это значение и отслеживать задержку YDB, частоту ошибок и использование ресурсов.\n\n- **`job.query.seconds` / `queryTimeoutSeconds`**\n  - Максимально допустимое время для выполнения запроса. При достижении таймаута возвращается ошибка `CLIENT_DEADLINE_EXCEEDED`, и запрос перезапускается. Этот параметр является защитой от зависших запросов, которые могли бы замедлить общую обработку, если бы им было разрешено выполняться произвольное количество времени. Значение должно быть выбрано таким образом, чтобы позволить выполнение всех используемых потенциально сложных запросов, но при этом ограничить максимальное время выполнения в худшем случае, например, при выполнении на перегруженном узле YDB.\n\n- **`job.dict.scan.seconds` / `dictionaryScanSeconds`**\n  - Интервал между проверками изменений словаря, потенциально влияющих на MV в рамках конкретной задачи.\n  - **Режим STREAM и сканирования**: не влияет.\n  - **Режим BATCH**: больше времени между проверками означает более редкие проверки изменений, что позволяет этим изменениям накапливаться. Накопление большего количества изменений позволяет обработать эти изменения в одном сканировании вместо запуска нескольких сканирований для каждой меньшей порции изменений. Связанная настройка `job.max.row.changes` ограничивает общее количество изменений, разрешённых к обработке в одном пакете, что помогает гарантировать, что слишком много обновлений словаря не переполнят память текущего экземпляра YDB Materializer.\n\nПри настройке этих параметров начните со значений по умолчанию, наблюдайте за метриками YDB (задержка, пропускная способность, CPU, память, таймауты запросов), затем настраивайте один параметр за раз. Для большинства рабочих нагрузок безопаснее сохранять размеры пакетов и количество потоков умеренными для обработчиков STREAM (предпочитая предсказуемую задержку), и использовать более агрессивные значения только для запланированных операций BATCH или сканирований, где допустима более высокая кратковременная нагрузка.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fydb-platform%2Fydb-materializer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fydb-platform%2Fydb-materializer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fydb-platform%2Fydb-materializer/lists"}