Resource groups#

Resource groups накладывают ограничения на использование ресурсов и могут применять политики очередей для запросов, выполняющихся внутри них, либо распределять ресурсы между подгруппами. Запрос принадлежит одной resource group и потребляет ресурсы из этой группы (и её родительских групп). За исключением ограничения на количество запросов в очереди, когда resource group исчерпывает ресурс, это не приводит к падению выполняющихся запросов; вместо этого новые запросы помещаются в очередь. Resource group может либо иметь подгруппы, либо принимать запросы, но не может делать и то, и другое одновременно.

Resource groups и связанные с ними правила выбора (selection rules) настраиваются через менеджер, который является подключаемым (pluggable).

Вы можете использовать файловый или основанный на базе данных менеджер resource group:

  • Добавьте файл etc/resource-groups.properties

  • Установите свойство resource-groups.configuration-manager в file или db

  • Добавьте дополнительные параметры конфигурации для выбранного менеджера.

File resource group manager#

Файловый менеджер resource group читает JSON конфигурационный файл, указанный в resource-groups.config-file:

resource-groups.configuration-manager=file
resource-groups.config-file=etc/resource-groups.json

Путь к JSON файлу может быть абсолютным или относительным относительно директории данных Trino. JSON файл должен присутствовать только на coordinator.

Database resource group manager#

Менеджер resource group на базе базы данных загружает конфигурацию из реляционной базы данных. Поддерживаемые базы данных: MySQL, PostgreSQL и Oracle.

resource-groups.configuration-manager=db
resource-groups.config-db-url=jdbc:mysql://localhost:3306/resource_groups
resource-groups.config-db-user=username
resource-groups.config-db-password=password

Конфигурация resource group должна быть заполнена через таблицы resource_groups_global_properties, resource_groups и selectors. Если какие-либо из этих таблиц отсутствуют при запуске Trino, они будут созданы автоматически.

Правила в таблице selectors обрабатываются в порядке убывания значений в поле priority.

Таблица resource_groups также содержит поле environment, которое сопоставляется со значением свойства node.environment в Node properties (Свойства узла). Это позволяет хранить конфигурации resource group для разных кластеров Trino в одной базе данных.

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

Свойства Database resource group manager#

Имя свойства

Описание

Значение по умолчанию

resource-groups.config-db-url

URL базы данных для загрузки конфигурации.

none

resource-groups.config-db-user

Пользователь базы данных для подключения.

none

resource-groups.config-db-password

Пароль пользователя базы данных.

none

resource-groups.max-refresh-interval

Максимальный период времени, в течение которого кластер продолжает принимать запросы после ошибок обновления, из-за которых конфигурация устаревает.

1h

resource-groups.refresh-interval

Как часто кластер перечитывает конфигурацию из базы данных.

1s

resource-groups.exact-match-selector-enabled

Включение этого флага позволяет использовать дополнительную таблицу exact_match_source_selectors для настройки правил выбора resource group на основе точного совпадения имени source, environment и типа запроса. По умолчанию правила загружаются только из таблицы selectors с использованием regex-фильтрации для source и других параметров.

false

Свойства resource group#

  • name (обязательно): имя группы. Может быть шаблоном (см. ниже).

  • maxQueued (обязательно): максимальное количество запросов в очереди. При достижении этого лимита новые запросы отклоняются.

  • softConcurrencyLimit (необязательно): количество одновременно выполняющихся запросов, после которого новые запросы будут запускаться только если все соседние resource groups ниже своих soft-лимитов недоступны, либо если все доступные группы превышают свои soft-лимиты.

  • hardConcurrencyLimit (обязательно): максимальное количество выполняющихся запросов.

  • softMemoryLimit (необязательно): максимальный объём распределённой памяти, который может использовать эта группа, прежде чем новые запросы начнут помещаться в очередь. Может задаваться как абсолютное значение (например, 1GB) или как процент (например, 10%) от общей памяти кластера.

  • softCpuLimit (необязательно): максимальное количество CPU-времени, которое эта группа может использовать за период (см. cpuQuotaPeriod), прежде чем будет применён штраф к максимальному количеству выполняющихся запросов. Также должен быть задан hardCpuLimit.

  • hardCpuLimit (необязательно): максимальное количество CPU-времени, которое эта группа может использовать за период.

  • hardPhysicalDataScanLimit (необязательно): максимальный объём данных, который эта группа может сканировать за период, прежде чем новые запросы начнут помещаться в очередь. Должен задаваться как абсолютное значение (например, 1GB).

  • schedulingPolicy (необязательно): определяет, как выбираются запросы из очереди для выполнения и как подгруппы получают право запускать свои запросы. Может принимать одно из следующих значений:

    • fair (по умолчанию): запросы из очереди обрабатываются по принципу FIFO, а подгруппы по очереди запускают новые запросы, если у них есть очередь.

    • weighted_fair: подгруппы выбираются на основе их schedulingWeight и количества уже выполняющихся запросов. Ожидаемая доля выполняющихся запросов для подгруппы вычисляется на основе весов всех доступных подгрупп. Подгруппа с наименьшей относительной загрузкой запускает следующий запрос.

    • weighted: запросы из очереди выбираются стохастически пропорционально их приоритету, заданному через query_priority session property. Подгруппы выбираются для запуска новых запросов пропорционально их schedulingWeight.

    • query_priority: все подгруппы также должны быть настроены с query_priority. Запросы из очереди выбираются строго по их приоритету.

  • schedulingWeight (необязательно): вес подгруппы, используемый в политиках weighted и weighted_fair. По умолчанию 1. См. Пример scheduling weight.

  • jmxExport (необязательно): если true, статистика группы экспортируется в JMX для мониторинга. По умолчанию false.

  • subGroups (необязательно): список подгрупп.

