ちょっと話題の記事

話題の Google Cloud の新しい DB の AlloyDB for PostgreSQL を調査して、分析クエリ高速化機能のカラム型エンジンを試してみた

Google Cloud の新しい DB の AlloyDB for PostgreSQL を実際に触って、目玉機能のカラム型エンジンも実際に試してみました。

ウィスキー、シガー、パイプをこよなく愛する大栗です。

Google I/O 2022 で新しいデータベースである AlloyDB for PostgreSQL が発表されました。実際にサービスを触ってみたのでまとめてみます。

AlloyDB for PostgreSQL は2022年5月18日現在において、プレビューのステータスです。このプロダクトまたは機能は、Google Cloud Platform の利用規約の一般提供前のサービス規約の対象となります。一般提供前のプロダクトと機能では、サポートが制限されることがあります。また、一般提供前のプロダクトや機能に変更が加えられると、他の一般提供前バージョンと互換性がない場合があります。詳細については、リリースステージの説明をご覧ください。

AlloyDB for PostgreSQL とは

AlloyDB for PostgreSQL は PostgreSQL と互換性を持つエンタープライズグレードで、標準的な PostgreSQL と比較して、トランザクションで 4 倍、分析クエリで最大 100 倍の性能を発揮するデータベースです。

AlloyDB の特徴

  1. 優れたパフォーマンスと拡張性
    商用グレードのワークロードに対して、優れたパフォーマンスとスケールを提供します。標準的な PostgreSQL と比較して 4 倍、AmazonのPostgreSQL 互換サービスと比較して 2 倍の速度でトランザクションを処理できると主張しています。ここで言われているAmazonのPostgreSQL 互換サービスは Amazon Aurora PostgreSQL 互換エディションを指していると思われます。
  2. 業界トップクラスの可用性
    (現在はプレビューであるため SLA は提供されていませんが)メンテナンスを含めて 99.99% の高可用性 SLAを実現します。大半の障害を自動で検出して、数秒以内に回復します。
  3. リアルタイムのビジネスインサイト
    標準的な PostgreSQL と比較して最大 100 倍高速な分析クエリを実現します。カラム型でデータをメモリに格納し高速なスキャンや集計を実現します。トランザクションと分析を同じデータで処理する HTAP を実現します。
  4. 予測しやすく明朗な価格設定
    高額な独自ライセンスが不要で、I/O 課金が無いため、予測しやすく明朗な価格設定になっています。インスタンスのメモリに加えて自動的にプロビジョニングされる無料の超高速キャッシュによりコストパフォーマンスを高めます。
  5. 機械学習による管理とインサイト
    AlloyDB はデータベースのパッチ適用、バックアップ、スケーリング、レプリケーションを自動的に処理しますが、適応型アルゴリズムと機械学習により PostgreSQL の バキューム管理、ストレージとメモリ管理、データ階層化、分析高速化は更に進んでいます。また Google Cloud の AI プラットフォームである Vertex AI と統合されます。

AlloyDB の構成

AlloyDB の構成概要は以下のようになっています。AlloyDB は全体をクラスタという概念でまとめており、一つのクラスタ ストレージを持ち、プライマリインスタンスと複数の読み取りプールノードを持ちます。各インスタンスは 1 つのクラスタ ストレージを共有しており、プライマリインスタンスは書き込みと読み取りが可能で、読み取りプールノードは読み取り専用となっています。またプライマリインスタンスはフェイルオーバー専用のレプリカを持っています。


Introducing AlloyDB, a PostgreSQL-compatible cloud database service https://www.youtube.com/watch?v=o5pKfH0Tonwより引用

AlloyDB のストレージ

AlloyDB のストレージは Google の分散ファイルシステムである Colossus1の上に実装されており、先行書き込みログ (WAL) を書き込むと、ストレージレイヤーでログ処理サービス (LPS) が WAL レコードを処理してマテリアラズされたデータベースブロックを生成して、障害に強くシャーディングされたリージョナルブロックストレージに書き込みます。


