Backup and recovery
===================

PGDは、分散された高可用システムであるように設計されています。クラスターの1つ以上のノードが失われた場合、それらを置き換える最良の方法は、残りのノードから直接新しいノードのクローンを作成することです。

PGDにおけるバックアップとリカバリーの役割は、次の状況などのディザスターリカバリーDRを提供することです。

- クラスター内のすべてのノードの損失

- データの破損、アプリケーションエラー、またはセキュリティ侵害の結果、マルチプルのノードにわたる重大な修正不可能なデータの破損

論理バックアップとリストア
--------------------------

pg_dumpは、 *論理バックアップ*
と呼ばれることもあります、通常はPGDで使用できます。

postgresql.confの一時的な設定
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

最初に、\ ``postgresql.conf`` に次の設定を一時的に設定します。

::


   #  Increase from the default of `1GB` to something large, but still a

   #  fraction of your disk space since the non-WAL data must also fit.

   #  This decreases the frequency of checkpoints.

   max_wal_size = 100GB

   #  Increase the amount of memory for building indexes. Default is

   #  64MB. For example, 1GB assuming 128GB total RAM.

   maintenance_work_mem = 1GB

   #  Increase the receiver and sender timeout from 1 minute to 1hr to

   #  allow large transactions through.

   wal_receiver_timeout = 1h
   wal_sender_timeout = 1h

   #  Increase the number of writers to make better use of parallel

   #  apply. Default is 2. Make sure this isnt overriden lower by the

   #  node group config num_writers setting.

   bdr.writers_per_subscription = 5

   #  Increase Raft-related election timeouts with default values of 6s

   #  and 3s.

   bdr.raft_global_election_timeout = 20s
   bdr.raft_group_election_timeout = 10s

   #  Increase the size of the shared memory queue used by the receiver to

   #  send data to the writer process from the default 1MB.

   bdr.writer_input_queue_size = 32MB

さらに

- トランザクションがストリーミングされるように、デフォルトのbdr.streaming_mode
  = ’auto’がオーバーライドされていないことを確認します。

- 上記にリストされているセッションまたはpostgresql.conf設定が、一般にノードグループレベルの設定でオーバーライドされていないことを確認します。

次に、 pg_dumpおよびpg_restoreに進みます。

pg_dump / pg_restore
^^^^^^^^^^^^^^^^^^^^

グローバルロックタイムアウトのリスクを軽減するために、事前データ、データ、および事後データを個別にダンプすることをお勧めします。例

.. code:: console

   pg_dump -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB -v --exclude-schema="bdr" --exclude-extension="bdr" --section=pre-data -Fc -f pgd-pre-data.dump
   pg_dump -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB -v --exclude-schema="bdr" --exclude-extension="bdr" --section=data -Fc -f pgd-data.dump
   pg_dump -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB -v --exclude-schema="bdr" --exclude-extension="bdr" --section=post-data -Fc -f pgd-post-data.dump

そして、これらのSQLファイルをノードで直接実行して復元します。接続マネージャーポートでこれらを実行しないでください。

.. code:: console

   PGOPTIONS="-cbdr.commit_scope=local" pg_restore -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB --section=pre-data -f pgd-pre-data.dump
   PGOPTIONS="-cbdr.commit_scope=local" pg_restore -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB --section=data -f pgd-data.dump
   psql -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB -c SELECT bdr.wait_slot_confirm_lsn(NULL, NULL)
   PGOPTIONS="-cbdr.commit_scope=local" pg_restore -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB --section=post-data -f pgd-post-data.dump
   psql -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PGD_DB -c SELECT bdr.wait_slot_confirm_lsn(NULL, NULL)

この時点で、ダンプはクラスター内のすべてのノードで復元されます。

対照的に、単純なpg_dumpおよびpg_restoreでセクションを分割しない場合、リストアはグローバルロックタイムアウトで失敗する可能性があります。

pg_restoreでグローバルロックタイムアウトが発生する場合は、
``-cbdr.ddl_locking=off`` を\ ``PGOPTIONS`` に追加します。

``-j`` /``--jobs`` でpg_restoreを実行する場合、 ``max_worker_processes``
および\ ``max_parallel_maintenance_workers``
を同じ量だけ増やす必要があります。

単一ノードクラスターへの復元を優先します
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

