Azure Database for PostgreSQL Hyperscale(Citus)しらべてみた

Azure Database for PostgreSQLには単一サーバとHyperscaleが存在します。HyperscaleのベースはCitusと言う名前のPostgreSQLの拡張機能であり、Citus Data社が開発を行ってきました。2019年1月にMicrosoftがCitus Data社を買収したことにより、Azure Database for PostgreSQLのデプロイオプションとしてAzureマネージドサービス「Hyperscale」が提供されました。

#Azure Database for PostgreSQL -Hyperscale(Citus)のアーキテクチャ

##Azure Database for PostgreSQL -Hyperscale(Citus)とは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/efd00a08-2b93-6f28-e39f-d4a76c32f127.png)

Azure Database for PostgreSQLにはもともとSingle Serverが存在しており、以下のようなデータベースに必要な機能がマネージドで提供されていました。

* 高可用性
* 垂直スケーリング
* セキュリティ機能
* 自動バックアップやポイントインタイムリカバリ

以下にも情報をまとめています。
[OracleからAzure DB（PostgreSQL）へ移行する~⑨初めての人必見！Azure Database for PostgreSQLについて（前編）](https://qiita.com/masahiro-yamaguchi/items/f7081d7984bf5c6d0592)
[OracleからAzure DB（PostgreSQL）へ移行する~⑩初めての人必見！Azure Database for PostgreSQLについて（後編）](https://qiita.com/masahiro-yamaguchi/items/aaff88e13c95667761e4)

これに加えて、Hyperscaleのオプションが加わったことで、シャーディング技術が利用できるようになったのは大きな変化と言えます。

拡張性の高いHyperscaleは、ビックデータ分析や、他にもデータ量が多くスループット要件の高いトランザクションのアプリケーションへの利用が想定されます。

ハイパースケール の大きな特徴である「シャーディングによる水平スケーリング」についてまずは深堀します。

##スケーラブルアーキテクチャ
一般的なデータベースの分散方式は「シェアードエブリシング方式」と「シェアードナッシング方式」となるかと思います。

### シェアードエブリシング方式
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/4f63763e-36dc-f23c-f987-25d168debf13.png)

* リクエスト処理のノード分散はしているが、ディスクは共有している。
* どのノードも全てのデータにアクセスできるため障害児のダウンタイムが短い。
* ディスクアクセスに負荷が集中することでボトルネックになりえることがある。
* Oracle RACでこの方式を採用。

###シェアードナッシング方式
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/f269d209-9387-0728-b921-05b5adc3b05e.png)

* 全てのノードに専用のストレージが割り当てられている。
* ストレージにディスクアクセスの競合が発生しない。
* あるノードに障害が発生した際にディスクのデータが孤立する可能性がある。
* Hyperscaleでこの分散方式を採用

##Hyperscale(Citus)の構成
Hyperscale（Citus）ではシェアードナッシング方式が採用されています。これを実現するため、Hyperscaleには用途の違う２種類のノードが存在します。

* __コーディネーターノード__
クライアントからの接続を受け付ける単一のノードです。クライアントから受け取ったクエリは、適宜ワーカーノードに処理が渡されます。またデータがどのワーカーノードに存在するかを管理しています。

* __ワーカーノード__
コーディネーターノードから受け取ったクエリの結果を返すノードです。クライアントから直接接続することはできず、各ワーカーノード間でも通信されることはありません。一般的なSQL処理のステップである「解析・実行・フェッチ処理」は、全てこのワーカーノード上で行われます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/b9eb1cc7-0d09-0eea-7a04-a6ec9501147f.png)

##クエリ実行時の動作
Hyperscaleではクエリは6つのステップに分かれて実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/95a32cb7-3b28-9eca-d677-008979b08f81.png)