Пример scheduling weight#

Scheduling weight — это способ назначения приоритета ресурсу. Подгруппы с более высоким значением scheduling weight получают более высокий приоритет. Например, чтобы обеспечить своевременное выполнение запросов пайплайнов, задайте для них более высокий вес, чем для adhoc-запросов.

В следующем примере запросы пайплайнов имеют вес 350, что выше, чем у adhoc-запросов с весом 150. Это означает, что примерно 70% (350 из 500) запросов поступают из подгруппы pipeline, и 30% (150 из 500) — из подгруппы adhoc в заданный период времени. В качестве альтернативы, если задать для каждой подгруппы значение 1, нагрузка будет распределяться равномерно — по 50% на каждую подгруппу.

{
  {
    "name": "pipeline",
    "schedulingWeight": 350,
  },
  {
    "name": "adhoc",
    "schedulingWeight": 150
  }
}

Правила селекторов (Selector rules)#

Правила сопоставления используют возможности регулярных выражений Java. Java реализует регулярные выражения через пакет java.util.regex. Подробнее см. документацию Java.

  • user (необязательно): Java regex для сопоставления с именем пользователя.

  • originalUser (необязательно): Java regex для сопоставления с исходным именем пользователя, т.е. до изменений session user. Например, если пользователь “foo” выполняет SET SESSION AUTHORIZATION 'bar', то originalUser — это “foo”, а user — “bar”.

  • authenticatedUser (необязательно): Java regex для сопоставления с аутентифицированным пользователем, который всегда соответствует пользователю, прошедшему аутентификацию, независимо от изменений session user.

  • userGroup (необязательно): Java regex для сопоставления с каждой группой, к которой принадлежит пользователь.

  • source (необязательно): Java regex для сопоставления со строкой источника.

  • queryText (необязательно): regex для сопоставления со строкой SQL-запроса.

  • queryType (необязательно): строка для сопоставления с типом отправленного запроса:

    • SELECT: запросы SELECT.

    • EXPLAIN: запросы EXPLAIN, но не EXPLAIN ANALYZE.

    • DESCRIBE: запросы DESCRIBE, DESCRIBE INPUT, DESCRIBE OUTPUT, а также SHOW-запросы, такие как SHOW CATALOGS, SHOW SCHEMAS и SHOW TABLES.

    • INSERT: запросы INSERT, CREATE TABLE AS и REFRESH MATERIALIZED VIEW.

    • UPDATE: запросы UPDATE.

    • MERGE: запросы MERGE.

    • DELETE: запросы DELETE.

    • ANALYZE: запросы ANALYZE.

    • DATA_DEFINITION: запросы, влияющие на определение данных. Включают операторы CREATE, ALTER и DROP для схем, таблиц, представлений и materialized views, а также команды управления prepared statements, привилегиями, сессиями и транзакциями. Если внешним клиентам требуется доступ к процедуре system.runtime.kill_query() для остановки выполняющихся или ожидающих запросов, необходимо использовать этот queryType, чтобы kill_query() выполнялся напрямую, а не ставился в очередь ожидания завершения исходного запроса.

    • ALTER_TABLE_EXECUTE: запросы, выполняющие табличные процедуры через ALTER TABLE EXECUTE.

  • clientTags (необязательно): список тегов. Для совпадения каждый тег из этого списка должен присутствовать в списке тегов клиента, связанных с запросом.

  • group (обязательно): группа, в которой будут выполняться эти запросы.

