ロジカルデコーディングは、データベースのテーブルへの恒久的な更新を、一貫性があって、データベース内部の状態に関する詳細な知識がなくても容易に理解できる形式として取得するプロセスです。
PostgreSQLにおいてロジカルデコーディングは、記憶装置のレベルで更新を記述する先行書き込みログ(WAL)の内容を、タプルやSQL文のストリームといったアプリケーション固有の形式にデコードすることによって実装されています。
論理レプリケーションの文脈ではスロットは、元のサーバで行われた変更と同じ順序でクライアント上でリプレイできるようなストリームを表します。 それぞれのスロットは、単一のデータベース上の変更操作の連鎖をストリームとして流します。
またPostgreSQLには、ストリーミングレプリケーションスロットがあります (26.2.5参照)。しかし、ここでの説明とは少し違う使い方がされています。
それぞれのレプリケーションスロットはPostgreSQLクラスタの中で一意な識別子を持っています。 スロットは、そのために使用される接続とは独立しており、クラッシュセーフです。
ロジカルスロットは、通常の操作においては、各々の変更操作を一度だけ送出します。 それぞれのスロットにおける現在位置は、チェックポイントのときにだけ永続的になります。 ですからクラッシュすると、スロットは以前のLSNに戻ってしまうかもしれませんし、サーバの再起動時には最近の変更が再送されることになります。 ロジカルデコーディングのクライアントは、同じメッセージを複数回扱うことによる好ましくない結果を避けることに対して責任を追っています。 クライアントはデコーディングの際に最後に確認したLSNを記録し、繰り返されるデータをスキップしたり、(レプリケーションプロトコルを使う場合に)サーバに開始時点を決めさせるのではなく、記録しておいたLSNからデコーディングを始めるように要求するかもしれません。 レプリケーション進捗追跡機能はこの目的のために設計されています。 replication originsを参照してください。
単一のデータベース中に、お互いに独立した複数のスロットが存在しても構いません。 それぞれのスロットは自分自身の状態を持っており、データベース更新のストリーム上の別の場所から変更データを受信する異なる消費者があり得ます。 多くのアプリケーションにとっては、各消費者に対して個別のスロットが必要となるでしょう。
論理レプリケーションスロットは、受信者の状態については関知しません。 同時にでなければ、同じスロットを使う複数の異なる受信者を持つことさえできます。 その場合は、直近の受信者がストリームの消費を終了した時点から更新データを受信するだけです。 どの時点でも1つのスロットからの変更を消費できるのは1つの受信側だけです。
論理レプリケーションスロットは、ホットスタンバイ上でも作成できます。
システムカタログから必要な行をVACUUMが削除するのを防ぐためには、スタンバイ上でhot_standby_feedbackを設定する必要があります。
それでも、必要な行が削除されると、スロットは無効になります。
プライマリとスタンバイの間に物理スロットを使用することを強くお勧めします。
そうしないと、hot_standby_feedbackが動作するのは接続が生きている間だけです(たとえばノードの再起動で破壊されます)。
その場合、プライマリはスタンバイ上のロジカルデコーディングが必要とするシステムカタログ行を削除するかもしれません(スタンバイ上のcatalog_xminについては知らないため)。
既存のスタンバイ上のロジカルスロットも、プライマリ上のwal_levelがlogicalよりも小さくなると無効になります。
これはスタンバイがWALストリームでそのような変更を検出したときにすぐに行われます。
これは、遅れているwalsender(もしあれば)に対して、プライマリでのwal_levelパラメータの変更までの一部のWALレコードがデコードされないことを意味します。
ロジカルスロットの作成には、現在実行中のすべてのトランザクションに関する情報が必要です。
プライマリではこの情報は直接利用できますが、スタンバイではこの情報をプライマリから取得する必要があります。
したがって、スロットの作成はプライマリで何らかのアクティビティが発生するのを待つ必要があるかもしれません。
プライマリがアイドル状態の場合、スタンバイ上でのロジカルスロットの作成にはかなりの時間がかかるかもしれません。
これは、プライマリでpg_log_standby_snapshot関数を呼び出すことで高速化できます。
レプリケーションスロットは、クラッシュをまたがって永続し、消費者の状態については関知しません。
スロットを使う接続がない場合でも、消費者が必要としているリソースが削除されることを防ぎます。
これによりストレージが消費されます。何故ならば、関連するWALもシステムカタログの行も、レプリケーションスロットが必要とする限りVACUUMによって削除されないからです。
極端な場合、トランザクションIDの周回(24.1.5を参照)を防ぐためのデータベース停止をもたらす可能性があります。
したがって、必要でなくなったスロットは削除すべきです。
プライマリ上の論理レプリケーションスロットは、ホットスタンバイと同期させることができます。
これは、スロットの作成時にpg_create_logical_replication_slotのfailoverパラメータを指定するか、CREATE SUBSCRIPTIONのfailoverオプションを指定することで可能です。
さらに、スタンバイでsync_replication_slotsを有効にする必要があります。
スタンバイでsync_replication_slotsを有効にすると、フェイルオーバースロットをスロット同期ワーカーによって定期的に同期させることができます。
同期を機能させるには、プライマリとスタンバイの間に物理レプリケーションスロットが必要であり(つまり、スタンバイでprimary_slot_nameが設定されている必要があります)、スタンバイではhot_standby_feedbackが有効になっている必要があります。
また、primary_conninfoに有効なdbnameを指定する必要があります。
この物理レプリケーションスロットを、プライマリのsynchronized_standby_slotsリスト内に指定することを強くお勧めします。
これは、サブスクライバーがホットスタンバイよりも早く変更を消費しないようにするためです。
正しく設定されていても、synchronized_standby_slots内に指定されたスロットを待つため、サブスクライバーに変更を送信するときに多少の遅延が予想されます。
synchronized_standby_slotsが使用されている場合、synchronized_standby_slotsで指定された物理レプリケーションスロットに関連付けられた対応するスタンバイが、プライマリサーバ上の最新のフラッシュされた位置までWALを受信することを確認するまで、プライマリサーバは完全にはシャットダウンしません。
sync_replication_slotsを有効にすると、フェイルオーバースロットの定期的な自動同期が可能になりますが、スタンバイ上でpg_sync_replication_slots関数を使用して手動で同期することもできます。
ただし、この関数は主にテストとデバッグを目的としており、注意して使用する必要があります。
自動同期とは異なり、周期的な再試行が行われないため、同期が失敗しやすくなります。
特に、スロットに必要なWALファイルまたはカタログ行がすでに削除されているか、スタンバイ上で削除されている可能性がある初期同期のシナリオではその傾向が強くなります。
それとは対照的に、sync_replication_slotsを介した自動同期はスロットを継続的に更新するため、シームレスなフェイルオーバーと高可用性を実現します。
そのため、スロットの同期には自動同期を推奨します。
スロットの同期が推奨通りに設定され、初期同期が自動的に、またはpg_sync_replication_slotを介して手動で行われた場合、次の条件を満たした場合にのみ、スタンバイは同期されたスロットを永続化できます:プライマリ上の論理レプリケーションスロットが、スタンバイ上で必要なWALおよびシステムカタログ行を保持している必要があります。 これにより、データの整合性が確保され、昇格後も論理レプリケーションをスムーズに継続できます。 必要なWALまたはカタログ行がスタンバイからすでに削除されている場合、スロットはデータ損失を回避するために永続化されません。 このような場合、次のログメッセージが表示されることがあります。
LOG: could not synchronize replication slot "failover_slot" DETAIL: Synchronization could lead to data loss, because the remote slot needs WAL at LSN 0/3003F28 and catalog xmin 754, but the standby has LSN 0/3003F28 and catalog xmin 756.
論理レプリケーションスロットが消費者によって活発に使用されている場合、手作業による介入は必要ありません。
スロットは自動的に進み、同期は次のサイクルで再開されます。
ただし、消費者が設定されていない場合は、pg_logical_slot_get_changesまたはpg_logical_slot_get_binary_changesを使用してプライマリ上のスロットを手動で進め、同期を続行できるようにすることをお勧めします。
フェイルオーバー後に論理レプリケーションを再開できるかどうかは、フェイルオーバー時のスタンバイ上で同期されていたスロットの持つpg_replication_slots.syncedの値によって決まります。
フェイルオーバー前に、スタンバイで同期状態が真となっている永続スロットだけが、フェイルオーバー後の論理レプリケーションに使用できます。
同期されていた一時スロットはロジカルデコーディングには使用できないため、これらのスロットによる論理レプリケーションは再開できません。
例えば、サブスクリプション無効になっているため、同期されたスロットがスタンバイ上で永続化できなかった場合、フェイルオーバー後にサブスクリプションを有効にしても、そのサブスクリプションは再利用できません。
同期されたロジカルスロットからフェイルオーバーの後に論理レプリケーションを再開するには、サブスクリプションの'conninfo'オプションを新しいプライマリサーバを指すように変更する必要があります。
これはALTER SUBSCRIPTION ... CONNECTIONを使用して行います。
スタンバイが昇格する前に先にサブスクリプションを無効にし、接続文字列を変更した後に再度有効にすることをお勧めします。
昇格中に古いプライマリが再度起動する場合があります。 このときサブスクリプションが無効なっていない場合、サブスクライバーは昇格後も接続文字列が変更されるまで、古いプライマリサーバからデータを受信し続ける可能性があります。 これによりデータの不整合の問題が生じ、サブスクライバーが新しいプライマリからのレプリケーションを継続できなくなる可能性があります。
出力プラグインは、先行書き込みログ(WAL)の内部データ表現を、レプリケーションスロットの消費者が必要とする形式に変換します。
ストリーミングレプリケーションのインタフェースを使って新しいスロットを作ると(CREATE_REPLICATION_SLOT参照)、スナップショットがエクスポートされます(9.28.5参照)。
このスナップショットはまさにその時点でのデータベースの状態を示しており、スナップショット以後のすべての変更は更新ストリームに含まれるようになります。
このことを利用して、スロットが作られた際のデータベースの状態をSET TRANSACTION SNAPSHOTを使って読み込むことにより、新しいレプリカを作ることができます。
このトランザクションは、その時点のデータベースの状態をダンプするために使用することができます。
また、スロットに含まれるデータを使って、ダンプした後で行われた更新を失うことなくデータベースを更新できます。
スナップショットのエクスポートが必要ないアプリケーションは、SNAPSHOT 'nothing'オプションを使ってスナップショットのエクスポートを抑止できます。