詳解 PostgreSQL[10/11対応]―現場で役立つ新機能と実践知識

第4章 PostgreSQL の運用に便利な機能―バックアップ,レプリケーション,パーティション,バージョンアップ

この記事を読むのに必要な時間:およそ 8 分

レプリケーション─⁠─データベースのリアルタイム複製

PostgreSQLはバージョンアップのたびに進化しています。その中でもRDBMSの重要な機能であるレプリケーションは,力を入れて強化されています。

レプリケーションの役割

レプリケーションは,DBをリアルタイムに複製してデータを同期するしくみです。プライマリからスタンバイにWALをもとにデータを連携して複製します。スタンバイは参照することが可能なため,可用性向上や参照の負荷分散などの目的で利用されます。プライマリに障害が発生した場合は,サービスを存続させるためにフェイルオーバーしてスタンバイをプライマリに切り替えます。

昨今ではクラウドサービスを利用すると,気軽にレプリケーションを利用できます。気軽に使えるため,ちゃんと調べずに使っている人も多く見受けられます。レプリケーションは有能な機能ですが制約も多く,問題が発生したときにトラブルシューティングのハードルは高くなりがちです。そこでレプリケーションについてしっかりと学び,レプリケーションのメリットを使い切りましょう。

PostgreSQLではレプリケーションとして,ストリーミングレプリケーションとロジカルレプリケーションの2つをサポートしています。以降で順に見ていきましょう。

ストリーミングレプリケーション─⁠─堅牢な複製

PostgreSQLのレプリケーションとしての主流は,ストリーミングレプリケーションです。

ストリーミングレプリケーションは図1のとおり,更新履歴であるWALを利用してレプリケーションします。PostgreSQLのストリーミングレプリケーションの特徴は,スタンバイはリードオンリーのため,書き込みできません。これにより,プライマリとの差分発生を防ぎ,安心してレプリケーションを運用できます。

図1 ストリーミングレプリケーションのしくみ

図1 ストリーミングレプリケーションのしくみ

ストリーミングレプリケーションは,プライマリとスタンバイでPostgreSQLのマイナーバージョンが違ってもレプリケーションできます。しかし,メジャーバージョンが違うとレプリケーションできません。

ロジカルレプリケーション─⁠─自由度の高い複製

PostgreSQLのもう一つのレプリケーションが,ロジカルレプリケーションです。PostgreSQL 10から追加されました。ストリーミングレプリケーションと同様にWALを利用してレプリケーションを行いますが,次の違いがあります。

  • PostgreSQLのメジャーバージョン違いでのレプリケーションが行える
  • テーブル単位でのレプリケーション
  • スタンバイへの書き込み
  • スタンバイ側へのインデックスやトリガの設定
  • 複数のプライマリから1つのスタンバイへ集約

自由度の高さを活かして,いろいろなシーンで利用できます。たとえばAサービスのDBとBサービスのDBで分かれている課金ログを1つのDBにまとめてから集計したり,強大なDBの一部のテーブルのみを取り出してレプリケーションすることなどができます。ただし,デメリットとして,柔軟だからこそデータが壊れやすいという課題があります。

手堅いストリーミングレプリケーションをメインに利用し,ストリーミングレプリケーションではできないことをロジカルレプリケーションでサポートするなど適切に使い分けることで,設計の幅が広がります。

レプリケーションを利用したスタンバイの構成

PostgreSQLでは図2のとおり,ストリーミングレプリケーションやロジカルレプリケーションを利用していろいろなスタンバイの構成を作ることができます。

図1 レプリケーションを利用したスタンバイの構成

図1 レプリケーションを利用したスタンバイの構成

カスケードレプリケーションは,スタンバイからさらにレプリケーションをして孫スタンバイを作ることで,プライマリの負荷を減らしながらスタンバイを増やせます。

マルチレプリケーションは,プライマリの負担が増えるものの,直接レプリケーションしているスタンバイの数を増やすことで,ホットスタンバイを複数台用意することなどができます。

パーティション─⁠─テーブルの水平分割

レプリケーションと同じく,パーティションはRDBMS にとって重要な機能です。しかし,PostgreSQLはバージョン10になるまでパーティション機能がありませんでした。そのため,トリガとPostgreSQLの機能である継承を使って擬似パーティションを作成していましたが,性能や構築の難易度が高く,ほかのRDBMSよりも劣っているというのが実情でした。

本節では,待望の機能であるパーティションを紹介していきます。

パーティションの役割

パーティションは,図3のように,1つのテーブルを複数に分割する機能です。大規模なデータを1つのテーブルに保存しているケースで有用です。アプリケーションからは1つのテーブルに見えるため,分割されていることを意識する必要はありません。テーブルが分割されることで,1つのパーティションに収まったクエリを処理する場合は全体量が小さくなるため,巨大な1つのテーブルを処理するよりも高速になります。

図3 パーティションのしくみ

図3 パーティションのしくみ

たとえば月次で集計するユーザーの課金履歴があったとします。サービス開始当初は1つのテーブルでまったく問題がなかったとしても,データ量が増えてくると集計SQLで取り出す処理がどんどん重くなっていきます。このような場合,図4のようにパーティションを利用すると,集計対象のデータが月次で分かれるので不要なデータを検索しません。そのため,集計SQLの性能が劇的に改善します。

図4 パーティションの活用例

図4 パーティションの活用例

パーティションの使い方

続いて,パーティションの実際の使い方です。