AlloyDB for PostgreSQL under the hood: Intelligent, database-aware storage https://cloud.google.com/blog/products/databases/alloydb-for-postgresql-intelligent-scalable-storageより引用

通常の PostgreSQL は、更新処理で Commit を行った時に WAL をストレージへ書き込み、定期的なチェックポイント処理で共有バッファのダーティページをストレージへ書き込み、不要になった WAL をストレージから削除するというディスクアクセスが必要となります。AlloyDB ではインテリジェントなストレージレイヤーにより、書き込みを行うプライマリインスタンスは WAL の書き込みだけを行い、それ以降のダーティページの書き込みや不要になった WAL の削除はストレージ側で実施されます。

インテリジェントなストレージレイヤーをコンピュートリソースと分離するアプローチにより、以下のようなメリットがあります。

  • コンピュートとストレージを各々にスケールさせることが可能になる
  • ストレージレイヤーでの複数ゾーンの冗長化でゾーン障害からシステムを保護する
  • 更新時に WAL レコードのみストレージレイヤーへ通信してネットワークを効率的に利用できる
  • 低レイテンシなリージョナルログストレージにより高負荷時でも高いパフォーマンスを発揮する
  • リードレプリカ作成時にデータコピーが不要となり高速に作成できる
  • ストレージレイヤーで継続的に WAL を再生するためリカバリ時の WAL 適用が最小になりシステムの再起動が高速になる
  • バックアップ操作がストレージレイヤで処理され、データベースレイヤーのパフォーマンスやリソースへ影響を与えない

AlloyDB のカラム型エンジン

AlloyDB ではカラム型エンジンにより分析クエリを高速に処理できます。カラム型エンジンはインスタンスのメモリ上で行フォーマットデータを AI/ML によって自動的にカラム型フォーマットへ変換します。またインスタンスのメモリや超高速キャッシュと複数レイヤーのキャッシュによって高速なパフォーマンスを発揮します。

各インスタンスのメモリ上でカラム型フォーマットを実現しており、インスタンスごとに設定を変えられるので、インスタンスごとにトランザクションワークロードと分析ワークロードを分けることも可能になります。


AlloyDB for PostgreSQL under the hood: Intelligent, database-aware storage https://cloud.google.com/blog/products/databases/alloydb-for-postgresql-intelligent-scalable-storageより引用

カラム型ストアに追加できるデータには制限があります。

  • 以下の組み込みデータ型の列のみサポートされます。
    • bigint, char, date, decimal, double precision, float4, float8, integer, numeric, real, serial, short, smallint, text, timestamp, toast, varchar
    • サポートされていないデータ型は、手動でカラム型ストアに追加しようとすると無視されます。
  • リーフでないパーティションテーブルは、データソースとしてサポートしていません。
  • 外部テーブルはデータソースとしてサポートされていません。
  • 5,000 行未満のテーブルは、データソースとしてサポートされません。

利用可能なリージョン

2022年5月18日時点で、以下のリージョンで AlloyDB for PostgreSQL を利用できます。

  • asia-northeast1 (東京)
  • asia-south1 (ムンバイ)
  • asia-southeast1 (シンガポール)
  • australia-southeast1 (シドニー)
  • europe-central2 (ワルシャワ)
  • europe-west1 (ベルギー)
  • europe-west2 (ロンドン)
  • europe-west3 (フランクフルト)
  • europe-west4 (オランダ)
  • southamerica-east1 (サンパウロ)
  • us-central1 (アイオワ)
  • us-east4 (北バージニア)
  • us-west3 (ソルトレイクシティ)

料金

AlloyDB の料金は、CPU と メモリの料金、ストレージの料金、ネットワークの料金で構成されます。なお、プレビュー期間中は AlloyDB を無料で利用できます。

AlloyDB for PostgreSQL pricing

CPU と メモリの料金

AlloyDB のコンピューティングリソースは vCPU とメモリ量で料金が設定されます。料金ページには zonal primary instance に言及がありますが、2022年5月18日現在プライマリインスタンスはリージョナルインスタンスのみであるため、プライマリインスタンスは2倍の料金がかかると考えておけばいいと思います。

