2

特定のドメインレベルのロジックに応じて、特定のテーブル (約 200) の特定のレコードのみをレプリケートする小さなアドホック レプリケーション フレームワーク (小売企業向け) を開発しています。

各ターゲット ホストの各レコードのレプリケーション ステータスを知るために、repStatus 文字(NUMBER_OF_HOSTS) 型の列があります。ホストは常に同じ位置を表します。

この列の各位置の値は、0 (アクションなし)、1 (レプリケート レコード)、2 (レプリケートされたレコード) 、3 (確認後に再送信)、A ( 1 回目のエラー)、B ( 2 回目のエラー) のいずれかです。など

たとえば、012Aは次のことを意味します。

  • ホスト 1 には何も送信しない
  • このレコードをホスト 2 に送信します
  • ホスト 3 で正常に受信されたレコード
  • ホスト 3 から受信したエラー

これは非常に簡単でシンプルに見えますが、「単純な読み方」があります。レコードのステータスを知るために、repStatus列を読むだけです。

ただし、アプリケーションがレプリケートするターゲット レコードを探す必要がある場合、このアプローチはパフォーマンスの問題につながるようです。

したがって、この問題をパフォーマンス的に解決するためのより良い設計があると確信しています。おそらく、テーブル、レコード、およびホストを参照する追加のテーブルが解決策になる可能性があります。

CREATE TABLE repStatus (tableID int, recordID int, targetHostID int, status int);

ステータス値新しいテーブルに正規化することもできます。ただし、200 テーブル * テーブルあたり ~500000 レコードは、1 つのテーブルで処理するにはかなりの数の行になる可能性があります。

経験に基づく代替案は大歓迎です。

4

2 に答える 2

2

したがって、典型的なクエリは、ホスト x にレプリケートされるすべてのレコードを取得することです...この特定のターゲットのrepStatus 1または3を持ちます。(それは問題ではないので、仮定を立てます。)

通常、ほとんどのレコードはすでに複製されているため、レプリケートされるレコードはまれなケースですよね? (より多くの仮定。)

式の部分インデックスは、非常に迅速な解決策になる場合があります。

各行にテキスト文字列を追加する設計を維持する場合は、次の行に沿って各ターゲットの部分インデックスを作成できます。

CREATE INDEX tbl_rep_part1_idx ON tbl (tbl_id, substr(repstatus,1,1))
WHERE substr(repstatus,1,1) = '1' OR
      substr(repstatus,1,1) = '3';


CREATE INDEX tbl_rep_part2_idx ON tbl (tbl_id, substr(repstatus,2,1))
WHERE substr(repstatus,2,1) OR
      substr(repstatus,2,1);

...

すべての部分インデックスの合計は、インデックスごとのオーバーヘッドにより、完全なインデックスよりも大きくなります。テーブルへの書き込み操作では、影響を受ける部分インデックスのみを更新する必要があります。

これらのクエリは非常に高速になります。

SELECT * FROM tbl WHERE substr(repstatus,1,1) = '1';

SELECT * FROM tbl WHERE substr(repstatus,1,1) = '1' OR
                        substr(repstatus,1,1) = '3';

インデックスへの追加tbl_idはオプションです。これを追加したのは、4 バイトの整数列を追加すると、パディングで失われたスペースを利用できるためです (インデックス サイズは大きくなりません)。使用する場合にのみ、それ (または別の小さな列) を含めます。

テキスト配列 + GIN インデックスと比較して何が期待できますか?

私の仮定が成り立つ場合にのみ、全体のアイデアが適用されます。テキスト配列 + GIN インデックスを介してこのルートを提案する理由は 3 つあります。

  1. はるかに小さい列サイズ。比較:

    SELECT pg_column_size('{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z}'::text[])  -- 232 byte
          ,pg_column_size('{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z}'::"char"[])  -- 50 byte
          ,pg_column_size('ABCDEFGHIJKLMNOPQRSTUVWXYZ'::text);  -- 27 byte
    

    これは、200 x 500k 行で問題になります。多くの。

  2. クエリあたりのインデックスがはるかに小さくなり、アクセスが高速になります。
    部分インデックスの合計は、1 つの完全な GIN インデックスよりも多少多くなりますが、テーブル全体をカバーしたい場合は、まれなケースのみをカバーする必要がある場合はそうではありませいずれにしても、クエリごとに必要なインデックスははるかに小さくなります。また、インデックスのサイズを考えると、インデックスがキャッシュされるとは思いません。これにより、この点がさらに強調されます

  3. 安価な書き込み操作。GIN はこの領域で問題があることで知られているため、単純で小さな B ツリー インデックスの更新は、小さな部分インデックスに対して大幅に高速になると予想しています。ただし、これは検証する必要があります。

