Learning Platform
Глоссарий Troubleshooting
Урок 11.03 · 22 мин
Средний
postgresmysqlsqlitefederated-queries

Сканеры баз: postgres, mysql, sqlite

DuckDB умеет читать не только файлы, но и другие базы данных. Расширения postgres, mysql и sqlite подключают живые PostgreSQL, MySQL и SQLite-базы прямо в SQL-сессию DuckDB. После этого таблицы внешней СУБД становятся таблицами DuckDB: их можно запрашивать, джойнить с локальными данными, джойнить с Parquet-файлами на S3. Это превращает DuckDB в federated query engine — движок, исполняющий один запрос поверх нескольких разнородных источников. Этот урок про то, как сканеры баз устроены, что они проталкивают во внешнюю СУБД, а что нет, и где это полезно.

Зачем DuckDB ходить в другую базу

Trino: федеративные запросы к PostgreSQL и MySQL Trino: pushdown в источник — что отдаётся базе, что считается сам

Типичная ситуация: транзакционные данные приложения живут в PostgreSQL, а аналитика, исторические снапшоты и большие выгрузки лежат в Parquet на S3. Чтобы построить отчёт, нужно соединить «свежее состояние из Postgres» с «историей из Parquet». Классический путь — выгрузить Postgres в файл, потом всё обработать. Это лишний шаг, и данные на момент анализа уже устаревают.

Сканер базы убирает этот шаг. DuckDB подключается к PostgreSQL напрямую, видит его таблицы как свои, и джойн «Postgres + Parquet» становится одним обычным SQL-запросом. При этом DuckDB остаётся аналитическим движком: тяжёлую агрегацию и джойн он выполняет своим векторизованным движком, а PostgreSQL используется как источник строк. OLTP-база не нагружается аналитической работой — она лишь отдаёт данные.

DuckDB как federated query engine
PostgreSQLТранзакционная база приложения; через расширение postgres её таблицы видны в DuckDB
postgres scanner
Движок DuckDBВекторизованный движок исполняет джойн и агрегацию поверх всех источников сразу
parquet
Parquet на S3Исторические данные в объектном хранилище; читаются через httpfs в тот же запрос

ATTACH: подключение внешней базы

Внешняя база подключается командой ATTACH с указанием типа. После этого она появляется в каталоге DuckDB как отдельная база со своими схемами и таблицами.

-- Подключить PostgreSQL
ATTACH 'host=10.0.0.5 port=5432 dbname=shop user=analyst password=...' AS pg (TYPE postgres);

-- Подключить SQLite-файл
ATTACH 'app.sqlite' AS lite (TYPE sqlite);

-- Подключить MySQL
ATTACH 'host=10.0.0.7 user=root database=orders' AS my (TYPE mysql);

Расширения postgres, mysql, sqlite — core и autoloadable: явный INSTALL/LOAD обычно не нужен, ATTACH с соответствующим TYPE подтянет расширение сам. Учётные данные для PostgreSQL и MySQL лучше держать не в строке подключения, а в secret типа postgres/mysql — тогда строка ATTACH не содержит пароля.

После ATTACH таблицы внешней базы адресуются через её псевдоним: pg.public.customers. Они ведут себя как обычные таблицы DuckDB:

ATTACH 'host=10.0.0.5 dbname=shop user=analyst' AS pg (TYPE postgres);

-- Запрос к таблице PostgreSQL прямо из DuckDB
SELECT country, COUNT(*) AS customers
FROM pg.public.customers
GROUP BY country
ORDER BY customers DESC;
┌─────────┬───────────┐
│ country │ customers │
│ varchar │   int64   │
├─────────┼───────────┤
│ DE      │     14820 │
│ FR      │      9310 │
│ PL      │      6105 │
└─────────┴───────────┘

И главное — джойн внешней таблицы с локальными данными или файлами:

-- Свежие клиенты из Postgres + история заказов из Parquet на S3
SELECT c.country, SUM(o.amount) AS revenue
FROM pg.public.customers c
JOIN 's3://bucket/orders/*.parquet' o ON o.customer_id = c.id
GROUP BY c.country;

