56.4. Планирование запросов в обертке внешних данных#

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 = $1, где значение параметра будет получено во время выполнения из вычисления дерева выражений fdw_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 не поддерживается, так как спецификация обязательна.