Sequences

多くのアプリケーションでは、一意の代理IDをデータベースデータベースオブジェクトを使用してこれらを生成します。 PostgreSQLでは、これらは、CREATE SEQUENCEコマンドを使用して手動で作成され、nextval()ファンクション、serialおよびbigserialシーケンスまたはalternativeGENERATED BY DEFAULT AS IDENTITY列を呼び出して取得することができます。

ただし、 PostgreSQLの標準シーケンスはマルチノード対応ではなく、ローカルノードで一意の値のみを生成します。このようなシーケンスによって生成された一意のIDは、マルチマスターレプリケーションで競合とデータロス(INSERTsの破棄による)を引き起こすため、これは重要です。

BDRグローバルシーケンス

このため、 BDRは、グローバルシーケンスと呼ばれる、 BDRグループ全体のbigintまたはbigserialデータ型のシーケンスを使用して、一意のIDを生成するアプリケーション透過的な方法を提供します。

BDRグローバルシーケンスは、アプリケーションがデータベースを使用して、ほとんどの場合(必ずしもすべてではない)に機能する非同期分散システムで一意の合成キーを生成する簡単な方法を提供します。

BDRグローバルシーケンスを使用すると、挿入の競合の問題を回避できます。グローバルシーケンスを使用している列にPRIMARY KEYまたはUNIQUE制約を定義した場合、ノードが他のノードと同じ値を取得することはできません。 BDRがノード間の挿入を同期する場合、競合することはありません。

BDRグローバルシーケンスはPostgreSQLシーケンスを拡張するため、クラッシュセーフです。それらを使用するには、bdr_applicationロールが付与されている必要があります。

グローバルシーケンスにはさまざまなアルゴリズムがあります。

  • タイムシャードシーケンス

  • グローバルに割り当てられたレンジシーケンス

タイム制約シーケンスは、ノード間通信を必要としないアルゴリズムを使用して値を生成するため、より高速で堅牢であり、作成されたタイムスタンプを記録する便利なプロパティがあります。 64ビットのBIGINTデータ型で、19桁のレンジを生成しローカルが、これはJavascript整数型などの一部のホスト言語データ型では使用するには長すぎる場合があります。 BIGINTまたはINTEGERシーケンスに適したものにします。

グローバルシーケンスは、bdr.alter_sequence_set_kind()関数を使用して作成できます。このファンクションは標準のPostgreSQLシーケンスを取り、 BDRグローバルシーケンスとしてマークします。また、シーケンスを変換して標準のPostgreSQLシーケンスに戻すこともできシーケンス(以下を参照)。

BDRは構成変数bdr.default_sequence_kindも提供します。これは、CREATE SEQUENCEコマンドが実行されるとき、またはserial、bigserial orGENERATED BY DEFAULT AS IDENTITY列が作成されるときに作成されるシーケンスの種類を決定します。有効な設定は次のとおりです。

  • local(デフォルト)は、新しく作成されたことを意味します シーケンスは、標準のPostgreSQL (ローカル)シーケンスです。

  • 常にグローバルに割り当てられたレンジシーケンスを作成するgalloc。

  • timeshardは、BIGINTシーケンスのタイムシャーディングされたグローバルシーケンスを作成します。 INTEGERシーケンスで使用すると、エラーがスローされます。

bdr.sequencesビューには、個々のシーケンスの種類に関する情報が表示されます。

currval()およびlastval()は、すべてのタイプのグローバルシーケンスに対して正しく機能します。

タイムシャードシーケンス

タイムハードシーケンスによって生成されたIDは、標準のPostgreSQLシーケンスのように、データ挿入の大まかなオーダーを取得するために使用できるように、緩やかに時間順に並べられます。同じミリ秒以内に生成された値は、1つのノード上であってもオーダーが狂う可能性があります。緩やかな時間順序のプロパティは、それらがレンジパーティションキーとしての使用に適していることを意味します。

タイムシャードシーケンスは1つ以上のノードで機能し、ノード結合プロセスの完了後にノード間通信を必要としません。そのため、拡張ネットワークパーティションのリスクがあり、レプリケーションラグやノード間レイテンシの影響を受けない場合でも、引き続き使用できます。

