はじめに
こんにちは!
エンジニア3年目のTKDSです!
今回はメタデータ管理をPostgresqlやMySQLなどのSQLデータベースが担う新しいOSSのLakehouseフォーマット、Ducklakeについて試してみました!
今回はPostgresql+s3(minio)+DuckDBで構築してます。
Ducklakeの概要
Ducklakeは公式HPの一文によるとSQLデータベースをカタログ置き場に、データをparquetファイルに保存する形式のLakehouseフォーマットだそうです。
DuckLake delivers advanced data lake features without traditional lakehouse complexity by using Parquet files and your SQL database. It's an open, standalone format from the DuckDB team.
(Google翻訳) DuckLakeは、ParquetファイルとSQLデータベースを使用することで、従来のレイクハウスの複雑さを排除しながら、高度なデータレイク機能を提供します。DuckDBチームが提供するオープンなスタンドアロンフォーマットです。
簡単にLakehouseを使えると解釈しておきましょう!
では早速準備して使っていきます。
準備
今回、Postgresqlとminioをcomposeで建て、それぞれカタログ置き場、データ置き場として使います。
環境構築後、DuckDB CLIからクエリを打って操作していく形です 。
1. composeでPostgresqlとminioを起動
まず.envファイルにパスワード等を記載しておいてください。 Minioのパスワードは短いと以下のエラーでコンテナ起動できないので注意。
MINIO_ROOT_USER length should be at least 3, and MINIO_ROOT_PASSWORD length at least 8 characters
MINIO_ROOT_USER=root-minio MINIO_ROOT_PASSWORD=root-minio POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres POSTGRES_DB=postgres
ディレクトリを作っておきます。
mkdir ./data/minio mkdir ./data/postgres
以下の内容でcomposeファイルを書きます。
services: minio: image: quay.io/minio/minio:RELEASE.2025-05-24T17-08-30Z container_name: minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} volumes: - ./data/minio:/data ports: - "127.0.0.1:9000:9000" - "127.0.0.1:9001:9001" healthcheck: test: ["CMD", "mc", "ready", "local"] start_period: 10s interval: 30s timeout: 20s retries: 3 postgres: image: postgres:17.5-bookworm container_name: postgres environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - ./data/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro ports: - "127.0.0.1:5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 networks: default: name: ducknet
init.sqlには以下を記載します。
CREATE DATABASE ducklake;
docker compose up
で起動します。
2. minioでバケットを作成する
今回GUIから作りました。
http://localhost:9001/にアクセスしてください。
ログインして、左側のCreate Bucketからバケットを作ります。
今回名前はducklake-pocにしました。
3. DuckDB側の準備と起動
DuckDBをインストールします。
今回はCLIのDuckDBを使います。
HPにあるコマンド
curl https://install.duckdb.org | sh
インストールできたか確認
$ duckdb -version v1.3.0 71c5c07cdd
DuckDBに必要な拡張機能をインストールします。
duckdbコマンドでCLIを起動して以下のコマンドを打ってください。
INSTALL ducklake; INSTALL httpfs; INSTALL postgres; LOAD postgres; LOAD ducklake; LOAD httpfs;
postgres、httpfsはDucklakeのドキュメントには書いてなかったですが、とりあえず関係してそうなのでインストールしておきました。
Postgresqlとオブジェクトストレージに接続し、アタッチしたカタログデータベースを使用状態にします。
まずs3接続用シークレットを作ります。
create secret (type s3, key_id 'root-minio', secret 'root-minio', endpoint 'localhost:9000', use_ssl false, url_style 'path');
では接続します。
ATTACH 'ducklake:postgres:dbname=ducklake_catalog host=localhost user=postgres password=postgres' AS postgres_ducklake (DATA_PATH 's3://ducklake-poc');
USEしたときにエラーでなければ成功です。
USE postgres_ducklake;
Attachしたタイミングでテーブルが作成されます。 publicスキーマに作られてます。
$ docker exec -it postgres psql -U postgres -d ducklake psql (17.5 (Debian 17.5-1.pgdg120+1)) Type "help" for help. ducklake=# \dt Did not find any relations. ducklake=# \dt List of relations Schema | Name | Type | Owner --------+---------------------------------------+-------+---------- public | ducklake_column | table | postgres public | ducklake_column_tag | table | postgres public | ducklake_data_file | table | postgres public | ducklake_delete_file | table | postgres public | ducklake_file_column_statistics | table | postgres public | ducklake_file_partition_value | table | postgres public | ducklake_files_scheduled_for_deletion | table | postgres public | ducklake_inlined_data_tables | table | postgres public | ducklake_metadata | table | postgres public | ducklake_partition_column | table | postgres public | ducklake_partition_info | table | postgres public | ducklake_schema | table | postgres public | ducklake_snapshot | table | postgres public | ducklake_snapshot_changes | table | postgres public | ducklake_table | table | postgres public | ducklake_table_column_stats | table | postgres public | ducklake_table_stats | table | postgres public | ducklake_tag | table | postgres public | ducklake_view | table | postgres (19 rows) ducklake=# \dt public.ducklake_column; List of relations Schema | Name | Type | Owner --------+-----------------+-------+---------- public | ducklake_column | table | postgres (1 row)
これで使う準備は完了です。
試す
Introductionのサンプルを元に試しにやってみます。
Postgresqlとminioで環境構築してるので、一部を改変して実行してます。
※色々横道それながらやってたので、一部オブジェクトファイルのファイル名違ったりしますが気にしないでください。
CREATE TABLE nl_train_stations AS FROM 'https://blobs.duckdb.org/nl_stations.csv';
これでCSVファイルからデータを読み込みテーブルが作成されます。
DBを見てみるとテーブルの情報が作成されてます。
ducklake=# select * from ducklake_table; table_id | table_uuid | begin_snapshot | end_snapshot | schema_id | table_name ----------+--------------------------------------+----------------+--------------+-----------+------------------- 1 | 01974993-3ba1-7505-b4bf-eecff46886e0 | 1 | | 0 | nl_train_stations (1 row) ducklake=# select table_id,column_name from ducklake_column; table_id | column_name ----------+------------- 1 | id 1 | code 1 | uic 1 | name_short 1 | name_medium 1 | name_long 1 | slug 1 | country 1 | type 1 | geo_lat 1 | geo_lng (11 rows)
オブジェクトストレージ側にもparquetファイルが作成されます。
では続きをやっていきます。 以下コマンドはparquetファイルの情報を取得しています。
D FROM glob('s3://ducklake-poc/*') ; ┌─────────────────────────────────────────────────────────────────────────┐ │ file │ │ varchar │ ├─────────────────────────────────────────────────────────────────────────┤ │ s3://ducklake-poc/ducklake-01974989-7208-7460-964d-c17d34b116c7.parquet │ └─────────────────────────────────────────────────────────────────────────┘ D FROM 's3://ducklake-poc/*.parquet' LIMIT 10; ┌───────┬─────────┬───┬─────────────────┬─────────────────┐ │ id │ code │ … │ geo_lat │ geo_lng │ │ int64 │ varchar │ │ double │ double │ ├───────┼─────────┼───┼─────────────────┼─────────────────┤ │ 266 │ HT │ … │ 51.69048 │ 5.29362 │ │ 269 │ HTO │ … │ 51.700553894043 │ 5.3183331489563 │ │ 227 │ HDE │ … │ 52.4091682 │ 5.893611 │ │ 8 │ AHBF │ … │ 50.7678 │ 6.091499 │ │ 818 │ AW │ … │ 50.78036 │ 6.070715 │ │ 51 │ ATN │ … │ 51.921326524551 │ 6.5786272287369 │ │ 5 │ AC │ … │ 52.2785 │ 4.977 │ │ 550 │ EAHS │ … │ 52.079796120944 │ 7.0163583755493 │ │ 12 │ AIME │ … │ 45.55438 │ 6.64869 │ │ 819 │ ACDG │ … │ 49.004048 │ 2.571133 │ ├───────┴─────────┴───┴─────────────────┴─────────────────┤ │ 10 rows 11 columns (4 shown) │ └────────────────
チュートリアルの通りに変更を加えます。
UPDATE nl_train_stations SET name_long='Johan Cruijff ArenA' WHERE code = 'ASB';
変更を加えるとファイルが増えてるのがわかります。
FROM glob('s3://ducklake-poc/*') ; ┌──────────────────────────────────────────────────────────────────────────────┐ │ file │ │ varchar │ ├──────────────────────────────────────────────────────────────────────────────┤ │ s3://ducklake-poc/ducklake-01974993-3ba1-7387-ac08-101cab81f9f7.parquet │ │ s3://ducklake-poc/ducklake-01974994-9e69-7177-a6e5-253d9cf9d4af.parquet │ │ s3://ducklake-poc/ducklake-01974994-9e83-7c25-b00b-1f41f679902e-delete.par… │ └──────────────────────────────
過去のデータの状態を記憶したsnapshot一覧も見ることができるようです。
FROM postgres_ducklake.snapshots(); ┌─────────────┬────────────────────────────┬────────────────┬─────────────────────────────────────────────────────────────────────┐ │ snapshot_id │ snapshot_time │ schema_version │ changes │ │ int64 │ timestamp with time zone │ int64 │ map(varchar, varchar[]) │ ├─────────────┼────────────────────────────┼────────────────┼─────────────────────────────────────────────────────────────────────┤ │ 0 │ 2025-06-07 17:43:15.138+09 │ 0 │ {schemas_created=[main]} │ │ 1 │ 2025-06-07 17:47:54.805+09 │ 1 │ {tables_created=[main.nl_train_stations], tables_inserted_into=[1]} │ │ 2 │ 2025-06-07 17:49:26.108+09 │ 1 │ {tables_inserted_into=[1], tables_deleted_from=[1]}
バージョンごとに内容を確認可能です。
SELECT name_long FROM nl_train_stations AT (VERSION => 1) WHERE code = 'ASB'; nl_train_stations AT (VERSION => 2) WHERE code = '┌─────────────────────────┐ │ name_long │ │ varchar │ ├─────────────────────────┤ │ Amsterdam Bijlmer ArenA │ └─────────────────────────┘ SELECT name_long FROM nl_train_stations AT (VERSION => 2) WHERE code = 'ASB'; ┌─────────────────────┐ │ name_long │ │ varchar │ ├─────────────────────┤ │ Johan Cruijff ArenA │ └──────────────
カタログからデタッチするには以下のコマンドを使います。
USE memory; DETACH postgres_ducklake;
ここまでで色々試すことができました!
まとめ
今回はDucklakeをPostgresql + s3(Minio) + DuckDBで構築しました!
インターネット上に情報が少なくかなり苦労しましたが、動かせてよかったです!
データ本体はオブジェクトストレージに置く形式なので、低コストで大量のデータをおけるのではないでしょうか?
これからもちょくちょく触ってみたいと思いました。
ここまで読んでいただきありがとうございました!