版 | 改訂日 | 変更内容 |
---|---|---|
1.0 | 2017/06/20 | 2016年度版として新規作成
- PostgreSQL 9.6 を対象バージョンとする
|
2.0 | 2018/04/11 | 2017年度版として主に以下の章を加筆修正
- 5章 ストリーミングレプリケーション
PostgreSQL 10 に対応
複数スレーブ対応 (1.0版では2ノードに限定していた)
- 6章 ロジカルレプリケーションの追加
PostgreSQL 10 の新機能として追加
|
本作品はCC-BYライセンスによって許諾されています。 ライセンスの内容を知りたい方は こちら でご確認ください。 文書の内容、表記に関する誤り、ご要望、感想等につきましては、PGEConsのサイト を通じてお寄せいただきますようお願いいたします。
PostgreSQLエンタープライズコンソーシアム(略称 PGECons)は、PostgreSQL本体および各種ツールの情報収集と提供、整備などの活動を通じて、ミッションクリティカル性の高いエンタープライズ領域へのPostgreSQLの普及を推進することを目的として設立された団体です。
PGECons 技術部会ではPostgreSQLの普及に資する課題を活動テーマとし、3つのワーキンググループで具体的な活動を行っています。
これら3つのワーキンググループのうち、WG1、WG3については 2015 年度まではそれぞれ、「性能ワーキンググループ」、「設計運用ワーキンググループ」という名称で活動してきました。2016年度は、従来の活動領域を広げる意図のもとでそれらを再定義し、上記のような名称に改めました。
これに伴い、WG3ではPostgreSQLの設計運用を中心としたさまざまな課題の解決のための調査検証を行い、PostgreSQLが広く活用される事を推進していくこととしました。
本資料はWG3の2017年度の活動としてPostgreSQLにおけるレプリケーション機能について、調査検討した結果をまとめたものです。
これまでにも、WG3では3度にわたってレプリケーションについて取り上げています。
2013年では、PostgreSQLをエンタープライズ領域で活用するにあたって、業務要件とコストとのバランスを考えて可能な限り業務を継続できるように、システムの運用や保守サービスをPostgreSQLで実現する手法についてまとめました [PGECons_WG3_2013] 。報告書では、可用性を向上させるための手段としてレプリケーションを取り上げています。今年度の報告書では取り上げなかった、ストレージレプリケーション(DRBD)、トリガベースレプリケーション(Slony-I)についても紹介しています。
2014年度では、可用性のうち「災害対策」に焦点を当て、ITサービス継続を可能とする PostgreSQL の構成について調査検討しています [PGECons_WG3_2014] 。ストリーミングレプリケーションを含む代表的な PostgreSQL のシステム構成を挙げて、データベースのデータ保全性、サービス継続性の観点から各構成の得失を示しました。
2016年度では、ストリーミングレプリケーションの2ノードにおける運用ノウハウの整理や PostgreSQL をベースとして双方向レプリケーションがリリースされています。
これらの開発動向と過去の検討結果とを踏まえて、2017年度は PostgreSQL 10にて機能拡充が進むストリーミングレプリケーションの3ノードにおける運用ノウハウの整理と、新機能のロジカルレプリケーションの調査を実施しました。
本資料の読者は以下のような知識を有していることを想定しています。
[PGECons_WG3_2013] | PostgreSQL Enterprise Consortium. 2013年度WG3活動成果報告書. 2014. https://www.pgecons.org/wp-content/uploads/PGECons/2013/WG3/pgecons-wg3-2013-report.pdf |
[PGECons_WG3_2014] | PostgreSQL Enterprise Consortium. 2014年度WG3活動成果報告書 (可用性編). 2014. https://www.pgecons.org/wp-content/uploads/PGECons/2014/WG3/PGECons_2014_WG3_Availability.pdf |
[PostgresBDR] | 2ndQuadrant. Postgres-BDR. Version Postgres-BDR94 1.0.2, November 14, 2016. https://2ndquadrant.com/en-us/resources/bdr/ |
データベースにおいて、レプリケーションとは複数のデータベースサーバの間で、 何らかの一貫性を保ちながら、その内容を複製する手法を指します。PostgreSQLにおいては、 9.0 以降の各バージョンの基本機能として、レプリケーションが実現されています。 また、PostgreSQLに付加するツールによってもレプリケーションが実現されています。 この章では、各種のレプリケーション機能を目的に応じて使い分けるために、機能と特性について簡単に紹介します。
レプリケーションによって複数のデータベースサーバの複製を作ることによって、シングルサーバでは対応が難しい要件にも対応できるようになります。それらは、データの冗長性(複製があること)と複数サーバによる処理の分散の結果です。
可用性はITシステムの非機能要件の一つで、システムを継続的に利用可能とすることです [IPA] 。 可用性は「継続性」「耐障害性」「災害対策」「回復性」という4つの要素から構成されますが、レプリケーションによってデータベースサーバを冗長化することで、1つのサーバで故障や災害が生じたときにもレプリカが格納されている残りのサーバでサービスを継続することができるるようになります。また、運用上の停止が必要な場合であっても、各サーバを順次停止して作業することで、サービス全体としては停止させないようにできます。
先に挙げた可用性の4つの要素をどの程度満足するかはデータベースの構成によって変わってきます。詳しくは『2013年度WG3活動成果報告書』 [PGECons_WG3_2013] を参考にしてください。
レプリケーションによって、同じ情報を格納しているデータベースサーバが複数存在することになります。それらのサーバでアプリケーションからの要求にこたえることが出来れば、システム全体としての性能状況が期待できます(スケールアウト)。アプリケーションからの要求を複数のサーバに分散させる際には、更新(削除・挿入を含む)クエリを特定の1サーバに集約する「シングルマスタ」構成と、複数のサーバに分散する「マルチマスタ」構成があります。また、参照クエリを複数のサーバに分散させることを参照負荷分散と呼びます。
レプリケーションクラスタを性能向上に用いる場合、複数のデータをベースを同時に運用することから生じる特有の課題があります。レプリケーション方式を選択する際には、それらの課題をどの程度解決しているのかについても考慮する必要があります。
ある瞬間に同一の参照クエリを異なるサーバに送った時に、まったく同じ結果が返ってくるものと、そうでないものとがあります。同じ結果が得られる場合、サーバは同期している、同期レプリケーションであると言います。
マルチマスタ構成の場合には同期の問題に加えて、更新の衝突と一貫性の維持が問題となります。
ここでは、本報告書で取り上げるレプリケーション手法を中心に、PostgreSQL で利用できる代表的なレプリケーション手法を紹介します。レプリケーションを利用する立場からは、シングルマスタとマルチマスタに二分することができます。その上で、レプリカを生成する方法に着目して代表例を挙げ、そのメリット・デメリットを説明します。
なお、『2013年度WG3活動成果報告書』 [PGECons_WG3_2013] では可用性向上の観点から、レプリケーションを含めて様々な PostgreSQL の構成を取り上げていますので、併せてご覧ください。
コミュニティのWikiページには、PostgreSQL上で動作するクラスタソフトウェアについての解説があり、その中にレプリケーションも含まれています [PGWiki_replica] 。ここで紹介する紹介するレプリケーションソフトウェアについても紹介されています。
シングルマスタ構成の場合、レプリカを生成する手法には以下のようなものがあります。
- ストレージレプリケーション
- トリガベースレプリケーション
- クエリベースレプリケーション
- ストリーミングレプリケーション
- ロジカルレプリケーション
PostgreSQLやその上で動作するツールを介することなく、データを格納するストレージのレベルでデータを複製します。ストレージ装置自体がレプリカを生成するものや、DRBD [DRBD] のようにLinux上で動作するソフトウェアによる実現があります。
PostgreSQLのデータベース内に更新によって起動されるトリガを設定しておき、更新による変分を受信側のサーバに送り出すもの。代表的な製品に Slony-I があります。以下では Slony-I での主なメリット・デメリットを紹介します。
アプリケーションプログラムとDBサーバ(PostgreSQL)の間に入るミドルウェアによって、発行されたクエリを複製して複数のDBサーバに送信することで、データベースを複製します。代表的な製品に Pgpool-II [Pgpool-II] があります。
PostgreSQL データベースでは、更新をコミットした際にその結果をクラッシュ等で失わないように更新情報をファイルに書きこむログ先行書込み(Write Ahead Logging; WAL)を用いています。このWALファイルにはデータベースに対する更新を全て復元することができる情報が含まれていますから、これを他のDBサーバに転送することでデータベースを複製することができる --- これがストリーミングレプリケーションの基本的な考え方です。
ストリーミングレプリケーションは、WALファイルに書かれた内容をほぼそのまま受信側(スレーブサーバ)に送り出すことで、送信側(マスタサーバ)と物理的に一致するDBを複製します。
ロジカルレプリケーションは、PostgreSQLバージョン10から標準採用された機能です。ストリーミングレプリケーションが送信側のWALをそのまま受信側に転送するのに対して、ロジカルレプリケーションは送信側でWALファイルをデコードし、必要な変更内容のみを受信側に送ります。
マルチマスタ構成の場合、レプリカを生成する手法には以下のようなものがあります。
- Bi-Directional Replication
- Bucardo
2nd Quadrant社が公開している Bi-Directional Replication は、先に紹介した論理レプリケーションを用いてデータを複製しつつ、複数のサーバでデータの更新を可能としたものです。主な用途としては地理的に離れた場所にある複数のサーバ間で、データを共有する利用形態を想定しています [PostgresBDR] 。
[IPA] | 独立行政法人情報処理推進機構. 非機能要求グレード 利用ガイド[活用編]. 2010. http://www.ipa.go.jp/files/000026853.pdf |
[PGWiki_replica] | Smith, G.; Grittner, K.; Pino, Conrad T.; Ringer, C.; Simon, R. et al. Replication, Clustering, and Connection Pooling. 2017. https://wiki.postgresql.org/wiki/Replication,_Clustering,_and_Connection_Pooling |
[DRBD] | LINBIT, Inc. http://www.drbd.org/en/ |
[pglogical] | 2ndQuadrant. pglogical. https://2ndquadrant.com/en/resources/pglogical/ |
[Pgpool-II] | PgPool Global Development Group. http://www.pgpool.net/mediawiki/index.php/Main_Page |
[Bucardo] | Jensen, G.; Sabino, G. M. et al. https://bucardo.org/wiki/Main_Page |
PostgreSQLのストリーミングレプリケーション(以下、SR構成)は以下を目的とした構成です。
以下の特徴があります。
SR基本構成図(スレーブ1台)
PostgreSQL 10 における主な機能を示します。
SR構成の運用上の注意点
以下のSR構成にて検証を実施しています。
対象バージョンは PostgreSQL 10
3ノード構成
PostgreSQLの機能に限定
PostgreSQLのSR構成には様々な機能があり、 対処する障害に応じて適切に設定する必要があります。 また各機能は組み合わせることが可能です。
- "ストリーミングレプリケーション"を"SR"と表記します。
- "レプリケーションスロット"を"スロット"と表記します。
機能 | 内容・目的 | 注意点・補足 |
---|---|---|
アーカイブWAL | 必要なWALファイルの保持。以下の2種類。
|
|
レプリケーションスロット | スレーブに必要なWALファイルを保持する事によるSR構成の維持。 対応する各スレーブの未転送のWALを保持する。 |
|
遅延レプリケーション | スレーブ側のWAL適用を意図して遅らせることで、スレーブを一定の過去の状態に維持する。 操作ミス対策に任意の時点まで巻き戻せるようにする。 |
|
WAL圧縮 | Full Page Write時(チェックポイント後の最初の更新時)に、WALファイルに書き出すフルページイメージを圧縮する。 圧縮されたWALは適用時に解凍される。 WALサイズを大幅に圧縮できる可能性があり、転送負荷の低減が期待される。特にディザスタ・リカバリ構成で有効と考えられる。 |
|
基本的なSR環境の設定手順を紹介します。 尚、マスタとスレーブの両サーバにPostgreSQLはインストール済みであり、 マスタ側ではデータベースクラスタを構築していることを前提としています。
SR構成に最低限必要な設定は以下の通りです。 スレーブはマスタのベースバックアップから作成されるため、マスタに設定したパラメータは全てスレーブも同様の値に設定されます。
- "ストリーミングレプリケーション"を"SR"と表記します。
- "レプリケーションスロット"を"スロット"と表記します。
¶ 項目 内容 マスタのIPアドレス およびサーバ名 192.168.100.101/24 server1 スレーブのIPアドレスおよびサーバ名 192.168.100.102/24 server2 ポート番号 5432 レプリケーション用ユーザ/パスワード repuser/password
¶ 設定値 内容 host replication repuser 192.168.100.101/32 trusthost replication repuser 192.168.100.102/32 trustまたはCIDR指定にてhost replication repuser 192.168.102.0/24 trust SR用ユーザの接続を許可する。・pg_hba.confは両ノード共通の設定となる事から、どちらがマスタになっても使用できる設定とする。・IPアドレスはWAL転送を行うネットワークLANを指定。パブリックLAN以外に、ハートビート系LANも有力。・認証方式は任意だが、passwordやmd5ではパスワードファイルにより入力を求められないようにする。
¶ パラメータ 設定値 内容 listen_addresses '0.0.0.0' 接続を受け入れるIPを指定。再起動で反映。 wal_level replica WALに書かれる情報量を指定。SR構成の場合は replica に設定。デフォルトがreplicaであるため変更不要。再起動で反映。 wal_keep_segments 64 暫定値として、max_wal_size/16MBの値を設定。スロットを使用する場合は設定不要。リロードで反映。 synchronous_commit on 目的に応じて設定する。remote_apply : 完全同期。WAL適用までを保証。on : 同期。WAL転送後のディスクへの書き込みまで保証。remote_write : 準同期。WAL転送後のメモリ書き込みまで保証。local : 非同期。ローカルへの書き込みまで保証(リモートは保証しない)。off : 完全非同期。ローカルへの書き込みも保証しない。同期式におけるスレーブ障害時および復旧時の同期/非同期の切り替えはsynchronous_standby_namesにて行う。本パラメータは固定。リロードで反映。 synchronous_standby_names ''(非同期) SRの非同期と同期を切り替えに使用。非同期の場合 : ''同期の場合 : '*' あるいは特定スレーブのapplication_nameリロードで反映。 max_wal_senders 10 起動するwal senders数を指定。pg_basebackupでWALストリーミングを指定する場合はその分も考慮通常はデフォルトの10で問題ない。再起動で反映。 max_replication_slot 10 レプリケーションスロットを作成可能な最大数通常はデフォルトの10で問題ない。再起動で反映。 restart_after_crash off onの場合、インスタンス障害(postgresプロセスは残存)の場合に、自動的に再起動を行う。シングル構成では便利な機能であるが、SR構成では管理が複雑化するため、offに設定するのが一般的。リロードで反映。
¶ パラメータ 設定値 内容 hot_standby on ホットスタンバイとして参照可能な状態で起動する。スレーブに対しても監視SQLを実行できるように通常は有効化する。デフォルトでonであるため、変更は不要。 hot_standby_feedback on onの場合、スレーブが現在処理している問い合わせについて、マスタへフィードバックを送る。通常は有効化する。特にスロットを作成する場合には有効化が必須。デフォルトはoffであるため、変更が必要。
¶ パラメータ 設定値 内容 standby_mode on スレーブとして起動か、PITRのリカバリ処理かを区別をする設定。onの場合は、スレーブとして起動する。WALファイルの最後に達してもリカバリを終了せず、マスタへ接続して新WALセグメント取得を継続。 primary_conninfo 'host=server1 port=5432 user=repuser password=password application_name=server2' マスタへの接続文字列を指定。hostはWAL転送を行うLANを指定。2サーバ間の転送のみである事から、ハートビートLANでも良い。portはPostgreSQLがリスニングしているポート番号(portパラメータ)。userはreplication仮想データベースへ接続できるユーザを指定(ユーザのreplication属性およびpg_hba.confの設定)。passwordはパスワードが必要な場合に設定。パスワードの記載を避けたい場合は、~/.pgpassを使用。application_nameは視認性を高めるのが目的であり必須ではない。ここではスレーブ名を指定。デフォルトで'wal_receiver'。 recovery_target_timeline latest リカバリが作成する個別のタイムラインを指定。SR構成ではlatestを指定して最新タイムラインを追従。 primary_slot_name <スロット名> 当該スレーブ用のスロットを作成している場合は、スロット名を設定。最新バージョンではスロットを作成するのが一般的。 restore_command アーカイブWALファイルのリストア(コピー)方法を指定 スロットを使用している場合は設定不要
ここでは基本的な2ノード構成における手順を記載します。
¶ 項目 内容 同期方式 非同期(synchronous_standby_names = '') レプリケーションスロット 使用しない(1)レプリケーションユーザ作成
マスタサーバでレプリケーション用のユーザを作成します。
postgres=# CREATE ROLE repuser LOGIN REPLICATION PASSWORD 'password';(2)設定ファイルの変更
マスタで$PGDATA配下のpg_hba.confとpostgresql.confを変更します。
pg_hba.conf
レプリケーションユーザがデータベース接続できるようpg_hba.confを次のように設定します。 ここでは便宜上、trust認証を指定しています。 md5認証を指定する場合はパスワードファイル(~/.pgpass)を使用して、パスワードの入力が不要となるように設定します。
$ vi $PGDATA/pg_hba.conf [pg_hba.conf] # デフォルトで以下の設定が登録されています。 local replication all trust host replication all 127.0.0.1/32 trust host replication all ::1/128 trust # 以下の様に変更します。 host replication repuser 192.168.100.100/32 trust host replication repuser 192.168.100.101/32 trust host replication repuser 192.168.100.102/32 trustpostgresql.conf
マスタとして稼働させるため、postgresql.confを次のように設定します。 スレーブ側の設定も含めておきます。スレーブ側の設定は、マスタ側では無視されます。
$ vi $PGDATA/postgresql.conf [postgresql.conf] port = 5432 (デフォルト) listen_addresses = '*' wal_level = replica (デフォルト) synchronous_commit = on (デフォルト) synchronous_standby_names = '' (デフォルト) max_wal_senders = 10 (デフォルト) max_replication_slots = 10 (デフォルト) restart_after_crash = off hot_standby = on (デフォルト) hot_standby_feedback = onパラメータの記述後、設定ファイルの変更を反映するためにPostgreSQLを再起動します。
$ pg_ctl restart(3)pg_basebackupによる物理ファイルのコピー (スレーブ)
スレーブ側でpg_basebackupを実行し、スレーブデータベースを構築します。
$ pg_basebackup -h <マスタIP> -p 5432 -U repuser -D $PGDATA --progress --verboseなおパスワードファイルはpg_basebackupではコピーされないため、必要に応じてコピーします。
$ scp <マスタIP>:~/.pgpass ~/(4)recovery.confの設定(スレーブ)
pg_basebackupで取得したデータベースクラスタの設定を変更し、スレーブとして稼働するようにします。 取得先($PGDATA)配下でrecovery.confを作成します。 なおスレーブに必要なパラメータ設定は(postgresql.conf)は、マスタ側で設定済みのファイルがコピーされているため、変更不要です。
recovery.conf
$PGDATA配下にrecovery.confを作成し、以下を記述します。
$ vi $PGDATA/recovery.conf [recovery.conf] standby_mode = 'on' primary_conninfo = 'host=server1 port=5432 user=repuser password=repuser' recovery_target_timeline = latest(5)PostgreSQLの起動(スレーブ)
スレーブのPostgreSQLを起動します。
$ pg_ctl start(6)状況確認(マスタ)
SRが構築されていることを確認します。
[マスタ側で確認] postgres=# \x Expanded display is on. postgres=# SELECT * FROM pg_stat_replication; -[ RECORD 1 ]----+------------------------------ pid | 40724 usesysid | 16385 usename | repuser application_name | walreceiver client_addr | <スレーブのIP> client_hostname | client_port | 48077 backend_start | 2018-01-04 01:30:54.01041+09 backend_xmin | state | streaming sent_lsn | 0/3000060 write_lsn | 0/3000060 flush_lsn | 0/3000060 replay_lsn | 0/3000060 write_lag | flush_lag | replay_lag | sync_priority | 0 sync_state | async(7)状況確認(スレーブ)
[スレーブ側で確認] postgres=# \x Expanded display is on. postgres=# SELECT * FROM pg_stat_wal_receiver; -[ RECORD 1 ]---------+-------------------------------------------------- pid | 52484 status | streaming receive_start_lsn | 0/3000000 receive_start_tli | 1 received_lsn | 0/3000140 received_tli | 1 last_msg_send_time | 2018-01-04 01:35:50.514256+09 last_msg_receipt_time | 2018-01-04 01:18:46.923761+09 latest_end_lsn | 0/3000140 latest_end_time | 2018-01-04 01:33:20.23195+09 slot_name | conninfo | user=repuser password=******** dbname=replication host=<マスタのIP> port=<マスタのポート番号> fallback_application_name=walreceiver sslmode=disable sslcompression=1 target_session_attrs=anyconninfo列は実際には1行で表示されますが、便宜上改行しています。
2ノード構成からスレーブ(server3)を追加する方法は、基本的には前述の手順と同様です。 複数スレーブ方式とカスケード方式の違いはserver3側でpg_basebackupを実行するのは同様で、マスタの指定の違いだけです。 カスケード方式では、マスタに負荷をかけないというメリットがあります。
- pg_basebackupの実行(server3)
(複数スレーブ構成:マスタはsever1) $ pg_basebackup -h server1 -p 5432 -U repuser -D $PGDATA --progress --verbose (カスケード構成 :マスタはserver2) $ pg_basebackup -h server2 -p 5432 -U repuser -D $PGDATA --progress --verbose
- recovery.confの設定(server3)
recovery.conf
$PGDATA配下にrecovery.confを作成します。 カスケード方式の場合にはserver2からコピーされたファイルが存在するので、それを修正します。 server2の設定とほぼ同様で、異なるのはマスタの指定です。
$ vi $PGDATA/recovery.conf [recovery.conf] (複数スレーブ構成:マスタはsever1) primary_conninfo = 'host=server1 port=5432 user=repuser password=repuser' (カスケード構成 :マスタはserver2) primary_conninfo = 'host=server1 port=5432 user=repuser password=repuser'
SR構成においてもアーカイブモード運用は有力です。 スレーブが物理バックアップとも言えますのでアーカイブモード運用は必須ではありませんが、以下を目的として構成する事もあります。
既存のレプリケーションスロットの設定に加え、次のパラメータを指定します。
サーバ 設定ファイル パラメータ 設定値 内容 マスタ postgresql.conf archive_mode on アーカイブを有効化する。 マスタ postgresql.conf archive_command cp %p <アーカイブの保存先>/%f アーカイブの実行コマンドを指定する。 スクリプトを指定する事も可能であるため、複雑な処理を組み込む事が可能。 スレーブ recovery.conf restore_command 'scp <マスタのユーザ名>@<マスタのホスト名>:<マスタのアーカイブ・ディレクトリ>%f %p' マスタのアーカイブを取得するコマンドを指定する。 WAL同期を保証する場合に必要となる。ただしレプリケーションスロットを使用する場合は不要である。 注釈
- restore_commandにてscpコマンドを使用する場合、スレーブはマスタのPostgreSQLのOSユーザに sshにてパスワードなしで接続できるようになる必要があります。 またはアーカイブ・ディレクトリをNFSマウント等、マスタ/スレーブ間で共有可能なパスにする事で、 アーカイブの扱いが容易になります。
- 障害時スレーブをマスタにする場合に備え、マスタに設定したパラメータは スレーブ側でも事前に有効化することを推奨します。
(1)マスタ側のpostgresql.confのパラメータを以下のように設定する。
$ vi $PGDATA/postgresql.conf [postgresql.conf] archive_mode = on archive_command = 'cp %p <アーカイブの保存先>/%f'(2)必要ならばスレーブ側のrecovery.confのパラメータに以下を追加する。
$ vi $PGDATA/recovery.conf [recovery.conf] restore_command = 'scp <マスタのユーザ名>@<マスタのホスト名>:<マスタのアーカイブ・ディレクトリ>%f %p'(3)マスタ側を再起動し、その後スレーブ側を再起動する。
[マスタ/スレーブの両方で実施] $ pg_ctl restart(4)マスタ側で強制的にWALファイルを切り替え、アーカイブWALファイルが出力されることを確認する。
postgres=# SELECT pg_switch_xlog(); pg_switch_xlog ---------------- 0/7017008 (1 row) $ <アーカイブ・ディレクトリ> 000000010000000000000007
スレーブに未転送のWALを保持することで、SRの維持を保証します。 アーカイブ運用でも同期の保証は可能ですが、アーカイブログの管理などが問題となり、 ノーアーカイブ運用をしている環境も多くあります。 そのような環境において、レプリケーションの維持を保証するためには、レプリケーションスロットの設定が必要です。 またアーカイブ運用においても、アーカイブWALファイルの削除にSRの考慮が不要になるため、有用な設定です。
既存のレプリケーションスロットの設定に加え、次のパラメータを指定します。基本的なSRの構築手順にて設定済みのパラメータも、改めて記載しています。
¶ サーバ 設定ファイル パラメータ 設定値 内容 マスタ postgresql.conf max_replication_slots スレーブ数以上(設定済み) 作成可能なレプリケーションスロット数を指定する。 スレーブ postgresql.conf hot_standby_feedback on(設定済み) スレーブの状態をマスタにフィードバックする。 スレーブ recovery.conf primary_slot_name レプリケーションスロット名 使用するレプリケーションスロット名を指定する。 注釈
- 障害時スレーブをマスタにする場合に備え、マスタに設定したパラメータは スレーブ側でも事前に有効化することを推奨します。
レプリケーションスロットに関連する関数は以下の通りです。
¶ 関数名 説明 pg_create_physical_replication_slot(スロット名[, true/false]) レプリケーションスロットを作成する。
- スロット名:作成するレプリケーションスロット名を指定する。
- true/false:trueの場合、レプリケーションスロットは即座にWALを保持する。falseの場合従来通り、スレーブがレプリケーションスロットに繋いだ時点からWALを保持する。
pg_drop_replication_slot(スロット名) レプリケーションスロットを削除する。
- スロット名:削除するレプリケーションスロット名を指定する。
(1)マスタにてpostgresql.confにmax_replication_slotsを加え、設定反映のため再起動。
$ vi $PGDATA/postgresql.conf [postgresql.conf] max_replication_slots = 10 <--- デフォルト $ pg_ctl restart(2)マスタにてレプリケーションスロットを作成。
postgres=# SELECT pg_create_physical_replication_slot('slot1', true); pg_create_physical_replication_slot ------------------------------------- (slot1,0/B000220) (1 row)(3)マスタにてレプリケーションスロットの作成を確認。
postgres=# SELECT slot_name, restart_lsn, active FROM pg_replication_slots; slot_name | restart_lsn | active -----------+-------------+-------- slot1 | 0/B000220 | f (1 row)・active列が'f'であることから、まだ使用されていない。・restart_lsn列に値があることから、使用されていなくてもWALの保持は開始している。(4)スレーブにてrecovery.confにprimary_slot_nameを加え、設定反映のため再起動。
$ vi $PGDATA/postgresql.conf [recovery.conf] primary_slot_name = 'slot1' $ pg_ctl restart(5)マスタ側でレプリケーションスロットが使用されていることを確認。
postgres=# SELECT slot_name, restart_lsn, active FROM pg_replication_slots; slot_name | restart_lsn | active -----------+-------------+-------- slot1 | 0/B0002C8 | t (1 row)・active列が't'であることから、使用されている。
同期モードの懸念として、WAL転送の待機によるパフォーマンスへの影響があります。 以下の図はsynchronous_commitの各設定によるパフォーマンス比較です。(onの値を1.00とした相対値)。
- サーバススペック
- CPU : Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz 16Core
- メモリ : 64GB
- OS : Red Hat Enterprise Linux Server release 6.5 (Santiago)
- PostgreSQLのバージョン : PostgreSQL 9.6.1
- パラメータ設定
- キャッシュヒット率の影響を受けないようほぼ100%となるように調整
- shared_buffers : 8GB
- 処理中に自動VACUUMが発生しないように無効化
- autovacuum :off
- 処理中にチェックポイントが発生しないように調整
- checkpoint_timeout :1h
- max_wal_size :10GB
- 処理前に毎回手動でチェックポイント実行
- トランザクションツールはpgbench を使用。
- 初期化スケール 100
- 100セッション、25ワーカスレッドのトランザクションを実施(pgbench -c 100 -j 25)
- 通常のOLTPアプリケーションではこれほど激しいトランザクションではないため、低下は限定的と考えられます。
- 同期モードの実装にあたっては、実際のアプリケーションで検証してご確認ください。
スレーブの適用を一時的に遅延させます。 マスタの操作ミスが即座に伝搬されるのを防ぐのが目的です。 WAL転送までは遅延なく処理されるため、データ保全には影響ありません。 同期転送(synchronous_commit=on)との組み合わせができます。
以下の検証を行います。
検証1: 遅延レプリケーションの設定および動作確認スレーブへの適用が指定した時間分、遅延する事。およびマスタでの遅延確認。検証2: 問題発生前の状態まで適用オペレーションミス発生を想定し、発生前の状態まで適用し、昇格。
既存のレプリケーションスロットの設定に加え、次のパラメータを指定します。
¶ サーバ 設定ファイル パラメータ 設定値 スレーブ postgresql.conf hot_standby_feedback on(フィードバックを有効化) スレーブ recovery.conf recovery_min_apply_delay 遅延させる時間を指定
(1)マスタにてテストに使用するテーブルを作成。
(マスタ) postgres=# CREATE TABLE delay_test(id integer, time1 timestamp); postgres=# \d delay_test Table "public.delay_test" Column | Type | Modifiers --------+-----------------------------+----------- id | integer | time1 | timestamp without time zone |(2)スレーブで遅延レプリケーションの設定を行い、再起動。
(スレーブ) $ vi $PGDATA/postgresql.conf [postgresql.conf] hot_standby_feedback = on $ vi $PGDATA/recovery.conf [recovery.conf] recovery_min_apply_delay = '60min' $ pg_ctl restart(3)マスタにてテストテーブルにデータを追加。
(マスタ) postgres=# INSERT INTO delay_test VALUES (1, localtimestamp); postgres=# SELECT * FROM delay_test; id | time1 ----+---------------------------- 1 | 2018-03-10 18:05:46.29221 (1 rows)(4)スレーブにて即座に反映されない事を確認。
(スレーブ) postgres=# SELECT * FROM delay_test; id | time1 ----+------- (0 rows) <--- INSERTが反映されていない
- (5)マスタにて適用の遅延を確認。
- pg_stat_replicationビューのreplay_lag列で確認
(マスタ) postgres=# SELECT write_lag,flush_lag,replay_lag FROM pg_stat_replication ; -[ RECORD 1 ]--------------- write_lag | 00:00:00.000323 flush_lag | 00:00:00.00048 replay_lag | 00:00:38.354572 <--- recovery_min_apply_delayに達するまで適用を待機(6)1時間経過後、スレーブにて適用された事を確認。
(スレーブ) postgres=# SELECT *,localtimestamp FROM delay_test; id | time1 | localtimestamp ----+----------------------------+---------------------------- 1 | 2018-03-10 18:05:46.29221 | 2018-03-10 19:06:10.521195 (q rows)(7)マスタにて適用の遅延がクリアされた事を確認。
(マスタ) postgres=# SELECT write_lag,flush_lag,replay_lag FROM pg_stat_replication ; -[ RECORD 1 ]--------------- write_lag | flush_lag | replay_lag |
(1)マスタにてテストテーブルにデータを追加。
(マスタ) postgres=# INSERT INTO delay_test VALUES (2, localtimestamp); postgres=# SELECT * FROM delay_test; id | time1 ----+---------------------------- 1 | 2018-03-10 18:05:46.29221 2 | 2018-03-10 19:30:11.23870 (1 rows)id=2の更新(INSERT)をオペレーションミスとみなし、スレーブを更新前の時点まで適用
(2)マスタを停止。
$ pg_ctl stop(3)スレーブにて1件も反映されない事を確認。
(スレーブ) postgres=# SELECT * FROM delay_test; id | time1 ----+------- (0 rows) <--- INSERTが反映されていない(4)スレーブを停止。
$ pg_ctl stop(5)スレーブにてrecovery.confを修正。
$ vi $PGDATA/recovery.conf [recovery.conf] restore_command = 'cp <$PGDATAパス>/pg_wal/%f %p' <--- WALのパスを指定 recovery_target_time = '2018-03-10 19:30:00' <--- id=2のCOMMIT直前の時刻 recovery_target_timeline = latest <--- 最新のタイムライン recovery_target_action = 'promote' <--- 適用中断後、昇格(6)スレーブにて指定時間までのリカバリおよび昇格(スレーブ→新マスタ)
$ pg_ctl start $ pg_controldata | grep state <--- マスタ/スレーブの確認 Database cluster state: in production <--- マスタ
¶ メッセージ 時間 LOG: starting point-in-time recovery to 2018-03-10 19:30:00+09 recovery_target_time LOG: recovery stopping before commit of transaction 578, time 2018-03-10 19:30:11.423701 id=2のCOMMIT時間 LOG: last completed transaction was at log time 2018-03-10 18:05:46.314276 id=1のCOMMIT時間 (8)新マスタにてid=1のみ適用されていることを確認。
postgres=# SELECT * FROM delay_test ; id | time1 ----+--------------------------- 1 | 2018-03-10 18:05:46.29221 (1 row)
Full Page Write時(チェックポイント後の最初の更新時)に、WALに書き出すフルページイメージを圧縮します。 圧縮されたWALは適用時に解凍されます。 WALファイルのサイズが小さくなるため、書き込みや転送の時間短縮 が期待されます。 注意点として、圧縮処理および解凍処理が発生するため、通常より余分にCPUを使用します。 適用についてはそれらを総合的に判断します。 一般的な適用場面として、SR構成において効果的と考えられます。 特に以下の構成で有力です。
- 同期モード(転送まで)または完全同期モード(適用まで)
WAL転送の遅延を懸念して同期モード設定を躊躇する場面でWAL圧縮を検討します。非同期モードでもWAL圧縮は有力ですが、一定のWAL転送の遅延は許容されるため、マスタのCPU負荷を優先してWAL圧縮を適用しないという判断も考えられます。
- スレーブの遠隔地配置(ディザスタ・リカバリ)
スレーブを遠隔地に配置する構成において、非同期モードでもWAL転送の遅延が許容範囲を超える場合にWAL圧縮の適用を検討します。
既存のレプリケーションスロットの設定に加え、次のパラメータを指定します。
¶ サーバ 設定ファイル パラメータ 設定値 マスタ postgresql.conf wal_compression 'on'
タイミング | サイト | メッセージ |
---|---|---|
スレーブ停止時 | マスタ | スレーブが複数台存在し、Quorum-based 同期レプリケーションを設定していない場合に、
片系スレーブの起動または停止を行うことで同期優先順位が変化した場合のみされます。
START_REPLICATION 0/57000000 TIMELINE 1
|
マスタ停止時 | スレーブ | エラーメッセージが繰り返し出力されます。
LOG: invalid record length at 132/EF8AD7F0: wanted 24, got 0
FATAL: could not connect to the primary server: could not connect to server: Connection refused
Is the server running on host "master" (192.168.100.100) and accepting
TCP/IP connections on port 5432?
|
WAL再利用によるロスト | マスタ | エラーメッセージが繰り返し出力されます。
ERROR: requested WAL segment 0000001100000132000000EF has already been removed
|
WAL再利用によるロスト | スレーブ | エラーメッセージが繰り返し出力されます。
LOG: started streaming WAL from primary at 132/EF000000 on timeline 17
FATAL: could not receive data from WAL stream: ERROR: requested WAL segment 0000001100000132000000EF has already been removed
|
デフォルトで出力される情報に加えて、以下が出力されます。
タイミング | サイト | メッセージ |
---|---|---|
スレーブ起動または停止時 | マスタ | スレーブが複数台存在し、Quorum-based 同期レプリケーションを設定していない場合に、
片系スレーブの起動または停止を行うことで同期優先順位が変化した場合のみ出力されます。
LOG: received replication command: IDENTIFY_SYSTEM
LOG: received replication command:
START_REPLICATION 0/57000000 TIMELINE 1
|
WAL再利用によるロスト | マスタ、スレーブ(スレーブ出力はカスケード・レプリケーション時のみ) | エラーメッセージが繰り返し出力されます。
LOG: received replication command: IDENTIFY_SYSTEM
LOG: received replication command: START_REPLICATION 134/4A000000 TIMELINE 17
|
■前提
構成:
バージョン:PostgreSQL 10
同期モード:問わない
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #!/bin/sh
HOST=localhost
LIMIT=1
SLEEPTIME=2
ALERT="echo"
SLAVENUM=2
function send_alert(){
${ALERT} $1
}
IFS_DEFAULT=$IFS
while :
do
val1=$(psql -h $HOST -p 5432 -U postgres -q -t -c "select current_time;")
case $(psql -p 5432 -U postgres -q -t -c "SELECT pg_is_in_recovery();"
|awk '{print $NF}') in
t)
val2=$(psql --no-align -h $HOST -p 5432 -U postgres -q -t -c
"select client_addr,pg_wal_lsn_diff(master,replay_lsn)
as replaydiff from (select pg_last_wal_replay_lsn() master)
as m,pg_stat_replication;")
;;
f)
val2=$(psql --no-align -h $HOST -p 5432 -U postgres -q -t -c
"select client_addr,pg_wal_lsn_diff(master,replay_lsn)
as replaydiff from (select pg_current_wal_insert_lsn() master)
as m,pg_stat_replication;")
;;
esac
rownum=$(psql --no-align -h $HOST -p 5432 -U postgres -q -t -c
"select client_addr,pg_wal_lsn_diff(master,replay_lsn)
as replaydiff from (select pg_current_wal_insert_lsn() master)
as m,pg_stat_replication;" |wc -l)
for slave_lsn in ${val2};
do
if [ ${rownum} -ne ${SLAVENUM} ]; then
send_alert "Slave Down |${val1}"
fi
IFS=$'|'
byte=`echo ${slave_lsn} | awk '{print $NF}'`
if [ ${byte} -gt ${LIMIT} ]; then
send_alert "Replication Delay ${val1},${slave_lsn}"
fi
IFS=${IFS_DEFAULT}
sleep ${SLEEPTIME}
done
done
|
$ ./delay_test.sh Replication Delay 20:58:18.451839+09,192.168.100.101 11880 Replication Delay 20:58:18.451839+09,192.168.100.102 11880 Replication Delay 20:58:22.484532+09,192.168.100.101 11880 Replication Delay 20:58:22.484532+09,192.168.100.102 11880 Replication Delay 20:58:26.512656+09,192.168.100.101 12048 Replication Delay 20:58:26.512656+09,192.168.100.102 12048 Replication Delay 20:58:30.538009+09,192.168.100.102 12048
■前提構成
3ノード構成
同期モード:同期または完全同期 (synchronous_commit = on / remote_apply)
$ psql -U postgres -d testdb -q -t << EOF > \timing on > SET statement_timeout TO 5000; > insert into test(id) values(1); > delete from test where id=1; > EOF 時間: 0.192 ms ^CCancel request sent WARNING: canceling wait for synchronous replication due to user request DETAIL: The transaction has already committed locally, but might not have been replicated to the standby. 時間: 13626.496 ms
$ psql -U postgres -d testdb -q -t << EOF > \timing on > SET statement_timeout TO 5000; > insert into test(id) values(1); > delete from test where id=1; > EOF Time: 0.147 ms Time: 1.899 ms Time: 1.277 ms
testdb=# select * from pg_stat_replication; pid | usesysid | usename | application_name | client_addr | client_hostnam e | client_port | backend_start | backend_xmin | state | sen t_lsn | write_lsn | flush_lsn | replay_lsn | write_lag | flush_lag | flush_lag | sync_priority | sync_state -------+----------+---------+------------------+----------------+--------------- --+-------------+-------------------------------+--------------+-----------+---- -----------+----------------+----------------+-----------------+---------------+ ------------ (0 行)
$ timeout -sINT 5 psql -U postgres -d testdb -q -t << EOF > \timing on > insert into test(id) values(1); > delete from test where id=1; > EOF Cancel request sent WARNING: canceling wait for synchronous replication due to user request DETAIL: The transaction has already committed locally, but might not have been replicated to the standby. Time: 4994.166 ms (00:04.994) [postgres@test_pg01 data]$ echo $? 124
$ timeout -sINT 5 psql -U postgres -d testdb -q -t << EOF > \timing on > insert into test(id) values(1); > delete from test where id=1; > EOF Time: 2.382 ms Time: 1.246 ms [postgres@test_pg01 data]$ echo $? 0
SR構成においては、通常はマスタの障害を検知した場合にスレーブを昇格させます。つまりマスタは常に1台のみです。 ただしオペレーションミスにより、マスタが正常な状態にもかかわらずスレーブを昇格させてしまうという事も有り得ます。 稼働中のマスタ(シングル)が2台という危険な状態になります。その状態をスプリットブレインと定義します。
その場合でも全てのアプリケーションが元のマスタにのみ接続していれば問題ありませんが、 2台目のマスタにも接続が発生するとデータの整合性が損なわれてしまいます。 それを避けるため、スプリットブレイン状態になっていないかの監視の方法を検討します。
以下の監視についてまとめます。
received promote request selected new timeline ID: XX
マスタ稼働中にスレーブ側にこのようなメッセージが出力されていないかを監視します。
$ export LANG=C $ pg_controldata pg_control version number: 1002 Catalog version number: 201707211 Database system identifier: 6514575400714084610 Database cluster state: in archive recovery ~以下略~
多数の項目がありますが、ここでは"Database cluster state"に着目します。 必要な項目のみ抽出する例です。
$ pg_controldata | grep "Database cluster state" Database cluster state: in archive recovery
4種類の状態があります。
表示される値(英語) | 表示される値(日本語) | 意味 |
---|---|---|
in production | 運用中 | マスタとして稼働中 |
in archive recovery | アーカイブリカバリ中 | スレーブとして稼働中 |
shut down | シャットダウン | マスタとして停止中 |
shut down in recovery | リカバリしながらシャットダウン中 | スレーブとして停止中 |
sshコマンドでリモートの状態を容易に取得できます。
$ ssh <remote> $PGHOME/bin/pg_controldata $PGDATA | \ > grep "Database cluster state" Database cluster state: in production
=# SELECT pg_control_recovery(); pg_control_recovery ----------------------------- (0/5E0001B0,1,0/0,0/0,f) (1 行)
カンマ区切りにより5項目から構成されています。何れもpg_controldataコマンドでも取得できます。
項目 | マスタ | スレーブ |
---|---|---|
min_recovery_end_lsn | 0/0 | 0/15000178 |
min_recovery_end_timeline | 0 | 1 |
backup_start_lsn | 0/0 | 0/0 |
backup_end_lsn | 0/0 | 0/0 |
end_of_backup_record_required | f | f |
リカバリ中かどうかを示します。 マスタであれば f (false) 、スレーブであれば t (true) を表示します。
=# SELECT pg_is_in_recovery(); pg_is_in_recovery ------------------- t (1 行)
複数スレーブ構成では、不要な処理となります。
■前提 以降の手順では次の前提とします。
■対処一覧 大別すると3種類の対処方法が考えられます。
ID | 障害箇所 | 障害状況 | pg_basebackupとpg_rewindの使い分け |
---|---|---|---|
1
|
マスタ
|
マスタとスレーブの関係が崩れており再構成が必要
|
pg_basebackupコマンドを使用してフェイルバック
|
2
|
マスタ
|
マスタとスレーブの関係は巻き戻しで復旧可能
|
pg_rewindコマンドを使用してスイッチバック
|
3
|
マスタ
|
マスタとスレーブの切り替え可能
|
pg_rewindコマンドを使用しないでスイッチバック
|
4
|
スレーブ
|
マスタとスレーブの連携再開可能
|
同期式の場合は非同期式に切り替え
|
フェイルオーバについて記載します。
$ pg_ctl -w -m immediate stop
$ kill -9 `head -1 $PGDATA/postmaster.pid`
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - no response
$ pg_ctl promote
ただしこの時点ではsynchronous_standby_namesパラメータに値が設定されているため、新マスタで更新処理ができない状態です。
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_standby_names = '*'
[編集後]
synchronous_standby_names = ''
$ pg_ctl reload
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - accepting connections
以上でファイルオーバーは完了です。
pg_basebackupを使用したフェイルバックについて記載します。 初期構築手順とほぼ同じです。
マスタ障害発生によるフェイルオーバ後、旧マスタを新スレーブとしたレプリケーション構成図
■パラメータ
pg_basebackupに必要な設定を記載します。
サーバ | 設定ファイル | パラメータ | 設定値 | 内容 |
---|---|---|---|---|
マスタ | postgresql.conf | listen_address | 0.0.0.0 | 全てのIPアドレス(v4)からの接続を受け付ける |
マスタ | postgresql.conf | max_wal_senders | 2 | WALストリームオプションを付与する場合は、2以上を設定 |
■pg_basebackupコマンド
pg_basebackupコマンドの主なオプションは次の通りです。
¶ オプション 内容 -D <directory> 出力を書き出すディレクトリを指定。 -X <method>--wal-method=<method> 必要なWALファイルをバックアップに含める。method(収集方式)は以下から選択。
- fetch :WALファイルは最後に収集
- stream:バックアップ作成中に同時にWALをストリームで収集
運用中にpg_basebackupを実行する場合には stream を指定する。fetch (最後に収集)では、必要なWALファイルが削除される可能性があるため。 -S <slot_name>--slot=<slot_name> WALストリーミングの収集に指定したレプリケーションスロットを使用。-X stream とセットで指定。必要なWALファイルが削除されるのを防ぐ事を目的とする。運用中にpg_basebackupを実行する場合に使用を検討する。以下に注意する。
- 事前にレプリケーションスロットを作成する必要がある
- マスタのWAL領域の空きが十分である事を確認する
-R--write-recovery-conf 最低限のrecovery.confを作成。必要に応じてrecovery.confを加筆修正。 -r <rate>--max-rate=<rate> サーバから転送されるデータの最大転送速度を指定。運用中にpg_basebackupを実行する場合に使用を検討する。転送速度を抑える事で、マスタに対する影響を制限する事を目的とする。 -P 進行状況報告を有効化。pg_basebackup処理中におおよその進行状況を報告する。運用中に実行した場合、データベースクラスタのサイズが増加して進行状況が100%を超える場合がある。 -v 冗長モードを有効化。進行状況報告も有効な場合、現在処理中のファイル名を出力。
pg_basebackupでレプリケーションスロットが使用できます。 WAL収集方式に stream を指定する事でWALをほぼ確保できますが、スロットを指定する事でより確実になります。 スロットを使用する運用であれば、この段階で作成するのが有力です。
■フェイルバック手順
pg_basebackupにスロットを指定する場合を記載します。
$ rm -rf $PGDATA/*
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,144/EEFC8940)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots ;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 144/EEFC8940 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ pg_basebackup -h server2 -U rep_user -D $PGDATA -X stream -S slot_server1 -P -v -R
transaction log start point: 144/F3000028 on timeline 17
pg_basebackup: starting background WAL receiver
10503022/10503022 kB (100%), 1/1 tablespace
transaction log end point: 144/F3000130
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=rep_user host=server2 port=5432 sslmode=prefer sslcompression=1'
primary_slot_name = 'slot_server1'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=rep_user host=server2 port=5432 application_name=slave_server1 asslmode=prefer sslcompression=1'
primary_slot_name = 'slot_server1'
recovery_target_timeline = latest
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_standby_names = '*'
shared_preload_libraries = 'pg_stat_statements,pg_statsinfo'
[編集後]
synchronous_standby_names = ''
shared_preload_libraries = ''
$ pg_ctl start
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 11262
usesysid | 16384
usename | rep_user
application_name | slave_server1
client_addr | <新スレーブIP>
client_hostname |
client_port | 44548
backend_start | 2017-03-22 06:36:06.362576+09
backend_xmin | 1781
state | streaming --- ストリーミング中
sent_location | 0/10000060
write_location | 0/10000060
flush_location | 0/10000060
replay_location | 0/10000000
sync_priority | 2
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
active | t --- アクティブ
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_standby_names = ''
[編集後]
synchronous_standby_names = '*'
$ pg_ctl reload
$ psql -h server2 -U postgres postgres -c "SELECT * FROM pg_stat_replication" -x
-[ RECORD 1 ]----+------------------------------
pid | 11262
usesysid | 16384
usename | rep_user
application_name | s1
client_addr | <新スレーブIP>
client_hostname |
client_port | 44548
backend_start | 2017-03-22 06:36:06.362576+09
backend_xmin | 1781
state | streaming --- ストリーミング中
sent_location | 0/10000060
write_location | 0/10000060
flush_location | 0/10000060
replay_location | 0/10000000
sync_priority | 2
sync_state | sync --- 同期
これにて、以下の構成に復旧しました。
スイッチオーバについて記載します。
■スイッチオーバ手順
計画停止におけるマスタ/スレーブの切り替え手順です。 pg_basebackupやpg_rewindが不要であるためシンプルな手順です。
$ pg_ctl stop -m fast
$ pg_ctl promote
以降の手順はフェイルオーバの場合と同様であるため省略します。
$ psql =# SELECT pg_drop_replication_slot('slot_server2'); pg_create_physical_replication_slot ------------------------------------- (1 row) =# SELECT slot_name FROM pg_replication_slots ; (0 rows)
以上でスイッチオーバが完了しました。
pg_rewindを使用したスイッチバックについて記載します。
pg_rewindはタイムラインのずれたレプリケーションを再同期させる機能です。 実行後、ターゲットクラスタはソースクラスタと置き換えられた状態になります。 そのためpg_rewind後の操作は、通常のフェイルオーバ時と同じです。 タイムラインの分岐点からソースクラスタのWALを適用するため、更新量が少なければpg_basebackによる複製より高速です。 これによりフェイルオーバ時、旧マスタを容易に新スレーブとして起動させることができます。
■関連パラメータ
pg_rewindに必要な設定を記載します。
サーバ | 設定ファイル | パラメータ | 設定値 | 内容 |
---|---|---|---|---|
マスタ | postgresql.conf | full_page_writes | on | チェックポイント後の更新時、ディスクページの全内容をWALに書き込む。 |
マスタ | postgresql.conf | wal_log_hints | on | ヒントビット更新時もfull_page_writesを実行する。 |
■pg_rewindコマンド
pg_rewindコマンドのの主なオプションは次の通りです。
¶ オプション 内容 D <ターゲットクラスタ> pg_rewindを実行し、ソースクラスタの内容に置き換えるクラスタを指定する。 source-server="<ソースクラスタ>" 同期対象であるソースクラスタを指定します。主に次の接続文字列を使用します。
- host:ソースクラスタのホスト名またはIPアドレス
- port:ソースクラスタのポート番号
- dbname:ソースクラスタの接続先データベース名
- user:ソースクラスタの接続先ユーザ
P 進行状況をレポートとして表示する。
■スイッチバック手順
※事前にマスタ/スレーブで(1) 関連パラメータの設定がされていることを前提とします。
$ pg_ctl start -w $ pg_ctl stop -m fast -w
$ pg_rewind -D $PGDATA --source-server="host=server2 port=5432" servers diverged at WAL position 0/5015B70 on timeline 1 rewinding from last common checkpoint at 0/5015B00 on timeline 1 Done!
$ vi $PGDATA/recovery.conf [編集後] standby_mode = 'on' primary_conninfo = 'host=server2 port=5432 user=rep_user' recovery_target_timeline = 'latest'
[新マスタ] $ export LANG=C $ pg_controldata | grep " TimeLineID" Latest checkpoint's TimeLineID: 2 $ ssh server1 $PGHOME/bin/pg_controldata $PGDATA | grep " TimeLineID" Latest checkpoint's TimeLineID: 2
■pg_rewind使用時の注意点
正常停止が必要
pg_rewindを実行するデータベースクラスタは正常終了しなければいけません。物理障害等により正常停止できない場合、pg_rewindは使用できません。pg_basebackupを使用します。同一タイムラインの場合は実施不可
pg_promoteを実行せずに旧スレーブを新マスタにした場合、新マスタのタイムラインIDは変わらないため、新マスタと旧マスタのタイムラインIDは同じ状態です。この場合は、pg_rewindは実行できません。実行時期と所要時間の関係
pg_rewindによるフェイルバックの所要時間は2つの要素から構成されます。Step1. pg_rewindによる巻き戻し (旧マスタのWALを使用)Step2. WAL適用による追い付き (新マスタのWALを使用)新マスタで大量更新がある場合は、Step1は短時間で終了してもStep2で時間がかかります。結果として、pg_basebackupの方が効率が良い場合もあり得ます。また新マスタ昇格時のWALが削除されている場合は、後述するようにStep2でエラーとなる可能性もあります。その場合はpg_basebackupが必要となります。pg_rewindはフェイルオーバー後、あまり時間を置かずに実行する事がポイントです。旧マスタのWAL削除
旧マスタの巻き戻しに必要な旧マスタのWALが削除されているいる場合、pg_rewindは失敗します。例えば旧マスタが障害により大量更新の途中で異常終了した場合などに発生します。pg_rewind実行時に次のようなエラーが発生します。could not open file "/home/pg96/pg96_data/pg_xlog/0000000D00000002000000CF": No such file or directory could not find previous WAL record at 2/CF000140 Failure, exitingpg_rewindが成功するかどうかは検証(dry-runオプション)にて事前に確認する事ができます。$ pg_rewind -D $PGDATA --source-server="host=server2 port=5432" --dry-run servers diverged at WAL position 0/5015B70 on timeline 1 rewinding from last common checkpoint at 0/5015B00 on timeline 1 Done! | メッセージはdry-runオプションが無い場合と同じです。 | pg_rewindでエラーが発生する場合(スレーブのWAL削除)は、この検証にて確認できますが、 | pg_rewindでエラーが発生しないで、後から発生する場合(マスタのWAL削除)は検知できません。 | 検証の仕様について認識下さい。
新マスタのWAL削除
pg_rewind後、新スレーブは新マスタのWALを適用することで、新マスタと同期します。新マスタに昇格時のWALファイルが残っていない場合、新スレーブは追い付きができず、次のエラーがサーバログに出力され続けます。対策としてレプリケーションスロットの有効化が有力ですが、新マスタのWAL領域の枯渇にご注意下さい。ERROR: requested WAL segment 0000000D00000000000000F3 has already been removedタイムラインの巻き戻し
pg_rewindはPostgreSQL9.6からタイムラインの巻き戻しができるよになっています。これによりスプリットブレインが発生しても、新マスタをスレーブに戻すことが可能です。pg_rewindが不要な場合
pg_rewindはターゲットとソースクラスタのタイムラインIDが分岐した場合に実行が必要です。そのためタイムラインが枝分かれしなかった場合、pg_rewindを実行する必要はありません。例えばpg_rewind実行時に次のようなメッセージが出た場合、pg_rewindは実行せずに、以降の操作を継続します。servers diverged at WAL position 0/503A428 on timeline 2 no rewind required
$ psql -h server1 -U postgres postgres -c "INSERT INTO test1_t VALUES ( 1 )"
Cancel request sent
WARNING: canceling wait for synchronous replication due to user request
DETAIL: ** The transaction has already committed locally, but might not have been replicated to the standby. **
INSERT 0 1
※Ctrl+Cをキーインする等、意図的にキャンセルしない限り、応答が返ってきません。
$ pg_ctl -w -m immediate stop
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - no response
$ vi $PGDATA/postgresql.conf
[変更前]
synchronous_standby_names = '*'
[変更後]
synchronous_standby_names = ''
$ pg_ctl reload
$ psql -At -c "SELECT sync_state FROM pg_stat_replication;"
async
これでマスタが更新処理が可能な状態に復旧しました。 ただしシングル状態であるため、フェイルオーバと同様の作業を行います。
■同期モードについて
スレーブが複数ある場合、障害時にどのスレーブを新マスタに昇格するかが重要になります。 基本的には同期運用しているスレーブを昇格すべきですが、同期対象も複数選べるため、どの同期スレーブを優先するかが問題です。
これに対しPostgreSQLは、通常の同期運用では、同期優先度を明確にすることで、昇格すべきスレーブを絞っています。 しかし同期優先度が明確な場合、同期対象スレーブの性能に、マスタ側も大きく影響を受けます。 そのため複数同期においても、最も処理が進んでいるスレーブを同期対象とすることでスレーブの影響を抑えるクォーラムコミットという機能が用意されています。
ただしクォーラムコミットの場合、最も同期が進んでいるスレーブがその時によって異なるため、運用に工夫が必要です。 性能面と運用面のトレードオフを考慮した上で、同期モードを選択する必要があります。
synchronous_standby_names = 'FIRST X (standby_name1, standby_name2, ...)'
通常の同期モードで運用する場合、上記のようにsynchronous_standby_namesを設定します。 FIRSTは通常の同期モードの選択であり、省略可能です。 数字Xは同期対象のスレーブ数であり、カッコ内の左から順に優先して同期対象になります。 カッコ内のスレーブ名は、同期候補のスレーブです。
以下のような設定の場合、slave1~3が同期運用、slave4~5は潜在的な同期運用がされます。 潜在的な同期運用は、普段は非同期として運用され、必要な場合は同期へ昇格されます。 例えばslave2の環境がマスタから切断された場合、残ったスレーブで優先度が高いslave1,slave3,slave4が同期運用されます。 (つまり非同期運用であったslave4が同期運用へ昇格されます)
synchronous_standby_names = 'FIRST 3 (slave1, slave2, slave3, slave4, slave5)'
障害発生時は、同期の優先度が高いスレーブを新マスタへ昇格させます。 通常の同期モードではカッコ内の左側が優先度が高く、右に行くほど優先度が低くなります。
synchronous_standby_names = 'ANY X (standby_name1, standby_name2, ...)'
クォーラムコミットで運用する場合、上記のようにsynchronous_standby_namesを設定します。 ANYはクォーラムコミットの選択であり、こちらは省略できません。 数字Xは同期対象のスレーブ数であり、カッコ内のうちX台のスレーブの同期を待ちます。 カッコ内のスレーブ名は、同期候補のスレーブです。
以下のような設定の場合、slave1~5の全てがクォーラムコミットで運用されます。 マスタ側で更新があった場合、slave1~5のうちいずれか計3台でマスタからの更新反映が完了すれば、同期したとみなします。
synchronous_standby_names = 'ANY 3 (slave1, slave2, slave3, slave4, slave5)'
障害発生時、新マスタへ昇格させるスレーブを選択するのには注意が必要です。 確認方法は「SELECT pg_control_recovery();」コマンドにより現在のWAL位置を比較することです。 同期が進んでいる側はWALも進んでいます。これにより判断が可能です。
■対処一覧 大別すると4種類の対処方法が考えられます。
ID | 障害箇所 | 障害状況 | 対処 |
---|---|---|---|
1
|
マスタ
|
マスタとスレーブの関係が崩れており再構成が必要
|
pg_basebackupコマンドを使用してフェイルバック
|
2
|
マスタ
|
マスタとスレーブの関係は巻き戻しで復旧可能
|
pg_rewindコマンドを使用してスイッチバック
|
3
|
マスタ
|
マスタとスレーブの切り替え可能
|
pg_rewindコマンドを使用しないでスイッチバック
|
4
|
スレーブ
|
マスタとスレーブの連携再開可能
|
同期式の場合、残ったスレーブを非同期式に切り替え
|
フェイルオーバについて記載します。
次のような状況を想定しています。
$ pg_ctl -w -m immediate stop
$ kill -9 `head -1 $PGDATA/postmaster.pid`
$ pg_isready -h server1 -U postgres -d postgres
server1:5432 - no response
[server2]
=# SELECT pg_control_recovery();
pg_control_recovery
--------------------------
(0/1301F348,1,0/0,0/0,f)
(1 row)
[server3]
=# SELECT pg_control_recovery();
pg_control_recovery
--------------------------
(0/1301F170,1,0/0,0/0,f)
(1 row)
※WALはserver2が進んでいるため、server2を昇格させる。
server1:1301F348
server2:1301F170
$ pg_ctl promote
ただしこの時点ではsynchronous_standby_namesパラメータに値が設定されている場合、新マスタで更新処理ができない状態です。
$ vi $PGDATA/postgresql.conf
[編集後]
synchronous_standby_names = ''
$ pg_ctl reload
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - accepting connections
以上でファイルオーバーは完了です。
pg_basebackupを使用したフェイルバックについて記載します。 昇格されなかったスレーブは、新マスタのスレーブとして運用するために再設定が必要です。 旧マスタから新スレーブへの構築は初期構築手順とほぼ同じです。
マスタ障害発生によるフェイルオーバ後、旧マスタを新スレーブとしたレプリケーション構成図
■パラメータ
pg_basebackupに必要な設定を記載します。
サーバ | 設定ファイル | パラメータ | 設定値 | 内容 |
---|---|---|---|---|
マスタ | postgresql.conf | listen_address | 0.0.0.0 | 全てのIPアドレス(v4)からの接続を受け付ける |
マスタ | postgresql.conf | max_wal_senders | 2 | WALストリームオプションを付与する場合は、2以上を設定 |
■pg_basebackupコマンド
pg_basebackupコマンドの主なオプションは次の通りです。
¶ オプション 内容 -D <directory> 出力を書き出すディレクトリを指定。 -X <method>--xlog-method=<method> 必要なWALファイルをバックアップに含める。method(収集方式)は以下から選択。
- fetch :WALファイルは最後に収集
- stream:バックアップ作成中に同時にWALをストリームで収集
運用中にpg_basebackupを実行する場合には stream を指定する。fetch (最後に収集)では、必要なWALファイルが削除される可能性があるため。 -S <slot_name>--slot=<slot_name> WALストリーミングの収集に指定したレプリケーションスロットを使用。-X stream とセットで指定。必要なWALファイルが削除されるのを防ぐ事を目的とする。運用中にpg_basebackupを実行する場合に使用を検討する。以下に注意する。
- 事前にレプリケーションスロットを作成する必要がある
- マスタのWAL領域の空きが十分である事を確認する
-R--write-recovery-conf 最低限のrecovery.confを作成。必要に応じてrecovery.confを加筆修正。 -r <rate>--max-rate=<rate> サーバから転送されるデータの最大転送速度を指定。運用中にpg_basebackupを実行する場合に使用を検討する。転送速度を抑える事で、マスタに対する影響を制限する事を目的とする。 -P 進行状況報告を有効化。pg_basebackup処理中におおよその進行状況を報告する。運用中に実行した場合、データベースクラスタのサイズが増加して進行状況が100%を超える場合がある。 -v 冗長モードを有効化。進行状況報告も有効な場合、現在処理中のファイル名を出力。
pg_basebackupでレプリケーションスロットが使用できます。 WAL収集方式に stream を指定する事でWALをほぼ確保できますが、スロットを指定する事でより確実になります。 スロットを使用する運用であれば、この段階で作成するのが有力です。
■フェイルバック手順
次のような状況を想定しています。
pg_basebackupにスロットを指定する場合を記載します。
$ psql
[server1:旧マスタ→新スレーブ用のレプリケーションスロット]
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,144/EEFC8940)
(1 行)
[server3:旧スレーブ→新スレーブ用のレプリケーションスロット]
=# SELECT pg_create_physical_replication_slot('slot_server3',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server3,144/EEFC8940)
(1 行)
$ vi $PGDATA/recovery.conf
[編集前]
primary_conninfo = 'user=rep_user host=server1 port=5432 application_name=slave_server3 asslmode=prefer sslcompression=1'
[編集前]
primary_conninfo = 'user=rep_user host=server2 port=5432 application_name=slave_server3 asslmode=prefer sslcompression=1'
$ pg_ctl restart
$ rm -rf $PGDATA/*
$ pg_basebackup -h server2 -U rep_user -D $PGDATA -X stream -S slot_server1 -P -v -R
transaction log start point: 144/F3000028 on timeline 17
pg_basebackup: starting background WAL receiver
10503022/10503022 kB (100%), 1/1 tablespace
transaction log end point: 144/F3000130
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=rep_user host=server2 port=5432 sslmode=prefer sslcompression=1'
primary_slot_name = 'slot_server1'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=rep_user host=server2 port=5432 application_name=slave_server1 asslmode=prefer sslcompression=1'
primary_slot_name = 'slot_server1'
recovery_target_timeline = latest
$ vi $PGDATA/postgresql.conf
[編集後]
synchronous_standby_names = ''
shared_preload_libraries = ''
$ pg_ctl start
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 1866
usesysid | 16384
usename | rep_user
application_name | slave_server3
client_addr | ::1
client_hostname |
client_port | 46840
backend_start | 2018-02-13 14:06:04.475996+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/B067570
write_lsn | 0/B067570
flush_lsn | 0/B067570
replay_lsn | 0/B067570
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
-[ RECORD 2 ]----+------------------------------
pid | 1776
usesysid | 16384
usename | rep_user
application_name | slave_server1
client_addr | ::1
client_hostname |
client_port | 46838
backend_start | 2018-02-13 14:05:53.580727+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/B067570
write_lsn | 0/B067570
flush_lsn | 0/B067570
replay_lsn | 0/B067570
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]----
slot_name | slot1
active | t --- アクティブ
-[ RECORD 2 ]----
slot_name | slot2
active | t --- アクティブ
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_standby_names = ''
[編集後]
synchronous_standby_names = 'ANY 1 (slave_server1, slave_server3)'
$ pg_ctl reload
$ psql -h server2 -U postgres postgres -c "SELECT * FROM pg_stat_replication" -x
-[ RECORD 1 ]----+------------------------------
pid | 1866
usesysid | 16384
usename | rep_user
application_name | slave2
client_addr | ::1
client_hostname |
client_port | 46840
backend_start | 2018-02-13 14:06:04.475996+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/B067570
write_lsn | 0/B067570
flush_lsn | 0/B067570
replay_lsn | 0/B067570
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | quorum --- クォーラムコミット
-[ RECORD 2 ]----+------------------------------
pid | 1776
usesysid | 16384
usename | rep_user
application_name | slave1
client_addr | ::1
client_hostname |
client_port | 46838
backend_start | 2018-02-13 14:05:53.580727+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/B067570
write_lsn | 0/B067570
flush_lsn | 0/B067570
replay_lsn | 0/B067570
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | quorum --- クォーラムコミット
これにて、以下の構成に復旧しました。
スイッチオーバについて記載します。
■スイッチオーバ手順
計画停止におけるマスタ/スレーブの切り替え手順です。 pg_basebackupやpg_rewindが不要であるためシンプルな手順です。
次のような状況を想定しています。
$ pg_ctl stop -m fast
$ pg_ctl promote
以降の手順はフェイルオーバの場合と同様であるため省略します。
$ psql =# SELECT pg_drop_replication_slot('slot_server2'); pg_create_physical_replication_slot ------------------------------------- (1 row) =# SELECT pg_drop_replication_slot('slot_server3'); pg_create_physical_replication_slot ------------------------------------- (1 row) =# SELECT slot_name FROM pg_replication_slots ; (0 rows)
以上でスイッチオーバが完了しました。
pg_rewindを使用したスイッチバックについて記載します。
pg_rewindはタイムラインのずれたレプリケーションを再同期させる機能です。 実行後、ターゲットクラスタはソースクラスタと置き換えられた状態になります。 そのためpg_rewind後の操作は、通常のフェイルオーバ時と同じです。 タイムラインの分岐点からソースクラスタのWALを適用するため、更新量が少なければpg_basebackupによる複製より高速です。 これによりフェイルオーバ時、旧マスタを容易に新スレーブとして起動させることができます。
■関連パラメータ
pg_rewindに必要な設定を記載します。
サーバ | 設定ファイル | パラメータ | 設定値 | 内容 |
---|---|---|---|---|
マスタ | postgresql.conf | full_page_writes | on | チェックポイント後の更新時、ディスクページの全内容をWALに書き込む。 |
マスタ | postgresql.conf | wal_log_hints | on | ヒントビット更新時もfull_page_writesを実行する。 |
■pg_rewindコマンド
pg_rewindコマンドのの主なオプションは次の通りです。
¶ オプション 内容 D <ターゲットクラスタ> pg_rewindを実行し、ソースクラスタの内容に置き換えるクラスタを指定する。 source-server="<ソースクラスタ>" 同期対象であるソースクラスタを指定します。主に次の接続文字列を使用します。
- host:ソースクラスタのホスト名またはIPアドレス
- port:ソースクラスタのポート番号
- dbname:ソースクラスタの接続先データベース名
- user:ソースクラスタの接続先ユーザ
P 進行状況をレポートとして表示する。
■スイッチバック手順
※事前にマスタ/スレーブで(1) 関連パラメータの設定がされていることを前提とします。
$ pg_ctl start -w $ pg_ctl stop -m fast -w
$ pg_rewind -D $PGDATA --source-server="host=server2 port=5432" servers diverged at WAL position 0/5015B70 on timeline 1 rewinding from last common checkpoint at 0/5015B00 on timeline 1 Done!
$ vi $PGDATA/recovery.conf [編集後] standby_mode = 'on' primary_conninfo = 'host=server2 port=5432 user=rep_user' recovery_target_timeline = 'latest'
[新マスタ] $ export LANG=C $ pg_controldata | grep " TimeLineID" Latest checkpoint's TimeLineID: 2 $ ssh server1 $PGHOME/bin/pg_controldata $PGDATA | grep " TimeLineID" Latest checkpoint's TimeLineID: 2
■pg_rewind使用時の注意点
正常停止が必要
pg_rewindを実行するデータベースクラスタは正常終了しなければいけません。物理障害等により正常停止できない場合、pg_rewindは使用できません。pg_basebackupを使用します。同一タイムラインの場合は実施不可
pg_promoteを実行せずに旧スレーブを新マスタにした場合、新マスタのタイムラインIDは変わらないため、新マスタと旧マスタのタイムラインIDは同じ状態です。この場合は、pg_rewindは実行できません。実行時期と所要時間の関係
pg_rewindによるフェイルバックの所要時間は2つの要素から構成されます。Step1. pg_rewindによる巻き戻し (旧マスタのWALを使用)Step2. WAL適用による追い付き (新マスタのWALを使用)新マスタで大量更新がある場合は、Step1は短時間で終了してもStep2で時間がかかります。結果として、pg_basebackupの方が効率が良い場合もあり得ます。また新マスタ昇格時のWALが削除されている場合は、後述するようにStep2でエラーとなる可能性もあります。その場合はpg_basebackupが必要となります。pg_rewindはフェイルオーバー後、あまり時間を置かずに実行する事がポイントです。旧マスタのWAL削除
旧マスタの巻き戻しに必要な旧マスタのWALが削除されているいる場合、pg_rewindは失敗します。例えば旧マスタが障害により大量更新の途中で異常終了した場合などに発生します。pg_rewind実行時に次のようなエラーが発生します。could not open file "/home/pg96/pg96_data/pg_xlog/0000000D00000002000000CF": No such file or directory could not find previous WAL record at 2/CF000140 Failure, exitingpg_rewindが成功するかどうかは検証(dry-runオプション)にて事前に確認する事ができます。$ pg_rewind -D $PGDATA --source-server="host=server2 port=5432" --dry-run servers diverged at WAL position 0/5015B70 on timeline 1 rewinding from last common checkpoint at 0/5015B00 on timeline 1 Done! | メッセージはdry-runオプションが無い場合と同じです。 | pg_rewindでエラーが発生する場合(スレーブのWAL削除)は、この検証にて確認できますが、 | pg_rewindでエラーが発生しないで、後から発生する場合(マスタのWAL削除)は検知できません。 | 検証の仕様について認識下さい。
新マスタのWAL削除
pg_rewind後、新スレーブは新マスタのWALを適用することで、新マスタと同期します。新マスタに昇格時のWALファイルが残っていない場合、新スレーブは追い付きができず、次のエラーがサーバログに出力され続けます。対策としてレプリケーションスロットの有効化が有力ですが、新マスタのWAL領域の枯渇にご注意下さい。ERROR: requested WAL segment 0000000D00000000000000F3 has already been removedタイムラインの巻き戻し
pg_rewindはPostgreSQL9.6からタイムラインの巻き戻しができるよになっています。これによりスプリットブレインが発生しても、新マスタをスレーブに戻すことが可能です。pg_rewindが不要な場合
pg_rewindはターゲットとソースクラスタのタイムラインIDが分岐した場合に実行が必要です。そのためタイムラインが枝分かれしなかった場合、pg_rewindを実行する必要はありません。例えばpg_rewind実行時に次のようなメッセージが出た場合、pg_rewindは実行せずに、以降の操作を継続します。servers diverged at WAL position 0/503A428 on timeline 2 no rewind required
$ psql -h server1 -U postgres postgres -c "INSERT INTO test1_t VALUES ( 1 )"
Cancel request sent
WARNING: canceling wait for synchronous replication due to user request
DETAIL: ** The transaction has already committed locally, but might not have been replicated to the standby. **
INSERT 0 1
※Ctrl+Cをキーインする等、意図的にキャンセルしない限り、応答が返ってきません。
$ pg_ctl -w -m immediate stop
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - no response
$ vi $PGDATA/postgresql.conf
[変更前]
synchronous_standby_names = 'FIRST X (standby_name1, standby_name2, ...)'
[変更後]
synchronous_standby_names = ''
$ pg_ctl reload
$ psql -At -c "SELECT sync_state FROM pg_stat_replication;"
async
これでマスタが更新処理が可能な状態に復旧しました。 ただしシングル状態であるため、フェイルオーバと同様の作業を行います。
■前提 以降の手順では次の前提とします。
■対処一覧 大別すると3種類の対処方法が考えられます。
ID | 障害箇所 | 障害状況 | pg_basebackupとpg_rewindの使い分け |
---|---|---|---|
1
|
マスタ
|
マスタとスレーブの関係が崩れており再構成が必要
|
pg_basebackupコマンドを使用してフェイルバック
|
2
|
マスタ
|
マスタとスレーブの関係は巻き戻しで復旧可能
|
pg_rewindコマンドを使用してスイッチバック
|
3
|
マスタ
|
マスタとスレーブの切り替え可能
|
pg_rewindコマンドを使用しないでスイッチバック
|
4
|
スレーブ1
|
マスタとスレーブの連携再開可能
|
同期式の場合は非同期式に切り替え
|
5
|
スレーブ1
|
マスタとスレーブの連携再開不可能
|
pg_basebackupコマンドを使用してフェイルバック
|
6
|
スレーブ2
|
マスタとスレーブの連携再開可能
|
pg_rewindコマンドを使用しないでフェイルバック
|
7
|
スレーブ2
|
マスタとスレーブの連携再開不可能
|
pg_basebackupコマンドを使用してフェイルバック
|
8
|
マスタ、スレーブ1
|
マスタとスレーブ2の連携再開可能
|
pg_rewindコマンドを使用しないでフェイルバック
|
9
|
マスタ、スレーブ1
|
マスタとスレーブ2の連携再開不可能
|
pg_basebackupコマンドを使用してフェイルバック
|
フェイルオーバについて記載します。
$ pg_ctl -w -m immediate stop
$ kill -9 `head -1 $PGDATA/postmaster.pid`
$ pg_isready -h server1 -U postgres -d postgres
server1:5432 - no response
$ pg_ctl promote
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - accepting connections
$ pg_isready -h server3 -U postgres -d postgres
server3:5432 - accepting connections
以上でファイルオーバーは完了です。
$ pg_ctl -w -m immediate stop
$ kill -9 `head -1 $PGDATA/postmaster.pid`
$ pg_ctl -w -m immediate stop
$ kill -9 `head -1 $PGDATA/postmaster.pid`
$ pg_isready -h server1 -U postgres -d postgres
server1:5432 - no response
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - no response
$ pg_ctl promote
$ pg_isready -h server3 -U postgres -d postgres
server3:5432 - accepting connections
以上でファイルオーバーは完了です。
フェイルバックについて記載します。
pg_basebackupを使用したフェイルバックについて記載します。 初期構築手順とほぼ同じです。
マスタ障害発生によるフェイルオーバ後、旧マスタを同期モード新スレーブとしたレプリケーション構成図
■関連パラメータ
pg_basebackupに必要な設定を記載します。
サーバ | 設定ファイル | パラメータ | 設定値 | 内容 |
---|---|---|---|---|
マスタ | postgresql.conf | listen_address | 0.0.0.0 | 全てのIPアドレス(v4)からの接続を受け付ける |
マスタ | postgresql.conf | max_wal_senders | 2 | WALストリームオプションを付与する場合は、2以上を設定 |
■pg_basebackupコマンド
pg_basebackupコマンドの主なオプションは次の通りです。
¶ オプション 内容 -D <directory> 出力を書き出すディレクトリを指定。 -X <method>--wal-method=<method> 必要なWALファイルをバックアップに含める。method(収集方式)は以下から選択。
- fetch :WALファイルは最後に収集
- stream:バックアップ作成中に同時にWALをストリームで収集
運用中にpg_basebackupを実行する場合には stream を指定する。fetch (最後に収集)では、必要なWALファイルが削除される可能性があるため。 -S <slot_name>--slot=<slot_name> WALストリーミングの収集に指定したレプリケーションスロットを使用。-X stream とセットで指定。必要なWALファイルが削除されるのを防ぐ事を目的とする。運用中にpg_basebackupを実行する場合に使用を検討する。以下に注意する。
- 事前にレプリケーションスロットを作成する必要がある
- マスタのWAL領域の空きが十分である事を確認する
-R--write-recovery-conf 最低限のrecovery.confを作成。必要に応じてrecovery.confを加筆修正。 -r <rate>--max-rate=<rate> サーバから転送されるデータの最大転送速度を指定。運用中にpg_basebackupを実行する場合に使用を検討する。転送速度を抑える事で、マスタに対する影響を制限する事を目的とする。 -P 進行状況報告を有効化。pg_basebackup処理中におおよその進行状況を報告する。運用中に実行した場合、データベースクラスタのサイズが増加して進行状況が100%を超える場合がある。 -v 冗長モードを有効化。進行状況報告も有効な場合、現在処理中のファイル名を出力。
pg_basebackupでレプリケーションスロットが使用できます。 WAL収集方式に stream を指定する事でWALをほぼ確保できますが、スロットを指定する事でより確実になります。 スロットを使用する運用であれば、この段階で作成するのが有力です。
■フェイルバック手順
pg_basebackupにスロットを指定する場合を記載します。
$ rm -rf $PGDATA/*
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server2',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server2,0/96000090)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/96000090 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ pg_basebackup -h server2 -U repuser -D $PGDATA -X stream -S slot_server2 -P -v -R
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/99000028 on timeline 32
pg_basebackup: starting background WAL receiver
138336/138336 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/99000130
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
起動
$ pg_ctl start
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server1'
設定を反映
$ pg_ctl reload
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,0/9A000060)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/9A000060 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
再起動
$ pg_ctl restart
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 46558
usesysid | 16384
usename | repuser
application_name | server1
client_addr | <server1IP>
client_hostname |
client_port | 64897
backend_start | 2018-02-09 11:22:23.535181+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/9A000140
write_lsn | 0/9A000140
flush_lsn | 0/9A000140
replay_lsn | 0/9A000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
active | t --- アクティブ
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 97277
usesysid | 16384
usename | repuser
application_name | server3
client_addr | <server3IP>
client_hostname |
client_port | 52882
backend_start | 2018-02-09 11:55:20.971553+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/9A000140
write_lsn | 0/9A000140
flush_lsn | 0/9A000140
replay_lsn | 0/9A000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
これにて、以下の構成に復旧しました。
pg_basebackupを使用したフェイルバックについて記載します。 初期構築手順とほぼ同じです。
マスタインスタンス障害発生によるフェイルオーバ後、旧マスタを同期モード新スレーブとしたレプリケーション構成図
■フェイルバック手順
$ pg_ctl stop
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server2',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server2,0/96000090)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/96000090 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = on
synchronous_standby_names = '*'
shared_preload_libraries = 'pg_stat_statements,pg_statsinfo'
[編集後]
synchronous_commit = off
synchronous_standby_names = ''
shared_preload_libraries = ''
$ pg_ctl start
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server1'
設定を反映
$ pg_ctl reload
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,0/9A000060)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/9A000060 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
再起動
$ pg_ctl restart
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 12505
usesysid | 16384
usename | repuser
application_name | server1
client_addr | <server1IP>
client_hostname |
client_port | 58195
backend_start | 2018-02-07 21:55:00.722782+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 86651
usesysid | 16384
usename | repuser
application_name | server3
client_addr | <server3IP>
client_hostname |
client_port | 18206
backend_start | 2018-02-07 22:13:40.199786+09
backend_xmin | 606
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
これにて、以下の構成に復旧しました。
pg_basebackupを使用したフェイルバックについて記載します。 初期構築手順とほぼ同じです。
マスタ(server1)/スレーブ1(server2)サイトに障害が発生し、フェイルオーバ後、 マスタ(server1)/スレーブ1(server2)を元の状態に復旧したレプリケーション構成図
pg_basebackupに必要な設定については、以下と同様であるため省略します。
「旧マスタを同期モードスレーブとしてフェイルバック」
pg_basebackupでレプリケーションスロットが使用できます。 WAL収集方式に stream を指定する事でWALをほぼ確保できますが、スロットを指定する事でより確実になります。 スロットを使用する運用であれば、この段階で作成するのが有力です。
■フェイルバック手順
pg_basebackupにスロットを指定する場合を記載します。
$ rm -rf $PGDATA/*
$ rm -rf $PGDATA/*
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server3',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server3,0/AD000090)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server3
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/AD000090 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ pg_basebackup -h server3 -U repuser -D $PGDATA -X stream -S slot_server3 -P -v -R
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/3B000028 on timeline 2
pg_basebackup: starting background WAL receiver
203852/203852 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/3B000130
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server3 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server3'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server3 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server3'
recovery_target_timeline = 'latest'
$ pg_ctl start
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,0/AE000028)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/AE000028 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ pg_basebackup -h server1 -U repuser -D $PGDATA -X stream -S slot_server1 -P -v -R
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/3B000028 on timeline 2
pg_basebackup: starting background WAL receiver
203852/203852 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/3B000130
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 application_name=server2 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
$ pg_ctl start
$ pg_ctl stop
$ pg_ctl promote
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server2',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server2,0/AE000028)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/AE000028 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ mv $PGDATA/recovery.done $PGDATA/recovery.conf
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
起動
$ pg_ctl start
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server2'
設定を反映
$ pg_ctl reload
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 12505
usesysid | 16384
usename | repuser
application_name | server2
client_addr | <server2IP>
client_hostname |
client_port | 58195
backend_start | 2018-02-07 21:55:00.722782+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
active | t --- アクティブ
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 86651
usesysid | 16384
usename | repuser
application_name | server3
client_addr | <server3IP>
client_hostname |
client_port | 18206
backend_start | 2018-02-07 22:13:40.199786+09
backend_xmin | 606
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
slot_name | active
--------------+--------
slot_server2 | t
(1 row)
$ psql =# SELECT slot_name,active FROM pg_replication_slots; slot_name | active --------------+-------- slot_server3 | f (1 row) =# SELECT pg_drop_replication_slot('slot_server3'); pg_create_physical_replication_slot ------------------------------------- (1 row) =# SELECT slot_name FROM pg_replication_slots; (0 rows)
これにて、以下の構成に復旧しました。
スイッチオーバについて記載します。
■スイッチオーバ手順
計画停止におけるマスタ/スレーブの切り替え手順です。 pg_basebackupやpg_rewindが不要であるためシンプルな手順です。
$ pg_ctl stop -m fast
$ pg_ctl promote
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server2',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server2,0/B40000C8)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/B40000C8 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = on
synchronous_standby_names = '*'
shared_preload_libraries = 'pg_stat_statements,pg_statsinfo'
[編集後]
synchronous_commit = off
synchronous_standby_names = ''
shared_preload_libraries = ''
再起動
$ pg_ctl restart
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,0/B4000028)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/B4000028 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server1'
設定を反映
$ pg_ctl reload
$ vi $PGDATA/recovery.conf
[編集前]
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=12079 sslmode=disable sslcompression=1 target_session_attrs=any'
[編集後]
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=12079 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
再起動
$ pg_ctl restart
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 12505
usesysid | 16384
usename | repuser
application_name | server1
client_addr | <server1IP>
client_hostname |
client_port | 58195
backend_start | 2018-02-07 21:55:00.722782+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
active | t --- アクティブ
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 86651
usesysid | 16384
usename | repuser
application_name | server3
client_addr | <server3IP>
client_hostname |
client_port | 18206
backend_start | 2018-02-07 22:13:40.199786+09
backend_xmin | 606
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-----------
slot_name | slot_server1
active | f
以上でスイッチオーバが完了しました。
■スイッチオーバ手順
計画停止におけるマスタ/スレーブの切り替え手順です。 pg_basebackupやpg_rewindが不要であるためシンプルな手順です。
$ pg_ctl stop -m fast
$ pg_ctl promote
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server3',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server3,0/BA0000C8)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server3
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/BA0000C8 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server3 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server3'
recovery_target_timeline = 'latest'
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = on
synchronous_standby_names = '*'
shared_preload_libraries = 'pg_stat_statements,pg_statsinfo'
[編集後]
synchronous_commit = off
synchronous_standby_names = ''
shared_preload_libraries = ''
$ pg_ctl start
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,0/BA000028)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/BA000028 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
再起動
$ pg_ctl restart
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 12505
usesysid | 16384
usename | repuser
application_name | server1
client_addr | <server1IP>
client_hostname |
client_port | 58195
backend_start | 2018-02-07 21:55:00.722782+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server3
active | t --- アクティブ
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 86651
usesysid | 16384
usename | repuser
application_name | server2
client_addr | <server2IP>
client_hostname |
client_port | 18206
backend_start | 2018-02-07 22:13:40.199786+09
backend_xmin | 606
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
active | t --- アクティブ
以上でスイッチオーバが完了しました。
スイッチバックについて記載します。
同期モードスレーブになっていた旧マスタをマスタに復帰させるスイッチバックついて記載します。
同期モードスレーブになっていた旧マスタをマスタとして復帰させたレプリケーション構成図
■スイッチバック手順
$ pg_ctl stop
$ pg_ctl promote
$ cp $PGDATA/recovery.done $PGDATA/recovery.conf
$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 application_name=server2 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server2'
設定を反映
$ pg_ctl reload
$ pg_ctl start
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
$ pg_ctl restart
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 12505
usesysid | 16384
usename | repuser
application_name | server2
client_addr | <server2IP>
client_hostname |
client_port | 58195
backend_start | 2018-02-07 21:55:00.722782+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 86651
usesysid | 16384
usename | repuser
application_name | server3
client_addr | <serrver3IP>
client_hostname |
client_port | 18206
backend_start | 2018-02-07 22:13:40.199786+09
backend_xmin | 606
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
以上でスイッチバックが完了しました。
非同期モードスレーブからマスタへスイッチバックついて記載します。
非同期モードスレーブになっていた旧マスタをマスタとして復帰させたレプリケーション構成図
■スイッチバック手順
$ pg_ctl stop
$ pg_ctl promote
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server2',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server2,0/BB0000C8)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/BB0000C8 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ cp $PGDATA/recovery.done $PGDATA/recovery.conf
$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
再起動
$ pg_ctl restart
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server2'
$ pg_ctl reload
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 12505
usesysid | 16384
usename | repuser
application_name | server2
client_addr | <server2IP>
client_hostname |
client_port | 58195
backend_start | 2018-02-07 21:55:00.722782+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
active | t --- アクティブ
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 86651
usesysid | 16384
usename | repuser
application_name | server3
client_addr | <server3IP>
client_hostname |
client_port | 18206
backend_start | 2018-02-07 22:13:40.199786+09
backend_xmin | 606
state | streaming --- ストリーミング中
sent_lsn | 0/6A000818
write_lsn | 0/6A000818
flush_lsn | 0/6A000818
replay_lsn | 0/6A000818
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
=# SELECT slot_name,active FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
active | t --- アクティブ
$ psql =# SELECT slot_name FROM pg_replication_slots; slot_name -------------- slot_server3 (1 row) =# SELECT pg_drop_replication_slot('slot_server3'); pg_create_physical_replication_slot ------------------------------------- (1 row) =# SELECT slot_name FROM pg_replication_slots; (0 rows)
以上でスイッチバックが完了しました。
pg_rewindを使用したスイッチバックについて記載します。
pg_rewindはタイムラインのずれたレプリケーションを再同期させる機能です。 実行後、ターゲットクラスタはソースクラスタと置き換えられた状態になります。 そのためpg_rewind後の操作は、通常のスイッチオーバ時と同じです。 タイムラインの分岐点からソースクラスタのWALを適用するため、更新量が少なければpg_basebackによる複製より高速です。 これによりスイッチオーバ時、旧マスタを容易に新スレーブとして起動させることができます。
スイッチオーバー後、pg_rewindで旧マスタを新スレーブとして戻したレプリケーション構成図
■関連パラメータ
pg_rewindに必要な設定を記載します。
サーバ | 設定ファイル | パラメータ | 設定値 | 内容 |
---|---|---|---|---|
pg_rewind実行サーバ | postgresql.conf | full_page_writes | on | チェックポイント後の更新時、ディスクページの全内容をWALに書き込む。 |
pg_rewind実行サーバ | postgresql.conf | wal_log_hints | on | ヒントビット更新時もfull_page_writesを実行する。 |
■pg_rewindコマンド
pg_rewindコマンドの主なオプションは次の通りです。
¶ オプション 内容 D <ターゲットクラスタ> pg_rewindを実行し、ソースクラスタの内容に置き換えるクラスタを指定する。 source-server="<ソースクラスタ>" 同期対象であるソースクラスタを指定します。主に次の接続文字列を使用します。
- host:ソースクラスタのホスト名またはIPアドレス
- port:ソースクラスタのポート番号
- dbname:ソースクラスタの接続先データベース名
- user:ソースクラスタの接続先ユーザ
P 進行状況をレポートとして表示する。
■フェイルバック手順
※事前にマスタで関連パラメータの設定がされていることを前提とします。
$ pg_ctl stop -m fast
$ pg_ctl promote
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = on
synchronous_standby_names = '*'
shared_preload_libraries = 'pg_stat_statements,pg_statsinfo'
[編集後]
synchronous_commit = off
synchronous_standby_names = ''
shared_preload_libraries = ''
$ pg_ctl start
$ psql
=# select * from test;
col1
------
1
3
2
(3 rows)
=# insert into test values(4);
INSERT 0 1
=# select * from test;
col1
------
1
3
2
4
(4 rows)
$ pg_ctl stop -m fast
$ pg_rewind -D $PGDATA --source-server="host=server2 port=5432"
servers diverged at WAL location 0/D0000098 on timeline 45
rewinding from last common checkpoint at 0/D0000028 on timeline 45
Done!
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server2',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server2,0/D00000C8)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/D00000C8 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ cp $PGDATA/recovery.done $PGDATA/recovery.conf
$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 application_name=server1 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
recovery_target_timeline = 'latest'
$ pg_ctl start
$ psql
=# select * from test;
col1
------
1
3
2
(3 rows)
$ vi $PGDATA/postgresql.conf
[編集前]
synchronous_commit = off
synchronous_standby_names = ''
[編集後]
synchronous_commit = on
synchronous_standby_names = 'server1'
設定を反映
$ pg_ctl reload
$ psql
=# SELECT pg_create_physical_replication_slot('slot_server1',true); -- 第2パラメータにtrueを指定
pg_create_physical_replication_slot
-------------------------------------
(slot_server1,0/D0000028)
(1 行)
=# \x on
拡張表示は on です。
=# SELECT * FROM pg_replication_slots;
-[ RECORD 1 ]-------+-------------
slot_name | slot_server1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | f -- まだスロットは使用されてないため false
active_pid |
xmin |
catalog_xmin |
restart_lsn | 0/D0000028 -- trueの指定により、作成直後からrestat_lsnを認識
confirmed_flush_lsn |
$ vi $PGDATA/recovery.conf
[編集前]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=5432 sslmode=disable sslcompression=1 target_session_attrs=any'
[編集後]
standby_mode = 'on'
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=5432 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
recovery_target_timeline = 'latest'
再起動
$ pg_ctl restart
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 82328
usesysid | 16384
usename | repuser
application_name | server1
client_addr | 172.16.25.111
client_hostname |
client_port | 63206
backend_start | 2018-02-28 22:40:16.337761+09
backend_xmin |
state | streaming --- ストリーミング中
sent_lsn | 0/D0019538
write_lsn | 0/D0019538
flush_lsn | 0/D0019538
replay_lsn | 0/D0019538
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync --- 同期
$ psql
=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 51369
usesysid | 16384
usename | repuser
application_name | server3
client_addr | 172.16.25.143
client_hostname |
client_port | 47281
backend_start | 2018-02-28 23:02:39.56715+09
backend_xmin |
sent_lsn | 0/D0019538
write_lsn | 0/D0019538
flush_lsn | 0/D0019538
replay_lsn | 0/D0019538
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async --- 非同期
[マスタ(server2)] $ export LANG=C $ pg_controldata | grep " TimeLineID" Latest checkpoint's TimeLineID: 46 [スレーブ1(server1)] $ export LANG=C $ pg_controldata | grep " TimeLineID" Latest checkpoint's TimeLineID: 46 [スレーブ2(server3)] $ export LANG=C $ pg_controldata | grep " TimeLineID" Latest checkpoint's TimeLineID: 46
■pg_rewind使用時の注意点
正常停止が必要
pg_rewindを実行するデータベースクラスタは正常終了しなければいけません。物理障害等により正常停止できない場合、pg_rewindは使用できません。pg_basebackupを使用します。同一タイムラインの場合は実施不可
pg_promoteを実行せずに旧スレーブを新マスタにした場合、新マスタのタイムラインIDは変わらないため、新マスタと旧マスタのタイムラインIDは同じ状態です。この場合は、pg_rewindは実行できません。実行時期と所要時間の関係
pg_rewindによるフェイルバックの所要時間は2つの要素から構成されます。Step1. pg_rewindによる巻き戻し (旧マスタのWALを使用)Step2. WAL適用による追い付き (新マスタのWALを使用)新マスタで大量更新がある場合は、Step1は短時間で終了してもStep2で時間がかかります。結果として、pg_basebackupの方が効率が良い場合もあり得ます。また新マスタ昇格時のWALが削除されている場合は、後述するようにStep2でエラーとなる可能性もあります。その場合はpg_basebackupが必要となります。pg_rewindはフェイルオーバー後、あまり時間を置かずに実行する事がポイントです。旧マスタのWAL保管
旧マスタの巻き戻しに必要な旧マスタのWALが削除されている場合、pg_rewindは失敗します。例えば旧マスタが障害により大量更新の途中で異常終了した場合などに発生します。pg_rewind実行時に次のようなエラーが発生します。could not open file "/home/pg96/pg96_data/pg_xlog/0000000D00000002000000CF": No such file or directory could not find previous WAL record at 2/CF000140 Failure, exitingpg_rewindが成功するかどうかは検証(dry-runオプション)にて事前に確認する事ができます。$ pg_rewind -D $PGDATA --source-server="host=server2 port=5432" --dry-run servers diverged at WAL position 0/5015B70 on timeline 1 rewinding from last common checkpoint at 0/5015B00 on timeline 1 Done! | メッセージはdry-runオプションが無い場合と同じです。 | pg_rewindでエラーが発生する場合(スレーブのWAL削除)は、この検証にて確認できますが、 | pg_rewindでエラーが発生しないで、後から発生する場合(マスタのWAL削除)は検知できません。 | 検証の仕様について認識下さい。
新マスタのWAL保管
pg_rewind後、新スレーブは新マスタのWALを適用することで、新マスタと同期します。新マスタに昇格時のWALファイルが残っていない場合、新スレーブは追い付きができず、次のエラーがサーバログに出力され続けます。対策としてレプリケーションスロットの有効化が有力ですが、新マスタのWAL領域の枯渇にご注意下さい。ERROR: requested WAL segment 0000000D00000000000000F3 has already been removedタイムラインの巻き戻し
pg_rewindはPostgreSQL9.6からタイムラインの巻き戻しができるよになっています。これによりスプリットブレインが発生しても、新マスタをスレーブに戻すことが可能です。pg_rewindが不要な場合
pg_rewindはターゲットとソースクラスタのタイムラインIDが分岐した場合に実行が必要です。そのためタイムラインが枝分かれしなかった場合、pg_rewindを実行する必要はありません。例えばpg_rewind実行時に次のようなメッセージが出た場合、pg_rewindは実行せずに、以降の操作を継続します。servers diverged at WAL position 0/503A428 on timeline 2 no rewind required
$ psql -h server1 -U postgres postgres -c "INSERT INTO test1_t VALUES ( 1 )"
Cancel request sent
WARNING: canceling wait for synchronous replication due to user request
DETAIL: ** The transaction has already committed locally, but might not have been replicated to the standby. **
INSERT 0 1
※Ctrl+Cをキーインする等、意図的にキャンセルしない限り、応答が返ってきません。
$ pg_ctl -w -m immediate stop
$ pg_isready -h server2 -U postgres -d postgres
server2:5432 - no response
$ vi $PGDATA/postgresql.conf
[変更前]
synchronous_standby_names = '*'
[変更後]
synchronous_standby_names = ''
$ pg_ctl reload
これでマスタが更新処理が可能な状態に復旧しました。
$ vi $PGDATA/recovery.conf
[変更前]
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server2 port=12079 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server2'
[変更後]
primary_conninfo = 'user=repuser passfile=''/home/p101/.pgpass'' host=server1 port=12079 application_name=server3 sslmode=disable sslcompression=1 target_session_attrs=any'
primary_slot_name = 'slot_server1'
$ pg_ctl restart
同期モードのスレーブも復活させたい場合は、フェイルバックと同様の作業を行います。
PostgreSQL 9.0でSR機能が実装されて以降、メジャーバージョン毎にSR関連の新機能を実装しています。
"ミスオペレーション"を"ミスオペ"と略記する場合があります。
"レプリケーションスロット"と表記した場合は物理型を指します。論理型の場合は明記します。
"レプリケーションスロット"を"スロット"と略記する場合があります。
バージョン | 分野または目的 | 概要 | 関連パラメータ |
---|---|---|---|
9.1
|
管理性の向上
データ保護
|
同期モードの実装
COMMIT時にWALの転送完了までを保証する。
データ保護とパフォーマンスとのトレードオフ
|
[マスタのpostgresql.conf]
synchronous_standby_names
|
9.1
|
管理性向上
昇格処理の明瞭化
|
pg_ctl promote コマンドによる昇格
明確なコマンドとなり分かり易くなった。
従来はtrigger_fileで設定したパスにtouchコマンド等でファイルを作成する方式であった。
|
|
9.1
|
管理性向上
SR状況把握の簡易化
|
pg_stat_replicationビューの追加
マスタでの、スレーブへのWAL転送および適用の状況把握方法が簡易になった。wal_receiver_status_interval間隔で反映。
|
[スレーブのpostgresql.conf]
wal_receiver_status_interval
|
9.1
|
管理性向上
SR状況把握の簡易化
|
pg_last_xact_replay_timestamp関数の追加
スレーブにて最終適用された時間を取得する
|
|
9.2
|
データ保護とパフォーマンスの調整
|
同期方式にremote_writeの追加
synchronous_commitの選択肢として、on/off/localに加え、remote_writeが追加された。
スレーブのメモリに書き込むまでを保証する。その時点でOSがハングした場合、WALは損失する。
|
[マスタのpostgresql.conf]
synchronous_commit
|
9.2
|
高可用性の向上
|
カスケードレプリケーションの実装
スレーブにぶら下がる2段目のSR構成が可能となった。
マスタの負荷を限定。
|
|
9.2
|
管理性の向上
SR状況把握の簡易化
|
pg_xlog_location_diff関数の追加
スレーブの転送や適用がどの程度遅れているかを取得するのが容易になった。
従来は pg_stat_replicationビューを参照していたが、ログの位置をバイト数に換算する計算が必要だった。
本関数により16進の差分をバイト数として取得できる。
|
|
9.3
|
管理性の向上
役割交換の効率化
|
スイッチバックの実装
historyファイルの転送により実装
|
|
9.3
|
管理性の向上
|
カスケードレプリケーションの管理性向上
カスケードのSR構成において、スレーブの新マスタ昇格後に、スレーブとSRの継続が可能に
|
|
9.3
|
障害時間の短縮
|
昇格処理の高速化
昇格処理にて、リカバリのみ実行し、チェックポイントを省略する事で時間短縮を実現。
従来はチェックポイントも昇格処理にて実行していた。
|
|
9.4
|
管理性の向上
ミスオペ対策
|
遅延レプリケーションの実装
スレーブでの適用を一定時間(recovery_min_apply_delay)遅らせる事で、マスタで発生したミスオペの伝搬を防ぐ。以下の事項に注意。
|
[スレーブのrecovery.conf]
recovery_min_apply_delay
|
9.4
|
管理性の向上
WAL保持の保証
|
レプリケーションスロットの実装
スレーブに必要なWALをマスタで保持し続ける事を保証。
特に複数スレーブ構成にて効果的。以下の事項に注意。
|
[マスタのpostgresql.conf]
max_replication_slots
[スレーブのpostgresql.conf]
hot_standby_feedback
|
9.4
|
論理レプリケーション
|
論理レプリケーションの関数の実装
行レベルの変更内容を出力する関数が実装された。
必要な設定
|
[マスタのpostgresql.conf]
wal_level
max_replication_slot
|
9.5
|
WAL転送効率の向上
|
WAL圧縮機能の実装
WALを圧縮する事で、転送効率の向上する。
圧縮/解凍によるオーバーヘッドとのトレードオフだが、通常はメリットの方が大きい。
SRに特化した機能では無いが、特に次のSR構成での適用が効果的と考えられる。
|
[マスタのpostgresql.conf]
wal_compression
|
9.5
|
管理性の向上
SR構築の効率化
|
pg_rewindコマンドの実装
昇格した新マスタと旧マスタを再同期する事で、SR構成を実装。物理的な障害でない場合は、pg_rewindにて対応可能な可能性がある。
新マスタから旧マスタへ差分バックアップを転送および旧マスタのWALを適用。差分バックアップの転送はpg_basebackupと同様であるため、同様の設定が必要。
|
[マスタのpostgresql.conf]
wal_log_hints
full_page_writes
max_wal_senders
[マスタのpg_hba.conf]
replication疑似データベースとの認証設定
|
9.5
|
管理性の向上
|
レプリケーション関連メッセージの出力
ログ監視の利便性が向上。ただしあまり多くは出力されない。
|
[マスタのpostgresql.conf]
log_replication_commands
|
9.5
|
管理性の向上
継続的アーカイブ
|
スレーブでのアーカイブ出力機能
スレーブにおいて自分のWALを出力する事で、昇格時に途切れること無く継続的なアーカイブが可能。
|
[スレーブのpostgresql.conf]
archive_mode
|
9.6
|
スケールアウト
参照負荷分散
|
完全同期レプリケーション機能の実装
スレーブへの適用完了までを保証。
スレーブ参照時にマスタと同一データが保証される事で、参照負荷分散によるスケールアウトが期待される。
|
[マスタのpostgresql.conf]
synchronous_commit
|
9.6
|
データ保護の多重化
|
複数同期スレーブ構成機能
従来は複数スレーブの内、同期モードが設定できるのは1台のみであったが、その制限が無くなった。
同時に複数スレーブに対して同期モードが設定できる。
|
[マスタのpostgresql.conf]
synchronous_standby_names
|
9.6
|
管理性の向上
SR状況把握の簡易化
|
pg_stat_wal_receiverビューの追加
従来はマスタにてpg_stat_replicationビューが参照できたが、スレーブにてpg_stat_wal_receiverビューが参照できる。
スレーブからレプリケーションの状況が把握が容易に。
|
|
9.6
|
管理性の向上
監視手法の多様化
|
pg_control_recovery関数の追加
制御ファイル情報の内、リカバリに関する情報を取得。
従来はpg_controldataコマンドで制御ファイルの情報を取得したが、SELECT文で取得できる事で管理手法の選択肢が増えた。
|
|
9.6
|
管理性の向上
役割交換の簡易化
|
pg_rewindの機能拡張
タイムラインIDの変更後にも対応できるようになった。
マスタに適用する事でスレーブに戻す事が可能。
|
|
10.0
|
管理性の向上
|
一時レプリケーションスロット
レプリケーションスロットを一時的に作成できるようになった。
pg_basebackupコマンド実行時のみ使用する場合に有効。
一時的なスロットかどうかはpg_replication_slotsカタログのtemporary列で判断する。
|
|
10.0
|
管理性の向上
|
Quorum-based同期レプリケーション
synchronous_standby_namesにてスレーブのリストに対してANYを指定する事で、同期レプリケーションとなるスレーブが定足数に応じて任意に選択される。
|
[マスタのpostgresql.conf]
synchronous_standby_names
|
10.0
|
WAL対象の拡充
|
ハッシュインデックスのWAL出力
従来は非推奨だったハッシュインデックスの活用が増える可能性あり。
SRの機能ではないが、関連事項として掲載。
|
[マスタのpostgresql.conf]
synchronous_standby_names
|
PostgreSQL 10.0よりロジカルレプリケーション機能がサポートされました。 ロジカルレプリケーションでは、テーブルデータに対する論理的な変更内容を用いて、サーバ間のデータレプリケーションを実現します。 PostgreSQL 9.0よりサポートされているストリーミングレプリケーションでは、データベースを構成するファイルの変更内容を物理的に複製することで、 サーバ間のデータレプリケーションを実現します。 従来のストリーミングレプリケーションの物理的なレプリケーションと対比する形で、ロジカルレプリケーションと呼称されています。
ロジカルレプリケーションは、PostgreSQL 9.0よりサポートされているストリーミングレプリケーションと比較すると以下の特徴を持ちます。
ただし、レプリケーション先のテーブルデータを更新した場合や、特定の処理のみをレプリケーションする場合は、 レプリケーション元とレプリケーション先でデータが異なる状態になります。 上記のような場合にデータの整合性はユーザが保証する必要があります。
ロジカルレプリケーションでは、Publisher(パブリッシャ)/Subscriber(サブスクライバ)モデルを採用しており、 レプリケーション元のサーバはPublisher、レプリケーション先のサーバはSubscriberと呼称されます。 Publisher上には、レプリケーション対象とするテーブルの論理集合であるPublication(パブリケーション)を定義し、 Subscriber上には、レプリケーション対象するパブリケーションとその接続情報であるSubscription(サブスクリプション)を定義します。 ロジカルレプリケーションにおける各用語の説明を下表に記載します。
No. | 用語 | 説明 |
---|---|---|
1 | Publisher(パブリッシャ) | レプリケーション元となるサーバ |
2 | Subscriber(サブスクライバ) | レプリケーション先となるサーバ
|
3 | Publication(パブリケーション) | レプリケーション対象とするテーブルの定義。CREATE PUBLICATIONコマンドで作成。 |
4 | Subscription(サブスクリプション) | レプリケーション対象するパブリケーションとその接続定義。CREATE SUBSCRIPTIONコマンドで作成。 |
5 | Replication Slot(レプリケーションスロット) | レプリケーションの状態を保持するオブジェクト。Subscription作成時に、Publisher上に作成される。
デフォルトでは作成されるReplication Slotの名前は、Subscriptionの名前と同じに設定される。
|
ロジカルレプリケーションは、下記の2段階でレプリケーションが実施されます。
動作は下図のイメージとなります。
PublisherからSubscriberへの「2. データ変更内容の送受信によるデータ同期」は下図の処理で実施されます。
ロジカルレプリケーションは以下のようなケースで有用と考えます。
ロジカルレプリケーションの典型的な利用例がPostgreSQLの文書 [1] に記載されていますので、ご参照下さい。
[1] | PostgreSQL 10.0文書 - 第31章 論理レプリケーション |
ロジカルレプリケーションの制限事項は以下の通りです。(PostgreSQL 10時点)
No. | 制限事項 | 補足 |
---|---|---|
1 | データベーススキーマおよびDDLコマンドはレプリケーションされない | データベーススキーマは、pg_dump --schema-onlyを利用して移行可能 |
2 | シーケンスはレプリケーションされない | シーケンスはレプリケーションされないが、
シーケンスによって裏付けされたSERIAL型や識別列のデータは、テーブルデータの一部としてレプリケーション可能
|
3 | TRUNCATEコマンドはレプリケーションされない | DELETEコマンドで回避することは可能 |
4 | ラージオブジェクトはレプリケーションされない | 通常のテーブルにデータを格納する以外の回避方法なし |
5 | テーブル以外のオブジェクトはレプリケーションできない | ビュー、マテリアライズドビュー、パーティションのルートテーブル(親テーブル)、 外部テーブルはレプリケーションしようとするとエラーになる |
制限事項の詳細は、PostgreSQLの文書 [2] に記載されていますので、ご参照下さい。
[2] | PostgreSQL 10.0文書 - 第31章 論理レプリケーション 31.4. 制限事項 |
CREATE PUBLICATION name [ FOR TABLE [ ONLY ] table_name [ * ] [, ...] | FOR ALL TABLES ] [ WITH ( publication_parameter [= value] [, ... ] ) ]
「CREATE PUBLICATION」コマンドの仕様は、PostgreSQLの文書 [1] に記載されていますので、ご参照下さい。
[1] | PostgreSQL 10.0文書 - SQLコマンド CREATE PUBLICATION |
項目 | 説明 |
---|---|
PostgreSQLバージョン | 10.1
|
OSバージョン | CentOS 7.4
|
サーバ構成(各ホスト名) | 2サーバ(node1,node2) |
データベース内の全てのテーブルをレプリケーションする際の設定手順について確認します。
■ 事前準備
該当サーバにsshにて接続します。
(node1,node2のサーバにおいて実施) ssh接続を用いて、 該当環境へ接続 ユーザ: root パスワード: xxxxxxx
名前解決のために各サーバのホスト名を設定します。
(node1,node2のサーバにおいて実施) # vi /etc/hosts [下記をファイル末尾に追加] 192.168.56.101 node1 192.168.56.102 node2
PostgreSQL間の通信のため、5432ポートを解放します。
(node1,node2のサーバにおいて実施) # firewall-cmd --permanent --add-port=5432/tcp # firewall-cmd --reload以下のコマンド5432でポートが解放されていることを確認します。
(node1,node2のサーバにおいて実施) # firewall-cmd --list-ports 5432/tcp
PostgreSQL10のyumレポジトリ用のパッケージをインストールします。
(node1,node2のサーバにおいて実施) # yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-1.noarch.rpm以下のコマンドで該当のパッケージがインストールされたことを確認します。
(node1,node2のサーバにおいて実施) # yum list installed pgdg-centos10.noarch [省略] インストール済みパッケージ pgdg-centos10.noarch 10-1 installed
yumコマンドを用いて、PostgreSQLをインストールします。
(node1,node2のサーバにおいて実施) # yum install postgresql10 postgresql10-server postgresql10-libs postgresql10-contrib以下のコマンドでパッケージをインストールされたことを確認します。
(node1,node2のサーバにおいて実施) # yum list installed postgresql10 postgresql10-server postgresql10-libs postgresql10-contrib [省略] インストール済みパッケージ postgresql10.x86_64 10.1-1PGDG.rhel7 @pgdg10-updates-testing postgresql10-contrib.x86_64 10.1-1PGDG.rhel7 @pgdg10-updates-testing postgresql10-libs.x86_64 10.1-1PGDG.rhel7 @pgdg10-updates-testing postgresql10-server.x86_64 10.1-1PGDG.rhel7 @pgdg10-updates-testing
postgresユーザの環境変数の設定を行います。
(node1,node2のサーバにおいて実施) # su - postgres $ vi ~/.bash_profile [下記をファイル末尾に追加] export PATH=${PATH}:/usr/pgsql-10/bin export PGHOME=/usr/pgsql-10 export PGDATA=/var/lib/pgsql/10/data export PGDATABASE=postgres export PGPORT=5432 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/pgsql-10/lib以下のコマンドで環境変数の設定を確認します。($PGDATAの確認を例示しています。)
(node1,node2のサーバにおいて実施) $ exit ログアウト # su - postgres $ echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/usr/pgsql-10/bin $ echo $PGDATA /var/lib/pgsql/10/data
データベースクラスタの作成を実施します。
(node1,node2のサーバにおいて実施) $ initdb -D $PGDATA -A trust -U postgres --no-locale --encoding UTF8
postgresql.confに以下を設定します。
(node1,node2のサーバにおいて実施) $ vi $PGDATA/postgresql.conf [下記の修正を加える] listen_addresses = '*' # 全てのネットワークインターフェースへの接続を受け付ける設定 wal_level = 'logical' # ロジカルレプリケーションに必要なWAL出力設定 max_wal_senders = 10 # wal senderプロセス数の最大数を10に設定(デフォルト値) max_replication_slots = 10 # レプリケーションスロットの最大数を10に設定(デフォルト値) log_line_prefix = '[%m][%d][%h][%u][%e][%p] ' # ログメッセージの解析時に必要な情報を付与
node1,node2間でのデータベース接続を許可します。
(node1,node2のサーバにおいて実施) $ vi $PGDATA/pg_hba.conf [下記の修正を加える] host all all 192.168.56.0/24 trust ※ 上記の設定は、SubscriberがPublisherのデータベースに接続する際に利用されます。 そのため、Publisherにのみ上記設定が必要となります。
PostgreSQLを起動します。
(node1,node2のサーバにおいて実施) $ pg_ctl start以下のコマンドでPostgreSQLの起動を確認します。
(node1,node2のサーバにおいて実施) $ pg_ctl status pg_ctl: サーバが動作中です(PID: 3517) /usr/pgsql-10/bin/postgres
■ ロジカルレプリケーションの動作確認(データベース単位)
本検証で利用するデータベースを作成します。
(node1,node2のサーバにおいて実施) $ createdb logicalreptest以下のコマンドでデータベースが作成されたことを確認します。
(node1,node2のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ----------------+----------+----------+---------+-------+----------------------- logicalreptest | postgres | UTF8 | C | C | [省略]
作成したデータベースにテスト用のテーブルを作成します。
(node1,node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE TABLE t1logical (c1 INT, PRIMARY KEY (c1)); =# CREATE TABLE t2logical (c1 INT, PRIMARY KEY (c1));以下のコマンドで作成したテーブルを確認します。
=# \d List of relations Schema | Name | Type | Owner --------+-----------+-------+---------- public | t1logical | table | postgres public | t2logical | table | postgres (2 rows)
node1上にデータベース内の全テーブル(作成したlogicalとt2logicalテーブル)をレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node1_logicaldb FOR ALL TABLES;以下のコマンドでPublicationが作成されたことを確認します。作成したpublicationではデータベース内の全テーブルをレプリケーション対象としているため、All tables列がtrueであることを確認します。(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node1_logicaldb | postgres | t | t | t | t (1 row)
node2上にnode1上に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node2_logicaldb CONNECTION 'host=node1 dbname=logicalreptest port=5432 user=postgres' PUBLICATION pub_node1_logicaldb;以下のコマンドでSubscriptionが作成されたことを確認します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node2_logicaldb | postgres | t | {pub_node1_logicaldb} (1 row)
テスト用に作成した2つのテーブルにレコードをそれぞれINSERTします。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# INSERT INTO t1logical VALUES (1); =# INSERT INTO t2logical VALUES (2); =# SELECT * FROM t1logical; c1 ---- 1 (1 rows) =# SELECT * FROM t2logical; c1 ---- 2 (1 rows)Subscription側のテーブルにレコードがINSERTされていることを確認します。(レプリケーションされていることを確認)
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t1logical; c1 ---- 1 <-- node1と同じ結果がレプリケーションされていること (1 rows) ---- 2 <-- node1と同じ結果がレプリケーションされていること (1 rows)
データベース内の任意のテーブルのみをレプリケーションする際の設定手順について確認します。
データベース単位でレプリケーションの構築手順について確認します。
■ ロジカルレプリケーションの動作確認(テーブル単位)
本検証で利用するデータベースを作成します。
(node1,node2のサーバにおいて実施) $ createdb logicalreptest2以下のコマンドでデータベースが作成されたことを確認します。
(node1,node2のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -----------------+----------+----------+---------+-------+----------------------- logicalreptest2 | postgres | UTF8 | C | C | [省略]
作成したデータベースに検証用のテーブルを作成します。
(node1,node2のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# CREATE TABLE t3logical (c1 INT, PRIMARY KEY (c1));以下のコマンドでテーブルが作成されたことを確認します。
(node1,node2のサーバにおいて実施) =# \d List of relations Schema | Name | Type | Owner --------+-----------+-------+---------- public | t3logical | table | postgres (1 row)
node1上にt3logicalテーブルのみをレプリケーション対象とするPublicationを作成します。 (FOR TABLEにt3logicalを指定)
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# CREATE PUBLICATION pub_node1_t3logical FOR TABLE t3logical;以下のコマンドでPublicationが作成されたことを確認します。 作成したPublicationでは、All tables列がfalseであることを確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node1_t3logical | postgres | f | t | t | t (1 row)
node2上にPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# CREATE SUBSCRIPTION sub_node2_t3logical CONNECTION 'host=node1 dbname=logicalreptest2 port=5432 user=postgres' PUBLICATION pub_node1_t3logical;以下のコマンドでSubscriptionが作成されたことを確認します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node2_t3logical | postgres | t | {pub_node1_t3logical} (1 row)
テスト用に作成したテーブルにレコードをINSERTします。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# INSERT INTO t3logical VALUES (1); =# INSERT INTO t3logical VALUES (2); =# SELECT * FROM t3logical; c1 ---- 1 2 (2 rows)Subscription側のテーブルにもレコードがINSERTされていることを確認します。(レプリケーションされていることを確認)
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest2 =# SELECT * FROM t3logical; c1 ---- 1 <-- node1と同じ結果がレプリケーションされていること 2 (2 rows)
本検証では、下記のレプリケーション設定において更新処理の限定が実現可能かの検証を実施しました。
■ 初期状態
現在のレプリケーション状態を確認します。
(node1,node2のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication" (0 rows)
本検証で利用するデータベースを作成します。
(node1,node2のサーバにおいて実施) $ createdb testlogicalre以下のコマンドでデータベースが作成された事を確認します。
(node1,node2のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ---------------+----------+----------+---------+-------+----------------------- testlogicalre | postgres | UTF8 | C | C | <-- 作成したDBを確認 [省略]
作成したデータベースにテスト用のテーブルを作成します。
(node1,node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# CREATE TABLE t1logical (c1_no integer PRIMARY KEY, c2_date date); =# SELECT * FROM t1logical; c1_no | c2_date -------+--------- (0 rows)以下のコマンドで作成したテーブルを確認します。
(node1,node2のサーバにおいて実施) =# \dt List of relations Schema | Name | Type | Owner --------+-----------+-------+---------- public | t1logical | table | postgres (1 row)
■ ロジカルレプリケーションでのレプリケーション内容の変更
node1に作成したt1logicalテーブルに対し、insertとupdateをレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) =# CREATE PUBLICATION pub_test_node1 FOR TABLE t1logical WITH (publish='insert, update'); <-- 指定した更新処理のみをレプリケーション =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ----------------+------------+----------- pub_test_node1 | public | t1logical (1 row)以下のコマンドでPublicationが作成された事を確認します。作成したpublicationでは対象のテーブルのinsertとupdateをレプリケーション対象としているため、InsertsとUpdates列がtrueであることを確認します。(node1のサーバにおいて実施) =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ----------------+----------+------------+---------+---------+--------- pub_test_node1 | postgres | f | t | t | f <-- DELETEはレプリケーション対象に含めない (1 row)node2にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) =# CREATE SUBSCRIPTION sub_test_node2 CONNECTION 'host=node1 dbname=testlogicalre port=5432 user=postgres' PUBLICATION pub_test_node1; =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+----------- 16435 | 16430 | r | 0/181A380 (1 row)以下のコマンドでSubscriptionが作成された事を確認します。
(node2のサーバにおいて実施) # \dRs List of subscriptions Name | Owner | Enabled | Publication ----------------+----------+---------+------------------ sub_test_node2 | postgres | t | {pub_test_node1} (1 row)
現在のレプリケーション状態を確認します。作成したPublicationとSubscriptionが正常にレプリケーションされているかをapplication_name、state、sync_state項目で確認します。(node1のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication" -[ RECORD 1 ]----+------------------------------ pid | 1264 usesysid | 10 usename | postgres application_name | sub_test_node2 <-- subscription_nameが表示される client_addr | 192.168.56.102 client_hostname | client_port | 53076 backend_start | 2017-12-20 10:44:04.503952+09 backend_xmin | state | streaming <-- streaming が表示される sent_lsn | 0/181A380 write_lsn | 0/181A380 flush_lsn | 0/181A380 replay_lsn | 0/181A380 write_lag | flush_lag | replay_lag | sync_priority | 0 sync_state | async <-- async(非同期)
■ ロジカルレプリケーション動作確認
テスト用に作成したPublication側のテーブルにレコードをINSERTします。
(node1のサーバにおいて実施) =# INSERT INTO t1logical VALUES (1,'2017/12/20'); =# SELECT * FROM t1logical; c1_no | c2_date -------+------------ 1 | 2017-12-20 (1 row)Subscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM t1logical; c1_no | c2_date -------+------------ 1 | 2017-12-20 (1 row)
Publication側のテーブルのレコードをUPDATEします。
(node1のサーバにおいて実施) =# UPDATE t1logical SET c2_date = '2017/12/25' WHERE c1_no = 1; =# SELECT * FROM t1logical; c1_no | c2_date -------+------------ 1 | 2017-12-25 (1 row)Subscription側のテーブルでもレコードがUPDATEされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM t1logical; c1_no | c2_date -------+------------ 1 | 2017-12-25 (1 row)
Publication側のテーブルのレコードをDELETEします。
(node1のサーバにおいて実施) =# DELETE FROM t1logical WHERE c1_no = 1; =# SELECT * FROM t1logical; c1_no | c2_date -------+--------- (0 rows) <-- 指定のデータが削除されるSubscription側のテーブルではレコードがDELETEされていない事を確認します。(レプリケーションされていない事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM t1logical; c1_no | c2_date -------+------------ 1 | 2017-12-25 <-- DELETEはレプリケーションされていない (1 row)
ロジカルレプリケーションの構築に必要な設定について説明します。
ロジカルレプリケーションに関係する実行時パラメータは以下のとおりです。
パラメータ名 | パラメータの説明 | 設定するサーバ |
---|---|---|
wal_level | WALに書かれる情報量。ロジカルレプリケーションでは「logical」を設定する。 | ロジカルレプリケーションのPublisher、
ストリーミングレプリケーションのマスタ
|
max_wal_senders | wal senderプロセスの最大数 | ロジカルレプリケーションのPublisher、
ストリーミングレプリケーションのマスタ
|
max_logical_replication_workers | logical replication workerプロセスの最大数 | ロジカルレプリケーションのSubscriber |
max_worker_processes | バックグラウンドプロセスの最大数 | ロジカルレプリケーションのPublisher、Subscriber |
max_sync_workers_per_subscription | Subscriptionあたりのワーカープロセスの並列度 | ロジカルレプリケーションのSubscriber |
max_replication_slots | レプリケーションスロットの最大数 | ロジカルレプリケーションのPublisher
ストリーミングレプリケーションのマスタ
|
いくつかの実行時パラメータはロジカルレプリケーション稼働時に起動されるプロセス数との関係を理解して設定値を決める必要があります。ここでは以下の実行時パラメータについて考察します。
■初期状態
本章は以下の環境を利用した検証結果を元に解説します。
ここでPublisher、Subscriberのプロセスを確認すると、以下のプロセスが起動していることがわかります。
(サーバ1において実施)
$ ps aux | grep postgres
postgres 2503 0.0 0.8 389296 16492 pts/0 S 15:15 0:00 /usr/pgsql-10/bin/postgres
postgres 2504 0.0 0.1 242108 1936 ? Ss 15:15 0:00 postgres: logger process
postgres 2506 0.0 0.1 389448 3664 ? Ss 15:15 0:00 postgres: checkpointer process
postgres 2507 0.0 0.1 389296 3428 ? Ss 15:15 0:00 postgres: writer process
postgres 2508 0.0 0.3 389296 6324 ? Ss 15:15 0:00 postgres: wal writer process
postgres 2509 0.0 0.1 389752 3048 ? Ss 15:15 0:00 postgres: autovacuum launcher process
postgres 2510 0.0 0.1 244360 2204 ? Ss 15:15 0:00 postgres: stats collector process
postgres 2511 0.0 0.1 389588 2492 ? Ss 15:15 0:00 postgres: bgworker: logical replication launcher
postgres 2513 0.0 0.3 392428 5788 ? Ss 15:15 0:00 postgres: wal sender process repusr1 192.168.127.32(40906) idle
(サーバ2において実施)
$ ps aux | grep postgres
postgres 2387 0.0 0.8 389296 16492 pts/0 S 15:15 0:00 /usr/pgsql-10/bin/postgres
postgres 2388 0.0 0.1 242108 1932 ? Ss 15:15 0:00 postgres: logger process
postgres 2390 0.0 0.2 389448 3928 ? Ss 15:15 0:00 postgres: checkpointer process
postgres 2391 0.0 0.1 389296 3428 ? Ss 15:15 0:00 postgres: writer process
postgres 2392 0.0 0.3 389296 6324 ? Ss 15:15 0:00 postgres: wal writer process
postgres 2393 0.0 0.1 389752 3076 ? Ss 15:15 0:00 postgres: autovacuum launcher process
postgres 2394 0.0 0.1 244360 2200 ? Ss 15:15 0:00 postgres: stats collector process
postgres 2395 0.0 0.1 389588 2756 ? Ss 15:15 0:00 postgres: bgworker: logical replication launcher
postgres 2398 0.0 0.3 397072 6320 ? Ss 15:15 0:00 postgres: bgworker: logical replication worker for subscription 16394
■Publication,Subscriptionを追加した時の起動プロセス
Publisher,Subscriberにそれぞれテーブルdata2を追加し、テーブルdata2を複製するPublication,Subscriptionを作成します。
(サーバ1において実施)
pubdb=> CREATE TABLE data2 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
pubdb=> CREATE PUBLICATION pub2 FOR TABLE data2;
(サーバ2において実施)
subdb=> CREATE TABLE data2 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
subdb=# CREATE SUBSCRIPTION sub2 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub2;
NOTICE: created replication slot "sub2" on publisher
CREATE SUBSCRIPTION
Publisherの動的統計情報ビューpg_stat_replicationを見ると、今回追加したレプリケーション(RECORD 1)元々設定済のレプリケーション(RECORD 2)の情報が確認できます。
(サーバ1において実施)
pubdb=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 2692
usesysid | 16385
usename | repusr1
application_name | sub2
client_addr | 192.168.127.32
client_hostname |
client_port | 40909
backend_start | 2017-12-14 16:22:28.821828+09
backend_xmin |
state | streaming
sent_lsn | 0/16D7B60
write_lsn | 0/16D7B60
flush_lsn | 0/16D7B60
replay_lsn | 0/16D7B60
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 2 ]----+------------------------------
pid | 2513
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 40906
backend_start | 2017-12-14 15:15:46.183256+09
backend_xmin |
state | streaming
sent_lsn | 0/16D7B60
write_lsn | 0/16D7B60
flush_lsn | 0/16D7B60
replay_lsn | 0/16D7B60
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
ここでPublisher、Subscriberのプロセスを確認すると以下のプロセスが起動しており、wal sender, logical replication workerがそれぞれSubscription単位に起動していることがわかります。
(サーバ1において実施)
$ ps aux | grep postgres
postgres 2503 0.0 0.8 389296 16492 pts/0 S 15:15 0:00 /usr/pgsql-10/bin/postgres
postgres 2504 0.0 0.1 242108 1936 ? Ss 15:15 0:00 postgres: logger process
postgres 2506 0.0 0.2 389448 4188 ? Ss 15:15 0:00 postgres: checkpointer process
postgres 2507 0.0 0.1 389296 3428 ? Ss 15:15 0:00 postgres: writer process
postgres 2508 0.0 0.3 389296 6324 ? Ss 15:15 0:00 postgres: wal writer process
postgres 2509 0.0 0.1 389752 3048 ? Ss 15:15 0:00 postgres: autovacuum launcher process
postgres 2510 0.0 0.1 244360 2204 ? Ss 15:15 0:00 postgres: stats collector process
postgres 2511 0.0 0.1 389588 2492 ? Ss 15:15 0:00 postgres: bgworker: logical replication launcher
postgres 2513 0.0 0.3 392428 5788 ? Ss 15:15 0:00 postgres: wal sender process repusr1 192.168.127.32(40906) idle
postgres 2692 0.0 0.2 392292 5252 ? Ss 16:22 0:00 postgres: wal sender process repusr1 192.168.127.32(40909) idle
(サーバ2において実施)
$ ps aux | grep postgres
postgres 2387 0.0 0.8 389296 16492 pts/0 S 15:15 0:00 /usr/pgsql-10/bin/postgres
postgres 2388 0.0 0.1 242108 1932 ? Ss 15:15 0:00 postgres: logger process
postgres 2390 0.0 0.2 389448 4192 ? Ss 15:15 0:00 postgres: checkpointer process
postgres 2391 0.0 0.1 389296 3428 ? Ss 15:15 0:00 postgres: writer process
postgres 2392 0.0 0.3 389296 6324 ? Ss 15:15 0:00 postgres: wal writer process
postgres 2393 0.0 0.1 389752 3076 ? Ss 15:15 0:00 postgres: autovacuum launcher process
postgres 2394 0.0 0.1 244360 2200 ? Ss 15:15 0:00 postgres: stats collector process
postgres 2395 0.0 0.1 389588 2756 ? Ss 15:15 0:00 postgres: bgworker: logical replication launcher
postgres 2398 0.0 0.3 397072 6320 ? Ss 15:15 0:00 postgres: bgworker: logical replication worker for subscription 16394
postgres 2571 0.0 0.3 397072 5992 ? Ss 16:22 0:00 postgres: bgworker: logical replication worker for subscription 16400
■初期データコピー実行中の起動プロセス
次にロジカルレプリケーションのSubscription作成時に実行される初期データのコピー処理による起動プロセスの増減を確認します。ここではベンチマークツールpgbenchを使ってあらかじめデータベースpubdbに1000万件の初期データを持つテーブルpgbench_accountsを作成します。
(サーバ1において実施)
$ pgbench -i -s 10 -U pubusr1 pubdb
Publisherのデータベースpubdbに接続し、新たなPublication pub3を作成します。
(サーバ1において実施)
pubdb=> CREATE PUBLICATION pub3 FOR TABLE pgbench_accounts;
Subscriberのデータベースsubdbに接続し、レプリケーション対象テーブルを作成します。
(サーバ2において実施)
subdb=> CREATE TABLE pgbench_accounts (aid INT PRIMARY KEY, bid INT, abalance INT, filler character(84));
データベースsubdbにSubscription sub3を作成し、ロジカルレプリケーションを開始します。
(サーバ2において実施)
subdb=# CREATE SUBSCRIPTION sub3 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub3;
Subscription sub3を作成した直後にPublisher,Subscriberの起動プロセスを確認すると、wal sender, logical replication workerがそれぞれSubscriptionに加えて初期データコピーを実行するプロセス分増えていることがわかります。
(サーバ1において実施)
$ ps aux | grep postgres
postgres 2503 0.0 0.6 389296 12532 ? S 12月14 0:01 /usr/pgsql-10/bin/postgres
postgres 2504 0.0 0.0 242108 1576 ? Ss 12月14 0:00 postgres: logger process
postgres 2506 0.0 2.2 389612 42160 ? Ss 12月14 0:00 postgres: checkpointer process
postgres 2507 0.0 0.1 389296 2892 ? Ss 12月14 0:00 postgres: writer process
postgres 2508 0.0 0.3 389296 5836 ? Ss 12月14 0:02 postgres: wal writer process
postgres 2509 0.0 0.1 389752 2564 ? Ss 12月14 0:00 postgres: autovacuum launcher process
postgres 2510 0.0 0.0 244360 1816 ? Ss 12月14 0:02 postgres: stats collector process
postgres 2511 0.0 0.1 389588 2120 ? Ss 12月14 0:00 postgres: bgworker: logical replication launcher
postgres 2513 0.0 1.9 425924 36336 ? Ss 12月14 0:24 postgres: wal sender process repusr1 192.168.127.32(40906) idle
postgres 2692 0.0 1.9 425852 35920 ? Ss 12月14 0:24 postgres: wal sender process repusr1 192.168.127.32(40909) idle
postgres 5731 0.0 0.2 392292 5248 ? Ss 10:15 0:00 postgres: wal sender process repusr1 192.168.127.32(40958) idle
postgres 5732 19.8 0.4 392592 8788 ? Ss 10:15 0:01 postgres: wal sender process repusr1 192.168.127.32(40959) COPY
(サーバ2において実施)
$ ps aux | grep postgres
postgres 2387 0.0 0.8 389296 16492 ? S 12月14 0:01 /usr/pgsql-10/bin/postgres
postgres 2388 0.0 0.1 242108 2000 ? Ss 12月14 0:00 postgres: logger process
postgres 2390 0.0 2.3 389612 43740 ? Ss 12月14 0:00 postgres: checkpointer process
postgres 2391 0.0 0.1 389296 3428 ? Ss 12月14 0:00 postgres: writer process
postgres 2392 0.0 0.3 389296 6324 ? Ss 12月14 0:01 postgres: wal writer process
postgres 2393 0.0 0.1 389752 3092 ? Ss 12月14 0:00 postgres: autovacuum launcher process
postgres 2394 0.0 0.1 244360 2200 ? Ss 12月14 0:02 postgres: stats collector process
postgres 2395 0.0 0.1 389588 2756 ? Ss 12月14 0:00 postgres: bgworker: logical replication launcher
postgres 2398 0.0 0.3 397072 6320 ? Ss 12月14 0:03 postgres: bgworker: logical replication worker for subscription 16394
postgres 2571 0.0 0.3 397072 5992 ? Ss 12月14 0:03 postgres: bgworker: logical replication worker for subscription 16400
postgres 5597 0.0 0.3 397072 5728 ? Ss 10:15 0:00 postgres: bgworker: logical replication worker for subscription 16418
postgres 5598 83.0 3.5 397536 67052 ? Rs 10:15 0:06 postgres: bgworker: logical replication worker for subscription 16418 sync 16413
なお、初期データのコピーが完了するとコピー用のプロセス(上記のPID 5732,5598)が終了し、wal sender,logical replication workerの数がSubscriptionと同じ3つに変化することも確認できました。
■max_worker_processesと起動プロセスの関係
max_worker_processesはバックグラウンドプロセスの最大数を定義する実行時パラメータでデフォルト値は8です。ロジカルレプリケーションに関係するプロセスもバックグラウンドプロセスとして起動するため、このパラメータとの関係を検証します。
まず、Subscriberのmax_worker_processesを3に変更して再起動すると、サーバログに以下の様なエラーメッセージが出力されます。
(サーバ2のサーバログ)
2017-12-15 11:00:44.951 JST [5755] LOG: logical replication apply worker for subscription "sub1" has started
2017-12-15 11:00:44.963 JST [5756] LOG: logical replication apply worker for subscription "sub2" has started
2017-12-15 11:00:44.966 JST [5754] WARNING: out of background worker slots
2017-12-15 11:00:44.966 JST [5754] HINT: You might need to increase max_worker_processes.
この時、Subscriberの動的統計情報ビューpg_stat_activityでバックグラウンドプロセスの情報を表示すると、logical replication launcher と logical replication worker がそれぞれバックグラウンドプロセスとして起動していることがわかります。
(サーバ2において実施)
postgres=# select datname,pid,usename,backend_start,wait_event_type,wait_event,state,backend_type from pg_stat_activity where backend_type = 'background worker';
datname | pid | usename | backend_start | wait_event_type | wait_event | state | backend_type
---------+------+----------+-------------------------------+-----------------+---------------------+-------+-------------------
| 5790 | postgres | 2017-12-15 11:01:16.733998+09 | Activity | LogicalLauncherMain | | background worker
subdb | 5791 | postgres | 2017-12-15 11:01:16.742653+09 | Activity | LogicalApplyMain | idle | background worker
subdb | 5792 | postgres | 2017-12-15 11:01:16.750161+09 | Activity | LogicalApplyMain | idle | background worker
subdb | 5793 | postgres | 2017-12-15 11:01:16.760481+09 | Activity | LogicalApplyMain | idle | background worker
一方でPublisherのmax_worker_processesを3に変更して再起動してもサーバログにエラーメッセージは出力されません。この時のPublisherのバックグラウンドプロセスの情報を表示すると、logical replication launcher だけがカウントされ、wal senderはカウントされていません。
(サーバ1において実施)
pubdb=# select datname,pid,usename,backend_start,wait_event_type,wait_event,state,backend_type from pg_stat_activity where backend_type = 'background worker';
datname | pid | usename | backend_start | wait_event_type | wait_event | state | backend_type
---------+------+----------+-------------------------------+-----------------+---------------------+-------+-------------------
| 2511 | postgres | 2017-12-14 15:15:28.478386+09 | Activity | LogicalLauncherMain | | background worker
このように、Publisherのwal senderとSubscriberのlogical replication workerはともにSubscriptionの数に合わせて増加しますが、Publisherのwal senderはmax_worker_processesで管理するバックグラウンドプロセスとは異なる扱いであることがわかりました。
検証結果を踏まえて以下の実行時パラメータの決め方について考察します。
■max_wal_senders
■max_logical_replication_workers
■max_worker_processes
■max_sync_workers_per_subscription
■max_replication_slots
primary keyが定義されていないテーブルにおけるロジカルレプリケーションの設定方法を確認します。
ロジカルレプリケーションでは、UPDATEもしくはDELTEをレプリケーションさせるために、 変更したレコードを特定するための情報であるREPLICA IDENTITYをPublication側のテーブルに設定する必要があります。 REPLICA IDENTITYが指定されていないテーブルがPublicationに追加された場合、UPDATEもしくはDELETEが行われるとエラーが発生します。
デフォルトではprimary keyが定義されたテーブルの場合、primary keyがREPLICA IDENTITYとして自動設定されます。 また、REPLICA IDENTITYにユニークインデックス(NOT NULLかつ部分インデックスや遅延可能インデックスではない)を設定することで、UPDATEとDELETEのレプリケーションが可能です。 テーブルに上記のキーが存在しない場合は、REPLICA IDENTITYにFULLを設定することで、UPDATEとDELETEのレプリケーションが可能です。[1]
REPLICA IDENTITYは、ロジカルレプリケーション利用時において、WALファイルに書き込む情報量に影響します。 REPLICA IDENTITYに指定された列では、変更前の古いレコードがWALファイルに書き込まれます。 FULLを指定した場合には、古いレコードを全てWALファイルに書き込みため、非効率です。
REPLICA IDENTITYの設定方法の詳細は、PostgreSQL 文書をご確認下さい。[2]
[1] | PostgreSQL 10.0文書 - 第31章 論理レプリケーション 31.1. パブリケーション |
[2] | PostgreSQL 10.0文書 - SQLコマンド ALTER TABLE |
primary keyが定義されていないテーブルにおけるレプリケーションが可能な否かを確認するため、 下表のパターンでロジカルレプリケーションの設定を実施しました。 パターンと動作確認結果は下表の通りです。(○:レプリケーション可能、×:レプリケーション不可)
¶ テーブル構成 INSERT UPDATE DELETE 備考 primary keyが定義されたテーブル ○ ○ ○ -unique制約(not null制約)とreplica identityにuniqueインデックスを指定 ○ ○ ○ -上記に該当しないテーブル ○ × × Publication側のテーブルにDELETEおよびUPDATEを実施した場合、エラーになる上記に該当しないテーブルにreplica identityにfullを指定 ○ ○ ○ -
テスト用のテーブルを作成します。
(node1,node2のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# CREATE TABLE t8logical (c1 INT, c2 TEXT, PRIMARY KEY (c1)); =# CREATE TABLE t9logical (c1 INT NOT NULL, c2 TEXT); =# CREATE UNIQUE INDEX t9logical_ukey ON t9logical (c1); =# ALTER TABLE t9logical REPLICA IDENTITY USING INDEX t9logical_ukey; =# CREATE TABLE t10logical (c1 INT, c2 TEXT); =# CREATE TABLE t11logical (c1 INT, c2 TEXT); =# ALTER TABLE t11logical REPLICA IDENTITY FULL;以下のコマンドでテーブルが作成されたことを確認します。
(node1,node2のサーバにおいて実施) =# \d List of relations Schema | Name | Type | Owner --------+------------+-------+---------- public | t10logical | table | postgres public | t11logical | table | postgres public | t8logical | table | postgres public | t9logical | table | postgres (4 rows)
node1上の各テーブルをレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# CREATE PUBLICATION pub_node1_t8logical FOR TABLE t8logical; =# CREATE PUBLICATION pub_node1_t9logical FOR TABLE t9logical; =# CREATE PUBLICATION pub_node1_t10logical FOR TABLE t10logical; =# CREATE PUBLICATION pub_node1_t11logical FOR TABLE t11logical;以下のコマンドでPublicationが作成されたことを確認します。
(node1のサーバにおいて実施) =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ----------------------+----------+------------+---------+---------+--------- pub_node1_t10logical | postgres | f | t | t | t pub_node1_t11logical | postgres | f | t | t | t pub_node1_t8logical | postgres | f | t | t | t pub_node1_t9logical | postgres | f | t | t | t (5 rows)
node2上にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# CREATE SUBSCRIPTION sub_node2_t8logical CONNECTION 'host=node1 dbname=logicalreptest3 port=5432 user=postgres' PUBLICATION pub_node1_t8logical; =# CREATE SUBSCRIPTION sub_node2_t9logical CONNECTION 'host=node1 dbname=logicalreptest3 port=5432 user=postgres' PUBLICATION pub_node1_t9logical; =# CREATE SUBSCRIPTION sub_node2_t10logical CONNECTION 'host=node1 dbname=logicalreptest3 port=5432 user=postgres' PUBLICATION pub_node1_t10logical; =# CREATE SUBSCRIPTION sub_node2_t11logical CONNECTION 'host=node1 dbname=logicalreptest3 port=5432 user=postgres' PUBLICATION pub_node1_t11logical;以下のコマンドでSubscriptionが作成されたことを確認します。
(node2のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ----------------------+----------+---------+------------------------ sub_node2_t10logical | postgres | t | {pub_node1_t10logical} sub_node2_t11logical | postgres | t | {pub_node1_t11logical} sub_node2_t8logical | postgres | t | {pub_node1_t8logical} sub_node2_t9logical | postgres | t | {pub_node1_t9logical} (4 rows)
primary keyが定義されたテーブルに対して、INSERT,UPDATE,DELETEを実施します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# INSERT INTO t8logical VALUES (1, 'MERRY CHRISTMAS'); =# INSERT INTO t8logical VALUES (2, 'CHRISTMAS DAY'); =# UPDATE t8logical SET c2 = 'HAPPY NEW YEAR' WHERE c1 = 2; =# DELETE FROM t8logical WHERE c1 = 1; =# SELECT * FROM t8logical; c1 | c2 ----+---------------- 2 | HAPPY NEW YEAR (1 row)Subscription側にデータが反映されていることを確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# SELECT * FROM t8logical; c1 | c2 ----+---------------- 2 | HAPPY NEW YEAR (1 row)unique制約(not null制約)とreplica identityにuniqueインデックスが定義されたテーブルに対して、INSERT,UPDATE,DELETEを実施します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# INSERT INTO t9logical VALUES (1, 'MERRY CHRISTMAS'); =# INSERT INTO t9logical VALUES (2, 'CHRISTMAS DAY'); =# UPDATE t9logical SET c2 = 'HAPPY NEW YEAR' WHERE c1 = 2; =# DELETE FROM t9logical WHERE c1 = 1; =# SELECT * FROM t9logical; c1 | c2 ----+---------------- 2 | HAPPY NEW YEAR (1 row)Subscription側にデータが反映されていることを確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# SELECT * FROM t9logical; c1 | c2 ----+---------------- 2 | HAPPY NEW YEAR (1 row)上記に該当しないテーブルに対して、INSERT,UPDATE,DELETEを実施します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# INSERT INTO t10logical VALUES (1, 'MERRY CHRISTMAS'); =# INSERT INTO t10logical VALUES (2, 'CHRISTMAS DAY'); =# UPDATE t10logical SET c2 = 'HAPPY NEW YEAR' WHERE c1 = 2; ERROR: cannot update table "t10logical" because it does not have a replica identity and publishes updates HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. ※ REPLICA IDENTITYが指定されていない場合は、UPDATEできない旨がメッセージとして出力される。 =# DELETE FROM t10logical WHERE c1 = 1; ERROR: cannot delete from table "t10logical" because it does not have a replica identity and publishes deletes HINT: To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE. =# SELECT * FROM t10logical; c1 | c2 ----+----------------- 1 | MERRY CHRISTMAS 2 | CHRISTMAS DAY (2 rows)Subscription側にデータが反映されないことを確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# SELECT * FROM t10logical; c1 | c2 ----+----------------- 1 | MERRY CHRISTMAS 2 | CHRISTMAS DAY (2 rows)上記に該当しないテーブルにreplica identityにfullを指定したテーブルに対して、INSERT,UPDATE,DELETEを実施します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# INSERT INTO t11logical VALUES (1, 'MERRY CHRISTMAS'); =# INSERT INTO t11logical VALUES (2, 'CHRISTMAS DAY'); =# UPDATE t11logical SET c2 = 'HAPPY NEW YEAR' WHERE c1 = 2; =# DELETE FROM t11logical WHERE c1 = 1; =# SELECT * FROM t11logical; c1 | c2 ----+---------------- 2 | HAPPY NEW YEAR (1 row)Subscription側にデータが反映されていることを確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest3 =# SELECT * FROM t11logical; c1 | c2 ----+---------------- 2 | HAPPY NEW YEAR (1 row)
ロジカルレプリケーションにおいて同期レプリケーションを設定する手順を確認します。
本検証では、下記のレプリケーション設定において同期モードが実現可能かの検証を実施しました。
■ 初期状態
現在のレプリケーション状態を確認します。
(node1,node2のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication" (0 rows)
本検証で利用するデータベースを作成します。
(node1,node2のサーバにおいて実施) $ createdb testlogicaldb以下のコマンドでデータベースが作成された事を確認します。
(node1,node2のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ---------------+----------+----------+---------+-------+----------------------- testlogicaldb | postgres | UTF8 | C | C | <-- 作成したDBを確認 [省略]
作成したデータベースにテスト用のテーブルを作成します。
(node1,node2のサーバにおいて実施) $ psql -U postgres testlogicaldb =# CREATE TABLE t1logical (c1_no integer PRIMARY KEY, c2_date_time timestamp); =# SELECT * FROM t1logical; c1_no | c2_date_time -------+-------------- (0 rows)以下のコマンドで作成したテーブルを確認します。
(node1,node2のサーバにおいて実施) =# \dt List of relations Schema | Name | Type | Owner --------+-----------+-------+---------- public | t1logical | table | postgres (1 row)
■ ロジカルレプリケーションでの作成
node1に作成したt1logicalテーブルをレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) =# CREATE PUBLICATION test_slot1 FOR TABLE t1logical; =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ------------+------------+----------- test_slot1 | public | t1logical (1 row)以下のコマンドでPublicationが作成された事を確認します。
=# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ------------+----------+------------+---------+---------+--------- test_slot1 | postgres | f | t | t | t <-- オールテーブルではない (1 row)node2にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) =# CREATE SUBSCRIPTION test_slot1 CONNECTION 'host=node1 dbname=testlogicaldb port=5432 user=postgres' PUBLICATION test_slot1; =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 24755 | 24750 | r | 2/912A8A10 (1 row)以下のコマンドでSubscriptionが作成された事を確認します。
# \dRs List of subscriptions Name | Owner | Enabled | Publication ------------+----------+---------+-------------- test_slot1 | postgres | t | {test_slot1} (1 row)
現在のレプリケーション状態を確認します。作成したPublicationとSubscriptionが正常にレプリケーションされているかをapplication_name、state、sync_state項目で確認します。(sync_stateは現時点ではasyncとなります。)(node1のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication" -[ RECORD 1 ]----+------------------------------ pid | 1667 usesysid | 10 usename | postgres application_name | test_slot1 <-- subscription_nameが表示される client_addr | 192.168.43.11 client_hostname | client_port | 35564 backend_start | 2018-01-05 10:39:02.914132+09 backend_xmin | state | streaming <-- streaming が表示される sent_lsn | 2/912A8A10 write_lsn | 2/912A8A10 flush_lsn | 2/912A8A10 replay_lsn | 2/912A8A10 write_lag | flush_lag | replay_lag | sync_priority | 0 sync_state | async <-- async(現時点では非同期)
■ 同期レプリケーションの設定
コマンドラインからレプリケーションの設定を同期レプリケーションにします。
(node1のサーバにおいて実施) $ psql -U postgres testlogicaldb =# ALTER SYSTEM SET synchronous_standby_names = 'test_slot1'; <-- application_nameを指定する設定ファイルで同期レプリケーションに変更された事を確認します。
(node1のサーバにおいて実施) $ cat /var/lib/pgsql/10/data/postgresql.auto.conf # Do not edit this file manually! # It will be overwritten by ALTER SYSTEM command. synchronous_standby_names = 'test_slot1' <-- 同期設定にapplication_nameが設定されている設定ファイルの内容を反映します。
(node1のサーバにおいて実施) $ pg_ctl reloadPostgreSQL起動時にsynchronous_standby_namesパラメータが読み込まれている事を確認します。
(nodeのサーバにおいてサーバ実施) $ tail /var/lib/pgsql/10/data/log/postgresql-2018-01-05.log <-- ログで設定が読み込まれていることを確認 LOG: received SIGHUP, reloading configuration files LOG: parameter "synchronous_standby_names" changed to "test_slot1" LOG: standby "test_slot1" is now a synchronous standby with priority 1現在のレプリケーション状態を確認します。sync_stateがsyncとなっている事を確認します。(node1のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication" -[ RECORD 1 ]----+------------------------------ pid | 1667 usesysid | 10 usename | postgres application_name | test_slot1 client_addr | 192.168.43.11 client_hostname | client_port | 35564 backend_start | 2018-01-05 10:39:02.914132+09 backend_xmin | state | streaming sent_lsn | 2/912A8A10 write_lsn | 2/912A8A10 flush_lsn | 2/912A8A10 replay_lsn | 2/912A8A10 write_lag | flush_lag | replay_lag | sync_priority | 1 sync_state | sync <-- sync(同期)になる
■ ロジカルレプリケーション動作確認
テスト用に作成したPublication側のテーブルにレコードをINSERTします。
(node1のサーバにおいて実施) $ psql -U postgres testlogicaldb =# INSERT INTO t1logical VALUES (1,localtimestamp); =# SELECT * FROM t1logical; c1_no | c2_date_time -------+---------------------------- 1 | 2018-01-05 11:07:36.590471 (1 row)Subscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM t1logical; c1_no | c2_date_time -------+---------------------------- 1 | 2018-01-05 11:07:36.590471 (1 row)
Publication側のテーブルのレコードをUPDATEします。
(node1のサーバにおいて実施) =# UPDATE t1logical SET c2_date_time = localtimestamp WHERE c1_no = 1; =# SELECT * FROM t1logical; c1_no | c2_date_time -------+---------------------------- 1 | 2018-01-05 11:10:14.419084 (1 row)Subscription側のテーブルでもレコードがUPDATEされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM t1logical; c1_no | c2_date_time -------+---------------------------- 1 | 2018-01-05 11:10:14.419084 (1 row)
Publication側のテーブルのレコードをDELETEします。
(node1のサーバにおいて実施) =# DELETE FROM t1logical WHERE c1_no = 1; =# SELECT * FROM t1logical; c1_no | c2_date_time -------+-------------- (0 rows) <-- 指定のデータが削除されるSubscription側のテーブルでもレコードがDELETEされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM t1logical; c1_no | c2_date_time -------+-------------- (0 rows) <-- 指定のデータが削除される
Subscription側のデータベース、テーブルのoidを確認します。
(node2のサーバにおいて実施) =# SELECT oid, datname FROM pg_database; oid | datname -------+--------------- 24748 | testlogicaldb [省略] =# SELECT oid, datname FROM pg_database; relid | relname -------+----------------- 24750 | t1logical [省略]Subscription側のロック情報を確認します。
(node2のサーバにおいて実施) =# SELECT locktype, database, relation, mode FROM pg_locks; locktype | database | relation | mode ------------+----------+----------+----------------- relation | 24748 | 11577 | AccessShareLock <-- ロックされていない virtualxid | | | ExclusiveLock (2 rows)Subscription側のテーブルをトランザクション内でロック状態のままにします。(ロックモード : EXCLUSIVE MODE)
(node2のサーバにおいて実施) =# BEGIN; =# LOCK t1logical IN ACCESS EXCLUSIVE MODE ; <-- テーブルをロックSubscription側のロック情報を確認します。
(node2のサーバにおいて実施) =# SELECT locktype, database, relation, mode FROM pg_locks; locktype | database | relation | mode ---------------+----------+----------+--------------------- relation | 24748 | 11577 | AccessShareLock virtualxid | | | ExclusiveLock transactionid | | | ExclusiveLock relation | 24748 | 24750 | AccessExclusiveLock <-- テーブルをロック [省略]Subscription側のテーブルがロック状態のまま、Publication側のテーブルにレコードをINSERTします。Subscription側(スタンバイ)からCommitの応答があるまで待機状態となります。(ただし、60秒以上応答がない場合は、タイムアウトのログが出力されます。)(node1のサーバにおいて実施) =# INSERT INTO t1logical VALUES (1,localtimestamp); <-- セカンダリから commit 応答がないためプロンプトが止まる(node1のサーバにおいて実施) $ tail /var/lib/pgsql/10/data/log/postgresql-2018-01-05.log LOG: terminating walsender process due to replication timeout <-- 設定値 wal_sender_timeout (default 60s)待ってログが出力される[Ctrl+c] Cancel request sent WARNING: canceling wait for synchronous replication due to user request DETAIL: The transaction has already committed locally, but might not have been replicated to the standby. INSERT 0 1Subscription側のテーブルロックを解除するとレプリケーションが動作します。Subscription側のテーブルでもレコードがINSERTされている事を確認します。(node2のサーバにおいて実施) =# COMMIT; <-- ロック解除 =# SELECT locktype, database, relation, mode FROM pg_locks; locktype | database | relation | mode ------------+----------+----------+----------------- relation | 24748 | 11577 | AccessShareLock <-- ロックされていない virtualxid | | | ExclusiveLock (2 rows) =# SELECT * FROM t1logical ; c1_no | c2_date_time -------+---------------------------- 1 | 2018-01-05 13:44:52.134441 <-- レプリケーションされている (1 row)
ロジカルレプリケーションで複数のSubscriptionにレプリケーションが可能か確認します。
本検証では、3ノードで下記構成のレプリケーションが実現可能かの検証を実施しました。
■ ロジカルレプリケーションの動作確認(テーブル単位)
本検証で利用するデータベースを作成します。
(node1,node2,node3のサーバにおいて実施) $ createdb logicalreptest以下のコマンドでデータベースが作成された事を確認します。
(node1,node2,node3のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ----------------+----------+----------+---------+-------+----------------------- logicalreptest | postgres | UTF8 | C | C | [省略]
作成したデータベースにテスト用のテーブルを作成します。
(node1,node2,node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE TABLE t1logical (c1 INT, PRIMARY KEY (c1)); =# CREATE TABLE t2logical (c1 INT, PRIMARY KEY (c1)); =# CREATE TABLE t3logical (c1 INT, PRIMARY KEY (c1));以下のコマンドで作成したテーブルを確認します。
(node1,node2,node3のサーバにおいて実施) =# \dt List of relations Schema | Name | Type | Owner --------+-----------+-------+---------- public | t1logical | table | postgres public | t2logical | table | postgres public | t3logical | table | postgres (3 rows)
node1に作成したt1logicalテーブルをレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node1_t1logical FOR TABLE t1logical;以下のコマンドでPublicationが作成された事を確認します。
(node1のサーバにおいて実施) =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node1_t1logical | postgres | f | t | t | t (1 row)node2にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node2_t1logical CONNECTION 'host=node1 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node1_t1logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node2のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node2_t1logical | postgres | t | {pub_node1_t1logical} (1 row)node3にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node3_t1logical CONNECTION 'host=node1 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node1_t1logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node3のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node3_t1logical | postgres | t | {pub_node1_t1logical} (1 row)
node2に作成したt2logicalテーブルをレプリケーション対象とするPublicationを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node2_t2logical FOR TABLE t2logical;以下のコマンドでPublicationが作成された事を確認します。
(node2のサーバにおいて実施) =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node2_t2logical | postgres | f | t | t | t (1 row)node3にnode2に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node3_t2logical CONNECTION 'host=node2 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node2_t2logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node3のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node3_t1logical | postgres | t | {pub_node1_t1logical} sub_node3_t2logical | postgres | t | {pub_node2_t2logical} (2 rows)node1にnode2に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node1_t2logical CONNECTION 'host=node2 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node2_t2logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node1のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node1_t2logical | postgres | t | {pub_node2_t2logical} (1 row)
node3に作成したt3logicalテーブルをレプリケーション対象とするPublicationを作成します。
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node3_t3logical FOR TABLE t3logical;以下のコマンドでPublicationが作成された事を確認します。
=# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node3_t3logical | postgres | f | t | t | t (1 row)node3に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node2_t3logical CONNECTION 'host=node3 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node3_t3logical;以下のコマンドでSubscriptionが作成された事を確認します。
=# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node2_t1logical | postgres | t | {pub_node1_t1logical} sub_node2_t3logical | postgres | t | {pub_node3_t3logical} (2 rows)node3に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node1_t3logical CONNECTION 'host=node3 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node3_t3logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node1のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node1_t2logical | postgres | t | {pub_node2_t2logical} sub_node1_t3logical | postgres | t | {pub_node3_t3logical} (2 rows)
node1のPublication側のテーブル(t1logical)にレコードをINSERTします。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# INSERT INTO t1logical VALUES (1); =# INSERT INTO t1logical VALUES (2); =# SELECT * FROM t1logical; c1 ---- 1 2 (2 rows)node2のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t1logical; c1 ---- 1 <-- node1と同じ結果がレプリケーションされていること 2 (2 rows)node3のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t1logical; c1 ---- 1 <-- node1と同じ結果がレプリケーションされていること 2 (2 rows)
node2のPublication側のテーブル(t2logical)にレコードをINSERTします。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# INSERT INTO t2logical VALUES (3); =# INSERT INTO t2logical VALUES (4); =# SELECT * FROM t2logical; c1 ---- 3 4 (2 rows)node3のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t2logical; c1 ---- 3 <-- node2と同じ結果がレプリケーションされていること 4 (2 rows)node1のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t2logical; c1 ---- 3 <-- node2と同じ結果がレプリケーションされていること 4 (2 rows)
node3のPublication側のテーブル(t3logical)にレコードをINSERTします。
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# INSERT INTO t3logical VALUES (5); =# INSERT INTO t3logical VALUES (6); =# SELECT * FROM t3logical; c1 ---- 5 6 (2 rows)node2のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t3logical; c1 ---- 5 <-- node3同じ結果がレプリケーションされていること 6 (2 rows)node1のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t3logical; c1 ---- 5 <-- node3と同じ結果がレプリケーションされていること 6 (2 rows)
■ 動作確認
node1での各Publication/Subscriptionの定義を確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest ・Publicationに設定したテーブルを確認 =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ---------------------+------------+----------- pub_node1_t1logical | public | t1logical (1 row) ・レプリケーションしている状態の確認 =# SELECT pid, application_name, client_addr, state, sync_state FROM pg_stat_replication; pid | application_name | client_addr | state | sync_state ------+---------------------+---------------+-----------+------------ 1291 | sub_node2_t1logical | node2 | streaming | async 1298 | sub_node3_t1logical | node3 | streaming | async (2 rows) ・Subscriptionの設定情報 =# SELECT subslotname, subpublications, subconninfo FROM pg_subscription; subslotname | subpublications | subconninfo ---------------------+-----------------------+----------------------------------------------------------------------------- sub_node1_t2logical | {pub_node2_t2logical} | host=node2 dbname=logicalreptest port=5432 user=postgres password=postgres sub_node1_t3logical | {pub_node3_t3logical} | host=node3 dbname=logicalreptest port=5432 user=postgres password=postgres (2 rows)node2での各Publication/Subscriptionの定義を確認します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest ・Publicationに設定したテーブルを確認 =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ---------------------+------------+----------- pub_node2_t2logical | public | t2logical (1 row) ・レプリケーションしている状態の確認 =# SELECT pid, application_name, client_addr, state, sync_state FROM pg_stat_replication; pid | application_name | client_addr | state | sync_state ------+---------------------+---------------+-----------+------------ 1323 | sub_node1_t2logical | node1 | streaming | async 1344 | sub_node3_t2logical | node3 | streaming | async (2 rows) ・Subscriptionの設定情報 =# SELECT subslotname, subpublications, subconninfo FROM pg_subscription; subslotname | subpublications | subconninfo ---------------------+-----------------------+----------------------------------------------------------------------------- sub_node2_t3logical | {pub_node3_t3logical} | host=node3 dbname=logicalreptest port=5432 user=postgres password=postgres sub_node2_t1logical | {pub_node1_t1logical} | host=node1 dbname=logicalreptest port=5432 user=postgres password=postgres (2 rows)node3での各Publication/Subscriptionの定義を確認します。
(node3において実施) $ psql -U postgres logicalreptest ・Publicationに設定したテーブルを確認 =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ---------------------+------------+----------- pub_node3_t3logical | public | t3logical (1 row) ・レプリケーションしている状態の確認 =# SELECT pid, application_name, client_addr, state, sync_state FROM pg_stat_replication; pid | application_name | client_addr | state | sync_state ------+---------------------+---------------+-----------+------------ 1346 | sub_node1_t3logical | node1 | streaming | async 1348 | sub_node2_t3logical | node2 | streaming | async (2 rows) ・Subscriptionの設定情報 =# SELECT subslotname, subpublications, subconninfo FROM pg_subscription; subslotname | subpublications | subconninfo ---------------------+-----------------------+---------------------------------------------------------------------------- sub_node3_t1logical | {pub_node1_t1logical} | host=node1 dbname=logicalreptest port=5432 user=postgres password=postgres sub_node3_t2logical | {pub_node2_t2logical} | host=node2 dbname=logicalreptest port=5432 user=postgres password=postgres (2 rows)
ロジカルレプリケーションを利用したカスケード構成の挙動を確認します。
本検証では、下記構成のレプリケーションが実現可能かの検証を実施しました。
■ ロジカルレプリケーションの動作確認(テーブル単位)
本検証で利用するデータベースを作成します。
(node1,node2,node3のサーバにおいて実施) $ createdb logicalreptest以下のコマンドでデータベースが作成された事を確認します。
(node1,node2,node3のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ----------------+----------+----------+---------+-------+----------------------- logicalreptest | postgres | UTF8 | C | C | [省略]
作成したデータベースにテスト用のテーブルを作成します。
(node1,node2,node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE TABLE t1logical (c1 INT, PRIMARY KEY (c1));以下のコマンドで作成したテーブルを確認します。
(node1,node2,node3のサーバにおいて実施) =# \d List of relations Schema | Name | Type | Owner --------+-----------+-------+---------- public | t1logical | table | postgres (1 row)
node1に作成したt1logicalテーブルをレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node1_t1logical FOR TABLE t1logical;以下のコマンドでPublicationが作成された事を確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node1_t1logical | postgres | f | t | t | t (1 row)
node2にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node2_t1logical CONNECTION 'host=node1 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node1_t1logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node2_t1logical | postgres | t | {pub_node1_t1logical} (1 row)
node1に作成したPublicationとレプリケーションする対象となっていたテーブル(t1logical)を新たにレプリケーション対象としたPublicationをnode2に作成します。(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node2_t1logical FOR TABLE t1logical;以下のコマンドでPublicationが作成された事を確認します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node2_t1logical | postgres | f | t | t | t (1 row)
node3にnode2に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node3_t1logical CONNECTION 'host=node2 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node2_t1logical;以下のコマンドでSubscriptionが作成された事を確認します。
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# \dRs List of subscriptions Name | Owner | Enabled | Publication ---------------------+----------+---------+----------------------- sub_node3_t1logical | postgres | t | {pub_node2_t1logical} (1 row)
node1のPublication側のテーブルにレコードをINSERTします。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# INSERT INTO t1logical VALUES (1); =# INSERT INTO t1logical VALUES (2); =# SELECT * FROM t1logical; c1 ---- 1 2 (2 rows)
node2のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(node1からレプリケーションされている事を確認)
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t1logical; c1 ---- 1 <-- node1と同じ結果がレプリケーションされていること 2 (2 rows)node3のSubscription側のテーブルにもレコードがINSERTされている事を確認します。(node2からレプリケーションされている事を確認)
(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# SELECT * FROM t1logical; c1 ---- 1 <-- node1と同じ結果がレプリケーションされていること 2 (2 rows)
■ 動作確認
node1でPublicationの定義を確認します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest ・Publicationに設定したテーブルを確認 =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ---------------------+------------+----------- pub_node1_t1logical | public | t1logical (1 row) ・レプリケーションしている状態の確認 =# SELECT pid, application_name, client_addr, state, sync_state FROM pg_stat_replication; pid | application_name | client_addr | state | sync_state ------+---------------------+---------------+-----------+------------ 31121| sub_node2_t1logical | node2 | streaming | async (1 rows)node2でのPublication/Subscriptionの定義を確認します。
(node2のサーバにおいて実施) $ psql -U postgres logicalreptest ・Publicationに設定したテーブルを確認 =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ---------------------+------------+----------- pub_node2_t1logical | public | t1logical (1 row) ・レプリケーションしている状態の確認 =# SELECT pid, application_name, client_addr, state, sync_state FROM pg_stat_replication; pid | application_name | client_addr | state | sync_state ------+---------------------+---------------+-----------+------------ 9185 | sub_node3_t1logical | node3 | streaming | async (1 rows) ・Subscriptionの設定情報 =# SELECT subslotname, subpublications, subconninfo FROM pg_subscription; subslotname | subpublications | subconninfo ---------------------+-----------------------+----------------------------------------------------------------------------- sub_node2_t1logical | {pub_node1_t1logical} | host=node1 dbname=logicalreptest port=5432 user=postgres password=postgres (1 rows)node3のSubscriptionの定義を確認します。
(node3において実施) $ psql -U postgres logicalreptest ・レプリケーションしている状態の確認 =# SELECT pid, application_name, client_addr, state, sync_state FROM pg_stat_replication; (0 rows) ・Subscriptionの設定情報 =# SELECT subslotname, subpublications, subconninfo FROM pg_subscription; subslotname | subpublications | subconninfo ---------------------+-----------------------+---------------------------------------------------------------------------- sub_node3_t1logical | {pub_node2_t1logical} | host=node2 dbname=logicalreptest port=5432 user=postgres password=postgres (1 row)
■ 追加検証(PublicationとSubscriptionの関係をループさせた場合の挙動)
node2に作成したPublicationとレプリケーションする対象となっていたテーブル(t1logical)を新たにレプリケーション対象としたPublicationをnode3に作成します。(node3のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE PUBLICATION pub_node3_t1logical FOR TABLE t1logical;以下のコマンドでPublicationが作成された事を確認します。
$ psql -U postgres logicalreptest =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ---------------------+----------+------------+---------+---------+--------- pub_node3_t1logical | postgres | f | t | t | t (1 row)
node1にnode3に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# CREATE SUBSCRIPTION sub_node1_t1logical CONNECTION 'host=node3 dbname=logicalreptest port=5432 user=postgres password=postgres' PUBLICATION pub_node3_t1logical;
レプリケーション元とレプリケーション先がループしているため、初期データのコピー時に一意制約違反が発生し、5秒毎にエラーメッセージが出力されます。(node1のサーバにおいて実施) $ psql -U postgres logicalreptest =# INSERT INTO t1logical VALUES (1); (PostgreSQLログのエラーメッセージ) [2018-01-31 17:18:03.060 JST][][][00000][444] LOG: logical replication table synchronization worker for subscription "sub_node1_t1logical", table "t1logical" has started [2018-01-31 17:18:03.072 JST][][][23505][444] ERROR: duplicate key value violates unique constraint "t1logical_pkey" [2018-01-31 17:18:03.072 JST][][][23505][444] DETAIL: Key (c1)=(1) already exists. [2018-01-31 17:18:03.072 JST][][][23505][444] CONTEXT: COPY t1logical, line 1 [2018-01-31 17:18:03.073 JST][][][00000][227] LOG: worker process: logical replication worker for subscription 16393 sync 16385 (PID 444) exited with exit code 1
■ 環境構築
現在のレプリケーション状態を確認します。
(node1,node2のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication;" (0 rows)
本検証で利用するデータベースを作成します。
(node1,node2のサーバにおいて実施) $ createdb testlogicalre以下のコマンドでデータベースが作成された事を確認します。
(node1,node2のサーバにおいて実施) $ psql -U postgres -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ---------------+----------+----------+---------+-------+----------------------- testlogicalre | postgres | UTF8 | C | C | [省略]
(node1,node2のサーバにおいて実施) $ pgbench -i -U postgres testlogicalre $ psql -U postgres testlogicalre =# DROP TABLE pgbench_history; =# CREATE TABLE pgbench_history(tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22)) PARTITION BY RANGE (mtime); =# CREATE TABLE pgbench_history_201801 PARTITION OF pgbench_history FOR VALUES FROM ('2018-01-01') TO ('2018-02-01'); =# CREATE TABLE pgbench_history_201712 PARTITION OF pgbench_history FOR VALUES FROM ('2017-12-01') TO ('2018-01-01'); =# CREATE TABLE pgbench_history_201711 PARTITION OF pgbench_history FOR VALUES FROM ('2017-11-01') TO ('2017-12-01'); =# ALTER TABLE pgbench_history_201801 ADD CONSTRAINT pgbench_history_201801_pkey PRIMARY KEY(tid, aid); =# ALTER TABLE pgbench_history_201712 ADD CONSTRAINT pgbench_history_201712_pkey PRIMARY KEY(tid, aid); =# ALTER TABLE pgbench_history_201711 ADD CONSTRAINT pgbench_history_201711_pkey PRIMARY KEY(tid, aid); =# SELECT relname, n_live_tup AS rowcount FROM pg_stat_all_tables WHERE relname LIKE 'pgbench_history%' ORDER BY relname; relname | rowcount ------------------------+---------- pgbench_history_201711 | 0 pgbench_history_201712 | 0 pgbench_history_201801 | 0 (3 rows)
node1に作成したパーティションの子テーブルをレプリケーション対象とするPublicationを作成します。
(node1のサーバにおいて実施) =# CREATE PUBLICATION pub_node1_parttables FOR TABLE pgbench_history_201711; =# ALTER PUBLICATION pub_node1_parttables ADD TABLE pgbench_history_201712; =# ALTER PUBLICATION pub_node1_parttables ADD TABLE pgbench_history_201801; =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ----------------------+------------+------------------------ pub_node1_parttables | public | pgbench_history_201801 pub_node1_parttables | public | pgbench_history_201712 pub_node1_parttables | public | pgbench_history_201711 (3 rows)以下のコマンドでPublicationが作成された事を確認します。
(node1のサーバにおいて実施) =# \dRp List of publications Name | Owner | All tables | Inserts | Updates | Deletes ----------------------+----------+------------+---------+---------+--------- pub_node1_parttables | postgres | f | t | t | t (1 row)node2にnode1に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node2のサーバにおいて実施) =# CREATE SUBSCRIPTION sub_node2_parttables CONNECTION 'host=node1 dbname=testlogicalre port=5432 user=postgres' PUBLICATION pub_node1_parttables; =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 18381 | 18372 | r | 0/109C2570 18381 | 18369 | r | 0/109C2570 18381 | 18366 | r | 0/109C25A8 (3 rows)以下のコマンドでSubscriptionが作成された事を確認します。
(node2のサーバにおいて実施) =# \dRs List of subscriptions Name | Owner | Enabled | Publication ----------------------+----------+---------+------------------------ sub_node2_parttables | postgres | t | {pub_node1_parttables} (1 row)
■ ロジカルレプリケーション簡易動作確認
pgbenchで動作させるスクリプトを作成します。
(node1のサーバにおいて実施) $ vi part_test.sql ----- \set aid random(1, 90 * :scale) \set bid random(1, 1 * :scale) \set tid random(1, 10000 * :scale) \set delta random(-5000, 5000) BEGIN; INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CAST('2018-01-31' AS date) - CAST(:aid AS integer)); UPDATE pgbench_history SET filler = to_char(:bid + :delta, '9999') WHERE tid = :tid AND aid = :aid; END; -----node1のPublication側のテーブルに対して、pgbenchでスクリプトを実行します。
(node1のサーバにおいて実施) $ pgbench -U postgres -c 1 -t 1000 -f part_test.sql testlogicalreレコードがパーティショニングテーブルに振り分けられている事を確認します。
(node1のサーバにおいて実施) $ psql -U postgres testlogicalre =# ANALYZE; =# SELECT relname, n_live_tup AS rowcount FROM pg_stat_all_tables WHERE relname LIKE 'pgbench_history%' ORDER BY relname; relname | rowcount ------------------------+---------- pgbench_history_201711 | 348 pgbench_history_201712 | 332 pgbench_history_201801 | 320 (3 rows)Publication側のパーティショニングテーブルのレコード情報を確認します。
(node1のサーバにおいて実施) =# SELECT * FROM pgbench_history_201711 ORDER BY tid LIMIT 3; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+---------------------+------------------------ 27 | 1 | 75 | 2772 | 2017-11-17 00:00:00 | 2773 125 | 1 | 90 | 1513 | 2017-11-02 00:00:00 | 1514 146 | 1 | 81 | -3348 | 2017-11-11 00:00:00 | -3347 (3 rows) (node1のサーバにおいて実施) =# SELECT * FROM pgbench_history_201712 ORDER BY tid LIMIT 3; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+---------------------+------------------------ 66 | 1 | 35 | -4563 | 2017-12-27 00:00:00 | -4562 156 | 1 | 55 | 1206 | 2017-12-07 00:00:00 | 1207 231 | 1 | 51 | -894 | 2017-12-11 00:00:00 | -893 (3 rows) (node1のサーバにおいて実施) =# SELECT * FROM pgbench_history_201801 ORDER BY tid LIMIT 3; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+---------------------+------------------------ 13 | 1 | 22 | 339 | 2018-01-09 00:00:00 | 340 22 | 1 | 29 | -1540 | 2018-01-02 00:00:00 | -1539 41 | 1 | 13 | -4569 | 2018-01-18 00:00:00 | -4568 (3 rows)パーティショニングテーブルの予測行数がnode1と一致しているか否かを確認します。
(node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# ANALYZE; =# SELECT relname, n_live_tup AS rowcount FROM pg_stat_all_tables WHERE relname LIKE 'pgbench_history%' ORDER BY relname; relname | rowcount ------------------------+---------- pgbench_history_201711 | 348 pgbench_history_201712 | 332 pgbench_history_201801 | 320 (3 rows)Subscription側のパーティショニングテーブルでnode1とレコードが一致している事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) =# SELECT * FROM pgbench_history_201711 ORDER BY tid LIMIT 3; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+---------------------+------------------------ 27 | 1 | 75 | 2772 | 2017-11-17 00:00:00 | 2773 125 | 1 | 90 | 1513 | 2017-11-02 00:00:00 | 1514 146 | 1 | 81 | -3348 | 2017-11-11 00:00:00 | -3347 (3 rows) =# SELECT * FROM pgbench_history_201712 ORDER BY tid LIMIT 3; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+---------------------+------------------------ 66 | 1 | 35 | -4563 | 2017-12-27 00:00:00 | -4562 156 | 1 | 55 | 1206 | 2017-12-07 00:00:00 | 1207 231 | 1 | 51 | -894 | 2017-12-11 00:00:00 | -893 (3 rows) =# SELECT * FROM pgbench_history_201801 ORDER BY tid LIMIT 3; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+---------------------+------------------------ 13 | 1 | 22 | 339 | 2018-01-09 00:00:00 | 340 22 | 1 | 29 | -1540 | 2018-01-02 00:00:00 | -1539 41 | 1 | 13 | -4569 | 2018-01-18 00:00:00 | -4568 (3 rows)
(node1のサーバにおいて実施) $ psql -x -U postgres -c "SELECT * FROM pg_stat_replication;" -[ RECORD 1 ]----+------------------------------ pid | 1446 usesysid | 10 usename | postgres application_name | sub_node2_parttables <-- subscriptionの名前が表示される client_addr | 192.168.56.102 client_hostname | client_port | 52428 backend_start | 2018-02-05 10:21:33.710471+09 backend_xmin | state | streaming <-- streaming が表示される sent_lsn | 0/10A29160 write_lsn | 0/10A29160 flush_lsn | 0/10A29160 replay_lsn | 0/10A29160 write_lag | flush_lag | replay_lag | sync_priority | 0 sync_state | async <-- async(非同期)
■ テーブル追加
新たにパーティショニングテーブルを作成します。
(node1,node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# CREATE TABLE pgbench_history_201802 PARTITION OF pgbench_history FOR VALUES FROM ('2018-02-01') TO ('2018-03-01'); =# ALTER TABLE pgbench_history_201802 ADD CONSTRAINT pgbench_history_201802_pkey PRIMARY KEY(tid, aid);以下のコマンドで作成したテーブルを確認します。
=# \dt List of relations Schema | Name | Type | Owner --------+------------------------+-------+---------- public | pgbench_accounts | table | postgres public | pgbench_branches | table | postgres public | pgbench_history | table | postgres public | pgbench_history_201711 | table | postgres public | pgbench_history_201712 | table | postgres public | pgbench_history_201801 | table | postgres public | pgbench_history_201802 | table | postgres public | pgbench_tellers | table | postgres (8 rows)
作成したパーティショニングテーブルを既存のPublicationに追加します。
(node1のサーバにおいて実施) $ psql -U postgres testlogicalre =# ALTER PUBLICATION pub_node1_parttables ADD TABLE pgbench_history_201802; =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ----------------------+------------+------------------------ pub_node1_parttables | public | pgbench_history_201801 pub_node1_parttables | public | pgbench_history_201712 pub_node1_parttables | public | pgbench_history_201711 pub_node1_parttables | public | pgbench_history_201802 (4 rows)更新したPublicationの情報をSubscriptionに反映させます。
(node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 18381 | 18372 | r | 0/109C2570 18381 | 18369 | r | 0/109C2570 18381 | 18366 | r | 0/109C25A8 (3 rows) =# ALTER SUBSCRIPTION sub_node2_parttables REFRESH PUBLICATION; =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 18381 | 18382 | r | 0/10A3EB98 18381 | 18372 | r | 0/109C2570 18381 | 18369 | r | 0/109C2570 18381 | 18366 | r | 0/109C25A8 (4 rows)
■ ロジカルレプリケーション動作確認
新たに作成したPublication側のパーティショニングテーブルにレコードをINSERTします。
(node1のサーバにおいて実施) $ psql -U postgres testlogicalre =# SELECT * FROM pgbench_history_201802; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+-------+-------- (0 rows) =# INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (7777, 1, 12, 4321, '2018-02-11 00:00:00'); =# SELECT * FROM pgbench_history_201802; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 7777 | 1 | 12 | 4321 | 2018-02-11 00:00:00 | (1 row)Subscription側のパーティショニングテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# SELECT * FROM pgbench_history_201802; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 7777 | 1 | 12 | 4321 | 2018-02-11 00:00:00 | (1 row)
■ 子テーブルの取り外し/取り付け
node1のパーティショニングテーブルの取り外しとPublicationからの削除を行います。
(node1のサーバにおいて実施) $ psql -U postgres testlogicalre =# SELECT * FROM pgbench_history WHERE tid = 7777; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 7777 | 1 | 24 | 4321 | 2018-02-11 00:00:00 | (1 row) =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ----------------------+------------+------------------------ pub_node1_parttables | public | pgbench_history_201801 pub_node1_parttables | public | pgbench_history_201712 pub_node1_parttables | public | pgbench_history_201711 pub_node1_parttables | public | pgbench_history_201802 (4 rows) =# ALTER TABLE pgbench_history DETACH PARTITION pgbench_history_201802; =# ALTER PUBLICATION pub_node1_parttables DROP TABLE pgbench_history_201802; =# SELECT * FROM pgbench_history WHERE tid = 7777; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+-------+-------- (0 rows) =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ----------------------+------------+------------------------ pub_node1_parttables | public | pgbench_history_201801 pub_node1_parttables | public | pgbench_history_201712 pub_node1_parttables | public | pgbench_history_201711 (3 rows)node2のパーティショニングテーブルの取り外しと更新したPublicationの情報のSubscriptionへの反映を行います。
(node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# SELECT * FROM pgbench_history WHERE tid = 7777; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 7777 | 1 | 24 | 4321 | 2018-02-11 00:00:00 | (1 row) =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 18381 | 18382 | r | 0/10A3EB98 18381 | 18372 | r | 0/109C2570 18381 | 18369 | r | 0/109C2570 18381 | 18366 | r | 0/109C25A8 (4 rows) =# ALTER TABLE pgbench_history DETACH PARTITION pgbench_history_201802; =# ALTER SUBSCRIPTION sub_node2_parttables REFRESH PUBLICATION; =# SELECT * FROM pgbench_history WHERE tid = 7777; tid | bid | aid | delta | mtime | filler -----+-----+-----+-------+-------+-------- (0 rows) =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 18381 | 18372 | r | 0/109C2570 18381 | 18369 | r | 0/109C2570 18381 | 18366 | r | 0/109C25A8 (3 rows)
node1のパーティショニングテーブルの取り付けとPublicationへの追加を行います。
(node1のサーバにおいて実施) $ psql -U postgres testlogicalre =# TRUNCATE TABLE pgbench_history_201802; =# ALTER TABLE pgbench_history ATTACH PARTITION pgbench_history_201802 FOR VALUES FROM ('2018-02-01') TO ('2018-03-01'); =# ALTER PUBLICATION pub_node1_parttables ADD TABLE pgbench_history_201802; =# SELECT * FROM pg_publication_tables; pubname | schemaname | tablename ----------------------+------------+------------------------ pub_node1_parttables | public | pgbench_history_201801 pub_node1_parttables | public | pgbench_history_201712 pub_node1_parttables | public | pgbench_history_201711 pub_node1_parttables | public | pgbench_history_201802 (4 rows)node2のパーティショニングテーブルの取り付けと更新したPublicationの情報のSubscriptionへの反映を行います。
(node2のサーバにおいて実施) $ psql -U postgres testlogicalre =# TRUNCATE TABLE pgbench_history_201802; =# ALTER TABLE pgbench_history ATTACH PARTITION pgbench_history_201802 FOR VALUES FROM ('2018-02-01') TO ('2018-03-01'); =# ALTER SUBSCRIPTION sub_node2_parttables REFRESH PUBLICATION; =# SELECT * FROM pg_subscription_rel; srsubid | srrelid | srsubstate | srsublsn ---------+---------+------------+------------ 18381 | 18382 | r | 0/10A523F8 18381 | 18372 | r | 0/109C2570 18381 | 18369 | r | 0/109C2570 18381 | 18366 | r | 0/109C25A8 (4 rows)
■ 子テーブルをレプリケーションし、パーティショニングを構成
本検証で利用するデータベースを作成します。
(node1のサーバにおいて実施) $ createdb testlogicalre2 (node2のサーバにおいて実施) $ createdb testlogicalre2_c1 $ createdb testlogicalre2_c2 $ createdb testlogicalre2_c3以下のコマンドでデータベースが作成された事を確認します。
(node1のサーバにおいて実施) $ psql -U postgres -p 5432 -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ----------------+----------+----------+---------+-------+----------------------- testlogicalre2 | postgres | UTF8 | C | C | [省略] (node2のサーバにおいて実施) $ psql -U postgres -p 5432 -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -------------------+----------+----------+---------+-------+----------------------- testlogicalre2_c1 | postgres | UTF8 | C | C | testlogicalre2_c2 | postgres | UTF8 | C | C | testlogicalre2_c3 | postgres | UTF8 | C | C | [省略]
(node1のサーバにおいて実施) $ pgbench -i -U postgres testlogicalre2 $ psql -U postgres testlogicalre2 =# DROP TABLE pgbench_history; =# CREATE TABLE pgbench_history(tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22)) PARTITION BY RANGE (mtime); =# CREATE TABLE pgbench_history_201711 PARTITION OF pgbench_history FOR VALUES FROM ('2017-11-01') TO ('2017-12-01'); =# CREATE TABLE pgbench_history_201712 PARTITION OF pgbench_history FOR VALUES FROM ('2017-12-01') TO ('2018-01-01'); =# CREATE TABLE pgbench_history_201801 PARTITION OF pgbench_history FOR VALUES FROM ('2018-01-01') TO ('2018-02-01'); =# ALTER TABLE pgbench_history_201711 ADD CONSTRAINT pgbench_history_201711_pkey PRIMARY KEY(tid, aid); =# ALTER TABLE pgbench_history_201712 ADD CONSTRAINT pgbench_history_201712_pkey PRIMARY KEY(tid, aid); =# ALTER TABLE pgbench_history_201801 ADD CONSTRAINT pgbench_history_201801_pkey PRIMARY KEY(tid, aid); =# SELECT relname, n_live_tup AS rowcount FROM pg_stat_all_tables WHERE relname LIKE 'pgbench_history%' ORDER BY relname; relname | rowcount ------------------------+---------- pgbench_history_201711 | 0 pgbench_history_201712 | 0 pgbench_history_201801 | 0 (3 rows)node2で作成した各データベースにテーブルを作成します。
(node2のサーバにおいて実施) $ psql -U postgres -p 5432 testlogicalre2_c1 =# CREATE TABLE pgbench_history_201711(tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22)); =# ALTER TABLE pgbench_history_201711 ADD CONSTRAINT pgbench_history_201711_pkey PRIMARY KEY(tid, aid); $ psql -U postgres -p 5432 testlogicalre2_c2 =# CREATE TABLE pgbench_history_201712(tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22)); =# ALTER TABLE pgbench_history_201712 ADD CONSTRAINT pgbench_history_201712_pkey PRIMARY KEY(tid, aid); $ psql -U postgres -p 5432 testlogicalre2_c3 =# CREATE TABLE pgbench_history_201801(tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22)); =# ALTER TABLE pgbench_history_201801 ADD CONSTRAINT pgbench_history_201801_pkey PRIMARY KEY(tid, aid);
node2の各データベースに作成したテーブルをレプリケーション対象とするPublicationをそれぞれ作成します。
(node2のサーバにおいて実施) $ psql -U postgres -p 5432 testlogicalre2_c1 =# CREATE PUBLICATION pub_node2_parttables1 FOR TABLE pgbench_history_201711; $ psql -U postgres -p 5432 testlogicalre2_c2 =# CREATE PUBLICATION pub_node2_parttables2 FOR TABLE pgbench_history_201712; $ psql -U postgres -p 5432 testlogicalre2_c3 =# CREATE PUBLICATION pub_node2_parttables3 FOR TABLE pgbench_history_201801;node1にnode2に作成したPublicationとレプリケーションするSubscriptionを作成します。
(node1のサーバにおいて実施) $ psql -U postgres -p 5432 testlogicalre2 =# CREATE SUBSCRIPTION sub_node1_parttables1 CONNECTION 'host=node2 dbname=testlogicalre2_c1 port=5432 user=postgres' PUBLICATION pub_node2_parttables1; =# CREATE SUBSCRIPTION sub_node1_parttables2 CONNECTION 'host=node2 dbname=testlogicalre2_c2 port=5432 user=postgres' PUBLICATION pub_node2_parttables2; =# CREATE SUBSCRIPTION sub_node1_parttables3 CONNECTION 'host=node2 dbname=testlogicalre2_c3 port=5432 user=postgres' PUBLICATION pub_node2_parttables3;
node2の各データベースのPublication側のテーブルにレコードをINSERTします。
(node2のサーバにおいて実施) $ psql -U postgres -p 5432 testlogicalre2_c1 =# INSERT INTO pgbench_history_201711 (tid, bid, aid, delta, mtime) VALUES (1111, 1, 12, 4321, '2017-11-11 00:00:00'); =# SELECT * FROM pgbench_history_201711; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 1111 | 1 | 12 | 4321 | 2017-11-11 00:00:00 | (1 row) $ psql -U postgres -p 5432 testlogicalre2_c2 =# INSERT INTO pgbench_history_201712 (tid, bid, aid, delta, mtime) VALUES (2222, 2, 34, 8765, '2017-12-11 00:00:00'); =# SELECT * FROM pgbench_history_201712; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 2222 | 2 | 34 | 8765 | 2017-12-11 00:00:00 | (1 row) $ psql -U postgres -p 5432 testlogicalre2_c3 =# INSERT INTO pgbench_history_201801 (tid, bid, aid, delta, mtime) VALUES (3333, 3, 56, 2109, '2018-01-11 00:00:00'); =# SELECT * FROM pgbench_history_201801; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 3333 | 3 | 56 | 2109 | 2018-01-11 00:00:00 | (1 row)Subscription側のテーブルにもレコードがINSERTされている事を確認します。(レプリケーションされている事を確認)
(node1のサーバにおいて実施) $ psql -U postgres -p 5432 testlogicalre2 =# SELECT * FROM pgbench_history; tid | bid | aid | delta | mtime | filler ------+-----+-----+-------+---------------------+-------- 3333 | 3 | 56 | 2109 | 2018-01-11 00:00:00 | 2222 | 2 | 34 | 8765 | 2017-12-11 00:00:00 | 1111 | 1 | 12 | 4321 | 2017-11-11 00:00:00 | (3 rows)
ロジカルレプリケーションが稼働する環境を監視する際に利用する情報について説明します。
本章は以下の環境を利用した検証結果を元に解説します。なお、ストリーミングレプリケーションと共用する情報(動的統計情報ビュー pg_stat_replication 等)もあるため、それぞれの表示形式の違いが比較できるようにロジカルレプリケーションとストリーミングレプリケーションを両方利用する環境を利用します。
Publisherのサーバにおいて、Publicationが作成されたデータベースに接続して確認できる情報は以下のとおりです。
■システムカタログ pg_publication [1]
接続中のデータベースに定義されているPublicationの情報を確認できます。 Publicationの名前、所有者に加えて、レプリケーションが行われる更新処理の種別がわかります。 また、FOR ALL TABLES句を指定して作成したPublicationは、puballtables = t となります。
(サーバ1において実施)
pubdb=# select oid, * from pg_publication;
oid | pubname | pubowner | puballtables | pubinsert | pubupdate | pubdelete
-------+---------+----------+--------------+-----------+-----------+-----------
16392 | pub1 | 16386 | f | t | t | t
16399 | pub2 | 16386 | f | t | t | t
16471 | pub3 | 10 | f | t | t | t
pubdb_all_table=# select oid, * from pg_publication;
oid | pubname | pubowner | puballtables | pubinsert | pubupdate | pubdelete
-------+---------------+----------+--------------+-----------+-----------+-----------
16475 | pub_all_table | 10 | t | t | t | t
[1] | PostgreSQL 10.0文書 - 51.40. pg_publication |
■システムカタログ pg_publication_rel [2]
接続中のデータベースに定義されているPublicationがレプリケーション対象とするテーブルを確認できます。
(サーバ1において実施)
pubdb=# select * from pg_publication_rel;
prpubid | prrelid
---------+---------
16392 | 16387
16399 | 16394
16471 | 16465
16392 | 16496
pubdb_all_table=# select * from pg_publication_rel;
prpubid | prrelid
---------+---------
ただし、Publication、テーブルのOIDしか保有しないため、実際に利用するには pg_publication, pg_class と結合して、 それぞれの名称を取得する必要があるでしょう。また、FOR ALL TABLES句を指定して作成したPublicationでは レプリケーション対象となっているテーブルが表示されません。
[2] | PostgreSQL 10.0文書 - 51.41. pg_publication_rel |
■システムカタログ pg_publication_tables [3]
接続中のデータベースに定義されているPublicationがレプリケーション対象とするテーブルを確認できます。
(サーバ1において実施)
pubdb=# select * from pg_publication_tables;
pubname | schemaname | tablename
---------+------------+-----------
pub1 | public | data1
pub1 | public | data1_1
pub2 | public | data2
pub3 | public | data3
pubdb_all_table=# select * from pg_publication_tables;
pubname | schemaname | tablename
---------------+------------+-----------
pub_all_table | public | data1
pub_all_table | public | data2
前述のpg_publication_relと比較して、Publication、テーブルの名前が直接確認できること、 FOR ALL TABLES句を指定して作成したPublicationでもレプリケーション対象のテーブルが確認できることから 実際の運用ではこちらを活用するとよいでしょう。
[3] | PostgreSQL 10.0文書 - 51.78. pg_publication_tables |
■psqlのメタコマンド
psqlコマンドのメタコマンド「dRp+」でもPublicationの情報を確認できます。
(サーバ1において実施)
pubdb=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes
---------+------------+---------+---------+---------
pubusr1 | f | t | t | t
Tables:
"public.data1"
"public.data1_1"
Publication pub2
Owner | All tables | Inserts | Updates | Deletes
---------+------------+---------+---------+---------
pubusr1 | f | t | t | t
Tables:
"public.data2"
Publication pub3
Owner | All tables | Inserts | Updates | Deletes
----------+------------+---------+---------+---------
postgres | f | t | t | t
Tables:
"public.data3"
pubdb_all_table=# \dRp+
Publication pub_all_table
Owner | All tables | Inserts | Updates | Deletes
----------+------------+---------+---------+---------
postgres | t | t | t | t
システムカタログpg_publication, pg_publication_relの情報がまとめて表示されますが、 FOR ALL TABLES句を指定して作成したPublicationのテーブルは表示されません。
■システムカタログpg_replication_slots [4]
Publisherに自動作成されるロジカルレプリケーションスロットの情報を確認できます。
(サーバ1において実施)
pubdb=# select * from pg_replication_slots;
slot_name | plugin | slot_type | datoid | database | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn
---------------+----------+-----------+--------+-----------------+-----------+--------+------------+------+--------------+-------------+---------------------
sub3 | pgoutput | logical | 16384 | pubdb | f | t | 31511 | | 684 | 0/56083970 | 0/560839A8
sub2 | pgoutput | logical | 16384 | pubdb | f | t | 31509 | | 684 | 0/56083970 | 0/560839A8
sub1 | pgoutput | logical | 16384 | pubdb | f | t | 32366 | | 684 | 0/56083970 | 0/560839A8
sub_all_table | pgoutput | logical | 16474 | pubdb_all_table | f | t | 31512 | | 684 | 0/56083970 | 0/560839A8
Publicationの情報は接続するデータベースに存在するものしか見えませんが、レプリケーションスロットはデータベースクラスタ内に存在するものが全て表示されます。 なお、slot_type はロジカルレプリケーションスロットでは logical 、ストリーミングレプリケーションスロットは physical と表示されます。
[4] | PostgreSQL 10.0文書 - 51.80. pg_replication_slots |
Subscriberのサーバにおいて、Subscriptionが作成されたデータベースに接続して確認できる情報は以下のとおりです。
■システムカタログ pg_subscription [5]
データベースに存在するSubscriptionの情報を確認できます。 Subscriptionの名前、所有者に加えて、接続先のデータベース、Publicationの名前等がわかります。 なお、pg_publicationでは接続したデータベース内のPublicationのみが見えましたが、pg_subscriptionでは接続したデータベースに関わらず、 全てのSubscriptionが表示されます。
(サーバ2において実施)
subdb=# select * from pg_subscription;
subdbid | subname | subowner | subenabled | subconninfo | subslotname | subsynccommit | subpublications
---------+---------------+----------+------------+--------------------------------------------------------------------------+---------------+---------------+-----------------
16384 | sub2 | 10 | t | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1 | sub2 | off | {pub2}
16384 | sub3 | 10 | t | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1 | sub3 | off | {pub3}
16477 | sub_all_table | 10 | t | host=192.168.127.31 dbname=pubdb_all_table user=repusr1 password=repusr1 | sub_all_table | off | {pub_all_table}
16384 | sub1 | 10 | t | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1 | sub1 | off | {pub1}
[5] | PostgreSQL 10.0文書 - 51.52. pg_subscription |
■システムカタログ pg_subscription_rel [6]
接続中のデータベースに定義されているSubscriptionがレプリケーション対象とするテーブルを確認できます。 また、対象テーブルの他に、レプリケーションのステータス(i = initialize, d = data is being copied, s = synchronized, r = ready (normal replication))やLSNが表示されます。
(サーバ2において実施)
subdb=# select * from pg_subscription_rel;
srsubid | srrelid | srsubstate | srsublsn
---------+---------+------------+------------
16463 | 16455 | r | 0/55FB63D0
16400 | 16395 | r | 0/16D7B60
16476 | 16470 | r | 0/55FDE4A0
16463 | 16490 | r | 0/56068D38
subdb_all_table=# select * from pg_subscription_rel;
srsubid | srrelid | srsubstate | srsublsn
---------+---------+------------+------------
16484 | 16479 | r | 0/5602B818
16484 | 16485 | r | 0/56048B00
ただし、Subscription、テーブルのOIDしか保有しないため、実際に利用するには pg_subscription, pg_class と結合して、それぞれの名称を取得する必要があるでしょう。
[6] | PostgreSQL 10.0文書 - 51.53. pg_subscription_rel |
■psqlのメタコマンド
psqlコマンドのメタコマンド「dRs+」でもSubscriptionの情報を確認できます。 システムカタログpg_subscriptionと同様の情報が見やすく整形されて表示されます。
(サーバ2において実施)
subdb=# \dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Synchronous commit | Conninfo
------+----------+---------+-------------+--------------------+----------------------------------------------------------------
sub1 | postgres | t | {pub1} | off | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1
sub2 | postgres | t | {pub2} | off | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1
sub3 | postgres | t | {pub3} | off | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1
subdb_all_table=# \dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Synchronous commit | Conninfo
---------------+----------+---------+-----------------+--------------------+--------------------------------------------------------------------------
sub_all_table | postgres | t | {pub_all_table} | off | host=192.168.127.31 dbname=pubdb_all_table user=repusr1 password=repusr1
なお、pg_subscriptionではデータベースクラスタ内のSUBSCRIPTIONが全て表示されたのに対し、メタコマンド「dRs+」では接続中のデータベース内のSubscriptionしか表示されません。
Publisherのサーバにおいて確認できる情報は以下のとおりです。
■サーバログ
CREATE PUBLICATION文を実行した時点ではPublisherのサーバログには何も表示されません。 SubscriberでCREATE SUBSCRIPTION文を実行した時点で以下のログが出力されます。
(サーバ1のサーバログ)
2018-02-06 11:24:40.990 JST [20086] LOG: starting logical decoding for slot "sub_all_table"
2018-02-06 11:24:40.990 JST [20086] DETAIL: streaming transactions committing after 0/56084A90, reading WAL from 0/56084A58
■動的統計情報ビュー pg_stat_replication [7]
ストリーミングレプリケーションと同様に、ロジカルレプリケーションの稼働状況を確認できます。 接続中のデータベースに関わらず、データベースクラスタ全体の情報が表示されます。
(サーバ1において実施)
postgres=# \x
postgres=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 20086
usesysid | 16385
usename | repusr1
application_name | sub_all_table
client_addr | 192.168.127.32
client_hostname |
client_port | 46452
backend_start | 2018-02-06 11:24:40.988512+09
backend_xmin |
state | streaming
sent_lsn | 0/58000140
write_lsn | 0/58000140
flush_lsn | 0/58000140
replay_lsn | 0/58000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 2 ]----+------------------------------
pid | 31511
usesysid | 16385
usename | repusr1
application_name | sub3
client_addr | 192.168.127.32
client_hostname |
client_port | 46353
backend_start | 2018-01-17 11:07:07.204636+09
backend_xmin |
state | streaming
sent_lsn | 0/58000140
write_lsn | 0/58000140
flush_lsn | 0/58000140
replay_lsn | 0/58000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 3 ]----+------------------------------
pid | 32366
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46411
backend_start | 2018-01-17 15:01:47.434102+09
backend_xmin |
state | streaming
sent_lsn | 0/58000140
write_lsn | 0/58000140
flush_lsn | 0/58000140
replay_lsn | 0/58000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 4 ]----+------------------------------
pid | 31509
usesysid | 16385
usename | repusr1
application_name | sub2
client_addr | 192.168.127.32
client_hostname |
client_port | 46351
backend_start | 2018-01-17 11:07:07.188398+09
backend_xmin |
state | streaming
sent_lsn | 0/58000140
write_lsn | 0/58000140
flush_lsn | 0/58000140
replay_lsn | 0/58000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 5 ]----+------------------------------
pid | 21625
usesysid | 16385
usename | repusr1
application_name | nk_PGECons3
client_addr | 192.168.127.33
client_hostname |
client_port | 55519
backend_start | 2018-02-06 16:00:40.749454+09
backend_xmin |
state | streaming
sent_lsn | 0/58000140
write_lsn | 0/58000140
flush_lsn | 0/58000140
replay_lsn | 0/58000140
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
ここでは RECORD 1〜4 でサーバ1-2間のロジカルレプリケーション、RECORD 5 でサーバ1-3間のストリーミングレプリケーションの情報が表示されています。ロジカルレプリケーションとストリーミングレプリケーションはほぼ同じ形式で見えますが、application_nameはそれぞれ以下の情報が表示されています。
なお、Subscriptionを停止させると、pg_stat_replicationから該当するレプリケージョンの情報が表示されなくなります。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 DISABLE;
ALTER SUBSCRIPTION
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 20086
usesysid | 16385
usename | repusr1
application_name | sub_all_table
client_addr | 192.168.127.32
client_hostname |
client_port | 46452
backend_start | 2018-02-06 11:24:40.988512+09
backend_xmin |
state | streaming
sent_lsn | 0/580074E0
write_lsn | 0/580074E0
flush_lsn | 0/580074E0
replay_lsn | 0/580074E0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 2 ]----+------------------------------
pid | 31511
usesysid | 16385
usename | repusr1
application_name | sub3
client_addr | 192.168.127.32
client_hostname |
client_port | 46353
backend_start | 2018-01-17 11:07:07.204636+09
backend_xmin |
state | streaming
sent_lsn | 0/580074E0
write_lsn | 0/580074E0
flush_lsn | 0/580074E0
replay_lsn | 0/580074E0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 3 ]----+------------------------------
pid | 31509
usesysid | 16385
usename | repusr1
application_name | sub2
client_addr | 192.168.127.32
client_hostname |
client_port | 46351
backend_start | 2018-01-17 11:07:07.188398+09
backend_xmin |
state | streaming
sent_lsn | 0/580074E0
write_lsn | 0/580074E0
flush_lsn | 0/580074E0
replay_lsn | 0/580074E0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 4 ]----+------------------------------
pid | 21625
usesysid | 16385
usename | repusr1
application_name | nk_PGECons3
client_addr | 192.168.127.33
client_hostname |
client_port | 55519
backend_start | 2018-02-06 16:00:40.749454+09
backend_xmin |
state | streaming
sent_lsn | 0/580074E0
write_lsn | 0/580074E0
flush_lsn | 0/580074E0
replay_lsn | 0/580074E0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
ロジカルレプリケーションもストリーミングレプリケーションも、pg_stat_replicationの件数を定期的に確認することで稼働状況を監視するとよいでしょう。
[7] | PostgreSQL 10.0文書 - 表28.5 pg_stat_replicationビュー |
Subscriberのサーバにおいて確認できる情報は以下のとおりです。
■サーバログ
CREATE SUBSCRIPTION文を実行した時点で以下のログが出力されます。
(サーバ2のサーバログ)
2018-02-06 11:20:29.883 JST [4245] LOG: logical replication apply worker for subscription "sub_all_table" has started
2018-02-06 11:20:29.888 JST [4246] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data1" has started
2018-02-06 11:20:29.899 JST [4247] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data2" has started
2018-02-06 11:20:29.901 JST [4246] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data1" has finished
2018-02-06 11:20:29.912 JST [4247] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data2" has finished
「logical replication table synchronization worker for subscription (Subscription名), table (テーブル名) has finished」と出力されていれば、レプリケーション対象のテーブルに対する初期データの転送が完了しており正常にロジカルレプリケーションが開始したことが確認できます。
■動的統計情報ビュー pg_stat_subscription [8]
Subscription単位にロジカルレプリケーションの稼働状況を確認できます。 接続中のデータベースに関わらず、データベースクラスタ全体の情報が表示されます。
(サーバ2において実施)
subdb_all_table=# select * from pg_stat_subscription;
subid | subname | pid | relid | received_lsn | last_msg_send_time | last_msg_receipt_time | latest_end_lsn | latest_end_time
-------+---------------+-------+-------+--------------+-------------------------------+-------------------------------+----------------+-------------------------------
16400 | sub2 | 31707 | | 0/58000140 | 2018-02-06 16:00:07.649126+09 | 2018-02-06 16:00:07.482495+09 | 0/58000140 | 2018-02-06 16:00:07.649126+09
16476 | sub3 | 31709 | | 0/58000140 | 2018-02-06 16:00:07.649515+09 | 2018-02-06 16:00:07.482694+09 | 0/58000140 | 2018-02-06 16:00:07.649515+09
16463 | sub1 | 32626 | | 0/58000140 | 2018-02-06 16:00:07.649203+09 | 2018-02-06 16:00:07.482386+09 | 0/58000140 | 2018-02-06 16:00:07.649203+09
16508 | sub_all_table | 4267 | | 0/58000140 | 2018-02-06 16:00:07.649259+09 | 2018-02-06 16:00:07.482608+09 | 0/58000140 | 2018-02-06 16:00:07.649259+09
[8] | PostgreSQL 10.0文書 - 表28.7 pg_stat_subscription View |
ロジカルレプリケーションが稼働する環境で障害が発生した場合の挙動を確認します。
本章は以下の環境を利用した検証結果を元に解説します。
ロジカルレプリケーションに関連する以下のプロセスが異常終了した時の挙動を確認します。
■初期状態
Publisherでは logical replication launcher と wal sender のプロセスが起動しています。 また、pg_stat_replicationでロジカルレプリケーションが稼働中であることが確認できます。
(サーバ1において実施)
-bash-4.2$ ps aux | grep postgres
postgres 2179 0.0 0.8 389356 16552 ? S 1月09 0:56 /usr/pgsql-10/bin/postgres
postgres 2180 0.0 0.1 242164 2404 ? Ss 1月09 0:00 postgres: logger process
postgres 2182 0.0 0.3 389496 5716 ? Ss 1月09 0:01 postgres: checkpointer process
postgres 2183 0.0 0.2 389356 4092 ? Ss 1月09 0:20 postgres: writer process
postgres 2184 0.0 0.3 389356 6744 ? Ss 1月09 0:23 postgres: wal writer process
postgres 2185 0.0 0.1 389812 3352 ? Ss 1月09 0:38 postgres: autovacuum launcher process
postgres 2186 0.0 0.1 244420 2620 ? Ss 1月09 1:09 postgres: stats collector process
postgres 2187 0.0 0.1 389648 3060 ? Ss 1月09 0:01 postgres: bgworker: logical replication launcher
postgres 21882 0.0 0.3 392540 5780 ? Ss 2月06 0:00 postgres: wal sender process repusr1 192.168.127.32(46474) idle
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 21882
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46474
backend_start | 2018-02-06 16:40:08.541313+09
backend_xmin |
state | streaming
sent_lsn | 0/5800DBB0
write_lsn | 0/5800DBB0
flush_lsn | 0/5800DBB0
replay_lsn | 0/5800DBB0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
また、Subscriberでは logical replication launcher と logical replication worker のプロセスが起動しており、 pg_stat_subscriptionからもロジカルレプリケーションが稼働中であることが確認できます。
(サーバ2において実施)
-bash-4.2$ ps aux | grep postgres
postgres 5405 0.0 0.3 397072 6264 ? Ss 2月06 0:02 postgres: bgworker: logical replication worker for subscription 16463
postgres 31698 0.0 0.8 389296 16496 ? S 1月17 0:47 /usr/pgsql-10/bin/postgres
postgres 31699 0.0 0.1 242108 2008 ? Ss 1月17 0:00 postgres: logger process
postgres 31701 0.0 0.2 389448 4724 ? Ss 1月17 0:00 postgres: checkpointer process
postgres 31702 0.0 0.1 389296 3436 ? Ss 1月17 0:15 postgres: writer process
postgres 31703 0.0 0.3 389296 6332 ? Ss 1月17 0:18 postgres: wal writer process
postgres 31704 0.0 0.1 389752 3132 ? Ss 1月17 0:32 postgres: autovacuum launcher process
postgres 31705 0.0 0.1 244360 2264 ? Ss 1月17 1:07 postgres: stats collector process
postgres 31706 0.0 0.1 389588 2760 ? Ss 1月17 0:01 postgres: bgworker: logical replication launcher
subdb=# select * from pg_stat_subscription;
-[ RECORD 1 ]---------+------------------------------
subid | 16463
subname | sub1
pid | 5405
relid |
received_lsn | 0/5800DBB0
last_msg_send_time | 2018-02-07 13:45:31.222443+09
last_msg_receipt_time | 2018-02-07 13:45:31.05565+09
latest_end_lsn | 0/5800DBB0
latest_end_time | 2018-02-07 13:45:31.222443+09
■logical replication launcherの停止
Publisherのlogical replication launcher をKillコマンドで強制終了すると再度別のプロセスIDで起動されていることがわかります。
(サーバ1において実施)
-bash-4.2$ kill -9 2187
-bash-4.2$ ps aux | grep postgres
postgres 2179 0.0 0.8 389356 16552 ? S 1月09 0:56 /usr/pgsql-10/bin/postgres
postgres 2180 0.0 0.1 242168 2404 ? Ss 1月09 0:00 postgres: logger process
postgres 28990 0.0 0.1 389356 2152 ? Ss 13:56 0:00 postgres: checkpointer process
postgres 28991 0.0 0.1 389356 2156 ? Ss 13:56 0:00 postgres: writer process
postgres 28992 0.0 0.1 389356 2116 ? Ss 13:56 0:00 postgres: wal writer process
postgres 28993 0.0 0.1 389784 3004 ? Ss 13:56 0:00 postgres: autovacuum launcher process
postgres 28994 0.0 0.1 244288 1988 ? Ss 13:56 0:00 postgres: stats collector process
postgres 28995 0.0 0.1 389656 2520 ? Ss 13:56 0:00 postgres: bgworker: logical replication launcher
この時Publisher、Subscriberのログにはそれぞれ以下のメッセージが出力されています。
(サーバ1のサーバログ)
2018-02-07 13:56:05.609 JST [2179] LOG: worker process: logical replication launcher (PID 2187) was terminated by signal 9: Killed
2018-02-07 13:56:05.609 JST [2179] LOG: terminating any other active server processes
2018-02-07 13:56:05.609 JST [21882] WARNING: terminating connection because of crash of another server process
2018-02-07 13:56:05.609 JST [21882] DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
2018-02-07 13:56:05.609 JST [21882] HINT: In a moment you should be able to reconnect to the database and repeat your command.
2018-02-07 13:56:05.614 JST [2185] WARNING: terminating connection because of crash of another server process
2018-02-07 13:56:05.614 JST [2185] DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
2018-02-07 13:56:05.614 JST [2185] HINT: In a moment you should be able to reconnect to the database and repeat your command.
2018-02-07 13:56:05.620 JST [28988] FATAL: the database system is in recovery mode
2018-02-07 13:56:05.621 JST [2179] LOG: all server processes terminated; reinitializing
2018-02-07 13:56:05.652 JST [28989] LOG: database system was interrupted; last known up at 2018-02-07 13:45:23 JST
2018-02-07 13:56:05.692 JST [28989] LOG: database system was not properly shut down; automatic recovery in progress
2018-02-07 13:56:05.693 JST [28989] LOG: redo starts at 0/5800DAD0
2018-02-07 13:56:05.693 JST [28989] LOG: invalid record length at 0/5800DBB0: wanted 24, got 0
2018-02-07 13:56:05.693 JST [28989] LOG: redo done at 0/5800DB78
2018-02-07 13:56:05.698 JST [2179] LOG: database system is ready to accept connections
2018-02-07 13:56:10.640 JST [28998] LOG: starting logical decoding for slot "sub1"
2018-02-07 13:56:10.640 JST [28998] DETAIL: streaming transactions committing after 0/5800DB78, reading WAL from 0/5800DB78
2018-02-07 13:56:10.640 JST [28998] LOG: logical decoding found consistent point at 0/5800DB78
2018-02-07 13:56:10.640 JST [28998] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
WARNING: terminating connection because of crash of another server process
DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
HINT: In a moment you should be able to reconnect to the database and repeat your command.
2018-02-07 13:56:05.444 JST [5405] ERROR: could not receive data from WAL stream: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
2018-02-07 13:56:05.446 JST [31698] LOG: worker process: logical replication worker for subscription 16463 (PID 5405) exited with exit code 1
2018-02-07 13:56:05.450 JST [9938] LOG: logical replication apply worker for subscription "sub1" has started
2018-02-07 13:56:05.453 JST [9938] ERROR: could not connect to the publisher: FATAL: the database system is in recovery mode
2018-02-07 13:56:05.454 JST [31698] LOG: worker process: logical replication worker for subscription 16463 (PID 9938) exited with exit code 1
2018-02-07 13:56:10.463 JST [9939] LOG: logical replication apply worker for subscription "sub1" has started
上記より、Publisherのlogical replication launcherが停止した場合、Publisherのインスタンス再起動、リカバリ処理が行われた後にレプリケーションが再開されることがわかりました。
同様に、Subscriberのlogical replication launcher をKillコマンドで強制終了した場合も、再度別のプロセスIDで起動されていることがわかります。また、この時Subscriberのインスタンス再起動、リカバリ処理が行われてからレプリケーションが再開されています。
(サーバ2において実施)
-bash-4.2$ kill -9 31706
-bash-4.2$ ps aux | grep postgres
postgres 9947 0.0 0.1 389296 2124 ? Ss 13:57 0:00 postgres: checkpointer process
postgres 9948 0.0 0.1 389296 2128 ? Rs 13:57 0:00 postgres: writer process
postgres 9949 0.0 0.1 389296 2108 ? Rs 13:57 0:00 postgres: wal writer process
postgres 9950 0.0 0.1 389616 2980 ? Ss 13:57 0:00 postgres: autovacuum launcher process
postgres 9951 0.0 0.1 244228 2008 ? Ss 13:57 0:00 postgres: stats collector process
postgres 9952 0.0 0.1 389588 2760 ? Ss 13:57 0:00 postgres: bgworker: logical replication launcher
postgres 9953 0.0 0.3 397084 6240 ? Ss 13:57 0:00 postgres: bgworker: logical replication worker for subscription 16463
postgres 31698 0.0 0.8 389296 16500 ? S 1月17 0:47 /usr/pgsql-10/bin/postgres
postgres 31699 0.0 0.1 242108 2008 ? Ss 1月17 0:00 postgres: logger process
(サーバ1のサーバログ)
2018-02-07 13:57:43.391 JST [28998] LOG: unexpected EOF on standby connection
2018-02-07 13:57:43.498 JST [29004] LOG: starting logical decoding for slot "sub1"
2018-02-07 13:57:43.498 JST [29004] DETAIL: streaming transactions committing after 0/5800DC58, reading WAL from 0/5800DC20
2018-02-07 13:57:43.498 JST [29004] LOG: logical decoding found consistent point at 0/5800DC20
2018-02-07 13:57:43.498 JST [29004] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-02-07 13:57:43.216 JST [31698] LOG: worker process: logical replication launcher (PID 31706) was terminated by signal 9: Killed
2018-02-07 13:57:43.216 JST [31698] LOG: terminating any other active server processes
2018-02-07 13:57:43.218 JST [31704] WARNING: terminating connection because of crash of another server process
2018-02-07 13:57:43.218 JST [31704] DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
2018-02-07 13:57:43.218 JST [31704] HINT: In a moment you should be able to reconnect to the database and repeat your command.
2018-02-07 13:57:43.226 JST [31698] LOG: all server processes terminated; reinitializing
2018-02-07 13:57:43.250 JST [9946] LOG: database system was interrupted; last known up at 2018-02-07 13:46:06 JST
2018-02-07 13:57:43.309 JST [9946] LOG: recovered replication state of node 1 to 0/5800DA98
2018-02-07 13:57:43.309 JST [9946] LOG: database system was not properly shut down; automatic recovery in progress
2018-02-07 13:57:43.310 JST [9946] LOG: redo starts at 0/73706380
2018-02-07 13:57:43.310 JST [9946] LOG: invalid record length at 0/73706460: wanted 24, got 0
2018-02-07 13:57:43.310 JST [9946] LOG: redo done at 0/73706428
2018-02-07 13:57:43.315 JST [31698] LOG: database system is ready to accept connections
2018-02-07 13:57:43.327 JST [9953] LOG: logical replication apply worker for subscription "sub1" has started
■wal senderの停止
Publisherのwal senderをKillコマンドで強制終了します。
(サーバ1において実施)
-bash-4.2$ kill -9 29004
-bash-4.2$ ps aux | grep postgres
postgres 2179 0.0 0.8 389356 16552 ? S 1月09 0:56 /usr/pgsql-10/bin/postgres
postgres 2180 0.0 0.1 242168 2404 ? Ss 1月09 0:00 postgres: logger process
postgres 29085 0.0 0.1 389356 2156 ? Ss 14:12 0:00 postgres: checkpointer process
postgres 29086 0.0 0.1 389356 2160 ? Ss 14:12 0:00 postgres: writer process
postgres 29087 0.0 0.1 389356 2120 ? Ss 14:12 0:00 postgres: wal writer process
postgres 29088 0.0 0.1 389784 3004 ? Ss 14:12 0:00 postgres: autovacuum launcher process
postgres 29089 0.0 0.1 244288 1992 ? Ss 14:12 0:00 postgres: stats collector process
postgres 29090 0.0 0.1 389656 2520 ? Ss 14:12 0:00 postgres: bgworker: logical replication launcher
postgres 29093 0.0 0.3 392476 5952 ? Ss 14:12 0:00 postgres: wal sender process repusr1 192.168.127.32(46482) idle
(サーバ1のサーバログ)
2018-02-07 14:12:19.446 JST [2179] LOG: server process (PID 29004) was terminated by signal 9: Killed
2018-02-07 14:12:19.447 JST [2179] LOG: terminating any other active server processes
2018-02-07 14:12:19.450 JST [28993] WARNING: terminating connection because of crash of another server process
2018-02-07 14:12:19.450 JST [28993] DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
2018-02-07 14:12:19.450 JST [28993] HINT: In a moment you should be able to reconnect to the database and repeat your command.
2018-02-07 14:12:19.456 JST [2179] LOG: all server processes terminated; reinitializing
2018-02-07 14:12:19.485 JST [29083] LOG: database system was interrupted; last known up at 2018-02-07 14:11:06 JST
2018-02-07 14:12:19.511 JST [29084] FATAL: the database system is in recovery mode
2018-02-07 14:12:19.519 JST [29083] LOG: database system was not properly shut down; automatic recovery in progress
2018-02-07 14:12:19.519 JST [29083] LOG: redo starts at 0/5800DEE8
2018-02-07 14:12:19.519 JST [29083] LOG: invalid record length at 0/5800DFC8: wanted 24, got 0
2018-02-07 14:12:19.519 JST [29083] LOG: redo done at 0/5800DF90
2018-02-07 14:12:19.524 JST [2179] LOG: database system is ready to accept connections
2018-02-07 14:12:24.529 JST [29093] LOG: starting logical decoding for slot "sub1"
2018-02-07 14:12:24.529 JST [29093] DETAIL: streaming transactions committing after 0/5800DF90, reading WAL from 0/5800DF90
2018-02-07 14:12:24.529 JST [29093] LOG: logical decoding found consistent point at 0/5800DF90
2018-02-07 14:12:24.529 JST [29093] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-02-07 14:12:19.280 JST [9953] ERROR: could not receive data from WAL stream: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
2018-02-07 14:12:19.281 JST [31698] LOG: worker process: logical replication worker for subscription 16463 (PID 9953) exited with exit code 1
2018-02-07 14:12:19.285 JST [9998] LOG: logical replication apply worker for subscription "sub1" has started
2018-02-07 14:12:19.344 JST [9998] ERROR: could not connect to the publisher: FATAL: the database system is in recovery mode
2018-02-07 14:12:19.345 JST [31698] LOG: worker process: logical replication worker for subscription 16463 (PID 9998) exited with exit code 1
2018-02-07 14:12:24.354 JST [9999] LOG: logical replication apply worker for subscription "sub1" has started
上記よりPublisherのwal senderが停止した場合も、Publisherインスタンスの再起動、リカバリ処理が行われた後にレプリケーションが再開されることがわかりました。
■logical replication workerの停止
Subscriberのlogical replication workerをKillコマンドで強制終了します。
(サーバ2において実施)
-bash-4.2$ kill -9 9999
-bash-4.2$ ps aux | grep postgres
postgres 10013 0.0 0.1 389296 2124 ? Ss 14:16 0:00 postgres: checkpointer process
postgres 10014 0.0 0.1 389296 2128 ? Ss 14:16 0:00 postgres: writer process
postgres 10015 0.0 0.1 389296 2104 ? Ss 14:16 0:00 postgres: wal writer process
postgres 10016 0.0 0.1 389616 2980 ? Ss 14:16 0:00 postgres: autovacuum launcher process
postgres 10017 0.0 0.1 244228 1968 ? Ss 14:16 0:00 postgres: stats collector process
postgres 10018 0.0 0.1 389588 2760 ? Ss 14:16 0:00 postgres: bgworker: logical replication launcher
postgres 10019 0.0 0.3 397052 6340 ? Ss 14:16 0:00 postgres: bgworker: logical replication worker for subscription 16463
postgres 31698 0.0 0.8 389296 16500 ? S 1月17 0:47 /usr/pgsql-10/bin/postgres
postgres 31699 0.0 0.1 242108 2008 ? Ss 1月17 0:00 postgres: logger process
(サーバ1のサーバログ)
2018-02-07 14:16:13.087 JST [29093] LOG: unexpected EOF on standby connection
2018-02-07 14:16:13.161 JST [29139] LOG: starting logical decoding for slot "sub1"
2018-02-07 14:16:13.161 JST [29139] DETAIL: streaming transactions committing after 0/5800E360, reading WAL from 0/5800E328
2018-02-07 14:16:13.161 JST [29139] LOG: logical decoding found consistent point at 0/5800E328
2018-02-07 14:16:13.161 JST [29139] DETAIL: Logical decoding will begin using saved snapshot.
(サーバ2のサーバログ)
2018-02-07 14:16:12.920 JST [31698] LOG: worker process: logical replication worker for subscription 16463 (PID 9999) was terminated by signal 9: Killed
2018-02-07 14:16:12.920 JST [31698] LOG: terminating any other active server processes
2018-02-07 14:16:12.924 JST [9950] WARNING: terminating connection because of crash of another server process
2018-02-07 14:16:12.924 JST [9950] DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
2018-02-07 14:16:12.924 JST [9950] HINT: In a moment you should be able to reconnect to the database and repeat your command.
2018-02-07 14:16:12.930 JST [31698] LOG: all server processes terminated; reinitializing
2018-02-07 14:16:12.957 JST [10012] LOG: database system was interrupted; last known up at 2018-02-07 14:12:43 JST
2018-02-07 14:16:12.973 JST [10012] LOG: recovered replication state of node 1 to 0/5800DEB0
2018-02-07 14:16:12.973 JST [10012] LOG: database system was not properly shut down; automatic recovery in progress
2018-02-07 14:16:12.974 JST [10012] LOG: redo starts at 0/73706710
2018-02-07 14:16:12.974 JST [10012] LOG: invalid record length at 0/73706A30: wanted 24, got 0
2018-02-07 14:16:12.974 JST [10012] LOG: redo done at 0/737069F8
2018-02-07 14:16:12.974 JST [10012] LOG: last completed transaction was at log time 2018-02-07 14:15:21.893165+09
2018-02-07 14:16:12.980 JST [31698] LOG: database system is ready to accept connections
2018-02-07 14:16:12.990 JST [10019] LOG: logical replication apply worker for subscription "sub1" has started
上記より、Subscriberのlogical replication launcherが停止した場合もSubscriberインスタンスの再起動、リカバリ処理が行われた後にレプリケーションが再開されることがわかりました。
ロジカルレプリケーションのPublisherが停止した時の挙動を確認します。
■初期状態
Subscriberのpg_stat_subscriptionよりレプリケーションが稼働中であることを確認しました。
(サーバ2において実施)
subdb=# select * from pg_stat_subscription;
-[ RECORD 1 ]---------+------------------------------
subid | 16463
subname | sub1
pid | 10084
relid |
received_lsn | 0/5800ECB8
last_msg_send_time | 2018-02-07 14:37:30.310381+09
last_msg_receipt_time | 2018-02-07 14:37:30.143584+09
latest_end_lsn | 0/5800ECB8
latest_end_time | 2018-02-07 14:37:30.310381+09
■Publisherのノードを停止
Publisherで起動中のPostgreSQLサーバを強制終了します。
(サーバ1において実施)
-bash-4.2$ pg_ctl -m f stop
サーバ停止処理の完了を待っています....完了
サーバは停止しました
次にSubscriberのpg_stat_subscriptionを確認すると、レコードは消えないもののpid, LSN等の情報が表示されなくなりました。
(サーバ2において実施)
subdb=# select * from pg_stat_subscription;
-[ RECORD 1 ]---------+------
subid | 16463
subname | sub1
pid |
relid |
received_lsn |
last_msg_send_time |
last_msg_receipt_time |
latest_end_lsn |
latest_end_time |
また、この時のSubscriberのサーバログを確認すると、5秒毎に接続を試行してlogical replication workerプロセスがエラー終了する事象が繰り返されていました。
(サーバ2のサーバログ)
2018-02-07 14:55:44.067 JST [10084] LOG: data stream from publisher has ended
2018-02-07 14:55:44.067 JST [10084] ERROR: could not send end-of-streaming message to primary: no COPY in progress
2018-02-07 14:55:44.069 JST [10075] LOG: worker process: logical replication worker for subscription 16463 (PID 10084) exited with exit code 1
2018-02-07 14:55:44.072 JST [10118] LOG: logical replication apply worker for subscription "sub1" has started
2018-02-07 14:55:44.073 JST [10118] ERROR: could not connect to the publisher: FATAL: the database system is shutting down
2018-02-07 14:55:44.074 JST [10075] LOG: worker process: logical replication worker for subscription 16463 (PID 10118) exited with exit code 1
2018-02-07 14:55:49.083 JST [10119] LOG: logical replication apply worker for subscription "sub1" has started
2018-02-07 14:55:49.084 JST [10119] ERROR: could not connect to the publisher: could not connect to server: Connection refused
Is the server running on host "192.168.127.31" and accepting
TCP/IP connections on port 5432?
2018-02-07 14:55:49.085 JST [10075] LOG: worker process: logical replication worker for subscription 16463 (PID 10119) exited with exit code 1
この状態から、PublisherのPostgreSQLサーバを再度起動します。
(サーバ1において実施)
-bash-4.2$ pg_ctl start
サーバの起動完了を待っています....2018-02-07 14:58:44.785 JST [29300] LOG: listening on IPv4 address "0.0.0.0", port 5432
2018-02-07 14:58:44.785 JST [29300] LOG: listening on IPv6 address "::", port 5432
2018-02-07 14:58:44.785 JST [29300] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2018-02-07 14:58:44.790 JST [29300] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
2018-02-07 14:58:44.807 JST [29300] LOG: redirecting log output to logging collector process
2018-02-07 14:58:44.807 JST [29300] HINT: Future log output will appear in directory "log".
完了
サーバ起動完了
この時、Subscriberのpg_stat_subscriptionを見ると初期状態の様にレプリケーションが稼働中であることが確認できました。
(サーバ2において実施)
subdb=# select * from pg_stat_subscription;
-[ RECORD 1 ]---------+------------------------------
subid | 16463
subname | sub1
pid | 10161
relid |
received_lsn | 0/5800ED60
last_msg_send_time | 2018-02-07 14:58:59.837546+09
last_msg_receipt_time | 2018-02-07 14:58:59.670736+09
latest_end_lsn | 0/5800ED60
latest_end_time | 2018-02-07 14:58:59.837546+09
この時Publisher、Subscriberのログにはそれぞれ以下のメッセージが出力されています。
(サーバ2のサーバログ)
2018-02-07 14:58:49.397 JST [10161] LOG: logical replication apply worker for subscription "sub1" has started
(サーバ1のサーバログ)
2018-02-07 14:55:44.215 JST [2179] LOG: received fast shutdown request
2018-02-07 14:55:44.226 JST [2179] LOG: aborting any active transactions
2018-02-07 14:55:44.232 JST [2179] LOG: worker process: logical replication launcher (PID 29090) exited with exit code 1
2018-02-07 14:55:44.232 JST [29085] LOG: shutting down
2018-02-07 14:55:44.240 JST [29290] FATAL: the database system is shutting down
2018-02-07 14:55:44.252 JST [2179] LOG: database system is shut down
2018-02-07 14:58:44.809 JST [29302] LOG: database system was shut down at 2018-02-07 14:55:44 JST
2018-02-07 14:58:44.813 JST [29300] LOG: database system is ready to accept connections
2018-02-07 14:58:49.572 JST [29310] LOG: starting logical decoding for slot "sub1"
2018-02-07 14:58:49.572 JST [29310] DETAIL: streaming transactions committing after 0/5800EC80, reading WAL from 0/5800EC80
2018-02-07 14:58:49.572 JST [29310] LOG: logical decoding found consistent point at 0/5800EC80
2018-02-07 14:58:49.572 JST [29310] DETAIL: There are no running transactions.
この結果より、Publisherが停止した場合はレプリケーションが停止するが、再度起動すればレプリケーションも再開されることがわかりました。
ロジカルレプリケーションのSubscriberが停止した時の挙動を確認します。
■初期状態
Publisherのpg_stat_replicationよりレプリケーションが稼働中であることを確認しました。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 29213
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46484
backend_start | 2018-02-07 14:33:55.842956+09
backend_xmin |
state | catchup
sent_lsn | 0/5800EBD8
write_lsn | 0/5800EBD8
flush_lsn | 0/5800EBD8
replay_lsn | 0/5800EBD8
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
■Subscriberのノードを停止
Subscriberで起動中のPostgreSQLサーバを強制終了します。
(サーバ2において実施)
-bash-4.2$ pg_ctl -m f stop
サーバ停止処理の完了を待っています....完了
サーバは停止しました
次にPublisherのpg_stat_replicationを確認すると、レプリケーションの情報が表示されなくなりました。 また、この時Publisherのサーバログには何も出力されていませんでした。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
(0 rows)
なお、この状態のままPublisherで1件データをINSERTしても特に問題なく処理は完了します。
(サーバ1において実施)
pubdb=# insert into data1 values (6, 'pub6');
INSERT 0 1
この状態から、SubscriberのPostgreSQLサーバを再度起動します。
(サーバ2において実施)
-bash-4.2$ pg_ctl start
サーバの起動完了を待っています....2018-02-07 14:33:55.641 JST [10075] LOG: listening on IPv4 address "0.0.0.0", port 5432
2018-02-07 14:33:55.641 JST [10075] LOG: listening on IPv6 address "::", port 5432
2018-02-07 14:33:55.642 JST [10075] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2018-02-07 14:33:55.642 JST [10075] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
2018-02-07 14:33:55.656 JST [10075] LOG: redirecting log output to logging collector process
2018-02-07 14:33:55.656 JST [10075] HINT: Future log output will appear in directory "log".
完了
サーバ起動完了
ここで、Publisher側のpg_stat_replicationを見ると初期状態の様にレプリケーションが復旧していることが確認できました。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 29213
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46484
backend_start | 2018-02-07 14:33:55.842956+09
backend_xmin |
state | catchup
sent_lsn | 0/5800EBD8
write_lsn | 0/5800EBD8
flush_lsn | 0/5800EBD8
replay_lsn | 0/5800EBD8
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
また、PublisherでINSERTしたレコードがSubscriberのテーブルにも転送されています。
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2 | c3
----+------+----
1 | pub1 |
2 | pub2 |
3 | pub3 |
4 | pub4 |
5 | pub5 |
6 | pub6 |
この時Publisher、Subscriberのログにはそれぞれ以下のメッセージが出力されています。
(サーバ1のサーバログ)
2018-02-07 14:33:55.845 JST [29213] LOG: starting logical decoding for slot "sub1"
2018-02-07 14:33:55.845 JST [29213] DETAIL: streaming transactions committing after 0/5800E858, reading WAL from 0/5800E820
2018-02-07 14:33:55.845 JST [29213] LOG: logical decoding found consistent point at 0/5800E820
2018-02-07 14:33:55.845 JST [29213] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-02-07 14:33:55.658 JST [10077] LOG: database system was shut down at 2018-02-07 14:29:59 JST
2018-02-07 14:33:55.659 JST [10077] LOG: recovered replication state of node 1 to 0/5800E740
2018-02-07 14:33:55.662 JST [10075] LOG: database system is ready to accept connections
2018-02-07 14:33:55.674 JST [10084] LOG: logical replication apply worker for subscription "sub1" has started
この結果より、Subscriberが停止した場合はレプリケーションが停止するが、再度起動すればレプリケーションも再開されることがわかりました。
ロジカルレプリケーションで利用するネットワークが断絶した時の挙動を確認します。
■初期状態
Publisherのpg_stat_replicationよりレプリケーションが稼働中であることを確認しました。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+-----------------------------
pid | 29310
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46522
backend_start | 2018-02-07 14:58:49.56556+09
backend_xmin |
state | streaming
sent_lsn | 0/5800EE40
write_lsn | 0/5800EE40
flush_lsn | 0/5800EE40
replay_lsn | 0/5800EE40
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
■ネットワークの断絶
Publisherでレプリケーションに使用するネットワークを停止します。 ここではOS(CentOS7.2)のNetworkManagerを利用して対象インタフェース(ens192)を停止しました。
(サーバ1において実施)
[root@nk_PGECons_1 ~]# nmcli connection down ens192
Connection 'ens192' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/0)
[root@nk_PGECons_1 ~]# nmcli connection show
名前 UUID タイプ デバイス
ens192 fa64b28e-a7db-45f0-b7b1-0b2b298b20d2 802-3-ethernet --
ens160 c0791a7a-43be-4254-847d-2b793b583bba 802-3-ethernet ens160
しばらくの間はPublisherのpg_stat_replicationが初期状態のままですが、 1分ほど経過するとレプリケーションの情報が表示されなくなりました。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
(0 rows)
この時のPublisherのログには以下のメッセージが出力されました。ここではwal_sender_timeoutパラメータをデフォルトの60秒で設定していたため、 タイムアウトを検知して非活動のレプリケーション接続を停止しています。
(サーバ1のサーバログ)
2018-02-07 15:41:12.504 JST [29310] LOG: terminating walsender process due to replication timeout
■ネットワークの復旧
Publisherで停止したネットワークのインタフェースを復旧します。
(サーバ1において実施)
[root@nk_PGECons_1 ~]# nmcli connection up ens192
接続が正常にアクティベートされました (D-Bus アクティブパス: /org/freedesktop/NetworkManager/ActiveConnection/2)
[root@nk_PGECons_1 ~]# nmcli connection show
名前 UUID タイプ デバイス
ens192 fa64b28e-a7db-45f0-b7b1-0b2b298b20d2 802-3-ethernet ens192
ens160 c0791a7a-43be-4254-847d-2b793b583bba 802-3-ethernet ens160
Publisher側のpg_stat_replicationを確認すると、レプリケーションの情報が再び表示されるようになりました。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 29665
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46523
backend_start | 2018-02-07 15:44:40.511419+09
backend_xmin |
state | catchup
sent_lsn | 0/5800F300
write_lsn | 0/5800F300
flush_lsn | 0/5800F300
replay_lsn | 0/5800F300
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
Publisherのログには以下のメッセージが出力され、レプリケーションが再開されたことがわかります。
(サーバ1のサーバログ)
2018-02-07 15:44:40.514 JST [29665] LOG: starting logical decoding for slot "sub1"
2018-02-07 15:44:40.514 JST [29665] DETAIL: streaming transactions committing after 0/5800EE40, reading WAL from 0/5800EE08
2018-02-07 15:44:40.514 JST [29665] LOG: logical decoding found consistent point at 0/5800EE08
2018-02-07 15:44:40.514 JST [29665] DETAIL: There are no running transactions.
また、Subscriberのログは以下のメッセージが出力されており、ネットワークの復旧に伴って logical replication worker が再起動したことがわかります。
(サーバ2のサーバログ)
2018-02-07 15:44:40.338 JST [10161] ERROR: could not receive data from WAL stream: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
2018-02-07 15:44:40.339 JST [10075] LOG: worker process: logical replication worker for subscription 16463 (PID 10161) exited with exit code 1
2018-02-07 15:44:40.343 JST [10247] LOG: logical replication apply worker for subscription "sub1" has started
これらの結果より、ネットワークの断絶により一時的にレプリケーションが停止した場合でも、ネットワークが復旧すればレプリケーションも自動的に再開されることがわかりました。
ロジカルレプリケーションが稼働している環境で新たに作成したテーブルをレプリケーション対象として追加したり、テーブル定義を変更する手順を確認します。
本章は以下の環境を利用した検証結果を元に解説します。
データベース単位のPublicationが作成されたデータベースに対して、テーブルを追加する手順を説明します。
■初期状態
Publisherのデータベースpubdb_all_tableに接続し、データベース単位のPublication pub_all_tableを作成します。
(サーバ1において実施)
pubdb_all_table=# CREATE PUBLICATION pub_all_table FOR ALL TABLES;
CREATE PUBLICATION
pubdb_all_table=# \dRp+
Publication pub_all_table
Owner | All tables | Inserts | Updates | Deletes
----------+------------+---------+---------+---------
postgres | t | t | t | t
Pulisherのデータベースpubdb_all_tableにテーブルdata1を作成します。システムカタログpg_publication_tablesでテーブルdata1がPublication pub_all_tableのレプリケーション対象として認識されたことが確認できます。
(サーバ1において実施)
pubdb_all_table=# CREATE TABLE data1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
pubdb_all_table=# SELECT * FROM pg_publication_tables;
pubname | schemaname | tablename
---------------+------------+-----------
pub_all_table | public | data1
Subscriberのデータベースsubdb_all_tableに接続し、Subscriptionを作成します。なお、Subscription作成時点で接続先のPublicationの対象テーブルが存在しないとエラーになるため、あらかじめSubscriberのデータベースに同一名称のテーブルを作成しておいてください。
(サーバ2において実施)
subdb_all_table=# CREATE SUBSCRIPTION sub_all_table CONNECTION 'host=192.168.127.31 dbname=pubdb_all_table user=repusr1 password=repusr1' PUBLICATION pub_all_table;
ERROR: relation "public.data1" does not exist ★data1テーブルが存在しないとエラーとなる
subdb_all_table=# CREATE TABLE data1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
subdb_all_table=# CREATE SUBSCRIPTION sub_all_table CONNECTION 'host=192.168.127.31 dbname=pubdb_all_table user=repusr1 password=repusr1' PUBLICATION pub_all_table;
NOTICE: created replication slot "sub_all_table" on publisher
CREATE SUBSCRIPTION
Subscriptionを作成するとSubscriberのログに初期データ転送のメッセージが出力され、ロジカルレプリケーションが開始されたことが確認できます。
(サーバ2のサーバログ)
2018-01-17 11:09:23.050 JST [31749] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data1" has started
2018-01-17 11:09:23.062 JST [31749] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data1" has finished
■テーブルの追加手順
初期状態から、Pulisherのデータベースpubdb_all_tableにテーブルdata2を作成します。FOR ALL TABLES句を付けてCRATEしたPublicationのため、作成したテーブルdata2が自動的にレプリケーション対象として認識されていることがわかります。
(サーバ1において実施)
pubdb_all_table=# CREATE TABLE data2 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
pubdb_all_table=# SELECT * FROM pg_publication_tables;
pubname | schemaname | tablename
---------------+------------+-----------
pub_all_table | public | data1
pub_all_table | public | data2
次に、Subscriberのデータベースsubdb_all_tableにもテーブルdata2を作成します。この時点ではSubscription sub_all_tableにテーブルdata2は認識されておらず、テーブルdata2のレプリケーションが開始されていないことがわかります。
(サーバ2において実施)
subdb_all_table=# CREATE TABLE data2 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
subdb_all_table=# select a3.subname, a2.relname, a1.srsubstate, a1.srsublsn from pg_subscription_rel as a1 left outer join pg_class as a2 on a1.srrelid = a2.oid left outer join pg_stat_subscription as a3 on a1.srsubid = a3.subid;
subname | relname | srsubstate | srsublsn
---------------+---------+------------+------------
sub_all_table | data1 | r | 0/5602B818
既存のSubscriptionに新たなレプリケーション対象テーブルを認識させるために、ALTER SUBSCRIPTION文を実行します。再びpg_subscription_relを確認するとテーブルdata2が新たに認識されていることが確認できます。
(サーバ2において実施)
subdb_all_table=# ALTER SUBSCRIPTION sub_all_table REFRESH PUBLICATION;
ALTER SUBSCRIPTION
subdb_all_table=# select a3.subname, a2.relname, a1.srsubstate, a1.srsublsn from pg_subscription_rel as a1 left outer join pg_class as a2 on a1.srrelid = a2.oid left outer join pg_stat_subscription as a3 on a1.srsubid = a3.subid;
subname | relname | srsubstate | srsublsn
---------------+---------+------------+------------
sub_all_table | data1 | r | 0/5602B818
sub_all_table | data2 | d |
テーブル単位のPublicationが作成されたデータベースに対して、テーブルを追加する手順を説明します。
■初期状態
Publisherのデータベースpubdbにテーブルdata1をCREATEした後、レプリケーション対象とするPublication pub1を作成します。
(サーバ1において実施)
pubdb=# CREATE TABLE data1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
pubdb=> CREATE PUBLICATION pub1 FOR TABLE data1;
CREATE PUBLICATION
pubdb=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes
---------+------------+---------+---------+---------
pubusr1 | f | t | t | t
Tables:
"public.data1"
SubscriberのデータベースsubdbにSubscriptionを作成し、レプリケーションを開始します。
(サーバ2において実施)
subdb=# CREATE TABLE data1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
subdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub1;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
■テーブルの追加手順
初期状態から、Pulisherのデータベースpubdbにテーブルdata1_1を作成します。FOR ALL TABLES句を付けずにCRATEしたPublicationのため、作成したテーブルdata1_1がレプリケーション対象と認識されていないことがわかります。
(サーバ1において実施)
pubdb=# CREATE TABLE data1_1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
pubdb=# SELECT * FROM pg_publication_tables;
pubname | schemaname | tablename
----------+------------+-----------
pub1 | public | data1
テーブルdata1_1を既存のPublication pub1にレプリケーション対象として追加するには、ALTER PUBLICATION文を実行します。
(サーバ1において実施)
pubdb=# ALTER PUBLICATION pub1 ADD TABLE data1_1;
ALTER PUBLICATION
pubdb=# SELECT * FROM pg_publication_tables;
pubname | schemaname | tablename
---------+------------+-----------
pub1 | public | data1
pub1 | public | data1_1
次に、Subscriberのデータベースsubdbにテーブルdata1_1を作成します。この時点ではSubscription sub1にテーブルdata1_1は認識されません。
(サーバ2において実施)
subdb=# CREATE TABLE data1_1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
subdb=# select a3.subname, a2.relname, a1.srsubstate, a1.srsublsn from pg_subscription_rel as a1 left outer join pg_class as a2 on a1.srrelid = a2.oid left outer join pg_stat_subscription as a3 on a1.srsubid = a3.subid;
subname | relname | srsubstate | srsublsn
---------+---------+------------+------------
sub1 | data1 | r | 0/55FB63D0
既存のSubscriptionに新たなレプリケーション対象テーブルを認識させるために、ALTER SUBSCRIPTION文を実行します。pg_subscription_relを確認するとテーブルdata1_1が新たに認識されていることが確認できます。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION;
ALTER SUBSCRIPTION
subdb=# select a3.subname, a2.relname, a1.srsubstate, a1.srsublsn from pg_subscription_rel as a1 left outer join pg_class as a2 on a1.srrelid = a2.oid left outer join pg_stat_subscription as a3 on a1.srsubid = a3.subid;
subname | relname | srsubstate | srsublsn
---------+---------+------------+------------
sub1 | data1_1 | r | 0/56068D38
sub1 | data1 | r | 0/55FB63D0
レプリケーションが実行されているテーブルに新たな列を追加する手順を説明します。
■初期状態
前述のPublication pub1, Subscription sub1を利用し、テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、テーブルdata1にはそれぞれ1件のデータが存在する状態とします。
(サーバ1において実施)
pubdb=# SELECT * FROM data1;
c1 | c2
----+------
1 | pub1
(サーバ2において実施)
subdb=# SELECT * FROM data1;
c1 | c2
----+------
1 | pub1
■列の追加手順
レプリケーション対象のテーブル定義に不一致があると更新競合が発生し、ロジカルレプリケーションの停止につながります。意図しないエラーを起こさぬようあらかじめALTER SUBSCRIPTION文でSubscriptionを一時停止しておくとよいでしょう。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 DISABLE;
ALTER SUBSCRIPTION
subdb=# SELECT * FROM pg_stat_subscription;
subid | subname | pid | relid | received_lsn | last_msg_send_time | last_msg_receipt_time | latest_end_lsn | latest_end_time
-------+---------------+-------+-------+--------------+-------------------------------+-------------------------------+----------------+-------------------------------
16463 | sub1 | | | | | | |
この時、Subscriberのログには以下の様なメッセージが出力されます。
(サーバ2のサーバログ)
2018-01-17 14:51:52.437 JST [31708] LOG: logical replication apply worker for subscription "sub1" will stop because the subscription was disabled
ロジカルレプリケーションを停止した後、Publisherのテーブルdata1にALTER TABLE文で列を追加します。また、ここで確認のため1件レコードを挿入しておきます。
(サーバ1において実施)
pubdb=# ALTER TABLE data1 ADD COLUMN c3 VARCHAR(5);
ALTER TABLE
pubdb=# INSERT INTO data1 VALUES (2, 'pub2', 'pub2');
INSERT 0 1
pubdb=# SELECT * FROM data1;
c1 | c2 | c3
----+------+------
1 | pub1 |
2 | pub2 | pub2
次にSubscriberのテーブルdata1にもALTER TABLE文で同じ定義の列を追加します。Subscriptionを停止しているため2件目のレコードはまだ転送されていません。
(サーバ2において実施)
subdb=# ALTER TABLE data1 ADD COLUMN c3 VARCHAR(5);
ALTER TABLE
subdb=# SELECT * FROM data1;
c1 | c2 | c3
----+------+----
1 | pub1 |
SubscriberでSubscriptionを再度有効化し、ロジカルレプリケーションを再開すると、この時点で2件目のレコードが転送されます。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 ENABLE;
ALTER SUBSCRIPTION
subdb=# SELECT * FROM data1;
c1 | c2 | c3
----+------+------
1 | pub1 |
2 | pub2 | pub2
Subscriberのpg_stat_subscritpionとログを再度確認すると、正常にレプリケーションが再開されていることがわかります。
(サーバ2において実施)
subdb=# SELECT * FROM pg_stat_subscription;
subid | subname | pid | relid | received_lsn | last_msg_send_time | last_msg_receipt_time | latest_end_lsn | latest_end_time
-------+---------------+-------+-------+--------------+-------------------------------+-------------------------------+----------------+-------------------------------
16463 | sub1 | 32626 | | 0/56073298 | 2018-01-17 15:02:10.817291+09 | 2018-01-17 15:02:10.650377+09 | 0/56073298 | 2018-01-17 15:02:10.817291+09
(サーバ2のサーバログ)
2018-01-17 15:01:47.266 JST [32626] LOG: logical replication apply worker for subscription "sub1" has started
レプリケーションが実行されているテーブルにインデックスを追加する手順を説明します。
■初期状態
前述のPublication pub1, Subscription sub1およびテーブルdata1をそのまま利用します。初期状態でPublisher,Subscriberのテーブルdata1は以下の定義となっています。
(サーバ1において実施)
pubdb=# \d data1
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
c3 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
Publications:
"pub1"
(サーバ2において実施)
subdb=# \d data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
c3 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
■Publisherのテーブルにインデックスを追加
初期状態からPublisherのテーブルdata1にのみインデックスをCREATEすると、Subscriberのテーブルdata1にはインデックスが追加されないことがわかります。
(サーバ1において実施)
pubdb=# CREATE INDEX data1_c2_idx ON data1 (c2);
CREATE INDEX
pubdb=# \d data1
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
c3 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
"data1_c2_idx" btree (c2)
Publications:
"pub1"
(サーバ2において実施)
subdb=# \d data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
c3 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
■Subscriberのテーブルにインデックスを追加
次にSubscriberのテーブルdata1にのみインデックスをCREATEすると、Subscriberのテーブルdata1にのみインデックスが追加されていることがわかります。
(サーバ2において実施)
subdb=# CREATE INDEX data1_c3_idx ON data1 (c3);
CREATE INDEX
subdb=# \d data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
c3 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
"data1_c3_idx" btree (c3)
(サーバ1において実施)
pubdb=# \d data1
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
c3 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
"data1_c2_idx" btree (c2)
Publications:
"pub1"
ロジカルレプリケーションにおいてインデックスはレプリケーション対象とならないため、インデックスの追加、削除が既存のレプリケーションに影響を及ぼすことはありませんが、Publisherで作成したインデックスがSubscriberでも必要であれば同じCREATE INDEX文を発行する必要があります。
ストリーミングレプリケーションがデータベースクラスタへの操作、オブジェクトを全て複製するのと違い、PostgreSQL10のロジカルレプリケーションでは以下の様な操作、オブジェクトが複製されません。そのためこれらは必要に応じてPublisherとSubscriberで同じ操作を行う等の対処を求められます。
操作、オブジェクト | 動作検証 |
---|---|
CREATE TABLE, ALTER TABLE, CREATE INDEX等のDDL | |
シーケンスの定義、データ | ■ |
TRUNCATE | ■ |
Large Object | |
VIEW, MATERIALIZED VIEW | |
FOREIGN TABLE | |
UNLOGGED TABLE | |
パーティショニングの親テーブル |
ここでは、TRUNCATEとシーケンスに対するロジカルレプリケーションの挙動を確認します。
本章は以下の環境を利用した検証結果を元に解説します。
■初期状態
初期状態でテーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、テーブルdata1にはそれぞれ5件のデータが存在する状態とします。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
Publisherでテーブルdata1に対してTRUNCATE TABLEを実行します。
(サーバ1において実施)
pubdb=# TRUNCATE TABLE data1;
TRUNCATE TABLE
pubdb=# select * from data1;
c1 | c2
----+----
この状態でSubscriberのテーブルdata1は変化していないことが確認できます。
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
Subscriberでもテーブルdata1に対してTRUNCATE TABLEを実行することで、Publisherと一致します。
(サーバ2において実施)
subdb=# TRUNCATE TABLE data1;
TRUNCATE TABLE
subdb=# select * from data1;
c1 | c2
----+----
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+----
TRUNCATE TABLEはWALにも記録されるデータ更新操作であり、ストリーミングレプリケーションで複製される操作ですが、ロジカルレプリケーションでは複製されないため注意が必要です。
ここではSERIAL列を含むテーブルをロジカルレプリケーションで複製した場合の挙動を確認します。
■初期状態
Publisherのデータベースpubdb、Subscriberのデータベースsubdbにそれぞれテーブルdata3を作成し、ロジカルレプリケーションで複製される状態にします。 また、テーブルdata3にはSERIAL列を定義します。
(サーバ1において実施)
pubdb=# CREATE TABLE data3 (c1 SERIAL PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
pubdb=# CREATE PUBLICATION pub3 FOR TABLE data3;
CREATE PUBLICATION
(サーバ2において実施)
subdb=# CREATE TABLE data3 (c1 SERIAL PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
subdb=# CREATE SUBSCRIPTION sub3 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub3;
NOTICE: created replication slot "sub3" on publisher
CREATE SUBSCRIPTION
■SERIAL列のレプリケーション
Publisherのテーブルdata3にレコードを挿入します。挿入したレコードがSubscriberのテーブルdata3にも転送されます。
(サーバ1において実施)
pubdb=# INSERT INTO data3 (c2) VALUES ('pub1');
INSERT 0 1
pubdb=# SELECT * FROM data3;
c1 | c2
----+------
1 | pub1
(サーバ2において実施)
subdb=# SELECT * FROM data3;
c1 | c2
----+------
1 | pub1
この時点でPublisherに存在するシーケンスは現在値が1になっていますが、Subscriberに存在するシーケンスからは現在値が取得できません。 つまり、SERIAL列の値自体は複製されたものの、内部的に作成されたシーケンスのデータは複製されていません。
(サーバ1において実施)
pubdb=# SELECT currval('data3_c1_seq');
currval
---------
1
(サーバ2において実施)
subdb=# SELECT currval('data3_c1_seq');
ERROR: currval of sequence "data3_c1_seq" is not yet defined in this session
ここでSubscriberのテーブルdata3に対してレコードを挿入するとキー重複でエラーになります。 これはSubscriberのシーケンスにPublisherのシーケンスデータと関係なく1を発行したためです。
(サーバ2において実施)
subdb=# INSERT INTO data3 (c2) VALUES ('pub1');
ERROR: duplicate key value violates unique constraint "data3_pkey"
DETAIL: Key (c1)=(1) already exists.
subdb=# SELECT currval('data3_c1_seq');
currval
---------
1
この状態で再度Subscriberのテーブルdata3にレコードを挿入すると成功します。これは先ほどINSERTに失敗した際にSubscriberのシーケンスは1にカウントアップされており、今回のINSERTで2を発行したためキー重複が発生せず挿入できたためです。
(サーバ2において実施)
subdb=# INSERT INTO data3 (c2) VALUES ('pub1');
INSERT 0 1
subdb=# SELECT * FROM data3;
c1 | c2
----+------
1 | pub1
2 | pub1
subdb=# SELECT currval('data3_c1_seq');
currval
---------
2
この時点でPublisherのシーケンスは現在値が1のため、このままPublisherのテーブルdata3にINSERTするとSubscriberのテーブルでキー重複が発生します。 そこで、Publisherのシーケンスの現在値を2に進めてからレコード挿入するとPublisherで挿入したレコードがSubscriberに複製されました。
(サーバ1において実施)
pubdb=# SELECT * FROM data3;
c1 | c2
----+------
1 | pub1
pubdb=# SELECT currval('data3_c1_seq');
currval
---------
1
pubdb=# SELECT nextval('data3_c1_seq');
nextval
---------
2
pubdb=# INSERT INTO data3 (c2) VALUES ('pub1');
INSERT 0 1
pubdb=# SELECT * FROM data3;
c1 | c2
----+------
1 | pub1
3 | pub1
(サーバ1において実施)
subdb=# SELECT * FROM data3;
c1 | c2
----+------
1 | pub1
2 | pub1
3 | pub1
このように、PublisherでSERIAL列に付与された値はSubscriberのテーブルに転送されますが、内部的に使用するシーケンスのデータは複製されないため、Subscriberで独自にレコードを挿入するとシーケンスのデータに不整合が生じます。そのため、SERIAL列を含むテーブルでロジカルレプリケーションを行う場合はSubscriberでのレコード更新は抑制した方がよいでしょう。
マスタしか更新できないストリーミングレプリケーションに対し、ロジカルレプリケーションはSubscriberも更新することができます。ただし、PublisherとSubscriberのテーブル間で以下の様な更新処理の競合が発生する可能性があります。
競合パターン | 動作検証 (更新時) | 動作検証 (初期データ同期時) |
---|---|---|
主キー違反/一意キー違反 | ■ | ■ |
CHECK制約違反 | ■ | ■ |
更新データが存在しない | ■ | ■ |
削除データが存在しない | ■ | ■ |
テーブルが存在しない | ■ | ■ |
一部の列が存在しない | ■ | ■ |
データ型変換エラー | ■ | ■ |
テーブルのロック | ■ | ■ |
更新対象レコードのロック | ■ | ■ |
ここでは競合が発生した時の挙動と対応方法について検証します。
本章は以下の環境を利用した検証結果を元に解説します。
前述の環境において実際に競合が発生した時の挙動と競合を解消させるための方法を競合発生のパターン毎に紹介します。
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、テーブルdata1にはc1列をPRIMARY KEYとし、それぞれ以下のデータが存在する状態とします。
(サーバ1において実施)
pubdb=# \d data1
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
Publications:
"pub1"
pubdb=# select * from data1;
c1 | c2
----+-------
1 | aaaaa
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+-------
1 | aaaaa
2 | sub
■競合の発生
Publisherのテーブルdata1にc1=2のレコードを挿入するとPublisherのINSERTは成功します。
(サーバ1において実施)
pubdb=# insert into data1 values (2, 'pub');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+-------
1 | aaaaa
2 | pub
この時Subscriberでは主キー違反による競合が発生し、レプリケーションが停止します。 Publisher、Subscriberのログには競合が解消するまで5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-10 13:37:55.149 JST [5859] LOG: starting logical decoding for slot "sub1"
2018-01-10 13:37:55.149 JST [5859] DETAIL: streaming transactions committing after 0/55F82E30, reading WAL from 0/55F82DF8
2018-01-10 13:37:55.149 JST [5859] LOG: logical decoding found consistent point at 0/55F82DF8
2018-01-10 13:37:55.149 JST [5859] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-10 13:37:49.965 JST [2132] ERROR: duplicate key value violates unique constraint "data1_pkey"
2018-01-10 13:37:49.965 JST [2132] DETAIL: Key (c1)=(2) already exists.
2018-01-10 13:37:49.968 JST [2131] WARNING: out of background worker slots
2018-01-10 13:37:49.968 JST [2131] HINT: You might need to increase max_worker_processes.
2018-01-10 13:37:49.968 JST [2123] LOG: worker process: logical replication worker for subscription 16394 (PID 2132) exited with exit code 1
■競合の解消
Subscriberのテーブルでキーが重複するレコードを削除すると競合が解消し、レプリケーションが再開されます。 また、Publisher、Subscriberへのエラーメッセージも出力されなくなります。
(サーバ2において実施)
subdb=# delete from data1 where c1 = 2;
DELETE 1
subdb=# select * from data1;
c1 | c2
----+-------
1 | aaaaa
2 | pub
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、Subscriberのテーブルdata1のc1列にCHECK制約を付与します。
(サーバ2において実施)
subdb=# ALTER TABLE data1 ADD CONSTRAINT data1_c1_check CHECK (c1 < 10);
ALTER TABLE
subdb=# \d+ data1
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
c1 | integer | | not null | | plain | |
c2 | character varying(5) | | | | extended | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
Check constraints:
"data1_c1_check" CHECK (c1 < 10)
■競合の発生
Publisherのテーブルdata1に、Subscriberのみに追加したCHECK制約に違反するレコードを挿入すると、PublisherのINSERTは成功します。
(サーバ1において実施)
pubdb=# insert into data1 values (10, 'pub10');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+-------
1 | aaaaa
2 | pub
10 | pub10
この時SubscriberではCHECK制約違反による競合が発生し、レプリケーションが停止します。 Publisher、Subscriberのログには競合が解消するまで5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-10 14:05:32.268 JST [6057] LOG: starting logical decoding for slot "sub1"
2018-01-10 14:05:32.268 JST [6057] DETAIL: streaming transactions committing after 0/55F833E8, reading WAL from 0/55F833B0
2018-01-10 14:05:32.268 JST [6057] LOG: logical decoding found consistent point at 0/55F833B0
2018-01-10 14:05:32.268 JST [6057] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-10 14:05:32.097 JST [6020] LOG: logical replication apply worker for subscription "sub1" has started
2018-01-10 14:05:32.102 JST [6020] ERROR: new row for relation "data1" violates check constraint "data1_c1_check"
2018-01-10 14:05:32.102 JST [6020] DETAIL: Failing row contains (10, pub10).
2018-01-10 14:05:32.103 JST [2123] LOG: worker process: logical replication worker for subscription 16394 (PID 6020) exited with exit code 1
■競合の解消
Subscriberのテーブルdata1のCHECK制約をDROPすると競合が解消し、レプリケーションが再開されます。 また、Publisher、Subscriberへのエラーメッセージも出力されなくなります。
(サーバ2において実施)
subdb=# ALTER TABLE data1 DROP CONSTRAINT data1_c1_check;
ALTER TABLE
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、Publisher,Subscriberのテーブルdata1はそれぞれ以下のレコードが存在する状態とします。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+----
■競合の発生
Publisherでレコードを更新するとPublisherのUPDATEは成功しますが、Subscriberのレコードには変化がありません。また、Publisher,Subscriberのログにエラーメッセージも出力されていません。
(サーバ1において実施)
pubdb=# update data1 set c2 = 'pub1a';
UPDATE 1
pubdb=# select * from data1;
c1 | c2
----+-------
1 | pub1a
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+----
キーが一致するレコードが存在しない場合の競合は無視され、レプリケーションも停止しないことがわかります。
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、Publisher,Subscriberのテーブルdata1はそれぞれ以下のレコードが存在する状態とします。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+----
■競合の発生
Publisherでレコードを削除するとPublisherのDELETEは成功しますが、Subscriberのレコードには変化がありません。また、Publisher,Subscriberのログにエラーメッセージも出力されていません。
(サーバ2において実施)
subdb=# delete from data1 where c1 = 1;
DELETE 1
subdb=# select * from data1;
c1 | c2
----+----
subdb=# select * from data1;
c1 | c2
----+----
キーが一致するレコードが存在しない場合の競合は無視され、レプリケーションも停止しないことがわかります。
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。
■競合の発生
Subscriberでテーブルdata1をDROPした後、Publisherでテーブルdata1にレコードを挿入すると、PublisherのINSERTは成功します。
(サーバ2において実施)
subdb=# DROP TABLE data1;
DROP TABLE
(サーバ1において実施)
pubdb=# insert into data1 values (1, 'pub1');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
この時Subscriberではテーブル不一致による競合が発生し、レプリケーションが停止します。 Publisher、Subscriberのログには競合が解消するまで5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-10 15:09:10.872 JST [6331] LOG: starting logical decoding for slot "sub1"
2018-01-10 15:09:10.872 JST [6331] DETAIL: streaming transactions committing after 0/55F8B9E8, reading WAL from 0/55F8B9B0
2018-01-10 15:09:10.872 JST [6331] LOG: logical decoding found consistent point at 0/55F8B9B0
2018-01-10 15:09:10.872 JST [6331] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-10 15:09:10.700 JST [6297] LOG: logical replication apply worker for subscription "sub1" has started
2018-01-10 15:09:10.706 JST [6297] ERROR: logical replication target relation "public.data1" does not exist
2018-01-10 15:09:10.708 JST [2123] LOG: worker process: logical replication worker for subscription 16394 (PID 6297) exited with exit code 1
■競合の解消
Subscriberでテーブルdata1を再作成するとログにエラーは出力されなくなりますが、レプリケーションは再開されません。
(サーバ2において実施)
subdb=# CREATE TABLE data1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
subdb=# select * from data1;
c1 | c2
----+----
これは、再作成したテーブルdata1がSubscription sub1からレプリケーション対象として認識されていないためと考えられます。
(サーバ2において実施)
subdb=# \dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Synchronous commit | Conninfo
------+----------+---------+-------------+--------------------+----------------------------------------------------------------
sub1 | postgres | t | {pub1} | off | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1
subdb=# select a3.subname, a2.relname, a1.srsubstate, a1.srsublsn from pg_subscription_rel as a1 left outer join pg_class as a2 on a1.srrelid = a2.oid left outer join pg_stat_subscription as a3 on a1.srsubid = a3.subid;
subname | relname | srsubstate | srsublsn
---------+------------------+------------+------------
Subscription sub1に対してALTER SUBSCRIPTION文を実行して、sub1にレプリケーション対象テーブルを再認識させると、テーブルdata1のレプリケーションが再開されます。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION;
ALTER SUBSCRIPTION
subdb=# select a3.subname, a2.relname, a1.srsubstate, a1.srsublsn from pg_subscription_rel as a1 left outer join pg_class as a2 on a1.srrelid = a2.oid left outer join pg_stat_subscription as a3 on a1.srsubid = a3.subid;
subname | relname | srsubstate | srsublsn
---------+------------------+------------+------------
sub1 | data1 | r | 0/55F8C298
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、Subscriberでテーブルdata1のc2列をDROPします。
(サーバ2において実施)
subdb=# ALTER TABLE data1 DROP COLUMN c2;
ALTER TABLE
subdb=# \d+ data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
c1 | integer | | not null | | plain | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
■競合の発生
Publisherでテーブルdata1に対してレコードを挿入すると、PublisherのINSERTは成功します。
(サーバ1において実施)
pubdb=# insert into data1 values (1, 'pub1');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
この時Subscriberでは列の不一致による競合が発生し、レプリケーションが停止します。 Publisher、Subscriberのログには競合が解消するまで5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-10 15:53:11.464 JST [6480] LOG: starting logical decoding for slot "sub1"
2018-01-10 15:53:11.464 JST [6480] DETAIL: streaming transactions committing after 0/55F900C0, reading WAL from 0/55F90088
2018-01-10 15:53:11.464 JST [6480] LOG: logical decoding found consistent point at 0/55F90088
2018-01-10 15:53:11.464 JST [6480] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-10 15:53:11.284 JST [6472] LOG: logical replication apply worker for subscription "sub1" has started
2018-01-10 15:53:11.298 JST [6472] ERROR: logical replication target relation "public.data1" is missing some replicated columns
2018-01-10 15:53:11.299 JST [6420] LOG: worker process: logical replication worker for subscription 16394 (PID 6472) exited with exit code 1
■競合の解消
Subscriberでテーブルdata1のc2列を再度追加するとレプリケーションが再開されます。 また、Publisher、Subscriberへのエラーメッセージも出力されなくなります。
(サーバ2において実施)
subdb=# ALTER TABLE data1 ADD COLUMN c2 VARCHAR(5);
ALTER TABLE
subdb=# \d+ data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
c1 | integer | | not null | | plain | |
c2 | character varying(5) | | | | extended | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
■Subscriberのテーブルに列が多く定義されている場合
前述のとおりSubscriberのテーブルにPublisherのテーブルの列が存在しない場合は競合が発生します。 逆にPublisherのテーブルに存在しない列がSubscriberのテーブルに存在した場合、以下のとおり競合は発生しません。 この時Subscriberのテーブルのみに定義された列にはNULLが設定されます。
(サーバ1において実施)
pubdb=# \d data1
Table "public.data1"
Column | Type | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
c1 | integer | | not null |
c2 | character varying(5) | | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
Publications:
"pub1"
(サーバ2において実施)
subdb=# ALTER TABLE data1 ADD COLUMN c3 VARCHAR(5); <-- Publisherに存在しないc3列を追加する
ALTER TABLE
subdb=# \d+ data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
c1 | integer | | not null | | plain | |
c2 | character varying(5) | | | | extended | |
c3 | character varying(5) | | | | extended | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
(サーバ1において実施)
pubdb=# insert into data1 values (1, 'pub1');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2 | c3
----+------+------
1 | pub1 | NULL <-- c3列(Publisher未定義列)にはNULLが格納される
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、Subscriberでテーブルdata1のc2列を異なるデータ型に変更します。
(サーバ2において実施)
subdb=# ALTER TABLE data1 ALTER COLUMN c2 TYPE integer USING c2::integer;
ALTER TABLE
subdb=# \d+ data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
c1 | integer | | not null | | plain | |
c2 | integer | | | | plain | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
■競合の発生
Publisherでテーブルdata1に対してレコードを挿入すると、PublisherのINSERTは成功します。
(サーバ1において実施)
pubdb=# insert into data1 values (1, 'pub1');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
この時Subscriberでは型の不一致による競合が発生し、レプリケーションが停止します。 Publisher、Subscriberのログには競合が解消するまで5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-10 16:08:13.308 JST [6602] LOG: starting logical decoding for slot "sub1"
2018-01-10 16:08:13.308 JST [6602] DETAIL: streaming transactions committing after 0/55F943E8, reading WAL from 0/55F943B0
2018-01-10 16:08:13.308 JST [6602] LOG: logical decoding found consistent point at 0/55F943B0
2018-01-10 16:08:13.308 JST [6602] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-10 16:08:13.136 JST [6597] LOG: logical replication apply worker for subscription "sub1" has started
2018-01-10 16:08:13.142 JST [6597] ERROR: invalid input syntax for integer: "pub1"
2018-01-10 16:08:13.142 JST [6597] CONTEXT: processing remote data for replication target relation "public.data1" column "c2", remote type character varying
, local type character varying
2018-01-10 16:08:13.143 JST [6420] LOG: worker process: logical replication worker for subscription 16394 (PID 6597) exited with exit code 1
■競合の解消
Subscriberでc2列のデータ型を元に戻すとレプリケーションが再開されます。 また、Publisher、Subscriberへのエラーメッセージも出力されなくなります。
(サーバ2において実施)
subdb=# ALTER TABLE data1 ALTER COLUMN c2 TYPE VARCHAR(5);
ALTER TABLE
subdb=# \d+ data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
c1 | integer | | not null | | plain | |
c2 | character varying(5) | | | | extended | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
■データ型の暗黙的変換が可能な場合
PublisherとSubscriberのテーブルで列のデータ型が相違する場合でも暗黙的な変換が可能であれば以下のとおり競合は発生しません。
(サーバ2において実施)
subdb=# ALTER TABLE data1 ALTER COLUMN c2 TYPE date;
ALTER TABLE
subdb=# \d+ data1;
Table "public.data1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
c1 | integer | | not null | | plain | |
c2 | date | | | | plain | |
Indexes:
"data1_pkey" PRIMARY KEY, btree (c1)
(サーバ1において実施)
pubdb=# insert into data1 values (1, '2018/1/1');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+----------
1 | 2018/1/1
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------------
1 | 2018-01-01 <-- 暗黙的な型変換が可能なレコードはレプリケーションされる(TEXT型 -> DATE型)
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。
■競合の発生
Subscriberでレプリケーション対象のテーブルdata1を明示的にロックします。
(サーバ2において実施)
subdb=# BEGIN;
BEGIN
subdb=# LOCK TABLE data1 IN EXCLUSIVE MODE;
LOCK TABLE
Publisherでテーブルdata1に対してレコードを挿入すると、PublisherのINSERTは成功しますが、Subscriberのテーブルdata1には反映されません。 この時Publisher、Subscriberのログにはエラーメッセージは出力されておらず、レプリケーションがロック解放を待機した状態となっています。
(サーバ1において実施)
pubdb=# insert into data1 values (1, 'pub1');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+----
■競合の解消
Subscriberでトランザクションを終了し、ロックを解放すると、レプリケーションが再開され、データが転送されます。
(サーバ2において実施)
subdb=# COMMIT;
COMMIT
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。
■競合の発生
Subscriberでテーブルdata1のレコードをSELECT FOR UPDATEで明示的にロックします。
(サーバ2において実施)
subdb=# BEGIN;
BEGIN
subdb=# SELECT * FROM data1 WHERE c1 = 1 FOR UPDATE;
LOCK TABLE
Publisherで同一のレコードを更新すると、PublisherのUPDATEは成功しますが、Subscriberのテーブルdata1には反映されません。 この時Publisher、Subscriberのログにはエラーメッセージは出力されておらず、レプリケーションがロック解放を待機した状態となっています。
(サーバ1において実施)
pubdb=# update data1 SET c2 = 'pub11' where c1 = 1;
UPDATE 1
pubdb=# select * from data1;
c1 | c2
----+-------
1 | pub11
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
■競合の解消
Subscriberでトランザクションを終了し、ロックを解放すると、レプリケーションが再開され、データが転送されます。
(サーバ2において実施)
subdb=# COMMIT;
COMMIT
subdb=# select * from data1;
c1 | c2
----+-------
1 | pub11
ロジカルレプリケーションの開始時に実行される初期データ同期処理においても更新時と同様の競合が発生する可能があります。以下で初期データ同期時に競合が発生した時の挙動と競合を解消させるための方法をパターン毎に紹介します。
■初期状態
Publisher,Subscriberのテーブルdata1にはc1列をPRIMARY KEYとし、それぞれ以下のデータが存在する状態とします。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
2 | pub2
■競合の発生
SubscriberでSubscription sub1を作成し、ロジカルレプリケーションを開始します。この時Subscriberでは主キー違反による競合が発生し、レプリケーションが停止します。
(サーバ2において実施)
subdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub1;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
Publisher、Subscriberのログには競合が解消するまで5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-11 15:53:32.764 JST [10254] LOG: logical decoding found consistent point at 0/55FB4C50
2018-01-11 15:53:32.764 JST [10254] DETAIL: There are no running transactions.
2018-01-11 15:53:32.772 JST [10255] LOG: starting logical decoding for slot "sub1"
2018-01-11 15:53:32.772 JST [10255] DETAIL: streaming transactions committing after 0/55FB4C88, reading WAL from 0/55FB4C50
2018-01-11 15:53:32.772 JST [10255] LOG: logical decoding found consistent point at 0/55FB4C50
2018-01-11 15:53:32.772 JST [10255] DETAIL: There are no running transactions.
2018-01-11 15:53:32.792 JST [10256] LOG: logical decoding found consistent point at 0/55FB4C88
2018-01-11 15:53:32.792 JST [10256] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-11 15:53:32.602 JST [10275] LOG: logical replication apply worker for subscription "sub1" has started
2018-01-11 15:53:32.608 JST [10276] LOG: logical replication table synchronization worker for subscription "sub1", table "data1" has started
2018-01-11 15:53:32.630 JST [10276] ERROR: duplicate key value violates unique constraint "data1_pkey"
2018-01-11 15:53:32.630 JST [10276] DETAIL: Key (c1)=(2) already exists.
2018-01-11 15:53:32.630 JST [10276] CONTEXT: COPY data1, line 2
2018-01-11 15:53:32.631 JST [6420] LOG: worker process: logical replication worker for subscription 16447 sync 16424 (PID 10276) exited with exit code 1
■競合の解消
Subscriberのテーブルでキーが重複するレコードを削除すると競合が解消し、レプリケーションが再開されます。 また、Publisher、Subscriberへのエラーメッセージも出力されなくなります。
(サーバ2において実施)
subdb=# delete from data1 where c1 = 2;
DELETE 1
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
主キー違反/一意キー違反による競合と同じ挙動になると推測されるため割愛します。
初期データ同期では発生しないケースのため割愛します。
初期データ同期では発生しないケースのため割愛します。
Publisher,Subscriberのテーブルdata1にはc1列をPRIMARY KEYとし、それぞれ以下のデータが存在する状態とします。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
2 | pub2
SubscriberでテーブルをDROPしてから、Subscriptionを作成するとエラーとなります。Subscriberのデータベースにテーブルが存在しない状態ではSubscriptionを作成できないため、初期データ同期時には発生しないケースとなります。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
(サーバ2において実施)
subdb=# drop table data1;
DROP TABLE
subdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub1;
ERROR: relation "public.data1" does not exist
■初期状態
Publisherにはテーブルdata1を作成し、レコードが3件存在する状態にします。Subscriberには列が少ないテーブルdata1を作成します。
(サーバ1において実施)
pubdb=# CREATE TABLE data1 (c1 INT PRIMARY KEY, c2 VARCHAR(5));
CREATE TABLE
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
(サーバ2において実施)
subdb=# CREATE TABLE data1 (c1 INT PRIMARY KEY);
CREATE TABLE
■競合の発生
SubscriberでSubscription sub1を作成し、ロジカルレプリケーションを開始します。この時Subscriberでは列定義の相違による競合が発生し、レプリケーションが停止します。
(サーバ2において実施)
subdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub1;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
Publisher、Subscriberのログには5秒毎に以下のメッセージが繰り返し出力されます。
(サーバ1のサーバログ)
2018-01-11 16:04:15.836 JST [10365] LOG: starting logical decoding for slot "sub1"
2018-01-11 16:04:15.836 JST [10365] DETAIL: streaming transactions committing after 0/55FB5AC0, reading WAL from 0/55FB5A88
2018-01-11 16:04:15.836 JST [10365] LOG: logical decoding found consistent point at 0/55FB5A88
2018-01-11 16:04:15.836 JST [10365] DETAIL: There are no running transactions.
2018-01-11 16:04:15.849 JST [10366] LOG: logical decoding found consistent point at 0/55FB5AC0
2018-01-11 16:04:15.849 JST [10366] DETAIL: There are no running transactions.
(サーバ2のサーバログ)
2018-01-11 16:04:15.666 JST [10385] LOG: logical replication apply worker for subscription "sub1" has started
2018-01-11 16:04:15.673 JST [10386] LOG: logical replication table synchronization worker for subscription "sub1", table "data1" has started
2018-01-11 16:04:15.686 JST [10386] ERROR: logical replication target relation "public.data1" is missing some replicated columns
2018-01-11 16:04:15.688 JST [6420] LOG: worker process: logical replication worker for subscription 16454 sync 16449 (PID 10386) exited with exit code 1
Subscriberでテーブルdata1を参照しても、初期データは同期されていません。
(サーバ2において実施)
subdb=# select * from data1;
c1
----
■競合の解消
更新時の競合発生と同じく、Subscriberでテーブルdata1のc2列を追加するとレプリケーションが再開されます。
一部の列が存在しないパターンと同じ挙動になると推測されるため割愛します。
■初期状態
Publisher,Subscriberのテーブルdata1にはそれぞれ以下のデータが存在する状態とします。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+----
■競合の発生
Subscriberでテーブルdata1をLOCK TABLE 〜 IN EXCLUSIVE MODEで明示的にロックします。
(サーバ2において実施)
subdb=# BEGIN;
BEGIN
subdb=# LOCK TABLE data1 IN EXCLUSIVE MODE;
LOCK TABLE
Subscriberで上記のトランザクションを実行したまま、別のコンソールでSubscriptionを作成します。この時レプリケーションはロックを待機するため、テーブルdata1には初期データが同期されていないことがわかります。
(サーバ2において実施)
subdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub1;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
subdb=# select * from data1;
c1 | c2
----+----
■競合の解消
Subscriberでトランザクションを終了するとロックが解放されて、レプリケーションの初期データ同期が開始されます。
(サーバ2において実施)
subdb=# COMMIT;
COMMIT
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
初期データ同期では発生しないケースのため割愛します。
前述の様にSubscriberで競合の原因となる要素を取り除く方法とは別に、競合が発生するWALのSubscriberへの適用をスキップする方法について以下に紹介します。
■初期状態
テーブルdata1のデータがPublisher,Subscriber間でレプリケーションされている状態とします。また、テーブルdata1にはレコードが5件存在し、PublisherのWALのLSNは「0/55FB6948」となっています。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
pubdb=# SELECT pg_current_wal_lsn() ;
pg_current_wal_lsn
--------------------
0/55FB6948
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
subdb=# SELECT oid, subname FROM pg_subscription ;
oid | subname
-------+---------
16463 | sub1
subdb=# SELECT * FROM pg_replication_origin_status;
local_id | external_id | remote_lsn | local_lsn
----------+-------------+------------+------------
1 | pg_16463 | 0/55FB6948 | 0/7360A120
■競合の発生
Subscriberでテーブルdata1に6件目のレコードを挿入した後、Publisherで同一キーのレコードを挿入し、競合を発生させます。
(サーバ2において実施)
subdb=# insert into data1 values (6, 'sub6');
INSERT 0 1
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
6 | sub6
(サーバ1において実施)
pubdb=# insert into data1 values (6, 'pub6');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
6 | pub6
競合が発生しているため、PublisherのLSNまでSubscriberのLSN(remote_lsn)が到達していません。
(サーバ1において実施)
pubdb=# SELECT pg_current_wal_lsn() ;
pg_current_wal_lsn
--------------------
0/55FB6DB0
・SUBSCRIPTION側でremote_lsnを確認
(サーバ2において実施)
subdb=# SELECT * FROM pg_replication_origin_status;
local_id | external_id | remote_lsn | local_lsn
----------+-------------+------------+------------
1 | pg_16463 | 0/55FB6910 | 0/7360A120
■競合の解消
Subscriberでpg_replication_origin_advance関数を実行しLSNを進めることで、競合が発生しているWALをスキップさせます。この時点でSubscriberのサーバログにはレプリケーションが停止した際のエラーメッセージが出力されなくなります。
(サーバ2において実施)
subdb=# SELECT pg_replication_origin_advance ('pg_16463', '0/55FB6DB0');
pg_replication_origin_advance
-------------------------------
SubscriberでLSNを再度確認するとPublisherのLSNと一致していることがわかります。なお、競合発生直前に挿入したレコード(c1=6, c2=sub6)はそのまま残されています。
(サーバ2において実施)
subdb=# SELECT * FROM pg_replication_origin_status;
local_id | external_id | remote_lsn | local_lsn
----------+-------------+------------+------------
1 | pg_16463 | 0/55FB6DB0 | 0/7360A120
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
6 | sub6
この状態でPublisherに新しいレコードを挿入すると、Subscriberのc1=6のレコードは残されたままレプリケーションは再開されており、Publisherでレコード(c1=6,c2=pub6)を挿入したWALのみがスキップされたことがわかります。
(サーバ1において実施)
pubdb=# insert into data1 values (7, 'pub7');
INSERT 0 1
pubdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
6 | pub6
7 | pub7
(サーバ2において実施)
subdb=# select * from data1;
c1 | c2
----+------
1 | pub1
2 | pub2
3 | pub3
4 | pub4
5 | pub5
6 | sub6
7 | pub7
ここでは、ロジカルレプリケーションとストリーミングレプリケーションを併用する環境を想定し、以下の観点を確認します。
本章は以下の環境を利用した検証結果を元に解説します。
既にロジカルレプリケーションが稼働している環境に対してストリーミングレプリケーションを稼働させる手順は、新規でストリーミングレプリケーションを稼働させる手順と変わりません。(後で「SR環境の設定手順」への相互参照リンクを貼る)の手順にしたがってストリーミングレプリケーションのマスタ、スレーブを構築して下さい。
■初期状態
Publisherにはデータベースpubdbとpubdb_all_tableが存在します。また、pubdbデータベース、pubdb_all_tableデータベースには、Publicationがそれぞれ2つ、1つ存在します。
(サーバ1において実施)
pubdb=# \dRp+
Publication pub1
-[ RECORD 1 ]-------
Owner | pubusr1
All tables | f
Inserts | t
Updates | t
Deletes | t
Tables:
"public.data1"
"public.data1_1"
Publication pub2
-[ RECORD 1 ]-------
Owner | pubusr1
All tables | f
Inserts | t
Updates | t
Deletes | t
Tables:
"public.data2"
pubdb_all_table=# \dRp+
Publication pub_all_table
-[ RECORD 1 ]--------
Owner | postgres
All tables | t
Inserts | t
Updates | t
Deletes | t
ロジカルレプリケーションのPublisherとストリーミングレプリケーションのマスタを兼ねるサーバ1の動的統計情報ビューpg_stat_replicationには、ロジカルレプリケーションの情報(RECORD 1〜3)、ストリーミングレプリケーションの情報(RECORD 4)が確認できます。
(サーバ1において実施)
pubdb=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 2054
usesysid | 16385
usename | repusr1
application_name | sub_all_table
client_addr | 192.168.127.32
client_hostname |
client_port | 46604
backend_start | 2018-02-08 13:42:42.520503+09
backend_xmin |
state | streaming
sent_lsn | 0/580126B0
write_lsn | 0/580126B0
flush_lsn | 0/580126B0
replay_lsn | 0/580126B0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 2 ]----+------------------------------
pid | 1948
usesysid | 16385
usename | repusr1
application_name | sub2
client_addr | 192.168.127.32
client_hostname |
client_port | 46526
backend_start | 2018-02-08 13:36:25.199985+09
backend_xmin |
state | streaming
sent_lsn | 0/580126B0
write_lsn | 0/580126B0
flush_lsn | 0/580126B0
replay_lsn | 0/580126B0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 3 ]----+------------------------------
pid | 1850
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 46524
backend_start | 2018-02-08 13:15:08.479523+09
backend_xmin |
state | streaming
sent_lsn | 0/580126B0
write_lsn | 0/580126B0
flush_lsn | 0/580126B0
replay_lsn | 0/580126B0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 4 ]----+------------------------------
pid | 1846
usesysid | 16385
usename | repusr1
application_name | nk_PGECons3
client_addr | 192.168.127.33
client_hostname |
client_port | 55520
backend_start | 2018-02-08 13:14:36.673678+09
backend_xmin |
state | streaming
sent_lsn | 0/580126B0
write_lsn | 0/580126B0
flush_lsn | 0/580126B0
replay_lsn | 0/580126B0
write_lag | 00:00:00.00054
flush_lag | 00:00:00.000541
replay_lag | 00:00:00.000541
sync_priority | 0
sync_state | async
サーバ1のシステムカタログpg_replication_slotsでは、ロジカルレプリケーションのSubscriptionと合わせて自動作成されたレプリケーションスロットの情報が確認できます。
(サーバ1において実施)
pubdb=# select * from pg_replication_slots;
-[ RECORD 1 ]-------+----------------
slot_name | sub2
plugin | pgoutput
slot_type | logical
datoid | 16384
database | pubdb
temporary | f
active | t
active_pid | 1948
xmin |
catalog_xmin | 701
restart_lsn | 0/58017AA8
confirmed_flush_lsn | 0/58017AE0
-[ RECORD 2 ]-------+----------------
slot_name | sub1
plugin | pgoutput
slot_type | logical
datoid | 16384
database | pubdb
temporary | f
active | t
active_pid | 1850
xmin |
catalog_xmin | 701
restart_lsn | 0/58017AA8
confirmed_flush_lsn | 0/58017AE0
-[ RECORD 3 ]-------+----------------
slot_name | sub_all_table
plugin | pgoutput
slot_type | logical
datoid | 16474
database | pubdb_all_table
temporary | f
active | t
active_pid | 2054
xmin |
catalog_xmin | 701
restart_lsn | 0/58017AA8
confirmed_flush_lsn | 0/58017AE0
pubdbデータベース、pubdb_all_tableデータベースそれぞれのdata1テーブルにはあらかじめ1件データをINSERTしておきます。
(サーバ1において実施)
pubdb=# select * from data1;
c1 | c2
----+-------
1 | pub1
pubdb_all_table=# select * from data1;
c1 | c2
----+------
1 | pub1
Subscriberのsubdbデータベース、subdb_all_tableデータベースには、Subscriptionがそれぞれ2つ、1つ存在します。
(サーバ2において実施)
subdb=# \dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Synchronous commit | Conninfo
------+----------+---------+-------------+--------------------+----------------------------------------------------------------
sub1 | postgres | t | {pub1} | off | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1
sub2 | postgres | t | {pub2} | off | host=192.168.127.31 dbname=pubdb user=repusr1 password=repusr1
subdb_all_table=# \dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Synchronous commit | Conninfo
---------------+----------+---------+-----------------+--------------------+--------------------------------------------------------------------------
sub_all_table | postgres | t | {pub_all_table} | off | host=192.168.127.31 dbname=pubdb_all_table user=repusr1 password=repusr1
ロジカルレプリケーションのSubscriberであるサーバ2の動的統計情報ビューpg_stat_subscriptionには、ロジカルレプリケーションの情報が3件確認できます。
(サーバ2において実施)
subdb_all_table=# select * from pg_stat_subscription;
subid | subname | pid | relid | received_lsn | last_msg_send_time | last_msg_receipt_time | latest_end_lsn | latest_end_time
-------+---------------+-------+-------+--------------+-------------------------------+-------------------------------+----------------+-------------------------------
16463 | sub1 | 11038 | | 0/58011F60 | 2018-02-08 13:42:47.590367+09 | 2018-02-08 13:42:47.423435+09 | 0/58011F60 | 2018-02-08 13:42:47.590367+09
24677 | sub2 | 11072 | | 0/58011F60 | 2018-02-08 13:42:47.59058+09 | 2018-02-08 13:42:47.42367+09 | 0/58011F60 | 2018-02-08 13:42:47.59058+09
24678 | sub_all_table | 11158 | | 0/58011F60 | 2018-02-08 13:42:47.590534+09 | 2018-02-08 13:42:47.423605+09 | 0/58011F60 | 2018-02-08 13:42:47.590534+09
ストリーミングレプリケーションのスレーブであるサーバ3では動的統計情報ビューpg_stat_wal_receiverでストリーミングレプリケーションの情報が確認できます。
(サーバ3において実施)
pubdb=# select * from pg_stat_wal_receiver;
-[ RECORD 1 ]---------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
pid | 26152
status | streaming
receive_start_lsn | 0/58000000
receive_start_tli | 1
received_lsn | 0/58016A78
received_tli | 1
last_msg_send_time | 2018-02-08 13:52:22.063552+09
last_msg_receipt_time | 2018-02-08 13:52:21.953994+09
latest_end_lsn | 0/58016A78
latest_end_time | 2018-02-08 13:52:22.063552+09
slot_name |
conninfo | user=repusr1 password=******** dbname=replication host=192.168.127.31 port=5432 application_name=nk_PGECons3 fallback_application_name=walreceiver sslmode=prefer sslcompression=1 krbsrvname=postgres target_session_attrs=any
また、サーバ3にはマスタであるサーバ1で定義されているPublicationがストリーミングレプリケーションで複製されています。
(サーバ3において実施)
pubdb=# \dRp+
Publication pub1
-[ RECORD 1 ]-------
Owner | pubusr1
All tables | f
Inserts | t
Updates | t
Deletes | t
Tables:
"public.data1"
"public.data1_1"
Publication pub2
-[ RECORD 1 ]-------
Owner | pubusr1
All tables | f
Inserts | t
Updates | t
Deletes | t
Tables:
"public.data2"
pubdb_all_table=# \dRp+
Publication pub_all_table
-[ RECORD 1 ]--------
Owner | postgres
All tables | t
Inserts | t
Updates | t
Deletes | t
ただし、サーバ3はサーバ2とのロジカルレプリケーションは確立していないため、サーバ3の動的統計情報ビューpg_stat_replication,pg_replication_slotsを見ても、サーバ1と同じ情報は存在しません。
(サーバ3において実施)
pubdb_all_table=# select * from pg_replication_slots;
(0 rows)
pubdb_all_table=# select * from pg_stat_replication;
(0 rows)
■ストリーミングレプリケーションのマスタを切り替える手順
初期状態の構成でストリーミングレプリケーションのマスタに障害が発生した状態を想定し、昇格したスレーブへロジカルレプリケーションを切り替える手順を説明します。切替後の環境は以下の様になります。
まず、ストリーミングレプリケーションのマスタ(サーバ1)のPostgreSQLサーバを強制終了します。
(サーバ1において実施)
-bash-4.2$ pg_ctl -m f stop
サーバ停止処理の完了を待っています....完了
サーバは停止しました
ストリーミングレプリケーションのスレーブ(サーバ3)でpg_ctl promoteコマンドを実行し、マスタに昇格します。
(サーバ3において実施)
-bash-4.2$ pg_ctl promote
waiting for server to promote....完了
server promoted
サーバ3のログでマスタとして接続を受け付け始めたことが確認でき、ストリーミングレプリケーションのマスタ切替は完了します。
(サーバ3のサーバログ)
2018-02-08 15:16:34.693 JST [26148] LOG: received promote request
2018-02-08 15:16:34.693 JST [26148] LOG: redo done at 0/58020F38
2018-02-08 15:16:34.693 JST [26148] LOG: last completed transaction was at log time 2018-02-08 14:24:19.546571+09
2018-02-08 15:16:34.694 JST [26148] LOG: selected new timeline ID: 2
2018-02-08 15:16:34.734 JST [26148] LOG: archive recovery complete
2018-02-08 15:16:34.738 JST [26146] LOG: database system is ready to accept connections
次に、サーバ3を新たなPublisherとしたロジカルレプリケーションを開始させてみます。
まず、サーバ2でALTER SUBSCRIPTION文を実行して既存のSubscriptionの接続先を変更してみました。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.33 dbname=pubdb user=repusr1 password=repusr1';
ALTER SUBSCRIPTION
Subscriberのログを見るとサーバ3にレプリケーションスロット sub1 が存在しないことが原因で失敗しています。
(サーバ2のサーバログ)
2018-02-08 15:27:04.296 JST [11029] LOG: worker process: logical replication worker for subscription 24678 (PID 12728) exited with exit code 1
2018-02-08 15:27:04.299 JST [12729] LOG: logical replication apply worker for subscription "sub1" has started
2018-02-08 15:27:04.302 JST [12729] ERROR: could not start WAL streaming: ERROR: replication slot "sub1" does not exist
また、既存のSubscriptionを再作成しようとしても、レプリケーションスロット sub1 が存在しないことで削除も拒否されます。
(サーバ2において実施)
subdb=# DROP SUBSCRIPTION sub1;
ERROR: could not drop the replication slot "sub1" on publisher
DETAIL: The error was: ERROR: replication slot "sub1" does not exist
SubscriptionのDROP時にはレプリケーションスロットも合わせてDROPしようとします。今回の様にレプリケーションスロットが存在しないSubscriptionが残存する場合は、Subscriptionを一時停止(DISABLE)し、レプリケーションスロットとの対応を無効化(slot_name = NONE)してからDROPする必要があります。
(サーバ2において実施)
subdb=# ALTER SUBSCRIPTION sub1 DISABLE;
ALTER SUBSCRIPTION
subdb=# ALTER SUBSCRIPTION sub1 SET (slot_name = NONE);
ALTER SUBSCRIPTION
subdb=# DROP SUBSCRIPTION sub1;
DROP SUBSCRIPTION
subdb_all_table=# ALTER SUBSCRIPTION sub_all_table DISABLE;
ALTER SUBSCRIPTION
subdb_all_table=# ALTER SUBSCRIPTION sub_all_table SET (slot_name = NONE);
ALTER SUBSCRIPTION
subdb_all_table=# DROP SUBSCRIPTION sub_all_table;
DROP SUBSCRIPTION
SubscriptionをDROPした後、新たにSubscriptionを作り直すと、LRが開始されます。
(サーバ2において実施)
subdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.127.33 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub1 WITH (copy_data = false);
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
subdb=# CREATE SUBSCRIPTION sub2 CONNECTION 'host=192.168.127.33 dbname=pubdb user=repusr1 password=repusr1' PUBLICATION pub2 WITH (copy_data = false);
NOTICE: created replication slot "sub2" on publisher
CREATE SUBSCRIPTION
(サーバ2のサーバログ)
2018-02-08 15:53:08.879 JST [13210] LOG: logical replication apply worker for subscription "sub1" has started
2018-02-08 15:57:37.386 JST [13231] LOG: logical replication apply worker for subscription "sub2" has started
2018-02-08 15:57:37.393 JST [13232] LOG: logical replication table synchronization worker for subscription "sub2", table "data2" has started
2018-02-08 15:57:37.405 JST [13232] LOG: logical replication table synchronization worker for subscription "sub2", table "data2" has finished
なお、CREATE SUBSCRIPTION文にWITH (copy_data = false)を付与すると、ロジカルレプリケーション開始時の初期データコピーがスキップされます。試しにWITH (copy_data = false) を付けずに実行すると、初期データのコピー時に残存するデータとキー重複を起こし、ロジカルレプリケーションが開始されません。
(サーバ2において実施)
subdb_all_table=# CREATE SUBSCRIPTION sub_all_table CONNECTION 'host=192.168.127.33 dbname=pubdb_all_table user=repusr1 password=repusr1' PUBLICATION pub_all_table;
NOTICE: created replication slot "sub_all_table" on publisher
CREATE SUBSCRIPTION
(サーバ2のサーバログ)
2018-02-08 16:00:04.913 JST [13245] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data1" has started
2018-02-08 16:00:04.923 JST [13246] LOG: logical replication table synchronization worker for subscription "sub_all_table", table "data2" has started
2018-02-08 16:00:04.928 JST [13245] ERROR: duplicate key value violates unique constraint "data1_pkey"
2018-02-08 16:00:04.928 JST [13245] DETAIL: Key (c1)=(1) already exists.
2018-02-08 16:00:04.928 JST [13245] CONTEXT: COPY data1, line 1
2018-02-08 16:00:04.929 JST [11029] LOG: worker process: logical replication worker for subscription 24685 sync 16479 (PID 13245) exited with exit code 1
2018-02-08 16:00:04.939 JST [13246] ERROR: duplicate key value violates unique constraint "data2_pkey"
2018-02-08 16:00:04.939 JST [13246] DETAIL: Key (c1)=(1) already exists.
2018-02-08 16:00:04.939 JST [13246] CONTEXT: COPY data2, line 1
SRの新マスタとなったサーバ3の動的統計情報ビューpg_stat_replicationを確認すると、サーバ3⇒サーバ2のロジカルレプリケーションが確立していることがわかります。
(サーバ3において実施)
pubdb_all_table=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 26661
usesysid | 16385
usename | repusr1
application_name | sub_all_table
client_addr | 192.168.127.32
client_hostname |
client_port | 53059
backend_start | 2018-02-08 16:07:25.52522+09
backend_xmin |
state | catchup
sent_lsn | 0/58022DA0
write_lsn | 0/58022DA0
flush_lsn | 0/58022DA0
replay_lsn | 0/58022DA0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 2 ]----+------------------------------
pid | 26503
usesysid | 16385
usename | repusr1
application_name | sub2
client_addr | 192.168.127.32
client_hostname |
client_port | 52951
backend_start | 2018-02-08 15:57:37.444994+09
backend_xmin |
state | streaming
sent_lsn | 0/58022DA0
write_lsn | 0/58022DA0
flush_lsn | 0/58022DA0
replay_lsn | 0/58022DA0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
-[ RECORD 3 ]----+------------------------------
pid | 26486
usesysid | 16385
usename | repusr1
application_name | sub1
client_addr | 192.168.127.32
client_hostname |
client_port | 52949
backend_start | 2018-02-08 15:53:08.937385+09
backend_xmin |
state | streaming
sent_lsn | 0/58022DA0
write_lsn | 0/58022DA0
flush_lsn | 0/58022DA0
replay_lsn | 0/58022DA0
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
ロジカルレプリケーションとストリーミングレプリケーションを併用する場合、更新対象を一元化してデータの整合性を担保しやすくするため、ストリーミングレプリケーションのマスタとロジカルレプリケーションのPublisherは同じサーバにした方がよいと考えます。
この場合、ストリーミングレプリケーションのマスタがスレーブに切り替わってもPublicationを再定義する必要はありません。
一方でSubscriptionは再作成が必要で、古いSubscriptionはレプリケーションスロットとの対応を無効化してから削除する必要があります。 また、新たなSubscriberを作成する際は以下のいずれかの方法が選択できます。
前者は初期データコピーの処理が不要でロジカルレプリケーションを再開するまでの時間を短縮できますが、マスタの切替えからSubscriptionの再作成までに発生する更新を複製できないデメリットがありますので、運用形態によって選択する必要がありそうです。
本章ではまずPostgreSQLのロジカルレプリケーションの特徴、アーキテクチャ、ユースケース、制限事項を整理し、実際の構築例を交えてロジカルレプリケーションを設定する具体的な手順を説明しました。ストリーミングレプリケーションではマスタのベースバックアップからデータベースクラスタ単位で一致するスレーブを作成したのに対し、ロジカルレプリケーションではPublication/Subscriptionといったオブジェクトによって、テーブル単位や更新種別単位での柔軟な複製形態でのレプリケーションを構成できることがわかりました。
また、実行時パラメータについては稼働するプロセス数との関係を検証した結果を踏まえて妥当な値を算出する方法を提示しました。アーキテクチャがストリーミングレプリケーションと共通な部分があるため一部の実行時パラメータはストリーミングレプリケーションで使用するプロセス数を考慮した設定が必要であることがわかりました。
次に同期レプリケーション、複数Subscriptionへのレプリケーション、カスケード構成といった応用的な使い方を検証し、ロジカルレプリケーションでもストリーミングレプリケーションと同様の構成がとれることが確認できました。また、パーティショニングを組み合わせることで複数サーバのデータを1つのサーバに集約するデータ統合の用途にも応用できることが確認できました。
さらにロジカルレプリケーションを実際に運用するシーンを想定し、監視方法や障害発生時の挙動、レプリケーション開始後のテーブル追加、定義変更の手順について検証しました。ストリーミングレプリケーションと違いロジカルレプリケーションではSubscriberを更新できる代わりに整合性をユーザが担保しなければならないため、更新時の競合が発生した場合もユーザ側で検知、解消する必要があります。ただし、現状では競合が発生した際の対応方法は限られており、運用も容易ではないため、基本的に競合が発生しない設計、運用を心がけるべきです。つまり、Subscriberには検索用のインデックスを付与する程度に留めてデータの更新は行わないのが望ましいと考えます。また、ストリーミングレプリケーションとロジカルレプリケーションを併用する環境での障害を想定した検証では、Subscriptionを新しいPublicationにつなぎ直してロジカルレプリケーションを再開するまでの具体的な手順を確認できました。
ロジカルレプリケーションは、アーキテクチャの面でストリーミングレプリケーションと共通する部分がありますが、データベースクラスタ単位で複製されるストリーミングレプリケーションよりも柔軟にテーブル単位、更新種別単位で複製できるようになり、レプリケーションの活用範囲が広がったといえます。その一方で、運用面ではロジカルレプリケーションの対象とならない操作やオブジェクトが存在することや、更新時の競合が発生した時の運用が全てユーザに任されている点など現状では制約事項も多く、運用面の負荷が軽減される機能強化が今後求められます。
BDR(Bi-Directional Replication)は2ndQuadrant社によって開発された、オープンソース(PostgreSQL License)のマルチマスタ・レプリケーションシステムです。 双方向の非同期論理レプリケーションを使用し、地理的に分散したクラスタで使用するために設計されています。
マルチマスタについて
一般的なRDBMSの冗長化構成(マスタスレーブ構成)においては、更新処理を実行可能なサーバをマスタと呼称します。 マルチマスタとは、同一のデータを保持している複数のDB間(クラスタ)において、 更新処理を実施可能なサーバが複数台存在する構成を指します。 BDRでは双方向にレプリケーションを実施することで、複数のサーバへの更新を可能にしています。
BDRは以下のようなケースで有用です。
例としては以下のようにレスポンスを向上させるために各地でアプリケーション及びDBを動作させるようなケースが考えられます。
BDRでは「Logical Decoding」により、WALから論理的な変更点を抽出し、 各ノードで適用することで双方向レプリケーションを実現しています。 (Logical Decoding及びWALの送受信はバックグラウンドワーカープロセスが実施します)
従来のトリガーを用いた双方向レプリケーションの場合、 下図のように書き込みが余分に発生(変更記録、変更反映)してしまいます。
一方、BDRでは余分な書き込みが発生せずパフォーマンス的に有利となっています。
利用されているPostgreSQLのメカニズムの一覧です。
No. | 機能 | 概要 |
---|---|---|
1 | Event Triggers | 一つのテーブルに接続され、DMLイベントのみを補足する通常のトリガとは異なり、特定データベースのDDLイベントを捕捉可能。 |
2 | Logical Decoding | SQLを介して実行された更新処理を外部コンシューマへストリーミングするための機能。更新結果はロジカルレプリケーションスロットで識別され、ストリームに送出される。 |
3 | Replication Slots | マスタのデータベースの変更をスレーブ側に同じ順序で適用するための機能。 |
4 | Background Workers | ユーザ提供のコードを別プロセスで実行するように拡張する機能。Background Workerプロセスはpostgresプロセスによって起動、終了、監視される。 |
5 | Commit Timestamps | トランザクションがいつコミットされたかを確認するための機能。 |
6 | Replication Origins | レプリケーションの進行状況を追跡するための機能。双方向レプリケーションにてループの防止などが可能。 |
7 | DDL event capture | 実行されるDDLコマンドを返す機能。 |
8 | generic WAL messages for logical decoding | テキストあるいはバイナリのメッセージをWALに挿入できる仕組み(API)。Logical Decoding機能によって読み出されることを想定している。 |
マルチマスタ構成を取る場合、各ノードが持つ情報に不整合が発生しないように管理する仕組みが必要となります。 BDRでは結果整合性(eventually consistent)と呼ばれる一貫性モデルを採用し、整合性を確保します。
BDRでは一意な値を払い出すために、「グローバルシーケンス」、 「ステップ/オフセットシーケンス」という2つの手法を紹介しています。
グローバルシーケンスでは、各ノードに予め値の塊(chunk)を一定数ずつ割り振ることで値の重複を回避しています。
- chunkを消費するとvoting処理(下記参照)を行い、新たにchunkを割り振ります。
- グローバルシーケンスは廃止予定です。(下記のステップ/オフセットシーケンスを推奨)
voting処理
"chunk"と呼ばれるシーケンス番号のまとまりをノードに割り当てる処理をvoting処理と呼びます。 "chunk"が複数のノードに割り当てられないことを確認するため、ノード間で投票処理が行われており、正常に機能させるためには奇数台のノードが必要です。 過半数のノードが停止している場合は、投票処理にて過半数に到達しなくなるため、新しい"chunk"がノードに割り当てられません。 そして、"chunk"が枯渇した場合、nextvalの実行に失敗してしまいます。
類似機能及び製品との机上比較の結果です。
No. | 比較項目 | BDR | SR(Hot Standby) | Slony | ロジカルレプリケーション |
---|---|---|---|---|---|
1 | マルチマスタ | ○ | × | × | × |
2 | 選択的レプリケーション | ○ | × | ○ | ○ |
3 | 競合検知 | ○ | × | × | ○ |
4 | カスケーディング | × | ○ | ○ | ○ |
5 | WALベースレプリケーション | ○ | ○ | × | ○ |
6 | DDLレプリケーション | ○ | ○ | × | × |
7 | 自動レプリカ新テーブル | ○ | ○ | × | × |
8 | シーケンスレプリケーション | ○ | ○ | ○ | × |
9 | プライマリキー更新 | ○ | ○ | × | ○ |
10 | 同期コミット | ○ | ○ | × | ○ |
11 | 外部デーモン不使用 | ○ | ○ | × | ○ |
12 | レプリカへの書き込み | ○ | × | ○ | ○ |
サポートについては以下が存在します。
No. | サポート | 概要 |
---|---|---|
1 | 無償サポート | BDRコミュニティへのメール、BDRのGoogleグループが存在。
email: bdr-list@2ndQuadrant.com
|
2 | 有償サポート | 2ndQuadrant社によるサポートを受けることが可能です。
※ 2ndQuadrant社について
BDRの製造元で、PostgreSQLの専門家(コミッター等)が多数在籍する企業です。
PostgreSQLのコンサルティングサービス等を提供しています。
|
机上の情報整理および検証について、以下を主な目的としています。
項目 | 説明 |
---|---|
PostgreSQLバージョン | 9.4.10
※ 調査時点ではBDRは9.6に対応していなかったため
|
BDRバージョン | 1.0.2
|
OS | CentOS 7.1 |
構成 | 2ノード構成 |
ダウンロードモジュールは以下になります。
以下は構成図になります。
PostgreSQL BDR (Bi-Directional Replication)を利用したマルチマスタ環境の構築手順について確認します。
また、マルチマスタ環境構築後、各ノードに対して更新が実行可能かを確認します。
http://bdr-project.org/docs/next/installation-packages.html#INSTALLATION-PACKAGES-REDHAT
こちらの環境 で検証を実施しました。
設定手順
指定がない部分は、node1・node2両方で実施します。
BDR検証を実施するための環境を準備します。
ssh接続を用いて、該当環境へ接続 ユーザ: root パスワード: xxxxxxx
DNSの名前解決のために各サーバのホスト名を設定します。
# vi /etc/hosts [下記をファイル末尾に追加] 192.168.0.10 node1 192.168.0.12 node2
PostgreSQL間のBDR接続のために5432ポートの開放をします。
# firewall-cmd --permanent --add-port=5432/tcp # firewall-cmd --reload以下のコマンドで確認します。
# firewall-cmd --list-ports 5432/tcp
BDRレポジトリ用のRPMをインストールします。
# RHEL/CentOS users only: # yum install http://packages.2ndquadrant.com/postgresql-bdr94-2ndquadrant/yum-repo-rpms/postgresql-bdr94-2ndquadrant-redhat-latest.noarch.rpm以下のコマンドでインストール済み一覧を確認します。
# yum list installed postgresql-bdr94-2ndquadrant-redhat.noarch 0:1.0-3 [省略] インストール済みパッケージ postgresql-bdr94-2ndquadrant-redhat.noarch 1.0-3 installed
登録したBDRレポジトリからBDRをインストールします。
# yum install postgresql-bdr94-bdr [省略] インストール: postgresql-bdr94-bdr.x86_64 0:1.0.2-1_2ndQuadrant.el7.centos 依存性関連をインストールしました: postgresql-bdr94.x86_64 0:9.4.10_bdr1-1_2ndQuadrant.el7.centos postgresql-bdr94-contrib.x86_64 0:9.4.10_bdr1-1_2ndQuadrant.el7.centos postgresql-bdr94-libs.x86_64 0:9.4.10_bdr1-1_2ndQuadrant.el7.centos postgresql-bdr94-server.x86_64 0:9.4.10_bdr1-1_2ndQuadrant.el7.centos 完了しました!以下のコマンドでインストール済み一覧を確認します。
# yum list installed postgresql-bdr94-bdr.x86_64 0:1.0.2-1_2ndQuadrant.el7.centos [省略] インストール済みパッケージ postgresql-bdr94-bdr.x86_64 1.0.2-1_2ndQuadrant.el7.centos @postgresql-bdr94-2ndquadrant-redhat
データベースクラスタとコマンド実行ファイルに環境変数の設定をします。
$ vi ~/.bash_profile [下記の修正を加える] #PGDATA=/var/lib/pgsql/9.4-bdr/data PGDATA=/var/lib/pgsql/2ndquadrant_bdr/data/ [下記をファイル末尾に追加] export PATH=/usr/pgsql-9.4/bin:$PATH以下のコマンドで設定した環境変数を確認します。
$ exit ログアウト # su - postgres $ echo $PATH /usr/pgsql-9.4/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin $ echo $PGDATA /var/lib/pgsql/2ndquadrant_bdr/data/
BDR検証用に新規でデータベースクラスタを作成します。
$ mkdir -p $HOME/2ndquadrant_bdr/ $ initdb -D $HOME/2ndquadrant_bdr/data -A trust -U postgres --no-locale
BDRのパラメータ設定(postgresql.conf)をします。
$ vi /var/lib/pgsql/2ndquadrant_bdr/data/postgresql.conf [下記の修正を加える] listen_addresses = '*' shared_preload_libraries = 'bdr' wal_level = 'logical' track_commit_timestamp = on max_connections = 100 max_wal_senders = 10 max_replication_slots = 10 # Make sure there are enough background worker slots for BDR to run max_worker_processes = 10 log_line_prefix = '%m d=%d p=%p a=%a%q ' # These aren't required, but are useful for diagnosing problems #log_error_verbosity = verbose #log_min_messages = debug1 # Useful options for playing with conflicts #bdr.default_apply_delay=2000 # milliseconds #bdr.log_conflicts_to_table=on
BDRのクライアント認証設定(pg_hba.conf)をします。
$ vi /var/lib/pgsql/2ndquadrant_bdr/data/pg_hba.conf [下記の修正を加える] host all all 192.168.0.10/32 trust host all all 192.168.0.12/32 trust local replication postgres trust host replication postgres 0.0.0.0/0 trust host replication postgres ::1/128 trust
設定が環境しましたらPostgreSQLを起動します。起動時にBDRのバックグランドワーカの登録メッセージが出力される事を確認します。$ pg_ctl start サーバは起動中です。 -bash-4.2$ < 2016-10-19 01:47:01.891 JST >LOG: バックグラウンドワーカ"bdr supervisor"を登録しています < 2016-10-19 01:47:02.041 JST >LOG: ログ出力をログ収集プロセスにリダイレクトし ています < 2016-10-19 01:47:02.041 JST >ヒント: ここからのログ出力はディレクトリ"pg_log"に現れます。以下のコマンドでPostgreSQLの起動状況を確認します。
$ pg_ctl status pg_ctl: サーバが動作中です(PID: 10948) /usr/pgsql-9.4/bin/postgres
BDRの動作確認用のデータベースを作成します。
$ createdb bdrtest以下のコマンドで作成したデータベースへの接続を確認します。
$ psql bdrtest psql (9.4.9) "help" でヘルプを表示します. bdrtest=# \q -bash-4.2$
BDRに必要な拡張モジュールを登録します。
$ psql -U postgres bdrtest =# CREATE EXTENSION btree_gist; =# CREATE EXTENSION bdr;以下のコマンドでインストール済みの拡張モジュールを確認します。
=# \dx インストール済みの拡張の一覧 名前 | バージョン | スキーマ | 説明 ------------+------------+------------+----------------------------------------------- bdr | 1.0.1.0 | pg_catalog | Bi-directional replication for PostgreSQL btree_gist | 1.0 | public | support for indexing common datatypes in GiST plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (3 行)
node1でBDRグループを作成するため、「bdr.bdr_group_create」関数を実行します。
$ psql -U postgres bdrtest =# SELECT bdr.bdr_group_create( local_node_name := 'node1', node_external_dsn := 'host=node1 port=5432 dbname=bdrtest');グループが作成されたことを確認するため、「bdr.bdr_node_join_wait_for_ready」関数を実行します。
=# SELECT bdr.bdr_node_join_wait_for_ready(); bdr_node_join_wait_for_ready ------------------------------ (1 行)
node2でノード登録するために、「bdr.bdr_group_join」関数を実行します。
$ psql -U postgres bdrtest =# SELECT bdr.bdr_group_join( local_node_name := 'node2', node_external_dsn := 'host=node2 port=5432 dbname=bdrtest', join_using_dsn := 'host=node1 port=5432 dbname=bdrtest' );ノードが登録されたことを確認するため、「bdr.bdr_node_join_wait_for_ready」関数を実行します。
=# SELECT bdr.bdr_node_join_wait_for_ready(); bdr_node_join_wait_for_ready ------------------------------ (1 行)
ノードの状態を「bdr.bdr_nodes」テーブルの情報から確認します。
$ psql -U postgres bdrtest =# \x =# SELECT * FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_sysid | 6369044607312522907 node_timeline | 1 node_dboid | 16385 node_status | r node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_init_from_dsn | node_read_only | f -[ RECORD 2 ]------+------------------------------------ node_sysid | 6369045470046586386 node_timeline | 1 node_dboid | 16385 node_status | r node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_init_from_dsn | host=node1 port=5432 dbname=bdrtest node_read_only | f
node1でBDRの簡易動作検証を実施します。node1で作成されたテーブルとデータがnode2にレプリケーションされていることを確認します。$ psql -U postgres bdrtest =# CREATE TABLE t1bdr (c1 INT, PRIMARY KEY (c1)); =# INSERT INTO t1bdr VALUES (1); =# INSERT INTO t1bdr VALUES (2);
node2でBDRの簡易動作検証の結果を確認します。node1と同じテーブルとデータが表示できていれば、構築したBDR環境に問題はありません。node1と同様の簡易動作検証をnode2からも実施して下さい。$ psql -U postgres bdrtest =# SELECT * FROM t1bdr; c1 ---- 1 2 (2 行) =# INSERT INTO t1bdr VALUES (3);
ノードの追加/切り離しをオンライン(DB停止)なしで実行できるか否かを確認します。
本検証では、2台で構成されたBDRクラスタに対して、下記を実施しました。
「ノード切り離し」および「ノード追加」時に他ノード(上図のnode1)にトランザクションを実行し、トランザクションにエラーが発生するか否かを確認しました。
BDRの各ノード間のレプリケーションが正常に動作していることを「bdr.bdr_nodes」テーブルを用いて確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
pgbebchを利用して、検証時に利用するテーブルとデータを作成します。
(node1にて実施) $ pgbench -i -s 10 bdrtest
pgbenchを利用して、ノード1にトランザクションを継続的に実行します。 ノード2を切り離した発生した場合に、ノード1に対して実行したトランザクションにエラーが発生するか否かを確認します。
$ pgbench -h node1 -c 10 -t 100000 bdrtest starting vacuum...end.
ノード2を切り離しするため、「bdr.bdr_part_by_node_names」関数を実行します。
(node1のデータベースに接続) =# SELECT bdr.bdr_part_by_node_names(ARRAY['node2']); bdr_part_by_node_names ------------------------ (1 row)
ノードの切り離し結果を「bdr.bdr_nodes」テーブルを用いて確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | k <-- k(削除)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest
本手順は推奨される手順ではありませんが、削除状態のノードが「bdr.bdr_nodes」テーブルに残っていると削除したノードの追加が実行できないため暫定的な対処です。 BDRのIssuesで「bdr.bdr_connections」のデータも削除する事が提案されていたので、こちらも暫定的な手順ですが実行します。 詳細は下記をご参照下さい。
(node1のデータベースに接続) =# DELETE FROM bdr.bdr_connections USING bdr.bdr_nodes WHERE node_status = 'k' AND (node_sysid, node_timeline, node_dboid) = (conn_sysid, conn_timeline, conn_dboid); =# DELETE FROM bdr.bdr_nodes where node_status = 'k';
ノードの切り離し結果を「bdr.bdr_nodes」テーブルを用いて確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r node_init_from_dsn |
ノード2のデータベースから「bdr.remove_bdr_from_local_node」関数を用いてBDRを削除し、BDR拡張機能を削除します。
(node2のデータベースに接続) =# SELECT bdr.remove_bdr_from_local_node(true); WARNING: forcing deletion of possibly active BDR node NOTICE: removing BDR from node NOTICE: BDR removed from this node. You can now DROP EXTENSION bdr and, if this is the last BDR node on this PostgreSQL instance, remove bdr from shared_preload_libraries. remove_bdr_from_local_node ---------------------------- =# DROP EXTENSION bdr; DROP EXTENSION
手順1で実行したpgbenchにエラーが発生してないことを確認します。本検証ではエラーは発生しませんでした。
切り離したBDRノードを再度追加する場合、既存ノードのデータベースと追加するノードのデータベースのスキーマおよびデータを同期させる必要があります。 ノード間のデータコピーには、論理コピーと物理コピーの2つの手法があります。
項番 | コピー取得 | 説明 | 備考 |
---|---|---|---|
1 | bdr.bdr_group_join 関数実行 | ユーザが指定したノード内データベースのスキーマとデータダンプを取得 | pg_dumpコマンドに相当 |
2 | bdr_init_copyコマンド | ユーザが指定したノード上の全てのデータベースのコピーを取得 | pg_basebackupコマンドに相当 |
■論理コピーによるノード追加
pgbenchを利用して、ノード1にトランザクションを継続的に実行します。 ノード2に復旧する際に、ノード1に対して実行したトランザクションの停止が必要か否かを確認します。
$ pgbench -h node1 -c 10 -t 100000 bdrtest starting vacuum...end.
ノード2上で動作するPostgreSQLを起動させます。
$ pg_ctl start
BDRで利用したデータベースを削除します。
(node2のデータベースに接続) =# DROP DATABASE bdrtest ; ※ 接続が残っており、削除できない場合はPostgreSQLを再起動 DROP DATABASE
BDRで利用するデータべースを再度作成します。
(node2のデータベースに接続) =# CREATE DATABASE bdrtest;
無効化したBDRを再度有効化します。
(node2のデータベースに接続) =# CREATE EXTENSION btree_gist; CREATE EXTENSION =# CREATE EXTENSION bdr; CREATE EXTENSION
ノードを追加(復旧)させるため、「bdr.bdr_group_join」関数を実行します。
(node2のデータベースに接続) =# SELECT bdr.bdr_group_join( local_node_name := 'node2', node_external_dsn := 'host=node2 port=5432 dbname=bdrtest', join_using_dsn := 'host=node1 port=5432 dbname=bdrtest' ); bdr_group_join ---------------- (1 行)
ノードが追加されたことを確認するため、「bdr.bdr_node_join_wait_for_ready」関数を実行します。
(node2のデータベースに接続) =# SELECT bdr.bdr_node_join_wait_for_ready(); ※ トランザクションが実行中の場合、上記関数の結果が戻りません。node1のPostgreSQLログファイルに下記メッセージが出力された後、復旧の処理が開始されません。
(node1のログメッセージ抜粋) LOG: logical decoding found initial starting point at 0/BB399BF0 DETAIL: 10 transactions need to finish.pgbenchコマンドで実行中のトランザクションを停止すると、下記ログメッセージが出力され、復旧処理が開始されます。
(node1のログメッセージ抜粋) LOG: logical decoding found consistent point at 0/B2895648 DETAIL: There are no running transactions. LOG: exported logical decoding snapshot: "00046FE0-1" with 0 transaction IDs
ノード2が追加されたことを「bdr.bdr_nodes」テーブルの情報から確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
■ 物理コピーによるノード追加
pgbenchを利用して、ノード1にトランザクションを継続的に実行します。 ノード2に追加する際に、ノード1に対して実行したトランザクションの停止が必要か否かを確認します。
$ pgbench -h node1 -c 10 -t 100000 bdrtest starting vacuum...end.
ノード2上で動作するPostgreSQLが停止していることを確認します。
(node2にて実施) $ pg_ctl status pg_ctl: no server running
ノード2上で「bdr_init_copy」コマンドを実行し、ノード1上のコピーを取得します。
(node2にて実施) $ rm -rf $PGDATA/* $ bdr_init_copy -D $PGDATA -n node2 -h node1 -p 5432 -d bdrtest --local-host=node2 --local-port=5432 --local-dbname=bdrtest bdr_init_copy: starting ... Getting remote server identification ... Detected 1 BDR database(s) on remote server Updating BDR configuration on the remote node: bdrtest: creating replication slot ... bdrtest: creating node entry for local node ... Creating base backup of the remote node... 50357/50357 kB (100%), 1/1 tablespace Creating restore point on remote node ... Bringing local node to the restore point ... トランザクションログをリセットします。 Initializing BDR on the local node: bdrtest: adding the database to BDR cluster ... All donenode1のPostgreSQLログファイルに下記メッセージが出力された後、追加の処理が開始されません。
(node1のログメッセージ抜粋) LOG: logical decoding found initial starting point at 0/BB399BF0 DETAIL: 10 transactions need to finish.pgbenchコマンドで実行中のトランザクションを停止すると、下記ログメッセージが出力され、復旧処理が開始されます。
(node1のログメッセージ抜粋 LOG: logical decoding found consistent point at 0/B2895648 DETAIL: There are no running transactions. STATEMENT: SELECT pg_create_logical_replication_slot('bdr_25434_6369931070716042622_2_25434__', 'bdr');
ノード2が追加されたことを「bdr.bdr_nodes」テーブルの情報から確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
切り離したノードの情報がシステムカタログに残ってしまうため、データ操作が禁止されているシステムカタログのデータ削除が必要でした。
システムカタログのデータ操作は禁止されていますが、切り離したノードの情報がシステムカタログに残っている場合、 ノード追加時に下記のメッセージが出力され、ノードが追加が実施できないため、本検証では暫定対処としてシステムカタログのデータ削除を実施しております。
ERROR: System identification mismatch between connection and slot 詳細: Connection for bdr (6369029438929838565,1,16385,) resulted in slot on node bdr (6369034270871134883,2,16385,) instead of expected node LOG: ワーカプロセス: bdr db: bdrtest (PID 17232)は終了コード1で終了しました
ノード追加にはプロセスの再起動は必要ありませんが、トランザクションの停止が必要でした。
BDRに実装されたグローバルシーケンスの利用方法について確認します。 グルーバルシーケンスを利用することでノード毎に払い出されるシーケンス番号を独立させ、ノード間のシーケンス番号の競合を防ぐことができるか否かを確認します。
BDRの各ノード間のレプリケーションが正常に動作していることを「bdr.bdr_nodes」テーブルを用いて確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
node1のデータベースに接続し、serial型を列に持つgstestテーブルを作成します。 serial型を持つテーブルを作成すると、自動的にシーケンスが作成されます。 グローバルシーケンス作成のため、テーブル作成時に「SET LOCAL default_sequenceam = 'bdr';」を指定する必要があります。
(node1のデータベースに接続) =# BEGIN; SET LOCAL default_sequenceam = 'bdr'; <-- グローバルシーケンス作成用の設定 CREATE TABLE gstest ( id serial primary key, hogehoge text ); COMMIT; 参考. 上記DDL実行時にnode1に出力されるログメッセージ LOG: DDL LOCK TRACE: attempting to acquire in mode <ddl_lock> for (bdr (xxxxxxxx)) -------- STATEMENT: CREATE TABLE gstest ( id serial primary key, hogehoge text ); LOG: DDL LOCK TRACE: attempting to acquire in mode <write_lock> (upgrading from <ddl_lock>) for (bdr (xxxxxxxxx,)) >STATEMENT: CREATE TABLE gstest ( id serial primary key, hogehoge text ); --------
node1およびnode2のデータベースに接続し、グローバルシーケンスが作成されたことを確認します。
(node1のデータベースに接続) =# \ds List of relations -[ RECORD 1 ]--------- Schema | public Name | gstest_id_seq Type | sequence Owner | postgres(node2のデータベースに接続) =# \ds List of relations -[ RECORD 1 ]--------- Schema | public Name | gstest_id_seq Type | sequence Owner | postgres※ 下記のSQLで詳細情報(pg_classの情報)を確認可能です。
=# SELECT * FROM pg_class INNER JOIN pg_seqam ON (pg_class.relam = pg_seqam.oid) WHERE pg_seqam.seqamname = 'bdr' AND relkind = 'S';
node1のデータベースに接続し、1件のデータを投入します。
(node1のデータベースに接続) =# INSERT INTO gstest(hogehoge) VALUES ('test1'); INSERT 0 1node2のデータベースに接続し、1件のデータを投入します。
(node2のデータベースに接続) =# INSERT INTO gstest(hogehoge) VALUES ('test2'); INSERT 0 1
node1およびnode2のシーケンスの値に競合が発生していないことを確認します。 本検証では、node1に2が割り当てられ、node2には100001が割り当てられました。
(node1のデータベースに接続) =# SELECT * FROM gstest; id | hogehoge --------+---------- 2 | test1 <-- node1には2のシーケンスが割り当てられる 100001 | test2 <-- node2には100001のシーケンスが割り当てられる (2 rows)
node1に200,000件のデータを投入し、シーケンスの値が100,000を超えた場合、node2の100,001と競合しないか否かを確認しました。
(node1において実施) $ cat /tmp/gs.sh #!/bin/sh for i in `seq 1 200000` do psql -d bdrtest -c "INSERT into gstest(hogehoge) VALUES ('test${i}');" done $ sh /tmp/gs.sh INSERT 0 1 [省略]
node1のシーケンスが100,000を利用したのち、node1に割り振られた値を確認しました。
(node1のデータベースに接続) =# SELECT * FROM gstest WHERE 99999 <= id; id | hogehoge --------+------------ 99999 | test99997 100000 | test99998 100001 | test2 150001 | test99999 -< 100,000を超えると、node1のシーケンスが150,001に割り振られる。 150002 | test100000 150003 | test100001 150004 | test100002 150005 | test100003 150006 | test100004
グローバルシーケンスを用いることでノード間のシーケンス番号の競合を抑止できることが確認できました。 グルーバルシーケンスについてはいくつかの制限事項がマニュアルに記載されておりますので、注意して下さい。
選択的レプリケーションについて記載します。
テーブル単位での選択的レプリケーションの可否を確認します。 選択的レプリケーションの可否は互いのデータベース間での任意のテーブルのデータ状態を元に判断します。 また、選択的レプリケーションを実現する際の変更手順を明確にする事を目的とします。
選択的レプリケーション検証内容
選択的レプリケーションの動作確認の為に、下記の検証環境を構築します。
(1)データベース作成
テストを実施する為のデータベースを作成します。
(node1,node2において実施) =# CREATE DATABASE rep_test;(2)BDRの有効化
作成したデータベース上でBDR機能の有効化を実施します。
(node1,node2のデータベースにおいて実施) =# CREATE EXTENSION btree_gist; =# CREATE EXTENSION bdr;(3)BDRグループの作成
BDRノードのクラスタに「bdr.bdr_group_create」を利用して最初のノードを作成します。
(node1のデータベースに接続) =# SELECT bdr.bdr_group_create( local_node_name := 'node1', node_external_dsn := 'host=node1 port=5432 dbname=rep_test', replication_sets:= ARRAY['default','node1']); ※ replications_retにnode1を追加(4)ノード登録(node2)
既存のBDRノードのクラスタに「bdr.bdr_group_join」を利用してノードを登録します。 これにより、全てのノード間でレプリケーションが開始されます。
(node2のデータベースに接続) =# SELECT bdr.bdr_group_join( local_node_name := 'node2', node_external_dsn := 'host=node2 port=5432 dbname=rep_test', join_using_dsn := 'host=node1 port=5432 dbname=rep_test', replication_sets:= ARRAY['default','node2']); ※ replications_retにnode2を追加(5)ノードの状態確認
BDRグループ内のノードのメンバシップを「bdr.bdr_nodes」を利用して確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------- node_name | node1 node_local_dsn | host=node1 port=5432 dbname=rep_test node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------- node_name | node2 node_local_dsn | host=node2 port=5432 dbname=rep_test node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=rep_test <-- node1から作成されていることを確認(6)レプリケーションセットの確認
指定したノードのレプリケーションセットを「bdr.connection_get_replication_sets」を利用して確認します。
(node1のデータベースに接続) =# SELECT bdr.connection_get_replication_sets('node1'); -[ RECORD 1 ]-------------------+---------------- connection_get_replication_sets | {default,node1} <-- node1が追加されていること =# SELECT bdr.connection_get_replication_sets('node2'); -[ RECORD 1 ]-------------------+---------------- connection_get_replication_sets | {default,node2} <-- node2が追加されていること
まずは、通常の双方向レプリケーション動作を確認します。 片方のテーブルにデータを挿入した時にもう一方のテーブルにもデータが挿入されています。
(1)テーブル(all_rep)作成
テストを実施する為のテーブル(all_rep)を作成します。
(node1のデータベースに接続) =# CREATE TABLE all_rep(id int, PRIMARY KEY(id)); CREATE TABLE(2)レプリケーションセット確認
指定したテーブルのレプリケーションセットを「bdr.connection_get_replication_sets」を利用して確認します。
(node1のデータベースに接続) =# SELECT bdr.table_get_replication_sets('all_rep'); -[ RECORD 1 ]--------------+-------------- table_get_replication_sets | {default,all} <-- defaultが含まれていること(3)データ追加
node1のテーブルにデータを挿入します。
(node1のデータベースに接続) =# INSERT INTO all_rep VALUES (1); INSERT 0 1(4)レプリケーション確認
node2のテーブルにもデータがレプリケーションされている事を確認します。
(node2のデータベースに接続) =# SELECT id FROM all_rep; id ---- 1 <-- データがレプリケーションされていること (1 row)
次に、選択的レプリケーション動作を確認します。 指定したテーブルではレプリケーション動作が行われなくなりますので、任意のテーブルのみレプリケーションさせる事が出来ます。
(1)テーブル(node1_only)の作成
テストを実施する為のテーブル(node1_only)を作成します。
(node1のデータベースに接続) =# CREATE TABLE node1_only(id int, PRIMARY KEY(id)); CREATE TABLE(2)レプリケーションセット変更
指定したテーブルのレプリケーションセットを「bdr.table_set_replication_sets」を利用して'node1'に設定し、レプリケーション対象を'node1'のみにします。 設定したレプリケーションセットを確認するには、「bdr.table_get_replication_sets」を利用します。
(node1のデータベースに接続) =# SELECT bdr.table_set_replication_sets('node1_only', ARRAY['node1']); -[ RECORD 1 ]--------------+- table_set_replication_sets | =# SELECT bdr.table_get_replication_sets('node1_only'); -[ RECORD 1 ]--------------+------------ table_get_replication_sets | {node1,all} <-- node1が追加されていること(3)データ追加
node1のテーブルにデータを挿入します。
(node1のデータベースに接続) =# INSERT INTO node1_only VALUES (1); INSERT 0 1(4)レプリケーション確認
node2のテーブルにはデータがレプリケーションされていない事を確認します。
(node2のデータベースに接続) =# SELECT id FROM node1_only; id ---- <-- レプリケーションされていないこと (0 行)
同様に、もう一方からの選択的レプリケーション動作も確認します。
(1)テーブル(node2_only)の作成
テストを実施する為のテーブル(node2_only)を作成します。
(node2のデータベースに接続) =# CREATE TABLE node2_only(id int, PRIMARY KEY(id)); CREATE TABLE(2)レプリケーションセット変更
指定したテーブルのレプリケーションセットを「bdr.table_set_replication_sets」を利用して'node2'に設定し、レプリケーション対象を'node2'のみにします。 設定したレプリケーションセットを確認するには、「bdr.table_get_replication_sets」を利用します。
(node2のデータベースに接続) =# SELECT bdr.table_set_replication_sets('node2_only', ARRAY['node2']); -[ RECORD 1 ]--------------+- table_set_replication_sets | =# SELECT bdr.table_get_replication_sets('node2_only'); -[ RECORD 1 ]--------------+------------ table_get_replication_sets | {node2,all} <-- node2が追加されていること(3)データ追加
node2のテーブルにデータを挿入します。
(node2のデータベースに接続) =# INSERT INTO node2_only VALUES (1); INSERT 0 1(4)レプリケーション確認
node1のテーブルにはデータがレプリケーションされていない事を確認します。
(node1のデータベースに接続) =# SELECT id FROM node2_only; id ---- <-- レプリケーションされていないこと (0 行)
今回の検証結果では、レプリケーションセットに任意のノードを指定する事で選択的レプリケーションが実現される事が確認出来ました。 また、選択的レプリケーション実現の為の設定変更手順についても確認出来ました。
ただし、レプリケーションセットにテーブルを追加する場合は、過去のデータまで反映しないので、手動で同期が必要です。
選択的レプリケーションの主な注意事項は下記になります。
BDRはマルチマスタ構成する各ノードに対して、参照処理と更新処理を実行することが可能です。 複数ノードに対して同時に更新処理が実施された場合、各ノードに対して実行された更新処理が競合する事象が発生する場合があります。
BDR競合が発生した場合、最後の更新処理が適用されます(last_update_wins)。また、競合結果はテーブル「bdr.bdr_conflict_history」で確認可能です。
下記を明らかにするため検証を実施しました。
BDR動作検証概要図
下表の競合発生時の動作を検証しました。
項番 | 分類 | 説明 |
---|---|---|
1 | PRIMARY KEYまたはUNIQUE制約 | 2つの操作が同じUNIQUE KEYを持つ行に影響を及ぼす
行の競合を検証します。
|
2 | 外部キー制約 | 外部キー制約が定義されたテーブルにおいて、
制約に違反するデータ削除によって引き起こされる競合を検証します。
|
3 | 排他制約 | BDRでは排他制約をサポートしていないために、
排他制約において競合が発生した場合を検証します。
|
4 | グローバルなデータ | ノードのグローバル(PostgreSQLシステム全体)の
データ(ロールなど)が異なる場合での競合を検証します。
|
5 | ロックの競合とデッドロックの中断 | BDR適用プロセスとロックの競合について検証します。 |
6 | その他 | 自動的に解決出来ないデータの相違が発生した場合に
手動で調整を行う方法を検証します。
|
競合発生時にテーブル「bdr.bdr_conflict_history」に情報を記録するため、bdr.log_conflicts_to_tableパラメータがonに設定されていることを確認します。
※ offになっていたら、postgresql.confに「bdr.log_conflicts_to_table=on」を追記し、再読み込みを実行して下さい。
node1/node2のデータベースに接続し、パラメータを確認するために下記コマンドを実行します。
(node1にて実施) =# SHOW bdr.log_conflicts_to_table; bdr.log_conflicts_to_table ---------------------------- on (1 行)(node2にて実施) =# SHOW bdr.log_conflicts_to_table; bdr.log_conflicts_to_table ---------------------------- on (1 行)
ネットワーク遅延などの実際の環境を想定して検証を行うため、bdr.default_apply_delayパラメータを設定します。 このパラメータを設定する事でレプリケーションの反映を設定した時間(ミリ秒)遅らせる事が出来ます。
※ 今回の検証では、競合の発生を確認しやすくするために"2s"を設定しています。
node1/node2のデータベースに接続し、パラメータを確認するために下記コマンドを実行します。
(node1にて実施) =# SHOW bdr.default_apply_delay; bdr.default_apply_delay ---------------------------- 2s (1 行)(node2にて実施) =# SHOW bdr.default_apply_delay; bdr.default_apply_delay ---------------------------- 2s (1 行)
BDRの各ノード間のレプリケーションが正常に動作していることを「bdr.bdr_nodes」を利用して確認します。
(node1にて実施) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
pgbebchを利用して、検証時に利用するテーブルとデータを作成します。
(node1にて実施) $ pgbench -i -s 10 bdrtest $ psql -h node1 -d bdrtest =# INSERT INTO pgbench_accounts (aid, bid, abalance) VALUES (1000001, 1, 0);
■ 競合概要
最も一般的な競合として、2つの異なるノードのINSERTが同じPRIMARY KEYの値(または、単一のUNIQE制約の値)を持つデータを挿入するケースを検証しました。
■ 検証結果
タイムスタンプに従い、最後にデータが作成された方が保持されました。
■ 競合発生手順
各ノードからそれぞれINSERTを実行し、INSERT vs INSERT の競合を発生させます。
(node1のデータベースへSQLを投入) =# INSERT INTO pgbench_accounts (aid, bid, abalance) VALUES (1000002, 2, 0); (node2のデータベースへSQLを投入) =# INSERT INTO pgbench_accounts (aid, bid, abalance) VALUES (1000002, 3, 0);競合ログを「bdr.bdr_conflict_history」を利用して確認し、競合の発生を確認します。
※ 競合履歴テーブルでは競合ログが出力されている側のサーバをローカル、反対側をリモートとして表示します。 よって、それぞれのサーバで実行されたトランザクションをローカルトランザクション、リモートトランザクションとして説明します。
(node1またはnode2の競合履歴テーブルに競合ログが出力) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1; -[ RECORD 1 ]------------+--------------------------------------------------- conflict_id | 48 local_node_sysid | 6371561413984311673 local_conflict_xid | 3944663 local_conflict_lsn | 3/765949B8 local_conflict_time | 2017-01-25 10:36:03.787698+09 object_schema | public object_name | pgbench_accounts remote_node_sysid | 6371553825031594764 remote_txid | 3960113 remote_commit_time | 2017-01-25 10:32:48.772254+09 remote_commit_lsn | 2/CA1AD950 conflict_type | insert_insert conflict_resolution | last_update_wins_keep_local local_tuple | {"aid":1000002,"bid":3,"abalance":0,"filler":null} remote_tuple | {"aid":1000002,"bid":2,"abalance":0,"filler":null} local_tuple_xmin | 3944662 local_tuple_origin_sysid | error_message | error_sqlstate | error_querystring | error_cursorpos | error_detail | error_hint | error_context | error_columnname | error_typename | error_constraintname | error_filename | error_lineno | error_funcname |それぞれのテーブルの状態を確認し、競合発生時の処理を確認します。
※ 後にデータが作成されたローカルトランザクションが保持されています。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler --------+-----+----------+-------- 1000001 | 1 | 0 | 1000002 | 3 | 0 | (2 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler --------+-----+----------+-------- 1000001 | 1 | 0 | 1000002 | 3 | 0 | (2 行) ※ last_update_wins_keep_local : 最新のタイムスタンプであるローカル側の更新が適用
■ 競合概要
一つのノードでINSERTしたデータともう片方のノードでUPDATEしたデータが同じPRIMARY KEYの値を持つケースを検証しました。
■ 検証結果
INSERT/UPDATEの競合が発生した場合、競合を解消するためオペレータ側でのデータ操作が必要となり、注意が必要です。
■ 競合発生手順
下記テーブルを初期状態とします。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler --------+-----+----------+-------- 1000001 | 1 | 0 | (1 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler --------+-----+----------+-------- 1000001 | 1 | 0 | (1 行)各ノードからそれぞれINSERT/UPDATEを実行し、INSERT vs UPDATE の競合を発生させます。
(node1のデータベースへSQLを投入) =# INSERT INTO pgbench_accounts (aid, bid, abalance) VALUES (1000002, 4, 0); (node2のデータベースへSQLを投入) =# UPDATE pgbench_accounts SET aid = 1000002 WHERE aid = 1000001;競合ログを「bdr.bdr_conflict_history」を利用して確認し、競合の発生を確認します。
(node1またはnode2の競合履歴テーブルに競合ログが出力) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1; -[ RECORD 1 ]------------+--------------------------------------------------- conflict_id | 49 local_node_sysid | 6371561413984311673 local_conflict_xid | 3944715 local_conflict_lsn | 3/81295CF8 local_conflict_time | 2017-01-25 10:36:46.780822+09 object_schema | public object_name | pgbench_accounts remote_node_sysid | 6371553825031594764 remote_txid | 3960133 remote_commit_time | 2017-01-25 10:33:31.640126+09 remote_commit_lsn | 2/D1CE6F00 conflict_type | insert_insert conflict_resolution | last_update_wins_keep_local local_tuple | {"aid":1000002,"bid":1,"abalance":0,"filler":null} remote_tuple | {"aid":1000002,"bid":4,"abalance":0,"filler":null} local_tuple_xmin | 3944714 local_tuple_origin_sysid | error_message | error_sqlstate | error_querystring | error_cursorpos | error_detail | error_hint | error_context | error_columnname | error_typename | error_constraintname | error_filename | error_lineno | error_funcname |それぞれのテーブルの状態を確認し、競合発生時の処理を確認します。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | 1000002 | 4 | 0 | (2 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000002 | 1 | 0 | (1 行)PRIMARY KEYのUPDATEで発生した競合でリモート側では一意制約違反のために適用プロセスがデータ反映できず、 ローカル側では論理レプリケーション中に接続が切断され、データの整合性がとれなくなる状態が確認されました。
(node1のデータベースログ) LOG: starting background worker process "bdr (6371553825031594764,1,43905,)->bdr (6371561413984311673,2," ERROR: duplicate key value violates unique constraint "pgbench_accounts_pkey" DETAIL: Key (aid)=(1000002) already exists. CONTEXT: apply UPDATE from remote relation public.pgbench_accounts in commit 1/2F279190, xid 3941360 commited at 2017-01-10 11:00:22.736985+09 (action #2) from node (6371561413984311673,2,43865) LOG: worker process: bdr (6371553825031594764,1,43905,)->bdr (6371561413984311673,2, (PID 21287) exited with exit code 1 (node2のデータベースログ) bdr (6371553825031594764,1,43905,):receive LOG: starting logical decoding for slot "bdr_43865_6371553825031594764_1_43905__" bdr (6371553825031594764,1,43905,):receive DETAIL: streaming transactions committing after 1/2F279190, reading WAL from 1/2F279068 bdr (6371553825031594764,1,43905,):receive LOG: logical decoding found consistent point at 1/2F279068 bdr (6371553825031594764,1,43905,):receive DETAIL: There are no running transactions. bdr (6371553825031594764,1,43905,):receive LOG: could not receive data from client: Connection reset by peer bdr (6371553825031594764,1,43905,):receive LOG: unexpected EOF on standby connection ※ 競合するタプルをローカル側から手動で削除するか、新しいリモートタプルと競合しなくなるようにUPDATEする必要がある上記状態では、ローカル側からのレプリケーションが実施されない状態が続くため、解消するために、ローカル側のデータをUPDATEします。
(node1のデータベースへSQLを投入) =# UPDATE pgbench_accounts SET aid = 1000003 WHERE aid = 1000002; =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | 1000003 | 4 | 0 | (2 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000002 | 1 | 0 | (1 行) ※ 競合は解消され、次のデータからレプリケーションが始まるがデータの整合性で問題あり
■ 競合概要
一つのノードでUPDATEしたデータともう片方のノードでDELETEしたデータが同じPRIMARY KEYの値を持つケースを検証しました。
■ 検証結果
UPDATE/DELETEが競合した場合、UPDATEが破棄されました。
■ 競合発生手順
下記テーブルを初期状態とします。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | (1 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | (1 行)各ノードからそれぞれUPDATE/DELETEを実行し、UPDATE vs DELETE の競合を発生させます。
(node1のデータベースにSQLを投入) =# UPDATE pgbench_accounts SET bid = 4 WHERE aid = 1000001; (node2のデータベースにSQLを投入) =# DELETE FROM pgbench_accounts WHERE aid = 1000001;競合ログを「bdr.bdr_conflict_history」を利用して確認し、競合の発生を確認します。
(node1またはnode2の競合履歴テーブルに競合ログが出力) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1; -[ RECORD 1 ]------------+--------------------------------------------------- conflict_id | 51 local_node_sysid | 6371561413984311673 local_conflict_xid | 0 local_conflict_lsn | 3/8BF7BA38 local_conflict_time | 2017-01-25 10:37:45.561678+09 object_schema | public object_name | pgbench_accounts remote_node_sysid | 6371553825031594764 remote_txid | 3960157 remote_commit_time | 2017-01-25 10:34:30.564936+09 remote_commit_lsn | 2/D9831CC0 conflict_type | update_delete conflict_resolution | skip_change local_tuple | remote_tuple | {"aid":1000001,"bid":4,"abalance":0,"filler":null} local_tuple_xmin | local_tuple_origin_sysid | error_message | error_sqlstate | error_querystring | error_cursorpos | error_detail | error_hint | error_context | error_columnname | error_typename | error_constraintname | error_filename | error_lineno | error_funcname |それぞれのテーブルの状態を確認し、競合発生時の処理を確認します。
※ DELETE後のUPDATEが破棄されています。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler -----+-----+----------+-------- (0 行) (node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler -----+-----+----------+-------- (0 行) ※ skip_change : 変更を無視し、破棄された
■ 競合概要
一つのノードでINSERTしたデータがもう片方のノードでDELETEされたデータと同じPRIMARY KEYの値を持つケースを検証しました。
■ 検証結果
DELETEの処理が破棄されました。競合発生の状態についてはシステムカタログから確認することができませんでした。
■ 競合発生手順
下記テーブルを初期状態とします。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | (1 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | (1 行)各ノードからそれぞれINSERT/DELETEを実行し、INSERT vs DELETE の競合を発生させます。
(node1のデータベースにSQLを投入) =# INSERT INTO pgbench_accounts (aid, bid, abalance) VALUES (1000002, 5, 0); (node2のデータベースにSQLを投入) =# DELETE FROM pgbench_accounts WHERE aid = 1000002;競合ログを「bdr.bdr_conflict_history」を利用して確認し、競合の発生を確認します。
※ 競合は確認出来ませんでした。
(node1またはnode2の競合履歴テーブルに競合ログが出力されない) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1;それぞれのテーブルの状態を確認します。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | 1000002 | 5 | 0 | (2 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | 1000002 | 5 | 0 | (2 行)
■ 競合概要
2つの異なるノードで実行されたDELETEが同じPRIMARY KEYの値を持つデータを削除するケースを検証しました。
■ 検証結果
DELETE/DELETEが競合した場合、片方のDELETEが無視され処理を完了しました。
■ 競合発生手順
下記テーブルを初期状態とします。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | (1 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler ---------+-----+----------+-------- 1000001 | 1 | 0 | (1 行)各ノードからそれぞれDELETEを実行し、DELETE vs DELETE の競合を発生させます。
(node1のデータベースにSQLを投入) =# DELETE FROM pgbench_accounts WHERE aid = 1000001; (node2のデータベースにSQLを投入) =# DELETE FROM pgbench_accounts WHERE aid = 1000001;競合ログを「bdr.bdr_conflict_history」を利用して確認し、競合の発生を確認します。
(node1またはnode2の競合履歴テーブルに競合ログが出力) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1; -[ RECORD 1 ]------------+--------------------------------------------------------- conflict_id | 52 local_node_sysid | 6371561413984311673 local_conflict_xid | 0 local_conflict_lsn | 3/A193CA88 local_conflict_time | 2017-01-25 10:39:07.794699+09 object_schema | public object_name | pgbench_accounts remote_node_sysid | 6371553825031594764 remote_txid | 3960197 remote_commit_time | 2017-01-25 10:35:52.78243+09 remote_commit_lsn | 2/E8E9A388 conflict_type | delete_delete conflict_resolution | skip_change local_tuple | remote_tuple | {"aid":1000001,"bid":null,"abalance":null,"filler":null} local_tuple_xmin | local_tuple_origin_sysid | error_message | error_sqlstate | error_querystring | error_cursorpos | error_detail | error_hint | error_context | error_columnname | error_typename | error_constraintname | error_filename | error_lineno | error_funcname |それぞれのテーブルの状態を確認し、競合発生時の処理を確認します。
※ ローカル側のノードからのDELETEが無視されています。
(node1のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler -----+-----+----------+-------- (0 行) (node2のデータベースを確認) =# SELECT * FROM pgbench_accounts WHERE aid >= 1000001; aid | bid | abalance | filler -----+-----+----------+-------- (0 行) ※ skip_change : 変更を無視し、破棄された
■ 競合概要
1つのノードで外部キー制約が定義されたテーブルに対してデータを挿入し、上記データ挿入がもう片方のノードに反映される前に、 もう片方のノードにおいて外部キーの参照先であるテーブルのデータを削除することで、外部キー制約が定義されたテーブルへのデータ挿入と、外部キー参照先テーブルに対するデータ削除を競合させます。
■ 検証結果
外部キー制約に違反する処理が実施された場合でもBDRではエラーにならず、データが外部キー制約違反の状態になりました。
■ 競合発生手順
下記の親子関係を持つテーブルを作成します。
(node1のデータベースに接続) =# CREATE TABLE parent(id integer primary key); =# CREATE TABLE child(id integer primary key, parent_id integer not null references parent(id)); =# INSERT INTO parent(id) VALUES (1), (2); =# INSERT INTO child(id, parent_id) VALUES (11, 1), (12, 2);各ノードからそれぞれINSERT/DELETEを実行し、外部キー制約の競合を発生させます。
※ node1からのINSERTはnode2では、子テーブルで参照されている親が存在せず、 node2のDELETEはnode1で参照している親データを削除しようとしているので、それぞれ適用出来なくなります。
(node1のデータベースにSQLを投入) =# INSERT INTO child(id, parent_id) VALUES (21, 2); (node2のデータベースにSQLを投入) =# DELETE FROM child WHERE parent_id = 2; =# DELETE FROM parent WHERE id = 2;それぞれのテーブルの状態を確認し、競合発生時の処理を確認します。
(node1のデータベースを確認) =# SELECT * FROM child; id | parent_id ----+----------- 11 | 1 21 | 2 (2 行) =# SELECT * FROM parent; id ---- 1 (1 行)(node2のデータベースを確認) =# SELECT * FROM child; id | parent_id ----+----------- 11 | 1 21 | 2 (2 行) =# SELECT * FROM parent; id ---- 1 (1 行) ※ 外部キー制約違反のデータがchildテーブルに存在する。
■ 競合概要
排他な関係である2つのデータを異なるノードから同時にテーブルにINSERTし、ノード間のデータに競合が発生するケースを検証します。
■ 検証結果
一つのノードでINSERTされたデータをもう片方のノードに反映させる際に排他制約違反が発生し、ノード間の接続が切断されます。 競合を解消するためにはオペレータでのデータ操作が必要となるため注意が必要です。
■ 競合発生手順
下記の排他制約を持つテーブルを作成します。
(node1のデータベースにSQLを投入) =# CREATE TABLE sample (aid integer primary key, range daterange, price integer); =# ALTER TABLE sample ADD EXCLUDE USING gist (price WITH =, range WITH &&);各ノードからそれぞれINSERTを実行し、排他制約の競合を発生させます。
※ [2012-04-18 ~ 2012-04/20]の期間が競合しているために排他制約違反を起こしています。
(node1のデータベースにSQLを投入) =# INSERT INTO sample VALUES(1, '[2012-04-16, 2012-04-20]', 10000); (node2のデータベースにSQLを投入) =# INSERT INTO sample VALUES(2, '[2012-04-18, 2012-04-23]', 10000);(node1またはnode2の競合履歴テーブルに競合ログが出力されない) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1;それぞれのテーブルの状態を確認し、競合発生時の処理を確認します。
※ 他のノードで実行されたデータ更新を反映する際に、制約違反が発生し、BDRによるノード間の接続が切断されます。
(node1のデータベースを確認) =# SELECT * FROM sample; aid | range | price -----+-------------------------+------- 1 | [2012-04-16,2012-04-21) | 10000 (1 行) (node2のデータベースを確認) =# SELECT * FROM sample; aid | range | price -----+-------------------------+------- 2 | [2012-04-18,2012-04-24) | 10000 (1 行)(node1のデータベースログ) bdr (6371561413984311673,2,42879,):receive LOG: starting logical decoding for slot "bdr_42892_6371561413984311673_2_42879__" bdr (6371561413984311673,2,42879,):receive DETAIL: streaming transactions committing after 1/15D41640, reading WAL from 1/15D413B0 bdr (6371561413984311673,2,42879,):receive LOG: logical decoding found consistent point at 1/15D413B0 bdr (6371561413984311673,2,42879,):receive DETAIL: There are no running transactions. bdr (6371561413984311673,2,42879,):receive LOG: unexpected EOF on standby connection LOG: starting background worker process "bdr (6371553825031594764,1,42892,)->bdr (6371561413984311673,2," ERROR: conflicting key value violates exclusion constraint "sample_price_range_excl" DETAIL: Key (price, range)=(10000, [2012-04-18,2012-04-24)) conflicts with existing key (price, range)=(10000, [2012-04-16,2012-04-21)). CONTEXT: apply INSERT from remote relation public.sample in commit 1/194FBC20, xid 3940666 commited at 2017-01-06 16:12:28.275376+09 (action #2) from node (6371561413984311673,2,42879) LOG: worker process: bdr (6371553825031594764,1,42892,)->bdr (6371561413984311673,2, (PID 27843) exited with exit code 1 (node2のデータベースログ) LOG: starting background worker process "bdr (6371561413984311673,2,42879,)->bdr (6371553825031594764,1," ERROR: conflicting key value violates exclusion constraint "sample_price_range_excl" DETAIL: Key (price, range)=(10000, [2012-04-16,2012-04-21)) conflicts with existing key (price, range)=(10000, [2012-04-18,2012-04-24)). CONTEXT: apply INSERT from remote relation public.sample in commit 1/15D41640, xid 3957701 commited at 2017-01-06 16:09:10.299493+09 (action #2) from node (6371553825031594764,1,42892) LOG: worker process: bdr (6371561413984311673,2,42879,)->bdr (6371553825031594764,1, (PID 29311) exited with exit code 1 bdr (6371553825031594764,1,42892,):receive LOG: starting logical decoding for slot "bdr_42879_6371553825031594764_1_42892__" bdr (6371553825031594764,1,42892,):receive DETAIL: streaming transactions committing after 1/194FB9D0, reading WAL from 1/194FB700 bdr (6371553825031594764,1,42892,):receive LOG: logical decoding found consistent point at 1/194FB700 bdr (6371553825031594764,1,42892,):receive DETAIL: There are no running transactions. bdr (6371553825031594764,1,42892,):receive LOG: unexpected EOF on standby connection ※ 各ノードで制約違反が発生し、BDRによる接続が切断されてしまう。解消のためリモートタプルが競合するローカルタプルを削除または変更する制約違反の状態を解消するために、両側のデータをUPDATEします。
(node1のデータベースにSQLを投入) =# UPDATE sample SET range = '[2012-04-11,2012-04-16]' WHERE aid = 1; (node2のデータベースにSQLを投入) =# UPDATE sample SET range = '[2012-04-23,2012-04-29]' WHERE aid = 2;(node1のデータベースを確認) =# SELECT * FROM sample; aid | range | price -----+-------------------------+------- 1 | [2012-04-11,2012-04-17) | 10000 2 | [2012-04-23,2012-04-30) | 10000 (2 行) (node2のデータベースを確認) =# SELECT * FROM sample; aid | range | price -----+-------------------------+------- 2 | [2012-04-23,2012-04-30) | 10000 1 | [2012-04-11,2012-04-17) | 10000 (2 行)
■ 競合概要
ロール(グローバルデータ)の情報がノード間で異なる状態で、他のノードに存在しないロールを利用した場合に発生する競合のケースを検証しました。
■ 検証結果
レプリケーション先のノードに同名のロールが存在しない場合、エラーになります。 エラーを解消するには、オペレータ側での操作が必要となるため注意が必要です。
■ 競合発生手順
新規に作成したロールでテーブルを作成します。
(node1のデータベースにSQLを投入) =# CREATE ROLE testuser SUPERUSER LOGIN; =# \c testuser =# CREATE TABLE test01 (id integer primary key); =# \d リレーションの一覧 スキーマ | 名前 | 型 | 所有者 ----------+--------+----------+---------- public | test01 | テーブル | testuser (1 行)(node2のデータベースにSQLを投入) =# \d リレーションがありません。 ※ ロールなどはレプリケーション対象とならないので、そのロールで作成されたテーブルもレプリケーション対象とならないデータベースログを確認し、ERRORが継続して出力されていれば競合が発生しています。
(node2のデータベースログ) LOG: starting background worker process "bdr (6371561413984311673,2,42879,)->bdr (6371553825031594764,1," ERROR: role "testuser" does not exist CONTEXT: during DDL replay of ddl statement: CREATE TABLE public.test01 (id pg_catalog.int4 , CONSTRAINT test01_pkey PRIMARY KEY (id) ) WITH (oids=OFF) apply QUEUED_DDL in commit 1/15DD2278, xid 3958054 commited at 2017-01-06 17:37:58.608995+09 (action #2) from node (6371553825031594764,1,42892) LOG: worker process: bdr (6371561413984311673,2,42879,)->bdr (6371553825031594764,1, (PID 30136) exited with exit code 1 ※ 権限は関係なく、同名のロールが作成されれば、競合は解決するレプリケーションを再開させるためにロールなどのグローバルデータを作成します。
(node2のデータベースにSQLを投入) =# CREATE ROLE testuser LOGIN; =# \d リレーションの一覧 スキーマ | 名前 | 型 | 所有者 ----------+--------+----------+---------- public | test01 | テーブル | testuser (1 行)
■ 競合概要
一つのノードで取得したロックともう片方のノードで取得したロックが、デッドロック状態となるケースを検証しました。
■ 検証結果
異なるノードで実行されたトランザクションでデッドロック状態が発生した場合、ロックが解除されるまでロック待ちが発生します。 PostgreSQLにおいてデッドロックが発生した場合、デッドロックを引き起こすトランザクションが自動でロールバックされますが、 異なるノード間で発生したデッドロックについては検出できないため、ロック待ち状態が続きます。
■ 競合発生手順
下記テーブルを初期状態とします。
(node1のデータベースにSQLを投入) =# CREATE TABLE sample (aid integer primary key, range daterange, price integer); =# ALTER TABLE sample ADD EXCLUDE USING gist (price WITH =, range WITH &&); =# INSERT INTO sample VALUES(1, '[2012-04-16, 2012-04-20]', 10000); =# INSERT INTO sample VALUES(2, '[2012-04-21, 2012-04-30]', 12000);(node1のデータベースを確認) =# SELECT * FROM sample; aid | range | price -----+-------------------------+------- 1 | [2012-04-16,2012-04-21) | 10000 2 | [2012-04-21,2012-05-01) | 12000 (2 行) (node2のデータベースを確認) =# SELECT * FROM sample; aid | range | price -----+-------------------------+------- 1 | [2012-04-16,2012-04-21) | 10000 2 | [2012-04-21,2012-05-01) | 12000 (2 行)1つのノードからテーブルをロックし、もう片方のノードからはUPDATEを実行し、ロック待ちの影響を受ける事を確認します。 ロックが解除されるまで、BDR適用プロセスはロック待ちが発生します。
(node1のデータベースにSQLを投入) =# BEGIN; =# LOCK TABLE sample IN ACCESS EXCLUSIVE MODE; =# SELECT pg_sleep(10); =# END; (node2のデータベースにSQLを投入) =# UPDATE sample SET price = 14000 WHERE aid = 2;(ロック待ちなので競合ログが出力されない) =# \x =# SELECT * FROM bdr.bdr_conflict_history ORDER BY conflict_id DESC LIMIT 1;
自動的には解決出来ないデータの相違が発生した場合は、以下設定を使用して手動で調整する必要があります。
※ レプリケーション環境を破壊することが可能であるため使用する際には注意が必要です。
項番 パラメータ 説明 1 bdr.do_not_replicate(boolean) このパラメータセットを持つトランザクションで行われた変更は、他ノードへのレプリケーションのためにキューに格納されない。=# CREATE TABLE test (id int primary key, name text); =# \q(node1に対してのみレコード追加を実行) $ export PGOPTIONS='-c bdr.do_not_replicate=on' $ psql -h node1 bdrtest =# INSERT INTO test values(1, 'test'); =# SELECT * from test; id | name ----+------ 1 | test (1 行) (node2には反映されていないことを確認) =# SELECT * from test; id | name ----+------ (0 行)
項番 パラメータ 説明 2 bdr.skip_ddl_replication(boolean) DDLによる変更のレプリケーションをスキップする。一部のノードのみにDDLを実行したい場合に有効。スーパーユーザのみが設定可能。(node1にのみテーブルを作成) =# BEGIN; =# SET LOCAL bdr.skip_ddl_replication = true; =# CREATE TABLE skip (id int primary key, name text); =# END; =# \d リレーションの一覧 スキーマ | 名前 | 型 | 所有者 ----------+--------+----------+---------- public | sample | テーブル | postgres public | skip | テーブル | postgres public | test | テーブル | postgres (3 行) (node2には反映されていないことを確認) =# \d リレーションの一覧 スキーマ | 名前 | 型 | 所有者 ----------+--------+----------+---------- public | sample | テーブル | postgres public | test | テーブル | postgres (2 行)
項番 パラメータ 説明 3 bdr.permit_unsafe_ddl_commands(boolean) 安全にレプリケーションできないスキーマの変更を許可する。スーパーユーザのみが設定可能。以下のようなデフォルトでは実行が許可されていないクエリを実行可能。* CREATE TABLE AS* ALTER TABLE ... ADD COLUMN ... DEFAULT* CREATE MATERIALIZED VIEW* REFRESH MATERIALIZED VIEW(デフォルトではエラーとなる) =# CREATE TABLE test2 AS SELECT * from test; ERROR: CREATE TABLE AS is not supported when bdr is active (エラーが出ないことを確認) =# BEGIN; =# SET LOCAL bdr.permit_unsafe_ddl_commands = true; =# CREATE TABLE test2 AS SELECT * from test; =# END;
一部の競合パターンにて、意図しない動作が発生するため、現段階では競合が発生しないパターンで利用すべきと考えます。
複数ノードで構成されるクラスタ環境内の1ノードに障害が発生した場合でも、他ノードで継続利用可能か否かを確認します。 また、障害が発生したノードをクラスタ環境に復旧させる手順を確認します。
BDRの各ノード間のレプリケーションが正常に動作していることを「bdr.bdr_nodes」テーブルを用いて確認します。
(node1にて実施) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
pgbebchを利用して、検証時に利用するテーブルとデータを作成します。
(node1にて実施) $ pgbench -i -s 10 bdrtest
pgbenchを利用して、ノード1にトランザクションを継続的に実行します。 ノード2に障害が発生した場合に、ノード1に対して実行したトランザクションにエラーが発生するか否かを確認します。
$ pgbench -h node1 -c 10 -t 100000 bdrtest starting vacuum...end.
ノード2で動作するPostgreSQLを停止させます。
(node2にて実施) $ pg_ctl stop -m i waiting for server to shut down..... done server stopped
ノード2障害時に出力されるログメッセージを確認します。 下記メッセージは出力されますが、pgbenchのトランザクションは継続して実行可能です。
$ tail -f [PostgreSQLのログファイル] LOG: starting background worker process "bdr (6367633348875313343,1,34478,)->bdr (6369931070716042622,2," ERROR: establish BDR: could not connect to server: Connection refused Is the server running on host "node2" (192.168.1.3) and accepting TCP/IP connections on port 5432?
ノード2障害後、ノード状態が変化するか否かを「bdr.bdr_nodes」テーブルを用いて確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)の状態のまま node_init_from_dsn | host=node1 port=5432 dbname=bdrtest ※ 状態変化は確認できませんでした。
障害が発生したノード2を切り離しするため、「bdr.bdr_part_by_node_names」関数を実行します。
(node1のデータベースに接続) =# SELECT bdr.bdr_part_by_node_names(ARRAY['node2']); bdr_part_by_node_names ------------------------ (1 row)
ノードの切り離し結果を「bdr.bdr_nodes」テーブルを用いて確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn , node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | k <-- k(削除)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest
本手順は推奨される手順ではありませんが、削除状態のノードが「bdr.bdr_nodes」テーブルに残っているとノード復旧が実行できないため暫定的な対処です。 BDRのIssuesで「bdr.bdr_connections」のデータも削除する事が提案されていたので、こちらも暫定的な手順ですが実行します。 詳細は下記をご参照下さい。
(node1のデータベースに接続) =# DELETE FROM bdr.bdr_connections USING bdr.bdr_nodes WHERE node_status = 'k' AND" (node_sysid, node_timeline, node_dboid) = (conn_sysid, conn_timeline, conn_dboid); =# DELETE FROM bdr.bdr_nodes where node_status = 'k';
手順1で実行したpgbenchにエラーが発生してないことを確認します。本検証ではエラーは発生しませんでした。
BDRノードの復旧方法(ノード追加)する場合、既存ノードのデータベースと復旧させるノードのデータベースのスキーマおよびデータを同期させる必要があります。 ノード間のデータコピーには、論理コピーと物理コピーの2つの手法があります。
項番 | コピー取得 | 説明 | 備考 |
---|---|---|---|
1 | bdr.bdr_group_join 関数実行 | ユーザが指定したノード内データベースのスキーマとデータダンプを取得 | pg_dumpコマンドに相当 |
2 | bdr_init_copyコマンド | ユーザが指定したノード上の全てのデータベースのコピーを取得 | pg_basebackupコマンドに相当 |
■ 論理コピーによる復旧
pgbenchを利用して、ノード1にトランザクションを継続的に実行します。 ノード2に復旧する際に、ノード1に対して実行したトランザクションの停止が必要か否かを確認します。
$ pgbench -h node1 -c 10 -t 100000 bdrtest starting vacuum...end.
ノード2上で動作するPostgreSQLを起動させます。
$ pg_ctl start
ノード2のデータベースからBDRを削除し、BDR拡張機能を削除します。
(node2のデータベースに接続) =# SELECT bdr.remove_bdr_from_local_node(true); WARNING: forcing deletion of possibly active BDR node NOTICE: removing BDR from node NOTICE: BDR removed from this node. You can now DROP EXTENSION bdr and, if this is the last BDR node on this PostgreSQL instance, remove bdr from shared_preload_libraries. remove_bdr_from_local_node ---------------------------- =# DROP EXTENSION bdr; DROP EXTENSION
BDRで利用したデータベースを削除します。
(node2のデータベースに接続) =# DROP DATABASE bdrtest ; ※ 接続が残っており、削除できない場合はPostgreSQLを再起動 DROP DATABASE
BDRで利用するデータべースを再度作成します。
(node2のデータベースに接続) =# CREATE DATABASE bdrtest;
無効化したBDRを再度有効化します。
(node2のデータベースに接続) =# CREATE EXTENSION btree_gist; CREATE EXTENSION =# CREATE EXTENSION bdr; CREATE EXTENSION
ノードを追加(復旧)させるため、「bdr.bdr_group_join」関数を実行します。
(node2のデータベースに接続) =# SELECT bdr.bdr_group_join( local_node_name := 'node2', node_external_dsn := 'host=node2 port=5432 dbname=bdrtest', join_using_dsn := 'host=node1 port=5432 dbname=bdrtest' ); bdr_group_join ---------------- (1 行)
ノードが追加されたことを確認するため、「bdr.bdr_node_join_wait_for_ready」関数を実行します。
(node2のデータベースに接続) =# SELECT bdr.bdr_node_join_wait_for_ready(); ※ トランザクションが実行中の場合、上記関数の結果が戻りません。node1のPostgreSQLログファイルに下記メッセージが出力された後、復旧の処理が開始されません。
(node1のログメッセージ抜粋) LOG: logical decoding found initial starting point at 0/BB399BF0 DETAIL: 10 transactions need to finish.pgbenchコマンドで実行中のトランザクションを停止すると、下記ログメッセージが出力され、復旧処理が開始されます。
(node1のログメッセージ抜粋) LOG: logical decoding found consistent point at 0/B2895648 DETAIL: There are no running transactions. LOG: exported logical decoding snapshot: "00046FE0-1" with 0 transaction IDs
ノード2が追加されたことを「bdr.bdr_nodes」テーブルの情報から確認します。
(node1のデータベースに接続) =# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
■ 物理コピーによる復旧
pgbenchを利用して、ノード1にトランザクションを継続的に実行します。 ノード2に復旧する際に、ノード1に対して実行したトランザクションの停止が必要か否かを確認します。
$ pgbench -h node1 -c 10 -t 100000 bdrtest starting vacuum...end.
ノード2上で動作するPostgreSQLが停止していることを確認します。
(node2にて実施) $ pg_ctl status pg_ctl: no server running
ノード2上で「bdr_init_copy」コマンドを実行し、node1上のコピーを取得します。
(node2にて実施) $ rm -rf $PGDATA/* $ bdr_init_copy -D $PGDATA -n node2 -h node1 -p 5432 -d bdrtest --local-host=node2 --local-port=5432 --local-dbname=bdrtest bdr_init_copy: starting ... Getting remote server identification ... Detected 1 BDR database(s) on remote server Updating BDR configuration on the remote node: bdrtest: creating replication slot ... bdrtest: creating node entry for local node ... Creating base backup of the remote node... 50357/50357 kB (100%), 1/1 tablespace Creating restore point on remote node ... Bringing local node to the restore point ... トランザクションログをリセットします。 Initializing BDR on the local node: bdrtest: adding the database to BDR cluster ... All donenode1のPostgreSQLログファイルに下記メッセージが出力された後、復旧の処理が開始されません。
(node1のログメッセージ抜粋) LOG: logical decoding found initial starting point at 0/BB399BF0 DETAIL: 10 transactions need to finish.pgbenchコマンドで実行中のトランザクションを停止すると、下記ログメッセージが出力され、 復旧処理が開始されます。
(node1のログメッセージ抜粋 LOG: logical decoding found consistent point at 0/B2895648 DETAIL: There are no running transactions. STATEMENT: SELECT pg_create_logical_replication_slot('bdr_25434_6369931070716042622_2_25434__', 'bdr');
ノード2が追加されたことを「bdr.bdr_nodes」テーブルの情報から確認します。
(node1のデータベースに接続) bdrtest=# SELECT node_name, node_local_dsn, node_status, node_init_from_dsn FROM bdr.bdr_nodes; -[ RECORD 1 ]------+------------------------------------ node_name | node1 node_local_dsn | host=node1 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | -[ RECORD 2 ]------+------------------------------------ node_name | node2 node_local_dsn | host=node2 port=5432 dbname=bdrtest node_status | r <-- r(正常)であることを確認 node_init_from_dsn | host=node1 port=5432 dbname=bdrtest <-- node1から作成されていることを確認
ノード障害が発生した際にも、他のノードではトランザクションを継続実行することが可能でした。 障害ノードを復旧させるためにはデータ操作が禁止されているシステムカタログのデータ削除が必要でした。
ノード障害後および復旧中にDDLを実行すると下記のエラーが出力されます。
=# CREATE TABLE test2(id int); ERROR: No peer nodes or peer node count unknown, cannot acquire global lock HINT: BDR is probably still starting up, wait a while
本試験はBDRの本来のユースケースである、 高レイテンシ環境との双方向レプリケーション環境における更新クエリのレスポンス改善とそれに伴う性能改善を確認したものです。
今回は 2014年度検証報告書 (可用性編) に記載された東京-シンガポール間の回線情報(応答速度76.95ms、帯域幅0.16Gbits/s)をもとに、 以下のような環境を構築しpgbenchを実行しました。
通常のストリーミングレプリケーションであれば、 更新クエリを実行したい場合はシンガポールのマスタノードに更新要求を実施する必要がありますが、 BDRでは最寄りのノードに対して更新要求を実施することが可能です。
pgbenchを実行する準備を行います。(BDR, SR環境両方)
# pgbench -i test -U postgres -s 10 NOTICE: table "pgbench_history" does not exist, skipping NOTICE: table "pgbench_tellers" does not exist, skipping NOTICE: table "pgbench_accounts" does not exist, skipping NOTICE: table "pgbench_branches" does not exist, skipping creating tables... 100000 of 100000 tuples (100%) done (elapsed 0.25 s, remaining 0.00 s). vacuum... set primary keys... done.node1, node2ともにテーブルが作成されていることを確認します。
# psql -U postgres test -c "\d" リレーションの一覧 スキーマ | 名前 | 型 | 所有者 ----------+------------------+----------+---------- public | pgbench_accounts | テーブル | postgres public | pgbench_branches | テーブル | postgres public | pgbench_history | テーブル | postgres public | pgbench_tellers | テーブル | postgres (4 行) # /usr/pgsql-9.4/bin/psql -U postgres -h node2 test -c "\d" リレーションの一覧 スキーマ | 名前 | 型 | 所有者 ----------+------------------+----------+---------- public | pgbench_accounts | テーブル | postgres public | pgbench_branches | テーブル | postgres public | pgbench_history | テーブル | postgres public | pgbench_tellers | テーブル | postgres (4 行)帯域制限及び遅延設定を行います。
(clientはnode2向け通信のみ制限) tc qdisc add dev eth0 root handle 1:0 htb tc class add dev eth0 parent 1:0 classid 1:10 htb rate 160Mbit tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 192.168.0.12/32 flowid 1:10 tc qdisc add dev eth0 parent 1:10 handle 10:1 netem delay 38ms 1ms (node1はnode2向け通信のみ制限) tc qdisc add dev enp0s25 root handle 1:0 htb tc class add dev enp0s25 parent 1:0 classid 1:10 htb rate 160Mbit tc filter add dev enp0s25 protocol ip parent 1:0 prio 1 u32 match ip dst 192.168.0.12/32 flowid 1:10 tc qdisc add dev enp0s25 parent 1:10 handle 10:1 netem delay 38ms 1ms (node2は両ノード向け通信とも制限) tc qdisc add dev enp0s25 root handle 1:0 htb tc class add dev enp0s25 parent 1:0 classid 1:0 htb rate 160Mbit tc qdisc add dev enp0s25 parent 1:0 handle 10:1 netem delay 38ms 1ms帯域制限の結果以下のような環境になります。
Client/Server | client | node1 | node2 |
---|---|---|---|
client | - | 遅延:0.5 ms
帯域幅:934 Mbits/s
|
遅延:76ms
帯域幅:160 Mbits/s
|
node1 | 遅延:0.5 ms
帯域幅:934 Mbits/s
|
- | 遅延:76ms
帯域幅:160 Mbits/s
|
node2 | 遅延:76ms
帯域幅:160 Mbits/s
|
遅延:76ms
帯域幅:160 Mbits/s
|
- |
更新クエリの応答速度を確認します。
# time psql -h node1 -x -c "UPDATE pgbench_branches SET bbalance = bbalance + 100 WHERE bid = 1" test UPDATE 1 real 0m0.025s user 0m0.001s sys 0m0.001s # time psql -h node2 -x -c "UPDATE pgbench_branches SET bbalance = bbalance + 100 WHERE bid = 1" test UPDATE 1 real 0m0.259s user 0m0.001s sys 0m0.000s (ネットワーク遅延に伴いレスポンスが低下していることを確認)
非同期SR構成のマスターに対してpgbenchを実施します。
# pgbench -U postgres -p 5433 -h node2 -s 10 -c 10 test -T 180 scale option ignored, using count from pgbench_branches table (10) starting vacuum...end. transaction type: TPC-B (sort of) scaling factor: 10 query mode: simple number of clients: 10 number of threads: 1 duration: 180 s number of transactions actually processed: 3078 latency average: 584.795 ms tps = 17.042442 (including connections establishing) tps = 17.057455 (excluding connections establishing)
競合が発生しているか確認するために、「bdr.bdr_conflict_history」を参照し競合履歴数を確認します。
# psql -h node1 -x -c "SELECT count(*) FROM bdr.bdr_conflict_history;" test -[ RECORD 1 ] count | 52265 # psql -h node2 -x -c "SELECT count(*) FROM bdr.bdr_conflict_history;" test -[ RECORD 1 ]- count | 132935BDR環境のノードにpgbenchを実施します。
# pgbench -U postgres -h node1 -s 10 -c 10 test -T 180 scale option ignored, using count from pgbench_branches table (10) starting vacuum...end. transaction type: TPC-B (sort of) scaling factor: 10 query mode: simple number of clients: 10 number of threads: 1 duration: 180 s number of transactions actually processed: 16677 latency average: 107.933 ms tps = 92.598796 (including connections establishing) tps = 92.602290 (excluding connections establishing)競合が発生していないことを確認します。
# psql -h node1 -x -c "SELECT count(*) FROM bdr.bdr_conflict_history;" test -[ RECORD 1 ] count | 52265 # psql -h node2 -x -c "SELECT count(*) FROM bdr.bdr_conflict_history;" test -[ RECORD 1 ]- count | 132935
本検証では以下を確認することが出来ました。
レスポンスタイムの低減
低レイテンシのサーバに更新クエリを実行可能であるため、レスポンスタイムを低減することが出来ました。
TPSの向上
レスポンスタイムが低減されたため、TPSの向上が見られました。"17.057455" -> "92.602290"(約5.4倍)
ただし、本検証では片系のみに変更を実施し競合が発生しないようにした試験だったため、このような結果になったものと思われます。
本検証で使用しているpgbench(TCP-Bライク)のような、現在の値に対して加算していくような処理の場合、結果整合で競合を解決することができないため、 絶対に競合が発生しない構成が必要です。
例)更新するテーブルを拠点ごとに分ける等
本検証では、BDRの機能や特徴および主なユースケースを机上調査を実施した上で、BDRの動作検証および性能検証を実施しました。 本検証で実施したBDRの動作検証の結果は下表の通りです。
記号 | 意味 |
---|---|
○ | 問題なし。 |
△ | 利用時に問題になるケースがある。 |
× | 対応していない。事実上使えない。 |
項番 | 検証概要 | 結果 | 補足 |
---|---|---|---|
1 | ノード追加/削除 | △ | ノード削除はオンラインで実行可能。ノード追加時にトランザクションの停止が必要。 また、削除したノードを追加する場合にはシステムカタログの操作が必要。 |
2 | グローバルシーケンス | ○ | シーケンスの競合を防ぐことが可能。ただし、マニュアルに記載された制限事項については確認が必要。 |
3 | 選択的レプリケーション | ○ | BDRを利用して任意のテーブルのデータ集約等を実現可能 |
4 | 更新処理競合時の動作 | △ | 更新が競合するパターンで意図しない動作が発生し、競合解消のため手動での操作が必要。 |
5 | ノード障害と復旧 | △ | ノード復旧時にはトランザクションの停止が必要。 また、障害が発生したノードを復旧させる際にシステムカタログの操作が必要。 |
本検証で利用したバージョン(1.0.2)では更新が競合するパターンで意図しない動作が発生するため、BDRを適用する場合、競合が発生しないようなアプリケーション設計やテーブル設計が必要になります。
例) 更新するテーブルを拠点ごとに分ける等
ノード障害時の運用においても、一般的でないシステムカタログの操作を必要とするといった今後の改善が必要と思われる結果が確認されました。
また、BDRの選択的レプリケーションを用いることで、PostgreSQLのストリーミング・レプリケーションでは実現できない、テーブル単位のレプリケーションが実現可能なことを確認できました。 上記機能を用いて、システム間のデータ連携(データ集約等)を柔軟に実現できると考えております。
性能検証の結果より、ユースケースで想定している「遠距離拠点間で双方向に更新する」場合に、レスポンスタイムの低減と処理向上につながることが確認できました。
机上で調査した通り、BDRを適用することで「遠距離拠点間のトランザクション性能改善」や選択的レプリケーション機能を利用した「柔軟なデータ連携」を実現できると考えます。
[BDR] | Postgres-BDR ドキュメント http://bdr-project.org/docs/stable/ |
項番 | 関数 | 参照ドキュメント |
---|---|---|
1 | bdr.bdr_group_create | functions-node-mgmt.html |
2 | bdr.bdr_group_join | functions-node-mgmt.html |
3 | bdr.bdr_part_by_node_names | functions-node-mgmt.html |
4 | bdr.remove_bdr_from_local_node | functions-node-mgmt.html |
5 | bdr.bdr_node_join_wait_for_ready | functions-node-mgmt.html |
6 | bdr.table_set_replication_sets | functions-replication-sets.html |
7 | bdr.table_get_replication_sets | functions-replication-sets.html |
8 | bdr.connection_get_replication_sets | functions-replication-sets.html |
項番 | システムカタログ | 参照ドキュメント |
---|---|---|
1 | bdr.bdr_conflict_history | catalog-bdr-conflict-history.html |
2 | bdr.bdr_nodes | catalog-bdr-nodes.html |
3 | bdr.bdr_connections | catalog-bdr-connections.html |
本文書ではPostgreSQLにおけるレプリケーション技術として標準機能として組み込まれているストリーミングレプリケーションと ロジカルレプリケーションについて取り上げました。さらに、マルチマスタ構成に対応したBi-Directional Replication(BDR)についても取り上げました。
ストリーミングレプリケーションは、2010年9月にリリースされたPostgreSQL9.0で標準機能として組み込まれ、徐々に実運用での利用される機会が増えるとともに、実運用で必要となる機能改善がその後のバージョンアップで継続的に行われています。
本文書ではストリーミングレプリケーション構成を構築する基本的な手順を確認した上で、レプリケーションスロット、遅延レプリケーション、WAL圧縮といった新しい機能に対しても、設定から実際の動作を確認するまでの具体的な手順を整理することができました。障害発生時の運用についてはPostgreSQL9.5から提供されるようになったpg_rewindコマンドを使うことで、これまで必要とされていたフルバックアップ取得を不要とする運用が可能になっていることを確認できました。さらに、複数台のスレーブが存在するあるいはカスケードレプリケーションといったより複雑な構成で障害が発生した場合の運用手順についても確認しました。
ロジカルレプリケーションは、2017年10月にリリースされたPostgreSQL10.0で標準機能として組み込まれました。ストリーミングレプリケーションと類似したアーキテクチャを持ちながら、テーブル単位や更新種別単位で柔軟なレプリケーション形態を取ったり、複製先を更新することができる等の特徴を有しています。
本文書ではロジカルレプリケーション構成を構築する基本的な手順、同期レプリケーション、複数Subscriptionへのレプリケーション、カスケード構成といった応用的な使い方、監視方法や障害発生時の挙動、レプリケーション開始後のテーブル追加、定義変更の手順について検証しました。検証結果より、現状のロジカルレプリケーションは柔軟にテーブル単位、更新種別単位で複製できることによりレプリケーションの活用範囲が広がった一方で、更新競合を解消する機能が貧弱なことやレプリケーションされないSQL、オブジェクトが多数あるといった制約事項もあるため、現状では更新競合が発生しないよう複製先を参照用途に限定して利用するのが現実的と考えます。
BDRは、現状のストリーミングレプリケーションでは不可能なマルチマスタ構成に対応することから、シングルマスタ構成に起因する課題解消が期待されます。今回の検証では、BDRの特徴、ユースケース、メカニズムを類似機能、製品と机上比較した上で、更新処理競合時の動作、ノード障害時の動作、更新性能についてそれぞれ検証しました。更新性能の検証結果より、ユースケースで想定している「遠距離拠点間で双方向に更新する」場合に、レスポンスタイムの低減とTPS向上につながることがわかりました。
一方で、現時点のバージョンでは更新が競合するパターンで意図しない動作が発生するため、現段階では競合が発生しないパターンで利用すべきと考えます。そこで、BDRの利用時は競合が発生しにくいテーブル設計にすることが望ましいです。また、ノード障害時の運用においても、一般的でないシステムカタログの操作を必要とするといった今後の改善が必要と思われる結果が確認されました。
これらの結果から、BDRについてはユースケースと実際の利用用途が合致しているかを見極めた上で利用するかどうかを判断する必要があるといえます。
今回の検証結果がストリーミングレプリケーション構成を実際に利用している方々の運用改善につながること、またロジカルレプリケーションやBDRを利用すべきかどうかを判断する際の参考情報として活用いただけることを期待しています。
(企業・団体名順)
版 | 所属企業・団体名 | 部署名 | 氏名 |
---|---|---|---|
第1.0版
(2016年度WG3)
|
株式会社アシスト | データベース技術本部 | 竹内 尚也 |
株式会社アシスト | データベース技術本部 | 柘植 丈彦 | |
株式会社オージス総研 | プラットフォームサービス本部 IT基盤技術部 | 大西 斉 | |
TIS株式会社 | IT基盤技術本部 IT基盤技術推進部 | 中西 剛紀 | |
日本電信電話株式会社 | オープンソースソフトウェアセンタ | 坂田 哲夫 | |
株式会社富士通ソーシアルサイエンスラボラトリ | プラットフォームインテグレーション本部 第四システム部 | 小山田 政紀 | |
株式会社富士通ソーシアルサイエンスラボラトリ | プラットフォームインテグレーション本部 第四システム部 | 高橋 勝平 | |
株式会社富士通ソーシアルサイエンスラボラトリ | プラットフォームインテグレーション本部 第四システム部 | 香田 紗希 | |
第2.0版
(2017年度WG3)
|
株式会社アシスト | サービス事業部 サポートセンター | 神谷 美恵子 |
株式会社アシスト | データベース技術本部 技術統括部 | 竹内 尚也 | |
株式会社アシスト | データベース技術本部 技術統括部 | 柘植 丈彦 | |
株式会社オージス総研 | プラットフォームサービス本部 IT基盤技術部 | 大西 斉 | |
TIS株式会社 | IT基盤技術本部 | 中西 剛紀 | |
株式会社富士通ソーシアルサイエンスラボラトリ | ソリューション開発センター ソリューションビジネス部 | 小山田 政紀 | |
株式会社富士通ソーシアルサイエンスラボラトリ | 第二システム事業本部 第三システム部 | 香田 紗希 |