Как писать UDF-функции Greenplum на Python: краткий обзор расширения PL/Python для дата-инженера и разработчика распределенных приложений. Как его установить, настроить и использовать: сопоставления типов данных, SQL-запросы, модули и функции.
Поддержка Python в MPP-СУБД
Поскольку освоить Python намного проще других языков программирования, например, Java или C#, неудивительно, что он сегодня очень популярен. Этот процедурный язык программирования поддерживается множеством сред разработки и исполнения программ, а также различными СУБД. Масштабируемая MPP-СУБД Greenplum (GP) тоже поддерживает Python с помощью соответствующего расширения PL/Python. Оно позволяет писать пользовательские функции базы данных Greenplum на языке Python, чтобы быстро создавать приложения, используя готовые библиотеки и модули. Можно запускать блоки кода PL/Python как анонимные блоки кода.
Это расширение по умолчанию устанавливается вместе с базой данных Greenplum, включая версию Python и PL/Python. В рамках установки Greenplum пользовательская среда gpadmin настраивается на использование Python, установленного вместе с базой данных. Примечательно, что GP предоставляет набор Python-библиотек для Data Science, которые можно использовать с языком PL/Python.
Чтобы создать и запустить UDF-функцию PL/Python в базе данных, нужно зарегистрировать язык в GP. Это следует сделать для каждой базы данных с помощью SQL-команды CREATE EXTENSION, запущенной от имени суперпользователя.
Для базы данных, где не нужен PL/Python, можно удалить его поддержку с помощью SQL-команды DROP EXTENSION, также запущенной от имени суперпользователя. Команда по умолчанию завершается ошибкой, если какие-либо существующие объекты или функции зависят от языка. Чтобы удалить все зависимые объекты, включая функции, созданные с помощью PL/Python, следует в команде DROP EXTENSION указать параметр CASCADE.
Разработка UDF-функций Greenplum на PL/Python
Тело определяемой пользователем функции PL/Python представляет собой Python-скрипт. При вызове функции ее аргументы передаются как элементы массива args[], а именованные аргументы передаются Python-скрипту как обычные переменные. Результат возвращается функцией PL/Python с оператором return или оператором yield в случае набора строк.
Пустое значение (None) переводится в Null-значение SQL. Когда UDF возвращает логический тип данных, Greenplum оценивает возвращаемое значение на предмет истинности в соответствии с правилами Python: 0 и пустая строка являются ложными, а «f» будет истинным. Сопоставление типов данных показано в таблице.
Примитивные типы данных Greenplum |
Типы данных Python |
boolean |
bool |
bytea (binary data, byte array) |
bytes |
smallint, bigint, oid |
int |
real, double |
float |
numeric |
decimal |
other primitive types |
string |
SQL null value |
None |
Передать массив значений SQL в функции PL/Python можно через список Python. Аналогично, функции PL/Python возвращают значения массива SQL в виде списка Python. Указать массив в PL/Python можно с помощью символов []. PL/Python рассматривает многомерные массивы как списки списков: передать многомерный массив функции PL/Python можно, используя вложенные списки Python. Когда функция PL/Python возвращает многомерный массив, все внутренние списки на каждом уровне должны иметь одинаковый размер.
PL/Python также принимает другие последовательности Python, такие как кортежи, в качестве аргументов функций для обратной совместимости с версиями Greenplum, где не поддерживаются многомерные массивы. В таких случаях последовательности Python всегда обрабатываются как одномерные массивы, поскольку они неоднозначны для составных типов данных.
Передать аргументы составного типа в функцию PL/Python можно, используя сопоставления Python. Имена элементов сопоставления являются именами атрибутов составных типов. Если атрибут имеет Null-значение, оно сопоставляется как None.
Можно вернуть результат составного типа как тип последовательности (кортеж или список), указав составной тип как кортеж, а не список в многомерном массиве. Примечательно, что нельзя вернуть массив составных типов в виде списка из-за неоднозначности определения, представляет ли список составной тип или это лишь другое измерение массива. Кортежи составного типа указываются с помощью символов (). Аналогичным образом, функция Python может возвращать набор скалярных или составных типов из любого типа последовательности: кортеж, список, набор.
SQL-запросы и модули PL/Python
Модуль PL/Python plpy предоставляет две функции Python для запуска SQL-запроса и подготовки плана выполнения запроса: plpy.execute и plpy.prepare(). Подготовка плана выполнения SQL-запроса полезна, если он запускается из нескольких Python-функций. PL/Python также поддерживает функцию plpy.subtransaction(), помогающую управлять вызовами plpy.execute() в явной подтранзакции.
Вызов plpy.execute() со строкой запроса и необязательным аргументом limit приводит к выполнению запроса и возврату результата в Python-объекте, который эмулирует объект списка или словаря. Доступ к строкам, возвращенным в объекте результата, можно получить по номеру строки и имени столбца. Нумерация строк набора результатов начинается с 0. Объект результата является изменяемым и имеет следующие дополнительные методы:
- nrows — количество строк, возвращенных запросом;
- status — возвращаемое значение SPI_execute().
Например, этот Python-оператор в UDF-функции PL/Python выполняет SQL-запрос выбора первых 5 строк из таблицы my_table: rv = plpy.execute(«SELECT * FROM my_table», 5)
Набор результатов хранится в объекте rv. Если в таблице my_table есть столбец my_column, доступ к нему будет следующим: my_col_data = rv[i][«my_column»]
Поскольку в рассматриваемом случае функция plpy.execute() возвращает максимум 5 строк, индекс i может быть целым числом от 0 до 4.
Функция plpy.prepare() подготавливает план выполнения запроса и вызывается со строкой запроса и списком типов параметров, если в запросе есть ссылки на них. Например, следующий оператор готовит план выполнения UDF-функции PL/Python для SQL-запроса фильтрации фамилии пользователей (last_name) из таблицы my_users по заданному условию:
plan = plpy.prepare("SELECT last_name FROM my_users WHERE first_name = $1", [ "text" ])
Здесь строковый текст — это тип данных переменной, которая передается для значения условия $1. После подготовки плана выполнения SQL-запроса можно использовать функцию plpy.execute() для его запуска: rv = plpy.execute(plan, [ «Ann» ], 5)
По аналогии с limit, третий аргумент означает предел количества возвращаемых строк и является необязательным. Подготовленный план выполнения SQL-запроса с помощью модуля PL/Python автоматически сохраняется. Чтобы эффективно использовать сохраненные планы при вызовах UDF-функций, можно использовать один из словарей постоянного хранилища Python SD или GD.
Глобальный словарь SD доступен для хранения данных между вызовами функций и является общедоступными данными, доступными для всех функций Python в рамках сеанса. Поэтому использовать глобальный словарь следует с осторожностью.
Каждая функция получает свою собственную среду выполнения в интерпретаторе Python. Например, глобальные данные и аргументы функции из myfunc недоступны для myfunc2. Исключением являются данные в словаре GD.
CREATE FUNCTION usesavedplan() RETURNS trigger AS $$ if SD.has_key("plan"): plan = SD["plan"] else: plan = plpy.prepare("SELECT 1") SD["plan"] = plan # rest of function $$ LANGUAGE plpythonu;
С точки зрения производительности импорт модуля Python является дорогостоящей операцией и может снизить скорость выполнения программы. При частом применении одного и того же модуля можно использовать глобальные переменные Python для его загрузки при первом вызове, чтобы не требовать его импорта при последующих вызовах. К примеру, следующая функция PL/Python использует словарь постоянного хранилища GD, чтобы избежать импорта модуля, если он уже был импортирован и находится в GD:
psql=# CREATE FUNCTION pytest() returns text as $$ if 'mymodule' not in GD: import mymodule GD['mymodule'] = mymodule return GD['mymodule'].sumd([1,2,3]) $$;
Модуль Python plpy реализует следующие функции для управления ошибками и сообщениями:
- debug
- log
- info
- notice
- warning
- error
- fatal
- debug
Функции сообщений plpy.error() и plpy.fatal() вызывают исключение Python, которое, если его не перехватить, распространяется на вызывающий запрос, вызывая отмену текущей транзакции или подтранзакции. Эти функции могут иметь сообщение в качестве аргумента, смысл выполнения от этого не меняется. Другие функции сообщений генерируют только сообщения с разными уровнями приоритета. Как сообщения определенного приоритета передаются клиенту и записываются в журнал сервера, контролируется параметрами конфигурации сервера Greenplum log_min_messages и client_min_messages.
При установке модуля Python в базу данных Greenplum он должен быть добавлен на всех хостах сегмента и зеркальных хостах в кластере. При расширении Greenplum следует добавить модули Python на узлы нового сегмента. Для этого можно использовать команды gpssh и gpscp, которые запустят утилиты копирования файлов на хостах Greenplum.
В заключение отметим несколько ограничений PL/Python:
- Greenplum не поддерживает триггеры PL/Python;
- PL/Python доступен только как ненадежный язык базы данных Greenplum, т.е. может быть установлен и деинсталлирован только суперпользователями;
- обновляемые курсоры (UPDATE…WHERE CURRENT OF и..WHERE CURRENT OF) не поддерживаются.
О возможности использовать Python для анализа данных в Greenplum c pandas-подобным API вместо PL/Python читайте в нашей новой статье. А в этом материале вы узнаете, как сделать запуск UDF-функций Python на узлах сегмента Greenplum быстрым и безопасным с помощью Docker-контейнеров и расширения PL/Container.
Освойте практику администрирования и эксплуатации Greenplum и Arenadata DB для эффективного хранения и аналитики больших данных на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:
Источники