Timeshardシーケンスは、標準シーケンスとは異なる方法で一意のIDを生成します。アルゴリズムは、シーケンス番号に3つのコンポーネントを使用します。シーケンスの最初のコンポーネントは、シーケンス番号生成時のタイムスタンプです。シーケンス番号の2番目のコンポーネントは、各BDRノードに割り当てられた一意のIDです。これにより、異なるノードからのIDは常に異なることが保証されます。最後に、3番目のコンポーネントは、ローカルシーケンス自分自身によって生成された番号です。

シーケンス番号に一意のノードIDを追加オーダーことで、競合がないことを保証できますが、順序付けの別の有用なプロパティを保持する必要もあります。タイムスタンプを最初に置くことでこれが保証されます。

タイムハードシーケンスには、いくつかの制限と注意事項が適用されます。

タイムシャードシーケンスは64ビット幅で、bigintまたはbigserialが必要です。生成される値は少なくとも19桁です。実用的な32ビットintegerバージョンはないため、serialsequencesでは使用できません。代わりにグローバルに割り当てられたレンジシーケンスを使用してください。

任意の与えられたシーケンスのためにanygivenノードでミリ秒あたりに生成された8192シーケンス値の制限があります。 1つのノード上の1つのシーケンスからミリ秒あたり8192を超えるシーケンスが生成される場合、生成された値はラップして衝突する可能性があります。パフォーマンス上の理由でチェックは行われません。各msのスタートに値は0にリセットされません。通常、衝突はINSERTまたはUPDATEでaUNIQUE制約違反になります。異なるノードで生成されたシーケンス値はノードIDを含むため、決して衝突することはないため、複製の競合を引き起こすことはできません。

実際には、これは無害です。他の作業が行われたり、行が挿入されたり、インデックスが更新されたりするため、値はこの制約を引き起こすほど速く生成されません。

おそらくもっと重要なのは、タイムスタンプコンポーネントが2050年に値を使い果たし、bigintと組み合わせて使用した場合、値は2033年に負の数にラップれることを意味します。この日付を超えてアプリケーションをデプロイする予定がある場合は、下記のUUID、KSUUID、およびその他のアプローチのいずれかを試すか、代わりにグローバルに割り当てられたレンジシーケンスを使用してください。

タイムハードシーケンスの入力として使用されるシーケンスのINCREMENTオプションは、事実上無視されます。これは、多くのオブジェクトリレーショナルマッパー(ORM)ツール、特にHibernateのように、 シーケンスキャッシングを行うアプリケーションに関連する可能性があります。アプリケーションがキャッシュされた値で何でもできるようになるまでに。

同様に、START、MINVALUE、MAXVALUE、およびCACHEの設定は、基になるシーケンスで変更される場合がありますが、そうすることには利点がありません。シーケンスの下位14ビットが使用され、残りは破棄されるため、値のレンジの制限は関数の結果に影響しません。同じ理由で、setval()はタイムハードシーケンスには役立ちません。

グローバルに割り当てられたレンジのシーケンス

グローバルに割り当てられたレンジ(またはgalloc)シーケンスは、各ノードに値の範囲(チャンク)を割り当てます。ローカルレンジが使い果たされると、他のノード間のコンセンサスによって新しいレンジがグローバルに割り当てられます。これは、キースペースを効率的に使用しますが、現在割り当てられているローカルレンジが使い果たされた場合、シーケンスジェネレータが進行するには、クラスター内のノードの大部分にローカルノードを接続する必要があります。

タイムハードシーケンスとは異なり、gallocシーケンスは、 PostgreSQLが提供するすべてのシーケンスデータ型(smallint、 整数 、bigint)をサポートします。つまり、gallocsequencesは、JavaScriptで整数を使用するなど、64ビットシーケンスが問題のある環境で使用できることを意味しスペース。

各投票によって割り当てられるレンジは、現在シーケンスが使用しているデータ型に基づいて事前に決定されています。

  • smallint-1000個の数字

  • 整数-1 000 000の数字

  • bigint-1000 000 000の数字

各ノードはseq_chunk_sizeの2つのチャンクを割り当てます。1つは現在の使用に加え、将来の使用のために予約たチャンクです。したがって、1つのノードから生成される値は単調に増加します。ただし、グローバルに表示すると、生成された値はまったく順序付けられません。これにより、b-treeインデックスへの影響によりパフォーマンスが低下する可能性があり、通常、生成された値はレンジパーティションキーとして有用ではないことを意味します。

