Автоматическое прерывание запросов: руководство по настройке SQL Block Rule и Workload Policy
Автоматическое прерывание запросов представляет собой защитный механизм, который не позволяет длительным или ресурсоёмким запросам нарушать стабильность кластера. Когда запрос превышает заданные ограничения по ресурсам или времени, механизм автоматически завершает его, не позволяя одному запросу исчерпать системные ресурсы или замедлить выполнение других рабочих нагрузок.
Doris предоставляет две стратегии прерывания, которые действуют на разных этапах жизненного цикла запроса:
| Критерий | SQL Block Rule (прерывание на этапе планирования) | Workload Policy (прерывание во время выполнения) |
|---|---|---|
| Этап применения | Этап планирования запроса: перехват до начала выполнения | Этап выполнения запроса: прерывание во время выполнения |
| Основание для решения | Оценочный объём сканирования, сопоставление SQL с шаблонами | Показатели в реальном времени: фактическое время выполнения, использование памяти, объём сканирования |
| Точность | Основано на оценке стоимости, возможны ложные срабатывания | Основано на мониторинге в реальном времени, более высокая точность |
| Сценарий использования | Блокировка известных опасных шаблонов SQL и полного сканирования таблиц | Прерывание выполняющихся запросов, превышающих ожидаемые показатели |
| Поддерживаемые операторы | Любые операторы (DDL, DML) | select, insert into select, stream load, routine load |
SQL Block Rule
SQL Block Rule перехватывает операторы, соответствующие определённым шаблонам, на этапе планирования, не допуская их выполнения. Обычно эти правила настраивают администраторы баз данных, чтобы предотвратить известные рискованные операции, такие как полное сканирование таблиц и вызовы опасных функций.
Свойства правила
Каждое правило задаётся следующими свойствами, которые определяют его поведение и область действия:
| Свойство | Описание | Значение |
|---|---|---|
sql | Регулярное выражение для сопоставления с текстом запроса | Строка с регулярным выражением Java |
sqlHash | Хеш для сопоставления с текстом запроса | Строка с хешем SQL |
cardinality | Максимальное количество строк, разрешённых для сканирования | Положительное целое число |
partition_num | Максимальное количество партиций, разрешённых для сканирования | Положительное целое число |
tablet_num | Максимальное количество tablet, разрешённых для сканирования | Положительное целое число |
require_partition_filter | Требуется ли указывать эффективный фильтр по партициям в запросах к партиционированным внутренним таблицам и таблицам Hive. Поддерживается в Doris 4.0.7 и более поздних версиях ветки 4.0, а также в Doris 4.1.4 и более поздних версиях ветки 4.1. | "true" или "false" |
global | Является ли правило глобальным | "true" (глобальное) / "false" (действует только для привязанных пользователей) |
enable | Включено ли правило | "true" / "false" |
Сценарии использования и примеры
Сценарий 1: ограничение количества сканируемых строк
Проблема: операции полного сканирования таблицы, такие как SELECT * FROM t, потребляют значительные ресурсы IO и CPU на BE.
Решение: задайте верхний предел количества строк, сканируемых в одной таблице. Запросы, превышающие этот порог, блокируются.
CREATE SQL_BLOCK_RULE rule_card
PROPERTIES
(
"cardinality" = "1000",
"global" = "true",
"enable" = "true"
);
Если при сканировании одной таблицы требуется прочитать более 1000 строк, запрос отклоняется.
Сценарий 2: ограничение количества сканируемых партиций
Проблема: сканирование слишком большого количества партиций значительно увеличивает потребление ресурсов CPU на BE. При запросах к внешним таблицам оно также создаёт дополнительную нагрузку на сеть и увеличивает затраты на получение метаданных. Обычно это происходит из-за отсутствующих или некорректно заданных условий фильтрации по партициям.
Решение: задайте максимальное количество партиций, которые может сканировать один запрос.
CREATE SQL_BLOCK_RULE rule_part_num
PROPERTIES
(
"partition_num" = "30",
"global" = "true",
"enable" = "true"
);
Если при сканировании одной таблицы требуется просмотреть более 30 партиций, запрос отклоняется.
Сценарий 3: ограничение количества сканируемых tablet
Проблема: сканирование слишком большого количества tablet также значительно увеличивает потребление ресурсов CPU на BE.
Решение: задайте максимальное количество tablet, которые может сканировать один запрос.
CREATE SQL_BLOCK_RULE rule_tablet_num
PROPERTIES
(
"tablet_num" = "200",
"global" = "true",
"enable" = "true"
);
Если при сканировании одной таблицы требуется просмотреть более 200 tablet, запрос отклоняется.
Сценарий 4: блокировка определённых шаблонов SQL
Проблема: некоторые шаблоны запросов, например вызовы определённых функций или выполнение опасных операций, могут приводить к чрезмерной вычислительной сложности или длительному планированию.
Решение: блокируйте такие запросы с помощью сопоставления с регулярными выражениями.
Пример 1: блокировка использования определённой функции
Следующее правило блокирует все запросы, содержащие функцию abs:
CREATE SQL_BLOCK_RULE rule_abs
PROPERTIES(
"sql"="(?i)abs\\s*\\(.+\\)",
"global"="true",
"enable"="true"
);
Пояснение регулярного выражения:
(?i): сопоставление без учёта регистраabs: имя целевой функции\\s*: допускает любые пробельные символы между именем функции и открывающей скобкой\\(.+\\): сопоставляется с аргументами функции
Пример 2: блокировка опасных операций
Аналогичным образом с помощью регулярных выражений можно блокировать set global (для предотвращения непреднамеренного изменения переменных) или truncate table (для предотвращения непреднамеренного удаления данных).
Регулярные выражения используют синтаксис Java. Подробности см. в документации Pattern.
Сценарий 5: настройка правил для определённых пользователей
Проблема: по умолчанию правила действуют глобально, но в некоторых случаях ограничение должно применяться только к определённым пользователям.
Решение: создайте неглобальное правило и привяжите его к нужному пользователю.
-
При создании правила задайте для
globalзначение"false":CREATE SQL_BLOCK_RULE rule_001
PROPERTIES (
"sql"="select \\* from t",
"global" = "false",
"enable" = "true"
); -
Привяжите правило к нужному пользователю:
SET PROPERTY FOR 'root' 'SQL_block_rules' = 'rule_001'; -
Убедитесь, что правило действует:
MySQL root@127.0.0.1:test> select * from t;
(1105, 'errCode = 2, detailMessage = errCode = 2, detailMessage = SQL match regex SQL block rule: rule_001')
Управление несколькими правилами:
- Чтобы привязать к пользователю несколько правил, перечислите их имена через запятую в списке правил, например
'rule_001,rule_002'. - Чтобы удалить все правила пользователя, задайте пустую строку в качестве списка правил, например
SET PROPERTY FOR 'root' 'SQL_block_rules' = '';.
Сценарий 6: обязательная фильтрация по партициям
В партиционированных внутренних таблицах и таблицах Hive отсутствие фильтров по столбцам партиционирования может привести к сканированию всех партиций. Для запросов к таким партиционированным таблицам можно сделать эффективный фильтр по партициям обязательным:
CREATE SQL_BLOCK_RULE rule_require_partition_filter
PROPERTIES
(
"require_partition_filter" = "true",
"global" = "true",
"enable" = "true"
);
После включения правила запросы вида SELECT * FROM partitioned_table будут блокироваться. Запросы с предикатами по партициям, например SELECT * FROM partitioned_table WHERE dt = '2024-01-01', по-прежнему смогут выполняться.
Примечания
- Метод оценки: количество сканируемых строк, партиций и tablet оценивается на этапе планирования для наихудшего случая (учитывается только отсечение партиций и tablet; другие условия фильтрации не учитываются). Запросы, у которых фактический объём сканирования меньше заданного значения, также могут блокироваться.
- Влияние на производительность: сопоставление с регулярными выражениями требует значительных вычислительных ресурсов. Слишком большое количество правил с регулярными выражениями или их чрезмерная сложность создают существенную нагрузку на CPU на FE, поэтому добавляйте их с осторожностью.
- Временное отключение правила: измените значение свойства
"enable"правила на"false", чтобы временно отключить его без удаления.
Workload Policy
Workload Policy отслеживает показатели в реальном времени на этапе выполнения запроса и применяет такие действия, как прерывание или изменение переменных, к запросам, превышающим ожидаемые показатели. В отличие от статической оценки SQL Block Rule на этапе планирования, Workload Policy принимает решения на основе фактических данных во время выполнения и обеспечивает более высокую точность.
Workload Policy поддерживается начиная с Doris 2.1. Поддержка различных типов рабочих нагрузок приведена ниже:
| Тип рабочей нагрузки | Поддерживается |
|---|---|
| select | Да |
| insert into select | Да |
| insert into values | Нет |
| stream load | Да |
| routine load | Да |
| backup | Нет |
| compaction | Нет |
Создание и настройка
Для создания политики используйте команду CREATE WORKLOAD POLICY. Требуется привилегия admin_priv.
Базовый синтаксис:
CREATE WORKLOAD POLICY <policy_name>
CONDITIONS(<condition_expr>)
ACTIONS(<action_expr>)
PROPERTIES('<key>'='<value>');
В следующем примере создаётся политика, которая отменяет запросы, выполняющиеся дольше 1000 мс:
CREATE WORKLOAD POLICY test_cancel_policy
CONDITIONS(query_time > 1000)
ACTIONS(cancel_query)
PROPERTIES('enabled'='true');
Условия срабатывания
Условия определяют, когда срабатывает политика. Несколько условий разделяются запятыми (,), что соответствует логическому AND.
| Условие | Описание |
|---|---|
username | Имя пользователя, передаваемое в запросе. Начиная с Doris 4.1.3 его можно сочетать с показателями выполнения на стороне BE, такими как query_time, be_scan_rows, be_scan_bytes и query_be_memory_bytes, для запуска cancel_query. Это условие поддерживает только оператор равенства (=), а имя пользователя не может быть пустым |
be_scan_rows | Количество строк, сканируемых SQL-запросом в рамках одного процесса BE. При параллельном выполнении значения суммируются |
be_scan_bytes | Количество байтов, сканируемых SQL-запросом в рамках одного процесса BE. При параллельном выполнении значения суммируются (единица измерения: байты) |
query_time | Время выполнения SQL-запроса в одном процессе BE (единица измерения: миллисекунды) |
query_be_memory_bytes | Объём памяти, используемой SQL-запросом в рамках одного процесса BE. При параллельном выполнении значения суммируются (единица измерения: байты) |
Действия
Действия определяют, что происходит при выполнении условий. В настоящее время в политике можно задать только одно действие.
| Действие | Описание |
|---|---|
cancel_query | Отменить запрос |
Свойства политики
| Свойство | Описание | Значение по умолчанию |
|---|---|---|
enabled | Включена ли политика. Значение: true или false | true |
priority | Приоритет в диапазоне от 0 до 100. Чем больше значение, тем выше приоритет. Если запрос соответствует нескольким политикам, действует только политика с наивысшим приоритетом | 0 |
workload_group | Имя привязанной группы Workload Group. Если оно задано, политика применяется только к запросам в этой Workload Group. Если значение пустое, политика применяется ко всем запросам | Пустое значение (глобально) |
Привязка к Workload Group
Чтобы ограничить действие политики определённой группой Workload Group, привяжите её с помощью свойства workload_group:
CREATE WORKLOAD POLICY test_cancel_big_query
CONDITIONS(query_time > 1000)
ACTIONS(cancel_query)
PROPERTIES('workload_group'='normal');
Приведённая выше форма 'workload_group'='normal' допустима только в необлачном режиме (с совместным размещением хранилища и вычислений). В облачном режиме (с разделением хранилища и вычислений) требуется полная форма <compute_group>.<workload_group>, например 'workload_group'='compute_group_a.normal'; иначе оператор завершается с ошибкой: workload_group must be '<compute_group>.<workload_group>' in cloud mode. Подробности см. в разделе CREATE WORKLOAD POLICY.
Примеры
Пример 1: прерывание длительных запросов
Из следующего журнала аудита видно, что SQL-запрос обычно выполняется за 4.5 секунды:
MySQL [hits]> SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\\.)?([^/]+)/.*$', '\\1') AS k, AVG(length(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;
+-----------------------------------------------------------------------+------------------+----------+---------------------------------------------------------------------------------------------------------------------+
| k | l | c | min(Referer) |
+-----------------------------------------------------------------------+------------------+----------+---------------------------------------------------------------------------------------------------------------------+
| 1 | 85.4611926713085 | 67259319 | http://%26ad%3D1%25EA%25D0%26utm_source=web&cd=19590&input_onlist/би-2 место будущей кондицин |
| http:%2F%2Fwwww.regnancies/search&evL8gE&where=all&filmId=bEmYZc_WTDE | 69 | 207347 | http:%2F%2Fwwww.regnancies/search&evL8gE&where=all&filmId=bEmYZc_WTDE |
| http://новострашная | 31 | 740277 | http://новострашная |
| http://loveche.html?ctid | 24 | 144901 | http://loveche.html?ctid |
| http://rukodeliveresult | 23 | 226135 | http://rukodeliveresult |
| http://holodilnik.ru | 20 | 133893 | http://holodilnik.ru |
| http://smeshariki.ru | 20 | 210736 | http://smeshariki.ru |
| http:%2F%2Fviewtopic | 20 | 391115 | http:%2F%2Fviewtopic |
| http:%2F%2Fwwww.ukr | 19 | 655178 | http:%2F%2Fwwww.ukr |
| http:%2F%2FviewType | 19 | 148907 | http:%2F%2FviewType |
| http://state=2008 | 17 | 139630 | http://state=2008 |
+-----------------------------------------------------------------------+------------------+----------+---------------------------------------------------------------------------------------------------------------------+
11 rows in set (4.50 sec)
Создайте политику, которая отменяет любой запрос, выполняющийся дольше 3 секунд:
CREATE WORKLOAD POLICY test_cancel_3s_query
CONDITIONS(query_time > 3000)
ACTIONS(cancel_query);
Выполните тот же SQL-запрос ещё раз: теперь он будет прерван:
MySQL [hits]> SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\\.)?([^/]+)/.*$', '\\1') AS k, AVG(length(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;
ERROR 1105 (HY000): errCode = 2, detailMessage = (127.0.0.1)[CANCELLED]query cancelled by workload policy,id:12345
Пример 2: прерывание ресурсоёмких запросов определённого пользователя во время выполнения
Начиная с Doris 4.1.3 можно сочетать username с показателями выполнения на стороне BE, чтобы отменять только запросы определённого пользователя. Следующая политика отменяет запросы пользователя test_user, если время их выполнения в одном BE превышает 3000 мс:
CREATE WORKLOAD POLICY cancel_user_long_query
CONDITIONS(username='test_user', query_time > 3000)
ACTIONS(cancel_query);
Аналогичным образом username можно сочетать с be_scan_rows, be_scan_bytes или query_be_memory_bytes, чтобы ограничить объём сканирования или использование памяти BE для определённого пользователя.
Примечания
- Задержка асинхронного выполнения: политики проверяются асинхронным потоком каждые 500 мс, поэтому их применение происходит с некоторой задержкой. Очень короткие запросы могут завершиться до проверки и обойти политику.
- Механизм приоритетов: запрос может соответствовать нескольким политикам, но действует только политика с наивысшим приоритетом (наибольшим значением
priority). - Ограничение на изменение: в настоящее время нельзя напрямую изменить действие и условие существующей политики. Удалите политику и создайте её заново.