DuckLake: лейкхаус-формат с метаданными в SQL-каталоге
Лейкхаус (lakehouse) — это попытка соединить дешёвое объектное хранилище data lake с транзакционными гарантиями data warehouse. Данные лежат файлами Parquet в S3 или аналоге, а сверху работает table format, который превращает россыпь файлов в таблицы со схемой, версиями и ACID. Так устроены Apache Iceberg и Delta Lake.
У всех классических table format есть общая архитектурная черта: метаданные тоже лежат файлами на объектном хранилище — JSON-манифесты, Avro-списки, snapshot-файлы. DuckLake делает иначе. Это лейкхаус-формат от DuckDB Labs, который держит все метаданные в реляционной SQL-базе, а файлами на объектном хранилище оставляет только сами данные. Это маленькое на словах решение меняет почти всё: атомарность транзакций, скорость операций над метаданными, простоту реализации. В этом уроке разберём устройство DuckLake, причину «метаданные в SQL» и три поддерживаемых бэкенда каталога.
DuckLake достиг версии 1.0 13 апреля 2026 года — это production-ready релиз с гарантией обратной совместимости формата.
Проблема метаданных-в-файлах
Чтобы понять, зачем DuckLake, нужно увидеть, что болит у форматов с файловыми метаданными. Когда метаданные таблицы — это набор файлов на объектном хранилище, каждая операция чтения превращается в цепочку обращений: сначала прочитать корневой указатель, по нему — файл текущего снапшота, по нему — манифест-лист, по нему — манифесты, и только потом узнать, какие data-файлы вообще нужно открыть.
Из этой схемы вытекают три конкретные боли. Первая — латентность планирования: чтобы узнать, какие файлы читать, нужно несколько последовательных round-trip к объектному хранилищу, а у S3 латентность каждого запроса — десятки миллисекунд. Вторая — атомарность только в пределах одной таблицы: смена «текущего» снапшота — это атомарная замена одного указателя, и согласованно поменять сразу несколько таблиц нечем; кросс-табличной транзакции нет. Третья — проблема мелких файлов: каждая мелкая правка порождает новые мелкие data-файлы и новые метаданные, число файлов растёт, чтение деградирует, и нужна отдельная процедура compaction.
Корень всех трёх проблем один: объектное хранилище — это key-value-склад больших блобов, оно отлично хранит данные, но плохо подходит под метаданные, которым нужны быстрые точечные запросы и транзакции.
Идея DuckLake: метаданные туда, где им место
DuckLake исходит из простого наблюдения. Метаданные таблиц — это по своей природе реляционные данные: список таблиц, список колонок, список снапшотов, список data-файлов с их статистикой, связи между ними. А для реляционных данных с транзакциями и быстрыми запросами уже сорок лет существует идеальный инструмент — реляционная СУБД.
Поэтому DuckLake разделяет лейкхаус на два чётко разных слоя:
Каталог — это набор обычных таблиц в SQL-базе. В них записано всё: какие в лейкхаусе есть таблицы и колонки, какие существуют снапшоты, какие data-файлы относятся к каждому снапшоту, какая у каждого файла статистика (min/max по колонкам, число строк). Сами Parquet-файлы лежат на объектном хранилище и являются иммутабельными — их не редактируют, только добавляют новые.
Что это даёт по сравнению с файловыми метаданными:
- Планирование запроса — один SQL-запрос к каталогу. Вместо цепочки round-trip к S3 движок делает один запрос к базе: «дай мне data-файлы таблицы X для снапшота N, у которых min/max по колонке date пересекается с фильтром». СУБД возвращает готовый список с pruning по статистике.
- Атомарность сразу по многим таблицам. Коммит в DuckLake — это обычная транзакция в каталожной СУБД. А реляционная транзакция атомарна по определению: можно согласованно поменять метаданные пяти таблиц одним коммитом. Кросс-табличная ACID-транзакция получается бесплатно, из ACID самой СУБД.
- Мелкие правки не плодят мелкие файлы. Раз каталог — это база, маленькое изменение можно записать прямо в строки каталога, не создавая крошечный Parquet-файл. Этот механизм — data inlining — разберём в следующем уроке.
Архитектура: спецификация, реализация, бэкенды
DuckLake устроен как три отдельных вещи, и их полезно не путать.
Спецификация — это открытый документ, описывающий формат: какие таблицы должны быть в каталоге, какая у них схема, как кодируются снапшоты и ссылки на файлы. Спецификация не привязана к DuckDB; по ней может быть написан любой совместимый клиент.
Эталонная реализация — это расширение ducklake для DuckDB. Оно реализует спецификацию: умеет создавать лейкхаус, читать и писать таблицы, делать снапшоты. Расширение ducklake уже входит в число самых скачиваемых core-расширений DuckDB.
Бэкенды каталога — это конкретные СУБД, в которых физически живёт каталог. И здесь важная точность: DuckLake поддерживает ровно три бэкенда.
| Бэкенд каталога | Характер | Когда выбирать |
|---|---|---|
| SQLite | Embedded, файл, один писатель | Локальная разработка, однопользовательский лейкхаус, эксперименты |
| PostgreSQL | Сетевой, настоящий multi-writer | Команда, продакшн, несколько процессов пишут параллельно |
| DuckDB | Self-referential, embedded | Самодостаточный лейкхаус: DuckDB и движок, и каталог |
Других бэкендов нет. Каталогом DuckLake не может быть MySQL или произвольная СУБД — список закрытый и состоит из SQLite, PostgreSQL и DuckDB. Требования к бэкенду — он должен говорить на SQL, поддерживать первичные ключи и уметь хранить таблицы постоянно; этим трём СУБД эти требования соответствуют, и именно они поддержаны.
Выбор бэкенда — это и выбор модели конкуренции записи. SQLite — embedded-файл с одним писателем: для локального лейкхауса отлично, но команде не подходит. PostgreSQL — сетевая СУБД с настоящим multi-writer: несколько процессов параллельно коммитят в один лейкхаус. DuckDB-каталог даёт полностью самодостаточный лейкхаус, где один и тот же движок и исполняет запросы, и хранит метаданные. Под продакшн с несколькими писателями берут PostgreSQL.
Как это выглядит на практике
Создание DuckLake-лейкхауса — это ATTACH с указанием бэкенда каталога и места для данных. Сначала простейший случай — каталог в SQLite, данные в локальной папке:
-- Установить и подключить расширение DuckLake
INSTALL ducklake;
LOAD ducklake;
-- Создать лейкхаус: каталог в SQLite-файле, данные Parquet в папке
ATTACH 'ducklake:sqlite:catalog.sqlite' AS lake (DATA_PATH 'lake_data/');
USE lake;
-- Дальше — обычный SQL. Таблица создаётся в лейкхаусе.
CREATE TABLE orders AS
SELECT * FROM 'raw/orders.parquet';
-- Результат:
-- 842_119 строк. В lake_data/ появились Parquet-файлы,
-- а в catalog.sqlite — метаданные таблицы и первый снапшот.
После этого в SQLite-файле catalog.sqlite лежат метаданные, в папке lake_data/ — Parquet-файлы с данными. Можно заглянуть в каталог и увидеть, что метаданные — это обычные строки в обычных таблицах:
-- Список снапшотов лейкхауса — обычная таблица в каталоге
FROM lake.snapshots();
-- snapshot_id | snapshot_time | schema_version
-- ------------+---------------------+---------------
-- 0 | 2026-05-20 09:14:02 | 0
-- 1 | 2026-05-20 09:14:05 | 1
Для команды каталог переносят в PostgreSQL — меняется только строка подключения, SQL остаётся тем же:
-- Тот же лейкхаус, но каталог в PostgreSQL, данные на S3
ATTACH 'ducklake:postgres:dbname=lakehouse host=db.internal' AS lake
(DATA_PATH 's3://my-bucket/lake/');
Существенно, что для пользователя DuckLake выглядит как обычная база DuckDB. Вы делаете CREATE TABLE, INSERT, SELECT, оконные функции, friendly SQL — всё как всегда. То, что под капотом данные — это Parquet на объектном хранилище, а метаданные — строки в SQL-каталоге, спрятано за расширением ducklake. Лейкхаус-формат с серьёзными гарантиями подключается одной строкой ATTACH.
Попробуй сам
Понадобится DuckDB 1.5.x. Расширение ducklake ставится из core-репозитория.
Задания:
- Выполните
INSTALL ducklake; LOAD ducklake;, затем создайте локальный лейкхаус с каталогом в SQLite:ATTACH 'ducklake:sqlite:cat.sqlite' AS lake (DATA_PATH 'lake_data/');. Создайте в нём таблицу из любого CSV или черезrange(). - Посмотрите на файловую раскладку: какие файлы появились в папке
lake_data/и что лежит вcat.sqlite. Откройтеcat.sqliteотдельным подключением DuckDB или SQLite и убедитесь, что метаданные — это обычные таблицы. - Выполните
FROM lake.snapshots();иFROM lake.table_info();(или аналогичные служебные функции лейкхауса) — посмотрите, как каталог описывает таблицы и снапшоты. - Создайте отдельный лейкхаус с каталогом-бэкендом DuckDB вместо SQLite. Сравните, что изменилось в строке
ATTACH, и сформулируйте, почему для команды из нескольких писателей ни SQLite, ни DuckDB-каталог не подойдут, а PostgreSQL — подойдёт.
Iceberg: иерархия метаданных — metadata file, manifest list, manifests