Learning Platform
Глоссарий Troubleshooting
Урок 12.05 · 24 мин
Средний
federationantipatternsexplain-analyzeperformance

Реальная федерация: join между PostgreSQL и Parquet на S3, антипаттерны

Этот урок собирает весь модуль воедино на одном сквозном сценарии — самом частом федеративном запросе в реальной дата-инженерии: join таблицы из реляционной базы с большой таблицей-фактом в lakehouse. Мы пройдём такой запрос от плана до результата, увидим, как Trino распределяет работу между источниками, и — главное — разберём антипаттерны: ошибки, из-за которых федеративный запрос из быстрого превращается в катастрофически медленный. Понимание этих ошибок отличает инженера, который умеет писать федеративные запросы, от того, кто просто знает их синтаксис.

Сценарий: dimension в PostgreSQL, fact в Iceberg на S3

Типичная задача. Есть большая fact-таблица событий events — сотни миллионов строк, лежит в Iceberg на S3 (аналитический слой lakehouse). И есть небольшая dimension-таблица customers — справочник клиентов, десятки тысяч строк, живёт в боевом PostgreSQL приложения. Нужен отчёт: выручка по странам клиентов за период. Данные в двух разных системах — это и есть классическая федерация.

-- Федеративный join: fact в Iceberg на S3, dimension в PostgreSQL
SELECT c.country,
       count(*)        AS events,
       sum(e.amount)   AS revenue
FROM iceberg.analytics.events  e
JOIN postgresql.public.customers c
  ON e.customer_id = c.customer_id
WHERE e.event_date >= DATE '2026-05-01'
  AND c.segment = 'enterprise'
GROUP BY c.country
ORDER BY revenue DESC;

--  country | events  |  revenue
-- ---------+---------+-----------
--  DE      | 1840200 | 4920100.00
--  US      | 1502880 | 4115500.00
--  FR      |  610440 | 1284900.00

Без федерации этот отчёт потребовал бы ETL-пайплайна, копирующего customers из PostgreSQL в lakehouse, расписания, мониторинга. С Trino — это один запрос. Но чтобы он был быстрым, надо понимать, что под ним происходит.

Airflow: архитектура процессов

Как Trino распределяет работу между источниками

Trino исполняет федеративный join не как «скачать обе таблицы и сджойнить». Каждый источник обрабатывается своим коннектором, и к каждому применяется свой pushdown — ровно то, что мы разбирали в уроках 3 и 4.

К стороне PostgreSQL применяется JDBC-pushdown. Фильтр c.segment = 'enterprise' проталкивается в PostgreSQL — база вернёт не весь справочник, а только enterprise-клиентов. Projection pushdown оставит только нужные колонки — customer_id, country. PostgreSQL отдаст маленький отфильтрованный результат.

К стороне Iceberg применяется механика lakehouse из модулей 9-10. Фильтр e.event_date >= DATE '2026-05-01' запускает partition pruning: Trino по метаданным Iceberg отбрасывает партиции и файлы вне диапазона дат, читая только нужные Parquet-файлы из S3.

Сам join Trino выполняет у себя, распределённо: он соединяет отфильтрованные данные двух источников. Здесь работает важная оптимизация — dynamic filtering (она разбиралась в модуле про CBO). Сторона customers после фильтра по segment мала. Trino собирает с неё множество значений customer_id и формирует рантайм-фильтр, который применяется к чтению events: фактически Trino читает из Iceberg не все события за период, а только события тех клиентов, что вообще попали в результат. Dynamic filtering — то, что делает join «маленькое к большому» по-настоящему быстрым.

Федеративный join: своя обработка каждого источника
PostgreSQL: customersJDBC-pushdown: фильтр segment и выбор колонок проталкиваются в базу, она отдаёт маленький отфильтрованный результат
Iceberg: eventsLakehouse-механика: фильтр по дате запускает partition pruning, читаются только нужные Parquet-файлы из S3
Trino соединяет, dynamic filtering урезает чтение fact
Join в TrinoTrino распределённо джойнит отфильтрованные данные; dynamic filtering применяет значения customer_id со стороны customers к чтению events

Когда всё это срабатывает, запрос быстрый: каждый источник отдал минимум данных, по сети проехало немного, join работает над компактными наборами. Когда что-то из этого ломается — начинаются антипаттерны.

Антипаттерн 1: запрос, который мешает pushdown

