资源组

资源组对资源使用设置限制,并可以对在其中运行的查询强制执行排队策略,或将其资源分配给子组。一个查询属于单个资源组,并从该组(及其祖先)消耗资源。除了对排队查询的限制外,当资源组用完资源时,不会导致正在运行的查询失败;相反,新的查询将被排队。资源组可以有子组,也可以接受查询,但不能同时执行两者。

在 PrestoDB 中,资源组是管理查询执行和资源分配的强大工具。它们允许管理员控制资源如何在 Presto 集群中分配和使用。

资源使用限制

资源组可以对资源使用设置限制,例如 CPU 时间、内存使用量或总查询数量。这在多租户环境中特别有用,您希望确保没有单个用户或查询独占系统资源。

资源消耗

一个查询属于单个资源组,它从该组及其父组消耗资源。如果资源组用完某种资源,不会导致正在运行的查询失败。相反,新的查询将被排队,直到资源再次可用。

子组和查询接受

子组允许分层资源分配,其中每个子组都可以有自己的资源限制和排队策略。直接接受查询的资源组是叶组,它使用分配的资源执行查询。

资源组和相关的选择规则由管理器配置,管理器是可插拔的。Presto 资源管理可以通过两种方式完成

基于文件的资源管理

在基于文件的资源管理器中,有关资源组的配置信息存储在 JSON 文件中。该文件包含所有资源组的定义以及选择它们的规则。配置文件在 Presto 服务器启动时加载并使用。对文件的任何更改都需要重新启动 Presto 服务器才能生效。

基于数据库的资源管理

在基于数据库的资源管理器中,有关资源组的配置信息存储在关系数据库中。数据库包含所有资源组的定义以及选择它们的规则。与基于文件的资源管理器不同,对数据库中配置的更改会立即生效,不需要重新启动 Presto 服务器。

两种方法都有其优缺点。基于文件的资源管理更易于设置,但灵活性较差,而基于数据库的资源管理更难设置,但提供了更大的灵活性,并支持动态更改。

文件资源组管理器

PrestoDB 中的文件资源组管理器是一种使用 JSON 配置文件管理资源的方式。该文件包含所有资源组的定义以及为给定查询选择适当资源组的规则。

要设置基于文件的资源组管理器

  1. 创建文件 etc/resource-groups.properties

  2. etc/resource-groups.properties 中,使用以下代码示例将 resource-groups.configuration-manager 属性设置为 file

    resource-groups.configuration-manager=file
    
  3. etc 中创建一个名为 resource-groups.json 的 JSON 文件。该文件应包含资源组的定义。每个资源组都可以指定诸如最大内存、最大排队查询和最大运行查询之类的内容。

    有关创建资源组定义的信息,请参见 资源组属性。有关 resource-groups.json 文件的示例,请参见 文件资源组管理器

  4. etc/resource-groups.properties 中,添加一行,使用以下代码示例指定 JSON 文件的位置。将 resource-groups.config-file 属性设置为 <file_path>,其中 <file_path> 是 JSON 文件的路径。

    resource-groups.config-file=etc/resource-groups.json
    
  5. 重新启动 Presto 服务器。新的资源组在重新启动后立即生效。

数据库资源组管理器

PrestoDB 中的数据库资源组管理器是一种使用关系数据库管理资源的方式。

要设置基于数据库的资源组管理器

  1. 创建文件 etc/resource-groups.properties

  2. resource-groups.configuration-manager 属性设置为 db

  3. 设置关系数据库:数据库应可被 Presto 访问。它将用于存储资源组的配置。

  4. 为资源组和选择规则创建表:您需要在数据库中创建表,用于存储资源组的定义以及为给定查询选择适当资源组的规则。

  5. 要在 Presto 配置中指定数据库,请在 etc/resource-groups.properties 中添加一行,指定数据库的 JDBC URL:resource-groups.config-db-url = <jdbc_url>,其中 <jdbc_url> 是数据库的 JDBC URL。

    注意:目前仅支持 MySQL。

etc/resource-groups.properties 应类似于以下示例

resource-groups.configuration-manager=db
resource-groups.config-db-url=jdbc:mysql://127.0.0.1:3306/resource_groups?user=<user>&password=<password>

<user><password> 替换为实际的用户名和密码。

使用数据库资源组管理器,对数据库中配置的更改会立即生效,不需要重新启动 Presto 服务器。这允许更灵活地对资源组配置进行动态更改。

资源组配置必须通过表 resource_groups_global_propertiesresource_groupsselectors 填充。如果 Presto 启动时任何表都不存在,它们会自动创建。

selectors 中的规则按 priority 字段中的值降序处理。

数据库资源组管理器属性

数据库资源组管理器属性

属性名称

描述

默认值

resource-groups.config-db-url

用于加载配置的数据库 URL。

resource-groups.max-refresh-interval

在刷新失败后,集群将继续接受查询的最长时间段,这会导致配置变得陈旧。