項目 アイオワ(us-central1) 東京(asia-northeast1)
vCPU 1 vCPU あたり $0.06608 1 vCPU あたり $0.08458
メモリ $0.0112/GB $0.01434/GB

現在選択可能な vCPU とメモリ量でアイオワと東京の月額料金を算出すると以下になります。月額は 730 時間換算です。

マシンタイプ 1ヶ月:アイオワ(us-central1) 1ヶ月:東京(asia-northeast1)
4 vCPU、32 GB $454.5856/月 $581.956/月
8 vCPU、64 GB $909.1712/月 $1163.912/月
16 vCPU、128 GB $1818.3424/月 $2327.824/月
32 vCPU、256 GB $3636.6848/月 $4655.648/月
64 vCPU、512 GB $7273.3696/月 $9311.296/月

ストレージの料金

AlloyDB はクラスタに対してストレージが設定されるため、読み取りプールが増加してもストレージ料金に影響しません。AlloyDB は実際に使用したストレージに対して料金を支払います。

項目 1時間:アイオワ(us-central1) 1ヶ月:アイオワ(us-central1) 1時間:東京(asia-northeast1) 1ヶ月:東京(asia-northeast1)
Regional cluster storage $0.0004109/GB $0.3/GB $0.000526/GB $0.38/GB

ネットワークの料金

Northern America Europe Asia Indonesia Oceania Latin America
Northern America $0.02/GB
Europe $0.05/GB $0.02/GB
Asia $0.08/GB $0.08/GB $0.08/GB
Indonesia $0.10/GB $0.10/GB $0.10/GB N/A
Oceania $0.10/GB $0.10/GB $0.10/GB $0.08/GB $0.08/GB
Latin America $0.14/GB $0.14/GB $0.14/GB $0.14/GB $0.14/GB $0.14/GB

制限

現時点で以下の上限が設定されています。

項目 上限
リージョンごとプロジェクトごとのクラスタ数 3
リージョンごとプロジェクトごとの vCPU 1,100
クラスタごとの読み取りプールノード 20
クラスタごとのデータベースストレージ 500 GB(GA では 64 TB)
インスタンス当たりの最大同時接続数 マシン対応に基づきmax_connectionsを設定

プレビュー中は無料での利用となり公正な使用制限がされており、以下の月間使用制限があります。

  • コンピュート:15,000 USD の使用量、us-central1 における最低 125 vCPU および 1,000 GB の RAM に相当。
  • ストレージ:650 USD の使用量、us-central1 における 2TB 以上に相当

やってみる

AlloyDB クラスタの作成

まずは Cloud Console で AlloyDB for PostgreSQL のメニューを開きます。初めて AlloyDB を触る場合には API の有効化が必要なのでENABLEをクリックして有効化します。また、Compute Engine、Resource Manager、Service Networking の各種 API も無効の場合は有効化しておきます。

上か下のCREATE CLUSTERをクリックして AlloyDB クラスタを作成していきます。

ここではクラスタとプライマリインスタンスだけ作成するのでHighly availableを選択してCONTINUEをクリックします。

以下のように設定値を入力します。Network で選択した VPC がプライベート サービス コネクトを設定していない場合はSET UP CONNECTIONをクリックして設定を行います。

項目 設定値 備考
Cluster ID 任意 ここでは test-cluster-1 を設定
Password 任意
Region asia-northeast1(Tokyo)
Network (任意の VPC)

プライベート サービス コネクトを設定していない場合、以下の設定を行います。ここでは IP アドレスのレンジを自動割り当てにしたので Allocate an IP range でUse an automatically allocated IP rangeを選択して、CONTINUECREATE CONNECTIONとクリックしてプライベート サービス コネクトを作成します。

Instance ID に任意の名称を入力し、マシンタイプを選択してCREATE CLUSTERをクリックします。ここでは最低スペックの4 vCPU, 32 GBを選択しました。必要に応じて Flag を設定します。