Все правила внутри одного selector объединяются логическим AND. Следовательно, все условия должны совпасть, чтобы selector был применён.

Selectors обрабатываются последовательно, и используется первый совпавший.

Глобальные свойства#

  • cpuQuotaPeriod (необязательно): период, в течение которого применяются CPU-квоты.

  • physicalDataScanQuotaPeriod (необязательно): период, в течение которого применяются квоты на сканирование физических данных.

Передача свойств selector#

Имя source можно задать следующим образом:

  • CLI: используйте опцию --source.

  • JDBC драйвер при использовании в клиентских приложениях: добавьте свойство source в конфигурацию подключения и задайте значение при использовании Java-приложения, которое использует JDBC Driver.

  • JDBC драйвер в Java-программах: добавьте свойство с ключом source и значением в объект Connection, как показано в примере.

Client tags можно задать следующим образом:

  • CLI: используйте опцию --client-tags.

  • JDBC драйвер при использовании в клиентских приложениях: добавьте свойство clientTags в конфигурацию подключения и задайте значение при использовании Java-приложения.

  • JDBC драйвер в Java-программах: добавьте свойство с ключом clientTags и значением в объект Connection, как показано в примере.

Пример#

В приведённой ниже конфигурации есть несколько resource groups, некоторые из которых являются шаблонами. Шаблоны позволяют администраторам динамически формировать дерево resource groups. Например, в группе pipeline_${USER} переменная ${USER} заменяется на имя пользователя, отправившего запрос. Также поддерживается ${SOURCE}, которая заменяется на источник запроса. Вы также можете использовать пользовательские именованные переменные в регулярных выражениях для user, source, originalUser, authenticatedUser и queryText.

Существует шесть selectors, определяющих, какие запросы выполняются в каких resource groups:

  • Первый selector сопоставляет запросы от пользователя bob и помещает их в группу admin.

  • Следующий selector сопоставляет запросы с original пользователем bob и помещает их в группу admin.

  • Следующий selector сопоставляет запросы с authenticated пользователем bob и помещает их в группу admin.

  • Следующий selector сопоставляет запросы от группы пользователей admin и помещает их в группу admin.

  • Следующий selector сопоставляет все DDL-запросы (data definition) с source, содержащим pipeline, и помещает их в группу global.data_definition. Это может уменьшить время ожидания, так как такие запросы обычно быстрые.

  • Следующий selector сопоставляет запросы с source, содержащим pipeline, и помещает их в динамически создаваемую пользовательскую pipeline-группу внутри global.pipeline.

  • Следующий selector сопоставляет запросы от BI-инструментов, у которых source соответствует регулярному выражению jdbc#(?<toolname>.*) и client tags включают hipri. Такие запросы помещаются в динамически создаваемую подгруппу внутри global.adhoc. Подгруппы формируются на основе значений переменных toolname и user, полученных из source и пользователя запроса соответственно. Например, запрос с source jdbc#powerfulbi, пользователем kayla и тегами hipri и fast будет направлен в resource group global.adhoc.bi-powerfulbi.kayla.

  • Последний selector является универсальным (catch-all) и помещает все не сопоставленные ранее запросы в пользовательскую adhoc-группу.

В совокупности эти selectors реализуют следующую политику:

  • Пользователь bob и любой пользователь из группы admin считается администратором и может выполнять до 50 параллельных запросов. bob будет считаться администратором, даже если изменит session user (например, через SET SESSION AUTHORIZATION или заголовок X-Trino-User). Запросы выполняются с учётом приоритета, заданного пользователем.

Для остальных пользователей:

  • Одновременно может выполняться не более 100 запросов.

  • До 5 параллельных DDL-запросов с source pipeline. Запросы выполняются по FIFO.

  • Не-DDL запросы выполняются в группе global.pipeline с общей параллельностью 45 и ограничением 5 на пользователя. Выполнение по FIFO.

  • Для BI-инструментов: каждый инструмент может выполнять до 10 запросов, а каждый пользователь — до 3. Если общий спрос превышает 10, следующий слот получает пользователь с наименьшим числом выполняющихся запросов, обеспечивая справедливость при конкуренции.

  • Все остальные запросы помещаются в пользовательскую группу внутри global.adhoc.other с аналогичным поведением.