1h

resource-groups.exact-match-selector-enabled

设置此标志将启用使用额外的 exact_match_source_selectors 表来配置资源组选择规则,这些规则为源、环境和查询类型定义了基于精确名称的匹配。默认情况下,规则仅从 selectors 表加载,并对 source 使用基于正则表达式的过滤器,以及其他过滤器。

false

资源组属性

资源组使用一组属性定义,这些属性决定了资源的分配和使用方式。以下是可为资源组设置的关键属性

  • name(必需):资源组的名称。这是必需属性。

  • maxQueued (必需): 资源组中可以排队的最大查询数。如果达到此限制,新查询将被拒绝。

  • hardCpuLimit (可选): 此组在一个时期内可以使用 CPU 时间的最大量。

  • softMemoryLimit (必需): 资源组可以使用内存的最大量。这可以以绝对值(例如“10GB”)或可用内存的百分比(例如“50%”)指定。

  • hardConcurrencyLimit (必需): 资源组可以同时运行的最大查询数。

  • softConcurrencyLimit (可选): 并发查询数的软限制。如果超过此限制,调度程序将尝试阻止新的查询启动,但不会强制正在运行的查询停止。

  • softCpuLimit (可选): 此组在一个时期内可以使用 CPU 时间的最大量(见 cpuQuotaPeriod),在对最大运行查询数施加惩罚之前。还必须指定 hardCpuLimit

  • schedulingPolicy (可选): 确定如何在资源组内调度查询的策略。这可以设置为四个值之一 fairweightedweighted_fairquery_priority

    • fair (默认): 排队的查询按先入先出顺序处理,并且如果子组有任何排队的查询,它们必须轮流启动新查询。

    • weighted_fair: 子组是根据其 schedulingWeight 以及它们已经同时运行的查询数来选择的。子组的预期运行查询份额是根据所有当前符合条件的子组的权重计算的。与份额相比,并发性最小的子组将被选中以启动下一个查询。

    • weighted: 排队的查询是根据其优先级以随机方式选择的,优先级通过 query_priority {doc} session property </sql/set-session> 指定。子组被选中以按其 schedulingWeight 启动新查询。

    • query_priority: 所有子组也必须配置为 query_priority。排队的查询严格按照其优先级进行选择。

  • schedulingWeight (可选): 当父组使用 weighted 调度策略时,资源组的权重。权重越高,组获得的父组资源份额就越大。

  • jmxExport (可选): 如果设置为 true,则资源组的统计信息将通过 JMX 导出。默认为 false

  • perQueryLimits (可选): 指定每个资源组中的每个查询在被杀死之前可以消耗的最大资源。这些限制不会从父组继承。可以设置三种类型的限制

    • executionTimeLimit (可选): 指定查询执行的最大时间的绝对值(例如,1h)。

    • totalMemoryLimit (可选): 指定查询可能消耗的最大分布式内存的绝对值(例如,1GB)。

    • cpuTimeLimit (可选): 指定查询可能使用的最大 CPU 时间的绝对值(例如,1h)。

  • workerPerQueryLimit (可选): 指定每个查询必须可用的最小工作程序数。旨在用于工作程序数量随时间变化的弹性集群中。

  • subGroups (可选): 子组列表。资源组中的子组列表。每个子组都可以有自己的属性集。

调度权重示例

调度加权是为资源分配优先级的方法。调度权重较高的子组将获得更高的优先级。例如,为了确保按计划执行的管道查询的及时执行,请将其权重设置得高于即席查询。

以下是一个示例

如果您有一个根资源组 global,有两个子组:engineeringmarketingengineering 子组的调度权重为 3,marketing 子组的调度权重为 1。在此设置中,engineering 子组将获得父组 75% 的资源(因为 3 是 4 的总权重的 75%),而 marketing 子组将获得父组 25% 的资源(因为 1 是 4 的总权重的 25%)。

调度加权允许您在资源分配方面优先考虑某些子组。在此示例中,来自 engineering 子组的查询将优先于来自 marketing 子组的查询。

选择器规则

以下是 PrestoDB 中选择器规则的关键组成部分

  • group (必需): 这些查询将在其中运行的组。

  • user (可选): 这是一个正则表达式,匹配提交查询的用户。

  • source (可选): 这匹配查询的来源,通常是提交查询的应用程序。

  • queryType (可选): 与提交的查询类型匹配的字符串

    • SELECT: SELECT 查询。

    • EXPLAIN: EXPLAIN 查询(但不包括 EXPLAIN ANALYZE)。

    • DESCRIBE: DESCRIBEDESCRIBE INPUTDESCRIBE OUTPUTSHOW 查询。

    • INSERT: INSERTCREATE TABLE ASREFRESH MATERIALIZED VIEW 查询。

    • UPDATE: UPDATE 查询。

    • DELETE: DELETE 查询。

    • ANALYZE: ANALYZE 查询。

    • DATA_DEFINITION: 更改/创建/删除架构/表/视图的元数据以及管理准备好的语句、权限、会话和事务的查询。

  • clientTags (可选): 标签列表。要匹配,此列表中的每个标签都必须在与查询关联的客户端提供的标签列表中。

  • selectorResourceEstimate (可选): 基于资源估计的资源组选择。
    • executionTime

    • peakMemory

    • cpuTime

  • clientInfo (可选): 与客户端信息匹配的字符串。

  • principal (可选): 这是一个正则表达式,匹配提交查询的主体。

  • schema (可选): 这匹配查询的会话架构。