クラスタとプライマリインスタンスが起動するまでしばらく待ちます。Cloud SQL 等とは異なり、クラスタというリソースがあることに注意してください。クラスタを作成して、その上に別途プライマリインスタンスや読み取りノードプールを作成していきます。

ログインと基本動作の確認

しばらくするとプライマリインスタンスが作成されるので Private IP を確認します。

psql でログインしてみます。ここではクライアント OS は Debian 10 です。

$ PGPASSWORD=<password> psql -U postgres -h <IP Address>
psql (14.3 (Debian 14.3-1.pgdg100+1), server 14.2)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=>

デフォルト状態での拡張機能を確認すると PL/pgSQL のみが入っています。他に拡張機能が必要な場合はドキュメントに記載のある範囲で導入できます。

postgres=> SELECT * FROM pg_extension;
  oid  | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition 
-------+---------+----------+--------------+----------------+------------+-----------+--------------
 13766 | plpgsql |       10 |           11 | f              | 1.0        |           | 
(1 row)

フェイルオーバーを確認してみます。フェイルオーバーはプライマリインスタンスがスタンバイと切り替わりますが、IP アドレスは変わりません。

以下のようなスクリプトを作成します。

test.sh

#! /bin/bash

while true
do
  (echo 'select now()' | PGPASSWORD=password psql -U postgres -h $1 -t) &
  sleep 0.5
done

引数にプライマリインスタンスの IP アドレスを引数にして実行します。

bash ./test.sh <IP Address>
 2022-05-17 10:27:19.777953+00

 2022-05-17 10:27:20.278747+00

プライマリインスタンスのメニューからFailoverをクリックします。

プライマリインスタンス名を入力してTRIGGER FAILOVERをクリックします。

フェイルオーバー中は以下の様なメッセージが表示されます。

psql: error: connection to server at "10.34.80.2", port 5432 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections?
psql: error: connection to server at "10.34.80.2", port 5432 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections?
psql: error: connection to server at "10.34.80.2", port 5432 failed: FATAL:  the database system is starting up
psql: error: connection to server at "10.34.80.2", port 5432 failed: FATAL:  the database system is starting up

しばらくすると正常にアクセスできるようになります。大半のデータベース障害を自動的に検出し、数秒以内に回復するとなっていますが、プレビューの段階ではもう少し時間がかかるようなので、GA までに高速化されることを期待します。

 2022-05-17 10:34:34.801716+00

 2022-05-17 10:34:35.098082+00

 2022-05-17 10:34:35.313894+00

読み取りプールノードの追加

次に読み取りプールノードを追加してみます。

Cloud Console に戻り、+ ADD READ POOLまたはADD READ POOL INSTANCEをクリックします。

Read pool instance ID は任意ですが、ここでは read1 としています。Node count は冗長性を考えて 2 台にします。マシンタイプは選択できる最小の 4 vCPU, 32 GB を選択しました。これでCREATE READ POOLをクリックします。なお、選択できない 2 vCPU, 16 GB は現在開発中のようです。

読み取りプールノードが作成されるまでしばらく待ちます。

複数ノードを起動しているため read1 の High availability がHighly available (multiple read nodes)となります。そして Private IP を確認します。

読み取りプールノードにログインします。

$ PGPASSWORD=<password> psql -U postgres -h <IP Address>
psql (14.3 (Debian 14.3-1.pgdg100+1), server 14.2)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=>

レプリケーションの情報を確認するためにリカバリ情報関数の内容を確認してみます。読み取りノードプールはストレージレベルでのデータ共有をしつつ、以下のように PostgreSQL 自体のレプリケーション機構も使用していることが分かります。読み取りノードプール側のキャッシュに未反映のデータに対するレプリケーションなどを行っているのではないでしょうか。

postgres=> SELECT pg_is_in_recovery();
 pg_is_in_recovery 
-------------------
 t
(1 row)

postgres=> SELECT pg_last_wal_receive_lsn();
 pg_last_wal_receive_lsn 