Один запрос — два источника, разные технологии. Это и есть federated-запрос.

Pushdown в сканерах баз: что уходит во внешнюю СУБД

Здесь — самая важная для производительности часть. Когда DuckDB читает из PostgreSQL, он не обязан тянуть всю таблицу к себе. Он умеет протолкнуть часть работы в саму PostgreSQL: DuckDB генерирует SQL-запрос к внешней базе и включает в него фильтры и список нужных колонок.

  • Projection pushdown: если запрос трогает 3 колонки из 30, DuckDB отправит в PostgreSQL SELECT col1, col2, col3 FROM ..., а не SELECT *. По сети придут только 3 колонки.
  • Filter pushdown: предикат WHERE country = 'DE' уходит в WHERE-часть запроса к PostgreSQL. Фильтрацию делает PostgreSQL своим индексом, а по сети передаются только подходящие строки.
Pushdown во внешнюю СУБД: фильтрует источник
Запрос DuckDB с WHERE и выбором колонокПользователь пишет обычный запрос к pg.public.customers с фильтром и списком колонок
генерация SQL для Postgres
SELECT нужных колонок WHERE предикатDuckDB строит SQL-запрос к PostgreSQL с проброшенными фильтрами и проекцией
по сети только отфильтрованное
PostgreSQL вернул минимум строкФильтрацию выполнил PostgreSQL своим индексом; DuckDB получает уже урезанный набор

А вот чего сканер не проталкивает: сложную агрегацию, оконные функции, многотабличные джойны между внешними таблицами в общем случае выполняет сам DuckDB своим векторизованным движком. Это осознанный дизайн: DuckDB как аналитический движок считает агрегаты быстрее типичной OLTP-базы. Роль PostgreSQL в federated-запросе — отдать отфильтрованные и спроецированные строки; тяжёлую аналитику берёт на себя DuckDB.

Проверить, что именно ушло в PostgreSQL, можно через EXPLAIN: в плане у оператора сканирования postgres виден сгенерированный SQL-текст. Если фильтр в нём есть — pushdown сработал.

WARNING

Pushdown ломается, когда фильтр использует выражение, которого нет в SQL внешней СУБД, или функцию, специфичную для DuckDB. Тогда DuckDB не может включить предикат в запрос к PostgreSQL, тянет таблицу целиком и фильтрует у себя. На большой внешней таблице это превращается в перекачку всей таблицы по сети. Всегда проверяйте EXPLAIN: если ожидаемого фильтра в сгенерированном SQL нет — переформулируйте предикат проще, ближе к стандартному SQL.

Перенос данных между базами

Сканеры работают в обе стороны. Раз внешняя база видна как обычная, в неё можно и писать, и из неё можно копировать. Это делает DuckDB удобным инструментом миграции и ETL между СУБД.

-- Скопировать таблицу из PostgreSQL в локальную таблицу DuckDB
CREATE TABLE local_customers AS SELECT * FROM pg.public.customers;

-- Целиком скопировать одну базу в другую
COPY FROM DATABASE pg TO my;

-- Записать результат запроса в таблицу MySQL
INSERT INTO my.orders_summary
SELECT customer_id, COUNT(*), SUM(amount)
FROM pg.public.orders GROUP BY customer_id;

Типичные сценарии: одноразовая миграция PostgreSQL -> MySQL, регулярная выгрузка горячих таблиц OLTP в Parquet для аналитики, перенос SQLite-базы мобильного приложения в аналитическое хранилище.

РасширениеВнешняя СУБДПодключениеЗапись
postgresPostgreSQLСтрока подключения (host/port/db)Да
mysqlMySQL / MariaDBСтрока подключенияДа
sqliteSQLiteПуть к файлу базыДа
TIP

Расширение sqlite особенно удобно тем, что SQLite-база — это один файл, без сервера. ATTACH к SQLite не требует ни сети, ни учётных данных. Это делает связку «DuckDB читает SQLite» отличным способом аналитики над данными приложений, которые хранят состояние в SQLite-файле, — мобильные приложения, десктопные программы, браузеры.

Транзакционная семантика и ограничения

Внешняя база остаётся внешней — со своей моделью конкурентности и своими блокировками. DuckDB не управляет транзакциями PostgreSQL так, как своими. При чтении это незаметно, но при записи важно помнить: DuckDB отправляет во внешнюю СУБД команды, а изоляция и блокировки — на стороне той СУБД.

Производительность тоже своя. Чтение из PostgreSQL ограничено скоростью PostgreSQL и сети — это не колоночное хранилище DuckDB с его сжатием и zonemap. Если одна и та же внешняя таблица читается в аналитике многократно, разумнее один раз скопировать её в нативную таблицу DuckDB или в Parquet и дальше работать с быстрой локальной копией. Federated-запрос хорош для свежести данных, не для повторяющейся тяжёлой аналитики поверх одних и тех же строк.

Попробуй сам

Для задания удобен SQLite — он не требует сервера.

  1. Создайте SQLite-файл с парой таблиц (можно прямо из DuckDB: ATTACH 'test.sqlite' AS lite (TYPE sqlite); CREATE TABLE lite.users AS SELECT range AS id, 'user' || range AS name FROM range(1000);).
  2. Откройте новую сессию DuckDB, выполните ATTACH 'test.sqlite' AS lite (TYPE sqlite) и запросите SELECT COUNT(*) FROM lite.users. Убедитесь, что таблица SQLite видна как обычная.
  3. Создайте локальную таблицу или Parquet-файл и сделайте джойн его с таблицей из SQLite в одном запросе. Это ваш первый federated-запрос.
  4. Если есть доступный PostgreSQL: подключите его через ATTACH, выполните EXPLAIN SELECT col1, col2 FROM pg.schema.table WHERE col1 = 'X' и найдите в плане сгенерированный SQL-текст для PostgreSQL. Убедитесь, что фильтр и список колонок в него проброшены (pushdown).

Trino: федерация как первоклассная функция
Проверка знанийKnowledge check
Что делает DuckDB федеративным движком при работе с расширениями postgres, mysql, sqlite, и какую часть работы сканер проталкивает во внешнюю СУБД, а какую выполняет сам?
ОтветAnswer
Расширения postgres, mysql и sqlite позволяют подключить живые PostgreSQL, MySQL и SQLite-базы прямо в SQL-сессию DuckDB командой ATTACH с указанием TYPE. После подключения таблицы внешней базы появляются в каталоге DuckDB и адресуются через псевдоним (например pg.public.customers); они ведут себя как обычные таблицы DuckDB — их можно запрашивать и, главное, джойнить с локальными таблицами и даже с Parquet-файлами на S3. Это и делает DuckDB federated query engine: один SQL-запрос исполняется поверх нескольких разнородных источников. Сканер проталкивает (pushdown) во внешнюю СУБД две вещи: projection pushdown — DuckDB генерирует для внешней базы SQL-запрос со списком только нужных колонок, а не SELECT *, поэтому по сети приходят лишь нужные колонки; и filter pushdown — предикат из WHERE включается в WHERE-часть запроса к внешней базе, и фильтрацию выполняет сама внешняя СУБД своим индексом, передавая по сети только подходящие строки. А вот тяжёлую аналитику — сложную агрегацию, оконные функции, многотабличные джойны — сканер во внешнюю базу не проталкивает: их выполняет сам DuckDB своим векторизованным движком. Это осознанный дизайн: роль внешней OLTP-базы в federated-запросе — отдать отфильтрованные и спроецированные строки, не нагружаясь аналитической работой, а тяжёлый счёт берёт на себя DuckDB, который как аналитический движок делает это быстрее. Проверить, что pushdown сработал, можно через EXPLAIN: в плане у оператора сканирования виден сгенерированный для внешней СУБД SQL-текст, и если фильтр в нём присутствует — предикат проброшен. Если фильтр использует функцию, специфичную для DuckDB, pushdown ломается и таблица тянется целиком.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что делает DuckDB federated query engine при использовании расширений postgres, mysql, sqlite?

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

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

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

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