选择器按顺序处理,第一个匹配的选择器将被使用。

全局属性

  • cpuQuotaPeriod (可选): 强制执行 CPU 配额的时期。 cpuQuotaPeriod 是一个全局属性,通常用于基于容器的环境中,以控制容器在指定时期内可以使用的 CPU 资源量。

请注意,这些属性的具体实现和命名可能会因不同的容器运行时和编排系统而异。

提供选择器属性

来源名称可以按如下方式设置

  • CLI: 使用 --source 选项。

  • JDBC 驱动程序在客户端应用程序中使用时:将 source 属性添加到连接配置中,并在使用使用 JDBC 驱动程序的 Java 应用程序时设置值。

  • 与 Java 程序一起使用的 JDBC 驱动程序:在 Connection 实例上添加一个键为 source、值为 示例 的属性。

客户端标签可以按如下方式设置

  • CLI: 使用 --client-tags 选项。

  • JDBC 驱动程序在客户端应用程序中使用时:将 clientTags 属性添加到连接配置中,并在使用使用 JDBC 驱动程序的 Java 应用程序时设置值。

  • 使用 Java 程序的 JDBC 驱动程序:在 Connection 实例中添加一个键为 clientTags 的属性,其值如 示例 所示。

示例

在下面的示例配置中,有多个资源组,其中一些是模板。模板允许管理员动态构建资源组树。例如,在 pipeline_${USER} 组中,${USER} 将扩展为提交查询的用户的名称。${SOURCE} 也受支持,它将扩展为提交查询的源。您也可以在 sourceuser 正则表达式中使用自定义命名变量。

有四个选择器,它们定义哪些查询在哪个资源组中运行。

  • 第一个选择器匹配来自 bob 的查询,并将它们放置在 admin 组中。

  • 第二个选择器匹配来自包含 pipeline 的源名称的所有数据定义 (DDL) 查询,并将它们放置在 global.data_definition 组中。这有助于减少此类查询的队列时间,因为它们预计会很快完成。

  • 第三个选择器匹配来自包含 pipeline 的源名称的查询,并将它们放置在 global.pipeline 组下的动态创建的每个用户管道组中。

  • 第四个选择器匹配来自 BI 工具的查询,这些工具的源匹配正则表达式 jdbc#(?<toolname>.*),并且具有客户端提供的标记,这些标记是 hi-pri 的超集。这些查询将被放置在 global.pipeline.tools 组下的动态创建的子组中。动态子组是根据命名变量 toolname 创建的,该变量是从源的正则表达式中提取的。

    考虑一个源为 jdbc#powerfulbi、用户为 kayla 且客户端标记为 hiprifast 的查询。此查询被路由到 global.pipeline.bi-powerfulbi.kayla 资源组。

  • 最后一个选择器是一个万能选择器,它将所有尚未匹配的查询放置到每个用户 adhoc 组中。

总而言之,这些选择器实现了以下策略。

  • 用户 bob 是管理员,可以运行最多 50 个并发查询。查询将根据用户提供的优先级运行。

对于其他用户

  • 最多可以同时运行 100 个查询。

  • 最多可以同时运行 5 个带有源 pipeline 的并发 DDL 查询。查询按 FIFO 顺序运行。

  • 非 DDL 查询将在 global.pipeline 组下运行,总并发数为 45,每个用户并发数为 5。查询按 FIFO 顺序运行。

  • 对于 BI 工具,每个工具最多可以运行 10 个并发查询,每个用户最多可以运行 3 个。如果总需求超过 10 个的限制,则运行查询最少的用户将获得下一个并发时段。这种策略在争用情况下会导致公平性。

  • 所有剩余的查询都将被放置到 global.adhoc.other 下的每个用户组中,其行为类似。

文件资源组管理器

{
  "rootGroups": [
    {
      "name": "global",
      "softMemoryLimit": "80%",
      "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
                }
              ]
            },
            {
              "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"
    },
    {
      "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"
}

数据库资源组管理器

-- 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_concurrency_limit, max_queued, scheduling_policy, jmx_export, environment) VALUES ('global', '80%', 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_concurrency_limit, max_queued, environment, parent) VALUES ('${USER}', '10%', 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);

-- 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'; -- 7
-- 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', 7);

-- 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 '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.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);