-------------------------
 0/5B0048A0
(1 row)

postgres=> SELECT pg_last_wal_replay_lsn();
 pg_last_wal_replay_lsn 
------------------------
 0/5B004950
(1 row)

postgres=> SELECT pg_last_xact_replay_timestamp();
 pg_last_xact_replay_timestamp 
-------------------------------
 2022-05-17 07:23:44.883908+00
(1 row)

カラム型エンジンの設定

カラム型エンジンを設定してみます。

プライマリインスタンスのメニューからEditをクリックします。

Flags のADD FLAGをクリックします。

google_columnar_engine.enabledを選択して値をonに設定して、UPDATE INSTANCEをクリックします。更新するとインスタンスが再起動します。

再起動後にプライマリインスタンスにログインします。検証用データベースを作成して、google_columnar_engine拡張機能を作成します。

postgres=> CREATE DATABASE test1;
CREATE DATABASE
postgres=> \c test1
psql (14.3 (Debian 14.3-1.pgdg100+1), server 14.2)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
You are now connected to database "test1" as user "postgres".
test1=> CREATE EXTENSION IF NOT EXISTS google_columnar_engine;
CREATE EXTENSION

拡張機能の一覧を見てgoogle_columnar_engineが増えていることを確認します。

test1=> SELECT * FROM pg_extension;
  oid  |        extname         | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition 
-------+------------------------+----------+--------------+----------------+------------+-----------+--------------
 13766 | plpgsql                |       10 |           11 | f              | 1.0        |           | 
 24581 | google_columnar_engine |    16390 |         2200 | t              | 1.0        |           | 
(2 rows)

検証用データを作成します。以下のエントリのTPROC-H の実施を参考にサンプルデータを作成してベンチマークも実行します。

diset tpch pg_tpch_dbasetest1を、diset tpch pg_scale_fact10を変更しています。

データを作成したら以下のような行数になりました。

テーブル 行数
customer 1500000
lineitem 60006054
nation 25
orders 15000000
part 2000000
partsupp 8000000
region 5
supplier 100000

分析クエリを実施して統計情報が収集されたのでgoogle_columnar_engine_run_recommendation関数でカラムストアのサイズの推奨設定を確認します。インスタンスのメモリサイズの半分の MB の値を入力します。メモリサイズが 32 GB なので、32 / 2 * 1024 = 16384 となります。実施すると以下のように出力されます。