特に、Postgresダンプからクラスターを最初にセットアップする場合、単一のPGDノードを使用してクラスターに復元することをお勧めします。次に、クラスター内に必要なノードごとに\ ``pgd node setup``
を実行します。これは、内部で\ ``bdr_init_physical``
を使用する物理的結合を実行します。

シークエンス
^^^^^^^^^^^^

pg_dumpは、ローカルシーケンスとグローバルシーケンスの両方をローカルシーケンスであるかのようにダンプします。この動作は意図的なもので、PGDスキーマをダンプして他のPostgreSQLデータベースに移植できるようにします。これは、ダンプ時にシーケンス種類のメタデータが失われることを意味するため、リストアはすべてのシーケンス種類をリストア時に\ ``bdr.default_sequence_kind``
の値に効果的にリセットします。

シーケンスごとに正確なシーケンス種類をリセットする復元後スクリプトを作成するには、次のようなSQLスクリプトを使用することができます。

.. code:: sql

   SELECT SELECT bdr.alter_sequence_set_kind(||
           nspname||.||relname||,||seqkind||);
   FROM bdr.sequences
   WHERE seqkind != local;

``bdr.crdt_raw_value = on`` を使用してpg_dumpを実行した場合、
``bdr.crdt_raw_value = on`` でのみダンプをリロードできます。

テクニカルサポートでは、
PGDのバックアップとリカバリーに物理的バックアップ技術を使用することをお勧めします。

物理的なバックアップとリストア
------------------------------

`Barman <https://www.enterprisedb.com/docs/supported-open-source/barman/>`_ などの標準のPostgreSQLソフトウェアを使用して、 EDB
Postgres分散クラスター内のノードの物理バックアップを取得できます。

PostgreSQLノードに適用されるのと同じ手順を使用して、PGDノードの物理バックアップを実行できます。
PGDノードは、 BDR拡張機能を実行する単なるPostgreSQLノードです。

PostgreSQLバックアップ技術をPGDに適用するときは、次の特定の点を考慮してください。

- PGDは単一のデータベースのレベルで動作しますが、物理バックアップにはインスタンス内のすべてのデータベースが含まれます。データベースを簡単にバックアップおよび復元できるように計画します。

- バックアップは、1つのノードのみのコピーを作成します。最も単純なケースでは、すべてのノードにすべてのデータのコピーがあるため、すべてのデータをキャプチャするには1つのノードのみをバックアップする必要があります。ただし、その単一のコピーを含むサイトがダウンした場合、PGDの目標は満たされないため、最小はサイトごとに少なくとも1つのノードバックアップ多数のコピーなど。

- ただし、各ノードにはレプリケーションされていないローカルデータがある場合、またはレプリケーションセットの定義が複雑な場合があるため、すべてのノードがすべてのレプリケーションセットをサブスクライブしない場合があります。これらの場合、バックアップ計画には、レプリケートされていないローカルデータと、各レプリケーションセットをサブスクライブする少なくとも1つのノードのバックアップをバックアップする方法に関する計画も含める必要があります。

リストア
^^^^^^^^

標準のPostgreSQLノードと同じ手順で物理バックアップを取ることができますが、
PGDノードの物理バックアップを復元するのは少し複雑です。

EDB Postgres分散クラスター障害またはバックアップからの新しいクラスターのシード
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

物理バックアップを復元するための最も一般的なユースケースには、データセンターに障害が発生した場合など、クラスター内のすべてのPGDノードの障害または交換が含まれます。

また、この手順を実行して、 EDB
Postgres分散クラスターの現在のコンテンツのクローンを作成して、QAまたは開発インスタンスをシードすることもできます。

その場合、単一のPGDノードとオプションでWALアーカイブの物理バックアップに基づいて、PGD機能を復元できます。

- いくつかのPGDノードがまだライブで実行されている場合は、
  PGDノードを復元したホストをフェンスオフして、残りのPGDノードに接続できないようにします。この実践により、新しいノードが既存のクラスターを混乱させないようになります。

- PGDノードの1つの物理バックアップから単一のPostgreSQLノードを復元します。

- バックアップに関連付けられているWALアーカイブがある場合は、適切な\ ``postgresql.conf``
  を作成し、リカバリーでPostgreSQLを起動して最新の状態まで再生します。必要に応じて、ここで代替の\ ``recovery_target``
  を指定できます。

- 復元されたノードを起動するか、スタンバイ復旧している場合は読み取り/書き込みに昇格させます。生き残ったノードからフェンスしてください。

- 物理バックアップに含まれる残りのPGDメタデータをクリーンアップします。

