56.4. Планирование запросов в обертке внешних данных#
56.4. Планирование запросов в обертке внешних данных #
Функции обратного вызова FDW GetForeignRelSize
,
GetForeignPaths
, GetForeignPlan
,
PlanForeignModify
, GetForeignJoinPaths
,
GetForeignUpperPaths
и PlanDirectModify
должны соответствовать работе планировщика Tantor BE.
Вот некоторые заметки о том, что они должны делать.
Вся информация в root
и baserel
может быть использована для сокращения объема информации, которую необходимо извлечь из внешней таблицы (и, следовательно, снизить стоимость).
Особенно интересен baserel->baserestrictinfo
, так как он содержит ограничения (предложения WHERE
), которые должны использоваться для фильтрации выбираемых строк. (FDW сам по себе не обязан применять эти ограничения, так как ядро исполнителя может проверять их вместо этого).
baserel->reltarget->exprs
может быть использован для определения столбцов, которые необходимо извлечь; но обратите внимание, что он перечисляет только столбцы, которые должны быть выведены узлом плана ForeignScan
, а не столбцы, которые используются при оценке ограничений, но не выводятся в запросе.
Различные частные поля доступны для функций планирования FDW, чтобы хранить информацию. Обычно все, что вы сохраняете в частных полях FDW, должно быть выделено с помощью palloc, чтобы оно было освобождено в конце планирования.
baserel->fdw_private
- это указатель void
, который доступен для функций планирования FDW для хранения информации, относящейся к конкретной внешней таблице. Ядро планировщика не изменяет его, кроме как для инициализации его значением NULL при создании узла RelOptInfo
. Он полезен для передачи информации от GetForeignRelSize
к GetForeignPaths
и/или от GetForeignPaths
к GetForeignPlan
, тем самым избегая повторного вычисления.
GetForeignPaths
может определить значение различных путей доступа, храня личную информацию в поле fdw_private
узлов ForeignPath
.
fdw_private
объявлен как указатель List
, но на самом деле может содержать что угодно, так как ядро планировщика не обрабатывает его.
Однако, рекомендуется использовать представление, которое может быть сериализовано с помощью nodeToString
для использования с отладочной поддержкой, доступной в бэкенде.
GetForeignPlan
может проверить поле fdw_private
выбранного узла ForeignPath
и сгенерировать списки fdw_exprs
и fdw_private
, которые будут размещены в узле плана ForeignScan
и будут доступны во время выполнения. Оба этих списка должны быть представлены в форме, которую copyObject
умеет копировать. Список fdw_private
не имеет других ограничений и не интерпретируется ядром бэкенда. Список fdw_exprs
, если он не пуст, должен содержать деревья выражений, которые предполагается выполнить во время выполнения. Эти деревья будут подвергнуты постобработке планировщиком, чтобы сделать их полностью исполняемыми.
В функции GetForeignPlan
обычно переданный список целей может быть скопирован в узел плана как есть. Переданный список scan_clauses
содержит те же предложения, что и baserel->baserestrictinfo
, но может быть переупорядочен для более эффективного выполнения. В простых случаях FDW может просто удалить узлы RestrictInfo
из списка scan_clauses
(с помощью функции extract_actual_clauses
) и поместить все предложения в список квалификаций узла плана, что означает, что все предложения будут проверены исполнителем во время выполнения. Более сложные FDW могут проверять некоторые из предложений внутренне, в таком случае эти предложения могут быть удалены из списка квалификаций узла плана, чтобы исполнитель не тратил время на повторную проверку их.
В качестве примера, FDW может определить некоторые ограничительные предложения в форме foreign_variable
=
sub_expression
, которые могут быть выполнены на удаленном сервере с учетом локально вычисленного значения sub_expression
. Фактическое определение такого предложения должно происходить во время GetForeignPaths
, поскольку это повлияет на оценку стоимости пути. Поле fdw_private
пути, вероятно, будет содержать указатель на узел RestrictInfo
определенного предложения. Затем GetForeignPlan
удалит это предложение из scan_clauses
, но добавит sub_expression
в fdw_exprs
, чтобы убедиться, что оно будет преобразовано в исполняемую форму. Вероятно, также будет добавлена информация управления в поле fdw_private
узла плана, чтобы сообщить функциям выполнения, что делать во время выполнения. Запрос, передаваемый на удаленный сервер, будет содержать что-то вроде WHERE
, где значение параметра будет получено во время выполнения из вычисления дерева выражений foreign_variable
= $1fdw_exprs
.
Любые предложения, удаленные из списка qual узла плана, должны быть добавлены в fdw_recheck_quals
или проверены повторно с помощью RecheckForeignScan
, чтобы обеспечить правильное поведение на уровне изоляции READ COMMITTED
. Когда происходит одновременное обновление для другой таблицы, участвующей в запросе, исполнитель может потребоваться проверить, что все исходные условия все еще выполняются для кортежа, возможно, с использованием другого набора значений параметров. Использование fdw_recheck_quals
обычно проще, чем реализация проверок внутри RecheckForeignScan
, но этот метод будет недостаточным, когда внешние соединения были не указаны, поскольку кортежи соединения в этом случае могут иметь некоторые поля, становящиеся NULL, без отклонения всего кортежа.
Еще одно поле ForeignScan
, которое может быть заполнено FDW,
это fdw_scan_tlist
, которое описывает кортежи, возвращаемые
FDW для данного узла плана. Для простых сканирований внешней таблицы это поле может быть
установлено в NIL
, что означает, что возвращаемые кортежи имеют
тип строки, объявленный для внешней таблицы. Ненулевое значение должно быть
целевым списком (списком TargetEntry
), содержащим переменные и/или
выражения, представляющие возвращаемые столбцы. Это может быть использовано, например, для
показа того, что FDW опустил некоторые столбцы, которые он заметил,
что не понадобятся для запроса. Кроме того, если FDW может вычислить выражения,
используемые запросом, более дешево, чем это может быть сделано локально, он может добавить
эти выражения в fdw_scan_tlist
. Обратите внимание, что планы соединения
(созданные из путей, созданных GetForeignJoinPaths
) всегда должны
предоставлять fdw_scan_tlist
, чтобы описать набор
столбцов, которые они вернут.
FDW всегда должен строить хотя бы один путь, который зависит только от ограничительных предложений таблицы. В запросах с соединением он также может выбрать путь(и), который зависит от условий соединения, например, foreign_variable
=
local_variable
. Такие условия не будут найдены в baserel->baserestrictinfo
, но их нужно искать в списке соединений отношения. Путь, использующий такое условие, называется “параметризованным путем”. Он должен идентифицировать другие отношения, используемые в выбранных условиях соединения, с помощью подходящего значения param_info
; используйте get_baserel_parampathinfo
, чтобы вычислить это значение. В GetForeignPlan
часть объединительного условия local_variable
будет добавлена в fdw_exprs
, а затем во время выполнения случай работает так же, как для обычного ограничительного условия.
Если FDW поддерживает удаленные соединения, функция GetForeignJoinPaths
должна создавать структуры ForeignPath
для потенциальных удаленных соединений таким же образом, как функция GetForeignPaths
работает для базовых таблиц. Информация о предполагаемом соединении может быть передана вперед функции GetForeignPlan
такими же способами, описанными выше. Однако, baserestrictinfo
не имеет значения для соединений; вместо этого, соответствующие условия соединения для конкретного соединения передаются в функцию GetForeignJoinPaths в виде отдельного параметра (extra->restrictlist
).
FDW может также поддерживать прямое выполнение некоторых действий плана, которые находятся выше уровня сканирования и соединения, таких как группировка или агрегация. Чтобы предложить такие варианты, FDW должен генерировать пути и вставлять их в соответствующее верхнее отношение. Например, путь, представляющий удаленную агрегацию, должен быть вставлен в отношение UPPERREL_GROUP_AGG
с использованием функции add_path
. Этот путь будет сравниваться с путем локальной агрегации на основе стоимости, выполняемой путем простого сканирования для внешнего отношения (обратите внимание, что такой путь также должен быть предоставлен, иначе возникнет ошибка на этапе планирования). Если путь удаленной агрегации побеждает, что обычно и происходит, он будет преобразован в план обычным образом, вызывая функцию GetForeignPlan
. Рекомендуемое место для генерации таких путей - это функция обратного вызова GetForeignUpperPaths, которая вызывается для каждого верхнего отношения (т.е. для каждого шага обработки после сканирования/соединения), если все базовые отношения запроса поступают из одного и того же FDW.
PlanForeignModify
и другие обратные вызовы, описанные в
Раздел 56.2.4, разработаны с учетом предположения,
что внешнее отношение будет сканироваться обычным образом, а затем
индивидуальные обновления строк будут управляться локальным узлом плана ModifyTable
.
Такой подход необходим для общего случая, когда обновление требует чтения как локальных, так и внешних таблиц.
Однако, если операция может быть выполнена полностью удаленным сервером,
FDW может сгенерировать путь, представляющий это, и вставить его
в верхнее отношение UPPERREL_FINAL
, где он будет
соревноваться с подходом ModifyTable
.
Такой подход также может быть использован для реализации удаленного SELECT FOR UPDATE
,
вместо использования обратных вызовов блокировки строк, описанных в
Раздел 56.2.6.
Имейте в виду, что путь, вставленный в UPPERREL_FINAL
, отвечает за
реализацию всего поведения запроса.
При планировании операций UPDATE
или DELETE
,
функции PlanForeignModify
и PlanDirectModify
могут обратиться к структуре RelOptInfo
для внешней таблицы и использовать ранее созданные данные baserel->fdw_private
,
полученные функциями планирования сканирования. Однако, при операции INSERT
целевая таблица не сканируется, поэтому для нее не существует структуры RelOptInfo
.
List
, возвращаемый функцией PlanForeignModify
,
имеет те же ограничения, что и список fdw_private
узла плана ForeignScan
,
то есть он должен содержать только структуры, которые функция copyObject
умеет копировать.
INSERT
с предложением ON CONFLICT
не поддерживает указание цели конфликта, так как ограничения уникальности или ограничения на исключение на удаленных таблицах неизвестны локально. Это в свою очередь означает, что ON CONFLICT DO UPDATE
не поддерживается, так как спецификация обязательна.