🦆

Duckdbを使用したSQL操作をするAWS Lambda関数作成(Python)

2024/12/07に公開

概要

以前からDuckdb×AWS Lambdaを使用してS3上のSQL操作を行うという例は耳にしていた。
(以下参考記事🙏)
https://zenn.dev/penginpenguin/articles/b508f04a3431a8
https://qiita.com/shinonome_taku/items/cb9bfd7419a77a80e235

ただこの例ではpythonでLambda関数を作成していなかったので手軽に作成できる方法を探していた。
結果的にはうまくいったが、かなり詰まったため記事として残しておこうと思います。

[前提]

S3上のcsvデータにAWS Lambdaがアクセス可能な権限をもっている状態である。
S3はトリガーとして設定済み
Lambdaの設定は以下の通り

①Lambdaレイヤーの作成(外部ライブラリduckdbを入れる)

外部のライブラリを使用するためにはその都度lambda関数にインストールしなければならない。
ただそれだと毎回手間であるため、lambda layerにインストールするのが良いということらしい。

Lambda layerに入れるフォルダを作成する。

ターミナル上で以下コードを実行する。

mkdir python

なぜpythonという名前にしないといけないか?
Layerのコードは /opt 以下に展開される.../opt/python以下に外部ライブラリが展開されるようにしないといけないとのこと。

[参考記事]
https://dev.classmethod.jp/articles/lambda-layer-basics-how-it-works/

ローカルでduckdbをインストール

純Pythonのライブラリなら問題ない?ようだが今回のduckdbのようにC++等他言語で大部分が書かれているものなどはLambdaレイヤーにライブラリを置いてもno module errorになる。(要検証)
lambda上がlinux環境なのでlinux環境に対応するように外部ライブラリを作成しないといけないらしい。。
この問題を解決するために、Dockerでlinux環境を作成したり、C/C++環境を一緒にインストールしたり結構迷子になりながら色々試していましたが自分では再現できませんでした。。
そんな中、方々で質問していたのですが、たまたま某所で以下の記事の作者の方に教えていただき、無事解決しました。ありがとうございます!🙏
https://qiita.com/minorun365/items/85cb57f19fe16a87acff

これにならってターミナルで以下のコードを打ちました。

pip install -t python --platform manylinux2014_x86_64 --only-binary=:all: duckdb

解説
-t python:-t はtargetの略でつまりインストールしたライブラリをpythonというフォルダにインストールしますよ...ということ

--platform manylinux2014_x86_64:

Pythonパッケージをインストールする際に、それがどのプラットフォーム用にビルドされたものかを指定する。
通常、pipは現在の環境(ローカルPC)のプラットフォームを自動検出して適切なバイナリ(Wheelファイル)やソースコードを取得するが、明示的に--platformを指定することで、特定の環境向けにビルドされたバイナリをインストールできる...とのこと。
manylinux2014 x86_64 でLambda上で実行できるようなパッケージとしてインストールする。

zipファイルにする。

pythonフォルダ内にインストールした外部ライブラリをzip化したいのでターミナルで以下のコードを実行します。

zip -r layer.zip python

これでLambda 上のLayerに置くことのできるzipファイルを作成できました。🙌

Lambda Layerにzipファイルをアップロードする。

この設定でzipファイルをアップロードしました。
(10MB超えていますが自分は普通にアップロードしました。)
※あと画像ではarm64optionですが、実際にはx86_64オプションです。

②Lambda関数を作成・実行

https://dev.classmethod.jp/articles/get-s3-object-with-python-in-lambda/

とりあえずLambda関数にs3fullaccessロールをつけ,かつLambda関数のトリガーにデータソースのS3を置く。
その後以下コードを実行

import duckdb

def lambda_handler(event, context):
        # DuckDBのメモリデータベースに接続
        con = duckdb.connect(database=':memory:')
        con.execute("SET home_directory='/tmp';")
        
        # httpfs拡張機能のインストール&ロード
        con.execute("INSTALL httpfs;")
        con.execute("LOAD httpfs;")
        test = con.sql(r'SELECT * FROM read_csv("s3://kagsource/test.csv")').df()
        print(test.head())

duckdbはインメモリで駆動
home_directoryにはtmpを設定しないと動かないようなので設定
s3にhttpfs拡張機能でアクセス
トリガーに設定しているs3をs3://{バケット名}/{対象データソース}で指定。
とりあえずデータフレームとしてheadをprint

テスト実行結果は以下の通り

Status: Succeeded
Test Event Name: test

Response:
null

PassengerId  Pclass  ... Cabin Embarked
0          892       3  ...  None        Q
1          893       3  ...  None        S
2          894       2  ...  None        Q
3          895       3  ...  None        S
4          896       3  ...  None        S
[5 rows x 11 columns]


Duration: 5980.96 ms	Billed Duration: 5981 ms	Memory Size: 512 MB	Max Memory Used: 200 MB	Init Duration: 390.31 ms

#reportidなどは削除

メモリは512MBで最大の使用メモリは200MB、実行時間はだいたい6秒...
でした。

Discussion