🦆

pg_duckdb - PostgreSQL プロセス上の DuckDB で OLAP ワークロードを処理する

2024/11/10に公開

本記事の概要

pg_duckdb は、PostgreSQL のプロセス上でインプロセス OLAP クエリエンジンである DuckDB を実行するための拡張です。 DuckDB を利用することで、以下のようなメリットがあります。

  • PostgreSQL のネイティブクエリエンジンと比べ、OLAP ワークロードをより高速に処理できる。
  • DuckDB の拡張を利用し、 Iceberg や Delta lake といったオープンテーブルフォーマットでオブジェクトストレージ上に構築したデータレイクのデータを分析できる。

本記事では、 PostgreSQL 拡張の pg_duckdb の概要、私が pg_duckgb に注目した背景、および簡単な使い方などを紹介します。

pg_duckdb の概要

pg_duckdb は、スタートアップの Hydra、DuckDB 開発元の DuckDB Labs、SaaS 版 DuckDB サービスを提供している MotherDuck が共同開発した PostgreSQL 拡張です。

以下の図は、記事やGithubプロジェクトのドキュメントから筆者が想像した pg_duckdb を使ったアーキテクチャ図です(公式が出している図ではない点に注意)。

pg_duckdb

pg_dudkcb は、PostgreSQL の拡張として、PostgreSQL にインストールされ、PostgreSQL のプロセス上で DuckDB を実行します。OLTP ワークロード向けに設計された PostgreSQL のネイティブクエリエンジンと比較し、 DuckDB は、より高速に OLAP ワークロードを処理できます。

一方で、行指向なデータフォーマットを使ったローカルストレージをスキャンするアプローチは、OLAP ワークロードの性能向上において限界があるため、より OLAP のワークロード向いたデータレイクへのアクセスも対応しています。具体的には、S3 や GCS といったクラウドオブジェクトストレージ上に配置された列指向オープンデータフォーマット Parquet やオープンテーブルフォーマットの Iceberg や Delta lake などへのクエリもサポートしています。

プロジェクトの概要やプロダクトの特性の詳細については、Github リポジトリおよびプロジェクトがアナウンスされた MotherDuck のブログを参照してください。

pg_duckdb に注目する背景

ここでは、過去の筆者の体験および昨今の技術開発の状況を踏まえて、筆者が pg_duckdb に注目するに至った背景について紹介します。

筆者が 2020 年 4 月当時スタートアップだった前職に最初のデータエンジニアとして入社した当時、前職にはデータウェアハウスは存在せず、既存の AWS RDS PostgreSQL を OLAP 用のデータベースとして利用するように指示されました。これは、それまでデータ分析のプロジェクトやプロダクトは社内に存在しなかったため、どの程度の投資をして、投資対効果が得られるか判断材料がなかったため、まずは、半年くらい既存の枠組みの中でデータ分析プロジェクトを遂行し、成果を出してから翌年に向けた必要な投資判断を行うという意思決定がなされていたためです。

その後、ちょうど半年くらい経った頃、性能に問題があるクエリが散見されるようになり、パフォーマンスチューニングに時間を取られるようになった際に、上司からビジネスが拡大しているため、数年で規模が数倍になった場合に問題のない設計を提案して欲しいと言われ、クラウドデータウェアハウスを評価し、導入に至りました。導入したクラウドデータウェアハウスのユーザ体験には満足していましたが、小さいコンピューティングリソースを利用している割にはコストが高いため、その1年後にはコスト最適化に時間をが取られるようになりました。

その後、転職した後も、常にクラウドデータウェアハウスのコスト最適化を行うことになりました。実際の利用状況を調べてみると、ワークロードのサイズはあまり大きくない割には、クラウドデータウェアハウスの利用にかかるコストが非常に高い点に悩まされてきた時に、DuckDB ブームが起き、MotherDuck 社の象徴的な Big Data Is Dead の記事が話題になり、処理データが小さいのであれば、DuckDB のような、より簡素で、安い方法でデータプラットフォームを構築できないか考えるようになりました。

https://motherduck.com/blog/big-data-is-dead/

