Пользовательские функции и таблицы Executable
В данной статье описаны способы расширения встроенной функциональности ADQM/ClickHouse для выполнения специализированных задач:
-
пользовательские функции SQL — создаются из лямбда-выражений, которые могут использовать конструкции и функции SQL;
-
исполняемые пользовательские функции | табличная функция executable | табличные движки Executable и ExecutablePool — инструменты, позволяющие вызывать внешние исполняемые программы или скрипты (например, Bash, Python, C++) для обработки данных.
Создайте таблицу sales с тестовыми данными, которые будут использоваться в примерах, приведенных в данной статье:
CREATE TABLE sales (sale_id UInt32, product_name String, product_price UInt32, quantity UInt8)
ENGINE = MergeTree() ORDER BY (sale_id);
INSERT INTO sales VALUES
(1, 'product_1', 100, 15),
(2, 'product_2', 200, 20),
(3, 'product_3', 300, 5),
(4, 'product_2', 200, 25),
(5, 'product_1', 100, 50);
┌─sale_id─┬─product_name─┬─product_price─┬─quantity─┐ 1. │ 1 │ product_1 │ 100 │ 15 │ 2. │ 2 │ product_2 │ 200 │ 20 │ 3. │ 3 │ product_3 │ 300 │ 5 │ 4. │ 4 │ product_2 │ 200 │ 25 │ 5. │ 5 │ product_1 │ 100 │ 50 │ └─────────┴──────────────┴───────────────┴──────────┘
Пользовательские функции SQL
Чтобы создать функцию из лямбда-выражения, используйте запрос CREATE FUNCTION:
CREATE FUNCTION <function_name> [ON CLUSTER <cluster_name>] AS (<parameter_0>, <parameter_1> ...) -> <expression>;
где:
-
<function_name>— имя функции, которое должно быть уникально среди всех пользовательских и системных функций; -
<parameter_0>, <parameter_1> …— список параметров, в котором должны быть перечислены все используемые функцией переменные; -
<expression>— выражение, состоящее из параметров функции, констант, операторов и вызовов других функций. Рекурсивные функции не допускаются.
Удалить пользовательскую функцию SQL можно запросом DROP FUNCTION <function_name>.
Пример
Создайте функцию, вычисляющую произведение аргументов:
CREATE FUNCTION sql_udf AS (a, b) -> a * b;
Убедитесь, что функция добавлена в список функций ADQM:
SELECT name, create_query FROM system.functions WHERE origin = 'SQLUserDefined';
┌─name────┬─create_query─────────────────────────────────┐ 1. │ sql_udf │ CREATE FUNCTION sql_udf AS (a, b) -> (a * b) │ └─────────┴──────────────────────────────────────────────┘
Используйте созданную функцию для расчета общей стоимости каждой продажи в тестовой таблице sales:
SELECT sale_id, product_price, quantity, sql_udf(product_price, quantity) AS total_price FROM sales;
┌─sale_id─┬─product_price─┬─quantity─┬─total_price─┐ 1. │ 1 │ 100 │ 15 │ 1500 │ 2. │ 2 │ 200 │ 20 │ 4000 │ 3. │ 3 │ 300 │ 5 │ 1500 │ 4. │ 4 │ 200 │ 25 │ 5000 │ 5. │ 5 │ 100 │ 50 │ 5000 │ └─────────┴───────────────┴──────────┴─────────────┘
Исполняемые пользовательские функции
Исполняемая пользовательская функция (User-Defined Function, UDF) вызывает внешнюю программу или скрипт для обработки данных. Чтобы создать такую функцию, необходимо выполнить следующие действия:
-
Опишите конфигурацию функции в XML-файле:
<functions> <function> <name>...</name> <type>executable</type> <return_type>...</return_type> <return_name>...</return_name> <argument> <type>...</type> <name>...</name> </argument> <argument> <type>...</type> <name>...</name> </argument> <format>...</format> <command>...</command> </function> </functions>Параметры конфигурации исполняемой пользовательской функцииname
Имя функции
type
Вариант запуска команды. Возможные значения:
-
executable— запускается одна команда; -
executable_pool— создается пул команд.
return_type
Тип возвращаемого значения
return_name
Имя возвращаемого значения. Необходимо указать, если имя возвращаемого значения является частью сериализации для формата пользовательской функции (например, Native или JSONEachRow). Значение по умолчанию —
resultargument
Описание аргумента, включающее его тип (
type) и, опционально, имя (name). Имена аргументов необходимо указывать, если они являются частью сериализации для формата пользовательской функции (например, Native или JSONEachRow). Значение имени аргумента по умолчанию —cc номером аргумента. Каждый аргумент функции описывается отдельноformat
Формат, в котором аргументы передаются в команду
command
Имя скрипта для выполнения или команда, если значение параметра
execute_direct—false. Команда должна читать аргументы из stdin и выводить результат в stdout. Обработка должна выполняться в цикле, то есть после обработки группы аргументов команда должна ожидать следующую группуlifetime
Интервал перезагрузки функции в секундах. Если значение
0(по умолчанию), функция не перезагружаетсяexecute_direct
Если значение
1(по умолчанию), выполняется поиск команды, указанной вcommand, в каталоге с файлами пользовательских скриптов, который определяется параметромuser_scripts_pathв конфигурации сервера. Дополнительные аргументы скрипта можно указать, разделяя их пробелами (например:<script_name> <arg1> <arg2>). Еслиexecute_direct = 0, содержимоеcommandпередается как аргумент дляbin/sh -cВ конфигурации исполняемой пользовательской функции можно также дополнительно указать параметры выполнения скрипта, описание которых приведено ниже.
-
-
В параметре
user_defined_executable_functions_configконфигурации сервера (config.xml) укажите путь к XML-файлу с конфигурацией функции (путь можно указать абсолютным или относительно конфигурационного файла сервера). Конфигурации исполняемых пользовательских функций могут находиться в одном или нескольких XML-файлах — в последнем случае можно использовать wildcard-символы*и?, чтобы указать путь к файлам, например:<user_defined_executable_functions_config>*_function.xml</user_defined_executable_functions_config> -
В параметре
user_scripts_pathконфигурации сервера укажите каталог, где будут храниться скрипты для исполняемых пользовательских функций (по умолчанию /var/lib/clickhouse/user_scripts), и поместите в этот каталог скрипт, указанный в конфигурации функции какcommand. Убедитесь, что:-
На сервере ADQM установлено программное обеспечение, необходимое для запуска исполняемого скрипта.
-
На выполнение скрипта выдано разрешение.
-
Пример
Данный пример показывает, как создать пользовательскую функцию, выполняющую Python-скрипт. Предварительно проверьте, что на сервере ADQM установлен Python и все необходимые библиотеки.
Опишите конфигурацию функции в файле /etc/clickhouse-server/UDFs.xml:
<functions>
<function>
<type>executable</type>
<name>total_price_function</name>
<return_type>UInt32</return_type>
<return_name>total_price</return_name>
<argument>
<type>UInt32</type>
<name>product_price</name>
</argument>
<argument>
<type>UInt8</type>
<name>quantity</name>
</argument>
<format>JSONEachRow</format>
<command>total_price.py</command>
</function>
</functions>
Укажите путь к файлу с конфигурацией исполняемой пользовательской функции в параметре user_defined_executable_functions_config файла config.xml:
<user_defined_executable_functions_config>UDFs.xml</user_defined_executable_functions_config>
В файле /var/lib/clickhouse/user_scripts/total_price.py напишите Python-скрипт:
#!/usr/bin/python3
import sys
import json
if __name__ == '__main__':
for line in sys.stdin:
data = json.loads(line)
price = data['product_price']
quantity = data['quantity']
result = {'total_price': price * quantity}
print(json.dumps(result), end='\n')
sys.stdout.flush()
Дайте разрешение на выполнение скрипта:
$ sudo chmod +x /var/lib/clickhouse/user_scripts/total_price.py
Убедитесь, что функция добавлена в ADQM:
SELECT name FROM system.functions WHERE origin = 'ExecutableUserDefined';
┌─name─────────────────────┐ 1. │ total_price_function │ └──────────────────────────┘
Выполните запрос на выборку данных из таблицы sales, используя функцию total_price_function для вычисления стоимости каждой продажи:
SELECT sale_id, product_price, quantity, total_price_function(product_price, quantity) AS total_price FROM sales;
┌─sale_id─┬─product_price─┬─quantity─┬─total_price─┐ 1. │ 1 │ 100 │ 15 │ 1500 │ 2. │ 2 │ 200 │ 20 │ 4000 │ 3. │ 3 │ 300 │ 5 │ 1500 │ 4. │ 4 │ 200 │ 25 │ 5000 │ 5. │ 5 │ 100 │ 50 │ 5000 │ └─────────┴───────────────┴──────────┴─────────────┘
Табличная функция executable
Табличная функция executable создает таблицу на основе выходных данных функции, определенной в пользовательском скрипте, который выводит строки в stdout.
Синтаксис табличной функции executable в общем виде:
executable(<script_name>, <format>, <table_structure>, [<input_query> ...] [,SETTINGS <parameter_name>=<value>, ...])
где:
-
<script_name>— имя файла исполняемого скрипта, сохраненного в каталоге, который определяется параметромuser_scripts_pathв конфигурации сервера (по умолчанию — /var/lib/clickhouse/user_scripts/). Скрипт может считывать данные из любого источника. -
<format>— формат, в котором функция будет принимать данные. -
<table_structure>— структура генерируемой таблицы (указывается в виде<column_name1> <data_type1>, <column_name2> <data_type2>, …). -
<input_query>— запрос (или набор запросов), результаты которого будут передаваться в stdin для чтения скриптом. -
SETTINGS— дополнительные настройки выполнение скрипта.
Перед вызовом табличной функции executable убедитесь, что на сервере ADQM установлены все необходимые пакеты для запуска исполняемого скрипта, а также что на выполнения скрипта выдано разрешение.
|
ПРИМЕЧАНИЕ
|
Пример
Заполните файл /var/lib/clickhouse/user_scripts/add_tax.py текстом Python-скрипта, который будет добавлять налог к цене продукта:
#!/usr/bin/python3
import sys
if __name__ == '__main__':
for line in sys.stdin:
split_line = line.split()
product = split_line[0]
price = int(split_line[1])
price_with_tax = price*1.2
print(product + '\t' + str(price) + '\t' + str(price_with_tax) + '\n', end='')
sys.stdout.flush()
Дайте разрешение на выполнение скрипта:
$ sudo chmod +x /var/lib/clickhouse/user_scripts/add_tax.py
Используйте табличную функцию executable, чтобы получить таблицу с ценами уникальных продуктов из таблицы продаж sales и вычислить цены с учетом налога с помощью скрипта add_tax.py:
SELECT * FROM executable(
'add_tax.py',
TabSeparated,
'product String, price UInt32, price_with_tax Float32',
(SELECT DISTINCT ON (product_name) product_name, product_price FROM sales));
Результат:
┌─product───┬─price─┬─price_with_tax─┐ 1. │ product_1 │ 100 │ 120 │ 2. │ product_2 │ 200 │ 240 │ 3. │ product_3 │ 300 │ 360 │ └───────────┴───────┴────────────────┘
Табличные движки Executable и ExecutablePool
Табличные движки Executable и ExecutablePool позволяют создать таблицу, строки которой генерируются из указанного скрипта:
-
Executable — запускает скрипт при каждом запросе данных;
-
ExecutablePool — поддерживает пул постоянных процессов и берет процессы из пула для чтения данных.
Синтаксис запроса для создания таблицы на основе движка Executable или ExecutablePool в общем виде:
CREATE TABLE <table_name> (<column_name1> <data_type1>, <column_name2> <data_type2>, ...)
ENGINE = Executable|ExecutablePool(<script_name>, <format>, [<input_query> ...])
[SETTINGS <parameter_name>=<value>, ...];
где:
-
<script_name>— исполняемый скрипт, на основе выходных данных которого будут строиться данные таблицы<table_name>каждый раз, когда в нее будет направляться запросSELECT. Скрипт может считывать данные из любого источника. Файл скрипта должен быть сохранен в каталоге, определяемом параметром конфигурации сервераuser_scripts_path(по умолчанию — /var/lib/clickhouse/user_scripts/). -
<format>— формат данных, в котором таблица будет принимать данные. -
<input_query>— запрос (или набор запросов), результаты которого будут передаваться в stdin для чтения скриптом. -
SETTINGS— дополнительные параметры выполнения скрипта.
Пример
Создайте таблицу типа Executable, которая будет заполняться данными, генерируемыми скриптом add_tax.py из примера выше:
CREATE TABLE prices_with_tax (product String, price UInt32, price_with_tax Float32)
ENGINE = Executable('add_tax.py', TabSeparated, (SELECT DISTINCT ON (product_name) product_name, product_price FROM sales));
Запрос CREATE TABLE создает таблицу, но не вызывает скрипт. Скрипт вызывается запросом на выборку данных из таблицы:
SELECT * FROM prices_with_tax;
┌─product───┬─price─┬─price_with_tax─┐ 1. │ product_1 │ 100 │ 120 │ 2. │ product_2 │ 200 │ 240 │ 3. │ product_3 │ 300 │ 360 │ └───────────┴───────┴────────────────┘
Параметры выполнения скрипта
При создании исполняемой пользовательской функции, вызове табличной функции executable и создании таблиц на основе движков Executable/ExecutablePool (базовый синтаксис запросов приводится в соответствующих разделах выше) можно указать следующие параметры, регулирующие выполнение скрипта.
send_chunk_header |
Контролирует, нужно ли отправлять количество строк в каждом блоке данных перед отправкой блока на обработку. Значение по умолчанию — |
command_termination_timeout |
Максимальное время (в секундах), в течение которого команда должна завершиться после закрытия конвейера. Если команда не завершается, то процессу отправляется сигнал |
command_read_timeout |
Время ожидания чтения данных из stdout команды (в миллисекундах). Значение по умолчанию — |
command_write_timeout |
Время ожидания записи данных в stdin команды (в миллисекундах). Значение по умолчанию — |
pool_size |
Размер пула процессов. Если значение Параметр релевантен для:
|
max_command_execution_time |
Максимальное время выполнения команды исполняемого скрипта для обработки блока данных (в секундах). Значение по умолчанию — Параметр релевантен для:
|