- PostgreSQLインスタンスを完全に停止して再起動します。

- ``bdr.join_node_group()``
  ファンクション呼び出しに基づいた標準プロシージャでPGDノードをさらに追加します。

PGDメタデータのクリーンアップ
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

残ったPGDメタデータをクリーンアップするには

1. `bdr.drop_node <https://www.enterprisedb.com/docs/pgd/latest/reference/tables-views-functions/functions-internal#bdrdrop_node>`_ を使用してPGDノードを削除します。

2. PostgreSQLを完全に停止して再起動します重要！

レプリケーション起点のクリーンアップ
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

レプリケーションオリジンはシステムカタログに永続的に記録されるため、別の手順で明示的に削除する必要があります。したがって、これらはバックアップと復元されたインスタンスに含まれます。これらは依存関係として明示的に記録されていないため、
BDR拡張機能をドロップするときに自動的に削除されません。

クラッシュセーフな方法で着信レプリケーションの進行状況を追跡するため、PGDはリモートマスターノードごとに1つのレプリケーションオリジンを作成します。したがって、以前のクラスターのノードごとに、これを1回実行します。

::

   SELECT pg_replication_origin_drop(bdr_dbname_grpname_nodename);

次のようにして、レプリケーションオリジンをリストできます。

::

   SELECT * FROM pg_replication_origin;

PGDによって作成されたものは、名前で簡単に認識されます。

レプリケーションスロットのクリーンアップ
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``pg_basebackup``
で物理バックアップが作成された場合、レプリケーションスロットはバックアップから省略されます。

他のバックアップ方法には、レプリケーションスロットが古いまたは無効な状態で保持される場合があります。バックアップを復元したら、次のコマンドを使用してすべてのレプリケーションスロットをドロップします。

::

   SELECT pg_drop_replication_slot(slot_name)
   FROM pg_replication_slots;

一部のスロットを保持する理由がある場合は、
``WHERE slot_name LIKE 'bdr%'``
句を追加できますが、これはほとんど役に立ちません。

..  Warning::
   これらのコマンドを使用して、ライブPGDノードでレプリケーションスロットをドロップしないでください

手動リストアでの\ ``max_worker_processes`` の設定
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

PGDは通常、\ ``max_worker_processes = 100``
で実行されます。復元されたノードがPostgresのデフォルトの32で起動した場合、復旧は中止されます。

::

   FATAL: recovery aborted because of insufficient parameter settings

``barman recover``
は、この調整を自動的に処理します。手動リストアの場合、Postgresを起動する前に\ ``max_worker_processes``
を正しい値に設定します。正常なピアからランタイム値を見つけるには

.. code:: sql

   SELECT setting FROM pg_settings WHERE name = max_worker_processes;

論理レプリケーションスロットの再作成
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``pg_basebackup --wal-method=stream``
を介して取得された物理バックアップには、論理レプリケーションスロットが含まれません。
Barmanは内部で\ ``pg_basebackup`` を実行しているため、
``backup_method = postgres`` で\ ``barman backup``
を介して取得されたバックアップにも同じことが当てはまります。
``barman recover`` または手動\ ``pg_basebackup`` リストアの後、
BDRレプリケーションを再開する前に、復元された各ノードでスロットを再作成する必要があります。

PGDは、ピアの\ ``node_uuid``
とデータベース名のハッシュによって、ピアごとのスロットを名前付けます。復元後、
BDRのサブスクライバーワーカーは、この正確な形式でスロットに\ ``START_REPLICATION``
を発行します。スロットが存在しない場合、\ ``pgd manager``
バックグラウンドワーカーは\ ``replication slot "bdr_<...>" does not exist``
とクラッシュループに入り、レプリケーションは進行しません。

復元された各ノードで、次を実行します。

.. code:: sql

   - - 1. Group slot. bdr.local_group_slot_name() returns the canonical
   - -    bdr_group_<group_uuid>_<dbhash> name.
   SELECT pg_create_logical_replication_slot(bdr.local_group_slot_name(), bdr);

   - - 2. One slot per peer. The name must use the peers node_uuid and the
   - -    database-name hash, because BDR subscribers ask for that exact name.
   SELECT pg_create_logical_replication_slot(
       bdr_node_
         || replace(n.node_uuid::text, -, )
         || _
         || lpad(to_hex(hashtext(current_database())), 8, 0),
       bdr
   )
   FROM bdr.node n
   WHERE n.node_id != (SELECT node_id FROM bdr.local_node);