その後、業務で Iceberg や Delta lake のようなオープンテーブルフォーマットと、Unity Catalog や Apache Polaris のようなオープンメタストアを評価する機会があり、データをオープンテーブルフォーマットでオブジェクトストレージ上に保存し、メタデータや権限管理をメタストア上でやり、 ETL や OLAP ワークロードは DuckDB でできれば、今後、より簡素で安いデータプラットフォームが構築できる可能性ができるのではないかと同僚とも議論していました。その時に pg_duckdb のプロジェクトがブログでアナウンスされました。

https://motherduck.com/blog/pg_duckdb-postgresql-extension-for-duckdb-motherduck/

もし、この拡張が 2020 年後半に GA していれば、まず pg_duckdb でできる範囲で、もう少しギリギリのところまで頑張ろうという判断になり、クラウドデータウェアハウスを導入する判断を先送りしていた可能性があるなと考えました。ハードウェアの性能は毎年向上していますし、DuckDB のようなソフトウェアレベルでの技術革新も OSS で行われており、高コストになりがちなクラウドデータウェアハウスが必要になると判断するハードルが以前よりも高くなってきている、なくてもなんとかできる範囲が拡大しているのではと考え、現段階での pg_duckdb を一度触ってみようと考えました。

pg_duckdb をコンテナで起動する

ここからは実際に pg_duckdb を試用する手順について紹介します。

(注) 本記事は、Apple M1 Max および Docker Desktop 4.25.0で検証しています。

pg_duckdb を試す上で一番簡単な方法は、すでに pg_duckdb がインストールされた状態の PostgreSQL コンテナイメージを利用することです。以下のコメントでコンテナを起動し、psql コマンドで PostgreSQL データベースにログインしてください。

$ docker run -d \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=duckdb \
  pgduckdb/pgduckdb:16-main
$ psql postgres://postgres:duckdb@localhost:5432/postgres

TPCS-DS データセットで PostgreSQL ネイティブと pg_duckdb のクエリパフォーマンスを比較する

PostgreSQL のローカルストレージを pg_duckdb でクエリするメリットを確認するため、以下のブログ記事で紹介されているスクリプトで PostgreSQL に TPCS-DS データセットをロードしましょう。

https://motherduck.com/blog/pgduckdb-beta-release-duckdb-postgres/

$ PGUSER=postgres \
PGPASSWORD=duckdb \
PGHOST=localhost \
PGPORT=5432 \
PGDATABASE=postgres \
bash load-tpcds.sh 1

ブログ記事で紹介されている通り、以下のクエリを性能比較に利用します。

https://github.com/duckdb/duckdb/blob/af39bd0dcf66876e09ac2a7c3baa28fe1b301151/extension/tpcds/dsdgen/queries/01.sql

まずは、PostgreSQL 上でそのままクエリを実行すると、92 秒かかりました。SET duckdb.force_execution = true; を実行し、強制的に pg_duckcb で同じクエリを実行すると、 144 ミリ秒で終了しました。大幅にパフォーマンスが改善しました。

# \timing on
# \i 01.sql
Time: 92042.015 ms (01:32.042)
-- force to use duckdb extention
# SET duckdb.force_execution = true; 
# \i 01.sql
Time: 144.381 ms
# \i 01.sql

データレイク上のデータを処理する

次にオブジェクトストレージ上のファイルを処理する方法についても紹介します。

オブジェクトストレージ上に構築したデータレイクに、Parquet の様なオープンデータフォーマット、Iceberg や Delta lake の様なオープンテーブルフォーマットなど OLAP に向いた列指向のフォーマットでデータを保管し、処理するアプローチを取ることで、データレイク上の集計結果を簡単に PostgreSQL へ取り込むことができますし、PostgreSQL 上のトランザクションデータをデータレイクに取り込むことも可能です。

筆者は、本業のメインクラウドプロバイダーは Google Cloud なため、 GCS を利用しますが、AWS S3 でも同様な処理が可能です。DuckDB の GCS 機能は認証に HMAK キーを利用するため、事前にキーを取得してください。

https://duckdb.org/docs/guides/network_cloud_storage/gcs_import.html

HMAK キーを DuckDB のシークレットに格納できます。 key_id, secret は実際の値に置き換えてください。

$ INSERT INTO duckdb.secrets (type, key_id, secret,region)
VALUES ('GCS', 'key_id', 'secret', 'europe-west-2');

サンプルデータとして以下の Iris Species データ (CSV) を利用します。 データをダウンロードし、GCS バケットにアップロードしてください。

https://www.kaggle.com/datasets/uciml/iris

