Workarounds for DDL restrictions#

PGD DDLオペレーション処理の制限の一部は回避できます。多くの場合、操作をより小さな変更に分割すると、単一のステートメントとして許可されないか、過剰なロックが必要な目的の結果が生成される場合があります。

コンストレインの追加#

DMLロックを必要とせずにCHECK およびFOREIGN KEY 制約を追加できます。これには、2段階のプロセスが含まれます。

  • ALTER TABLE ... ADD CONSTRAINT ... NOT VALID

  • ALTER TABLE ... VALIDATE CONSTRAINT

2つの異なるトランザクションでこれらの手順を実行します。これらの手順は両方とも、テーブルでのみDDLロックを取得するため、1つ以上のノードがダウンしている場合でも実行できます。ただし、コンストレインを検証するには、PGDは次のことを確認する必要があります。

  • クラスター内のすべてのノードはADD CONSTRAINT コマンドを参照します。

  • コンストレインを検証するノードは、これらのノードにNOT VALIDコンストレインを作成する前に、他のすべてのノードからレプリケーション変更を適用します。

したがって、新しいメカニズムでは、制約の検証中にすべてのノードがアップしている必要はありませんが、すべてのノードがALTER TABLE .. ADD CONSTRAINT ... NOT VALID コマンドを適用し、十分に進行していることは必要です。 PGDは、コンストレインを検証する前に、一貫した状態に到達するのを待機します。

新しい機能では、クラスターがRaftプロトコルバージョン24以降で実行されている必要があります。 Raftプロトコルがまだアップグレードされていない場合、古いメカニズムが使用され、DMLロック要求が発生します。

列の追加#

揮発性のデフォルトを使用して列を追加するには、別のトランザクションで次のコマンドを実行します。

ALTER TABLE mytable ADD COLUMN newcolumn coltype; -- Note the lack of DEFAULT or NOT NULL

ALTER TABLE mytable ALTER COLUMN newcolumn DEFAULT volatile-expression;

BEGIN;
SELECT bdr.global_lock_table(mytable);
UPDATE mytable SET newcolumn = default-expression;
COMMIT;

このアプローチでは、スキーマの変更と行の変更をPGDが実行できる別のトランザクションに分割し、PGDグループ内のすべてのノードで一貫したデータが得られます。

最良の結果を得るには、更新をチャンクにバッチ処理して、一度に数万〜数十万行を更新しないようにします。これは、埋め込みトランザクションでPROCEDURE を使用して行うことができます。

変更の最後のバッチは、テーブルのグローバルDMLロックを取得するトランザクションで実行する必要があります。そうしないと、他のノードのテーブルに同時に挿入される行を見逃す可能性があります。

必要に応じて、UPDATE が完了した後、ALTER TABLE mytable ALTER COLUMN newcolumn NOT NULL; を実行できます。

カラムのタイプの変更#

列のタイプを変更すると、PostgreSQLがテーブルを書き換える場合があります。ただし、場合によっては、この書き換えを回避できる場合があります。例

CREATE TABLE foo (id BIGINT PRIMARY KEY, description VARCHAR(128));
ALTER TABLE foo ALTER COLUMN description TYPE VARCHAR(20);

制限をデータ型の変更ではなくテーブル制約にすることにより、テーブルのリライトを回避するようにこのステートメントをリライトできます。必要に応じて、後続のコマンドでコンストレインを検証して、長いロックを回避できます。

CREATE TABLE foo (id BIGINT PRIMARY KEY, description VARCHAR(128));
ALTER TABLE foo
  ALTER COLUMN description TYPE varchar,
  ADD CONSTRAINT description_length_limit CHECK (length(description) <= 20) NOT VALID;
ALTER TABLE foo VALIDATE CONSTRAINT description_length_limit;

検証が失敗した場合は、失敗した行のみをUPDATE できます。このテクニックは、 length() を使用するか、 scale() を使用してNUMERIC データ型を使用するTEXT およびVARCHAR に使用できます。

列タイプを変更する一般的な場合、最初に目的のタイプの列を追加します。

ALTER TABLE mytable ADD COLUMN newcolumn newtype;

BEFORE INSERT OR UPDATE ON mytable FOR EACH ROW .. として定義されたトリガーを作成します。これは、テーブルへの新しい書き込みによって新しい列が自動的に更新されるように、NEW.newcolumn をNEW.oldcolumn に割り当てます。

UPDATE 埋め込みトランザクションでPROCEDURE を使用して、oldcolumn の値をnewcolumn にコピーするバッチ内のテーブル。作業をバッチ処理することは、大きなテーブルの場合、レプリケーションラグを削減するのに役立ちます。 IDの範囲または任意の方法で更新しても問題ありません。または、小さなテーブルの場合、1パスでテーブル全体を更新できます。

CREATE INDEX ... 新しい列に必要なインデックス。ロック期間を短縮するために、各ノードでDDLレプリケーションを使用せずに個別に実行されるCREATE INDEX ... CONCURRENTLY を使用するのが安全です。

ALTER 必要に応じて、NOT NULL およびCHECK 制約を追加する列。

  1. BEGIN トランザクション。

  2. DROP 追加したトリガー。

  3. ALTER TABLE 列に必要なDEFAULT を追加します。

  4. DROP 古い列。

  5. ALTER TABLE mytable RENAME COLUMN newcolumn TO oldcolumn .

  6. COMMIT .

注釈

列を削除するため、テーブルに依存するビュー、プロシージャーなどを再作成する必要がある場合があります。列を削除する場合は、それを参照したすべてを再作成する必要があるため、注意してください。

他のタイプの変更#

ALTER TYPE ステートメントはレプリケートされますが、影響を受けるテーブルはロックされていません。

このDDLを使用する場合、新しいタイプを使用する前に、すべてのノードでステートメントが正常に実行されたことを確認してください。これは、

bdr.wait_slot_confirm_lsn() ファンクションを使用して実現できます。

この例では、DMLステートメントで新しい値を使用する前に、 DDLがすべてのノードに書き込まれるようにします。

ALTER TYPE contact_method ADD VALUE email;
SELECT bdr.wait_slot_confirm_lsn(NULL, NULL);