DuckDB で Apache Iceberg テーブルに書き込む - ノートPCで動くローカル・レイクハウス

2026年のデータエンジニアリングでいちばん語られているのが「レイクハウスの truth は Iceberg テーブルに置き、compute は差し替え可能にする」というパターンだ。そして compute 側の主役として、クラウドの ClickHouse と並んでノートPCで完結する DuckDB が急速に採用を伸ばしている。

これまで DuckDB の Iceberg 対応は「読むだけ」だった。潮目が変わったのが DuckDB 1.4.0 で、Iceberg REST カタログ経由の書き込みCREATE TABLE / INSERT)に対応し、続く 1.4.2 で UPDATE / DELETE まで入った。つまり別途 Spark や Trino を立てなくても、DuckDB 単体で Iceberg テーブルのライフサイクルを回せるようになった。本稿では 2026年6月時点の最新安定版 DuckDB 1.5.4(LTS は 1.4.5)を使い、ローカルにミニ・レイクハウスを組んで実際に書き込む。

仕組みの要点

Iceberg テーブルへの書き込みは、必ずカタログ経由で行う。DuckDB は Iceberg REST Catalog に ATTACH し、すべての書き込みはカタログにコミットされて新しいスナップショットになる。catalog なしの iceberg_scan() は読み取り専用なので、書きたいなら REST カタログが要る。

ローカルで再現するには次の 3 つを Docker で立てる。

  • iceberg-rest-fixture: Apache 公式の軽量 REST カタログ実装(ポート 8181)
  • MinIO: S3 互換オブジェクトストレージ(データ本体の置き場)
  • DuckDB CLI: クエリエンジン(ホスト側で動かす)

DuckDB は「カタログにメタデータを問い合わせ、データファイルは S3(MinIO) に直接読み書きする」という二段構えで動くので、DuckDB 側には REST エンドポイントと MinIO の S3 認証情報の両方を渡す。

実際に試す

前提

  • Docker / Docker Compose が動く環境(macOS / Linux)
  • DuckDB CLI 1.4.0 以上(本稿は 1.5.4 で確認)
  • ポート 8181 / 9000 / 9001 が空いていること

DuckDB CLI が未導入なら、公式のインストールスクリプトが手軽。

1
2
3
curl https://install.duckdb.org | sh
duckdb --version
# 例: v1.5.4 (Variegata) ...

1. ローカルのカタログと MinIO を Docker で起動

作業用ディレクトリに次の docker-compose.yml を置く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
services:
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: password
command: server /data --console-address ":9001"

mc:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
until (/usr/bin/mc alias set local http://minio:9000 admin password) do sleep 1; done;
/usr/bin/mc mb -p local/warehouse;
exit 0;
"

iceberg-rest:
image: apache/iceberg-rest-fixture
depends_on:
- minio
ports:
- "8181:8181"
environment:
AWS_ACCESS_KEY_ID: admin
AWS_SECRET_ACCESS_KEY: password
AWS_REGION: us-east-1
CATALOG_WAREHOUSE: s3://warehouse/
CATALOG_IO__IMPL: org.apache.iceberg.aws.s3.S3FileIO
CATALOG_S3_ENDPOINT: http://minio:9000
CATALOG_S3_PATH__STYLE__ACCESS: "true"

起動する。

1
docker compose up -d

mc サービスが MinIO 上に warehouse バケットを作り、iceberg-resthttp://localhost:8181 で REST カタログを提供する。

2. DuckDB からカタログに ATTACH する

DuckDB CLI を起動して、拡張のロード・MinIO 用 S3 シークレット作成・カタログへの ATTACH を順に流す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
INSTALL iceberg;
LOAD iceberg;
INSTALL httpfs;
LOAD httpfs;

-- MinIO を S3 として使うためのシークレット
CREATE SECRET minio_secret (
TYPE s3,
KEY_ID 'admin',
SECRET 'password',
ENDPOINT 'localhost:9000',
URL_STYLE 'path',
USE_SSL false,
REGION 'us-east-1'
);

-- ローカルの REST カタログに接続(認証なし)
ATTACH 'warehouse' AS lake (
TYPE iceberg,
ENDPOINT 'http://localhost:8181',
AUTHORIZATION_TYPE 'none'
);

AUTHORIZATION_TYPE 'none' がローカル開発用のポイント。本番の REST カタログはたいてい OAuth2 なので、そこは CREATE SECRET ... (TYPE iceberg, CLIENT_ID ..., CLIENT_SECRET ...) に置き換える。

3. スキーマとテーブルを作って書き込む

ここからは普通の SQL。カタログ名 lake を頭に付けるだけで、実体は Iceberg テーブルとして MinIO に書かれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE SCHEMA lake.sales;

CREATE TABLE lake.sales.events (
id INTEGER,
event_name VARCHAR,
event_time TIMESTAMP
);

INSERT INTO lake.sales.events VALUES
(1, 'click', TIMESTAMP '2026-07-01 10:00:00'),
(2, 'view', TIMESTAMP '2026-07-01 10:05:00'),
(3, 'click', TIMESTAMP '2026-07-02 09:30:00');

SELECT event_name, count(*) AS n
FROM lake.sales.events
GROUP BY event_name
ORDER BY n DESC;

期待される出力:

1
2
3
4
5
6
7
┌────────────┬───────┐
│ event_name │ n │
varchar │ int64 │
├────────────┼───────┤
│ click │ 2
view1
└────────────┴───────┘

4. UPDATE / DELETE も効く(1.4.2 以降)

行更新・削除もカタログ経由で新しいスナップショットとしてコミットされる。

1
2
3
4
UPDATE lake.sales.events SET event_name = 'view' WHERE id = 1;
DELETE FROM lake.sales.events WHERE event_time < TIMESTAMP '2026-07-02 00:00:00';

SELECT * FROM lake.sales.events ORDER BY id;

期待される出力(id=1 は前段の削除条件に該当するため消え、id=3 だけが残る):

1
2
3
4
5
6
┌───────┬────────────┬─────────────────────┐
│ id │ event_name │ event_time │
│ int32 │ varchartimestamp
├───────┼────────────┼─────────────────────┤
3 │ click │ 2026-07-02 09:30:00
└───────┴────────────┴─────────────────────┘

5. スナップショット履歴を確認する

Iceberg の強みはスナップショット単位の履歴管理。ここまでで INSERT / UPDATE / DELETE の 3 回コミットしたので、3 つのスナップショットが並ぶはずだ。

1
2
3
SELECT snapshot_id, sequence_number, timestamp_ms
FROM iceberg_snapshots('lake.sales.events')
ORDER BY sequence_number;

各コミットが 1 行ずつ、sequence_number が 1, 2, 3 と増えて記録される。これがそのまま監査ログとタイムトラベルの土台になる。

後片付け

1
docker compose down -v

まとめ

DuckDB 1.4 系以降は、Spark を立てずにノートPC一台で Iceberg テーブルを読み書きできる。ローカルで REST カタログ + MinIO を Docker で用意すれば、本番のクラウド・レイクハウス(S3 + Glue/Lakekeeper 等の REST カタログ)とほぼ同じ SQL がそのまま通る。開発・検証を手元で回し、ATTACH 先だけ差し替えて本番へ持っていく、という流れが現実的になった。


参考:


DuckDB で Apache Iceberg テーブルに書き込む - ノートPCで動くローカル・レイクハウス
https://blog.hashito.biz/2026/07/10/try-duckdb-iceberg-write-local-lakehouse/
著者
hashito
作成日
2026年7月10日
著作権