read_csv 関数でGCS 上の CSV ファイルをクエリすることができます。read_csv 関数を利用する際は、 AS (Species text) の箇所にデータ型を明示したカラムリストを書く必要があります。

$ \timing on

$ SELECT
  Species,
  count("Species") AS count
FROM read_csv ('gs://your_gcs_buckdt/Iris.csv')
AS (Species text)
GROUP BY Species
ORDER BY count DESC;

     Species     | count 
-----------------+-------
 Iris-virginica  |    50
 Iris-setosa     |    50
 Iris-versicolor |    50
(3 rows)
Time: 419.343 ms

CTAS で GCS 上の CSV ファイルからテーブルを作成できます。

$ CREATE TABLE iris AS
SELECT *
FROM read_csv ('gs://your_gcs_buckdt/csv/Iris.csv')
AS(Id int, SepalLengthCm float, SepalWidthCm float, PetalLengthCm float, PetalWidthCm float, Species text);
SELECT 150
Time: 381.610 ms

$ SELECT
  Species,
  count (Species) as count
FROM iris
GROUP BY Species;

     Species     | count 
-----------------+-------
 Iris-virginica  |    50
 Iris-versicolor |    50
 Iris-setosa     |    50
(3 rows)
Time: 14.379 ms

pg_duckdb は COPY コマンドを使った S3 や GCS への Parquet フォーマットでの出力機能をサポートしています。

$ COPY (
     SELECT *
     FROM iris
 ) TO 'gs://your_gcs_buckdt/parquet/iris.parquet';
COPY 150
Time: 474.428 ms

read_parquet 関数を使って GCS 上の Parquet データにアクセスできます。

$ SELECT
  Species,
  count(Species) AS count
FROM read_parquet ('gs://your_gcs_buckdt/parquet/iris.parquet')
AS (Species text)
GROUP BY Species
ORDER BY count DESC;

     Species     | count 
-----------------+-------
 Iris-versicolor |    50
 Iris-setosa     |    50
 Iris-virginica  |    50
(3 rows)

Time: 585.481 ms

Delta lake ファイルにアクセスする際は、delta_scan 関数を使います。事前に DuckDB 拡張の delta をインストールする必要があります。

TODO 筆者の環境では正常にアクセスができなかったため検証中

$ SELECT duckdb.install_extension('delta');
$ SELECT
  Species,
  count(Species) AS count
FROM delta_scan ('gs://your_gcs_buckdt/delta/')
AS (Species text)
GROUP BY Species
ORDER BY count DESC;

Iceberg ファイルにアクセスする際は、iceberg_scan 関数を使います。事前に DuckDB 拡張の iceberg をインストールする必要があります。

TODO 筆者の環境では正常にアクセスができなかったため検証中

$ SELECT duckdb.install_extension('iceberg');
$ SELECT
  Species,
  count(Species) AS count
FROM read_parquet ('gs://your_gcs_buckdt/iceberg/')
AS (Species text)
GROUP BY Species
ORDER BY count DESC;

まとめ、および pg_duckdb の使い所について所感

本記事では、pg_duckdb のプロジェクトやプロダクトの概要、筆者が pg_duckdb に注目する背景、簡単な PostgreSQL との性能比較、データレイクとの併用方法などを紹介しました。

処理リリースを使ってみての感想としては、pg_duckdb は比較的小さいデータにおいては PostgreSQL よりもパフォーマンスが高いことがわかりました。ただし、OLTP データベースのプロセス上で実行されるため、クラウドデータウェアハウスのようなスケーラブルな OLAP データベースと比較すると、スケーラビリティには限界があると考えられます。またデータレイクとの併用は、オブジェクトストレージからデータをロードするレイテンシがあり、キャッシングの仕組みがまだ不十分のため、特に大きいデータに対しては OLAP クエリの性能に限界があるなと考えます。

一方で、スタンドアロンなオペレーション系システムで、バックエンドデータベースとして PostgreSQL を使っており、中小規模な OLAP のワークロードがあるが、データウェアハウスのような高価で大掛かりな仕組みを導入するには躊躇するプロジェクトにおいては、今後十分ソリューションになりうると感じました。

pg_duckdb は今年発表されたプロジェクトであり、まだリリースは 1 回のみです。今後、発展することが期待できるプロダクトのため、今後、成熟した際にはまた評価したいと思います。

Discussion