资源
- 时间共享资源 (CPU、IO、查询槽位) - 管理在调度层级叶子节点排队的资源请求。请求会按照层级中定义的策略和约束进行调度。当查询访问相应资源时,就会创建资源请求。例如,当查询从磁盘读取数据,或使用 CPU 进行处理时,系统会在每完成一个工作量子,或通过套接字发送或接收一定字节数时创建资源请求。
- 空间共享资源 (内存) - 管理调度层级叶子节点上的资源分配。分配可能处于运行中或待处理状态。待处理的分配会被阻塞,直到释放出足够空间,或其他分配被驱逐 (killed) 。相关决策基于层级中定义的限制和策略。分配与查询 (或后台活动) 之间是一一对应的关系。分配会在查询开始执行时创建,并在查询结束时释放。运行中的分配其大小可以动态增减。
工作负载层级结构
max_* 设置定义的限制都是按主机生效的。工作负载 “user” 会将其资源进一步细分给 “development” 和 “production” 工作负载,其中 “production” 获得的资源是 “development” 的 3 倍:
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 查询。
工作负载设置会转换为一组合适的调度节点。有关底层细节,请参阅调度节点类型和选项的说明。
工作负载标记
workload 为查询打标,以区分不同的工作负载。如果未设置 workload,则使用值 “default”。请注意,也可以通过 profile 指定其他值。如果你希望某个用户的所有查询都使用固定的 workload 设置值进行标记,可以使用设置约束将 workload 固定为该值。
workload 设置。合并和变更分别使用 merge_workload 和 mutation_workload 服务器设置。这些值还可以通过 merge_workload 和 mutation_workload MergeTree 设置在特定表级别进行覆盖。
CPU 调度
- Master thread — 开始执行查询或 merge、变更等后台活动的第一个线程。
- Worker thread — 可由 master 派生出的额外线程,用于执行 CPU 密集型任务。
max_threads 的值较高时,大量工作线程很容易独占 CPU 资源。这样一来,新到达的查询就只能阻塞,等待为其 master 线程分配一个 CPU 插槽后才能开始执行。为避免这种情况,可以使用以下配置:
cpu_slot_preemption 服务器设置启用抢占。如果启用,每个线程都会定期续用其 CPU 插槽 (根据 cpu_slot_quantum_ns 服务器设置) 。如果 CPU 过载,这种续用可能会阻塞执行。当执行被阻塞较长时间时 (参见 cpu_slot_preemption_timeout_ms 服务器设置) ,查询会缩容,并且并发运行的线程数会动态减少。请注意,工作负载之间的 CPU 时间公平性可以得到保证,但在同一工作负载内部,不同查询之间的公平性在某些边缘情况下可能无法保证。
声明 CPU 资源后,
concurrent_threads_soft_limit_num 和 concurrent_threads_soft_limit_ratio_to_cores 设置将不再生效。此时,系统会改用工作负载设置 max_concurrent_threads 来限制分配给特定工作负载的 CPU 数量。若要实现之前的行为,请仅创建 WORKER THREAD 资源,将工作负载 all 的 max_concurrent_threads 设置为与 concurrent_threads_soft_limit_num 相同的值,并使用 workload = "all" 查询设置。此配置等同于将 concurrent_threads_scheduler 设置为 “fair_round_robin”。线程与 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 限流。
max_cpu_share,其总 CPU 资源上限为 70%。而 ingestion 至少有 0.8 * 0.25 = 20% 的保障份额,同时没有上限。
如果你希望最大化 ClickHouse server 的 CPU 利用率,请避免对根工作负载
all 使用 max_cpus 和 max_cpu_share。相反,应将 max_concurrent_threads 设置得更高。例如,在一个具有 8 个 CPU 的系统上,可设置 max_concurrent_threads = 16。这样一来,8 个线程可以运行 CPU 任务,同时另外 8 个线程可以处理 I/O 操作。额外的线程会制造 CPU 压力,从而确保调度规则得以执行。相比之下,设置 max_cpus = 8 永远不会产生 CPU 压力,因为 server 无法超过可用的 8 个 CPU。内存预留
内存预留调度目前处于 Experimental 阶段。只有在存在
MEMORY RESERVATION 资源时才会生效,其 SQL 接口和行为可能会在未来的发行版中发生变化。当前尚不支持合并和变更,对正在运行的查询进行驱逐也只是尽力而为:它会在查询的下一个内存同步点生效,而不是立即生效。MEMORY RESERVATION 资源,并通过工作负载设置至少为总预留内存配置一个限制:
reserve_memory 设置大于零,则该分配会以 pending 状态创建。处于 pending 状态的分配会在工作负载层级中预留所请求的内存量。如果没有足够的可用内存,该分配会一直保持 pending,直到释放出足够的内存,或者其他分配被逐出 (killed) 。当分配获准后,它会变为 running。处于 running 状态的分配会根据查询的内存消耗动态增大或缩小。分配的生命周期可以用下图的状态图表示:
叶子工作负载的待处理分配按 FIFO 顺序准入。当多个工作负载都有待处理分配时,会根据优先次序和权重设置决定准入顺序。优先次序更高的工作负载会先获得分配。具有相同优先次序的同级工作负载会按权重以最大最小公平原则共享内存,这意味着归一化内存使用量更低的工作负载 (即当前使用量加上请求增加量后,再除以权重) 会先获得分配。在驱逐时则采用相反的逻辑。当需要释放内存时,优先次序较低且归一化内存使用量较高的工作负载会先被驱逐。
请注意,时间共享资源使用 priority,而空间共享资源使用 优先次序。它们是彼此独立的设置,可以设为不同的值。更高的 priority 表示非破坏性抢占 (延迟或节流) ,而更高的 优先次序 则可能意味着破坏性驱逐 (报错并停止) 。某个工作负载在 CPU 调度中可以具有较高的 priority,但在内存预留中采用相同的 优先次序,以避免驱逐其他工作负载并丢失它们已经完成的工作。
每个设置了 max_memory 限制的工作负载都会确保其子树中已分配的总内存不超过该限制。如果待处理分配或增长中的分配会超出该限制,则会启动驱逐流程来释放内存。驱逐流程会选择一个要被终止的目标。killer 和 victim 的最近公共祖先工作负载会在以下情况下阻止驱逐:
- 待处理分配不能驱逐同一工作负载中正在运行的分配。 (killer 和 victim 工作负载相同) 。
- 优先次序较低的待处理分配绝不会终止优先次序较高的工作负载。
- 待处理分配不能终止优先次序相同的分配。请注意,优先次序相同的运行中分配可能会根据归一化内存使用量相互驱逐。 如果驱逐被阻止,或者未能释放足够的内存,则新分配会被阻塞,直到有足够的内存被释放。这些规则允许查询在内存压力下排队等待,并提供了一种便捷方式来避免 MEMORY_LIMIT_EXCEEDED 错误。
工作负载限制独立于其他限制内存消耗的方法,例如查询设置 max_memory_usage。它们可以结合使用,以更好地控制内存消耗。还可以基于用户 (而非工作负载) 设置独立的内存限制,但这种方式灵活性较差,也不提供内存预留和待处理查询排队等功能。参见 Memory overcommit
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 以下。
来看一个配置示例:
查询槽位调度
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 不会被子工作负载继承,并且仅对叶子工作负载有意义。
被阻塞的查询会无限期等待,并且在所有约束条件都满足之前,不会出现在
SHOW PROCESSLIST 中。工作负载和资源存储
CREATE WORKLOAD 和 CREATE RESOURCE 查询的形式持久化存储在磁盘上的 workload_path 或 ZooKeeper 中的 workload_zookeeper_path。为确保节点之间的一致性,建议使用 ZooKeeper 存储。或者,也可以将 ON CLUSTER 子句与磁盘存储配合使用。
基于配置的工作负载和资源
配置格式
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 保持为只读常量值。
除非已执行
CREATE WORKLOAD default,否则不要将 throw_on_unknown_workload 设置为 true。如果在启动期间执行了未显式设置 workload 的查询,可能会导致服务器启动问题。调度节点层级
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- 层级结构中的叶节点,可容纳正在运行和待处理的分配。
已弃用的 XML 配置
storage_configuration:
要为特定磁盘启用 IO 调度,必须在存储配置中指定 read_resource 和/或 write_resource。这样可以告诉 ClickHouse,对于给定磁盘的每个读写请求应使用哪个资源。读取资源和写入资源可以指向同一个资源名称,这对于本地 SSD 或 HDD 很有用。多个不同的磁盘也可以指向同一个资源,这对于远程磁盘很有用:例如,如果你希望在 “production” 和 “development” 工作负载 之间公平分配网络带宽。
示例:
另请参阅
- 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 全局服务器设置