当 ClickHouse 同时执行多个查询时,这些查询可能会使用共享资源 (CPU、内存和 IO) 。可以应用调度约束和策略,以规范不同工作负载之间资源的使用和共享。对于所有资源,都可以配置统一的调度层级结构。层级根节点表示共享资源,而叶子节点则是特定的工作负载,用于承载特定查询和后台活动的资源请求与分配。
资源
默认情况下,工作负载调度是禁用的。要启用它,需要创建用于调度的资源,以及至少一个工作负载。所有资源彼此独立,可以任意组合使用。
要启用 CPU 调度,需要为 MASTER 或 WORKER 线程创建 CPU 资源 (详见 CPU scheduling) :
CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)要为工作负载启用内存预留功能,您必须先创建 MEMORY 资源 (详见内存预留) :
CREATE RESOURCE memory (MEMORY RESERVATION)要启用查询槽位调度,必须先创建 QUERY 资源 (详情请参见查询槽位调度) :
CREATE RESOURCE query (QUERY)要为特定的 disk 启用 IO 调度,你必须为 WRITE 和 READ 访问创建读资源和写资源:
CREATE RESOURCE resource_name (WRITE DISK disk_name, READ DISK disk_name)
-- or
CREATE RESOURCE read_resource_name (WRITE DISK write_disk_name)
CREATE RESOURCE write_resource_name (READ DISK read_disk_name)一个资源可用于任意数量的磁盘,可用于 READ、WRITE,或同时用于 READ 和 WRITE。还有一种语法可将一个资源用于所有磁盘:
CREATE RESOURCE all_io (READ ANY DISK, WRITE ANY DISK);资源按共享模式分类:
- 时间共享资源 (CPU、IO、查询槽位) - 管理在调度层级叶子节点排队的资源请求。请求会按照层级中定义的策略和约束进行调度。当查询访问相应资源时,就会创建资源请求。例如,当查询从磁盘读取数据,或使用 CPU 进行处理时,系统会在每完成一个工作量子,或通过套接字发送或接收一定字节数时创建资源请求。
- 空间共享资源 (内存) - 管理调度层级叶子节点上的资源分配。分配可能处于运行中或待处理状态。待处理的分配会被阻塞,直到释放出足够空间,或其他分配被驱逐 (killed) 。相关决策基于层级中定义的限制和策略。分配与查询 (或后台活动) 之间是一一对应的关系。分配会在查询开始执行时创建,并在查询结束时释放。运行中的分配其大小可以动态增减。
工作负载层级结构
ClickHouse 提供了便捷的 SQL 语法来定义调度层级结构。所有资源都分布在统一的 WORKLOAD 层级结构中。对于特定资源,分配规则的某些细节可以调整,但层级结构本身保持一致。每个 WORKLOAD 都会为每种资源维护必要的调度节点。可以在任意工作负载内创建子工作负载,从而构建出整个层级结构。ClickHouse 不会对工作负载层级结构强制规定任何特定或预定义的结构。
下面是一个层级结构示例,它将所有资源分别分配给 "user" 和 "system" 工作负载,并分别保证 90% 和 10% 的资源份额。请注意,为工作负载定义的权重用于实现 最大最小公平原则,因此只能尽力提供下限保障 (而不是上限限制或配额) 。所有调度都是在每台主机上独立进行的,因此由 max_* 设置定义的限制都是按主机生效的。工作负载 "user" 会将其资源进一步细分给 "development" 和 "production" 工作负载,其中 "production" 获得的资源是 "development" 的 3 倍:
CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)
CREATE RESOURCE memory (MEMORY RESERVATION)
CREATE RESOURCE s3_read (READ DISK s3)
CREATE RESOURCE s3_write (WRITE DISK s3)
CREATE WORKLOAD all SETTINGS max_concurrent_threads_ratio_to_cores = 2, max_memory_ratio = 0.8, max_bytes_per_second = '2Gi'
CREATE WORKLOAD user IN all SETTINGS weight = 9
CREATE WORKLOAD system IN all
CREATE WORKLOAD development IN user
CREATE WORKLOAD production IN user SETTINGS weight = 3graph LR
subgraph Resources
cpu["cpu"]
mem["memory"]
nr["s3_read"]
nw["s3_write"]
mem["memory"]
oth["..."]
end
subgraph Workloads
all["all"]
usr["user"]
sys["system"]
wl1["..."]
dev["development"]
prd["production"]
wl2["..."]
all --> |≥90%| usr
all --> |≥10%| sys
all --> wl1
usr --> |≥25%| dev
usr --> |≥75%| prd
usr --> wl2
end
cpu --> |2xCores| all
mem --> |0.8xRAM| all
nr --> |2GBps| all
nw --> |2GBps| all
oth --> all
无子级的叶子工作负载名称可在查询设置 SETTINGS workload = 'name' 中使用。详情请参阅 Workload markup。
如需自定义工作负载,可以使用以下设置:
priority- (仅限时间共享) 同级工作负载按静态值提供服务 (值越小,优先级越高) 。驱动抢占。precedence- (仅限空间共享) 同级工作负载按静态值准入 (值越小,优先次序越高) 。驱动驱逐和准入。weight- 具有相同静态优先级或 优先次序 的同级工作负载按权重以公平方式共享资源。影响抢占、驱逐和准入。max_io_requests- 此工作负载中并发 IO 请求数量的上限。max_bytes_inflight- 此工作负载中并发请求的在途总字节数上限。max_bytes_per_second- 此工作负载的字节读取或写入速率上限。max_burst_bytes- 该工作负载在不被限流的情况下可处理的最大字节数 (对每种资源分别独立计算) 。max_concurrent_threads- 此工作负载中查询可使用的线程数量上限。max_concurrent_threads_ratio_to_cores- 与max_concurrent_threads相同,但会按可用 CPU 核心数进行归一化。max_cpus- 为此工作负载中的查询提供服务时可使用的 CPU 核心数量上限。max_cpu_share- 与max_cpus相同,但会按可用 CPU 核心数进行归一化。max_burst_cpu_seconds- 该工作负载在不因max_cpus而被限流的情况下可消耗的最大 CPU 秒数。max_memory- 为此工作负载保留的总内存上限。
通过工作负载设置指定的所有限制,对每种资源都是相互独立的。例如,若某个工作负载设置了 max_bytes_per_second = '10Mi',则对每个读取和写入资源都会分别施加 10 MB/s 的带宽限制。如果需要对读取和写入施加统一限制,请考虑让 READ 和 WRITE 访问使用同一资源。
无法为不同资源指定不同的工作负载层级结构。但可以为特定资源指定不同的工作负载设置值:
CREATE OR REPLACE WORKLOAD all SETTINGS max_io_requests = 100, max_bytes_per_second = '1Mi' FOR network_read, max_bytes_per_second = '2Mi' FOR network_write另请注意,如果某个工作负载或资源被其他工作负载引用,则无法删除。要更新工作负载的定义,请使用 CREATE OR REPLACE WORKLOAD 查询。
工作负载标记
可以使用设置 workload 为查询打标,以区分不同的工作负载。如果未设置 workload,则使用值 "default"。请注意,也可以通过 profile 指定其他值。如果你希望某个用户的所有查询都使用固定的 workload 设置值进行标记,可以使用设置约束将 workload 固定为该值。
SELECT count() FROM my_table WHERE value = 42 SETTINGS workload = 'production'
SELECT count() FROM my_table WHERE value = 13 SETTINGS workload = 'development'也可以为后台活动分配 workload 设置。合并和变更分别使用 merge_workload 和 mutation_workload 服务器设置。这些值还可以通过 merge_workload 和 mutation_workload MergeTree 设置在特定表级别进行覆盖。
CPU 调度
要为工作负载启用 CPU 调度,请创建 CPU 资源,并为并发线程数设置上限:
CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)
CREATE WORKLOAD all SETTINGS max_concurrent_threads = 100当 ClickHouse server 使用多个线程执行大量并发查询,且所有 CPU 插槽都被占用时,就会进入过载状态。在过载状态下,每个释放出的 CPU 插槽都会根据调度策略重新调度到相应的工作负载。对于共享同一工作负载的查询,插槽按 round robin 方式分配。对于属于不同工作负载的查询,插槽则按照为各工作负载指定的权重、priority和限制进行分配。
当线程未被阻塞且正在执行 CPU 密集型任务时,就会消耗 CPU 时间。出于调度需要,这里区分两类线程:
- Master thread — 开始执行查询或 merge、变更等后台活动的第一个线程。
- Worker thread — 可由 master 派生出的额外线程,用于执行 CPU 密集型任务。
为了获得更好的响应性,可能需要将 master 线程和工作线程使用的资源分开。当查询设置 max_threads 的值较高时,大量工作线程很容易独占 CPU 资源。这样一来,新到达的查询就只能阻塞,等待为其 master 线程分配一个 CPU 插槽后才能开始执行。为避免这种情况,可以使用以下配置:
CREATE RESOURCE worker_cpu (WORKER THREAD)
CREATE RESOURCE master_cpu (MASTER THREAD)
CREATE WORKLOAD all SETTINGS max_concurrent_threads = 100 FOR worker_cpu, max_concurrent_threads = 1000 FOR master_cpu这将对 master 线程和 worker 线程分别设置限制。即使 100 个 worker CPU 插槽全都处于忙碌状态,只要还有可用的 master CPU 插槽,新查询就不会被阻塞。它们将先以单线程开始执行。之后,如果 worker CPU 插槽可用,此类查询就可以扩展并派生出 worker 线程。另一方面,这种方法不会将插槽总数限制在 CPU 处理器数量以内,而运行过多并发线程会影响性能。
限制 master 线程的并发度,并不会限制并发查询的数量。CPU 插槽可能会在查询执行过程中途被释放,然后被其他线程重新获取。例如,在 master 线程并发限制为 2 的情况下,4 个并发查询仍然可以全部并行执行。在这种情况下,每个查询将获得一个 CPU 处理器 50% 的处理能力。要限制并发查询的数量,应使用单独的逻辑,而工作负载目前尚不支持这一点。
可以为工作负载使用独立的线程并发限制:
CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)
CREATE WORKLOAD all
CREATE WORKLOAD admin IN all SETTINGS max_concurrent_threads = 10
CREATE WORKLOAD production IN all SETTINGS max_concurrent_threads = 100
CREATE WORKLOAD analytics IN production SETTINGS max_concurrent_threads = 60, weight = 9
CREATE WORKLOAD ingestion IN production此配置示例为管理和生产环境分别提供了独立的 CPU 插槽池。生产池由分析和摄取共享。此外,如果生产池过载,在有需要时,释放的 10 个 slot 中有 9 个会被重新分配给分析查询。摄取查询在过载期间只会获得其中的 1 个 slot。这可能会改善面向用户的查询延迟。分析自身有 60 个并发线程的限制,因此始终至少会保留 40 个线程用于支持摄取。在不过载时,摄取可以使用全部 100 个线程。
要将某个查询排除在 CPU 调度之外,请将查询设置 use_concurrency_control 设为 0。
CPU 调度目前尚不支持合并和变更。
为了让工作负载获得公平的资源分配,需要在查询执行期间进行抢占和缩容。可通过 cpu_slot_preemption 服务器设置启用抢占。如果启用,每个线程都会定期续用其 CPU 插槽 (根据 cpu_slot_quantum_ns 服务器设置) 。如果 CPU 过载,这种续用可能会阻塞执行。当执行被阻塞较长时间时 (参见 cpu_slot_preemption_timeout_ms 服务器设置) ,查询会缩容,并且并发运行的线程数会动态减少。请注意,工作负载之间的 CPU 时间公平性可以得到保证,但在同一工作负载内部,不同查询之间的公平性在某些边缘情况下可能无法保证。
线程与 CPU
控制工作负载 CPU 消耗有两种方式:
- 线程数限制:
max_concurrent_threads和max_concurrent_threads_ratio_to_cores - CPU 限流:
max_cpus、max_cpu_share和max_burst_cpu_seconds
第一种方式允许根据当前服务器负载动态控制一个查询可以创建多少个线程。它实际上会降低查询设置 max_threads 所指定的上限。第二种方式则使用令牌桶算法对工作负载的 CPU 消耗进行限流。它不会直接影响线程数,而是限制该工作负载中所有线程的 CPU 总消耗。
使用 max_cpus 和 max_burst_cpu_seconds 的令牌桶限流含义如下:在任意长度为 delta 秒的时间间隔内,工作负载中所有查询的 CPU 总消耗都不能超过 max_cpus * delta + max_burst_cpu_seconds 个 CPU 秒。从长期来看,它将平均消耗限制在 max_cpus,但短期内可以超过这一限制。例如,给定 max_burst_cpu_seconds = 60 和 max_cpus=0.001,则可以在不被限流的情况下运行 1 个线程 60 秒、2 个线程 30 秒,或 60 个线程 1 秒。max_burst_cpu_seconds 的默认值为 1 秒。在并发线程很多时,较小的值可能会导致允许的 max_cpus 核心无法得到充分利用。
线程在持有 CPU 插槽时,可能处于以下三种主要状态之一:
- Running: 实际占用 CPU 资源。处于此状态的时间会计入 CPU 限流。
- Ready: 等待 CPU 可用。不计入 CPU 限流。
- Blocked: 正在执行 IO 操作或其他阻塞式系统调用 (例如等待互斥锁) 。不计入 CPU 限流。
下面来看一个同时结合 CPU 限流和线程数限制的配置示例:
CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)
CREATE WORKLOAD all SETTINGS max_concurrent_threads_ratio_to_cores = 2
CREATE WORKLOAD admin IN all SETTINGS max_concurrent_threads = 2, priority = -1
CREATE WORKLOAD production IN all SETTINGS weight = 4
CREATE WORKLOAD analytics IN production SETTINGS max_cpu_share = 0.7, weight = 3
CREATE WORKLOAD ingestion IN production
CREATE WORKLOAD development IN all SETTINGS max_cpu_share = 0.3这里,我们将所有查询的线程总数限制为可用 CPU 数量的 2 倍。Admin 工作负载最多只能使用两个线程,与可用 CPU 的数量无关。Admin 的优先级为 -1 (低于默认值 0) ,因此在需要时会优先获得任意 CPU 插槽。当 Admin 没有运行查询时,CPU 资源会在 production 和 development 工作负载之间分配。CPU 时间的保障份额基于权重 (4:1) :production 至少获得 80% (如果有需要) ,development 至少获得 20% (如果有需要) 。权重决定的是保障份额,而 CPU throttling 决定的是上限:production 不受限制,可以占用 100%,而 development 的上限为 30%,即使没有其他工作负载的查询,这个上限也会生效。Production 工作负载不是叶子节点,因此它的资源会按权重 (3:1) 在 analytics 和 ingestion 之间拆分。这意味着 analytics 至少有 0.8 * 0.75 = 60% 的保障份额,并且根据 max_cpu_share,其总 CPU 资源上限为 70%。而 ingestion 至少有 0.8 * 0.25 = 20% 的保障份额,同时没有上限。
内存预留
要为工作负载启用内存预留,请创建 MEMORY RESERVATION 资源,并通过工作负载设置至少为总预留内存配置一个限制:
CREATE RESOURCE memory (MEMORY RESERVATION)
CREATE WORKLOAD all SETTINGS max_memory = '2Gi'ClickHouse 会跟踪所有查询和后台活动的内存分配。已分配的字节数会沿调度层级逐级汇总到根节点。每个查询在其所属的叶子工作负载中都有一个对应的分配。如果查询的 reserve_memory 设置大于零,则该分配会以待处理 状态创建。处于待处理 状态的分配会在工作负载层级中预留所请求的内存量。如果没有足够的可用内存,该分配会一直保持待处理,直到释放出足够的内存,或者其他分配被逐出 (killed) 。当分配获准后,它会变为运行中。处于运行中 状态的分配会根据查询的内存消耗动态增大或缩小。分配的生命周期可以用下图的状态图表示:
stateDiagram-v2
[*] --> Pending: init [reserve_memory > 0]
[*] --> Running: init [reserve_memory == 0]
Pending --> Running: admit
state Running {
%% Region 1: increase flow
NotIncreasing --> Increasing: request
Increasing --> NotIncreasing: approve
--
%% Region 2: decrease flow
NotDecreasing --> Decreasing: request
Decreasing --> NotDecreasing: approve
}
Running --> Killed: evict
Running --> Released: finish
叶子工作负载的待处理分配按 FIFO 顺序准入。当多个工作负载都有待处理分配时,会根据优先次序和权重设置决定准入顺序。优先次序更高的工作负载会先获得分配。具有相同优先次序的同级工作负载会按权重以最大最小公平原则共享内存,这意味着归一化内存使用量更低的工作负载 (即当前使用量加上请求增加量后,再除以权重) 会先获得分配。在驱逐时则采用相反的逻辑。当需要释放内存时,优先次序较低且归一化内存使用量较高的工作负载会先被驱逐。
请注意,时间共享资源使用 priority,而空间共享资源使用 优先次序。它们是彼此独立的设置,可以设为不同的值。更高的 priority 表示非破坏性抢占 (延迟或节流) ,而更高的 优先次序 则可能意味着破坏性驱逐 (报错并停止) 。某个工作负载在 CPU 调度中可以具有较高的 priority,但在内存预留中采用相同的 优先次序,以避免驱逐其他工作负载并丢失它们已经完成的工作。
每个设置了 max_memory 限制的工作负载都会确保其子树中已分配的总内存不超过该限制。如果待处理分配或增长中的分配会超出该限制,则会启动驱逐流程来释放内存。驱逐流程会选择一个要被终止的目标。killer 和 victim 的最近公共祖先工作负载会在以下情况下阻止驱逐:
- 待处理分配不能驱逐同一工作负载中运行中的分配。 (killer 和 victim 工作负载相同) 。
- 优先次序较低的待处理分配绝不会终止优先次序较高的工作负载。
- 待处理分配不能终止优先次序相同的分配。请注意,优先次序相同的运行中分配可能会根据归一化内存使用量相互驱逐。 如果驱逐被阻止,或者未能释放足够的内存,则新分配会被阻塞,直到有足够的内存被释放。这些规则允许查询在内存压力下排队等待,并提供了一种便捷方式来避免 MEMORY_LIMIT_EXCEEDED 错误。
工作负载设置 max_waiting_queries 用于限制该工作负载的待处理分配数量。达到限制后,server 会返回错误 SERVER_OVERLOADED。请注意,max_waiting_queries 不会被子节点工作负载继承,并且仅对叶子工作负载有意义。
内存预留调度目前尚不支持合并和变更。
只有 reserve_memory 设置大于零的查询,才会在等待内存预留时被阻塞。不过,reserve_memory 为零的查询也会计入其工作负载的内存占用,必要时还可能被驱逐,以便为其他待处理或持续增长的内存分配释放内存。没有正确工作负载标记的查询不受内存预留调度的约束,也不能被调度器驱逐。
要为查询提供非弹性的内存预留,请将 reserve_memory 和 max_memory_usage 这两个查询设置设为相同的值。在这种情况下,查询会预留固定数量的内存,且无法动态增加其分配。请注意,在没有内存压力的情况下,弹性内存预留可以在不被终止的前提下,从 reserve_memory 增加到 max_memory_usage。但即使实际内存消耗更低,也不能减少到 reserve_memory 以下。
来看一个配置示例:
CREATE RESOURCE memory (MEMORY RESERVATION)
CREATE WORKLOAD all SETTINGS max_memory = '10Gi'
CREATE WORKLOAD system IN all SETTINGS weight = 1
CREATE WORKLOAD user IN all SETTINGS weight = 9
CREATE WORKLOAD production IN user SETTINGS precedence = 1, weight = 3
CREATE WORKLOAD staging IN user SETTINGS precedence = 1, weight = 1
CREATE WORKLOAD testing IN user SETTINGS precedence = 2在此示例中,所有查询和后台活动保留的总内存不能超过 10 GiB。system 工作负载至少保证 1 GiB (10 GiB 的 10%) ,而 user 工作负载至少保证 9 GiB (10 GiB 的 90%) 。在 user 工作负载内部,production 和 staging 工作负载按权重 (3:1) 共享内存,且优先次序同为 1。testing 工作负载的优先次序为 2,低于 production 和 staging。因此,testing 工作负载只能使用 production 和 staging 未使用的内存。
如果出现内存压力,testing 工作负载的内存分配会最先被驱逐。随后,如果还需要释放更多内存,并且 staging 工作负载 和 production 工作负载 超过各自的保证值,则 staging 工作负载 的内存分配会先于 production 工作负载 的内存分配被驱逐。请注意,production 和 staging 中处于等待状态的查询可以驱逐 testing 工作负载 中正在运行的内存分配以释放内存,但它们彼此不能相互驱逐,因为它们具有相同的优先次序。在内存压力情况下,它们会在队列中等待,这使系统能够避免因并发执行的查询过多而导致的 MEMORY_LIMIT_EXCEEDED 错误。
请注意,system 工作负载的优先次序为 0 (default) ,高于 production、staging 和 testing 工作负载,但它们并不是同级工作负载。它们最近的共同祖先是工作负载 all,其两个子节点具有相同的优先次序。因此,处于等待状态的 system 工作负载 不能驱逐其中任何一个,反之亦然。这确保了 system 活动不容易被驱逐。
查询槽位调度
要为 工作负载 启用查询槽位调度,请创建 QUERY 资源,并为并发查询数或每秒查询数设置上限:
CREATE RESOURCE query (QUERY)
CREATE WORKLOAD all SETTINGS max_concurrent_queries = 100, max_queries_per_second = 10, max_burst_queries = 20工作负载设置 max_concurrent_queries 用于限制给定工作负载可同时运行的并发查询数。它相当于查询设置 max_concurrent_queries_for_all_users 和服务器设置 max_concurrent_queries。异步 insert 查询以及某些特定查询 (如 KILL) 不计入此限制。
工作负载设置 max_queries_per_second 和 max_burst_queries 通过令牌桶限流器限制该工作负载的查询数量。它保证在任意时间间隔 T 内,新开始执行的查询数不会超过 max_queries_per_second * T + max_burst_queries。
工作负载设置 max_waiting_queries 用于限制该工作负载的等待中查询数。达到该限制时,服务器会返回错误 SERVER_OVERLOADED。请注意,max_waiting_queries 不会被子工作负载继承,并且仅对叶子工作负载有意义。
工作负载和资源存储
所有工作负载和资源的定义都会以 CREATE WORKLOAD 和 CREATE RESOURCE 查询的形式持久化存储在磁盘上的 workload_path 或 ZooKeeper 中的 workload_zookeeper_path。为确保节点之间的一致性,建议使用 ZooKeeper 存储。或者,也可以将 ON CLUSTER 子句与磁盘存储配合使用。
基于配置的工作负载和资源
除了基于 SQL 的定义外,还可以在服务器配置文件中预先定义工作负载和资源。这在云环境中很有用,因为有些限制受基础设施约束,而另一些限制则可由客户调整。基于配置的对象优先于通过 SQL 定义的对象,且不能使用 SQL 命令修改或删除。
配置格式
<clickhouse>
<resources_and_workloads>
CREATE RESOURCE memory (MEMORY RESERVATION);
CREATE RESOURCE s3disk_read (READ DISK s3);
CREATE RESOURCE s3disk_write (WRITE DISK s3);
CREATE WORKLOAD all SETTINGS max_memory = '2Gi', max_io_requests = 500 FOR s3disk_read, max_io_requests = 1000 FOR s3disk_write, max_bytes_per_second = '1280Mi' FOR s3disk_read, max_bytes_per_second = '3200Mi' FOR s3disk_write;
CREATE WORKLOAD production IN all SETTINGS weight = 3;
</resources_and_workloads>
</clickhouse>该配置使用与 CREATE WORKLOAD 和 CREATE RESOURCE 语句相同的 SQL 语法。所有查询都必须是有效的。
使用建议
对于云环境,典型的设置可能包括:
- 在配置中定义根工作负载和网络 IO 资源,以设置基础设施限制
- 设置
throw_on_unknown_workload以强制执行这些限制 - 创建
CREATE WORKLOAD default IN all,以自动将限制应用于所有查询 (因为workload查询设置的默认值是 'default') - 允许用户在已配置的层级结构内创建其他工作负载
这样可以确保所有后台活动和查询都遵守基础设施限制,同时仍为用户特定的调度策略保留灵活性。
另一个用例是为异构集群中的不同节点使用不同的配置。
严格资源访问
为了强制所有查询遵循资源调度策略,可以使用一个服务器设置:throw_on_unknown_workload。如果将其设置为 true,则每个查询都必须使用有效的 workload 查询设置,否则会抛出 RESOURCE_ACCESS_DENIED 异常。如果将其设置为 false,这类查询就不会使用资源调度器,也就是说,它将获得对任意 RESOURCE 的无限制访问。查询设置 use_concurrency_control = 0 允许查询绕过 CPU 调度器,并获得对 CPU 的无限制访问。要强制启用 CPU 调度,请创建一个设置约束,使 use_concurrency_control 保持为只读常量值。
调度节点层级
从调度子系统的角度看,每个资源都对应一个调度节点层级。ClickHouse 会根据 WORKLOAD 和 RESOURCE 的定义自动创建所有必需的调度节点。调度节点属于底层实现细节,可通过 system.scheduler 表查看。
CREATE RESOURCE network_write (WRITE DISK s3)
CREATE RESOURCE memory (MEMORY RESERVATION)
CREATE WORKLOAD all SETTINGS max_io_requests = 100, max_memory = '2Gi'
CREATE WORKLOAD development IN all
CREATE WORKLOAD production IN all SETTINGS weight = 3graph TD
nw_root(["network_write"])
-->nw_all{{"all"}}
-->nw_semp[\"semaphore"/]
-->|100 concurrent requests| nw_fair("p0_fair")
-->|75% bandwidth| nw_prod{{"production"}}
-->nw_prod_q["fifo"]
nw_fair
-->|25% bandwidth| nw_dev{{"development"}}
-->nw_dev_q["fifo"]
mem_root(["memory"])
-->mem_all{{"all"}}
-->mem_semp[\"limit"/]
-->|2Gi RAM| mem_fair("p0_fair")
-->|75% RAM| mem_prod{{"production"}}
-->mem_prod_q["queue"]
mem_fair
-->|25% RAM| mem_dev{{"development"}}
-->mem_dev_q["queue"]
时间共享节点类型:
inflight_limit(constraint) - 如果并发进行中的请求数超过max_requests,或其总成本超过max_cost,则会阻塞;必须只有一个子节点。bandwidth_limit(constraint) - 如果当前带宽超过max_speed(0 表示不受限制) ,或突发量超过max_burst(默认为max_speed) ,则会阻塞;必须只有一个子节点。fair(policy) - 按照最大最小公平原则,从其某个子节点中选择下一个要处理的请求;子节点可指定weight(默认为 1) 。priority(policy) - 按照静态优先级,从其某个子节点中选择下一个要处理的请求 (值越小,优先级越高) ;子节点应指定priority(默认为 0) 。fifo(queue) - 层级结构中的叶节点,可容纳超出资源容量的请求。
空间共享节点类型:
limit- 确保子节点的总分配量不超过限制;必要时会在子树中触发驱逐流程;必须只有一个子节点。fair_allocation- 按照最大最小公平原则执行驱逐;待处理分配绝不会驱逐正在运行的分配;子节点可指定weight(默认为 1) 。precedence_allocation- 按照静态优先次序执行驱逐 (值越小,优先次序越高) ;高优先次序的待处理分配会驱逐低优先次序的分配;子节点应指定precedence(默认为 0) 。queue- 层级结构中的叶节点,可容纳正在运行和待处理的分配。
另请参阅
- system.scheduler
- system.workloads
- system.resources
- merge_workload MergeTree 设置
- merge_workload 全局服务器设置
- mutation_workload MergeTree 设置
- mutation_workload 全局服务器设置
- workload_path 全局服务器设置
- workload_zookeeper_path 全局服务器设置
- cpu_slot_preemption 全局服务器设置
- cpu_slot_quantum_ns 全局服务器设置
- cpu_slot_preemption_timeout_ms 全局服务器设置