Самый частый антипаттерн — написать запрос так, что pushdown не срабатывает. Из урока 3: pushdown — возможность, а не гарантия, и нестандартное выражение в фильтре может его сорвать.

Опасный пример — обернуть фильтруемую колонку в функцию. Сравните:

-- ПЛОХО: функция над колонкой может помешать pushdown фильтра
WHERE lower(c.segment) = 'enterprise'

-- ХОРОШО: чистое сравнение колонки с константой — проталкивается охотно
WHERE c.segment = 'enterprise'

Если функция или её поведение не транслируются в источник точно, Trino не протолкнёт фильтр — он вытянет весь справочник customers и применит lower(...) у себя. Маленькая безобидная lower() превратила pushdown-фильтр в полное чтение таблицы. То же относится к фильтру по дате в fact-таблице: обернёте event_date в выражение — рискуете потерять partition pruning и прочитать всю таблицу-факт. Лечение — EXPLAIN (урок 3): посмотрите план, убедитесь, что фильтры ушли в источники, а не висят отдельными узлами в Trino.

Антипаттерн 2: тащить сырые данные, чтобы агрегировать в Trino

Второй антипаттерн — не дать источнику сделать работу, которую он сделал бы эффективно. Если из реляционной базы нужен агрегат, считать его должна база (aggregation pushdown), а не Trino над сырыми строками.

-- ПЛОХО: тянем все платежи в Trino, чтобы агрегировать здесь
SELECT customer_id, sum(amount)
FROM (SELECT customer_id, amount FROM postgresql.public.payments) t
GROUP BY customer_id;

-- ХОРОШО: позволяем PostgreSQL агрегировать у себя и отдать готовое
SELECT customer_id, sum(amount)
FROM postgresql.public.payments
GROUP BY customer_id;

Логически запросы эквивалентны, но конструкции, мешающие оптимизатору увидеть агрегацию насквозь, могут лишить его aggregation pushdown — и тогда Trino вытянет все строки payments, чтобы посчитать сумму у себя. Принцип: не «защищай» оптимизатор подзапросами, пиши агрегацию прямо — дай Trino шанс протолкнуть её в источник.

Антипаттерн 3: cross join разнородных источников

Третий антипаттерн — самый разрушительный. Join без условия соединения (или с условием, которое Trino не может протолкнуть и использовать) превращается в cross join — декартово произведение.

При федерации это особенно опасно. Декартово произведение таблицы из PostgreSQL на 100 тысяч строк и таблицы-факта из Iceberg на 100 миллионов строк — это 10 триллионов строк. Их физически негде разместить и нечем обработать; запрос либо исчерпает память кластера, либо будет работать часами. Всегда проверяйте, что у join есть корректное ON-условие по ключу, и что оно из тех, что Trino умеет использовать для dynamic filtering (равенство по ключу).

DANGER

Cross join разнородных федеративных источников — самая дорогая ошибка федерации. Случайно потерянное или некорректное ON-условие превращает join в декартово произведение: маленькая dimension на большую fact дают триллионы строк. Перед запуском федеративного join всегда проверяйте ON-условие. EXPLAIN покажет cross join в плане до того, как запрос положит кластер.

Антипаттерн 4: игнорировать нагрузку на источник

Четвёртый антипаттерн — не технический, а эксплуатационный, но в проде он бьёт больнее всего. Из уроков 1 и 3: pushdown переносит работу на источник, а тяжёлый запрос Trino способен ощутимо нагрузить боевую базу.

Антипаттерн — направить аналитический Trino-каталог прямо на боевую транзакционную базу под пользовательской нагрузкой. Тяжёлый федеративный отчёт, выполняемый по расписанию, добавит боевой базе нагрузки и может ухудшить работу самого приложения. Это особенно коварно, потому что технически запрос корректен и быстр — проблема в том, где он создаёт нагрузку. Правильно — направлять Trino-каталог на read-реплику источника. Аналитика идёт по реплике, боевой трафик приложения — по основной базе, они не мешают друг другу.

Чеклист здорового федеративного запроса

ПроверкаЧем смотретьАнтипаттерн, который ловит
Фильтры ушли в источникиEXPLAIN: фильтры внутри обращения к источнику, не отдельными узламиЗапрос, мешающий pushdown
Агрегация протолкнута, где можноEXPLAIN: агрегация на стороне источникаТащить сырьё ради агрегации в Trino
У join есть корректное ON-условиеEXPLAIN: нет cross join в планеCross join разнородных источников
Реально прочитанный объём малEXPLAIN ANALYZE: строки и байты по источникамСкрытое полное чтение таблицы
Каталог смотрит на репликуКонфигурация каталогаНагрузка на боевую базу

