PostgreSQLを単一ノード以上に拡張する:Citus実践ガイド

Database tutorial - IT technology blog
Database tutorial - IT technology blog

単一ノードのデータベースが限界に達したとき

私は通常、信頼性の高いスキーマが必要なプロジェクトではすぐにPostgreSQLを選択します。PostgreSQLは非常に強力なツールです。しかし、急成長するアプリケーションはいずれ限界に達します。プライマリデータベースが2TBまで膨れ上がったり、月額2,000ドルも払って64コアの巨大なRDSインスタンスを運用しているのに、ピーク時にはCPU使用率が90%に張り付いていたりすることもあるでしょう。

これこそが典型的な垂直スケーリング(スケールアップ)の限界です。より大きなインスタンスに資金を投入し続けることはできますが、費用対効果はすぐに低下します。アプリケーション層での手動シャアリングは一つの解決策ですが、メンテナンスが非常に困難で、ロジックの書き換えも必要になります。Citusはよりスマートな道を提供します。これはPostgreSQLを分散エンジンに変えるオープンソースの拡張機能で、データとクエリの負荷をノードのクラスター全体に分散させます。

私がCitusを好む理由は、それがフォークではないからです。標準的な拡張機能であるため、PostgreSQLを優れたものにしている機能を失うことはありません。JSONBによる半構造化データ、PostGISによる位置情報サービス、全文検索などを利用しながら、数十台のサーバーまでスケールアウトできます。

インストール:数分でクラスターを立ち上げる

分散構成を試す最も効率的な方法は、Docker Composeを使用することです。本番環境ではベアメタルやVMにpostgresql-16-citus-12.1をインストールすることになりますが、Dockerを使えばアーキテクチャを即座に可視化できます。

Citusクラスターは、1つのCoordinator(コーディネーター)と複数のWorker(ワーカー)で構成されます。

Coordinatorはメタデータとクエリのルーティングを管理します。Workerは実際のデータシャードを保存し、重い処理を担います。Coordinatorを指揮者、Workerをオーケストラと考えると分かりやすいでしょう。

セットアップをテストするために、以下のdocker-compose.ymlを保存してください:

version: '3.8'
services:
  coordinator:
    image: citusdata/citus:12.1
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=mypassword
  worker1:
    image: citusdata/citus:12.1
    depends_on: [coordinator]
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=mypassword
  worker2:
    image: citusdata/citus:12.1
    depends_on: [coordinator]
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=mypassword

docker-compose up -dでクラスターを起動します。コンテナが正常に動作したら、Coordinatorに入ってWorkerを登録します。これは標準的なSQLクライアントから実行可能です。

-- コーディネーターがデータの送信先を把握できるようにノードを登録する
SELECT citus_add_node('worker1', 5432);
SELECT citus_add_node('worker2', 5432);

-- クラスターがアクティブであることを確認する
SELECT * FROM citus_get_active_worker_nodes();

アーキテクチャ:データを正しくシャード化する

拡張機能をインストールするだけで魔法のようにパフォーマンスの問題が解決するわけではありません。Citusにデータをどのように分割するかを伝えるための**Distribution Column**(分散列、またはシャードキー)を選択する必要があります。この選択が、負荷がかかったときのクラスターのパフォーマンスを左右します。

例えば、異なる企業の数百万件のイベントを追跡するSaaSプラットフォームを想像してください。この場合、tenant_iduser_idが論理的な選択肢となります。共通のIDでシャード化することで、特定の顧客に関するすべてのデータが同じ物理ノードに保存されるようになります。

1. スキーマの定義

まずはCoordinator上でテーブルを作成します。主キーに関する要件に注意してください。Citusでは、シャードキーがいかなるユニーク制約の一部であることも求められます。

CREATE TABLE users (
    user_id bigserial PRIMARY KEY,
    email text,
    created_at timestamptz DEFAULT now()
);

CREATE TABLE events (
    event_id bigserial,
    user_id bigint,
    event_type text,
    payload jsonb,
    created_at timestamptz DEFAULT now(),
    PRIMARY KEY (user_id, event_id) 
);

2. ワークロードの分散

次に、create_distributed_table関数を使用します。国コードのリストのような小さなルックアップテーブルには、代わりにcreate_reference_tableを使用してください。リファレンステーブルはすべてのWorkerに複製されるため、結合(JOIN)が非常に高速になります。

-- コロケーション(共配置)を有効にするため、両方のテーブルをuser_idでシャード化する
SELECT create_distributed_table('users', 'user_id');
SELECT create_distributed_table('events', 'user_id');

コロケーション(共配置)はここでの秘策です。ユーザーとそのイベントが同じWorkerに存在するため、PostgreSQLはローカルで結合を実行できます。これにより、分散システムにおけるレイテンシの主な原因となる、ネットワーク経由でのギガバイト単位のデータの「シャッフル」を防ぐことができます。

3. 透過的なデータロード

アプリケーションのINSERT文を変更する必要はありません。Coordinatorにデータを送信すると、user_idがハッシュ化され、行が適切なシャードにルーティングされます。50台の異なるサーバーに書き込んでいても、単一のデータベースを操作しているように感じられます。

並列処理:パフォーマンス向上の実感

本当の成果は、巨大なデータセットに対して集計クエリを実行したときに現れます。1つのCPUコアが5億行を処理する代わりに、Citusはクエリを断片に分割し、すべてのWorkerで同時に実行します。

EXPLAIN ANALYZE 
SELECT count(*) FROM events 
WHERE event_type = 'checkout';

出力で「Citus Adaptive Executor」を確認してください。Workerが10台あれば、そのスキャンに対して実質的に10倍のI/O帯域幅と処理能力が得られます。以前は30秒かかっていたクエリが、3秒で終わるかもしれません。

データの偏り(スキュー)への対処

特定の顧客が他よりもはるかに大きく、「ホット」なシャードが発生することがあります。これはcitus_shardsをクエリしてサイズ分布を確認することで監視できます。特定のWorkerが苦戦している場合は、rebalance_table_shards()関数を使用します。これにより、データベースをオンラインに保ったままノード間でデータを移動し、ダウンタイムなしでシステムのバランスを維持できます。

高度なモニタリング

私は常にcitus_stat_statementsを有効にすることをお勧めします。これは、分散クエリのパフォーマンスを追跡する、標準のpg_stat_statementsの拡張版です。クエリが単一ノードにヒットしているか(高速)、クラスター全体へのブロードキャストを必要としているか(低速)を特定するのに役立ちます。

-- 最も重い分散クエリを特定する
SELECT query, calls, total_exec_time 
FROM citus_stat_statements 
ORDER BY total_exec_time DESC 
LIMIT 5;

データベースのスケーリングが「一度設定すれば終わり」になることは稀です。しかし、Citusは手動シャアリングの複雑さを取り除き、トラフィックの増加に合わせてスケールアウトすることを可能にします。PostgreSQLインスタンスが悲鳴を上げ始めているなら、分散モデルへの移行が最も論理的な次の一手です。

Share: