Zenn
🦆

DuckDB Update & Blog reading #3 AWS S3 TablesにAWS Lambdaからクエリ

2025/03/25に公開

まえがき

DuckDBの先週のBlogでAWS S3Tablesに直接接続させる方法が書かれていました。
これまではGlue経由でないとアクセスできなかったのですが、Apache Iceberg REST Catalog APIs経由でアクセスできるようになったので試してみました🙌

[今回の公式ブログ]

https://duckdb.org/2025/03/14/preview-amazon-s3-tables.html

Apache Iceberg REST Catalog APIsとDuckDB使用のメリット

GlueやAthena経由だとどうしても料金がかかってしまうが、比較的料金が安くなるはず。
まだ読み取りだけだが今後Icebergへのインサートなどが増えればよりIcebergが扱いやすくなりそう。

S3Tablesを用意する。

去年の暮れに実はのようにS3Tablesを作ってました。
https://zenn.dev/amana/articles/1c9821693691ca
普通のS3 bucketの作成とは違い、Lakeformation周りの設定もあり苦労した記憶があります。
ただ現在だとわかりやすくしっかりした記事がたくさんあるので作成する場合は他の記事を参考にした方が良いと思います。

自分は以前作ったS3Tablesが権限の設定か何かで再度使用することができなかったので🫠
以下の記事を参考にもう一度作っておきました。
https://dev.classmethod.jp/articles/aws-s3-tables-getting-started-tutorial-with-console/

https://dev.classmethod.jp/articles/add-s3-tables-iceberg-rest-catalog-api/

Athenaのクエリは以下のような感じ

CREATE TABLE test1.nk (
    id INT,
    name STRING
)
TBLPROPERTIES ('table_type' = 'ICEBERG');

Athenaでのクエリが成功するとこのような画面になります。
自分はS3のLOCATIONが必要?という勘違いをしたりして迷走しましたが、以前作ったものではなく一度東京リージョンで一から作成し直してようやく成功しました。

IAMユーザーにアタッチしているポリシーの該当部分です。lakeformation用の権限も付与しています。
(テスト環境のためリソースを*にしてます。)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3tables:GetTableBucket",
                "s3tables:ListTableBuckets",
                "s3tables:GetNamespace",
                "s3tables:ListNamespaces",
                "s3Tables:GetTable",
                "s3Tables:ListTables",
                "s3Tables:GetTableData",
                "s3Tables:PutTableData",
                "s3Tables:GetTableMetadataLocation",
                "s3Tables:UpdateTableMetadataLocation"
            ],
            "Resource":[
             "*"
            ]
                ,
            "Effect": "Allow"
        },
        {
            "Action": "lakeformation:GetDataAccess",
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

AWS Lambdaでduckdbを使用できるようにAWS Lambda Layerにduckdbを入れる。

これも以前作成済みです。python以外で作成されたpythonライブラリをLambda Layerに入れるのにかなり苦労した覚えがあります。(実際の解決策はシンプルでしたが...)
前回Layerに入れたものはもう古いのでもう一度入れ直しました。

[参考記事]
https://zenn.dev/amana/articles/7651ec03bb6c3e

これでAWS Lambda上でDuckDBを使用できるようになりました。

AWS Lambda関数

いよいよLambda関数を記述します。

import json
import duckdb

def lambda_handler(event, context):
    # 1
    con = duckdb.connect(database=':memory:')
    con.execute("SET home_directory='/tmp';")

    # 2
    con.sql("FORCE INSTALL aws FROM core_nightly")
    con.sql("FORCE INSTALL httpfs FROM core_nightly")
    con.sql("FORCE INSTALL iceberg FROM core_nightly")
    con.sql("LOAD aws")
    con.sql("LOAD httpfs")
    con.sql("LOAD iceberg")

    # 3
    con.sql("""
    CREATE SECRET (
    TYPE s3,
    KEY_ID 'XXXXXXXXXX',
    SECRET 'XXXXXXXXXX',
    REGION 'ap-northeast-1'
    );
    """)

    # 4
    con.sql("""
    ATTACH 'XXXXXXXXXX'
    AS s3_tables_db (
    TYPE iceberg,
    ENDPOINT_TYPE s3_tables
    );
    """)
    # 5
    print(con.sql("SHOW ALL TABLES"))

1:まずduckdbをインメモリ状態で接続しています。またLambda関数で使用するにはホームディレクトリとしてtmpを指定する必要があるのでSETしてます。

2:DuckDBの公式よりcore_nightlyのリポジトリ?をインストールする必要があるらしくFORCE INSTALLしています。またaws,httpfs,icebergの拡張機能も一緒にインストールしています。

3:シークレット情報を入力しています。このためにIAMユーザーを作成してアクセスキーとシークレットキーを入力しています。なので今回はLambda関数自体にはロールはつけていません。ただここで毎回権限設定をしないといけないのは問題だと思います。

4:ATTACHにはs3tablesのバケットのarnを入力しています。これでようやくs3_tables_dbという名前でS3tablesにクエリすることができるようになりました。

5:テーブル一覧を出力するクエリです。今回は以下のようになりました。

┌──────────────┬─────────┬─────────┬──────────────┬──────────────┬───────────┐
│   database   │ schema  │  name   │ column_names │ column_types │ temporary │
│   varchar    │ varchar │ varchar │  varchar[]   │  varchar[]   │  boolean  │
├──────────────┼─────────┼─────────┼──────────────┼──────────────┼───────────┤
│ s3_tables_db │ test    │ nk      │ [__]         │ [INTEGER]    │ false     │
└──────────────┴─────────┴─────────┴──────────────┴──────────────┴───────────┘

Athenaで作成したtest.nkというテーブルがあるのが分かります。
ただ現状はこのテーブルに中身がないのでAthenaの方に一旦戻って追加でINSERTしてみます。

INSERT INTO test.nk
VALUES
(1, 'nk')

そしてLambdaに以下のコードを追加して実行すると...

print(con.sql("FROM s3_tables_db.test.nk"))
┌───────┬─────────┐
│  id   │  name   │
│ int32 │ varchar │
├───────┼─────────┤
│     1 │ nk      │
└───────┴─────────┘

のように表示されました🙌
ここまで長かったです...

まとめ

結果的にはicebergのテーブル表示までなんとかできました。ただLambda上で使用するために毎回リポジトリやシークレットの実行をやるのはちょっと問題かなと思いました。(このLambda上のクエリで15秒くらいかかるので...)
自分の設定意外にも他にもやり方があるのかもしれません。ただ今後の拡張も含めて大いに期待できる機能だと思いました!

Discussion

ログインするとコメントできます