Внутренности PostgreSQL: что такое Portal

24 июня 2024

Изучая исходный код PostgreSQL, можно повстречать сущность под названием Portal. Возникает закономерный вопрос — что это такое, а также где и для чего используется? Попробуем разобраться.

Чтобы вживую увидеть Portal, подключимся gdb к бэкенду PostgreSQL и поставим следующую точку останова:

(gdb) b DefineRelation
(gdb) c

Если теперь выполнить запрос:

CREATE TABLE "data"("id" INT);

… то будет получен такой стэктрейс:

(gdb) bt
#0  DefineRelation
#1  0x000055559a5ebe88 in ProcessUtilitySlow
#2  0x000055559a5ebc80 in standard_ProcessUtility
#3  0x000055559a5eafa8 in ProcessUtility
#4  0x000055559a5e87b8 in PortalRunUtility
#5  0x000055559a5e8a44 in PortalRunMulti
#6  0x000055559a5e7eb8 in PortalRun (portal=0x5555c6a287b0, ... )
#7  0x000055559a5df9e4 in exec_simple_query
#8  0x000055559a5e4e04 in PostgresMain
#9  0x000055559a5db8b8 in BackendMain
#10 0x000055559a4ed70c in postmaster_child_launch
#11 0x000055559a4f4668 in BackendStartup
#12 0x000055559a4f0d30 in ServerLoop
#13 0x000055559a4f0598 in PostmasterMain
#14 0x000055559a3dc638 in main

Продолжая экспериментировать с разными запросами, мы убеждаемся, что их исполнение всегда происходит через PortalRun(), где portal фигурирует в качестве аргумента. За дальнейшими разъяснениями обратимся к исходному коду в portalmem.c и portal.h.

Из кода мы узнаем, что же собой представляет портал. Portal — это структура, отражающая состояние исполнения запроса. Один Portal соответствует одному SQL-запросу и, соответственно, имеет один возвращаемый результат.

Портал реализован, как структура PortalData. В ней немало полей. Все их мы разбирать не буем, ограничившись лишь парой наиболее интересных:

MemoryContext portalContext;

Портал имеет выделенный ему MemoryContext. C MemoryContext’ами к этому моменту мы уже знакомы.

ResourceOwner resowner;

Также каждому порталу присваивается ResourceOwner. Это отдельная важная структура, однако ее обсуждение выходит за рамки поста. Если в двух словах, то ResourceOwner отвечает за своевременное освобождение ресурсов, таких как открытые файлы, pin’ы на разделяемые буферы, и так далее.

void (*cleanup) (Portal portal);

Хук, вызываемый при освобождении портала. Как правило, это PortalCleanup(), объявленный в portalcmds.c.

const char *sourceText;

Текст SQL-запроса. В PostgreSQL ≥ 8.4 здесь не может быть NULL.

ParamListInfo portalParams;

Параметры запроса.

Чтобы посмотреть, как эта структура заполняется, в psql выполним:

PREPARE foo(INT) AS SELECT $1;

Затем создадим точку останова на PortalRun() и выполним запрос:

EXECUTE foo(123);

Точка останова срабатывает дважды:

#0  PortalRun                                   <- второй раз
#1  0x000055559a2cc158 in ExecuteQuery
#2  0x000055559a5eb5e0 in standard_ProcessUtility
#3  0x000055559a5eafa8 in ProcessUtility
#4  0x000055559a5e87b8 in PortalRunUtility
#5  0x000055559a5e84f8 in FillPortalStore
#6  0x000055559a5e7e2c in PortalRun             <- первый раз
#7  0x000055559a5df9e4 in exec_simple_query
#8  0x000055559a5e4e04 in PostgresMain
#9  0x000055559a5db8b8 in BackendMain
#10 0x000055559a4ed70c in postmaster_child_launch
#11 0x000055559a4f4668 in BackendStartup
#12 0x000055559a4f0d30 in ServerLoop
#13 0x000055559a4f0598 in PostmasterMain
#14 0x000055559a3dc638 in main

Значение полей при первом вызове функции:

(gdb) p portal->sourceText
$1 = 0x5555c69b31c0 "EXECUTE foo(123);"

(gdb) p portal->portalParams
$2 = (ParamListInfo) 0x0

… и при втором:

(gdb) p portal->sourceText
$3 = 0x5555c6ac73f0 "PREPARE foo(int) AS SELECT $1;"

(gdb) p portal->portalParams->numParams
$4 = 1

(gdb) p portal->portalParams->params[0]
$5 = {value = 123, isnull = false, pflags = 1, ptype = 23}

Видим, как через portalParams был передан аргумент для prepared statement.

Над порталами определен ряд функций, такие как CreatePortal(), PortalDrop(), PortalDefineQuery(), GetPortalByName() и другие. Полный список есть в portal.h, а датели реализации можно найти в portalmem.c. Заинтересованные читатели могут ознакомиться с этими функциями самостоятельно. Они простые.

Это базовые операции над порталами, а вся логика находится в файле pquery.c. Основных функций две — это PortalStart():

/* Подготовить портал к исполнению */
void
PortalStart(Portal portal, ParamListInfo params,
            int eflags, Snapshot snapshot)

… и PortalRun():

/* Исполнить запрос из портала */
bool
PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
          DestReceiver *dest, DestReceiver *altdest,
          QueryCompletion *qc)

В pquery.c есть комментарии относительно всех аргументов, возвращаемых значений, и т.д. Пример практического использования функций CreatePortal(), PortalDefineQuery(), PortalStart(), PortalRun() и PortalDrop() можно найти в функции exec_simple_query(), уже знакомой нам по представленным выше стектрейсам.

Конечно же, полная картина сложнее. Однако углубляться в изучение порталов еще сильнее как будто бы нет особого смысла. Даже разработчики PostgreSQL исключительно редко трогают порталы. В этом легко убедиться при помощи команды git log. Обычно достаточно в общих чертах понимать, что они из себя представляют. Если же когда-нибудь вам придется с головой погрузиться в детали реализации, то теперь у вас есть отправная точка. Для получения самой полной и актуальной информации рекомендуется читать код и ставить эксперименты под отладчиком.

Дополнение: В продолжение темы порталов вас может заинтересовать статья Внутренности PostgreSQL: сетевой протокол.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.