Сквозной вывод модуля. Федерация Trino даёт огромную силу — единый SQL поверх разнородного ландшафта данных без копирования. Но сила эта обусловлена pushdown и dynamic filtering, а они не срабатывают сами собой при любом запросе. Инженер обязан читать EXPLAIN, понимать, что протолкнулось в источник, а что нет, и избегать четырёх антипаттернов: запросов, ломающих pushdown; вытягивания сырья ради агрегации; cross join; и нагрузки на боевые источники. Федеративный запрос — это не «просто SQL с разными каталогами», это запрос, за поведением которого нужно следить.

Попробуй сам

В песочнице соберите полный сценарий федерации: каталог postgresql с dimension-таблицей customers и каталог iceberg с fact-таблицей events (свяжите их по customer_id, в events сделайте достаточно строк, чтобы разница была заметна). Упражнение первое: напишите федеративный join с фильтрами по обеим сторонам и агрегацией, выполните EXPLAIN и проверьте по чеклисту — фильтры ушли в источники, агрегация протолкнута, cross join нет. Упражнение второе, воспроизведение антипаттернов: возьмите рабочий запрос и (а) оберните фильтруемую колонку PostgreSQL в lower(), (б) в отдельной попытке временно уберите ON-условие join. Снимите EXPLAIN для каждого случая и зафиксируйте, как изменился план — где появилось полное чтение, где cross join. Упражнение третье: для рабочего и для «испорченного» запроса снимите EXPLAIN ANALYZE и сравните фактический объём данных, прочитанный из источников. Письменно разберите все четыре антипаттерна модуля: в чём каждый, как его увидеть в плане и как исправить.


Проверка знанийKnowledge check
Разберите сквозной федеративный join dimension-таблицы из PostgreSQL и fact-таблицы из Iceberg: как Trino распределяет работу между источниками и какие четыре антипаттерна способны убить производительность такого запроса?
ОтветAnswer
В типичном федеративном join небольшая dimension-таблица customers лежит в PostgreSQL, а большая fact-таблица events — в Iceberg на S3. Trino исполняет такой join не как скачивание обеих таблиц, а обрабатывает каждый источник своим коннектором со своим pushdown. К стороне PostgreSQL применяется JDBC-pushdown: фильтр по dimension и выбор колонок проталкиваются в базу, она отдаёт маленький отфильтрованный результат. К стороне Iceberg применяется lakehouse-механика: фильтр по дате запускает partition pruning, и читаются только нужные Parquet-файлы. Сам join Trino выполняет распределённо у себя, и здесь работает dynamic filtering: Trino собирает значения ключа с малой стороны customers и применяет их рантайм-фильтром к чтению events, читая не все события за период, а только события клиентов, попавших в результат. Когда всё это срабатывает, запрос быстрый. Четыре антипаттерна ломают это. Первый — запрос, мешающий pushdown: обёртка фильтруемой колонки в функцию вроде lower может сорвать predicate pushdown, и Trino вытянет всю таблицу вместо отфильтрованной; лечится проверкой EXPLAIN, что фильтры ушли в источники. Второй — тащить сырые данные ради агрегации в Trino: если из базы нужен агрегат, считать его должна база через aggregation pushdown, а конструкции вроде лишних подзапросов могут лишить оптимизатор этой возможности; нужно писать агрегацию прямо. Третий, самый разрушительный — cross join разнородных источников: потерянное или некорректное ON-условие превращает join в декартово произведение, и маленькая dimension на большую fact дают триллионы строк, исчерпывающие кластер; нужно всегда проверять ON-условие. Четвёртый — игнорировать нагрузку на источник: направлять аналитический каталог прямо на боевую транзакционную базу опасно, так как тяжёлый протолкнутый запрос её нагрузит; каталог нужно направлять на read-реплику. Общий вывод: сила федерации обусловлена pushdown и dynamic filtering, которые не срабатывают сами собой, поэтому инженер обязан читать EXPLAIN и избегать этих четырёх ошибок.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 4. В федеративном join dimension-таблицы из PostgreSQL и fact-таблицы из Iceberg на S3 — как Trino распределяет работу между источниками?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5