復元されたすべてのノードでこれらのコマンドを実行します。各ノードにスロットが存在すると、\ ``pgd manager``
バックグラウンドワーカーはクラッシュループを停止し、ピアごとのスロットは数秒以内に\ ``active = t``
に移行します。次の方法で確認します。

.. code:: sql

   SELECT slot_name, slot_type, active FROM pg_replication_slots ORDER BY slot_name;

..  Warning::
   リストア後にスロットを再作成するために`bdr.initialize_replication_slot()` を呼び出しないでください。ファンクションは存在しますが、作成されるスロットは、 BDRサブスクライバーが使用する`node_uuid` フォームではなく、ピアの`node_name` を使用します。 `pg_drop_replication_slot()` は`cannot drop replication slot as it is created automatically by BDR` で拒否するため、結果のスロットは使用されることはなく、後で削除することもできません。

レプリケーションセットにない拡張カタログテーブル
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

一部の拡張機能、たとえば `Postgres Analytics Accelerator (PGAA) <https://www.enterprisedb.com/docs/pgaa/latest/>`_  は、 ``bdr.tables``
の\ ``set_name = NULL``
を使用してカタログテーブルを登録するため、論理的にレプリケートされません。これらは、レプリケーションストリームを介してではなく、PGDのグローバルDDLロックと拡張機能のノードごとの\ ``object_access_hook``
を介して、実行時の一貫性を維持します。

バックアップの場合、これは各ノードのベースバックアップにこれらのカタログテーブルの独自の物理コピーが含まれることを意味します。クラスター全体の一貫性には、すべてのノードからの調整されたベースバックアップが必要です。単一ノードのベースバックアップは、そのノードのビューのみをキャプチャします。

マルチノードリストアの後、ノード全体で各テーブルのコンテンツの行数とダイジェストを比較します。
PGAAについては、特に 
`Restoring a PGD cluster <https://www.enterprisedb.com/docs/pgaa/latest/backup_restore#restoring-a-pgd-cluster>`_  を参照してください。

最終的な一貫性
--------------

EDB Postgres分散クラスター内のノードは *結果的に一貫性* ですが、
*完全に一貫性*
ではありません。特定のノードの物理バックアップは、そのノードが実際に想定する状態に制限されたポイントインタイムリカバリ機能を提供します。

次の例は、同じEDB
Postgres分散クラスター内の2つのノードがどのようにして同じ状態のシーケンスを通過しないか、通常は通過しないかを示しています。

2つのノード\ ``N1`` および\ ``N2`` を含み、最初は\ ``S``
状態にあるクラスターを検討します。トランザクション\ ``W1``
がノード\ ``N1`` に適用され、同時に競合しないトランザクション\ ``W2``
がノード\ ``N2`` に適用される場合、ノード\ ``N1`` は次の状態を経ます。

::

   (N1)   S  -->  S + W1  -->  S + W1 + W2

ノード\ ``N2`` は、次の状態を経ます。

::

   (N2)   S  -->  S + W2  -->  S + W1 + W2

つまり、ノード\ ``N1`` は決して*状態\ ``S + W2``
を引き受けることはなく、ノード\ ``N2`` も同様に状態\ ``S + W1``
を引き受けることはありません。ただし、両方のノードは同じ状態\ ``S + W1 + W2``
になります。この状況を考慮すると、バックアップ戦略の決定方法に影響を与える可能性があります。

ポイントインタイムリカバリーPITR
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

前の例では、変更も時間的に一貫していないことを示しました。 ``W1``
と\ ``W2`` は両方とも\ ``T1`` で発生しますが、変更\ ``W1`` は\ ``T2``
まで\ ``N2`` に適用されません。

PostgreSQL
PITRは、単一のマスターからCOMMITオーダーで変更が到着するという想定に基づいて設計されています。したがって、PITRは、特定の時点PITに到達するまで変更をスキャンすることにより可能です。このスキームを使用すると、\ ``T1``
などの観点から1つのノードを単一のPITに復元できます。ただし、その状態には、その時間近くにコミットしたがノードにまだ到着していない他のノードからの他のデータは含まれません。その結果、回復は部分的に一貫していない、または少なくとも1つのレプリケーションオリジンのみについて一貫していると考えられる場合があります。

PostgreSQL PITRでは、標準の構文を使用できます。

.. code:: text

   recovery_target_time = T1

