18 Занятие 18: PL/pgSQL / Отладка
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev1_18_plpgsql_debug.html Archive: DEV1-handouts-16.zip Страниц PDF: 28
Page 1: PL/pgSQL / Отладка
Авторские права
© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Проверки корректности
- Отладчик PL/pgSQL
- Служебные сообщения и различные способы их реализации
- Трассировка сеансов
Page 3: Проверки корректности
Проверки времени компиляции и времени выполнения
- plpgsql.extra_warnings
- plpgsql.extra_errors
- дополнительные проверки в расширении plpgsql_check
- Проверки в коде
- команда ASSERT
- Тестирование
Комментарии преподавателя
Отладка предполагает выполнение программы и изучение возникающих проблем, обычно с помощью специализированного отладчика или с помощью вывода отладочных сообщений.
Но можно заранее исключить определенные классы ошибок, включив проверку исходного кода во время компиляции и выполнения. Это управляется параметрами plpgsql.extra_warnings и plpgsql.extra_errors, как рассматривалось в теме «PL/pgSQL. Выполнение запросов».
https://postgrespro.ru/docs/postgresql/16/plpgsql-development-tips#PLPGS QL-EXTRA-CHECKS
Там же рассматривалось применение расширения plpgsql_check, которое обеспечивает более широкий спектр проверок.
Еще одна возможность обезопасить свой код — добавить в него проверки условий, которые всегда должны выполняться (т. н. «sanity check»). Удобный способ для этого — оператор ASSERT в PL/pgSQL.
https://postgrespro.ru/docs/postgresql/16/plpgsql-errors-and-messages#PL PGSQL-STATEMENTS-ASSERT
Нельзя не сказать и о важности тестирования кода. Тестирование не только позволяет изначально убедиться, что код работает так, как задумано, но и облегчает его дальнейшую модификацию — дает уверенность в том, что изменения не поломали существующий функционал. Мы не будем затрагивать эту тему; отметим только, что тестирование кода, работающего с базой данных, может оказаться непростым делом из-за необходимости подготовки тестовых примеров.
Page 4: Проверки корректности
Команда ASSERT позволяет указать условия, нарушения которых являются непредвиденной ошибкой. Можно провести определенную аналогию между такими условиями и ограничениями целостности в базе данных. Пример: функция, возвращающее номер подъезда по номеру квартиры:
Убедиться в правильности работы функции можно при помощи тестирования, проверив результат на некоторых «интересных» значениях:
Но при некорректных входных значениях функция будет выдавать бессмысленный результат, который, например, может быть передан дальше в другие подпрограммы, которые из-за этого тоже могут повести себя некорректно. Тестирование только кода данной функции здесь никак не поможет.
Можно обезопасить себя, добавив проверку:
Теперь некорректный вызов сразу же приведет к ошибке.
Page 5: PL/pgSQL Debugger
Состав
- расширение pldbgapi
- встроенная поддержка в некоторых графических средах
- Возможности
- установка точек прерывания
- пошаговое выполнение
- проверка и установка значений переменных
- не требуется изменение кода
- отладка работающих приложений
Комментарии преподавателя
PL/pgSQL Debugger — это отладчик для PL/pgSQL. Представляет собой расширение pldbgapi, которое официально поддерживается разработчиками PostgreSQL.
Расширение pldbgapi — это набор интерфейсных функций для сервера PostgreSQL, которые позволяют устанавливать точки прерывания, пошагово выполнять код программ, проверять и устанавливать значения переменных.
Исходный код отлаживаемых программ изменять не требуется, что дает возможность выполнять отладку работающих приложений. То есть процесс отладки не обязательно запускать отдельно, можно подключиться к уже работающему сеансу и начать его отладку.
Непосредственно пользоваться этими функциями неудобно, они в первую очередь предназначены для вызова из кода графических сред разработки. Некоторые из таких сред (включая DBeaver) имеют встроенный интерфейс для отладки. Но чтобы им воспользоваться, сначала все равно придется установить расширение pldbgapi в соответствующую базу данных.
Исходный код отладчика доступен по ссылке:
https://github.com/EnterpriseDB/pldebugger
Page 6: Отладка с PL/pgSQL Debugger
Выполним отладку одной из функций нашего приложения. Пакет поддержки отладчика postgresql-16-pldebugger уже установлен в виртуальной машине курса. Теперь нам нужно обеспечить загрузку разделяемой библиотеки, для этого установим параметр и перезапустим сервер:
Установим расширение отладчика в базу данных bookstore:
Отладку проведем с помощью графической утилиты DBeaver.
Page 7: Служебные сообщения
Не только отладка кода
- мониторинг долго выполняющихся процессов
- ведение журнала приложения
- Подходы к реализации
- вывод на консоль или в журнал сервера
- запись в таблицу или в файл
- передача информации другим процессам
Комментарии преподавателя
Второй способ отладки предполагает добавление в важные места кода служебных сообщений, выводящих информацию о текущем контексте. Анализируя сообщения, можно понять, что именно пошло не так.
Помимо собственно отладки, служебные сообщения могут выполнять и другие функции. Сообщения помогут определить, на каком этапе выполнения находится долго выполняющийся процесс. По аналогии с журналом сообщений СУБД приложение может записывать в свой собственный журнал важную информацию. Например, данные о запуске отчетов: название отчета, пользователь, когда и с какими параметрами запускался и т. д. Такая информация может сильно облегчить работу специалистов поддержки.
Можно выделить несколько подходов к реализации служебных сообщений в PL/pgSQL. Помимо уже знакомой команды RAISE, позволяющей выводить сообщения на консоль (а также в журнал сообщений сервера), можно отправлять сообщения другому процессу, записывать сообщения в таблицу или в файл.
При выборе того или иного подхода нужно обращать внимание на многие нюансы. Являются ли сообщения транзакционными (отправляются не дожидаясь окончания транзакции или только при ее завершении)? Можно ли одновременно использовать в нескольких сеансах? Как организовать доступ к журналу сообщений и как очищать его от старых записей? Какое влияние оказывают журналирование на производительность? Требуется ли модификация исходного кода?
Page 8: Команда RAISE
{#source-figure-008 caption=“Source slide page 8: Команда RAISE”}
Комментарии преподавателя
Мы уже встречались с командой RAISE. Она служит и для вызова исключительных ситуаций, что подробно рассматривалось в теме «PL/pgSQL. Обработка ошибок», и для отправки сообщений. Причем сообщения можно не только отправлять клиенту, но и записывать в журнал сервера.
В простом случае для отладки нужно добавить вызовы RAISE NOTICE в код функции, запустить функцию на выполнение (например в сеансе psql) и проанализировать получаемые по ходу выполнения сообщения. Сообщения RAISE нетранзакционные: они отправляются асинхронно и не зависят от статуса завершения транзакции.
Для управления отправкой сообщений используются уровень сообщения (DEBUG, LOG, NOTICE, INFO, WARNING) и параметры сервера. От значений параметров зависит, будет ли сообщение отправлено клиенту (client_min_messages) и/или записано в журнал сервера (log_min_messages). Сообщение будет отправлено, если уровень команды RAISE равен значению соответствующего параметра или больше его (находится правее на рисунке).
Значения параметров по умолчанию настроены так, что сообщения с уровнем NOTICE отправляются только клиенту, с уровнем LOG — только в журнал, а с уровнем WARNING — и клиенту, и в журнал.
Сообщения с уровнем INFO всегда отправляются клиенту, их нельзя перехватить параметром client_min_messages.
https://postgrespro.ru/docs/postgresql/16/plpgsql-errors-and-messages
Page 9: Команда RAISE
Создадим функцию для подсчета количества строк в таблице, имя которой передается во входном параметре.
Для динамического выполнения текст команды лучше предварительно записывать в переменную. В случае ошибки можно проанализировать значение этой переменной.
Строка, начинающаяся с «NOTICE» — наша отладочная информация. RAISE можно использовать для отслеживания хода выполнения долгого запроса. Предположим, что внутри кода, решающего некую задачу, четко выделяются три этапа выполнения. И по ходу работы подпрограммы мы хотим понимать, на каком из них находимся. Для этого на каждом этапе будем вызывать такую процедуру:
Основная процедура по завершении очередного этапа работы вызывает процедуру debug_message:
Команда RAISE выдает сообщения сразу, а не по окончании работы подпрограммы:
Page 10: Page 10
Такой подход удобен, когда можно вызвать функцию в отдельном сеансе. Если же функция вызывается из приложения, то удобнее писать и затем смотреть в журнал сервера. Перепишем процедуру debug_message для выдачи сообщения с уровнем, установленным в пользовательском параметре app.raise_level:
Для целей примера установим параметр на уровне сеанса:
Теперь в «обычной» жизни (app.raise_level = NONE) отладочные сообщения не будут выдаваться:
Запуская функцию в отдельном сеансе, мы можем получить отладочные сообщения, выставив app.raise_level в NOTICE:
Если же мы хотим включить отладку в приложении с записью в журнал сервера, то переключаем app.raise_level в LOG:
Смотрим в журнал сервера:
Управляя параметрами app.raise_level, log_min_messages и client_min_messages, можно добиться различного поведения при выводе отладочных сообщений.
Note Важно, что для этого не нужно менять код приложения.
Page 11: Процесс→процесс (IPC)
NOTIFY → LISTEN
- команды SQL
- транзакционное выполнение, неудобно для отладки
- Статус сеанса
- параметр application_name
- виден в представлении pg_stat_activity и выводе команды ps
- можно использовать в журнальных сообщениях
Комментарии преподавателя
Серверные процессы в PostgreSQL могут обмениваться информацией между собой.
Среди встроенных решений можно отметить следующие.
● Использование команд NOTIFY для отправки сообщений в одном процессе и LISTEN для получения в другом. Но эти команды являются транзакционными, поэтому они неудобны для отладки: 1. Сообщения отправляются только при фиксации транзакции, а не сразу после выполнения NOTIFY. Поэтому невозможно следить за ходом процесса. 2. Если транзакция завершится неуспешно, то сообщения вообще не будут отправлены.
● Использование параметра application_name. Сеанс с долго выполняющимся процессом может периодически записывать статус выполнения в application_name. В другом сеансе администратор может опрашивать представление pg_stat_activity, содержащее подробную информацию о всех выполняющихся сеансах. Также значение application_name обычно видно в выводе команды ps. Значение application_name также можно записывать в журнал сервера (настраивая параметр log_line_prefix). Это облегчит поиск нужных строк в журнале.
https://postgrespro.ru/docs/postgresql/16/runtime-config-logging#RUNTIME-CONFIG-LOGGING-WHAT
Page 12: Статус сеанса
Посмотрим, как использовать для отладки параметр application_name. Первый сеанс меняет значение этого параметра, второй — периодически опрашивает представление pg_stat_activity.
Новый вариант процедуры:
Запускаем в первом сеансе:
Во втором с паузой в две секунды обновляем строку из pg_stat_activity:
Page 13: Процесс→таблица
Расширение dblink
- входит в состав сервера
- накладные расходы на создание соединения
- Автономные транзакции
- Postgres Pro Enterprise
Комментарии преподавателя
Еще один способ сохранения отладочных сообщений — запись в таблицу базы данных.
К плюсам данного подхода относится то, что параллельная работа и доступ журналу обеспечиваются средствами самой СУБД.
Однако нужно позаботиться о том, чтобы вставка в таблицу была нетранзакционной. Для этого можно использовать расширение dblink, идущее в составе сервера PostgreSQL. Это расширение позволяет открыть новое соединение к той же самой базе данных, поэтому вставка в таблицу выполняется в другой — независимой — транзакции.
К минусам данного подхода относится то, что открытие нового соединения требует дополнительных ресурсов сервера.
Подробнее работа с dblink рассматривается в курсе DEV2.
https://postgrespro.ru/docs/postgresql/16/dblink
В Postgres Pro Enterprise доступен механизм автономных транзакций, не имеющий таких существенных накладных расходов, которые характерны для dblink (см. курс PGPRO «Возможности Postgres Pro Enterprise»).
Page 14: Запись в таблицу: расширение dblink
Установим расширение:
Создаем таблицу для записи сообщений. В таблице полезно сохранять информацию о пользователе и времени вставки. Столбец id нужен для гарантированной сортировки результата в порядке добавления строк.
Теперь перепишем процедуру так, чтобы она добавляла записи в таблицу log. Процедура открывает новый сеанс, выполняет вставку в отдельной транзакции и закрывает сеанс.
Для проверки запустим процедуру long_running в отдельной транзакции, которую в конце откатим.
Убедимся, что в таблице сохранились все вызовы debug_message. По значениям ts можно проверить, сколько времени прошло между вызовами.
Page 15: Процесс→файл
Расширение adminpack
- входит в состав сервера
- в том числе позволяет записывать текстовые файлы
- Недоверенные языки
- например, PL/Perl
Комментарии преподавателя
Записывать сообщения можно и в файл операционной системы.
Для этого, например, можно использовать расширение adminpack, позволяющее записывать данные в любой файл, доступ к которому есть у пользователя ОС postgres.
Другой вариант — написать функцию на недоверенном языке (таком, как PL/Perl — plperlu), которая будет выполнять ту же задачу. Различные языки серверного программирования рассматриваются в курсе DEV2.
https://postgrespro.ru/docs/postgresql/16/adminpack
Page 16: Запись в файл: pg_file_write
Установим расширение:
В очередной раз перепишем процедуру debug_message, теперь она будет записывать отладочную информацию в файл. Пользователь postgres, запустивший экземпляр СУБД, должен иметь возможность записи в этот файл, поэтому расположим его в домашнем каталоге этого пользователя.
Функция записывает отдельной строкой в файл журнала сообщение, переданное параметром, вместе с информацией о том, кто и когда записал строку. Для проверки запустим long_running в отдельной транзакции, которую в конце откатим.
Проверим, что записи в журнале появились. Чтобы пользователь student мог получить доступ к этому файлу, нужно использовать команду sudo:
Page 17: Трассировка SQL
Стандартная трассировка в журнал сообщений
- накладные расходы на запись в журнал
- большой размер файла журнала
- требуются инструменты для анализа
- нужен доступ к журналу (безопасность)
- Настройки
- долго выполняющиеся команды
- log_min_duration_statement
- какие команды записывать
- log_statement
- контекст сообщения
- log_line_prefix
- …
Комментарии преподавателя
В некоторых случаях может оказаться полезной трассировка всего происходящего в коде. Штатными средствами можно получить в журнале сообщений сервера SQL-запросы сеансов. Надо учитывать:
● Высоконагруженное приложение может выполнять огромное число запросов. Их запись в файл журнала может оказывать влияние на производительность подсистемы ввода-вывода.
● В большинстве случаев требуются специальные инструменты для анализа такого объема данных. Стандарт де-факто — pgBadger. https://github.com/darold/pgbadger
● К файлу журнала на сервере может не быть доступа у разработчи-ков приложений. К тому же в журнале производственной системы могут оказаться команды, содержащие конфиденциальную информацию.
Для настройки трассировки имеется ряд параметров, основные из которых приведены на слайде. Полный список:
https://postgrespro.ru/docs/postgresql/16/runtime-config-logging
Необязательно устанавливать конфигурационные параметры для всего кластера. Их действие можно ограничить отдельными сеансами при помощи команд SET, ALTER DATABASE, ALTER ROLE (как рассказы-валось в темах «Обзор базового инструментария. Установка и управление, psql» и «Организация данных. Логическая структура»).
Page 18: Трассировка SQL-команд
Расширение auto\_explain
- запись в журнал планов выполнения запросов
- трассировка вложенных запросов
- Настройки
- планы долгих команд
- auto_explain.log_min_duration
- вложенные запросы
- auto_explain.log_nested_statements
- …
Комментарии преподавателя
При включении трассировки SQL-команды попадают в журнал в том виде, в каком они отправлены на сервер. Если была вызвана подпрограмма PL/pgSQL, то в журнал будет записан только вызов этой подпрограммы (например, оператор SELECT или CALL), но не будет тех команд, которые выполняются внутри подпрограммы.
Чтобы увидеть в журнале запросы не только верхнего уровня, но и вложенные, потребуется штатное расширение auto_explain.
Как следует из названия расширения, его основная задача — записывать в журнал не только текст команды, но и план ее выполнения. Это тоже может оказаться полезно, хотя относится не к трассировке как таковой, а к оптимизации запросов (которая рассматривается в курсе QPT).
https://postgrespro.ru/docs/postgresql/16/auto-explain
Page 19: Трассировка PL/pgSQL
Расширение plpgsql\_check
- накладные расходы на запись сообщений
- большой объем выдачи
- Основные настройки
- включение трассировки
- plpgsql_check.enable_tracer
- plpgsql_check.tracer
- уровень сообщений
- plpgsql_check.tracer_errlevel
Комментарии преподавателя
Чтобы разобраться по журналу, какой процедурный код выполнялся, придется сопоставить SQL-запросы с подпрограммами на PL/pgSQL, а это может оказаться непросто. Стандартной возможности для трассировки PL/pgSQL-кода не предусмотрено, но расширение Павла Стехуле plpgsql_check (которое упоминалось в теме «PL/pgSQL. Выполнение запросов») позволяет ее выполнить.
Подобная трассировка вызывает большие накладные расходы и должна использоваться только для отладки, но не в промышленной эксплуатации.
https://github.com/okbob/plpgsql\_check
Page 20: Трассировка сеансов
Простой пример включения трассировки — установка параметра log_statement в значение all (записывать все команды, включая DDL, модификацию данных и запросы).
Выполним какой-нибудь запрос:
И выключим трассировку:
Информация о выполненных командах окажется в журнале сервера:
Однако в журнал попадает только команда верхнего уровня, но не запрос внутри функции get_count. Воспользуемся расширением auto_explain. Это расширение не нужно устанавливать в базу данных, но требуется загрузить в память. Загрузку можно настроить для всего экземпляра с помощью параметра shared_preload_libraries, либо проделать однократно для текущего процесса:
Установим трассировку всех команд независимо от длительности выполнения:
Включим трассировку вложенных запросов:
Сообщения выводятся с помощью того же механизма, что использует команда RAISE. По умолчанию используется уровень LOG, что обычно соответствует выводу в журнал. Изменив параметр, можно получать трассировку непосредственно в консоли:
Повторим запрос:
Page 21: Page 21
Мы видим не только вызов функции, но и вложенный запрос вместе с планами выполнения.
Page 22: Итоги
PL/pgSQL Debugger — API отладчика, используется в графических средах разработки
- Служебные сообщения — вывод на консоль, запись в журнал сообщений сервера, в таблицу или в файл, передача другим процессам
- Возможность трассировки сеансов
Page 23: Практика
- Измените функцию get_catalog так, чтобы динамически формируемый текст запроса записывался в журнал сообщений сервера.
- В приложении выполните несколько раз поиск, заполняя разные поля, и убедитесь, что команды SQL формируются правильно. 2. Включите трассировку команд SQL на уровне сервера.
- Поработайте в приложении и проверьте, какие команды
- попадают в журнал сообщений.
- Выключите трассировку.
Комментарии преподавателя
- Для включения трассировки установите значение параметра log_min_duration_statement в 0 и перечитайте конфигурацию. В журнал будут записываться все команды и время их выполнения.
Проще всего это сделать командой ALTER SYSTEM SET. Другие способы рассматривались в теме «Обзор базового инструментария. Установка и управление, psql». Не забудьте перечитать измененный конфигурационный файл.
После просмотра журнала следует вернуть значение параметра log_min_duration_statement в значение по умолчанию (−1), чтобы отключить трассировку. Удобный способ — команда ALTER SYSTEM RESET.
Page 24: 1. Функция get_catalog
Текст динамического запроса формируем в отдельной переменной, которую перед выполнением запишем в журнал сервера. Для более полной информации включим в сообщение значения переданных в функцию параметров. Отладочные строки в журнале можно найти по подстроке «DEBUG get_catalog». После отладки команду RAISE LOG можно удалить или закомментировать.
2. Включение и выключение трассировки SQL-запросов
Чтобы включить трассировку всех запросов на уровне сервера, можно выполнить:
Чтобы выключить:
Page 25: Page 25
Последние две команды попали в журнал сообщений:
Page 26: Практика+
1. Включите трассировку PL/pgSQL-кода средствами расширения plpgsql\_check и проверьте ее работу на примере нескольких подпрограмм, вызывающих одна другую. 2. При выводе отладочных сообщений из PL/pgSQL-кода важно понимать, к какой подпрограмме они относятся. В демонстрации имя функции выводилось вручную. Реализуйте функционал, автоматически добавляющий к тексту сообщений имя текущей функции или процедуры.
Комментарии преподавателя
Для включения трассировки загрузите расширение plpgsql_check в память сеанса командой LOAD, затем установите в сеансе оба параметра plpgsql_check.enable_tracer и plpgsql_check.tracer в значение «on».
Имя подпрограммы можно получить, разобрав стек вызовов. Воспользуйтесь подходом из практического задания 3 к теме «Обработка ошибок».
Page 27: 1. Трассировка с помощью plpgsql_check
Загрузим расширение (в данном случае устанавливать его в базу данных командой CREATE EXTENSION не нужно):
Включим трассировку:
Несколько функций, вызывающих друг друга:
Пример работы трассировки:
Выводятся не только события начала и окончания работы функций, но и значения параметров, а также затраченное на выполнение время (в расширении есть и возможность профилирования, которую мы не рассматриваем). Выключим трассировку:
Page 28: 2. Имя функции в отладочных сообщениях
Напишем процедуру, которая выводит верхушку стека вызовов (за исключением самой процедуры трассировки). Сообщение выводится с отступом, который соответствует глубине стека.
Пример работы трассировки: