Когда вышел DuckDB 1.0, я написал статью Всё что нужно знать про DuckDB. Теперь вышел DuckLake v1.0 — LakeHouse-формат из той же экосистемы. Это отличный повод разобраться, как он устроен и как его можно использовать в production-сценариях.

DuckLake — это новый LakeHouse-формат из экосистемы DuckDB. Он позволяет хранить данные в открытом формате .parquet, использовать S3-совместимые хранилища, версионировать данные, делать time travel и изменять схему таблиц.

Но его основная идея заключается в другом: метаданные хранятся не в сложной цепочке файлов в объектном хранилище, а в обычной реляционной базе данных.

В этой статье разберём, какую проблему решает DuckLake, чем его архитектура отличается от Apache Iceberg и как поднять DuckLake локально или в окружении, приближённом к production: PostgreSQL для каталога метаданных и S3/MinIO для файлов данных.

Если удобнее смотреть, чем читать, то я также записал видео — Что такое DuckLake. В статье будет резюме видео, результаты выполнения команд и ссылки на документацию, поэтому эти материалы хорошо дополняют друг друга.

Если хотите глубже погрузиться в отдельные темы, могут пригодиться мои предыдущие материалы:

Три эпохи работы с данными

Чтобы понять, зачем нужен DuckLake, давайте кратко посмотрим на развитие платформ данных.

Классические базы данных

Раньше всё было относительно просто. Мы создавали базу данных: PostgreSQL, MySQL, ClickHouse и так далее. Внутри неё лежали таблицы, данные, индексы, статистика и метаданные.

Хранение, вычисления и управление данными находились внутри одной системы.

Например, PostgreSQL хранит таблицы во внутреннем бинарном формате: в файлах данных, разбитых на страницы по 8 КБ. Рядом существуют служебные файлы, индексы и WAL-журнал. Мы не можем просто взять физические файлы таблицы и начать читать их через Spark, Pandas или DuckDB.

Данные фактически заперты внутри конкретной СУБД.

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

Data Lake

Потом появился Data Lake.

Основная идея: отделить хранение от вычислений.

Данные физически лежат вне вычислительного движка — например, в S3 и других объектных хранилищах либо в HDFS. Форматами хранения становятся .csv, .json, .parquet, .orc и другие открытые форматы.

Теперь можно хранить данные в том же S3 в .parquet-формате и читать их чем угодно:

  • Pandas;

  • DuckDB;

  • Apache Spark;

  • любым другим движком, который понимает этот формат.

Это дало огромную гибкость. Но появилась другая проблема: набор файлов сам по себе ещё не является таблицей.

Представим таблицу пользователей.

В январе мы начали писать .parquet-файлы со схемой: id, first_name, last_name. Через месяц бизнес попросил собирать email, и новые файлы стали записываться уже со схемой: id, first_name, last_name, email.

В результате в одном бакете появляются .parquet-файлы с разными схемами: в старых email отсутствует, а в новых — уже есть.

Такой Data Lake постепенно превращается в Data Swamp — болото данных, в котором становится сложно разобраться.

LakeHouse

LakeHouse — это попытка взять лучшее из двух миров:

  • данные хранятся отдельно, в открытом формате;

  • вычисления можно запускать разными движками;

  • но поверх файлов появляется табличная абстракция.

LakeHouse знает:

  • какие файлы относятся к таблице;

  • какая схема таблицы актуальна;

  • какие версии данных существуют и какая из них актуальна;

  • какие файлы больше не используются;

  • какие статистики есть у файлов;

  • какие партиции нужно читать для конкретного запроса.

В результате физически данные по-прежнему лежат в S3 или другом хранилище, но для пользователя это выглядит как обычная таблица:

SELECT *
FROM users
WHERE registration_date = '2025-01-01';

Именно такую задачу решают Apache Iceberg, Delta Lake, Apache Hudi и DuckLake.

Если хотите подробнее разобраться с Apache Iceberg, рекомендую мою статью — Инфраструктура для Data-Engineer Data Lake Apache Iceberg. Также рекомендую видео с мини-демо по созданию Data LakeHouse — Data LakeHouse (modern data stack) | Apache Iceberg, S3 MinIO, Trino, Spark, PostgreSQL.

Apache Iceberg: сложность метаданных

Одним из ключевых игроков в мире LakeHouse стал Apache Iceberg.

Упрощённо его архитектуру можно представить так:

  1. Данные — .parquet, .orc или .avro-файлы в объектном хранилище.

  2. Метаданные — metadata-файлы в .json, manifest list и manifest-файлы в .avro, которые описывают актуальное состояние таблицы и набор её файлов.

  3. Каталог — сервис или база данных, в которой хранится ссылка на актуальный metadata-файл таблицы.

Iceberg даёт очень важные возможности:

  • time travel;

  • schema evolution;

  • snapshots;

  • partition pruning;

  • ACID-операции;

  • работу с несколькими движками.

Но в чём основная сложность Iceberg? Давайте посмотрим, как в упрощённом виде выглядит обычная вставка данных в таблицу:

  1. Вычислительный движок обращается к каталогу и получает актуальное состояние таблицы.

  2. Записывает новые файлы данных в S3 или другое объектное хранилище.

  3. Создаёт новые metadata-, manifest list- и manifest-файлы.

  4. Обновляет в каталоге ссылку на новый актуальный metadata-файл.

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

То есть в объектном хранилище находятся не только данные, но и значительный объём технических файлов метаданных.

Это не значит, что Iceberg плохой. Это зрелый, мощный и проверенный формат, который подходит для множества production-сценариев. Но его инфраструктура и жизненный цикл метаданных могут быть сложнее, чем хотелось бы небольшой команде или проекту.

И вот здесь появляется DuckLake.

Что такое DuckLake

Идея DuckLake гениальна в своей простоте: зачем хранить метаданные в россыпи файлов на S3, если для этого есть нормальные, всем привычные реляционные базы данных?

Если говорить проще: в объектном хранилище лежат только файлы данных, которые можно читать и обрабатывать разными движками, а не только DuckDB. Вся информация о таблицах, схемах, снапшотах, статистике по файлам и версиях хранится в каталоге на базе реляционной СУБД.

DuckLake полностью убирает слой .json- и .avro-манифестов из объектного хранилища. Вставка данных в DuckLake выглядит прозрачнее:

  1. Подключаемся к каталогу метаданных.

  2. Пишем новые данные в S3 или другое хранилище.

  3. Обновляем метаданные в реляционной базе.

  4. Коммитим изменения.

То есть работа идёт через привычные табличные абстракции, а метаданные становятся более прозрачными и естественными для анализа и администрирования.

Важно разделять сам формат и его реализацию. Создатели DuckDB сделали не только спецификацию формата для DuckLake (Specification), но и программную реализацию в виде DuckDB Extension (DuckDB Extension). Это существенно снижает порог входа и закрывает основные практические вопросы использования формата.

Если сравнивать с Apache Iceberg, то подход отличается. Iceberg в первую очередь определяет спецификацию табличного формата, а поддержка формата реализуется различными компонентами экосистемы: движками запросов, каталогами и библиотеками. Например, с Iceberg работают PyIceberg, Spark, Trino и другие системы. Об этом я рассказывал в статье — Инфраструктура для Data-Engineer Apache Iceberg.

А теперь давайте посмотрим, как DuckLake выглядит на практике.

Практика

Весь код с примерами хранится в репозитории — pet_project_what_is_ducklake.

Рекомендую склонировать репозиторий, чтобы последовательно повторить примеры из статьи.

Все зависимости зафиксированы в файле pyproject.toml репозитория. Для управления окружением я использовал uv.

Все дальнейшие примеры будут показаны в виде ячеек Jupyter Notebook. Если хотите запустить Jupyter через uv, воспользуйтесь командой:

uv run jupyter lab

Чтобы выполнять SQL-команды в Jupyter Notebook, я использовал расширение JupySQL. Добавляйте %%sql в начало SQL-ячейки:

%%sql  
INSTALL ducklake;

При работе с DuckLake стоит учитывать, что у него есть две независимые части.

Первая — это каталог метаданных. Каталог — это база данных, которая поддерживает транзакции и ограничения PRIMARY KEY.

В качестве каталога можно использовать:

  • DuckDB — для локального сценария с одним клиентом;

  • SQLite — для нескольких локальных клиентов;

  • PostgreSQL — для многопользовательского LakeHouse и удалённых клиентов.

Так выглядит спецификация для DuckLake (Specification/Tables):

Спецификация DuckLake
Спецификация DuckLake

То есть метаданные можно анализировать обычным SQL-запросом.

Второе — это хранение данных. Хранилище данных — это компонент, в котором можно сохранять .parquet-файлы.

Основные файлы данных DuckLake хранит в .parquet-формате. При этом небольшие изменения DuckLake может временно хранить прямо в каталоге метаданных через data inlining. Data Inlining.

Как пример, для таблицы main.users структура файлов может выглядеть так:

s3://my-bucket/ducklake/
└── main/
    └── users/
        ├── ducklake-uuid-1.parquet
        ├── ducklake-uuid-2.parquet
        └── ducklake-uuid-3.parquet

Если упростить процесс вставки данных, то он будет выглядеть так:

  1. DuckDB формирует новый .parquet-файл.

  2. Файл записывается в S3 (объектное хранилище) или локальную директорию.

  3. В каталоге создаётся новый снапшот.

  4. В SQL-таблицы метаданных записывается информация о новом файле.

  5. Транзакция коммитится.

При чтении DuckLake оптимизатор запросов сначала получает актуальный снапшот и список файлов, которые относятся к таблице. Затем может использовать статистики файлов, чтобы не читать заведомо ненужные .parquet-файлы.

Локальный DuckLake

Самый простой способ попробовать DuckLake — создать его локально, без внешнего каталога и объектного хранилища.

Конфигурация будет выглядеть так:

  • Каталог метаданных — локальная БД DuckDB.

  • Хранение данных — локальная папка.

  • Вычислитель — DuckDB.

Устанавливаем расширение:

INSTALL ducklake;

Создаём и подключаем локальный DuckLake:

ATTACH 'ducklake:my_ducklake.ducklake' AS my_ducklake;
USE my_ducklake;

Создадим тестовую таблицу с синтетическими данными при помощи community-расширения fakeit:

INSTALL fakeit FROM community;
LOAD fakeit;

CREATE OR REPLACE TABLE fake_data AS
SELECT
    s.id AS id,
    fakeit_name_full() AS name,
    fakeit_contact_email() AS email,
    fakeit_address_city() AS city,
    fakeit_address_country() AS country
FROM
    generate_series(1, 100) AS s(id);

Проверим наполнение таблицы:

FROM fake_data

Получаем обычную таблицу на 100 строк с именами, email, городами и странами.

id

name

email

city

country

1

Andreane Trantow

gudrungreenholt@hamill.info

Croninfurt

San Marino

2

Jace Sanford

oswaldocollier@cummings.name

Skyemouth

Sri Lanka

...

...

...

...

...

У вас будет другой набор данных.

Самое интересное — заглянуть в служебный каталог. Там лежат десятки служебных таблиц: ducklake_table, ducklake_data_file, ducklake_snapshot, ducklake_snapshot_changes и другие — вся спецификация DuckLake описана в документации проекта.

Посмотреть список всех таблиц, включая служебные, можно стандартным SQL:

SELECT
    table_catalog,
    table_name
FROM information_schema.tables

DuckDB автоматически подключает каталог метаданных под именем __ducklake_metadata_<имя_DuckLake>. В нашем случае это __ducklake_metadata_my_ducklake. Переключаемся в этот каталог и смотрим историю изменений:

USE '__ducklake_metadata_my_ducklake';

FROM ducklake_snapshot_changes

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

snapshot_id

changes_made

author

commit_message

commit_extra_info

11

dropped_table:10,created_table:"main"."fake_da...

None

None

None

12

altered_table:11

None

None

None

13

inserted_into_table:11,deleted_from_table:11

None

None

None

14

dropped_table:11,created_table:"main"."fake_da...

None

None

None

Каждое действие — создание таблицы, ALTER, INSERT, DELETE — фиксируется отдельным снапшотом с понятным описанием изменения (created_table, altered_table, inserted_into_table и т.д.).

А в ducklake_snapshot можно увидеть время каждого снапшота и версию схемы на этот момент:

FROM ducklake_snapshot

snapshot_id

snapshot_time

schema_version

next_catalog_id

next_file_id

11

2026-06-11 05:44:32.263483+03:00

11

12

11

12

2026-06-11 05:54:05.183536+03:00

12

12

11

13

2026-06-11 05:55:50.376861+03:00

12

12

12

14

2026-07-21 13:57:49.193858+03:00

13

13

13

В ducklake_data_file хранится информация обо всех физических .parquet-файлах: путь, количество строк, размер и диапазон снапшотов, в котором файл актуален (begin_snapshot / end_snapshot). Благодаря этому DuckLake знает, какие файлы нужно читать для текущей версии таблицы, а какие относятся к её истории.

FROM ducklake_data_file

Результат (часть полей удалено для демонстрации):

data_file_id

begin_snapshot

end_snapshot

path

record_count

10

11

13

ducklake-019eb491-279a-7b48-a235-

100

11

13

14

ducklake-019eb49b-ad16-7981-9bee-

100

12

14

NULL

ducklake-019f8453-248b-7077-8557-

100

Так как данные физически хранятся в .parquet, их можно читать и сторонними инструментами. Но важно понимать: Pandas в примере читает конкретный файл, а не DuckLake-таблицу целиком. Он не использует каталог метаданных, не учитывает снапшоты и delete-файлы.

Для демонстрации при помощи JupySQL найдём путь к актуальному файлу и передадим его в read_parquet:

df = %sql FROM ducklake_data_file WHERE end_snapshot IS NULL
pd.read_parquet(
	f'my_ducklake.ducklake.files/main/fake_data/{df.path[0]}'
)

В нашем простом примере получим те же 100 строк, что и при чтении таблицы через DuckDB:

id

name

email

city

country

1

Andreane Trantow

gudrungreenholt@hamill.info

Croninfurt

San Marino

2

Jace Sanford

oswaldocollier@cummings.name

Skyemouth

Sri Lanka

...

...

...

...

...

DuckLake: PostgreSQL + S3 MinIO

Локальный DuckLake удобен для старта, но для сценария с несколькими клиентами и удалённым объектным хранилищем лучше разделить каталог метаданных и данные.

В этом примере:

  • Каталог метаданных — PostgreSQL, развёрнутый через Docker.

  • Хранение данных — S3-совместимое хранилище MinIO, развёрнутое через Docker.

  • Вычислитель — DuckDB.

Такой вариант уже напоминает полноценный LakeHouse.

Для этого развернём PostgreSQL, MinIO и контейнер, который создаст бакет:

docker-compose.yaml
x-config: &val "prod"

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  minio:
    image: minio/minio:RELEASE.2025-09-07T16-13-09Z
    container_name: s3-ducklake
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes:
      - ./data:/data
    command: server /data --console-address ":9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  mc:
    image: minio/mc:RELEASE.2025-08-13T08-35-41Z
    container_name: minio-init-ducklake
    depends_on:
      minio:
        condition: service_healthy
    environment:
      BUCKET_NAME: *val
    entrypoint: >
      /bin/sh -c "
      until (/usr/bin/mc alias set minio http://minio:9000 minioadmin minioadmin) do echo '...waiting...' && sleep 1; done;
      /usr/bin/mc mb --ignore-existing minio/$$BUCKET_NAME;
      exit 0;
      "

Для поднятия инфраструктуры необходимо воспользоваться командой: docker compose up -d.

Затем устанавливаем расширения:

INSTALL ducklake;
INSTALL postgres;
  • ducklake отвечает за формат DuckLake;

  • postgres позволяет DuckDB работать с PostgreSQL как с каталогом.

Для многопользовательского или удалённого сценария документация DuckLake рекомендует PostgreSQL как каталог метаданных. PostgreSQL должен быть версии 12 или выше. Choosing a Catalog Database.

Создаём secret с параметрами подключения к PostgreSQL-каталогу:

CREATE OR REPLACE SECRET
(
    TYPE postgres,
    HOST 'localhost',
    PORT 5432,
    DATABASE postgres,
    USER 'postgres',
    PASSWORD 'postgres'
);

В production, конечно же, не нужно использовать такие логин и пароль.

И секрет для S3 (в нашем случае — MinIO):

CREATE OR REPLACE SECRET
(
    TYPE s3,
    URL_STYLE 'path',
    USE_SSL FALSE,
    ENDPOINT 'localhost:9000',
    KEY_ID 'minioadmin',
    SECRET 'minioadmin'
);

Если DuckDB запущен внутри Docker Compose вместе с MinIO, в ENDPOINT обычно нужно использовать имя сервиса:

ENDPOINT 'minio:9000'

А если DuckDB работает на локальной машине:

ENDPOINT 'localhost:9000'

Подключаем DuckLake. PostgreSQL будет каталогом метаданных, а DATA_PATH определит место для физических файлов данных:

ATTACH 'ducklake:postgres:dbname=postgres' AS my_ducklake (DATA_PATH 's3://prod/ducklake/');
USE my_ducklake;

Теперь:

  • Метаданные DuckLake живут в PostgreSQL.

  • Файлы данных лежат в MinIO.

  • SQL выполняется через DuckDB.

Именно в этом и заключается сильная сторона архитектуры: для каталога используется привычная транзакционная база данных, а для больших объёмов данных — объектное хранилище.

Давайте также создадим таблицу:

INSTALL fakeit FROM community;
LOAD fakeit;

CREATE OR REPLACE TABLE fake_data AS
SELECT
    s.id AS id,
    fakeit_name_full() AS name,
    fakeit_contact_email() AS email,
    fakeit_address_city() AS city,
    fakeit_address_country() AS country
FROM
	generate_series(1, 100) AS s(id);

Проверим результат:

FROM fake_data;

id

name

email

city

country

1

Jasen Stamm

gildarunte@herzog.name

Kerlukeview

French Southern Territories

2

Bonita Lubowitz

glendasmitham@bernhard.com

Mafaldaport

Zambia

...

...

...

...

...

В PostgreSQL появятся служебные таблицы метаданных DuckLake, а в бакете prod.parquet-файлы.

Эволюция схемы (schema evolution)

Одна из основных проблем Data Lake — изменение схемы данных в файлах.

В DuckLake schema evolution реализована без обязательного переписывания старых файлов. DuckLake использует идентификаторы полей, поэтому при чтении может корректно восстановить актуальную структуру таблицы даже для файлов, записанных со старой схемой. Schema Evolution.

Давайте разберём это на примере и добавим новую колонку name_prefix в таблицу fake_data:

ALTER TABLE fake_data
ADD COLUMN name_prefix VARCHAR;

Проверим таблицу:

FROM fake_data;

id

name

email

city

country

name_prefix

1

Jasen Stamm

gildarunte@herzog.name

Kerlukeview

French Southern Territories

NULL

2

Bonita Lubowitz

glendasmitham@bernhard.com

Mafaldaport

Zambia

NULL

...

...

...

...

...

...

Новая колонка появится во всех строках, но старые данные будут иметь значение NULL.

Важно: при ALTER TABLE ... ADD COLUMN старые .parquet-файлы не переписываются. DuckLake понимает, что в старых файлах новой колонки ещё не было, и при чтении возвращает для неё NULL. Schema Evolution.

Теперь обновим данные:

UPDATE fake_data
SET name_prefix = fakeit_name_prefix();

Проверим результат:

FROM fake_data;

id

name

email

city

country

name_prefix

1

Jasen Stamm

gildarunte@herzog.name

Kerlukeview

French Southern Territories

Dr.

2

Bonita Lubowitz

glendasmitham@bernhard.com

Mafaldaport

Zambia

Mr.

...

...

...

...

...

...

Логически UPDATE в DuckLake можно представить так:

  1. Старая версия строки помечается как удалённая.

  2. Записывается новая версия строки с изменёнными значениями.

То есть на уровне табличной модели UPDATE представляет собой комбинацию DELETE и INSERT внутри одной транзакции. Queries#update.

При этом предыдущая версия таблицы никуда сразу не исчезает: DuckLake сохраняет снапшоты, поэтому к ней можно вернуться через time travel.

Путешествие во времени (Time travel)

Time travel — одна из ключевых возможностей LakeHouse-форматов, которая есть и в DuckLake.

При каждом изменении DuckLake создаёт снапшот. Пока старые снапшоты не удалены политикой хранения, таблицу можно прочитать в её прошлом состоянии. Time Travel.

Сначала посмотрим историю изменений:

USE '__ducklake_metadata_my_ducklake';

FROM ducklake_snapshot_changes;

snapshot_id

changes_made

author

commit_message

commit_extra_info

0

created_schema:"main"

None

None

None

1

created_table:"main"."fake_data",inserted_into...

None

None

None

2

altered_table:1

None

None

None

3

inserted_into_table:1,deleted_from_table:1

None

None

None

В нашем примере:

  • snapshot_id = 0 — создана схема main;

  • snapshot_id = 1 — создана таблица fake_data;

  • snapshot_id = 2 — изменена схема таблицы;

  • snapshot_id = 3 — обновлены данные. Посмотрим снапшоты:

USE '__ducklake_metadata_my_ducklake';
FROM ducklake_snapshot;

Результат (часть полей удалено для демонстрации):

snapshot_id

snapshot_time

schema_version

next_catalog_id

next_file_id

0

2026-06-04 14:57:50.133936+03:00

0

1

0

1

2026-06-04 15:00:07.920241+03:00

1

2

1

2

2026-06-04 15:00:08.359207+03:00

2

2

1

3

2026-06-04 15:03:10.959830+03:00

2

2

2

Вернёмся к DuckLake:

USE my_ducklake;

Теперь можно обратиться к таблице в конкретной версии (snapshot_id):

SELECT *
FROM fake_data AT (VERSION => 1);

id

name

email

city

country

1

Jasen Stamm

gildarunte@herzog.name

Kerlukeview

French Southern Territories

2

Bonita Lubowitz

glendasmitham@bernhard.com

Mafaldaport

Zambia

...

...

...

...

...

Например, в первой версии таблица ещё не содержала name_prefix.

А в снапшоте 3, который в этом примере является актуальным:

SELECT *
FROM fake_data AT (VERSION => 3);

id

name

email

city

country

name_prefix

1

Jasen Stamm

gildarunte@herzog.name

Kerlukeview

French Southern Territories

Dr.

2

Bonita Lubowitz

glendasmitham@bernhard.com

Mafaldaport

Zambia

Mr.

...

...

...

...

...

...

уже будет новая колонка с заполненными значениями.

Также можно читать таблицу по временному интервалу: INTERVAL '1 week' или на конкретное время: SNAPSHOT_TIME '2025-05-26 00:00:00'. Time travel работает, пока соответствующие снапшоты и файлы не были удалены политиками retention. Подробнее в документации — Time Travel.

Очистка старых снапшотов и файлов

DuckLake специально не удаляет старые данные сразу после DELETE, UPDATE или удаления таблицы.

Это необходимо для того, чтобы:

  • работал time travel;

  • старые запросы не ломались;

  • можно было восстановиться после ошибок;

  • не удалить файл, который ещё читает активный запрос.

Но если никогда ничего не чистить, хранилище будет расти бесконечно.

Давайте посмотрим, как вручную удалить старые снапшоты. Для начала выведем их текущий список:

USE '__ducklake_metadata_my_ducklake';
FROM ducklake_snapshot;

snapshot_id

snapshot_time

schema_version

next_catalog_id

next_file_id

0

2026-06-04 14:57:50.133936+03:00

0

1

0

1

2026-06-04 15:00:07.920241+03:00

1

2

1

2

2026-06-04 15:00:08.359207+03:00

2

2

1

3

2026-06-04 15:03:10.959830+03:00

2

2

2

Теперь удалим снапшоты старше одной минуты (вы можете выбрать другой временной интервал, в зависимости от задачи. Я выбрал данный интервал для демонстрации):

CALL ducklake_expire_snapshots(
    'my_ducklake',
    older_than => now() - INTERVAL '1 minute'
);

После этого старые снапшоты перестанут быть доступны для time travel.

Однако связанные с ними .parquet-файлы физически ещё не удаляются: DuckLake добавляет их в список файлов, запланированных на удаление. Expire Snapshots.

Чтобы физически удалить файлы, которые больше не нужны, вызовем ducklake_cleanup_old_files:

CALL ducklake_cleanup_old_files(
    'my_ducklake',
    cleanup_all => true
);

После этого DuckLake удалит все файлы, которые уже были запланированы на удаление. Актуальные .parquet-файлы, на которые ссылается текущий снапшот, останутся в хранилище. Cleanup of Files.

DuckLake рекомендует не удалять файлы слишком агрессивно: активные долгие запросы могут ещё читать старую версию данных. Подробнее — в документации Cleanup of Files.

Рекомендации

Рекомендации перечислю пунктами, чтобы не увеличивать объём статьи, да и формат позволяет приложить ссылки.

  • Умная работа со снапшотами и TTL, Cleanup of Files, Expire Snapshots — множество гибких настроек, которые позволяют настроить хранение метаданных и самих данных.

  • Регистрация внешних файлов, Adding Files — вы можете посчитать данные вообще на стороне, сторонним скриптом или в другой системе и затем просто положить готовые .parquet-файлы в свой в бакет и затем зарегистрировать в DuckLake.

  • Простота "из коробки" и экосистема, Choosing a Catalog Database — старт DuckLake занимает буквально пару команд. Также DuckLake не заперт внутри экосистемы DuckDB: сообщество вокруг "утки" сейчас максимально активное, и для DuckLake уже вовсю развиваются внешние интеграции: есть Spark-пакеты (DuckLake Spark Connector) и коннекторы к сторонним системам, вроде Trino (trino-ducklake, Trino DuckLake Connector — Feature Support).

  • Переход на Parquet V2, Configuration — мало кто использует это по умолчанию, но вы можете перевести хранение на .parquet второй версии. Это позволяет использовать более современные возможности формата, но перед включением стоит проверить совместимость инструментов, которые будут читать файлы.

  • Сжатие алгоритмом ZSTD, Configuration — по умолчанию используется snappy, но я рекомендую переключиться на ZSTD. Для большинства дата-инженерных задач это оптимальный алгоритм по закону Парето — он дает наилучший баланс между скоростью распаковки и степенью сжатия файлов. Если вы хотите получше разобраться со сжатием файлов, то рекомендую своё видео — Горячее/Тёплое/Холодное хранение: сравниваем сжатие Snappy, ZSTD, GZIP и LZ4 для хранения данных

  • Многопоточная запись, Configuration — если ваша главная цель — залить данные (сделать ingest) максимально быстро, то включите опцию per_thread_output. В этом режиме каждый вычислительный поток будет писать свой собственный .parquet-файл параллельно. Запись полетит как ракета, но есть нюанс: на выходе получится много мелких файлов, поэтому следите за гигиеной DuckLake.

  • Агрессивный Retry Wait Time, Configuration — для тех, кому нужен высокий TPS (количество транзакций в секунду) при загрузке данных. Вы можете снизить время ожидания (ducklake_retry_wait_ms) при записи в каталог. По умолчанию 100 ms, но на практике снижение до 1 ms может дать колоссальное ускорение вставки больших объёмов. Но учитывайте нагрузку на ваш каталог (Choosing a Catalog Database).

Заключение

Если подводить черту, то DuckLake — это максимально логичный и эволюционный шаг для концепции LakeHouse. Он сохраняет все плюсы Iceberg:

  • табличную абстракцию;

  • версионирование;

  • эволюцию схемы (schema evolution);

  • путешествие во времени (time travel).

Но при этом избавляет архитектуру от файлового ада метаданных в S3, перенося их туда, где им самое место — в транзакционную реляционную базу данных.

Для аналитики, дата-инженерии и команд DevOps это может быть очень удобным вариантом:

  • концептуально простым;

  • инфраструктурно понятным;

  • потенциально более быстрым и прозрачным в эксплуатации, чем подход с большим числом файлов метаданных.

Пробуйте DuckLake и тестируйте на своих проектах.


Если вам необходимы консультация, менторство, мок-собеседование или помощь с другими вопросами по дата-инженерии — обращайтесь. Все контакты указаны по ссылке.

Комментарии (0)