![[Welcome to AWS 企画] DuckDB v2.0 alpha のクライアント/サーバー構成(quack + CONNECT)で Amazon S3 Tables を試してみた](https://images.ctfassets.net/ct0aopd36mqt/5gViLtMI9HM9PfaRlj2Wz2/f45862ae56c4ac624462c5252b15760c/eyecatch_duckdb_1200x630.png?w=3840&fm=webp)
[Welcome to AWS 企画] DuckDB v2.0 alpha のクライアント/サーバー構成(quack + CONNECT)で Amazon S3 Tables を試してみた
クラウド事業統括本部の石川です。DuckDB v2.0 のプレビューが公開され、ほぼ同じ時期に開発元の DuckLabs が AWS に加わることが発表されました。本日は、v2.0-alpha を導入して、新しい CONNECT 文と Amazon S3 Tables の組み合わせを試してみました。
DuckLabs to Join AWS
2026年8月は、DuckDB に関する発表が2件続きました。8月17日に DuckDB v2.0 のプレビューが公開され、8月26日に DuckDB を開発する DuckLabs が AWS に加わることが発表されています。
DuckLabs の発表の大半は、プロジェクトがオープンソースのまま続くという説明です。v2.0 のプレビューのほうは、DuckDB が1プロセスの中だけで完結しない方向に機能が広がったという内容です。その代表が、DuckDB 同士をネットワーク越しにつなぐ Quack と、新しい CONNECT 文です。
前半で発表内容を整理し、後半で v2.0-alpha を実際に動かした結果をまとめます。
DuckLabs の AWS 参画で変わらないこと、広がること
発表では、アムステルダムに拠点を置く DuckLabs が AWS に加わる(join)と説明されています。買収(acquisition)という語は使われていません。発効日は2026年9月1日で、手続きは8月31日に完了したと追記されています。
変わらないもの
| 項目 | 発表内容 |
|---|---|
| ライセンス | DuckDB、DuckLake、Quack とすべての拡張は MIT ライセンスの自由なオープンソースソフトウェアのまま |
| ガバナンス | 非営利の DuckDB Foundation が引き続きプロジェクトを管理。ロードマップ・ライセンス・ガバナンスモデルに変更はない |
| 知的財産 | オープンソース版 DuckDB の知的財産は Foundation が保有しており、今後も保有し続ける |
| 開発体制 | DuckLabs のチームはアムステルダムに留まり、DuckDB の開発を続ける |
広がるもの
| 項目 | 発表内容 |
|---|---|
| 諮問委員会 | DuckDB Foundation が諮問委員会を設け、コミュニティの主要メンバーがプロジェクトの技術的な方向性に意見を出せるようにする |
| 拡張機能 | 拡張スタックを開き、他の開発者や組織が署名した拡張を DuckDB 上で動かせるようにする |
| サポート | コミュニティサポートの制限を解除する |
AWS 参画によって、AWS 上のデータ基盤と DuckDB の接続や運用が進む方向性として読むことはできます。本記事も、あくまで現時点で公開されている alpha ビルドで何ができるかを確認するものです。
DuckDB v2.0 (alpha) とは
DuckDB v2.0のコードネームは Cyanoptera(シナモンコガモに由来)です。v1.5(2026年3月)以降1万を超えるコミットが入っており、正式リリースは2026年秋、10月後半が予定されています。
プレビュー発表で挙げられている主な変更点は次のとおりです。
- Quack 拡張と CONNECT 文によるクライアント/サーバー構成
- VARIANT 型の追加(スキーマ宣言なしで半構造化データを格納する)
- トリガー(BEFORE / AFTER、FOR EACH ROW / FOR EACH STATEMENT)
- APPROX NEAREST による top-k 類似検索
- CTE 内での DML、ネストしたスキーマ、変数構文の簡素化
- 非同期 I/O の導入
- ストレージフォーマット v2.0
- PEG ベースの新しい SQL パーサー
- ICU ライブラリ依存の排除
- C API の刷新と、独自の拡張リポジトリをホストする仕組み
2026年9月2日には v2.0-alpha が公開され、フィーチャーフリーズを終えて v2.0-cyanoptera ブランチに移っています。alpha の時点で quack、httpfs、ducklake、iceberg、spatial の各コア拡張が利用できます。
本記事で扱うのは、この一覧のうち Quack 拡張と CONNECT 文によるクライアント/サーバー構成です。
Quack とは
Quack は HTTP を土台にした DuckDB のクライアント/サーバープロトコルです。DuckDB インスタンス同士が通信でき、複数の接続からの同時書き込みを扱えます。2026年5月に実験的機能として公開され、v2.0 で安定版になる予定とされています。
既定のリッスンポートは 9494 で、起動時に認証トークンがランダム生成されます。localhost での利用が前提で、インターネットへの直接公開は推奨されていません。ローカル以外への接続では SSL を明示的に有効にする必要があります。
やってみた
前提条件
- OS: macOS 26.6.2 (arm64)
- DuckDB: v2.0.0-alpha42839 (osx_arm64)
- AWS CLI: aws-cli/2.36.45
- AWS リージョン: ap-northeast-1
- AWS アカウントの権限: s3tables の作成・削除が可能な IAM ロール
DuckDB v2.0 は本記事執筆時点で alpha 版です。構文や挙動は正式リリースまでに変わる可能性があります。
DuckDB v2.0-alpha のインストール
公式のインストールスクリプトに DUCKDB_VERSION=alpha を渡すと、alpha 版の CLI が入ります。
% curl -sSL https://install.duckdb.org | DUCKDB_VERSION=alpha bash
*** DuckDB Linux/MacOS installation script, version v2.0.0-alpha42839 ***
.;odxdl,
.xXXXXXXXXKc
0XXXXXXXXXXXd cooo:
,XXXXXXXXXXXXK OXXXXd
0XXXXXXXXXXXo cooo:
.xXXXXXXXXKc
.;odxdl,
Destination binary /Users/ishikawa.satoru/.duckdb/cli/v2.0.0-alpha42839/duckdb already exists and seems to work
Updated symlink from /Users/ishikawa.satoru/.duckdb/cli/latest/duckdb to
/Users/ishikawa.satoru/.duckdb/cli/v2.0.0-alpha42839/duckdb
Hint: Append the following line to your shell profile:
export PATH="/Users/ishikawa.satoru/.duckdb/cli/latest":$PATH
To launch DuckDB v2.0.0-alpha42839 now, type
/Users/ishikawa.satoru/.duckdb/cli/latest/duckdb
既存の安定版と別のディレクトリに入るため、安定版を使っている環境でも切り替えて検証できます。バージョンとプラットフォームを確認します。
% ~/.duckdb/cli/latest/duckdb -c "SELECT version() AS version;"
┌───────────────────┐
│ version │
│ varchar │
├───────────────────┤
│ v2.0.0-alpha42839 │
└───────────────────┘
% ~/.duckdb/cli/latest/duckdb -c "PRAGMA platform;"
┌───────────┐
│ platform │
│ varchar │
├───────────┤
│ osx_arm64 │
└───────────┘
quack 拡張のインストール
alpha 版の初期状態では、quack を含む拡張はインストールされていません。
% ~/.duckdb/cli/latest/duckdb -c "SELECT extension_name, installed, loaded, install_mode FROM duckdb_extensions() WHERE extension_name IN ('quack','iceberg','httpfs','aws','ducklake') ORDER BY extension_name;"
┌────────────────┬───────────┬─────────┬───────────────┐
│ extension_name │ installed │ loaded │ install_mode │
│ varchar │ boolean │ boolean │ varchar │
├────────────────┼───────────┼─────────┼───────────────┤
│ aws │ false │ false │ NOT_INSTALLED │
│ ducklake │ false │ false │ NOT_INSTALLED │
│ httpfs │ false │ false │ NOT_INSTALLED │
│ iceberg │ false │ false │ NOT_INSTALLED │
│ quack │ false │ false │ NOT_INSTALLED │
└────────────────┴───────────┴─────────┴───────────────┘
quack をインストールします。alpha 版向けのビルドがリポジトリから取得されます。
% ~/.duckdb/cli/latest/duckdb -c "INSTALL quack; LOAD quack; SELECT extension_name, extension_version, install_mode FROM duckdb_extensions() WHERE extension_name='quack';"
┌────────────────┬───────────────────┬──────────────┐
│ extension_name │ extension_version │ install_mode │
│ varchar │ varchar │ varchar │
├────────────────┼───────────────────┼──────────────┤
│ quack │ 984d45d27e │ REPOSITORY │
└────────────────┴───────────────────┴──────────────┘
quack が提供する関数を確認します。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; SELECT function_name, function_type, parameters FROM duckdb_functions() WHERE function_name ILIKE '%quack%' ORDER BY function_name;"
┌─────────────────────────────┬───────────────┬────────────────────────────────┐
│ function_name │ function_type │ parameters │
│ varchar │ varchar │ varchar[] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_active_connections │ table │ [] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_cancel │ table │ [col0, col1] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_check_token │ scalar │ [col0, col1, col2] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_clear_cache │ table │ [] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_connection_id │ scalar │ [col0] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_identify │ table │ [meta, provider, region, │
│ │ │ hostname, name] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_nop_authorization │ scalar │ [col0, col1] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_query │ table │ [col0, col1, │
│ │ │ heartbeat_timeout, client_id, │
│ │ │ token, disable_ssl] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_query_by_name │ table │ [col0, col1, refresh_catalog, │
│ │ │ use_transaction] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_serve │ table │ [col0, create_secret_if_not_ex │
│ │ │ ists, token, allow_other_hostn │
│ │ │ ame, secret, disable_ssl] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_serve │ table │ [create_secret_if_not_exists, │
│ │ │ token, allow_other_hostname, │
│ │ │ secret, disable_ssl] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_server_list │ table │ [] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_stop │ table │ [col0] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ quack_uri_parser │ scalar │ [col0, col1] │
├─────────────────────────────┼───────────────┼────────────────────────────────┤
│ scan_data_from_quack_client │ table │ [col0, col1, ordered] │
└─────────────────────────────┴───────────────┴────────────────────────────────┘
15 rows
サーバの起動と停止、接続一覧、キャンセル、認証・認可のコールバックまで一式が揃っています。設定項目も確認します。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; SELECT name, value FROM duckdb_settings() WHERE name ILIKE '%quack%' AND name NOT LIKE '%debug%';"
┌───────────────────────────────────┬──────────────────────────────────────┐
│ name │ value │
│ varchar │ varchar │
├───────────────────────────────────┼──────────────────────────────────────┤
│ quack_authentication_function │ quack_check_token │
│ quack_authorization_function │ quack_nop_authorization │
│ quack_cache_max_rows │ 100000 │
│ quack_default_client_id │ 4b0342d6-25fd-4ca9-bd47-e2315a031091 │
│ quack_default_heartbeat_timeout │ 60 │
│ quack_enable_reconnects │ false │
│ quack_fetch_producer_buffer_bytes │ 268435456 │
│ quack_fetch_read_ahead │ 0 │
│ quack_loaded_at_us │ 1790062228815267 │
│ quack_prepare_inline_rows │ 24576 │
│ quack_rebalance_buffer_bytes │ 0 │
│ quack_result_ttl │ 3600 │
│ quack_server_keep_alive_timeout │ 300 │
│ quack_server_max_connections │ 1024 │
│ quack_target_batch_bytes │ 33554432 │
└───────────────────────────────────┴──────────────────────────────────────┘
15 rows
認証関数と認可関数が設定で差し替えられるようになっています。既定の認可関数は quack_nop_authorization で、認可の判断を行いません。最大同時接続数は1024、アイドル接続の保持は300秒です。
DuckDB をサーバとして起動する
サーバ用のデータベースファイルと初期化スクリプトを置く作業ディレクトリを作成します。
mkdir -p work
URI にはquack:// を付けて実行します。指定したパスのデータベースファイルが無い場合は新規に作成されます。
% ~/.duckdb/cli/latest/duckdb work/server.duckdb -c "LOAD quack; CALL quack_serve('quack://127.0.0.1:4213', token => 'duckdb-aws-2026'); SELECT 'returned' AS state;"
┌────────────────────────┬───────────────────────┬─────────────────┐
│ listen_uri │ listen_url │ auth_token │
│ varchar │ varchar │ varchar │
├────────────────────────┼───────────────────────┼─────────────────┤
│ quack://127.0.0.1:4213 │ http://127.0.0.1:4213 │ duckdb-aws-2026 │
└────────────────────────┴───────────────────────┴─────────────────┘
┌──────────┐
│ state │
│ varchar │
├──────────┤
│ returned │
└──────────┘
quack_serve はListen URI、HTTP の URL、認証トークンを返しますが、quack_serve が呼び出し側をブロックしません。後続の SELECT 'returned' が実行されている通り、サーバはバックグラウンドで起動し、制御は即座に戻ります。つまり -c で実行すると、そのまま CLI が終了してサーバも消えます。
**サーバを常駐させるには、DuckDB のプロセス自体を生かし続ける必要があります。**サーバ側の初期化スクリプトを用意します。
cat > work/server_init.sql <<'SQL'
LOAD quack;
CALL quack_identify(name => 'tokyo-server', provider => 'local', region => 'ap-northeast-1', hostname => 'macbook-local');
DROP TABLE sales;
CREATE TABLE IF NOT EXISTS sales (id INTEGER, region VARCHAR, amount DECIMAL(10,2));
INSERT INTO sales VALUES (1, 'ap-northeast-1', 1200.50), (2, 'us-east-1', 890.00), (3, 'ap-northeast-1', 430.25);
CALL quack_serve('quack://127.0.0.1:4213', token => 'duckdb-aws-2026');
SQL
quack_identify はこのノードの識別情報を設定する関数です。後でクエリの実行場所を判別するために使います。サーバを起動します。-init で初期化スクリプトを読ませ、標準入力を開いたままにする方法を採りました。
tail -f /dev/null | ~/.duckdb/cli/latest/duckdb -init work/server_init.sql work/server.duckdb > work/server.log 2>&1 &
[1] 26311 26312
% cat work/server.log
-- Loading resources from work/server_init.sql
┌─────────┐
│ ok │
│ boolean │
└─────────┘
0 rows
┌────────────────────────┬───────────────────────┬─────────────────┐
│ listen_uri │ listen_url │ auth_token │
│ varchar │ varchar │ varchar │
├────────────────────────┼───────────────────────┼─────────────────┤
│ quack://127.0.0.1:4213 │ http://127.0.0.1:4213 │ duckdb-aws-2026 │
└────────────────────────┴───────────────────────┴─────────────────┘
ポートがリッスン状態になっていることを確認します。
% lsof -nP -iTCP:4213 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
duckdb 57446 ishikawa.satoru 6u IPv4 0xdbb5282370e61a73 0t0 TCP 127.0.0.1:4213 (LISTEN)
DuckDB のプロセスが TCP の 4213 番で待ち受けています。
別プロセスのクライアントから ATTACH する
別のプロセスとしてクライアントを起動し、サーバに接続します。最初は公式ブログの例に倣いつつ quack:// 形式で書いてみます。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack://127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); SHOW DATABASES;"
Binder Error:
Unrecognized option for attach "token"
TOKEN が認識されません。ATTACH の型が quack として解決されていないことが原因でした。URI の書き方を変えて3パターン試します。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack://127.0.0.1:4213' AS qk (TYPE quack, TOKEN 'duckdb-aws-2026'); SHOW DATABASES;"
┌───────────────┐
│ database_name │
│ varchar │
├───────────────┤
│ memory │
│ qk │
└───────────────┘
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); SHOW DATABASES;"
┌───────────────┐
│ database_name │
│ varchar │
├───────────────┤
│ memory │
│ qk │
└───────────────┘
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack://127.0.0.1:4213' AS qk; SHOW DATABASES;"
IO Error:
Cannot open file "quack://127.0.0.1:4213/": No such file or directory
結果をまとめると次の通りです。
quack:host:portというコロン1つの形式は、そのまま quack として解決されます。公式ブログのATTACH 'quack:server.example.com'はこの形式ですquack://host:portというスラッシュ2つの形式は、TYPE quackを明示しないとファイルパスとして扱われますquack_serve側は逆にquack://形式を受け付けます
quack_serve と ATTACH で URI の扱いが揃っていない点は、alpha 版で迷いやすいところでした。以降はコロン1つの形式を使います。サーバ側にしか存在しないテーブルを読んでみます。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
SELECT * FROM qk.sales ORDER BY id;
"
┌───────┬────────────────┬───────────────┐
│ id │ region │ amount │
│ int32 │ varchar │ decimal(10,2) │
├───────┼────────────────┼───────────────┤
│ 1 │ ap-northeast-1 │ 1200.50 │
│ 2 │ us-east-1 │ 890.00 │
│ 3 │ ap-northeast-1 │ 430.25 │
└───────┴────────────────┴───────────────┘
クライアント側はインメモリのデータベースで起動しており、sales テーブルはサーバ側のファイルにしかありません。ネットワーク越しに読めています。
なお、DISABLE_SSL オプションも用意されていますが、localhost 宛の URI は既定で平文 HTTP として扱われるため、今回の検証では指定しなくても接続できました。
CONNECT でセッションの実行先を切り替える
ATTACH しただけの状態と、CONNECT した状態で何が変わるのかを確認します。判別には whoami() を使います。これは quack が提供するテーブルマクロで、現在のノードの識別情報と稼働情報を返します。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
CALL quack_identify(name => 'local-client');
SELECT * FROM whoami();
"
┌─────────┐
│ ok │
│ boolean │
└─────────┘
0 rows
┌──────────────┬──────────┬──────────┬─────────┬────────────────┬───────────────────────────────┬───────────────────────────────────────────────────────────────┐
│ name │ provider │ hostname │ region │ uptime │ ts_now │ meta │
│ varchar │ varchar │ varchar │ varchar │ interval │ timestamp with time zone │ json │
├──────────────┼──────────┼──────────┼─────────┼────────────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────┤
│ local-client │ NULL │ NULL │ NULL │ 00:00:00.00456 │ 2026-09-22 19:38:34.107761+09 │ {"duckdb_version":"v2.0.0-alpha42839","platform":"osx_arm64"} │
└──────────────┴──────────┴──────────┴─────────┴────────────────┴───────────────────────────────┴───────────────────────────────────────────────────────────────┘
クライアント自身の情報が返ります。ここで CONNECT を挟んで、同じクエリを3回実行します。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
CALL quack_identify(name => 'local-client');
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
SELECT 'before CONNECT' AS phase, name, region, uptime FROM whoami();
CONNECT qk;
SELECT 'after CONNECT' AS phase, name, region, uptime FROM whoami();
DISCONNECT;
SELECT 'after DISCONNECT' AS phase, name, region, uptime FROM whoami();
"
┌─────────┐
│ ok │
│ boolean │
└─────────┘
0 rows
┌────────────────┬──────────────┬─────────┬─────────────────┐
│ phase │ name │ region │ uptime │
│ varchar │ varchar │ varchar │ interval │
├────────────────┼──────────────┼─────────┼─────────────────┤
│ before CONNECT │ local-client │ NULL │ 00:00:00.027709 │
└────────────────┴──────────────┴─────────┴─────────────────┘
┌───────────────┬──────────────┬────────────────┬───────────────┐
│ phase │ name │ region │ uptime │
│ varchar │ varchar │ varchar │ interval │
├───────────────┼──────────────┼────────────────┼───────────────┤
│ after CONNECT │ tokyo-server │ ap-northeast-1 │ 00:15:28.0104 │
└───────────────┴──────────────┴────────────────┴───────────────┘
┌──────────────────┬──────────────┬─────────┬────────────────┐
│ phase │ name │ region │ uptime │
│ varchar │ varchar │ varchar │ interval │
├──────────────────┼──────────────┼─────────┼────────────────┤
│ after DISCONNECT │ local-client │ NULL │ 00:00:00.03305 │
└──────────────────┴──────────────┴─────────┴────────────────┘
CONNECT 後は tokyo-server という別ノードの情報が返り、uptime も起動から15秒経過したサーバプロセスの値になっています。DISCONNECT するとクライアント自身に戻ります。クエリがサーバ側で実行されていることが確認できました。
CONNECT が名前解決の範囲に与える影響も確認します。クライアント側だけに存在するテーブルを作ってから CONNECT します。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
CREATE TABLE local_only (memo VARCHAR);
INSERT INTO local_only VALUES ('this table exists only on the client');
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
SELECT 'unqualified sales on server' AS probe, count(*) AS rows FROM sales;
SELECT current_database() AS current_database, current_schema() AS current_schema;
SELECT * FROM local_only;
"
┌─────────────────────────────┬───────┐
│ probe │ rows │
│ varchar │ int64 │
├─────────────────────────────┼───────┤
│ unqualified sales on server │ 3 │
└─────────────────────────────┴───────┘
┌──────────────────┬────────────────┐
│ current_database │ current_schema │
│ varchar │ varchar │
├──────────────────┼────────────────┤
│ server │ main │
└──────────────────┴────────────────┘
Invalid Input Error:
Table with name local_only does not exist!
Did you mean "pg_collation"?
LINE 1: SELECT * FROM local_only;
^^^^^^^^^^
CONNECT 後は、カタログ名を付けない sales がサーバ側のテーブルとして解決され、current_database() もサーバ側のデータベース名 server を返します。逆に、クライアント側にしかない local_only は見えなくなります。
CONNECT は単にリモートのカタログを参照できるようにするものではなく、セッション全体の実行先をサーバへ切り替えるものでした。qk.sales のようにカタログ名で修飾してリモートのテーブルを読むのとは、挙動が明確に異なります。
エッジケースも確認しておきます。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; DISCONNECT;"
Invalid Input Error:
DISCONNECT: no active CONNECT (already on LOCAL)
CONNECT していないから、DISCONNECTでエラーになる。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); CONNECT qk; CONNECT qk;"
Invalid Input Error:
Already connected to "qk"; DISCONNECT first before issuing another CONNECT
接続先はセッションごとに1つで、入れ子にはできません。
サーバ側の接続を観測する
DuckDB v2.0 では、長時間稼働させることを前提にメトリクスやログの整備が進められています。quack にも接続状況を見るための関数が用意されています。CONNECT した状態で呼ぶと、サーバ側で実行されます。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
SELECT * FROM quack_active_connections();
"
┌──────────────────────┬──────────────────────────────────┬───────────────────────────────────────────────────────────────┬──────────┬────────────────────────────────────────────────────────────────┬─────────────┬────────────────────────────┐
│ server_id │ connection_id │ query │ state │ client_id_hash │ cached_rows │ query_started_at │
│ varchar │ varchar │ varchar │ varchar │ varchar │ int64 │ timestamp │
├──────────────────────┼──────────────────────────────────┼───────────────────────────────────────────────────────────────┼──────────┼────────────────────────────────────────────────────────────────┼─────────────┼────────────────────────────┤
│ quack:127.0.0.1:4213 │ D5490975257A8425B98BDF5E25C0E99F │ SELECT * FROM quack_active_connections();\n │ active │ FD07289DCDFF86BE318336F5B27B7DADA330ED90C0194E689CCB3083E4BC1A │ NULL │ 2026-09-22 10:52:54.350403 │
│ │ │ │ │ DA │ │ │
├──────────────────────┼──────────────────────────────────┼───────────────────────────────────────────────────────────────┼──────────┼────────────────────────────────────────────────────────────────┼─────────────┼────────────────────────────┤
│ quack:127.0.0.1:4213 │ 9C4310C7D411F402BA30463101EAB02B │ \nSELECT schema_oid, sql, 'table'\nFROM duckdb_tables()\nUNIO │ finished │ B4E7D4218A6EC8D1EE6D3BB0CB9D45D977BB10832785DACDE01DD6EBAC0B1C │ NULL │ 2026-09-22 10:52:17.607531 │
│ │ │ N ALL\nSELECT schema_oid, view_name, 'view'\nFROM duckdb_view │ │ AE │ │ │
│ │ │ s()\n\t │ │ │ │ │
├──────────────────────┼──────────────────────────────────┼───────────────────────────────────────────────────────────────┼──────────┼────────────────────────────────────────────────────────────────┼─────────────┼────────────────────────────┤
│ quack:127.0.0.1:4213 │ 0DAD74C89555469CDDFFE763A0BCC742 │ ROLLBACK │ finished │ 009015DE55F4D8548AE9F341D5D86E325ACB9B251BB61B1F67AC0F4EF05A50 │ NULL │ 2026-09-22 10:44:41.621997 │
│ │ │ │ │ 22 │ │ │
├──────────────────────┼──────────────────────────────────┼───────────────────────────────────────────────────────────────┼──────────┼────────────────────────────────────────────────────────────────┼─────────────┼────────────────────────────┤
│ quack:127.0.0.1:4213 │ 582E261FC9E612BFD34FDC052C36163C │ ROLLBACK │ finished │ 84BB1B7EC86102277349E29CADF0C05243DB6BE2E1AF263E95A1CEB10F12C6 │ NULL │ 2026-09-22 08:39:17.255423 │
│ │ │ │ │ C7 │ │ │
└──────────────────────┴──────────────────────────────────┴───────────────────────────────────────────────────────────────┴──────────┴────────────────────────────────────────────────────────────────┴─────────────┴────────────────────────────┘
実行中のクエリ文、状態、クライアント ID のハッシュ、クエリ開始時刻が取得できます。前のセッションの ROLLBACK が finished として残っている点からも、サーバ側でセッションが管理されていることがわかります。
書き込みと永続化
CONNECT 経由で書き込みができるかを確認します。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
INSERT INTO sales VALUES (4, 'eu-west-1', 250.00);
SELECT region, count(*) AS cnt, sum(amount) AS total FROM sales GROUP BY region ORDER BY region;
"
┌───────┐
│ Count │
│ int64 │
├───────┤
│ 1 │
└───────┘
┌────────────────┬───────┬───────────────┐
│ region │ cnt │ total │
│ varchar │ int64 │ decimal(38,2) │
├────────────────┼───────┼───────────────┤
│ ap-northeast-1 │ 2 │ 1630.75 │
│ eu-west-1 │ 1 │ 250.00 │
│ us-east-1 │ 1 │ 890.00 │
└────────────────┴───────┴───────────────┘
別のクライアントを起動して、同じテーブルを読み直します。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); SELECT * FROM qk.sales ORDER BY id;"
┌───────┬────────────────┬───────────────┐
│ id │ region │ amount │
│ int32 │ varchar │ decimal(10,2) │
├───────┼────────────────┼───────────────┤
│ 1 │ ap-northeast-1 │ 1200.50 │
│ 2 │ us-east-1 │ 890.00 │
│ 3 │ ap-northeast-1 │ 430.25 │
│ 4 │ eu-west-1 │ 250.00 │
└───────┴────────────────┴───────────────┘
書き込みはサーバ側のデータベースファイルに反映されています。
認証とバインドアドレス
誤ったトークンで接続を試みます。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'wrong-token'); SELECT * FROM qk.sales;"
Invalid Input Error:
Authentication failed
次に、localhost 以外のアドレスにバインドしようとした場合の挙動です。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; CALL quack_serve('quack://0.0.0.0:4299', token => 'duckdb-aws-2026');"
Invalid Input Error:
Only localhost is allowed as a Quack RPC hostname by default, set allow_other_hostname=true to override. We strongly recommend reverse-proxying the Quack RPC when making it publicly available.
既定では localhost 以外にバインドできず、公開する場合はリバースプロキシを挟むことが推奨されています。明示的に許可すると 0.0.0.0 でも起動します。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; CALL quack_serve('quack://0.0.0.0:4299', token => 'duckdb-aws-2026', allow_other_hostname => true); SELECT * FROM quack_server_list();"
┌──────────────────────┬─────────────────────┬─────────────────┐
│ listen_uri │ listen_url │ auth_token │
│ varchar │ varchar │ varchar │
├──────────────────────┼─────────────────────┼─────────────────┤
│ quack://0.0.0.0:4299 │ http://0.0.0.0:4299 │ duckdb-aws-2026 │
└──────────────────────┴─────────────────────┴─────────────────┘
┌──────────────────────┬─────────────────────┬─────────┬────────┬────────────────────┬───────────────────────┐
│ listen_uri │ listen_url │ host │ port │ active_connections │ info │
│ varchar │ varchar │ varchar │ uint16 │ uint64 │ map(varchar, varchar) │
├──────────────────────┼─────────────────────┼─────────┼────────┼────────────────────┼───────────────────────┤
│ quack://0.0.0.0:4299 │ http://0.0.0.0:4299 │ 0.0.0.0 │ 4299 │ 0 │ {ipv6=false} │
└──────────────────────┴─────────────────────┴─────────┴────────┴────────────────────┴───────────────────────┘
ポートを省略した場合の既定値も確認しておきます。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; SELECT quack_uri_parser('quack:server.example.com', false) AS parsed_plain, quack_uri_parser('quack:server.example.com', true) AS parsed_ssl;"
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ parsed_plain │ parsed_ssl │
│ struct(host varchar, port usmallint, ipv6 boolean, ssl boolean, url varchar) │ struct(host varchar, port usmallint, ipv6 boolean, ssl boolean, url varchar) │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {'host': server.example.com, 'port': 9494, 'ipv6': false, 'ssl': false, 'url': 'http://server.example.com:9494'} │ {'host': server.example.com, 'port': 9494, 'ipv6': false, 'ssl': true, 'url': 'https://server.example.com:9494'} │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
既定ポートは 9494 です。なお、quack を公開する構成については、公式ドキュメントに AWS の CloudFormation による1クリックデプロイが紹介されています。EC2 インスタンス上で DuckDB と quack 拡張を nginx と Let's Encrypt の TLS の背後に置く構成です。
Amazon S3 Tables のテーブルバケットを作成する
ここからは AWS 側です。ap-northeast-1 にテーブルバケットを作成します。
% aws s3tables create-table-bucket --name duckdb-v20-alpha-20260922 --region ap-northeast-1
{
"arn": "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922"
}
ネームスペースを作成します。
% aws s3tables create-namespace \
--table-bucket-arn "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922" \
--namespace duckdb_demo \
--region ap-northeast-1
{
"tableBucketARN": "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922",
"namespace": [
"duckdb_demo"
]
}
テーブル自体は DuckDB 側から作成するため、ここでは作りません。
DuckDB サーバから S3 Tables に接続する
ここからは、先ほど立てた quack サーバに S3 Tables を担当させます。クライアントは quack 拡張だけを持ち、AWS の認証情報も iceberg 拡張も持ちません。
S3 Tables への接続には iceberg、httpfs、aws の各拡張が必要です。サーバプロセスが読み込むため、先にインストールしておきます。
% % ~/.duckdb/cli/latest/duckdb -c "INSTALL iceberg; INSTALL httpfs; INSTALL aws; SELECT extension_name, extension_version, installed FROM duckdb_extensions() WHERE extension_name IN ('iceberg','httpfs','aws') ORDER BY extension_name;"
┌────────────────┬───────────────────┬───────────┐
│ extension_name │ extension_version │ installed │
│ varchar │ varchar │ boolean │
├────────────────┼───────────────────┼───────────┤
│ aws │ 0a30cdb5ab │ true │
│ httpfs │ 96a2f2e88e │ true │
│ iceberg │ 61c948fb29 │ true │
└────────────────┴───────────────────┴───────────┘
いずれも alpha 向けのビルドが取得できました。サーバの初期化スクリプトを書き換えます。認証情報はクレデンシャルチェーンから取得し、テーブルバケットの ARN を ATTACH します。
% pkill -f "duckdb -init work/server_init.sql"; pkill -f "tail -f /dev/null"
% cat > work/server_init.sql <<'SQL'
LOAD quack; LOAD iceberg; LOAD httpfs; LOAD aws;
CREATE SECRET aws_sec (TYPE s3, PROVIDER credential_chain, REGION 'ap-northeast-1');
ATTACH 'arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922' AS s3tbl (TYPE iceberg, ENDPOINT_TYPE s3_tables);
CALL quack_identify(name => 'tokyo-server', provider => 'aws', region => 'ap-northeast-1', hostname => 'macbook-local');
CALL quack_serve('quack://127.0.0.1:4213', token => 'duckdb-aws-2026');
SQL
サーバを起動します。
% rm -f work/server.duckdb work/server.duckdb.wal work/server.log
% tail -f /dev/null | ~/.duckdb/cli/latest/duckdb -init work/server_init.sql work/server.duckdb > work/server.log 2>&1 &
% sleep 7
% cat work/server.log
-- Loading resources from work/server_init.sql
┌─────────┐
│ Success │
│ boolean │
├─────────┤
│ true │
└─────────┘
┌─────────┐
│ ok │
│ boolean │
└─────────┘
0 rows
┌────────────────────────┬───────────────────────┬─────────────────┐
│ listen_uri │ listen_url │ auth_token │
│ varchar │ varchar │ varchar │
├────────────────────────┼───────────────────────┼─────────────────┤
│ quack://127.0.0.1:4213 │ http://127.0.0.1:4213 │ duckdb-aws-2026 │
└────────────────────────┴───────────────────────┴─────────────────┘
先頭の Success が CREATE SECRET の、その次が quack_identify の戻りです。ATTACH はエラーを出していません。
クライアントから接続します。クライアントが読み込むのは quack 拡張だけです。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
SELECT name, provider, region FROM whoami();
SHOW DATABASES;
"
┌──────────────┬──────────┬────────────────┐
│ name │ provider │ region │
│ varchar │ varchar │ varchar │
├──────────────┼──────────┼────────────────┤
│ tokyo-server │ aws │ ap-northeast-1 │
└──────────────┴──────────┴────────────────┘
┌───────────────┐
│ database_name │
│ varchar │
├───────────────┤
│ s3tbl │
│ server │
└───────────────┘
CONNECT 後の SHOW DATABASES が返すのは、クライアント側の memory と qk ではなく、サーバ側の s3tbl と server です。サーバが ATTACH した S3 Tables のカタログが、クライアントのセッションから見えています。
この状態でテーブルを作り、データを投入します。実行されるのはサーバ側なので、S3 Tables へのリクエストもサーバが出します。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
CREATE TABLE s3tbl.duckdb_demo.sales (id INTEGER, region VARCHAR, amount DECIMAL(10,2), recorded_at TIMESTAMP);
INSERT INTO s3tbl.duckdb_demo.sales VALUES
(1, 'ap-northeast-1', 1200.50, TIMESTAMP '2026-09-22 10:00:00'),
(2, 'us-east-1', 890.00, TIMESTAMP '2026-09-22 10:05:00'),
(3, 'ap-northeast-1', 430.25, TIMESTAMP '2026-09-22 10:10:00'),
(4, 'eu-west-1', 250.00, TIMESTAMP '2026-09-22 10:15:00');
SELECT * FROM s3tbl.duckdb_demo.sales ORDER BY id;
"
┌────────┐
│ Count │
│ int64 │
└────────┘
0 rows
┌───────┐
│ Count │
│ int64 │
├───────┤
│ 4 │
└───────┘
┌───────┬────────────────┬───────────────┬─────────────────────┐
│ id │ region │ amount │ recorded_at │
│ int32 │ varchar │ decimal(10,2) │ timestamp │
├───────┼────────────────┼───────────────┼─────────────────────┤
│ 1 │ ap-northeast-1 │ 1200.50 │ 2026-09-22 10:00:00 │
│ 2 │ us-east-1 │ 890.00 │ 2026-09-22 10:05:00 │
│ 3 │ ap-northeast-1 │ 430.25 │ 2026-09-22 10:10:00 │
│ 4 │ eu-west-1 │ 250.00 │ 2026-09-22 10:15:00 │
└───────┴────────────────┴───────────────┴─────────────────────┘
CREATE TABLE と INSERT が quack 越しに通りました。DDL と DML のどちらもサーバ側で実行されています。


クライアント側に AWS の認証情報は無い
ここまでの操作でクライアントが持っているものを確認します。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
SELECT name, type, provider, scope FROM duckdb_secrets();
SELECT extension_name, loaded FROM duckdb_extensions() WHERE extension_name IN ('quack','iceberg','httpfs','aws') ORDER BY extension_name;
"
┌─────────┬─────────┬──────────┬───────────┐
│ name │ type │ provider │ scope │
│ varchar │ varchar │ varchar │ varchar[] │
└─────────┴─────────┴──────────┴───────────┘
0 rows
┌────────────────┬─────────┐
│ extension_name │ loaded │
│ varchar │ boolean │
├────────────────┼─────────┤
│ aws │ false │
│ httpfs │ false │
│ iceberg │ false │
│ quack │ true │
└────────────────┴─────────┘
シークレットは0件、読み込まれている拡張は quack だけです。念のため、同じクライアントから S3 Tables の ARN を直接 ATTACH してみます。
% ~/.duckdb/cli/latest/duckdb -c "LOAD iceberg; LOAD httpfs; LOAD aws; ATTACH 'arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922' AS s3tbl (TYPE iceberg, ENDPOINT_TYPE s3_tables); SELECT count(*) FROM s3tbl.duckdb_demo.sales;"
Invalid Configuration Error:
Could not find a valid storage secret (s3 or aws)
拡張を読み込んでも、シークレットが無いため S3 Tables には到達できません。CONNECT 経由でテーブルを作成できたのは、サーバ側の aws_sec が使われたためです。認証情報をサーバに集約したまま、クライアントから S3 Tables を操作できています。
AWS CLI から確認する
サーバ経由で作ったテーブルが、S3 Tables 側にどう見えているかを確認します。
% aws s3tables list-tables \
--table-bucket-arn "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922" \
--namespace duckdb_demo --region ap-northeast-1
{
"tables": [
{
"namespace": [
"duckdb_demo"
],
"name": "sales",
"type": "customer",
"tableARN": "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922/table/2233dc72-a57c-44f2-aea3-c064b9ad43b7",
"createdAt": "2026-09-22T12:48:50.705189+00:00",
"modifiedAt": "2026-09-22T12:48:51.781835+00:00"
}
]
}
% aws s3tables get-table \
--table-bucket-arn "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922" \
--namespace duckdb_demo --name sales --region ap-northeast-1
{
"name": "sales",
"type": "customer",
"tableARN": "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/duckdb-v20-alpha-20260922/table/2233dc72-a57c-44f2-aea3-c064b9ad43b7",
"namespace": [
"duckdb_demo"
],
"namespaceId": "1d118c9f-e0c0-4fd5-aa4b-bce1c3a8ed6b",
"versionToken": "945d675d12d726f0f526",
"metadataLocation": "s3://2233dc72-a57c-44f2-kwzbke3c6fw791zbjw4ozbndpc4s6apn1b--table-s3/metadata/00002-0dca634c-f809-4821-bd54-663da543e8dc.metadata.json",
"warehouseLocation": "s3://2233dc72-a57c-44f2-kwzbke3c6fw791zbjw4ozbndpc4s6apn1b--table-s3",
"createdAt": "2026-09-22T12:48:50.705189+00:00",
"createdBy": "123456789012",
"modifiedAt": "2026-09-22T12:48:51.781835+00:00",
"ownerAccountId": "123456789012",
"format": "ICEBERG",
"tableBucketId": "f865b73f-954b-4b15-a04d-7d8d543db68b"
}
ICEBERG フォーマットのテーブルとして、S3 Tables 側で管理されています。
集計とスナップショットを確認する
CONNECT 経由でもう1行追加し、集計とスナップショットを見ます。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
INSERT INTO s3tbl.duckdb_demo.sales VALUES (5, 'sa-east-1', 99.99, TIMESTAMP '2026-09-22 10:20:00');
SELECT region, count(*) AS cnt, sum(amount) AS total FROM s3tbl.duckdb_demo.sales GROUP BY region ORDER BY total DESC;
SELECT * FROM iceberg_snapshots('s3tbl.duckdb_demo.sales');
DISCONNECT;
"
┌───────┐
│ Count │
│ int64 │
├───────┤
│ 1 │
└───────┘
┌────────────────┬───────┬───────────────┐
│ region │ cnt │ total │
│ varchar │ int64 │ decimal(38,2) │
├────────────────┼───────┼───────────────┤
│ ap-northeast-1 │ 2 │ 1630.75 │
│ us-east-1 │ 1 │ 890.00 │
│ eu-west-1 │ 1 │ 250.00 │
│ sa-east-1 │ 1 │ 99.99 │
└────────────────┴───────┴───────────────┘
┌─────────────────┬─────────────────────┬─────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬───────────┐
│ sequence_number │ snapshot_id │ timestamp_ms │ manifest_list │ operation │
│ uint64 │ uint64 │ timestamp_ms │ varchar │ varchar │
├─────────────────┼─────────────────────┼─────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 2 │ 2687637034140385792 │ 2026-09-22 12:57:43.306 │ s3://2233dc72-a57c-44f2-kwzbke3c6fw791zbjw4ozbndpc4s6apn1b--table-s3/metadata/snap-2687637034140385792-700b9004-073f-4c88-bdef-b28fa2c2e4dc.avro │ append │
│ 1 │ 1330659414741503795 │ 2026-09-22 12:48:51.47 │ s3://2233dc72-a57c-44f2-kwzbke3c6fw791zbjw4ozbndpc4s6apn1b--table-s3/metadata/snap-1330659414741503795-68e78442-d0bf-4761-9a7f-01346d3632b6.avro │ append │
└─────────────────┴─────────────────────┴─────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴───────────┘
2回の INSERT がそれぞれ append のスナップショットとして記録されています。Iceberg のメタデータ操作も、すべてサーバ側で完結しています。
サーバ起動直後の ATTACH でつまずいた点
ところがここまでは順調でしたが、サーバを再起動すると状況が変わります。テーブルが存在する状態でサーバを立ち上げ直し、直後にクライアントから ATTACH します。
% pkill -f "duckdb -init work/server_init.sql"; pkill -f "tail -f /dev/null"
% tail -f /dev/null | ~/.duckdb/cli/latest/duckdb -init work/server_init.sql work/server.duckdb > work/server.log 2>&1 &
% sleep 7
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); SELECT 'attached' AS r;"
Binder Error:
Failed to bind remote table while attaching quack catalog "qk" (schema "duckdb_demo"): {"exception_type":"Catalog","exception_message":"Type with name UNKNOWN does not exist!\nDid you mean \"JSON\"?","name":"UNKNOWN","candidates":"JSON","location":"[34,7]","position":"34","type":"Type","error_subtype":"MISSING_ENTRY"}
SQL: CREATE TABLE duckdb_demo.sales(__ UNKNOWN);
ATTACH がリモートのカタログを取り込む段階で失敗しました。エラーに出ている CREATE TABLE duckdb_demo.sales(__ UNKNOWN) は、クライアントがサーバ側のカタログを再現しようとして生成した DDL です。列名が __、型が UNKNOWN になっており、S3 Tables 側のテーブルのスキーマが解決できていません。
カタログ同期を経由しない quack_query で、一度テーブルに触ります。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; SELECT count(*) AS c FROM quack_query('quack://127.0.0.1:4213','SELECT * FROM s3tbl.duckdb_demo.sales', token => 'duckdb-aws-2026');"
┌───────┐
│ c │
│ int64 │
├───────┤
│ 5 │
└───────┘
その後に改めて ATTACH します。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); SELECT 'attached' AS r;"
┌──────────┐
│ r │
│ varchar │
├──────────┤
│ attached │
└──────────┘
今度は成功しました。発生条件は次の通りです。
- S3 Tables 側にテーブルが1つも無い状態でサーバを起動した場合は発生しません。この記事の最初の ATTACH が通ったのはこのためです
- テーブルが存在する状態でサーバを起動すると、スキーマがサーバのメモリ上に読み込まれるまでの間、クライアントからの ATTACH が失敗します
quack_queryなどでサーバ側が一度そのテーブルに触れた後は、ATTACH が成功します
Iceberg カタログのスキーマが遅延ロードされることと、quack のカタログ同期のタイミングが噛み合っていないように見えます。alpha 版の段階なので、後日あらためて確認するか、再現手順を添えて issue として報告するのが適切な挙動だと考えられます。
サーバを停止する
クライアント側から quack_stop を呼んでサーバを停止できます。
% ~/.duckdb/cli/latest/duckdb -c "
LOAD quack;
ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026');
CONNECT qk;
CALL quack_stop('quack://127.0.0.1:4213');
SELECT * FROM quack_server_list();
"
┌─────────────────────────────────────────────┐
│ status │
│ varchar │
├─────────────────────────────────────────────┤
│ Stopped listening on quack://127.0.0.1:4213 │
└─────────────────────────────────────────────┘
┌────────────┬────────────┬─────────┬────────┬────────────────────┬───────────────────────┐
│ listen_uri │ listen_url │ host │ port │ active_connections │ info │
│ varchar │ varchar │ varchar │ uint16 │ uint64 │ map(varchar, varchar) │
└────────────┴────────────┴─────────┴────────┴────────────────────┴───────────────────────┘
0 rows
既定の認可関数 quack_nop_authorization は認可の判断を行わないため、トークンさえ持っていればクライアントからサーバを停止できます。運用時には quack_authorization_function の差し替えが必要になる箇所です。
停止後に接続を試みます。
% ~/.duckdb/cli/latest/duckdb -c "LOAD quack; ATTACH 'quack:127.0.0.1:4213' AS qk (TOKEN 'duckdb-aws-2026'); SELECT 1;"
IO Error:
Failed to send message: IO Error: Could not connect to server error for HTTP POST to 'http://127.0.0.1:4213/quack'
% lsof -nP -iTCP:4213 -sTCP:LISTEN
考察
CONNECT はカタログ参照ではなくセッションの切り替え
今回いちばん明確になったのは、ATTACH と CONNECT の役割の違いです。
ATTACH 'quack:...' はリモートのデータベースをカタログとして見えるようにするもので、qk.sales のように修飾して参照します。この時点ではクエリの実行主体はクライアントです。
CONNECT はセッションの実行先そのものをサーバに向けます。CONNECT 後は whoami() がサーバの情報を返し、current_database() もサーバ側の値になり、SHOW DATABASES もサーバのカタログ一覧を返します。クライアント側にしかないテーブルは見えなくなります。
この違いは、クライアント側に何を置かなくて済むかに直結します。今回の構成では、クライアントは quack 拡張だけを持ち、iceberg 拡張も AWS の認証情報も持たないまま、S3 Tables の Iceberg テーブルを作成し、行を投入し、集計まで実行できました。同じクライアントから S3 Tables の ARN を直接 ATTACH すると Could not find a valid storage secret で失敗するので、S3 へのアクセスがサーバ側で完結していることも確認できています。認証情報と拡張をサーバ側に集約する構成が SQL だけで組める点は、実務上の使いどころがありそうです。
現時点で確認できた制限
alpha 版として触った範囲で、次の点が引っかかりました。
- URI の書き方が
quack_serveとATTACHで揃っていません。quack_serveはquack://形式、ATTACHはquack:形式が素直に通ります。ATTACHでquack://を使う場合はTYPE quackの明示が必要です quack_serveはブロックしないため、サーバを常駐させるにはプロセスを生かし続ける仕組みを別途用意する必要があります。公式ドキュメントにはスタンドアロンでの常駐手順の記載が見当たらず、代わりに EC2 と nginx を使う CloudFormation でのデプロイが案内されています- S3 Tables のテーブルが存在する状態でサーバを起動すると、その直後のクライアントからの
ATTACH 'quack:...'がカタログ同期で失敗します。テーブルが1つも無い状態では発生せず、サーバ側で一度テーブルに触れた後は成功するため、スキーマの遅延ロードとカタログ同期のタイミングの問題と考えられます - 既定の認可関数は認可の判断を行わないため、トークンを持つクライアントはサーバの停止まで実行できます
いずれも alpha 版での挙動であり、2026年10月後半に予定されている正式リリースまでに変わる可能性があります。DuckDB チームは alpha 期間中のバグ報告を求めているので、同様の事象に当たった場合は issue として報告するのが良さそうです。
今回検証していないこと
次の点は確認していません。記事の内容から外挿しないようご注意ください。
- 公開ネットワークでの TLS 込みの運用。今回は localhost での平文通信のみです
- 大量データや多数クライアントでの性能・スケーラビリティ
- サーバ側が引き受ける AWS 権限をクライアントごとに分離する方法
- Athena や Amazon EMR など、他のエンジンから同じ S3 Tables を読む相互運用性
AWS 参画をどう見るか
AWS のブログでは、DuckDB を S3、Redshift、Athena、EMR、Glue ETL といった既存サービスと組み合わせていく方針と、Amazon Quick での採用実績が示されています。DuckLabs 側の発表では、AWS のインフラと規模に乗ることで到達できる利用者の幅を広げたいという意図が述べられています。
一方で、ライセンスとガバナンスは DuckDB Foundation の下で維持されることが、DuckDB 側と AWS 側の双方から明記されています。今回 alpha 版を触った限りでも、拡張のインストール経路や SQL の書き味は従来通りで、AWS 固有の手順が差し込まれている箇所はありませんでした。S3 Tables への接続も、Iceberg REST カタログを扱う既存の仕組みの延長にあります。
今回確認できたのは、DuckDB を実行基盤として切り出す quack と、データを AWS 側に置く S3 Tables が、それぞれ alpha 版で動作し、組み合わせても集計結果を返せるところまでです。これを本番構成として評価するには、認証の委譲、権限分離、同時実行、コスト、運用設計の検証が別途必要になります。
最後に
DuckDB v2.0 の alpha 版で、quack 拡張によるサーバ化と、Amazon S3 Tables への読み書きを試しました。
CONNECT はリモートのテーブルを参照する仕組みではなく、セッションの実行先をサーバに切り替える仕組みでした。この性質のおかげで、クライアント側に AWS の認証情報や iceberg 拡張を置かないまま、サーバ側に集約した状態で S3 Tables の Iceberg テーブルを作成し、行を投入し、集計まで実行できます。インプロセスデータベースとして使ってきた DuckDB に、実行環境を分離するという選択肢が加わります。
一方で、サーバを常駐させる手順、URI 形式の揺れ、Iceberg カタログとの組み合わせでのカタログ同期の失敗、既定の認可関数の扱いなど、alpha 版ならではの引っかかりもありました。正式リリースは2026年10月後半の予定です。本番での採用判断は、正式版で認証や同時実行まで含めて確認してからになります。
DuckDB v2.0 の alpha 版は、インストールスクリプトに DUCKDB_VERSION=alpha を渡すだけで既存環境と分離して試せます。S3 Tables に載せたデータを手元から触る使い方を検討している方は、正式リリースを待たずに挙動を確認しておくとよさそうです。
とにかく、AWSのアナリティクスサービスとどのように連携するのかが楽しみです。特にApache Iceberg v3 のクエリエンジンとして期待が高まります!