1. コーディネーターノードがクライアントからクエリを受け取る。
2. コーディネーターノードは「Distributed Query Excuters」を使用し、「Distributed Query Planner」を実行する。
3. 「Distributed Query Planner」では受け取ったクエリを、各ワーカーノード内で実行するための分散用クエリに変換する。
「Distributed Query Planner」とは、クエリを効率的に実行するための最適解を計画するプログラムであり、受けとったクエリを各ワーカーノードで実行するためのクエリに変換する処理を行っている。
4. 3 で変換されたクエリは再度「Distributed Query Executors」に返され最適な Executors を使用しワーカーノードにクエリを渡す。
5. クエリを受け取ったワーカーノードは PostgreSQL の Planner 及び Executorを使用しクエリの処理を行い、コーディネーターノードに結果を返す。
6. 各ワーカーノードから結果を受け取ったコーディネーターノードはそれぞれの結果を集計しクライアントに返す。

Hyperscaleでは、分散されたデータに対しクエリを実行し、各ワーカーノードから得られたデータをコーディネーターノードでマージしてクライアントに結果を戻す流れとなります。
利用者があまり意識するところではありませんが、Hyperscaleのクエリはこのような動きとなっています。

#Hyperscale(Citus)におけるテーブルについて
Hyperscaleでは、用途によりいくつかのテーブルの種類が用意されています。
ここでは、それぞれのテーブルの種類に注目して記載します。

##テーブルの種類
Hyperscale(Citus) にてテーブルを作成する際は、テーブルの種類について考慮する必要があります。 Hyperscale(Citus) には3つのテーブル型が存在し、それぞれが別々の目的で使用されます。

各テーブル型に関して、以下のような特徴があります。

* 分散テーブル：テーブルが分割され、各ワーカーノードに配置されます。
* 参照テーブル：テーブルがミラーされ、各ワーカーノードに配置されます。
* ローカルテーブル：通常のテーブルが、コーディネーターノードに配置されます。

分割されたテーブルデータは「シャード」と呼ばれます。
以下の図の通りHyperscaleではテーブルのデータをシャード単位で分割し各ノードに配置されることになり、このシャード単位にクエリを実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/83716353-a305-10c1-cfff-06792aba42bf.png)

##分散テーブル
どのテーブルでも同じですが、Hyperscaleにテーブルを作成しようとすると、まずコーディネーターノードにPostgreSQL標準のcreate table文を実行することでテーブルが作成されます。
分散テーブルは、１つのテーブルデータを複数のノードに分割して配置する方式となり、テーブル作成後にcreate_distributed_table 関数を実行することで、各ワーカーノードにテーブルが分割されて格納されるようになります。
この際、テーブルをいくつのシャードに分割するのか 、 またどれくらい冗長性をもたせるかなどを設定いたします。

テーブルの各行を、複数のワーカーノードに分散させて格納する方式となるため、ファクトテーブルのようなサイズが増え続けるテーブルに適しています。

この分散テーブルのメリットとしては、1つのノードが持つ、テーブルのデータ量を減らすことができるので、参照する際の CPUやメモリ負荷が分散されることがあげられます。
また、これに加えて、例えば、テーブルデータ全件を取得するような場合、各ノードが取得するデータ量が少なくなることからディスクI/Oの負荷も抑えることができるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/1fcf3b17-51bf-474f-4e0d-d71cfdee434a.png)

##参照テーブル
参照テーブルは、テーブルデータを”全て”のワーカーノードにミラー化する方式です。
巨大な分散テーブルと結合させるためのディメンションテーブルなどで利用が想定されます。

テーブルの作成方法としては、分散テーブルと同じようにコーディネーターノードにテーブルを
作成したのち、create_reference_table 関数を実行することで、すべてのワーカーノードに同じデータが格納されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/0f443e19-524b-8c8a-0fc9-fbe2f41ed6c4.png)

##ローカルテーブル
ローカルテーブルは、コーディネーターノードにのみ作成できるテーブルです。