gallocシーケンスの主な欠点は、割り当てられたレンジが使い果たされると、シーケンスジェネレータはノード間通信を必要とするローカルノードの次の範囲についてコンセンサスを要求する必要があることです。 BDRグループにアクセスできません。これは今後のリリースで回避される可能性があります。

CACHE、START、MINVALUE、およびMAXVALUEオプションはgallocシーケンスで正しく機能しますが、シーケンスをgalloc種類に変換する前にそれらを設定する必要があります。 INCREMENT BYオプションも正しく機能しますが、各シーケンスデータ型に割り当てられた上記の範囲以上のインクリメントを割り当てることはできません。setval()はgallocシーケンスのグローバル状態をリセットしないため、使用しないでください。

gallocシーケンスにはいくつかの制限が適用されます。 BDRは、特別なBDRカタログbdr.sequence_allocのgallocシーケンスを追跡します。このカタログは、gallocシーケンスに現在割り当てられているチャンクを追跡するために必要です。シーケンス名前と名前空間はこのカタログに保存されます。シーケンス名前/名前空間への変更はレプリケーションストリームで管理されるのに対し、シーケンスチャンクアロケーションはRaftで管理されるため、 BDRは現在、gallocsequencesの名前変更、別の名前空間への移動、またはgallocシーケンスを含む名前空間の名前変更をサポートしていません。ユーザは、アプリケーションスキーマの設計中にこの制限に留意する必要があります。

使用法

ローカルシーケンスをgallocに変換する前に、次の前提条件に注意する必要があります。

シーケンスの種類がgallocに変更されると、書き換えられ、ローカルシーケンスの定義されたスタート値からリスタートされます。これが稼動データベースの既存のシーケンスで発生する場合は、現在の値をクエリーし、スタート値を適切に設定する必要があります。このユースケースを支援するために、BDRはユーザーがファンクションbdr.alter_sequence_set_kind()を使用して開始値を渡すことを許可します。すでにオフセットを使用していて、マルチプルのノードから書き込みがある場合は、最大使用値を確認し、少なくとも次までシーケンスをリスタートする必要があります値。