test1=> SELECT google_columnar_engine_run_recommendation(
test1(>   16384,
test1(>   'PERFORMANCE_OPTIMAL'
test1(> );
                                                                                                                                                                                                                                                                                                                                  google_columnar_engine_run_recommendation                                                                                                                                                                                                                                                                                                                                  
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 (4455,"test1.public.customer(c_acctbal:1,c_address:1,c_comment:1,c_custkey:1,c_mktsegment:1,c_name:1,c_nationkey:1,c_phone:1),test1.public.lineitem(l_commitdate:1,l_discount:1,l_extendedprice:1,l_linestatus:1,l_orderkey:1,l_partkey:1,l_quantity:1,l_receiptdate:1,l_returnflag:1,l_shipdate:1,l_shipinstruct:1,l_shipmode:1,l_suppkey:1,l_tax:1),test1.public.orders(o_comment:1,o_custkey:1,o_orderdate:1,o_orderkey:1,o_orderpriority:1,o_orderstatus:1,o_shippriority:1),test1.public.part(p_brand:1,p_container:1,p_name:1,p_partkey:1,p_size:1,p_type:1),test1.public.partsupp(ps_partkey:1,ps_suppkey:1,ps_supplycost:1),test1.public.supplier(s_address:1,s_name:1,s_nationkey:1,s_suppkey:1)")

プライマリインスタンスでgoogle_columnar_engine.memory_size_in_mbを設定します。プライマリインスタンスのメニューからEditをクリックします。

Flags のADD FLAGをクリックします。

google_columnar_engine.memory_size_in_mbを選択して確認した推奨値の4455に設定してDONEをクリックして、UPDATE INSTANCEをクリックします。更新するとインスタンスが再起動します。

プライマリインスタンスにログインして、カラム型エンジンのレコメンドを手動で適用します。

test1=> SELECT google_columnar_engine_run_recommendation(0, 'FIXED_SIZE', TRUE);

カラム型ストアに追加された項目を確認します。以下のように自動でレコメンドされたものが設定されています。

test1=> SELECT * FROM g_columnar_relations;

 database_name | schema_name | relation_name | status |    size    | uncompressed_size | columnar_unit_count | invalid_block_count | block_count_in_cc | total_block_count | auto_refresh_trigger_count | auto_refresh_failure_count | auto_refresh_recent_status 
---------------+-------------+---------------+--------+------------+-------------------+---------------------+---------------------+-------------------+-------------------+----------------------------+----------------------------+----------------------------
 test1         | public      | partsupp      | Usable |  138477513 |         138477513 |                  45 |                   0 |            183224 |            183224 |                          0 |                          0 | NONE YET
 test1         | public      | supplier      | Usable |    7311932 |           7311932 |                   1 |                   0 |              2264 |              2264 |                          0 |                          0 | NONE YET
 test1         | public      | orders        | Usable | 1312378329 |        1312378329 |                  68 |                   0 |            278516 |            278516 |                          0 |                          0 | NONE YET
 test1         | public      | part          | Usable |  119587441 |         119587441 |                  11 |                   0 |             41919 |             41919 |                          0 |                          0 | NONE YET
 test1         | public      | lineitem      | Usable | 2957139448 |        2957139448 |                 325 |                   0 |           1330412 |           1330412 |                          1 |                          0 | SUCCESS
(5 rows)

カラム型エンジンのレコメンドを、手動ではなく定期的に自動追加するポリシーを作成する場合は以下のクエリを実行します。ここでは 1 時間ごとにレコメンドの適用を実施します。

test1=> SELECT google_columnar_engine_add_policy(
test1(>   'RECOMMEND_AND_POPULATE_COLUMNS',
test1(>   'EVERY', 1, 'HOURS'
test1(> );
 google_columnar_engine_add_policy 
-----------------------------------
 t
(1 row)

カラム型エンジンはメモリ上で動作するものなので、利用する場合はメモリサイズが大きいマシンスペックを選択すべきだと思われます。

クエリ状況の把握やモニタリング

AlloyDB にも Cloud SQL と同様に Query Insights があり、実行されたクエリの状況を把握できます。ここでは HammerDB で実行した TPC-H の派生ベンチマークである TPROC-H のクエリが表示されています。

CPU やメモリ、接続数やトランザクション数などモニタリングも AlloyDB のコンソールから可能です。

バックアップ

バックアップを実施します。

Backups のメニューでCREATE BACKUPをクリックします。

対象のクラスタを選択しバックアップ ID を入力してCREATEをクリックします。

バックアップが作成されます。

バックアップはストレージレイヤーで実施される機能なのですが、なぜかプライマリインスタンスを削除してクラスタだけの状態だとバックアップがエラーとなります。バックアップはストレージレイヤーで完結する機能なので、GA までにクラスタ単体でもバックアップが可能になってほしいです。

さいごに

AlloyDB は高速なトランザクションだけでなく分析クエリも高速に実行できるのが特徴です。分析クエリのためのカラム型エンジンの適用は個別の設定を考える必要がなく、統計情報を自動的に判断してくれるため運用負荷も少なくなっています。現時点ではDatabase Migration Serviceによる Oracle から PostgreSQL への変換はプレビューですが AlloyDB for PostgreSQL へも活用できると思われます。商用データベースからの移行先として重要なサービスになるため、今後も注視していきたいと思います。

また利用規約上ベンチマーク結果を公表できないため、本エントリでは速度や時間について具体的な数字に触れておりません。具体的なパフォーマンスを確認されたい場合は、エントリ内の手順や以下のベンチマーク取得エントリを参考にご自身で確認してみてください。