PGDでは、マルチプルのマスターからの変更が可能で、すべては1つのノードのWALログに記録され、レプリケーションオリジン識別子を使用して個別に識別されます。

PGDは、すべてまたは一部のレプリケーションオリジンの特定の時点へのPITRを可能にし、ノードのすべてのサブセットにわたって完全に一貫した観点を提供します。

したがって、マルチオリジンの場合、WALストリームを、複数のストリームがすべて1つの大きなストリームに混合されたものとして表示できます。
PITはまだ1つだけですが、それは各原点の異なるポイントとして個別に到達されます。

WALストリームは、要求されたオリジンがPITを見つけるまで読み取られます。
すべての変更は、その時点までに適用されますが、オリジンのPITに到達した後、トランザクションレコードはオリジンのコミット済みとしてマークされません。

WALには1つのLSN「停止ポイント」がありますが、シングルオリジンPITRの場合と同様に、単一のタイムスタンプが一貫して適用されます。

定義されたPITに到達すると、必要に応じてリカバリーを続行できるように新しいPITが設定される場合があります。

目的の停止ポイントに到達した後、復旧したサーバーが昇格する場合は、最初にシャットダウンします。
``pg_resetwal``
を使用して、このサーバーのタイムラインで使用されるよりも高いLSN値にLSNを移動します。このアプローチでは、論理デコードによって生成される重複LSNがないことが保証されます。

この特定の例では、\ ``N1`` は\ ``T1``
に復元されます。また、後まで\ ``N1`` に適用されなかった場合でも、 ``T1``
によってコミットされた他のノードからの変更も含まれます。

マルチオリジンPITRを要求するには、 ``postgresql.conf``
ファイルの標準の構文を使用します。

.. code:: text

   recovery_target_time = T1

2つの方法のいずれかで\ ``T1``
に復元されるレプリケーションオリジンのリストを指定する必要があります。
新しいパラメーター\ ``recovery_target_origins``
を介して別の\ ``multi_recovery.conf`` ファイルを使用できます。

.. code:: text

   recovery_target_origins = *

または、オリジンのサブセットを\ ``recovery_target_origins``
のリストとして指定できます。

.. code:: text

   recovery_target_origins = 1,3

指定された\ ``recovery_target_time``
へのローカルWALアクティビティのリカバリーは、常に暗黙的に実行されます。
``recovery_target_origins`` で指定されていないオリジンの場合、
``recovery_target_origins``
で記載されているリストの目標が達成される時期に応じて、任意の時点でリカバリーを停止できます。

``multi_recovery.conf``
ファイルが存在しない場合、リカバリーは、COMMITオーダーで単一のマスターから到着する変更を想定して設計された元のPostgreSQL
PITR動作がデフォルトになります。

..  Note::
   この機能は、 EDB Postgres Extendedでのみ使用できます。 Barmanは`multi_recovery.conf` ファイルを作成しません。

モニタリング
------------

次のクエリーを使用して、復元プロセスの進行状況を確認します。

.. code:: sql

   SELECT pg_size_pretty(pg_database_size(bdrdb));

上記のクエリーは、復元ノードのデータベースサイズを示しています。復元が進行し、元のノードのサイズに近づくと、サイズが増加します。ただし、肥大化のため、論理リストアは常に元のサイズより少し小さくなります。

.. code:: sql

   SELECT * FROM bdr.node_replication_rates;

上記のクエリーは、レプリケーションの速度を示しています。ただし、進行状況情報は、大規模なトランザクションの場合、誤解を招く可能性があります。遅延と進行状況が階段状に表示されます。

.. code:: sql

   SELECT
     application_name,
     state,
     wait_event_type,
     wait_event,
     now() - state_change AS state_change_ago
   FROM
     pg_stat_activity
   WHERE
     application_name LIKE %pg_restore%;

上記のクエリーは、
pg_restoreが何をしているか、ブロックされているかどうか、待機しているものについて、または動作しているかどうか、およびステータスを継続的に変更することに関する情報を示します。

次のビューを確認して、レプリケーションスロット、蓄積された遅延、破損したレプリケーションなどの問題を確認します。

- pg_catalog.pg_stat_replication_slots

- pg_catalog.pg_replication_slots

- bdr.node_slots

また、 ``bdr.stat_subscription``
を使用して、各サブスクリプションの統計を表示します。たとえば、パラレル適用またはトランザクションストリームを確認します。