-- determine highest sequence value across all nodes
SELECT max((x->'response'->0->>'nextval')::bigint)
   *   FROM json_array_elements(
   *       bdr.run_on_all_nodes(
   *           E'SELECT nextval(\'public.sequence\');'
   *           )::jsonb AS x;

-- turn into a galloc sequence
SELECT bdr.alter_sequence_set_kind('public.sequence'::regclass, 'galloc', $MAX+MARGIN);

ユーザーはシーケンスをロックできないため、 最大()値のクエリ中に操作を続行できるようにするには、$ MARGIN値を残す必要があります。

この例では、333,からシーケンスを開始し、クラスターに2つのノードがあるため、アロケーション4がいくつかあることがわかります。 nodeandとチャンクサイズごとに2は1000000で、整数シーケンスに関連しています。

SELECT * FROM bdr.sequence_alloc
   *   WHERE seqid = 'public.categories_category_seq'::regclass;
   *         seqid          | seq_chunk_size | seq_allocated_up_to | seq_nallocs |       seq_last_alloc
-------------------------+----------------+---------------------+-------------+-----------------------------
 categories_category_seq |        1000000 |             4000333 |           4 | 2020-05-21 20:02:15.957835+00
(1 row)

各ノードの特定のシーケンスに現在割り当てられている範囲を表示するには、次のクエリを使用します。

  • ノードNode1は、333から2000333までのレンジを使用しています。

SELECT last_value AS range_start, log_cnt AS range_end
   *   FROM categories_category_seq WHERE ctid = '(0,2)'; -- first range
 range_start | range_end
-------------+-----------
   *        334 |   1000333
(1 row)

SELECT last_value AS range_start, log_cnt AS range_end
   *   FROM categories_category_seq WHERE ctid = '(0,3)'; -- second range
 range_start | range_end
-------------+-----------
   *    1000334 |   2000333
(1 row)
  • ノードNode2は、2000004から4000003までのレンジを使用しています。

SELECT last_value AS range_start, log_cnt AS range_end
   *   FROM categories_category_seq WHERE ctid = '(0,2)'; -- first range
 range_start | range_end
-------------+-----------
   *    2000334 |   3000333
(1 row)

SELECT last_value AS range_start, log_cnt AS range_end
   *   FROM categories_category_seq WHERE ctid = '(0,3)'; -- second range
 range_start | range_end
-------------+-----------
   *    3000334 |   4000333

注単一のクエリー(WHERE ctid IN( ‘(0,2)’、 ‘(0,3)’)など)に結合することはできません。最初のレンジのみが表示されるためです。

ノードがチャンクを終了すると、コンセンサスに新しいものを要求し、最初に利用可能になります。この場合、4000334から5000333になります。これが新しい予約チャンクになり、古い予約チャンクの消費をスタートします。

UUID、KSUUID、およびその他のアプローチ

BDRで使用できるglobalsequencesを使用せずにグローバルに一意のIDを生成する方法は他にもあります。例:

  • UUID、およびそのBDRバリアント、KSUUID

  • ノードごとに異なるオフセットを持つローカルシーケンス(マニュアル)

  • 外部で調整された自然キー

BDRアプリケーションは他の方法を安全に使用できないことにノートしてください:BDRはノード間の行ロックを取得しないため、SELECT ... FOR UPDATE、UPDATE ... RETURNING ...などのシーケンス生成に依存するカウンターテーブルベースのアプローチはBDRでは正しく動作しません。同じ値が複数のノードで生成されます。同じ理由で、「ギャップレス」シーケンス生成の通常の戦略はBDRでは機能しません。ほとんどの場合、アプリケーションは、 二相コミットを使用して外部ソースからギャップのないシーケンスの生成を調整するか、 BDRグループの1つのノードでのみ生成する必要があります。

UUIDとKSUUID

代わりに、UUIDキーはシーケンスを完全に回避し、128ビットのユニバーサル一意識別子を使用します。これらは非常にラージランダム値または擬似ランダム値であり、同じ値を2回生成することはほぼ不可能です。 UUIDキーを使用する場合、ノードが継続的に通信する必要はありません。

信じられないほど衝突が発生したイベント、競合検出は、保持する2つの挿入されたレコードの新しい方を選択します。競合ロギングは、有効になっている場合、そのようなイベントをレコードしますが、約2^64キーが生成された後にのみ衝突が実際に発生する可能性があるため、例外的に発生することはほとんどありません。

UUIDキーの主なマイナス面は、それらがややスペースがあることです-ネットワークが非効率的であり、プライマリキーとしてだけでなく、外部キーで被参照される場合や、ワイヤで送信される場合により多くのスペースを消費します。 。

BDRは、KSUUIDと呼ばれるUUIDデータのK-Sortableバリアントを操作するための関数を提供します。これは、 PostgreSQLの標準のUUIDデータタイプを使用して保存できる値を生成します。 KSUUIDの値は、UUID標準に従って、タイムスタンプとランダムデータの両方を格納するという点でUUIDv1と似ています。違いは、KSUUIDがK- タイムスタンプであるということです。これにより、よりコンパクトなbtreeインデックスが生成され、検索の効率が向上し、結果データの自然な時間ソートが可能になるため、データベースキーとしてより便利になります。 UUIDv1とは異なり、KSUUIDの値にはそれらが生成されたコンピュータのMACが含まれないため、KSUUIDsを使用してもセキュリティ上の問題はありません。

KSUUID v2がすべての場合に推奨されるようになりました。生成された値は、通常の比較演算子で直接ソートできます。

BDRにはKSUUIDの2つのバージョン、v1およびv2があります。従来のKSUUID v1は非推奨になりましたが、既存のインストールをサポートするオーダーに保持されており、新規インストールには使用しないでください。v1とv2の内部コンテンツには互換性がないため、それらを操作する機能にも互換性がありません。 KSUUIDのv2もUUIDバージョン番号を格納しなくなりました。

ステップとオフセットのシーケンス

オフセットステップシーケンスでは、各ノードで通常のPostgreSQLシーケンスが使用されます。各シーケンスは同じ量だけ増加し、異なるオフセットで開始されます。例、ステップ1000では、node1のシーケンスは1001、2001、3001などを生成し、node2は1002を生成します、2002、3002など。このスキームは、ノードが拡張通信できない場合でもうまく機能しますが、設計者はスキーマを確立するときにノードの最大数を指定する必要があり、ノードごとの構成が必要です。ただし、間違いは簡単に重複するシーケンスにつながる可能性があります。

次のように、1つのノードで目的のシーケンスを作成することにより、このアプローチをBDRで構成するのは比較的シンプルです。

CREATE TABLE some_table (
   *   generated_value bigint primary key
);

CREATE SEQUENCE some_seq INCREMENT 1000 OWNED BY some_table.generated_value;

ALTER TABLE some_table ALTER COLUMN generated_value SET DEFAULT nextval('some_seq');

…次に、各ノードでsetval()を呼び出して、各ノードに異なるオフセット開始値を与えます。例:

-- On node 1
SELECT setval('some_seq', 1);

-- On node 2
SELECT setval('some_seq', 2);
   * -- ... etc

将来的にノードを変更することは困難で混乱を招くため、追加したいすべてのノード用のスペースを確保するのに十分なラージさのINCREMENTを許可する必要があります。

bigintの値を使用する場合、10000以上のオフセットを使用しても、キーの枯渇に関する実際的な懸念はありません。消耗に近づく可能性を得るには、数百台のマシンで毎秒数百万回の挿入を行う何百年も必要です。

BDRは現在、このようなステップ/オフセットシーケンスでのノードごとのオフセットの構成の自動化を提供していません。

複合キー

ステップ/オフセットシーケンスのバリアントは、PRIMARY KEY (node_number, generated_value)で構成される複合キーを使用することです。通常、ノード番号は、各ノードで異なる番号を返すファンクションから取得されます。このようなファンクションは、一時的にDDLレプリケーションを無効にして定数SQLファンクションを作成するか、レプリケーションセットのパートではない1行のテーブルを使用して各ノードに異なる値を格納することで作成できます。

グローバルシークエンス管理インターフェイス

BDRは、標準のPostgreSQLシーケンスとBDRグローバルシーケンスを変換するためのインタフェースを提供します。

次の関数はDDLと見なされるため、 DDLレプリケーションとグローバルロックが適用されることに注意してください。

bdr.alter_sequence_set_kind

シーケンスの所有者は、シーケンスの種類を設定できシーケンス。一度設定すると、seqkindはbdr.sequencesビューでのみ可視されます。他のすべての方法では、シーケンスは通常のシーケンスとして表示されます。

BDRはこのファンクションをDDLとして扱うため、 DDLレプリケーションとグローバルロックが適用されます(現在アクティブな場合)。 [DDL Replication]を参照してください。

あらすじ

bdr.alter_sequence_set_kind(seqoid regclass, seqkind text)

パラメーター

  • seqoid-変更するシーケンスの名前またはOID

  • seqkind-標準のPostgreSQLシーケンスの場合はlocal、 BDRの場合はtimeshard 「タイムアンドシャーディング」ベースのアルゴリズムを使用するグローバルシーケンス [ BDR Global Sequences]セクション、またはグローバルに割り当てられたレンジのgalloc ノード間のコンセンサスを使用して、一意の範囲を割り当てるシーケンス 各ノードのシーケンス番号

注

シーケンスの種類をgallocに変更すると、そのシーケンスに最初に割り当てられたレンジは、シーケンスのスタート値を開始点として使用します。シーケンスがgallocに変更される前にすでに使用されている既存の値がある場合、次のコマンドを使用して、新しく生成された値が既存の値と競合しないように開始点を移動することをお勧めします。

ALTER SEQUENCE seq_name START starting_value RESTART

このファンクションは、DDLステートメントと同じレプリケーションメカニズムを使用します。つまり、レプリケーションはddl filters構成の影響を受けます。

このファンクションは、グローバルDDLロックを取得します。また、シーケンスをローカルでロックします。

このファンクションはトランザクションです-トランザクションのROLLBACKで効果をロールバックでき、変更は現在のトランザクションに可視されます。

bdr.backwards_compatibility issetが30618以下に設定されていない限り、bdr.alter_sequence_set_kindファンクションはシーケンスの所有者のみが実行できます。

bdr.extract_timestamp_from_timeshard

このファンクションは、timeshardシーケンスのタイムスタンプコンポーネントを抽出します。結果値のタイプは「timestamptz」です。

あらすじ

bdr.extract_timestamp_from_timeshard(timeshard_seq bigint)

パラメーター

  • timeshard_seq-タイムハードシーケンスの値

注

このファンクションは、ローカルノードでのみ実行されます。

bdr.extract_nodeid_from_timeshard

このファンクションは、timeshardシーケンスのnodeidコンポーネントを抽出します。

あらすじ

bdr.extract_nodeid_from_timeshard(timeshard_seq bigint)

パラメーター

  • timeshard_seq-タイムハードシーケンスの値

注

このファンクションは、ローカルノードでのみ実行されます。

bdr.extract_localseqid_from_timeshard

このファンクションは、timeshardシーケンスのローカルシーケンス値コンポーネントを抽出します。

あらすじ

bdr.extract_localseqid_from_timeshard(timeshard_seq bigint)

パラメーター

  • timeshard_seq-タイムハードシーケンスの値

注

このファンクションは、ローカルノードでのみ実行されます。

bdr.timestamp_to_timeshard

このファンクションは、タイムスタンプ値をダミーのタイムハードシーケンス値に変換します。

これは、インデックス検索またはtimeshard列の値の比較や特定のタイムスタンプに役立ちます。

例、timeshardsequenceを使用している列idを持つテーブルfooを指定すると、次のように昨日の真夜中以降の変更の数を取得できます。

SELECT count(1) FROM foo WHERE id > bdr.timestamp_to_timeshard('yesterday')

この方法で定式化されたクエリーは、id列のインデックススキャンを使用します。

あらすじ

bdr.timestamp_to_timeshard(ts timestamptz)

パラメーター

  • ts-タイムハードシーケンス生成に使用されるタイムスタンプ

注

このファンクションはローカルノードでのみ実行されます。

KSUUID v2Functions

KSUUID v2データ、K-Sortable UUIDデータを操作するためのFunctions。

bdr.gen_ksuuid_v2

このファンクションは、引数として渡されたタイムスタンプの値またはNULLが渡された場合は現在のシステム時刻を使用して、新しいKSUUID v2値を生成します。システム時刻を使用してKSUUIDを自動的に生成する場合は、NULL引数を渡します。

結果値のタイプは「UUID」です。

あらすじ

bdr.gen_ksuuid_v2(timestamptz)

注

このファンクションは、ローカルノードでのみ実行されます。

bdr.ksuuid_v2_cmp

このファンクションは、KSUUID v2値を比較します。

最初の値がより新しい場合は1、2番目の値がより低い場合は-1、等しい場合は0を返します。

あらすじ

bdr.ksuuid_v2_cmp(uuid, uuid)

パラメーター

  • UUID-比較するKSUUID v2

注

このファンクションはローカルノードでのみ実行されます。

bdr.extract_timestamp_from_ksuuid_v2

このファンクションは、KSUUID v2のタイムスタンプコンポーネントを抽出します。結果値のタイプは「timestamptz」です。

あらすじ

bdr.extract_timestamp_from_ksuuid_v2(uuid)

パラメーター

  • UUID-タイムスタンプを抽出するKSUUID v2値

注

このファンクションは、ローカルノードでのみ実行されます。

KSUUID v1Functions

KSUUID v1データ、K-Sortable UUIDデータ(v1)を操作するためのFunctions。

bdr.gen_ksuuid

このファンクションは、現在のシステム時刻を使用して、新しいKSUUID v1値を生成します。結果値の型は「UUID」です。

あらすじ

bdr.gen_ksuuid()

注

このファンクションは、ローカルノードでのみ実行されます。

bdr.uuid_v1_cmp

このファンクションは、KSUUID v1の値を比較します。

最初の値がより新しい場合は1、2番目の値がより低い場合は-1、等しい場合は0を返します。

あらすじ

bdr.uuid_v1_cmp(uuid, uuid)

注

このファンクションは、ローカルノードでのみ実行されます。

パラメーター

  • UUID-比較するKSUUID v1

bdr.extract_timestamp_from_ksuuid

このファンクションは、KSUUID v1またはUUIDv1値のタイムスタンプコンポーネントを抽出します。結果値のタイプは「timestamptz」です。

あらすじ

bdr.extract_timestamp_from_ksuuid(uuid)

パラメーター

  • UUID-タイムスタンプを抽出するKSUUID v1値

注

このファンクションは、ローカルノードでのみ実行されます。