DuckDB и Apache Iceberg

Лейкхаус без кластера

|
DuckDB и Apache Iceberg

Пятьдесят мегабайт бинарника, миллисекундный старт, никакого JVM и координатора — просто скачал, загрузил расширение, и пишешь SQL прямо против таблиц в S3. С версии 1.5.3 (май 2026) DuckDB умеет не только читать Iceberg, но и писать: MERGE INTO, ALTER TABLE, спецификацию Iceberg V3 и партиционные трансформы. Впервые один встраиваемый движок тянет и чтение, и запись продакшн-таблиц.

Я уже пробовал работать DuckDB к айсбергу, когда знакомился с технологией Apache Iceberg на примере Trino и Nessie

Но ни один движок не работает в вакууме. Продакшн-лейкхаусы держат DuckDB рядом со Spark, Trino, Athena и Snowflake — каждый заточен под свой тип нагрузки. Вопрос не в том, использовать ли DuckDB, а в том, где он сидит в стеке, как держать таблицы оптимизированными под его паттерны запросов и как маршрутизировать правильные запросы в правильный движок.

Полезно думать о трёх слоях, которые не заменяют друг друга:

  1. Движок запросов (DuckDB) — как ты запускаешь SQL. Встраиваемый, стартует за миллисекунды, без кластера.
  2. Формат таблиц + каталог (Iceberg) — как данные хранятся, версионируются и обнаруживаются. Общий для всех движков.
  3. Слой обслуживания — как таблицы остаются здоровыми: компактирование, полная последовательность обслуживания, классификация проблем, политики и маршрутизация.

DuckDB отлично делает слой 1 и совсем не делает слой 3. Когда запрос вдруг замедляется в 10 раз, причина почти никогда не в движке — это раскладка файлов, долг от delete-файлов, устаревшая статистика или таблица, которую никто не обслуживает. Ниже пройдёмся по всем трём слоям.

flowchart LR
  A[Объектное хранилище<br/>Parquet-файлы] --> B[Таблицы Iceberg]
  B --> C[Каталог<br/>Glue / REST / S3 Tables]
  C --> D[DuckDB]
  C --> E[Spark]
  C --> F[Trino]
  C --> G[Athena]
  D --> H[Слой обслуживания]
  F --> H
  H --> B

Почему DuckDB для Iceberg

DuckDB — встраиваемая аналитическая база данных. Она работает внутри твоего процесса — Python, R, Node.js, Go, Java или standalone CLI — без внешних зависимостей. Нет сервера для запуска, нет кластера для масштабирования, нет сетевых задержек между приложением и движком.

Для Iceberg-нагрузок ключевое вот что:

  • Миллисекундный старт. DuckDB инициализируется за миллисекунды. Spark — от секунд до минут в зависимости от состояния кластера. Для интерактивных запросов, работы в ноутбуке и валидации в CI/CD эта разница определяет ощущение от инструмента.
  • Векторное колоночное выполнение. DuckDB обрабатывает данные колоночными векторами, читая row-группы Parquet напрямую. Вместе с метаданными Iceberg это значит, что DuckDB пропускает нерелевантные файлы и row-группы ещё до того, как коснётся данных.
  • 50-мегабайтный размер. Весь движок вместе с расширением Iceberg помещается в один бинарник. Никакой настройки JVM-хипа, shuffle-сервиса или конфигурации исполнителей.
  • Полный SQL. Оконные функции, CTE, коррелированные подзапросы, lateral join, QUALIFY, PIVOT, регулярные выражения — весь аналитический диалект.
  • Нативная интеграция с Python. import duckdb даёт внутрипроцессный движок, который в одном SQL-выражении умеет опрашивать Pandas DataFrame, Arrow-таблицы и Iceberg-таблицы.

Релиз 1.5.3 закрыл последнюю большую дыру: DuckDB теперь может писать в Iceberg через любой REST-каталог — INSERT, UPDATE, DELETE, MERGE INTO, ALTER TABLE и создание партиционированных таблиц. DuckDB больше не «только чтение» для Iceberg.

Подключение к каталогам Iceberg

Расширение Iceberg ставится автоматически при первом использовании. Для таблиц на S3 нужны ещё httpfs и, на AWS, расширение aws:

INSTALL iceberg;LOAD iceberg;INSTALL httpfs;LOAD httpfs;INSTALL aws;LOAD aws;

DuckDB поддерживает два режима доступа к Iceberg: сканирование по пути (только чтение) и таблицы через каталог (полный доступ на запись).

Сканирование по пути через iceberg_scan

Самый простой способ запросить таблицу — указать iceberg_scan на её хранилище:

SELECT order_date, SUM(total) AS revenueFROM iceberg_scan('s3://my-bucket/warehouse/orders/')WHERE order_date >= '2026-01-01'GROUP BY order_dateORDER BY revenue DESC;

DuckDB читает метаданные Iceberg, определяет, какие файлы данных подходят под предикат, и сканирует только нужные Parquet-файлы. Каталог не требуется — удобно для быстрой разведки, разовых анализов или окружений, где доступ к каталогу ограничен. Сканирование по пути не умеет писать.

Подключение REST-каталога

Для продакшена — и для любых операций записи — подключай REST-каталог Iceberg. Большинство каталогов авторизуются через OAuth2:

CREATE SECRET iceberg_secret (    TYPE ICEBERG,    CLIENT_ID 'admin',    CLIENT_SECRET 'password',    OAUTH2_SERVER_URI 'https://catalog.example.com/v1/oauth/tokens'); ATTACH 'warehouse' AS my_lake (    TYPE ICEBERG,    SECRET iceberg_secret,    ENDPOINT 'https://catalog.example.com');

После подключения каталог ведёт себя как обычная база DuckDB. Таблицы адресуются как catalog.schema.table и поддерживают весь SQL — SELECT, INSERT, UPDATE, DELETE, MERGE INTO и ALTER TABLE.

Свежесть каталога

DuckDB кэширует метаданные таблицы при подключении. Если Spark, Flink или Firehose закоммитят новые снапшоты, пока сессия открыта, последующий SELECT может вернуть устаревший снапшот. В долгоживущих процессах важны два рычага:

  • MAX_TABLE_STALENESS на ATTACH — как долго DuckDB может переиспользовать метаданные каталога перед обновлением (например, '10 minutes').
  • Переподключение или обновление после коммита внешнего писателя, если нужен свежий снапшот немедленно.

Это частая неожиданность в проде: ноутбук в 9:00 выглядит корректно, стриминговый джоб пишет в 9:05, а та же сессия DuckDB продолжает читать данные 9:00. Подключение к каталогу — это не живая подписка.

Чтение: паттерны запросов, которые остаются быстрыми

Когда каталог подключён, таблицы Iceberg — обычный SQL. Разница между запросом на 200 мс и на 20 секунд почти всегда в отсечении по метаданным, а не в движке выполнения.

SELECT customer_id, SUM(total) AS revenueFROM my_lake.sales.ordersWHERE order_date BETWEEN '2026-08-01' AND '2026-08-07'  AND country = 'US'GROUP BY customer_idORDER BY revenue DESCLIMIT 20;

Как это работает. DuckDB заранее смотрит на метаданные Iceberg и статистику row-групп Parquet и отсекает лишнее до того, как начнёт читать данные. Если колонка в фильтре партиционирована или отсортирована, движок сразу пропускает целые файлы и row-группы — читать байты вообще не приходится. А вот фильтр по обычной колонке (не партиционированной и не отсортированной) заставляет сканировать больше данных: сам движок не тормозит, но читает слишком много.

Если скан медленнее ожидаемого, загляни в метаданные:

SELECT * FROM iceberg_snapshots(my_lake.sales.orders);SELECT file_path, record_count, contentFROM iceberg_metadata(my_lake.sales.orders);

iceberg_snapshots показывает историю. iceberg_metadata — сканируешь ли ты сотни мелких файлов или компактный набор крупных; это первая диагностика, когда DuckDB «внезапно» замедлился.

Time travel и снапшоты

Коммиты Iceberg — это снапшоты. DuckDB может запросить любое историческое состояние по id снапшота или метке времени — из таблицы каталога или через iceberg_scan:

-- Таблица из каталогаSELECT *FROM my_lake.sales.orders AT (VERSION => 8027658604211071520); SELECT *FROM my_lake.sales.orders AT (TIMESTAMP => '2026-08-01 12:00:00'); -- Сканирование по путиSELECT count(*)FROM iceberg_scan(    's3://my-bucket/warehouse/orders/',    snapshot_from_id := 8027658604211071520);

Time travel — это функция чтения, а не политика хранения. Пока снапшоты живут, старые файлы и манифесты никто не удаляет — они занимают место на диске. Для аудита и откатов это удобно. Но если чистить снапшоты по расписанию никто не будет, счёт за хранение будет расти месяц за месяцем.

Запись: что открывает версия 1.5.3

До версии 1.5.3 DuckDB мог только читать Iceberg — запись не поддерживалась. Теперь появился и слой записи, которого не хватает многим продакшн-задачам. Правда, с ограничениями, о которых стоит знать.

Создание таблиц и INSERT

Создавай партиционированные таблицы Iceberg обычным SQL, включая bucket- и truncate-трансформы, появившиеся в 1.5.3:

CREATE TABLE my_lake.analytics.events (    event_id BIGINT,    user_id BIGINT,    country VARCHAR,    event_type VARCHAR,    event_time TIMESTAMP)PARTITIONED BY (bucket(16, user_id), truncate(2, country)); INSERT INTO my_lake.analytics.events    VALUES (1, 1001, 'RU', 'click', '2026-08-01 12:00:00'),           (2, 1002, 'US', 'view',  '2026-08-01 12:05:00');

Подбирай партиции под фильтры, которые реально гоняешь. Высококардинальный bucket по user_id помогает точечным поискам. Дата или усечённая страна — диапазонным и размерным фильтрам. Стратегия партицирования, не совпадающая с предикатами запросов, не даёт DuckDB ничего отсекать.

MERGE INTO для апсертов

MERGE INTO закрывает апсерты — самый частый паттерн записи в CDC- и slowly changing dimension-процессах:

MERGE INTO my_lake.analytics.customers AS target    USING staging_updates AS source    ON source.customer_id = target.customer_id    WHEN MATCHED THEN UPDATE SET *    WHEN NOT MATCHED THEN INSERT *;

MERGE INTO использует семантику merge-on-read: совпавшие строки записываются как позиционные удаления, а новые версии — как файлы данных. Такой же подход используют Spark и Trino, поэтому итоговое состояние таблицы интероперабельно между движками.

Эволюция схемы

ALTER TABLE теперь работает c Iceberg-таблицами – добавление, переименование и удаление колонок и переименование таблиц без переписывания данных:

ALTER TABLE my_lake.analytics.events ADD COLUMN device VARCHAR;ALTER TABLE my_lake.analytics.events RENAME COLUMN event_type TO action;ALTER TABLE my_lake.analytics.events DROP COLUMN device;

Изменения схемы в Iceberg – правка метаданных. DuckDB обновляет id текущей схемы в метаданных таблицы, и изменения сразу видны любому другому движку, подключённому к тому же каталогу.

Поддержка Iceberg V3

DuckDB 1.5.3 поддерживает спецификацию Iceberg V3: тип VARIANT, точность TIMESTAMP_NS, бинарные векторы удалений и lineage строк. Таблицы V3 кодируют удаления компактными Puffin-файлами вместо Parquet-файлов позиционных удалений — заметно снижая накладные расходы метаданных при частых обновлениях. Типы Geography и Unknown пока не поддерживаются.

CREATE TABLE my_lake.analytics.v3_eventsWITH ('format-version' = 3) AS    SELECT 1 AS id,           {'kind': 'click', 'x': 10}::VARIANT AS payload,           TIMESTAMP_NS '2026-08-01 12:00:00.123456789' AS event_time;

Ограничения записи, которые всё ещё действуют

Запись идёт через подключённый каталог — iceberg_scan остаётся read-only. UPDATE, DELETE и MERGE INTO пишут позиционные удаления (merge-on-read), а не copy-on-write. Поэтому частые апсерты накапливают долг delete-файлов, который каждый последующий скан DuckDB обязан применять. Это ожидаемое поведение Iceberg, а не баг DuckDB — и именно поэтому компактирование должно жить в одной ментальной модели с записью.

Налог от delete-файлов на чтение

Каждый MERGE, UPDATE или DELETE, закоммиченный DuckDB (или Spark, или Flink), добавляет delete-файлы. При следующем чтении DuckDB обязан загрузить эти удаления и отфильтровать совпавшие строки из файлов данных. Горстка delete-файлов — дёшево. Тысячи — типичный результат ночного CDC — превращают векторный скан в «сначала примени удаления, потом сканируй».

Это та же проблема merge-on-read, с которой сталкивается каждый Iceberg-движок. DuckDB не компактит delete-файлы. Если используешь DuckDB как писателя для апсертов, всё равно нужен процесс обслуживания, который переписывает файлы данных и сбрасывает применённые удаления. Иначе движок, казавшийся мгновенным в первый день, деградирует с каждым успешным MERGE.

Производительность: почему раскладка таблицы определяет скорость

DuckDB быстр, потому что читает Parquet напрямую с векторным выполнением, — но эта скорость целиком зависит от того, как организованы лежащие файлы. Раскладка таблицы — самый большой рычаг производительности запросов DuckDB, и именно это измерение большинство команд упускают.

Проблема мелких файлов

Любая Iceberg-таблица начинается как набор Parquet-файлов в объектном хранилище. Каждый запрос должен прочитать метаданные, определить релевантные файлы и достать их по сети. Если в таблице 50 000 мелких файлов (типично после стриминговой загрузки или частых мелких записей), DuckDB отправляет 50 000 HTTP GET к S3 — и накладные расходы I/O доминируют над общим временем запроса независимо от скорости движка.

Компактирование сливает мелкие файлы в крупные (256–512 МБ оптимальны для большинства нагрузок). После сжатия та же таблица может иметь 200 файлов вместо 50 000. DuckDB сканирует её за секунды вместо минут.

Порядок сортировки и отсечение по предикатам

Файлы Parquet содержат статистики min/max по row-группе и по колонке. Когда данные отсортированы по колонкам, по которым ты фильтруешь, отсечение по предикатам пропускает целые row-группы без чтения. WHERE event_date = '2026-08-01' на отсортированной по дате таблице читает долю данных, которую тот же запрос читает на неотсортированной.

Загвоздка: оптимальный порядок сортировки зависит от того, как таблицу реально опрашивают, и разные движки могут опрашивать её по-разному. Порядок, оптимальный для Trino-дашбордов, может не подойти для ad-hoc-разведки в DuckDB.

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

Скорость и цена компактирования

Компактирование — не бесплатное. Spark-компактирование таблицы на 200 ГБ занимает ~1600 секунд и стоит примерно 3000₽/ТБ. Rust/DataFusion-движок делает тот же binpack за 221 секунду при ~300₽/ТБ — примерно в 7 раз быстрее и на порядок дешевле. Sort-компактирование, переписывающее файлы в оптимальном порядке для отсечения предикатов, даёт ещё больший выигрыш, потому что Rust-движок обрабатывает Parquet напрямую, без накладных расходов java машины — тот же «без кластера» подход, что и у DuckDB, только применительно к обслуживанию.

Дело не только в более быстром сжатии. Компактирование ускоряет запросы DuckDB. Хорошо сжатые, правильно отсортированные таблицы со свежей статистикой колонок – фундамент производительности DuckDB на Iceberg. Без них ты меряешь ввод-вывод, а не скорость движка запросов.

Когда DuckDB, а когда Spark, Trino, Athena

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

Измерение DuckDB Spark Trino Athena
Развёртывание Встраиваемый, в процессе Распределённый кластер Распределённый кластер Serverless
Старт Миллисекунды Секунды–минуты Секунды Секунды
Лучше всего для Интерактивные запросы, ноутбуки, CI/CD, малые/средние данные Тяжёлый ETL, крупные трансформации, ML-пайплайны Конкурентные дашборды, федеративные запросы Ad-hoc serverless SQL
Конкурентность Однопользовательский (встраиваемый) Высокая (распределённый) Высокая (распределённый) Умеренная (цена за запрос)
Поддержка записи Полная (1.5.3, REST-каталог) Полная Полная Ограниченная (INSERT, CTAS)
Модель цены Бесплатный (open source) Стоимость кластера Стоимость кластера $5/ТБ скана
Макс. размер данных Память + диск одного узла Петабайты Петабайты Петабайты
Интеграция с Python Нативная (в процессе) PySpark (клиент-сервер) JDBC/REST JDBC/REST

Где DuckDB выигрывает

  • Интерактивная разведка: дата-сайентист открывает ноутбук, импортирует DuckDB и опрашивает Iceberg-таблицы в S3 без разворачивания чего-либо. Цикл обратной связи — миллисекунды, а не минуты.
  • Валидация данных в CI/CD: DuckDB работает внутри GitHub Action или CI-пайплайна для проверки схем таблиц, контроля качества данных и генерации тестовых отчётов — без поднятия кластера.
  • Малая и средняя аналитика: для данных, умещающихся в память одного узла (до сотен гигабайт со spill-to-disk), DuckDB не уступает или обгоняет распределённые движки, потому что не тратит время на координацию, shuffle и сеть.
  • Прототипирование и разработка: тестируй трансформации локально до деплоя в Spark или Trino в проде.
  • Вызовы ИИ-агентов: короткие, ограниченные SQL из цикла агента. Профиль старта и задержки DuckDB лучше ложится в формат «запрос—ответ», чем прогрев кластера.

Где DuckDB не подходит

  • Тяжёлый ETL в масштабе: многотерабайтные трансформации, требующие распределённого shuffle и параллельной записи в сотни партиций. Распределённая модель Spark существует не просто так — и замена Spark только для компактирования это другое решение, чем замена Spark для ETL.
  • Высококонкурентное обслуживание: DuckDB встраиваемый и однопроцессный. Он не вытягивает сотни конкурентных дашборд-запросов так, как Trino или Snowflake.
  • Стриминговое вливание: Flink и Spark Structured Streaming тянут непрерывный micro-batch в Iceberg. DuckDB пакетный.

Реальность такая: большинство прод-команд гоняет несколько движков одновременно. DuckDB — ноутбуки и ad-hoc. Spark — ETL. Trino — дашборды. Вопрос в том, как направить нужный запрос в нужный движок без захардкоженного решения в коде приложения.

Маршрутизация запросов между движками

В многодвижковом лейкхаусе задача маршрутизации обманчиво сложна. У каждого движка свои нюансы SQL-диалекта, своя модель цены и свои характеристики задержки. Команды обычно начинают с захардкоженного выбора движка — Spark для этого пайплайна, Trino для того дашборда, DuckDB для ad-hoc — и накапливают технический долг по мере эволюции ландшафта.

Нормальному слою маршрутизации нужно уметь диспетчеризацию SQL, перевод диалектов, failover по состоянию здоровья и выбор движка по стоимости. Ещё нужны стабильные endpoints, чтобы приложения не ломались, когда ты добавляешь более быстрый движок или убираешь старый.

Маршрутизационные группы сопоставляют типы нагрузки с пулами движков, с приоритетом и логикой failover:

  • Analytics (интерактивный, ad-hoc) → DuckDB + Trino, DuckDB предпочтителен для субсекундных запросов
  • BI (дашборды, отчёты) → Trino + Snowflake, оптимизирован под конкурентный доступ
  • ETL (трансформации, пайплайны) → Spark + Athena, оптимизирован под пропускную способность
  • ИИ-агенты → DuckDB + Trino через MCP, с лимитами стоимости и guardrails
flowchart LR
  A[Приложение] --> B[Слой маршрутизации]
  B --> C[Analytics<br/>DuckDB + Trino]
  B --> D[BI<br/>Trino + Snowflake]
  B --> E[ETL<br/>Spark + Athena]
  C --> F[Таблицы Iceberg]
  D --> F
  E --> F

Приложения получают стабильный endpoint на группу маршрутизации. Когда добавляешь DuckDB в analytics-пул или убираешь непродуктивный движок, слой маршрутизации поглощает изменение. Никаких правок в коде приложения и миграций endpoints.

Что DuckDB не делает: обслуживание в проде

DuckDB — движок запросов. Он читает и пишет данные. Продакшн-таблицам Iceberg нужен слой непрерывной эксплуатации, который не даёт ни один движок запросов, — и именно здесь большинство команд недоинвестируют.

Обслуживание таблиц

Каждая Iceberg-таблица копит эксплуатационный долг: мелкие файлы от стриминговой записи, протухшие снапшоты, съедающие хранилище, orphan-файлы от упавших джобов, манифесты, растущие глубже с каждым коммитом. Полная последовательность обслуживания — протухание снапшотов, удаление orphan-файлов, сжатие файлов данных, переписывание манифестов, пересчёт статистики колонок — должна выполняться в правильном порядке зависимостей, непрерывно, по всем таблицам.

DuckDB не запускает обслуживание. Trino и Athena тоже. Spark может гонять компактирование через хранимые процедуры, но планирование, мониторинг и выстраивание последовательности для сотен таблиц — отдельная инженерная задача, которая масштабируется вместе с лейкхаусом, а не с запросной нагрузкой.

Наблюдаемость

Когда запрос DuckDB внезапно замедляется в 10 раз, начинай с таблицы: число файлов, доля delete-файлов, накопление снапшотов, перекос партиций, устаревшая статистика. Без мониторинга здоровья на уровне таблиц диагностика превращается в гадание — и ты будешь крутить SQL, вместо того чтобы сжать файлы.

Операционный слой

Тем, кто не хочет собирать слой обслуживания руками, стоит посмотреть в сторону инструментов операционной плоскости, которые закрывают именно этот пробел. Автономное обслуживание прогоняет полную последовательность в правильном порядке зависимостей. Классификация проблем подсвечивает деградацию таблиц до того, как она доберётся до задержки запроса. Каскадные политики управляют компактированием, хранением и очисткой от каталога к неймспейсу к таблице — заменяя разрозненные Airflow DAG, которые команды копят по мере роста лейкхауса.

Связка простая: DuckDB отвечает за запросы, а слой обслуживания — за всё остальное: обслуживание, маршрутизацию, наблюдаемость, управление. Таблицы остаются компактными, правильно отсортированными и здоровыми. Запросы DuckDB — быстрыми.

Фреймворк решений

Используй DuckDB, когда:

  • Нагрузка интерактивная — ноутбуки, ad-hoc SQL, локальная разработка
  • Нужен миллисекундный старт — CI-проверки, вызовы агентов, короткоживущие процессы
  • Рабочий набор умещается в один узел (память плюс spill-to-disk)
  • Нужны чтение и запись через REST-каталог без поднятия Spark
  • Python или CLI — естественный интерфейс

Используй Spark, Trino или Athena, когда:

  • Трансформации многотерабайтные и требуют распределённого shuffle
  • Много конкурентных пользователей бьют в одни дашборды
  • Вливание — непрерывный стриминг
  • Уже платишь за кластер или serverless-модель скана, подходящую задаче

Добавляй слой обслуживания, когда:

  • Запросы DuckDB быстры настолько, насколько быстры раскладка файлов, удаления и статистика
  • Запускаешь больше одного движка и не хочешь захардкоженного выбора
  • Протухание снапшотов, очистка orphan-файлов и компактирование должны идти по порядку, по всему лейкхаусу
  • Хочешь здоровье и политики вместо растущей кучи DAG обслуживания

На практике DuckDB — движок, который ты добавляешь первым для разведки. Слой обслуживания — это то, что держит этот движок честным, когда те же таблицы пишут Spark, Flink, MERGE-джобы и агенты.

Итог

DuckDB делает Iceberg доступным без инфраструктуры. Установил бинарник, подключил каталог, запустил SQL. С версии 1.5.3 он тянет чтение, запись, эволюцию схемы, time travel и апсерты — покрывая полный жизненный цикл многих нагрузок без единого кластера.

Держи в голове модель из трёх слоёв. DuckDB — движок. Iceberg — общая таблица. Слой обслуживания — то, что не даёт delete-файлам, мелким файлам и накоплению снапшотов превратить миллисекундный движок в бенчмарк S3-задержки. Качество компактирования, порядок сортировки и статистика колонок — самые большие рычаги, и они требуют системы обслуживания, работающей непрерывно по всему лейкхаусу. В проде DuckDB — один движок в многодвижковой архитектуре, где маршрутизация, мониторинг здоровья и автономное обслуживание определяют, быстр ли конкретный запрос.

Начни с DuckDB для интерактивных и разработочных нагрузок. Добавь его в группу аналитиков рядом с Trino для субсекундных запросов. Держи лежащие таблицы оптимизированными с помощью осмысленного компактирования. И подключи каталоги, чтобы видеть полную операционную картину по каждому движку, таблице и каталогу лейкхауса.