Ресурсы
- Ресурсы с разделением по времени (CPU, IO, слоты запросов) - управляют запросами на ресурсы, которые ставятся в очередь в листьях иерархии планирования. Запросы планируются в соответствии с политиками и ограничениями, заданными этой иерархией. Запросы на ресурсы создаются, когда запрос обращается к соответствующему ресурсу. Например, когда запрос читает данные с диска или использует CPU для обработки, запросы на ресурсы создаются для каждого кванта выполненной работы или для объёма байтов, отправленных или полученных через сокет.
- Ресурсы с разделением по пространству (память) - управляют выделением ресурсов в листьях иерархии планирования. Выделения могут быть активными или ожидающими. Ожидающие выделения блокируются, пока не освободится достаточно места или не будет вытеснено (завершено) другое выделение. Решения принимаются на основе лимитов и политик, заданных иерархией. Между выделениями и запросами (или фоновой активностью) существует взаимно однозначное соответствие. Выделение создаётся, когда запрос начинает выполняться, и освобождается, когда он завершает работу. Активные выделения могут динамически увеличиваться или уменьшаться в размере.
Иерархия рабочих нагрузок
max_*, действуют отдельно на каждом хосте. Рабочая нагрузка “user” дополнительно распределяет свои ресурсы между рабочими нагрузками “development” и “production”, причём у “production” ресурсов в 3 раза больше, чем у “development”:
SETTINGS workload = 'name'. Подробности см. в разделе Разметка рабочих нагрузок.
Для настройки рабочей нагрузки можно использовать следующие настройки:
priority- (только для с разделением по времени) рабочие нагрузки одного уровня обслуживаются в соответствии со статическими значениями (меньшее значение означает более высокий приоритет). Определяет вытеснение.precedence- (только для с разделением по пространству) рабочие нагрузки одного уровня допускаются в соответствии со статическими значениями (меньшее значение означает более высокое старшинство). Определяет вытеснение и допуск.weight- рабочие нагрузки одного уровня с одинаковым статическим приоритетом или старшинством распределяют ресурсы в соответствии с весами на справедливой основе. Влияет на вытеснение и допуск.max_io_requests- ограничение на количество параллельных запросов ввода-вывода в этой рабочей нагрузке.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- максимальное количество CPU-секунд, которое рабочая нагрузка может потребить без ограничения скорости из-заmax_cpus.max_memory- ограничение на общий объём памяти, зарезервированной для этой рабочей нагрузки.
max_bytes_per_second = '10Mi' будет иметь ограничение пропускной способности 10 MB/s отдельно для каждого ресурса чтения и записи. Если требуется общее ограничение для чтения и записи, рассмотрите возможность использования одного и того же ресурса для доступа READ и WRITE.
Нельзя задать разные иерархии рабочих нагрузок для разных ресурсов. Однако можно задать разные значения настройки рабочей нагрузки для конкретного ресурса:
CREATE OR REPLACE WORKLOAD.
Настройки рабочей нагрузки преобразуются в соответствующий набор узлов планировщика. Подробности на более низком уровне см. в описании типов и параметров узлов планировщика.
Маркировка рабочих нагрузок
workload, чтобы различать разные рабочие нагрузки. Если workload не задана, используется значение “default”. Обратите внимание, что другое значение можно указать с помощью профилей настроек. Ограничения настроек можно использовать, чтобы сделать workload неизменяемой, если вы хотите, чтобы все запросы пользователя помечались фиксированным значением настройки workload.
workload можно также назначить для фоновых операций. Для слияний и мутаций используются настройки сервера merge_workload и mutation_workload соответственно. Эти значения также можно переопределить для конкретных таблиц с помощью настроек MergeTree merge_workload и mutation_workload.
Планирование CPU
- Master thread — первый поток, который начинает выполнять запрос или фоновую операцию, такую как merge или mutation.
- Worker thread — дополнительные потоки, которые master может порождать для выполнения ресурсоёмких задач.
max_threads. В этом случае входящие запросы будут блокироваться и ждать слот CPU, чтобы их master-потоки могли начать выполнение. Чтобы этого избежать, можно использовать следующую конфигурацию:
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 перестают действовать. Вместо них для ограничения числа CPU, выделяемых для конкретной рабочей нагрузки, используется настройка рабочей нагрузки max_concurrent_threads. Чтобы добиться прежнего поведения, создайте только ресурс WORKER THREAD, задайте для рабочей нагрузки all настройку max_concurrent_threads равной concurrent_threads_soft_limit_num и используйте настройку запроса workload = "all". Эта конфигурация соответствует значению “fair_round_robin” для настройки concurrent_threads_scheduler.Потоки и CPU
- Ограничение числа потоков:
max_concurrent_threadsиmax_concurrent_threads_ratio_to_cores - Троттлинг CPU:
max_cpus,max_cpu_shareиmax_burst_cpu_seconds
max_threads. Второй ограничивает потребление CPU рабочей нагрузкой с помощью алгоритма token bucket. Он не влияет напрямую на число потоков, но ограничивает суммарное потребление CPU всеми потоками рабочей нагрузки.
Троттлинг token bucket с 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: Выполняет операции ввода-вывода или другие блокирующие системные вызовы (например, ожидает mutex). Время, проведенное в этом состоянии, не учитывается при троттлинге CPU.
max_cpu_share её предел составляет 70% от общих ресурсов CPU. В то же время для ingestion гарантируется не менее 0.8 * 0.25 = 20%, при этом верхнего предела у неё нет.
Если вы хотите максимально загрузить CPU на сервере ClickHouse, не используйте
max_cpus и max_cpu_share для корневой рабочей нагрузки all. Вместо этого задайте более высокое значение max_concurrent_threads. Например, в системе с 8 CPU установите max_concurrent_threads = 16. Это позволит 8 потокам выполнять задачи CPU, а ещё 8 потокам — обрабатывать операции I/O. Дополнительные потоки создадут нагрузку на CPU, что обеспечит применение правил планирования. Напротив, значение max_cpus = 8 никогда не создаст нагрузки на CPU, потому что сервер не сможет превысить 8 доступных CPU.Резервирование памяти
Планирование с резервированием памяти — экспериментальная функция. Оно действует только при наличии ресурса
MEMORY RESERVATION, а его SQL-интерфейс и поведение могут измениться в будущих выпусках. Оно пока не поддерживается для слияний и мутаций, а вытеснение выполняющегося запроса работает в режиме best-effort: оно срабатывает в следующей точке синхронизации памяти запроса, а не мгновенно.reserve_memory больше нуля, выделение создается в состоянии pending. Выделение в состоянии pending резервирует запрошенный объем памяти в иерархии рабочих нагрузок. Если доступной памяти недостаточно, выделение остается в состоянии pending, пока не освободится достаточно памяти или другие выделения не будут вытеснены (завершены). Когда выделение получает допуск, оно переходит в состояние running. Выделение в состоянии running может динамически увеличиваться или уменьшаться в зависимости от потребления памяти запросом. Жизненный цикл выделения можно представить следующей диаграммой состояний:
Ожидающие выделения в листовой рабочей нагрузке обрабатываются в порядке FIFO. Если ожидающие выделения есть у нескольких рабочих нагрузок, они обрабатываются в соответствии с настройками старшинства и весов. Сначала обслуживаются рабочие нагрузки с более высоким старшинством. Дочерние рабочие нагрузки с одинаковым старшинством делят память в соответствии с весами по принципу max-min fairness, то есть первой обслуживается рабочая нагрузка с меньшим нормализованным использованием памяти (текущее использование плюс запрошенное увеличение, делённые на вес). При вытеснении применяется обратная логика. Когда нужно освободить память, первыми вытесняются рабочие нагрузки с меньшим старшинством и более высоким нормализованным использованием памяти.
Обратите внимание: ресурсы с разделением по времени используют priority, а ресурсы с разделением пространства — старшинство. Это независимые настройки, и для них можно задавать разные значения. Более высокий priority подразумевает неразрушающее вытеснение (задержку или throttling), тогда как более высокое старшинство может подразумевать разрушающее вытеснение (остановку с ошибкой). Рабочая нагрузка может иметь высокий priority для планирования CPU, но то же значение старшинства для резервирования памяти, чтобы не вытеснять другие рабочие нагрузки и не терять уже выполненную ими работу.
Каждая рабочая нагрузка с ограничением max_memory гарантирует, что общий объём памяти, выделенной в её поддереве, не превышает этот лимит. Если ожидающее или увеличивающееся выделение превысит лимит, запускается процедура вытеснения для освобождения памяти. Процедура вытеснения выбирает жертву, которую нужно завершить. Рабочая нагрузка, являющаяся наименьшим общим предком для инициатора и жертвы, предотвращает вытеснение в следующих ситуациях:
- Ожидающее выделение не может вытеснить выполняющиеся выделения в той же рабочей нагрузке. (Рабочие нагрузки инициатора и жертвы совпадают).
- Ожидающее выделение с меньшим старшинством никогда не завершает рабочую нагрузку с более высоким старшинством.
- Ожидающее выделение не может завершить выделение с тем же старшинством. Обратите внимание, что выполняющиеся выделения с тем же старшинством могут вытеснять друг друга на основе нормализованного использования памяти. Если вытеснение предотвращено или не освобождает достаточно памяти, новое выделение блокируется до тех пор, пока не освободится достаточный объём памяти. Эти правила позволяют ставить избыточные запросы в очередь в зависимости от нагрузки на память и дают удобный способ избежать ошибок MEMORY_LIMIT_EXCEEDED.
Ограничения рабочей нагрузки не зависят от других способов ограничить потребление памяти, таких как настройка запроса max_memory_usage. Их можно использовать вместе для более точного контроля потребления памяти. Можно задавать независимые ограничения памяти на основе пользователей (а не рабочих нагрузок). Это менее гибко и не предоставляет таких возможностей, как резервирование памяти и постановка ожидающих запросов в очередь. См. Memory overcommit
max_waiting_queries ограничивает количество ожидающих выделений для рабочей нагрузки. Когда лимит достигнут, сервер возвращает ошибку 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. Запросы async insert и некоторые специальные запросы, такие как KILL, не учитываются в этом ограничении.
Настройки рабочей нагрузки max_queries_per_second и max_burst_queries ограничивают количество запросов для рабочей нагрузки с помощью throttler’а token bucket. Это гарантирует, что за любой интервал времени 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. Все запросы должны быть корректными.
Рекомендации по использованию
- Определите корневую рабочую нагрузку и сетевые ресурсы ввода-вывода в конфигурации, чтобы задать ограничения инфраструктуры
- Установите
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 как неизменяемое значение только для чтения.
Не устанавливайте
throw_on_unknown_workload в true, пока не выполнен CREATE WORKLOAD default. Иначе это может привести к проблемам при запуске сервера, если во время старта будет выполнен запрос без явно заданной настройки workload.Иерархия узлов планировщика
inflight_limit(ограничение) — блокирует, если либо число одновременно обрабатываемых запросов превышаетmax_requests, либо их суммарная стоимость превышаетmax_cost; должен иметь один дочерний узел.bandwidth_limit(ограничение) — блокирует, если текущая пропускная способность превышаетmax_speed(0 означает отсутствие ограничений) или размер всплеска превышаетmax_burst(по умолчанию равенmax_speed); должен иметь один дочерний узел.fair(политика) — выбирает следующий запрос для обслуживания из одного из дочерних узлов по принципу max-min fairness; дочерние узлы могут задаватьweight(по умолчанию 1).priority(политика) — выбирает следующий запрос для обслуживания из одного из дочерних узлов в соответствии со статическими приоритетами (меньшее значение означает более высокий приоритет); дочерние узлы должны задаватьpriority(по умолчанию 0).fifo(очередь) — листовой узел иерархии, способный удерживать запросы, превышающие ёмкость ресурса.
limit— гарантирует, что суммарное выделение дочернего узла никогда не превышает заданный предел, и при необходимости запускает процедуру вытеснения в поддереве; должен иметь один дочерний узел.fair_allocation— выполняет вытеснение по принципу max-min fairness; ожидающие выделения никогда не вытесняют выполняющиеся; дочерние узлы могут задаватьweight(по умолчанию 1).precedence_allocation— выполняет вытеснение в соответствии со статическим приоритетом (меньшее значение означает более высокий приоритет); ожидающее выделение с более высоким приоритетом вытесняет выделения с более низким приоритетом; дочерние узлы должны задаватьprecedence(по умолчанию 0).queue— листовой узел иерархии, способный удерживать выполняющиеся и ожидающие выделения.
Устаревшая XML-конфигурация
storage_configuration сервера:
Чтобы включить планирование ввода-вывода для конкретного диска, необходимо указать 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 глобальная настройка сервера