ローカルテーブルは、基本的には分散テーブルや参照テーブルとの結合ができないため、結合に参加しない小規模の管理用テーブルなどに適しています。また、ローカルテーブルをサブクエリで使用することで、分散テーブルや参照テーブルと結合することはできます。
使用例として、アプリケーションのサインインの為の認証用テーブルなどでの使用が考えられます。また、ワーカーノードへどのように処理するかを決定する際のメタデータも格納されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/2bc76e8b-51d1-5f1c-f84a-d199d832777c.png)

##分散テーブルを作成してみる
###①テーブルの分散設定を決める
以下の2つのパラメータにより、ワーカーノードに分散するシャードの数が決定されます。
※ シャードとはワーカーノードに配置されるテーブルの断片の単位

__citus.shard_count__：対象テーブルを分割する数（デフォルト=32)
__citus.shard_replication_factor__：分割したテーブルのレプリケーション数(デフォルト=1)

```SQL
SET citus.shard_count = 6
SET citus.shard_replication_factor = 1
SHOW citus.shard_count
SHOW citus.shard_replication_factor
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/e41695ad-10d3-6378-aed4-8cf77faf6793.png)
shard_count では、対象テーブルを分割する数を指定します。ここでは6と設定していますので、一つのテーブルが6つに分割されるようになります。

shard_replication_factor は、それらのテーブルをいくつにミラーさせるかを指定します。ここでは 2 と設定したので、同じデータが2つずつ存在するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/e566d1e0-a7a0-0e4a-4697-dfb97ddcfd8e.png)


__ワーカーノード全体に存在するシャード数 = citus.shard_count × citus.shard_replication_factor__

###②Create Tableを実行する
Hyperscale(Citus) では Create Table文を実行すると、まずコーディネーターノードにテーブルが作成されます。この時点ではまだワーカーノードにテーブルは配置されていません。

```SQL
CREATE TABLE dist2(c1 INT PRIMARY KEY,c2 VARCHAR(10));
```

###③create_distributed_table 関数を実行する
作成したテーブル情報を用い create_distributed_table 関数を実行することで、各ワーカーノードにテーブルが分散されます。引数には (テーブル名, 分散に使用する列) を指定します。
__また、分散に使用する列には、主キーや一意キー制約が含まれる列を指定する必要があります。__

```SQL
SELECT create_distributed_table('dist2','c1');
```

###④作成したテーブルを確認する
シャードされたテーブルの状態を確認するには、pg_dist_placementとpg_dist_shardというメタデータテーブルを使用することで確認可能です。

```SQL
SELECT
	s.logicalrelid,s.shardid,p.groupid
FROM
	pg_dist_shard s
INNER JOIN
	pg_dist_placement p ON s.shardid=p.shardid
ORDER BY
	1,2;
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/eb8d87f8-0193-dcf7-ad5d-a5be38dfea28.png)

pg_dist_placementテーブルでは、シャード番号を管理する`shardid`列と どのワーカーノードに配置されたかを管理する`groupid` 列を確認することができます。
`shardid`列は、ミラーされているものであれば同じ数字になり、`groupid`列は、各ワーカーノードを表す数値となっています。
上記SQLの結果を図で表すと以下のイメージとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/428730c3-437e-8326-f119-e62a96a4bba3.png)

###⑤テーブルの削除
テーブルの削除はdrop table文にて問題なく削除できます。

```SQL
drop table dist2;
```

##ワーカーノードの追加
新しく追加したノードを利用するには、プロビジョニングを正常に完了した後に、分散テーブルのシャードを再調整する必要があります。これは、既存のノードから新しいノードにいくつかのシャードを移動することを意味します。なお、参照テーブルはノードの追加が完了した時点で、そのノードへテーブルがミラーされている状況になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/e60e49d7-d1f7-e39e-6b0c-35b0d676ffad.png)

```SQL
SELECT rebalance_table_shards('＜テーブル名＞');
```

#Hyperscale(Citus)の使いどころ
Hyperscaleの使いどころと料金体系を記載します。

##ユースケース
###マルチテナントとSaaSのアプリ
SaaSのアプリケーションをHyperscaleベースで構築すれば、複数テナントで多くのノードを共有しながらもテナント毎のデータは分離したまま使うことも可能になり、スケーラビリティやパフォーマンスに加えてセキュリティも担保することが可能となります。これを実現しているのがHyperscaleの「シャーディング技術」です。
Hyperscaleでは、1つの「コーディネーター」ノードの下に、複数の「ワーカーノード」が配置され、「シャードキー」に応じてデータが複数のワーカーノードに分散し格納されます。

このクラスタがアプリケーション側からは、単一のPostgreSQLとして見えています。クエリは、コーディネーターノードにより、対象のデータがあるワーカーノードに自動で振り分けられ分散処理されます。また、ワーカーノードを増やす際には、データベースを停止する必要はないので、ワーカーノードはユーザーの要求に応じて、増やすことも減らすことも自由に実行することができます。

* テナントを分散してホットスポットを最小化
* オンラインでリバランスすることが可能
* 大量のテナントをハードから独立

###リアルタイム運用分析
日々データが増え続け、大規模なクエリ要求があるようなシステムに、Hyperscaleは適しています。
Hyperscaleは、リアルタイムオンライントランザクションで、PBクラスのデータ容量のRDBに最適になっています。

* 数テラバイト/日のデータを投入
* 1秒未満のクエリレスポンス
* ノードを並列化し高速化を実現
* 複雑なETL処理を単純化

###高スループットのトランザクション/OLTPアプリ
拡張性の高いハイパースケールは、ビッグデータ分析などで利用することも可能です。他にもデータ量が多くスループット要件の高いトランザクションのアプリケーションにもが想定されます。

* 多数の同時ユーザ数でも高性能を維持
* 複数のノードにトランザクション処理を分散
* 大量のトランザクションを管理

##構成オプション
選択可能な仮想コア数は、コーディネーターノード、ワーカーノードともに同じですが、メモリはワーカーノードで最大432GBまで選択することが可能です。
またワーカーノードは最大20個まで作成することが可能です。ただし、それ以上作成する場合はサポートにリクエストする必要があり、データセンターのリソースに空きさえあれば、最大100個まで作成することが可能となっております。この100という上限はリージョンによる違いはありません。
ストレージですが、汎用SSDで500GB、1TB、2TBと３つのサイズから選択する必要があります。また、ディスク性能は、1GB＝3 IOPSとなっているで、仮に1TBのディスクを選択した場合は、30,000 IOPSとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/d5f4158d-702f-d877-d06e-79bbd852f2a8.png)


* Hyperscale(Citus)サーバグループ内のコーディネーター、およびワーカーノードのコンピューティングおよびストレージ設定を別々に選択ができる。
* ストレージには、データベースファイル、一時ファイル、トランザクションログ、およびPostgreSQLサーバーログが格納される。
* プロビジョニングするストレージサイズの合計容量によって、コーディネーター、およびワーカーノードそれぞれに利用できるI/O容量も決まる。

##料金体系
Hyperscaleの料金体系は最低限Hyperscaleを構成するために必要な「コーディネーターノード」と「ワーカーノード」には料金が発生いたします。またそれぞれにコンピューティング、ストレージの料金と内訳が分かれます。

例として以下のような構成を考えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/b3f0abb0-c63a-340b-5342-2124d7d843c0.png)
上記の構成の場合は、Hyperscaleの1ヶ月当たりの料金は以下の通りとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697647/5f6093fc-15b9-d65d-9af3-0f9309f2b3c2.png)

#最後に
Hyperscale（Citus）はPostgreSQLでアプリケーションから透過的に分散処理ができるので、大規模なシステムでの利用に適していますが、やや分散テーブルなどの設計などに考慮が必要そうです。