公式ドキュメントにパーティショニングとカラムナーストレージのことは書いてあるのだけれど、実データを入れてテーブルがどのように見えるかについて、分散テーブルも含めてまとめて説明したものはないように思うので、書いておく。
テーブルの種類
パーティションとカラムナーストレージを正しく理解するには、Cosmos DB for PostgreSQLのテーブルの種類についてまず理解している必要がある。
Local Table
Cosmos DB for PostgreSQLのCoordinatorノードにpsqlで接続して、例えば以下のようにテーブルを作成した場合、これらのテーブルはCoordinatorノードに作成されている。
CREATE TABLE github_events(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
user_id bigint,
org jsonb,
created_at timestamp
);
CREATE TABLE github_users(
user_id bigint,
url text,
login text,
avatar_url text,
gravatar_id text,
display_login text
);
確認してみよう。まずCoordinator。
$ psql "host=c-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.com port=5432 dbname=citus user=citus sslmode=require"
citus=> \dt
List of relations
Schema | Name | Type | Owner
--------+---------------+-------+-------
public | github_events | table | citus
public | github_users | table | citus
(2 rows)
citus=> SELECT * FROM citus_tables;
table_name | citus_table_type | distribution_column | colocation_id | table_size | shard_count | table_owner | access_method
------------+------------------+---------------------+---------------+------------+-------------+-------------+---------------
(0 rows)
念のため、Workerノードに接続して見てみる。
$ psql "host=w0-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.com port=5432 dbname=citus user=citus sslmode=require"
citus=> \dt
Did not find any relations.
Cosmos DB for PostgreSQLで、Local Tableを用いることはほとんど無い。Local Tableは、この後に説明するDistributed / Reference Tableのメタ情報を管理するために主に用いられ、それらのメタ情報を直接編集することもほとんどない。ただし、viewとして見ることは無きにしも非ず。
Distributed Table
Distributed Tableを作成するには、Local Tableを作成後、create_distributed_table()を使う。上のテーブルの例であれば、以下の通り。
SELECT create_distributed_table('github_events', 'user_id');
SELECT create_distributed_table('github_users', 'user_id');
Coordinatorノードで見ると、\dtの結果は変わらないが、
citus=> \dt
List of relations
Schema | Name | Type | Owner
--------+---------------+-------+-------
public | github_events | table | citus
public | github_users | table | citus
(2 rows)
citus_tablesを見ると、Distributed Tableになっていることが分かる。
citus=> SELECT * FROM citus_tables;
table_name | citus_table_type | distribution_column | colocation_id | table_size | shard_count | table_owner | access_method
---------------+------------------+---------------------+---------------+------------+-------------+-------------+---------------
github_events | distributed | user_id | 5 | 256 kB | 32 | citus | heap
github_users | distributed | user_id | 5 | 256 kB | 32 | citus | heap
(2 rows)
そして、pg_dist_shardから、シャードキーのハッシュ値をシャードに割り当てるための範囲が32(citus.shard_countのデフォルト値)に分割されていることが分かり、
citus=> SELECT * FROM pg_dist_shard;
logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue
---------------+---------+--------------+---------------+---------------
github_events | 102297 | t | -2147483648 | -2013265921
github_events | 102298 | t | -2013265920 | -1879048193
github_events | 102299 | t | -1879048192 | -1744830465
github_events | 102300 | t | -1744830464 | -1610612737
......
さらに、citus_shardsから、どのシャードが、どのWorkerノードに割り当てられているかが分かる。
citus=> SELECT * FROM citus_shards;
table_name | shardid | shard_name | citus_table_type | colocation_id | nodename
| nodeport | shard_size
---------------+---------+----------------------+------------------+---------------+--------------------------------------------------------
----+----------+------------
github_events | 102297 | github_events_102297 | distributed | 4 | private-w0-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 8192
github_events | 102298 | github_events_102298 | distributed | 4 | private-w1-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 8192
github_events | 102299 | github_events_102299 | distributed | 4 | private-w2-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 8192
......
つまり、「シャードカラムの値のハッシュ値が-2147483648から-2013265921の範囲の行は、102297というIDのシャードに格納され、そのシャードは、private-w0というWorkerノードに”github_events_102297″というテーブルとして割り当てられた」ことが分かる。
Reference Table
次に全てのWorkerノードに同一内容が複製されるReference Tableを作る。Local Tableを作成後、create_reference_table()を実行すれば良い。
CREATE TABLE geo_ips (
addrs cidr NOT NULL PRIMARY KEY,
latlon point NOT NULL
CHECK (-90 <= latlon[0] AND latlon[0] <= 90 AND
-180 <= latlon[1] AND latlon[1] <= 180)
);
SELECT create_reference_table('geo_ips');
citus_tablesを見てみる。
citus=> SELECT * FROM citus_tables;
table_name | citus_table_type | distribution_column | colocation_id | table_size | shard_count | table_owner | access_method
---------------+------------------+---------------------+---------------+------------+-------------+-------------+---------------
geo_ips | reference | <none> | 6 | 80 kB | 1 | citus | heap
github_events | distributed | user_id | 5 | 256 kB | 32 | citus | heap
github_users | distributed | user_id | 5 | 256 kB | 32 | citus | heap
(3 rows)
geo_ipsというReference Tableが追加されたことが分かる。さらに、pg_dist_shardとcitus_shardsを見る。
citus=> SELECT * FROM pg_dist_shard;
logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue
---------------+---------+--------------+---------------+---------------
......
geo_ips | 102425 | t | |
(65 rows)
citus=> SELECT * FROM citus_shards;
table_name | shardid | shard_name | citus_table_type | colocation_id | nodename
| nodeport | shard_size
---------------+---------+----------------------+------------------+---------------+--------------------------------------------------------
----+----------+------------
geo_ips | 102425 | geo_ips_102425 | reference | 6 | private-c-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.c
om | 5432 | 16384
geo_ips | 102425 | geo_ips_102425 | reference | 6 | private-w1-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 16384
geo_ips | 102425 | geo_ips_102425 | reference | 6 | private-w2-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 16384
geo_ips | 102425 | geo_ips_102425 | reference | 6 | private-w3-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 16384
geo_ips | 102425 | geo_ips_102425 | reference | 6 | private-w0-riohol.a35bbmlly7xdvy.postgres.cosmos.azure.
com | 5432 | 16384
つまり、Reference Tableは、「シャードキーの指定が無い=ハッシュ値の計算はされないがシャードIDが割り振られ、全てのWorkerノードにシャードが置かれる=複製されるDistributed Table」であることが分かる。
パーティションを作成する
パーティションの作成方法は、通常のPostgreSQLと全く同様に可能だ。
CREATE TABLE github_events(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
user_id bigint,
org jsonb,
created_at timestamp
) PARTITION BY RANGE (created_at);
次に、Distributed Tableにすれば良い。
SELECT create_distributed_table('github_events', 'repo_id');
ただし、これだけでは実際にはパーティションは作成されていない。あくまで、パーティションキーが定義されただけなので、以下のようにパーティションテーブルを追加する。
CREATE TABLE github_events_201902 PARTITION OF github_events
FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');
citus_shardsを見てみよう。
citus=> SELECT * FROM citus_shards;
table_name | shardid | shard_name | citus_table_type | colocation_id | nodename
| nodeport | shard_size
----------------------+---------+-----------------------------+------------------+---------------+------------------------------------------
------------------+----------+------------
github_events | 102426 | github_events_102426 | distributed | 7 | private-w0-riohol.a35bbmlly7xdvy.postgres
.cosmos.azure.com | 5432 | 0
......
github_events_201902 | 102458 | github_events_201902_102458 | distributed | 7 | private-w0-riohol.a35bbmlly7xdvy.postgres
.cosmos.azure.com | 5432 | 8192
......
github_eventsのパーティションとして、github_events_201902が作成され、そのシャード、github_events_201902_102458が作成されたことが分かる。
しかしこの方法には大きな問題がある。つまりパーティションを手動で作成しないとならない。
そこで、Cosmos for PostgreSQLではcreate_time_partition()が用意されている。つまり、
CREATE TABLE github_events(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
user_id bigint,
org jsonb,
created_at timestamp
) PARTITION BY RANGE (created_at);
SELECT create_distributed_table('github_events', 'repo_id');
SELECT create_time_partitions(
table_name := 'github_events',
partition_interval := '1 month',
end_at := now() + '12 months'
);
とすれば、パーティションを作成する期間を指定して、一気に作成することが可能だ。
citus=> SELECT partition
FROM time_partitions
WHERE parent_table = 'github_events'::regclass;
partition
------------------------
github_events_p2023_11
github_events_p2023_12
github_events_p2024_01
github_events_p2024_02
github_events_p2024_03
github_events_p2024_04
github_events_p2024_05
github_events_p2024_06
github_events_p2024_07
github_events_p2024_08
github_events_p2024_09
github_events_p2024_10
github_events_p2024_11
(13 rows)
ここで、もう一度、citus_shardsを見てみよう。
citus=> select * from citus_shards;
table_name | shardid | shard_name | citus_table_type | colocation_id | nodename
| nodeport | shard_size
------------------------+---------+-------------------------------+------------------+---------------+--------------------------------------
----------------------+----------+------------
github_events | 102522 | github_events_102522 | distributed | 9 | private-w0-riohol.a35bbmlly7xdvy.post
gres.cosmos.azure.com | 5432 | 0
......
github_events_p2023_11 | 102554 | github_events_p2023_11_102554 | distributed | 9 | private-w0-riohol.a35bbmlly7xdvy.post
gres.cosmos.azure.com | 5432 | 8192
......
つまり「各パーティションがDistributed Tableになっている」ことが分かる。
IoTのシナリオなどで、データをCosmos DB for PostgreSQLにストアする場合、このパーティションは非常に重要で、仮に上記のように月ごとに分割し、データが一定の時間間隔(例:毎秒)で必ず生成され取りこぼしが無い場合、各パーティションは最小28日、最大31日分をストアすることになり、データサイズがほぼ一定=インデックスサイズもほぼ一定=レスポンスが一定に保つことが期待できる(RDBMSなので確約はできない)。Distributed Tableにもなっているので、シャーディングの設計によっては、あるパーティションのシャードは、別のパーティションのシャードと同等のボリュームであることも期待できる。
カラムナーストレージ
執筆時点(2023年11月)のCosmos DB for PostgreSQLで利用可能なCitusはバージョン12.1なのだけれど、Citus 10からカラムナーストレージが利用可能になっている。要は列方向にデータを保持し圧縮(zstdが用いられている)する機能がサポートされている。上記のパーティションのうち、2023年11月のパーティションをカラムナーにしてみる。
CALL alter_old_partitions_set_access_method(
'github_events',
'2023-12-01 00:00:00' /* older_than */,
'columnar'
);
time_partitionsを確認してみよう。
citus=> SELECT partition, access_method
FROM time_partitions
WHERE parent_table = 'github_events'::regclass;
partition | access_method
------------------------+---------------
github_events_p2023_11 | columnar
github_events_p2023_12 | heap
github_events_p2024_01 | heap
......
github_events_p2024_10 | heap
github_events_p2024_11 | heap
(13 rows)
新しいパーティションは通常のheapのままだが、日時の指定によって古いパーティションがcolumnarになっていることが分かる。
もちろん、テーブルの作成時にカラムナーにしてしまうことも可能。
citus=> CREATE TABLE github_events(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
user_id bigint,
org jsonb,
created_at timestamp
) USING COLUMNAR;
データの中身によるが8分の1から40分の1程度の圧縮率が報告されている。
Distributed + Partitioning + Columnar Storageで、PostgreSQLをスケールアウトし、性能を一定に保ちつつ、ストレージの節約までを実現可能なことが理解されると幸いだ。