File resource group manager#

{
  "rootGroups": [
    {
      "name": "global",
      "softMemoryLimit": "80%",
      "hardPhysicalDataScanLimit": "50TB",
      "hardConcurrencyLimit": 100,
      "maxQueued": 1000,
      "schedulingPolicy": "weighted",
      "jmxExport": true,
      "subGroups": [
        {
          "name": "data_definition",
          "softMemoryLimit": "10%",
          "hardConcurrencyLimit": 5,
          "maxQueued": 100,
          "schedulingWeight": 1
        },
        {
          "name": "adhoc",
          "softMemoryLimit": "10%",
          "hardConcurrencyLimit": 50,
          "maxQueued": 1,
          "schedulingWeight": 10,
          "subGroups": [
            {
              "name": "other",
              "softMemoryLimit": "10%",
              "hardConcurrencyLimit": 2,
              "maxQueued": 1,
              "schedulingWeight": 10,
              "schedulingPolicy": "weighted_fair",
              "subGroups": [
                {
                  "name": "${USER}",
                  "softMemoryLimit": "10%",
                  "hardConcurrencyLimit": 1,
                  "maxQueued": 100,
                  "hardPhysicalDataScanLimit": "10GB"
                }
              ]
            },
            {
              "name": "bi-${toolname}",
              "softMemoryLimit": "10%",
              "hardConcurrencyLimit": 10,
              "maxQueued": 100,
              "schedulingWeight": 10,
              "schedulingPolicy": "weighted_fair",
              "subGroups": [
                {
                  "name": "${USER}",
                  "softMemoryLimit": "10%",
                  "hardConcurrencyLimit": 3,
                  "maxQueued": 10
                }
              ]
            }
          ]
        },
        {
          "name": "pipeline",
          "softMemoryLimit": "80%",
          "hardConcurrencyLimit": 45,
          "maxQueued": 100,
          "schedulingWeight": 1,
          "jmxExport": true,
          "subGroups": [
            {
              "name": "pipeline_${USER}",
              "softMemoryLimit": "50%",
              "hardConcurrencyLimit": 5,
              "maxQueued": 100
            }
          ]
        }
      ]
    },
    {
      "name": "admin",
      "softMemoryLimit": "100%",
      "hardConcurrencyLimit": 50,
      "maxQueued": 100,
      "schedulingPolicy": "query_priority",
      "jmxExport": true
    }
  ],
  "selectors": [
    {
      "user": "bob",
      "group": "admin"
    },
    {
      "originalUser": "bob",
      "group": "admin"
    },
    {
      "authenticatedUser": "bob",
      "group": "admin"
    },
    {
      "userGroup": "admin",
      "group": "admin"
    },
    {
      "source": ".*pipeline.*",
      "queryType": "DATA_DEFINITION",
      "group": "global.data_definition"
    },
    {
      "source": ".*pipeline.*",
      "group": "global.pipeline.pipeline_${USER}"
    },
    {
      "source": "jdbc#(?<toolname>.*)",
      "clientTags": ["hipri"],
      "group": "global.adhoc.bi-${toolname}.${USER}"
    },
    {
      "group": "global.adhoc.other.${USER}"
    }
  ],
  "cpuQuotaPeriod": "1h",
  "physicalDataScanQuotaPeriod": "1h"
}

Database resource group manager#

Этот пример предназначен для базы данных MySQL.

-- global properties
INSERT INTO resource_groups_global_properties (name, value) VALUES ('cpu_quota_period', '1h');

-- Every row in resource_groups table indicates a resource group.
-- The enviroment name is 'test_environment', make sure it matches `node.environment` in your cluster.
-- The parent-child relationship is indicated by the ID in 'parent' column.

-- create a root group 'global' with NULL parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_physical_data_scan_limit, hard_concurrency_limit, max_queued, scheduling_policy, jmx_export, environment) VALUES ('global', '80%', '50TB', 100, 1000, 'weighted', true, 'test_environment');

-- get ID of 'global' group
SELECT resource_group_id FROM resource_groups WHERE name = 'global';  -- 1
-- create two new groups with 'global' as parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued, scheduling_weight, environment, parent) VALUES ('data_definition', '10%', 5, 100, 1, 'test_environment', 1);
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued, scheduling_weight, environment, parent) VALUES ('adhoc', '10%', 50, 1, 10, 'test_environment', 1);