于 2012-11-02T16:06:09.667 に答える
2

私が最初に行うことは、厄介な文字列解析をどこからでも削除し、PostgreSQL のネイティブ型に置き換えることです。現在のソリューションと同様に、各レコードのレプリケーション ステータスを保存するには:

CREATE TYPE replication_status AS ENUM (
  'no_action',
  'replicate_record',
  'record_replicated',
  'error_1',
  'error_2',
  'error_3'
  );
ALTER TABLE t ADD COLUMN rep_status_array replication_status[];

これにより、ストレージ容量が少し増えます。enum 値は 1 ではなく 4 バイトであり、配列にはオーバーヘッドがあります。ただし、概念を隠すのではなくデータベースに教えると、次のように記述できます。

-- find all records that need to be replicated to host 4
SELECT * FROM t WHERE rep_status_array[4] = 'replicate_record';

-- find all records that contain any error status
SELECT * FROM t WHERE rep_status_array &&
  ARRAY['error_1', 'error_2', 'error_3']::replication_status[];

rep_status_arrayそれがユースケースに役立つ場合は、GIN インデックスを適切に配置できますが、クエリを調べて、使用するもの専用のインデックスを作成することをお勧めします。

CREATE INDEX t_replication_host_4_key ON t ((rep_status_array[4]));
CREATE INDEX t_replication_error_key ON t (id)
  WHERE rep_status_array && ARRAY['error_1', 'error_2', 'error_3']::replication_status[];

とはいえ、200 個のテーブルがある場合、これを 1 つのレプリケーション ステータス テーブルに分割したくなるでしょう。残りのレプリケーション ロジックがどのように機能するかに応じて、ステータスの配列を含む 1 つの行またはホストごとに 1 つの行のいずれかです。私はまだその列挙を使用します:

CREATE TABLE adhoc_replication (
  record_id bigint not null,
  table_oid oid not null,
  host_id integer not null,
  replication_status status not null default 'no_action',
  primary key (record_id,table_oid,host_id)
  );

PostgreSQL は内部的に各テーブルに OID ( try SELECT *, tableoid FROM t LIMIT 1) を割り当てます。これは、単一のデータベース システム内の便利で安定した数値識別子です。別の言い方をすれば、テーブルが削除されて再作成されると変更されます (たとえば、データベースをダンプして復元すると発生する可能性があります)。これと同じ理由で、開発と運用で異なる可能性が非常に高くなります。テーブルを追加または名前変更するときに、中断と引き換えにこれらの状況を機能させたい場合は、OID の代わりに列挙を使用します。

すべてのレプリケーションに単一のテーブルを使用すると、トリガーやクエリなどを簡単に再利用でき、ほとんどのレプリケーション ロジックをレプリケートしているデータから切り離すことができます。また、単一のインデックスを参照することで、すべての元のテーブルで特定のホストのステータスに基づいてクエリを実行できます。これは重要な場合があります。

テーブルのサイズに関しては、PostgreSQL は間違いなく同じテーブルで 1000 万行を処理できます。専用のレプリケーション関連テーブルを使用した場合は、常にホストごとに分割できます。(テーブルごとのパーティション分割は、私にはほとんど意味がありません。上流の行ごとにレプリケーション ステータスを格納するよりも悪いように思えます。) どの方法でパーティション分割するか、またはそれが適切かどうかは、データベースにどのような質問をするかによって完全に異なります。ベース テーブルで発生するアクティビティの種類。(パーティショニングとは、いくつかの大きな BLOB ではなく、多数の小さな BLOB を維持することを意味し、1 つの操作を実行するために多くの小さな BLOB にアクセスする可能性があります。) ディスク シークをいつ実行するかは、実際には選択の問題です。

于 2012-11-02T17:06:09.633 に答える