Postgres_FDW не опускает ГДЕ критерии
Я работаю с двумя базами данных PostgreSQL 9.6 и пытаюсь запросить одну из БД у другой, используя postgres_fdw (одна - рабочая резервная БД, содержащая данные, а другая - БД для различных анализов).
Я столкнулся с некоторым странным поведением, когда некоторые типы предложений WHERE в запросе не передаются в удаленную БД, а вместо этого сохраняются в локальной БД и используются для фильтрации результатов, полученных от удаленной БД. Это приводит к тому, что удаленная БД пытается отправить по сети больше информации, чем требуется локальной БД, и затронутые запросы значительно медленнее (15 секунд против 15 минут).
В основном я видел это с предложениями, относящимися к отметке времени, приведенные ниже примеры показывают, как я впервые столкнулся с проблемой, но я видел это в нескольких других вариантах, таких как замена CURRENT_TIMESTAMP на литерал TIMESTAMP (медленный) или литерал TIMESTAMP WITH TIME ZONE (быстро).
Есть ли где-то параметр, который мне не хватает, который поможет с этим? Я настраиваю это для команды, чтобы использовать со смешанным уровнем фона SQL, большинство из которых не имеют опыта просмотра планов EXPLAIN и еще много чего. Я придумал несколько обходных путей (например, поместил предложения относительного времени в sub-SELECT), но я продолжаю сталкиваться с новыми случаями проблемы.
Пример:
SELECT var_1
,var_2
FROM schema_A.table_A
WHERE execution_ts <= CURRENT_TIMESTAMP - INTERVAL '1 hour'
AND execution_ts >= CURRENT_TIMESTAMP - INTERVAL '1 week' - INTERVAL '1 hour'
ORDER BY var_1
Объяснить план
Sort (cost=147.64..147.64 rows=1 width=1048)
Output: table_A.var_1, table_A.var_2
Sort Key: (table_A.var_1)::text
-> Foreign Scan on schema_A.table_A (cost=100.00..147.63 rows=1 width=1048)
Output: table_A.var_1, table_A.var_2
Filter: ((table_A.execution_ts <= (now() - '01:00:00'::interval))
AND (table_A.execution_ts >= ((now() - '7 days'::interval) - '01:00:00'::interval)))
Remote SQL: SELECT var_1, execution_ts FROM model.table_A
WHERE ((model_id::text = 'ABCD'::text))
AND ((var_1 = ANY ('{1,2,3,4,5}'::bigint[])))
Вышеуказанное занимает около 15-20 минут для запуска, а ниже завершается в секундах.
SELECT var_1
,var_2
FROM schema_A.table_A
WHERE execution_ts <= (SELECT CURRENT_TIMESTAMP - INTERVAL '1 hour')
AND execution_ts >= (SELECT CURRENT_TIMESTAMP - INTERVAL '1 week' - INTERVAL '1 hour')
ORDER BY var_1
Объяснить план
Sort (cost=158.70..158.71 rows=1 width=16)
Output: table_A.var_1, table_A.var_2
Sort Key: table_A.var_1
InitPlan 1 (returns $0)
-> Result (cost=0.00..0.01 rows=1 width=8)
Output: (now() - '01:00:00'::interval)
InitPlan 2 (returns $1)
-> Result (cost=0.00..0.02 rows=1 width=8)
Output: ((now() - '7 days'::interval) - '01:00:00'::interval)
-> Foreign Scan on schema_A.table_A (cost=100.00..158.66 rows=1 width=16)
Output: table_A.var_1, table_A.var_2
Remote SQL: SELECT var_1, var_2 FROM model.table_A
WHERE ((execution_ts <= $1::timestamp with time zone))
AND ((execution_ts >= $2::timestamp with time zone))
AND ((model_id::text = 'ABCD'::text))
AND ((var_1 = ANY ('{1,2,3,4,5}'::bigint[])))
2 ответа
Любая функция, которая не IMMUTABLE
не будет сброшен
Смотрите функцию is_foreign_expr
в contrib/postgres_fdw/deparse.c
:
/*
* Returns true if given expr is safe to evaluate on the foreign server.
*/
bool
is_foreign_expr(PlannerInfo *root,
RelOptInfo *baserel,
Expr *expr)
{
...
/*
* An expression which includes any mutable functions can't be sent over
* because its result is not stable. For example, sending now() remote
* side could cause confusion from clock offsets. Future versions might
* be able to make this choice with more granularity. (We check this last
* because it requires a lot of expensive catalog lookups.)
*/
if (contain_mutable_functions((Node *) expr))
return false;
/* OK to evaluate on the remote server */
return true;
}
Я думаю, что проблема может быть контентом исполнения now()
(который CURRENT_TIMESTAMP
решает).
Валур вернулся now()
фиксируется для текущей транзакции - это означает, что она должна быть выполнена локально.
оборачивая его, отбор приводит к постоянному timestamptz
значение, позволяющее проводить оценку на расстоянии.
с использованием постоянной метки времени необходимого преобразования между меткой времени и меткой времени, которая выполняется с использованием текущих правил часовых поясов (согласно SET TIME ZONE TO ....
) и если база данных решит конвертировать удаленный timestamptz
по местному времени для сравнения с timestamp
буквально снова это должно быть сделано локально.
в общем timestamp
следует избегать (использовать timestamptz
вместо) кроме того, где вы
- хотите, чтобы значения соответствовали любым изменениям, внесенным в правила перехода на летнее время, и
- уверены, что вы никогда не захотите представлять временные метки в течение добавленного часа осенью