魅力いっぱいのPostgreSQLのパーティションを利用するためには,図5のとおり,DDLでテーブルを作成する必要があります。作成したテーブルにINSERTを実行すると,分割したテーブルに自動的に振り分けられていることがわかります。たとえば都道府県名やuser_idなどの任意の条件に合わせて,リストや範囲でパーティションできます。これによって,うまく設計すれば更新や参照の負荷を分散できます。

図5 リストパーティションの例

親テーブルの作成
demo=# CREATE TABLE public."販売履歴" (
  "商品名" character varying(64) NOT NULL,
  "価格" numeric NOT NULL DEFAULT 0,
  "売上日時" timestamp NOT NULL DEFAULT now(),
  "売上月" character varying(6) NOT NULL
) PARTITION BY LIST ("売上月");

文字列指定の場合のパーティション
demo=# CREATE TABLE "2018年10月" PARTITION OF "販売履歴"
  FOR VALUES IN ('201810');
demo=# CREATE TABLE "2018年9月" PARTITION OF "販売履歴"
  FOR VALUES IN ('20189');
demo=# CREATE TABLE "2018年8月" PARTITION OF "販売履歴"
  FOR VALUES IN ('20188');
demo=# CREATE TABLE "2018年7月" PARTITION OF "販売履歴"
  FOR VALUES IN ('20187');
demo=# CREATE TABLE "2018年6月" PARTITION OF "販売履歴"
  FOR VALUES IN ('20186');

データの投入
demo=# INSERT INTO "販売履歴"
  ("売上日時", "商品名", "価格", "売上月") VALUES (
  '2018-10-21 21:12:00'
  , 'WEB+DB PRESS Vol.107', 1480, '201810'
);

データの確認
demo=# SELECT "商品名","売上月" FROM "販売履歴";
        商品名        | 売上月
----------------------+--------
 WEB+DB PRESS Vol.107 | 201810
(1 row)

demo=# SELECT "商品名","売上月" FROM "2018年10月";
        商品名        | 売上月
----------------------+--------
 WEB+DB PRESS Vol.107 | 201810
(1 row)

demo=# SELECT "商品名","売上月" FROM "2018年9月";
 商品名 | 売上月
--------+--------
(0 rows)

範囲指定の場合のパーティション
demo=# CREATE TABLE public."販売履歴2" (
  "商品名" character varying(64) NOT NULL,
  "価格" numeric NOT NULL DEFAULT 0,
  "売上日時" timestamp NOT NULL DEFAULT now(),
) PARTITION BY RANGE ("売上日時");
demo=# CREATE TABLE "2018年10月RANGE"
  PARTITION OF "販売履歴2"
  FOR VALUES FROM ('2018-10-01 00:00:00')
  TO ('2018-11-1 00:00:00 ');
demo=# INSERT INTO "販売履歴2"
  ("売上日時", "商品名", "価格") VALUES (
  '2018-10-21 21:12:00','WEB+DB PRESS Vol.107',1480
);

確認
demo=# SELECT * FROM "2018年10月RANGE";
        商品名        | 価格 | 売上日時
----------------------+------+---------------------
 WEB+DB PRESS Vol.107 | 1480 | 2018-10-21 21:12:00
(1 row)

ハッシュパーティション─⁠─バランス良く分割する

PostgreSQL 10で追加されたパーティションは,PostgreSQL 11でさらに進化しています。PostgreSQL 11での目玉はハッシュパーティションです。リストや範囲との違いは,図6のように,分割する条件をハッシュにできます。そのため,INSERTごとにラウンドロビンのように分散されていきます。

図6 ハッシュパーティションのしくみ

図6 ハッシュパーティションのしくみ

これにより,更新が多いテーブルやユニークなIDでの参照が多いテーブルでも負荷を分散できますし,IDで取得するようなケースでも参照が分散される効果が期待できます。これは,ハッシュパーティションがなく,シャーディングで水平分割しがちだったuserテーブルなどで効果的な機能です。

そのほかの改善点

PostgreSQL 11のパーティションは,ほかにも紹介しきれないほど多くの改善があります。以下で主要な変更点のみ列挙します。

  • パースツリーの作成などクエリを実行するための前準備処理中でもパーティションをすばやく削除できる
  • クエリをエグゼキュータが実行中でもパーティションの削除を許可する
  • パーティションのキーがUPDATE文で変更された行は,更新された内容に基づいて,自動的に対象のパーティションに移動させる
  • パーティション化されたテーブルに,デフォルトのパーティションを指定する
  • パーティションテーブルからの外部キーを許可する・パーティションテーブルに対してINSERT ON CONFLICT文を実行する
  • パーティションキーが一意性を保証する場合は,パーティションテーブルの一意制約を許可する

毎年メジャーバージョンアップするPostgreSQLですが,パーティションだけを見ても,このように新バージョンになればなるほど改善されていくことがわかると思います。

著者プロフィール

曽根壮大(そねたけとも)

株式会社オミカレ副社長兼CTO。数々の業務システム,Webサービスなどの開発・運用を担当し,2017年に株式会社はてなでサービス監視サービス「Mackerel」のCRE(Customer Reliability Engineer)を経て現職。 コミュニティでは,Microsoft MVPをはじめ,日本PostgreSQLユーザ会の理事として勉強会の開催を担当し,各地で登壇している。 builderscon 2017,YAPC::Kansaiなどのイベントでベストスピーカーを受賞し,分かりやすく実践的な内容のトークに定評がある。 他に,岡山Python勉強会を主催し,オープンラボ備後にも所属。著書に『Software Design』誌で,データベースに関する連載「RDBアンチパターン」をまとめた『失敗から学ぶRDBの正しい歩き方』を執筆。

@soudai1025
はてなid:Soudai

著書