-- get ID of 'adhoc' group
SELECT resource_group_id FROM resource_groups WHERE name = 'adhoc';   -- 3
-- create 'other' group with 'adhoc' as parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued, scheduling_weight, scheduling_policy, environment, parent) VALUES ('other', '10%', 2, 1, 10, 'weighted_fair', 'test_environment', 3);

-- get ID of 'other' group
SELECT resource_group_id FROM resource_groups WHERE name = 'other';  -- 4
-- create '${USER}' group with 'other' as parent.
INSERT INTO resource_groups (name, soft_memory_limit, hard_physical_data_scan_limit, hard_concurrency_limit, max_queued, environment, parent) VALUES ('${USER}', '10%', '10GB', 1, 100, 'test_environment', 4);

-- create 'bi-${toolname}' group with 'adhoc' as parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued, scheduling_weight, scheduling_policy, environment, parent) VALUES ('bi-${toolname}', '10%', 10, 100, 10, 'weighted_fair', 'test_environment', 3);

-- get ID of 'bi-${toolname}' group
SELECT resource_group_id FROM resource_groups WHERE name = 'bi-${toolname}';  -- 6
-- create '${USER}' group with 'bi-${toolname}' as parent. This indicates
-- nested group 'global.adhoc.bi-${toolname}.${USER}', and will have a
-- different ID than 'global.adhoc.other.${USER}' created above.
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued,  environment, parent) VALUES ('${USER}', '10%', 3, 10, 'test_environment', 6);

-- create 'pipeline' group with 'global' as parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued, scheduling_weight, jmx_export, environment, parent) VALUES ('pipeline', '80%', 45, 100, 1, true, 'test_environment', 1);

-- get ID of 'pipeline' group
SELECT resource_group_id FROM resource_groups WHERE name = 'pipeline'; -- 8
-- create 'pipeline_${USER}' group with 'pipeline' as parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued,  environment, parent) VALUES ('pipeline_${USER}', '50%', 5, 100, 'test_environment', 8);

-- create a root group 'admin' with NULL parent
INSERT INTO resource_groups (name, soft_memory_limit, hard_concurrency_limit, max_queued, scheduling_policy, environment, jmx_export) VALUES ('admin', '100%', 50, 100, 'query_priority', 'test_environment', true);


-- Selectors

-- use ID of 'admin' resource group for selector
INSERT INTO selectors (resource_group_id, user_regex, priority) VALUES ((SELECT resource_group_id FROM resource_groups WHERE name = 'admin'), 'bob', 6);

-- use ID of 'admin' resource group for selector
INSERT INTO selectors (resource_group_id, user_group_regex, priority) VALUES ((SELECT resource_group_id FROM resource_groups WHERE name = 'admin'), 'admin', 5);

-- use ID of 'global.data_definition' resource group for selector
INSERT INTO selectors (resource_group_id, source_regex, query_type, priority) VALUES ((SELECT resource_group_id FROM resource_groups WHERE name = 'data_definition'), '.*pipeline.*', 'DATA_DEFINITION', 4);

-- use ID of 'global.pipeline.pipeline_${USER}' resource group for selector
INSERT INTO selectors (resource_group_id, source_regex, priority) VALUES ((SELECT resource_group_id FROM resource_groups WHERE name = 'pipeline_${USER}'), '.*pipeline.*', 3);

-- get ID of 'global.adhoc.bi-${toolname}.${USER}' resource group by disambiguating group name using parent ID
SELECT A.resource_group_id self_id, B.resource_group_id parent_id, concat(B.name, '.', A.name) name_with_parent
FROM resource_groups A JOIN resource_groups B ON A.parent = B.resource_group_id
WHERE A.name = '${USER}' AND B.name = 'bi-${toolname}';
--  7 |         6 | bi-${toolname}.${USER}
INSERT INTO selectors (resource_group_id, source_regex, client_tags, priority) VALUES (7, 'jdbc#(?<toolname>.*)', '["hipri"]', 2);

-- get ID of 'global.adhoc.other.${USER}' resource group for by disambiguating group name using parent ID
SELECT A.resource_group_id self_id, B.resource_group_id parent_id, concat(B.name, '.', A.name) name_with_parent
FROM resource_groups A JOIN resource_groups B ON A.parent = B.resource_group_id
WHERE A.name = '${USER}' AND B.name = 'other';
-- |       5 |         4 | other.${USER}    |
INSERT INTO selectors (resource_group_id, priority) VALUES (5, 1);