Pushdown в источник: что Trino отдаёт базе, а что считает сам
В прошлом уроке мы видели, что одна из задач JDBC-коннектора — транслировать часть запроса Trino в SQL источника. Эта «часть» — и есть ключ к производительности всей федерации. Когда Trino обращается к источнику, у него есть фундаментальный выбор: вычитать из источника сырые данные и обработать их у себя — или попросить источник самому выполнить часть работы и отдать уже готовый результат. Этот выбор называется pushdown, и от него зависит, будет ли федеративный запрос быстрым или невыносимо медленным. Этот урок разбирает механику pushdown, его виды и то, как увидеть, сработал ли он.
Два способа выполнить запрос к источнику
Возьмём конкретный запрос к таблице PostgreSQL:
SELECT count(*)
FROM postgresql.public.orders
WHERE status = 'completed';
Таблица orders в PostgreSQL — 50 миллионов строк, из них status = 'completed' — 3 миллиона. Есть два принципиально разных способа это выполнить.
Способ первый, без pushdown. Trino говорит источнику: «отдай мне всю таблицу orders». Источник отдаёт все 50 миллионов строк. Они едут по сети в Trino. Уже в Trino фильтр WHERE status = 'completed' отсекает строки, и count(*) считает результат. Итог верный — но по сети проехали 50 миллионов строк, чтобы получить одно число. Сеть и память Trino потрачены впустую на 47 миллионов строк, которые сразу же отброшены.
Способ второй, с pushdown. Trino понимает, что фильтрацию и подсчёт умеет сделать сам PostgreSQL. Он формирует и отправляет источнику SQL вроде SELECT count(*) FROM orders WHERE status = 'completed'. PostgreSQL исполняет это своим движком — у него для этого есть индексы и оптимизатор — и возвращает Trino одно число. По сети проехало одно значение вместо 50 миллионов строк.
Это и есть pushdown — проталкивание части запроса в источник. Разница между двумя способами на больших таблицах — это разница между секундами и десятками минут, между нормальным запросом и запросом, кладущим кластер. Pushdown — самый важный фактор производительности федерации.
Виды pushdown
Trino умеет проталкивать в источник не только фильтр. Основные виды pushdown — те же, что упоминались в модуле про cost-based optimizer, здесь рассмотрим их в контексте федерации.
Predicate pushdown — проталкивание фильтра. Условие WHERE отправляется источнику, и тот возвращает только подходящие строки. Источник применяет фильтр своими средствами, в том числе используя свои индексы. Это базовый и самый частый вид pushdown.
Projection pushdown — проталкивание выбора колонок. Если запросу нужны две колонки из тридцати, Trino просит источник вернуть только эти две (SELECT col1, col2 вместо SELECT *). Для колоночных источников это сокращает чтение; для строчных — хотя бы сокращает объём передачи по сети.
Aggregation pushdown — проталкивание агрегации. Функции count, sum, avg, GROUP BY Trino может отдать источнику, и тот вернёт уже агрегированный, маленький результат вместо сырых строк. Это самый эффектный вид pushdown: агрегация схлопывает миллионы строк в десятки.
Limit / TopN pushdown — проталкивание LIMIT и ORDER BY ... LIMIT. Если нужны 10 строк, нет смысла тянуть всю таблицу — LIMIT 10 уходит источнику.
| Вид pushdown | Что проталкивается | Что экономит |
|---|---|---|
| Predicate | WHERE-условие | Передачу отфильтрованных строк по сети |
| Projection | Список нужных колонок | Чтение и передачу ненужных колонок |
| Aggregation | count, sum, GROUP BY | Передачу сырых строк — источник отдаёт агрегат |
| Limit / TopN | LIMIT, ORDER BY + LIMIT | Передачу строк сверх лимита |
Почему pushdown срабатывает не всегда
Ключевой нюанс: pushdown — не гарантия, а возможность. Сработает он или нет, зависит от двух условий.
Первое — поддержка коннектором. Каждый JDBC-коннектор реализует pushdown в своём объёме. Один поддерживает aggregation pushdown полно, другой — частично, третий — только predicate. Reference-коннекторы (PostgreSQL, MySQL, SQL Server, Oracle) поддерживают pushdown наиболее полно — это одна из причин их статуса эталонных; у менее ходовых коннекторов набор уже.
Второе — транслируемость конкретной операции. Чтобы протолкнуть операцию, Trino должен суметь выразить её в SQL источника так, чтобы семантика совпала. Простой фильтр status = 'completed' транслируется почти всюду. Но если в условии — функция Trino, которой в источнике нет точного аналога, или поведение функции в источнике отличается (другие правила сравнения строк, другая работа с NULL), Trino не станет проталкивать операцию: некорректный результат хуже медленного. В таком случае Trino честно вычитает данные и обработает их сам, чтобы гарантировать правильность.
Отсюда практическое следствие: то, как написан запрос, влияет на pushdown. Фильтр по «чистому» сравнению колонки с константой протолкнётся охотнее, чем фильтр, обёрнутый в нестандартное выражение. Понимание pushdown — это понимание того, почему один вариант запроса быстрый, а логически эквивалентный — медленный.
Pushdown не срабатывает автоматически для всего. Если федеративный запрос неожиданно медленный, типичная причина — операция, которую Trino не смог протолкнуть в источник, и он вынужден тянуть сырые данные. Это не поломка: Trino выбирает корректность, когда не уверен в эквивалентности трансляции. Задача инженера — увидеть, что не протолкнулось, и по возможности переписать запрос так, чтобы pushdown стал возможен.
Как увидеть, сработал ли pushdown
Догадки тут не нужны — есть EXPLAIN. План запроса показывает, что Trino проталкивает в источник, а что выполняет сам.
Главный признак — как выглядит обращение к источнику в плане. Когда pushdown сработал, в плане у узла обращения к источнику видно, что в источник ушёл SQL уже с фильтром, с нужными колонками, с агрегацией. Когда pushdown не сработал, в плане видна отдельная операция Trino — отдельный узел Filter, отдельный узел Aggregate — выполняемая уже после чтения сырых данных из источника.
-- Смотрим план: протолкнулись ли фильтр и агрегация в PostgreSQL
EXPLAIN
SELECT status, count(*)
FROM postgresql.public.orders
WHERE order_date >= DATE '2026-01-01'
GROUP BY status;
Читая план, задавайте себе вопрос: где выполняется фильтр и агрегация — внутри обращения к источнику или отдельным шагом в Trino? Если внутри обращения к источнику — pushdown сработал, источник вернёт маленький агрегат. Если отдельным шагом Trino — pushdown не сработал, и Trino тянет сырые строки. Тот же план в EXPLAIN ANALYZE покажет фактический объём данных, прочитанный из источника, — прямое подтверждение, прокачаны ли через сеть миллионы строк или единицы.
Pushdown и баланс нагрузки
Последняя мысль, важная для эксплуатации. Pushdown переносит работу с Trino на источник — и это палка о двух концах.
С одной стороны, pushdown почти всегда выгоден: он радикально сокращает передачу данных по сети и память Trino, а реляционная база часто выполняет фильтрацию и агрегацию эффективно благодаря своим индексам. С другой — протолкнутая работа исполняется на источнике, и если источник — боевая транзакционная база под пользовательской нагрузкой, тяжёлая протолкнутая агрегация добавит ей нагрузки.
Обычно это всё равно меньшее зло, чем вытягивание миллионов строк (которое нагружает и сеть, и источник на полное сканирование, и Trino). Но в проде это учитывают: тяжёлые федеративные запросы направляют на read-реплику источника, а не на боевую базу. Pushdown — мощный инструмент, и как любой мощный инструмент требует понимания, куда именно он переносит нагрузку.
Попробуй сам
В песочнице с подключённым каталогом PostgreSQL создайте в базе таблицу orders (хотя бы несколько десятков тысяч строк, колонки status, order_date, amount). Упражнение первое: выполните EXPLAIN для SELECT count(*) FROM postgresql.public.orders WHERE status = 'completed' и найдите в плане, ушёл ли фильтр и подсчёт в источник или выполняются отдельными шагами в Trino. Упражнение второе: выполните EXPLAIN для запроса с GROUP BY и агрегацией sum(amount) — протолкнулась ли агрегация. Затем сравните с EXPLAIN ANALYZE — сколько строк фактически прочитано из источника. Упражнение третье, на границу pushdown: оберните колонку в фильтре в какое-нибудь нестандартное выражение или функцию и посмотрите EXPLAIN снова — изменилось ли поведение pushdown. Письменно объясните: почему pushdown — самый важный фактор производительности федерации, почему он срабатывает не всегда, и на какой компромисс по нагрузке инженер идёт, проталкивая тяжёлую агрегацию в боевую транзакционную базу.